Skip to content

Commit

Permalink
Merge pull request #379 from SquirrelCorporation/chores-better-onboar…
Browse files Browse the repository at this point in the history
…ding

[CHORE] Add no devices modal and update installation guides
  • Loading branch information
SquirrelDeveloper authored Oct 14, 2024
2 parents b5b47fb + 418c105 commit bd19f39
Show file tree
Hide file tree
Showing 16 changed files with 449 additions and 92 deletions.
2 changes: 1 addition & 1 deletion client/package.json
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
{
"name": "ssm-client",
"version": "0.1.20",
"version": "0.1.21",
"private": true,
"description": "SSM Client - A simple way to manage all your servers",
"author": "Squirrel Team",
Expand Down
6 changes: 6 additions & 0 deletions client/src/app.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -8,6 +8,7 @@ import DocumentationWidget from '@/components/HeaderComponents/DocumentationWidg
import { HealthWidget } from '@/components/HeaderComponents/HealthWidget';
import NotificationsWidget from '@/components/HeaderComponents/NotificationsWidget';
import UpdateAvailableWidget from '@/components/HeaderComponents/UpdateAvailableWidget';
import NoDeviceModal from '@/components/NoDevice/NoDeviceModal';
import { currentUser as queryCurrentUser, hasUser } from '@/services/rest/user';
import type { Settings as LayoutSettings } from '@ant-design/pro-components';
// @ts-ignore
Expand Down Expand Up @@ -123,8 +124,13 @@ export const layout: RunTimeLayoutConfig = ({
description={`The server version (${initialState?.currentUser?.settings?.server.version}) does not match the client version (${version}). You may need to retry a docker compose pull to update SSM.`}
type="warning"
showIcon
banner
/>
)}
{initialState?.currentUser?.devices?.overview &&
initialState?.currentUser?.devices?.overview?.length === 0 && (
<NoDeviceModal />
)}
{children}
</>
);
Expand Down
215 changes: 147 additions & 68 deletions client/src/components/DeviceConfiguration/CheckDeviceConnection.tsx
Original file line number Diff line number Diff line change
@@ -1,11 +1,18 @@
import TerminalHandler from '@/components/PlaybookExecutionModal/PlaybookExecutionHandler';
import SwitchConnexionMethod from '@/components/NewDeviceModal/SwitchConnexionMethod';
import TerminalHandler, {
TaskStatusTimelineType,
} from '@/components/PlaybookExecutionModal/PlaybookExecutionHandler';
import { getAnsibleSmartFailure } from '@/services/rest/ansible';
import {
CheckCircleOutlined,
ClockCircleOutlined,
CloseOutlined,
InfoCircleFilled,
LoadingOutlined,
SwitcherOutlined,
} from '@ant-design/icons';
import { Popover, Steps, Typography } from 'antd';
import { Alert, Button, Popover, Steps, Typography } from 'antd';
import { motion } from 'framer-motion';
import React, { useEffect, useRef, useState } from 'react';
import { API } from 'ssm-shared-lib';

Expand All @@ -15,16 +22,44 @@ export type CheckDeviceConnectionProps = {
dockerConnErrorMessage?: string;
};

const taskInit: TaskStatusTimelineType = {
_status: 'created',
status: 'finish',
icon: <ClockCircleOutlined />,
title: 'created',
};

const animationVariants = {
hidden: { opacity: 0, y: -20 },
visible: { opacity: 1, y: 0 },
};

const CheckDeviceConnection: React.FC<CheckDeviceConnectionProps> = (props) => {
const { execId, dockerConnRes, dockerConnErrorMessage } = props;
const timerIdRef = useRef();
const [isPollingEnabled, setIsPollingEnabled] = useState(false);
const [playbookStatus, setPlaybookStatus] = useState('running...');
const [dockerStatus, setDockerStatus] = useState('running...');
const [execRes, setExecRes] = useState(<></>);
const [smartFailure, setSmartFailure] = useState<
API.SmartFailure | undefined
>();
const statusesType: TaskStatusTimelineType[] = [taskInit];
const [savedStatuses, setSavedStatuses] = useState(statusesType);
const statusCallback = (status: string) => {
setPlaybookStatus(status);
};
const [count, setCount] = useState(0);

const isFinalStatusFailed = async () => {
if (savedStatuses?.find((status) => status._status === 'failed')) {
const res = await getAnsibleSmartFailure({ execId: execId });
if (res.data) {
setSmartFailure(res.data);
}
return true;
}
};
const execLogsCallBack = (execLog: API.ExecLog) => {
setExecRes((previous) => (
<>
Expand All @@ -43,8 +78,13 @@ const CheckDeviceConnection: React.FC<CheckDeviceConnectionProps> = (props) => {
undefined,
execLogsCallBack,
statusCallback,
setSavedStatuses,
);

useEffect(() => {
void isFinalStatusFailed();
}, [savedStatuses]);

useEffect(() => {
if (execId) {
terminalHandler.resetTerminal();
Expand All @@ -66,13 +106,17 @@ const CheckDeviceConnection: React.FC<CheckDeviceConnectionProps> = (props) => {
}, [dockerConnRes]);

useEffect(() => {
const pollingCallback = () => terminalHandler.pollingCallback(execId || '');
const pollingCallback = () => {
terminalHandler.pollingCallback(execId || '');
setCount((prevCount) => prevCount + 1);
};

const startPolling = () => {
// pollingCallback(); // To immediately start fetching data
setCount(0);
// Polling every 3 seconds
// @ts-ignore
timerIdRef.current = setInterval(pollingCallback, 3000);
setSmartFailure(undefined);
};

const stopPolling = () => {
Expand All @@ -90,72 +134,107 @@ const CheckDeviceConnection: React.FC<CheckDeviceConnectionProps> = (props) => {
};
}, [isPollingEnabled]);
return (
<Steps
direction="vertical"
items={[
{
title: 'Ansible Playbook : Ansible Ping & Call SSM API URL',
description: (
<>
{playbookStatus}{' '}
{playbookStatus === 'failed' && (
<Popover
title={'Ansible Connection Logs'}
content={
<div style={{ overflowY: 'scroll', height: '400px' }}>
{execRes}
</div>
}
overlayStyle={{
width: '400px',
height: '400px',
overflowY: 'scroll',
}}
>
<InfoCircleFilled />
</Popover>
)}
</>
),
icon:
playbookStatus === 'successful' ? (
<CheckCircleOutlined />
) : playbookStatus === 'failed' ? (
<CloseOutlined style={{ color: 'red' }} />
) : (
<LoadingOutlined />
<>
<Steps
direction="vertical"
items={[
{
title: 'Ansible Playbook : Ansible Ping & Call SSM API URL',
description: (
<>
{playbookStatus}{' '}
{playbookStatus === 'failed' && (
<Popover
title={'Ansible Connection Logs'}
content={
<div style={{ overflowY: 'scroll', height: '400px' }}>
{execRes}
</div>
}
overlayStyle={{
width: '400px',
height: '400px',
overflowY: 'scroll',
}}
>
<InfoCircleFilled />
</Popover>
)}
</>
),
},
{
title: 'Docker Connection test',
description: (
<>
{dockerStatus}{' '}
{dockerStatus === 'failed' && (
<Popover
content={
<Typography.Text type="danger">
{dockerConnErrorMessage}
</Typography.Text>
}
title={'Docker Connection Logs'}
>
<InfoCircleFilled />
</Popover>
)}
</>
),
icon:
dockerStatus === 'successful' ? (
<CheckCircleOutlined />
) : dockerStatus === 'failed' ? (
<CloseOutlined style={{ color: 'red' }} />
) : (
<LoadingOutlined />
icon:
playbookStatus === 'successful' ? (
<CheckCircleOutlined />
) : playbookStatus === 'failed' ? (
<CloseOutlined style={{ color: 'red' }} />
) : (
<LoadingOutlined />
),
},
{
title: 'Docker Connection test',
description: (
<>
{dockerStatus}{' '}
{dockerStatus === 'failed' && (
<Popover
content={
<Typography.Text type="danger">
{dockerConnErrorMessage}
</Typography.Text>
}
title={'Docker Connection Logs'}
>
<InfoCircleFilled />
</Popover>
)}
</>
),
},
]}
/>
icon:
dockerStatus === 'successful' ? (
<CheckCircleOutlined />
) : dockerStatus === 'failed' ? (
<CloseOutlined style={{ color: 'red' }} />
) : (
<LoadingOutlined />
),
},
]}
/>
{smartFailure && (
<motion.div
initial="hidden"
animate="visible"
variants={animationVariants}
transition={{ duration: 0.5 }}
>
<Alert
message={'Ansible failed'}
description={
<>
<Typography.Paragraph>
<b>Probable cause</b>: {smartFailure.cause}
<br />
<b>Probable Resolution</b>: {smartFailure.resolution}
</Typography.Paragraph>
</>
}
showIcon
type={'error'}
/>
</motion.div>
)}
{(playbookStatus === 'failed' || count > 10) && (
<motion.div
initial="hidden"
animate="visible"
variants={animationVariants}
transition={{ duration: 0.5 }}
>
<SwitchConnexionMethod />
</motion.div>
)}
</>
);
};

Expand Down
6 changes: 6 additions & 0 deletions client/src/components/NewDeviceModal/NewDeviceModal.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -247,6 +247,12 @@ const NewDeviceModal: React.FC<NewDeviceModalProps> = (props) => {
},
]}
/>
<Alert
message={
'Ensure that the server running Squirrel Servers Manager has port *8000* opened and accessible from the device you wish to add.'
}
showIcon
/>
</StepsForm.StepForm>
<StepsForm.StepForm
name="test"
Expand Down
45 changes: 45 additions & 0 deletions client/src/components/NewDeviceModal/SwitchConnexionMethod.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,45 @@
import { SwapOutlined, SwitcherOutlined } from '@ant-design/icons';
import { Alert, Button, Space, Typography } from 'antd';
import React from 'react';

const SwitchConnexionMethod = () => {
const [showDetails, setShowDetails] = React.useState(false);

return (
<Alert
style={{ marginTop: 15 }}
message={'Encountering problems?'}
description={
showDetails ? (
<Space direction={'vertical'}>
<Typography.Text>
Try switching to the classic Ansible SSH connexion method instead
of paramiko (not available when using a passphrase protected key).
</Typography.Text>
<Typography.Text>
SSH &rarr; Show advanced &rarr; Connection Method &rarr; *SSH*
</Typography.Text>
<Typography.Link
href={
'https://squirrelserversmanager.io/docs/technical-guide/troubleshoot'
}
target={'_blank'}
>
[More details]
</Typography.Link>
</Space>
) : undefined
}
action={
showDetails ? undefined : (
<Button size="small" onClick={() => setShowDetails(true)}>
Details
</Button>
)
}
showIcon
/>
);
};

export default SwitchConnexionMethod;
Loading

0 comments on commit bd19f39

Please sign in to comment.