From ed322436e681ca1bf82eac5cd8b666d08382705e Mon Sep 17 00:00:00 2001 From: Tom Butcher Date: Mon, 18 Aug 2025 00:54:20 +0100 Subject: [PATCH] Add OTP functionality and related components for host management - Introduced HostOTP component to handle one-time passcode generation and display. - Added OTPIcon for visual representation of OTP functionality. - Updated HostInfo component to integrate OTP modal and manage state for OTP actions. - Refactored Host model to include a new 'connect' action for OTP access. - Created new SVG and design assets for OTP icon representation. - Enhanced user experience with loading states and progress indicators for OTP validity. --- src/assets/icons/otpicon.afdesign | Bin 0 -> 48215 bytes src/assets/icons/otpicon.min.svg | 1 + src/assets/icons/otpicon.svg | 16 + .../Dashboard/Management/Hosts/HostInfo.jsx | 299 ++++++++++-------- .../Dashboard/Management/Hosts/HostOtp.jsx | 138 ++++++++ src/components/Icons/OTPIcon.jsx | 7 + src/database/models/Host.js | 101 +++++- 7 files changed, 411 insertions(+), 151 deletions(-) create mode 100644 src/assets/icons/otpicon.afdesign create mode 100644 src/assets/icons/otpicon.min.svg create mode 100644 src/assets/icons/otpicon.svg create mode 100644 src/components/Dashboard/Management/Hosts/HostOtp.jsx create mode 100644 src/components/Icons/OTPIcon.jsx diff --git a/src/assets/icons/otpicon.afdesign b/src/assets/icons/otpicon.afdesign new file mode 100644 index 0000000000000000000000000000000000000000..2081fc727f4e69d1f9f6c98ad075176b9e2502c2 GIT binary patch literal 48215 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 \ No newline at end of file diff --git a/src/assets/icons/otpicon.svg b/src/assets/icons/otpicon.svg new file mode 100644 index 0000000..58a8ce2 --- /dev/null +++ b/src/assets/icons/otpicon.svg @@ -0,0 +1,16 @@ + + + + + + + + + + + + + + + + diff --git a/src/components/Dashboard/Management/Hosts/HostInfo.jsx b/src/components/Dashboard/Management/Hosts/HostInfo.jsx index 3ae2a0f..6481cf1 100644 --- a/src/components/Dashboard/Management/Hosts/HostInfo.jsx +++ b/src/components/Dashboard/Management/Hosts/HostInfo.jsx @@ -1,6 +1,6 @@ -import React from 'react' +import React, { useRef, useState } from 'react' import { useLocation } from 'react-router-dom' -import { Space, Flex, Card } from 'antd' +import { Space, Flex, Card, Modal } from 'antd' import { LoadingOutlined } from '@ant-design/icons' import loglevel from 'loglevel' import config from '../../../../config.js' @@ -19,12 +19,15 @@ import ActionHandler from '../../common/ActionHandler.jsx' import ObjectActions from '../../common/ObjectActions.jsx' import ObjectTable from '../../common/ObjectTable.jsx' import InfoCollapsePlaceholder from '../../common/InfoCollapsePlaceholder.jsx' +import HostOTP from './HostOtp.jsx' const log = loglevel.getLogger('HostInfo') log.setLevel(config.logLevel) const HostInfo = () => { const location = useLocation() + const editFormRef = useRef(null) + const actionHandlerRef = useRef(null) const hostId = new URLSearchParams(location.search).get('hostId') const [collapseState, updateCollapseState] = useCollapseState('HostInfo', { info: true, @@ -33,103 +36,115 @@ const HostInfo = () => { auditLogs: true }) + const [hostOTPOpen, setHostOTPOpen] = useState(false) + const [editFormState, setEditFormState] = useState({ + isEditing: false, + editLoading: false, + formValid: false, + locked: false, + loading: false + }) + + const actions = { + reload: () => { + editFormRef?.current.handleFetchObject() + return true + }, + hostOTP: () => { + setHostOTPOpen(true) + return true + }, + edit: () => { + editFormRef?.current.startEditing() + return false + }, + cancelEdit: () => { + editFormRef?.current.cancelEditing() + return true + }, + finishEdit: () => { + editFormRef?.current.handleUpdate() + return true + } + } + return ( - - {({ - loading, - isEditing, - startEditing, - cancelEditing, - handleUpdate, - formValid, - objectData, - editLoading, - lock, - fetchObject - }) => { - // Define actions for ActionHandler - const actions = { - reload: () => { - fetchObject() - return true - }, - edit: () => { - startEditing() - return false - }, - cancelEdit: () => { - cancelEditing() - return true - }, - finishEdit: () => { - handleUpdate() - return true - } - } + <> + + + + + + + + + + + { + actionHandlerRef.current.callAction('finishEdit') + }} + cancelEditing={() => { + actionHandlerRef.current.callAction('cancelEdit') + }} + startEditing={() => { + actionHandlerRef.current.callAction('edit') + }} + editLoading={editFormState.editLoading} + formValid={editFormState.formValid} + disabled={editFormState.lock?.locked || editFormState.loading} + loading={editFormState.editLoading} + /> + + - return ( - - {({ callAction }) => ( - + + + } + active={collapseState.info} + onToggle={(expanded) => updateCollapseState('info', expanded)} + collapseKey='info' > - - - - - - - - - - { - callAction('finishEdit') - }} - cancelEditing={() => { - callAction('cancelEdit') - }} - startEditing={() => { - callAction('edit') - }} - editLoading={editLoading} - formValid={formValid} - disabled={lock?.locked || loading} - loading={editLoading} - /> - - - -
- - } - active={collapseState.info} - onToggle={(expanded) => - updateCollapseState('info', expanded) - } - collapseKey='info' - > + { + console.log('Got edit form state change', state) + setEditFormState((prev) => ({ ...prev, ...state })) + }} + > + {({ loading, isEditing, objectData }) => { + return ( } @@ -137,49 +152,59 @@ const HostInfo = () => { type='host' objectData={objectData} /> - + ) + }} + + + - } - active={collapseState.notes} - onToggle={(expanded) => - updateCollapseState('notes', expanded) - } - collapseKey='notes' - > - - - - + } + active={collapseState.notes} + onToggle={(expanded) => updateCollapseState('notes', expanded)} + collapseKey='notes' + > + + + + - } - active={collapseState.auditLogs} - onToggle={(expanded) => - updateCollapseState('auditLogs', expanded) - } - collapseKey='auditLogs' - > - {loading ? ( - - ) : ( - - )} - - -
-
- )} -
- ) - }} -
+ } + active={collapseState.auditLogs} + onToggle={(expanded) => + updateCollapseState('auditLogs', expanded) + } + collapseKey='auditLogs' + > + {editFormState.loading ? ( + + ) : ( + + )} + + + + + + { + setHostOTPOpen(false) + }} + footer={false} + > + + + ) } diff --git a/src/components/Dashboard/Management/Hosts/HostOtp.jsx b/src/components/Dashboard/Management/Hosts/HostOtp.jsx new file mode 100644 index 0000000..5601ce9 --- /dev/null +++ b/src/components/Dashboard/Management/Hosts/HostOtp.jsx @@ -0,0 +1,138 @@ +import PropTypes from 'prop-types' +import React, { useContext, useEffect, useState, useRef } from 'react' +import { Input, Result, Typography, Flex, Progress, Button } from 'antd' +import { LoadingOutlined } from '@ant-design/icons' +import { ApiServerContext } from '../../context/ApiServerContext' +import CopyButton from '../../common/CopyButton' +import OTPIcon from '../../../Icons/OTPIcon' + +const { Text } = Typography + +const HostOTP = ({ id }) => { + const { fetchHostOTP, sendObjectAction } = useContext(ApiServerContext) + const [hostObject, setHostObject] = useState(null) + const [loading, setLoading] = useState(true) + const [timeRemaining, setTimeRemaining] = useState(0) + const [totalTime, setTotalTime] = useState(0) + const [initialized, setInitialized] = useState(false) + const intervalRef = useRef(null) + + const fetchNewOTP = () => { + setLoading(true) + setHostObject(null) // Reset to show loading + fetchHostOTP(id, (hostOTPObject) => { + setHostObject(hostOTPObject) + setLoading(false) + + if (hostOTPObject?.otpExpiresAt) { + const now = Date.now() + const expiresAt = new Date(hostOTPObject.otpExpiresAt).getTime() + const remaining = Math.max(0, expiresAt - now) + + setTimeRemaining(remaining) + setTotalTime(remaining) + } + }) + } + + const sendTestAction = () => { + console.log('Sending test action...') + sendObjectAction(id, 'host', { method: 'testMethod' }, (result) => { + console.log('Got callback', result) + }) + } + + useEffect(() => { + if (hostObject === null && initialized == false) { + setInitialized(true) + fetchNewOTP() + } + }, [id]) + + useEffect(() => { + if (hostObject && timeRemaining > 0) { + intervalRef.current = setInterval(() => { + setTimeRemaining((prev) => { + const newTime = prev - 1000 + if (newTime <= 0) { + // OTP expired, fetch a new one + fetchNewOTP() + return 0 + } + return newTime + }) + }, 1000) + + return () => { + if (intervalRef.current) { + clearInterval(intervalRef.current) + } + } + } + }, [hostObject, timeRemaining]) + + // Clean up interval on unmount + useEffect(() => { + return () => { + if (intervalRef.current) { + clearInterval(intervalRef.current) + } + } + }, []) + + const progressPercent = + totalTime > 0 ? Math.max(0, (timeRemaining / totalTime) * 100) : 0 + + return ( + + + Enter the following one time passcode.} + icon={} + > + + + +
+ e.preventDefault()} // prevent typing + onKeyDown={(e) => e.preventDefault()} // prevent key input + onPaste={(e) => e.preventDefault()} // prevent pasting + /> +
+
+ {loading ? ( + + + + ) : ( + + )} +
+
+
+
+
+ ) +} + +HostOTP.propTypes = { + id: PropTypes.string.isRequired +} + +export default HostOTP diff --git a/src/components/Icons/OTPIcon.jsx b/src/components/Icons/OTPIcon.jsx new file mode 100644 index 0000000..c5731f5 --- /dev/null +++ b/src/components/Icons/OTPIcon.jsx @@ -0,0 +1,7 @@ +import React from 'react' +import Icon from '@ant-design/icons' +import { ReactComponent as CustomIconSvg } from '../../assets/icons/otpicon.min.svg' + +const OTPIcon = (props) => + +export default OTPIcon diff --git a/src/database/models/Host.js b/src/database/models/Host.js index 89097cb..16b6006 100644 --- a/src/database/models/Host.js +++ b/src/database/models/Host.js @@ -2,6 +2,7 @@ import HostIcon from '../../components/Icons/HostIcon' import InfoCircleIcon from '../../components/Icons/InfoCircleIcon' import ReloadIcon from '../../components/Icons/ReloadIcon' import EditIcon from '../../components/Icons/EditIcon' +import OTPIcon from '../../components/Icons/OTPIcon' export const Host = { name: 'host', @@ -15,7 +16,7 @@ export const Host = { default: true, row: true, icon: InfoCircleIcon, - url: (_id) => `/dashboard/production/hosts/info?hostId=${_id}` + url: (_id) => `/dashboard/management/hosts/info?hostId=${_id}` }, { @@ -23,14 +24,21 @@ export const Host = { label: 'Reload', icon: ReloadIcon, url: (_id) => - `/dashboard/production/hosts/info?hostId=${_id}&action=reload` + `/dashboard/management/hosts/info?hostId=${_id}&action=reload` + }, + { + name: 'connect', + label: 'Connect', + icon: OTPIcon, + url: (_id) => + `/dashboard/management/hosts/info?hostId=${_id}&action=hostOTP` }, { name: 'edit', label: 'Edit', row: true, icon: EditIcon, - url: (_id) => `/dashboard/production/hosts/info?hostId=${_id}&action=edit` + url: (_id) => `/dashboard/management/hosts/info?hostId=${_id}&action=edit` } ], columns: ['name', '_id', 'state', 'tags', 'connectedAt'], @@ -61,30 +69,95 @@ export const Host = { }, { name: 'state', - label: 'Status', + label: 'State', type: 'state', objectType: 'host', showName: false, readOnly: true }, { - name: 'host', - label: 'Host', - type: 'text', + name: 'active', + label: 'Active', + type: 'bool', required: true }, + { + name: 'online', + label: 'Online', + type: 'bool', + readOnly: true + }, + { + name: 'deviceInfo.os', + label: 'Operating System', + type: 'text', + required: false, + readOnly: true, + value: (objectData) => { + if ( + objectData.deviceInfo?.os?.type && + objectData.deviceInfo?.os?.release && + objectData.deviceInfo?.os?.arch + ) { + return `${objectData.deviceInfo.os.type} ${objectData.deviceInfo.os.release} (${objectData.deviceInfo.os.arch})` + } + } + }, + { + name: 'deviceInfo.os.hostname', + label: 'Hostname', + type: 'text', + required: false, + readOnly: true + }, + { + name: 'deviceInfo.cpu.model', + label: 'CPU Model', + type: 'text', + required: false, + readOnly: true + }, + { + name: 'deviceInfo.cpu', + label: 'CPU Info', + type: 'text', + required: false, + readOnly: true, + value: (objectData) => { + if ( + objectData.deviceInfo?.cpu?.cores && + objectData.deviceInfo?.cpu?.speedMHz + ) { + return `Cores: ${objectData.deviceInfo.cpu.cores}, Speed: ${objectData.deviceInfo.cpu.speedMHz} MHz` + } + } + }, + { + name: 'deviceInfo.user.username', + label: 'User', + type: 'text', + required: false, + readOnly: true + }, + { + name: 'deviceInfo.user.homedir', + label: 'User Home', + type: 'text', + required: false, + readOnly: true + }, + { + name: 'deviceInfo.process.nodeVersion', + label: 'NodeJS Version', + type: 'text', + required: false, + readOnly: true + }, { name: 'tags', label: 'Tags', type: 'tags', required: false - }, - { - name: 'operatingSystem', - label: 'Operating System', - type: 'text', - required: false, - readOnly: true } ] }