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.
This commit is contained in:
parent
ec2d656b6e
commit
ed322436e6
BIN
src/assets/icons/otpicon.afdesign
Normal file
BIN
src/assets/icons/otpicon.afdesign
Normal file
Binary file not shown.
1
src/assets/icons/otpicon.min.svg
Normal file
1
src/assets/icons/otpicon.min.svg
Normal file
@ -0,0 +1 @@
|
||||
<svg xmlns="http://www.w3.org/2000/svg" xml:space="preserve" style="fill-rule:evenodd;clip-rule:evenodd;stroke-linejoin:round;stroke-miterlimit:2" viewBox="0 0 64 64"><path d="M39.064 13.281a16 16 0 0 0-1.552 2.283 16 16 0 0 0-1.102 2.424H11.319c-2.038 0-3.189 1.143-3.189 3.185V42.78c0 2.014 1.151 3.185 3.189 3.185h17.908c-.335 1.012-.407 2.18-.02 3.274l.532 1.47H10.622C5.731 50.709 3 48.064 3 43.165V20.784c0-4.878 2.731-7.503 7.622-7.503zm21.781 21.78v8.104c0 4.899-2.731 7.544-7.644 7.544h-5.695a4.8 4.8 0 0 0-.008-2.542l-.362-1.277 3.085-.865q.1-.029.197-.06h2.108c2.037 0 3.189-1.171 3.189-3.185v-5.062c1.92-.572 3.643-1.468 5.13-2.657" style="fill-rule:nonzero" transform="translate(-1.5)"/><path d="M18.888 77.914c1.551 1.303 3.64 1.459 5.099.032l9.646-9.657c1.437-1.448 1.375-3.661-.031-5.099l-4.446-4.435 6.674-6.653c1.408-1.406 1.385-3.65-.021-5.099l-6.024-6.046c8.385-4.254 13.079-11.304 13.079-19.577C42.864 9.551 33.292 0 21.443 0 9.51 0 0 9.498 0 21.38c0 8.455 4.793 16.002 12.406 19.504v29.212c0 1.161.372 2.517 1.378 3.399zm2.555-5.961-3.36-3.328V36.464C11.107 34.977 5.994 28.773 5.994 21.38c0-8.491 6.895-15.355 15.449-15.355 8.533 0 15.387 6.864 15.387 15.355 0 7.34-5.135 13.607-12.9 15.254v7.047l5.867 5.918-6.228 6.134v5.982l4.05 3.978zm0-51.321c2.771 0 5.042-2.282 5.042-5.064s-2.271-5.022-5.042-5.022c-2.814 0-5.043 2.231-5.043 5.022 0 2.782 2.25 5.064 5.043 5.064" style="fill-rule:nonzero" transform="rotate(29.234 11.74 88.091)scale(.60562)"/><path d="M34.933 27.241a4.697 4.697 0 0 0 4.694-4.694 4.697 4.697 0 0 0-4.694-4.694 4.7 4.7 0 0 0-4.695 4.694 4.7 4.7 0 0 0 4.695 4.694m-14.642 0c2.565 0 4.669-2.104 4.669-4.694s-2.13-4.694-4.669-4.694a4.697 4.697 0 0 0-4.694 4.694 4.697 4.697 0 0 0 4.694 4.694" style="fill-rule:nonzero" transform="translate(1.5 13.281)scale(.8287)"/></svg>
|
||||
|
After Width: | Height: | Size: 1.8 KiB |
16
src/assets/icons/otpicon.svg
Normal file
16
src/assets/icons/otpicon.svg
Normal file
@ -0,0 +1,16 @@
|
||||
<?xml version="1.0" encoding="UTF-8" standalone="no"?>
|
||||
<!DOCTYPE svg PUBLIC "-//W3C//DTD SVG 1.1//EN" "http://www.w3.org/Graphics/SVG/1.1/DTD/svg11.dtd">
|
||||
<svg width="100%" height="100%" viewBox="0 0 64 64" version="1.1" xmlns="http://www.w3.org/2000/svg" xmlns:xlink="http://www.w3.org/1999/xlink" xml:space="preserve" xmlns:serif="http://www.serif.com/" style="fill-rule:evenodd;clip-rule:evenodd;stroke-linejoin:round;stroke-miterlimit:2;">
|
||||
<g transform="matrix(1,0,0,1,-1.5,7.10543e-15)">
|
||||
<path d="M39.064,13.281C38.491,13.982 37.971,14.744 37.512,15.564C37.071,16.351 36.704,17.161 36.41,17.988L11.319,17.988C9.281,17.988 8.13,19.131 8.13,21.173L8.13,42.78C8.13,44.794 9.281,45.965 11.319,45.965L29.227,45.965C28.892,46.977 28.82,48.145 29.207,49.239L29.739,50.709L10.622,50.709C5.731,50.709 3,48.064 3,43.165L3,20.784C3,15.906 5.731,13.281 10.622,13.281L39.064,13.281ZM60.845,35.061L60.845,43.165C60.845,48.064 58.114,50.709 53.201,50.709L47.506,50.709C47.725,49.911 47.734,49.038 47.498,48.167L47.136,46.89L50.221,46.025C50.287,46.006 50.353,45.986 50.418,45.965L52.526,45.965C54.563,45.965 55.715,44.794 55.715,42.78L55.715,37.718C57.635,37.146 59.358,36.25 60.845,35.061Z" style="fill-rule:nonzero;"/>
|
||||
<g transform="matrix(0.528487,0.29577,-0.29577,0.528487,46.0168,5.48596)">
|
||||
<path d="M18.888,77.914C20.439,79.217 22.528,79.373 23.987,77.946L33.633,68.289C35.07,66.841 35.008,64.628 33.602,63.19L29.156,58.755L35.83,52.102C37.238,50.696 37.215,48.452 35.809,47.003L29.785,40.957C38.17,36.703 42.864,29.653 42.864,21.38C42.864,9.551 33.292,0 21.443,0C9.51,0 0,9.498 0,21.38C0,29.835 4.793,37.382 12.406,40.884L12.406,70.096C12.406,71.257 12.778,72.613 13.784,73.495L18.888,77.914ZM21.443,71.953L18.083,68.625L18.083,36.464C11.107,34.977 5.994,28.773 5.994,21.38C5.994,12.889 12.889,6.025 21.443,6.025C29.976,6.025 36.83,12.889 36.83,21.38C36.83,28.72 31.695,34.987 23.93,36.634L23.93,43.681L29.797,49.599L23.569,55.733L23.569,61.715L27.619,65.693L21.443,71.953ZM21.443,20.632C24.214,20.632 26.485,18.35 26.485,15.568C26.485,12.787 24.214,10.546 21.443,10.546C18.629,10.546 16.4,12.777 16.4,15.568C16.4,18.35 18.65,20.632 21.443,20.632Z" style="fill-rule:nonzero;"/>
|
||||
</g>
|
||||
<g transform="matrix(0.828691,0,0,0.828691,3,13.2814)">
|
||||
<path d="M34.933,27.241C37.523,27.241 39.627,25.137 39.627,22.547C39.627,19.957 37.523,17.853 34.933,17.853C32.343,17.853 30.238,19.957 30.238,22.547C30.238,25.137 32.343,27.241 34.933,27.241Z" style="fill-rule:nonzero;"/>
|
||||
</g>
|
||||
<g transform="matrix(0.828691,0,0,0.828691,3,13.2814)">
|
||||
<path d="M20.291,27.241C22.856,27.241 24.96,25.137 24.96,22.547C24.96,19.957 22.83,17.853 20.291,17.853C17.701,17.853 15.597,19.957 15.597,22.547C15.597,25.137 17.701,27.241 20.291,27.241Z" style="fill-rule:nonzero;"/>
|
||||
</g>
|
||||
</g>
|
||||
</svg>
|
||||
|
After Width: | Height: | Size: 2.8 KiB |
@ -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 (
|
||||
<EditObjectForm id={hostId} type='host' style={{ height: '100%' }}>
|
||||
{({
|
||||
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
|
||||
}
|
||||
}
|
||||
<>
|
||||
<Flex
|
||||
gap='large'
|
||||
vertical='true'
|
||||
style={{
|
||||
height: 'calc(var(--unit-100vh) - 155px)',
|
||||
minHeight: 0
|
||||
}}
|
||||
>
|
||||
<Flex justify={'space-between'}>
|
||||
<Space size='middle'>
|
||||
<Space size='small'>
|
||||
<ObjectActions
|
||||
type='host'
|
||||
id={hostId}
|
||||
disabled={editFormState.loading}
|
||||
/>
|
||||
<ViewButton
|
||||
disabled={editFormState.loading}
|
||||
items={[
|
||||
{ key: 'info', label: 'Host Information' },
|
||||
{ key: 'notes', label: 'Notes' },
|
||||
{ key: 'auditLogs', label: 'Audit Logs' }
|
||||
]}
|
||||
visibleState={collapseState}
|
||||
updateVisibleState={updateCollapseState}
|
||||
/>
|
||||
</Space>
|
||||
<LockIndicator lock={editFormState.lock} />
|
||||
</Space>
|
||||
<Space>
|
||||
<EditButtons
|
||||
isEditing={editFormState.isEditing}
|
||||
handleUpdate={() => {
|
||||
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}
|
||||
/>
|
||||
</Space>
|
||||
</Flex>
|
||||
|
||||
return (
|
||||
<ActionHandler actions={actions} loading={loading}>
|
||||
{({ callAction }) => (
|
||||
<Flex
|
||||
gap='large'
|
||||
vertical='true'
|
||||
style={{
|
||||
height: 'calc(var(--unit-100vh) - 155px)',
|
||||
minHeight: 0
|
||||
}}
|
||||
<div style={{ height: '100%', overflowY: 'scroll' }}>
|
||||
<Flex vertical gap={'large'}>
|
||||
<ActionHandler
|
||||
actions={actions}
|
||||
loading={editFormState.loading}
|
||||
ref={actionHandlerRef}
|
||||
>
|
||||
<InfoCollapse
|
||||
title='Host Information'
|
||||
icon={<InfoCircleIcon />}
|
||||
active={collapseState.info}
|
||||
onToggle={(expanded) => updateCollapseState('info', expanded)}
|
||||
collapseKey='info'
|
||||
>
|
||||
<Flex justify={'space-between'}>
|
||||
<Space size='middle'>
|
||||
<Space size='small'>
|
||||
<ObjectActions
|
||||
type='host'
|
||||
id={hostId}
|
||||
disabled={loading}
|
||||
/>
|
||||
<ViewButton
|
||||
disabled={loading}
|
||||
items={[
|
||||
{ key: 'info', label: 'Host Information' },
|
||||
{ key: 'notes', label: 'Notes' },
|
||||
{ key: 'auditLogs', label: 'Audit Logs' }
|
||||
]}
|
||||
visibleState={collapseState}
|
||||
updateVisibleState={updateCollapseState}
|
||||
/>
|
||||
</Space>
|
||||
<LockIndicator lock={lock} />
|
||||
</Space>
|
||||
<Space>
|
||||
<EditButtons
|
||||
isEditing={isEditing}
|
||||
handleUpdate={() => {
|
||||
callAction('finishEdit')
|
||||
}}
|
||||
cancelEditing={() => {
|
||||
callAction('cancelEdit')
|
||||
}}
|
||||
startEditing={() => {
|
||||
callAction('edit')
|
||||
}}
|
||||
editLoading={editLoading}
|
||||
formValid={formValid}
|
||||
disabled={lock?.locked || loading}
|
||||
loading={editLoading}
|
||||
/>
|
||||
</Space>
|
||||
</Flex>
|
||||
|
||||
<div style={{ height: '100%', overflowY: 'scroll' }}>
|
||||
<Flex vertical gap={'large'}>
|
||||
<InfoCollapse
|
||||
title='Host Information'
|
||||
icon={<InfoCircleIcon />}
|
||||
active={collapseState.info}
|
||||
onToggle={(expanded) =>
|
||||
updateCollapseState('info', expanded)
|
||||
}
|
||||
collapseKey='info'
|
||||
>
|
||||
<EditObjectForm
|
||||
id={hostId}
|
||||
type='host'
|
||||
style={{ height: '100%' }}
|
||||
ref={editFormRef}
|
||||
onStateChange={(state) => {
|
||||
console.log('Got edit form state change', state)
|
||||
setEditFormState((prev) => ({ ...prev, ...state }))
|
||||
}}
|
||||
>
|
||||
{({ loading, isEditing, objectData }) => {
|
||||
return (
|
||||
<ObjectInfo
|
||||
loading={loading}
|
||||
indicator={<LoadingOutlined />}
|
||||
@ -137,49 +152,59 @@ const HostInfo = () => {
|
||||
type='host'
|
||||
objectData={objectData}
|
||||
/>
|
||||
</InfoCollapse>
|
||||
)
|
||||
}}
|
||||
</EditObjectForm>
|
||||
</InfoCollapse>
|
||||
</ActionHandler>
|
||||
|
||||
<InfoCollapse
|
||||
title='Notes'
|
||||
icon={<NoteIcon />}
|
||||
active={collapseState.notes}
|
||||
onToggle={(expanded) =>
|
||||
updateCollapseState('notes', expanded)
|
||||
}
|
||||
collapseKey='notes'
|
||||
>
|
||||
<Card>
|
||||
<NotesPanel _id={hostId} type='host' />
|
||||
</Card>
|
||||
</InfoCollapse>
|
||||
<InfoCollapse
|
||||
title='Notes'
|
||||
icon={<NoteIcon />}
|
||||
active={collapseState.notes}
|
||||
onToggle={(expanded) => updateCollapseState('notes', expanded)}
|
||||
collapseKey='notes'
|
||||
>
|
||||
<Card>
|
||||
<NotesPanel _id={hostId} type='host' />
|
||||
</Card>
|
||||
</InfoCollapse>
|
||||
|
||||
<InfoCollapse
|
||||
title='Audit Logs'
|
||||
icon={<AuditLogIcon />}
|
||||
active={collapseState.auditLogs}
|
||||
onToggle={(expanded) =>
|
||||
updateCollapseState('auditLogs', expanded)
|
||||
}
|
||||
collapseKey='auditLogs'
|
||||
>
|
||||
{loading ? (
|
||||
<InfoCollapsePlaceholder />
|
||||
) : (
|
||||
<ObjectTable
|
||||
type='auditLog'
|
||||
masterFilter={{ 'parent._id': hostId }}
|
||||
visibleColumns={{ _id: false, 'parent._id': false }}
|
||||
/>
|
||||
)}
|
||||
</InfoCollapse>
|
||||
</Flex>
|
||||
</div>
|
||||
</Flex>
|
||||
)}
|
||||
</ActionHandler>
|
||||
)
|
||||
}}
|
||||
</EditObjectForm>
|
||||
<InfoCollapse
|
||||
title='Audit Logs'
|
||||
icon={<AuditLogIcon />}
|
||||
active={collapseState.auditLogs}
|
||||
onToggle={(expanded) =>
|
||||
updateCollapseState('auditLogs', expanded)
|
||||
}
|
||||
collapseKey='auditLogs'
|
||||
>
|
||||
{editFormState.loading ? (
|
||||
<InfoCollapsePlaceholder />
|
||||
) : (
|
||||
<ObjectTable
|
||||
type='auditLog'
|
||||
masterFilter={{ 'parent._id': hostId }}
|
||||
visibleColumns={{ _id: false, 'parent._id': false }}
|
||||
/>
|
||||
)}
|
||||
</InfoCollapse>
|
||||
</Flex>
|
||||
</div>
|
||||
</Flex>
|
||||
|
||||
<Modal
|
||||
open={hostOTPOpen}
|
||||
destroyOnHidden={true}
|
||||
width={650}
|
||||
onCancel={() => {
|
||||
setHostOTPOpen(false)
|
||||
}}
|
||||
footer={false}
|
||||
>
|
||||
<HostOTP id={hostId} />
|
||||
</Modal>
|
||||
</>
|
||||
)
|
||||
}
|
||||
|
||||
|
||||
138
src/components/Dashboard/Management/Hosts/HostOtp.jsx
Normal file
138
src/components/Dashboard/Management/Hosts/HostOtp.jsx
Normal file
@ -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 (
|
||||
<Flex vertical align='center'>
|
||||
<Button onClick={sendTestAction}>Test Action</Button>
|
||||
<Result
|
||||
title={'Connect a Host.'}
|
||||
subTitle={<Text>Enter the following one time passcode.</Text>}
|
||||
icon={<OTPIcon />}
|
||||
>
|
||||
<Flex justify='center'>
|
||||
<Flex gap={'small'} align='center' justify='center'>
|
||||
<CopyButton
|
||||
size='default'
|
||||
text={hostObject?.otp}
|
||||
disabled={loading}
|
||||
/>
|
||||
<div>
|
||||
<Input.OTP
|
||||
disabled={loading}
|
||||
size='large'
|
||||
value={hostObject?.otp}
|
||||
onChange={(e) => e.preventDefault()} // prevent typing
|
||||
onKeyDown={(e) => e.preventDefault()} // prevent key input
|
||||
onPaste={(e) => e.preventDefault()} // prevent pasting
|
||||
/>
|
||||
</div>
|
||||
<div style={{ margin: '0 6px 0 8px', paddingBottom: '5px' }}>
|
||||
{loading ? (
|
||||
<Text>
|
||||
<LoadingOutlined />
|
||||
</Text>
|
||||
) : (
|
||||
<Progress
|
||||
type='circle'
|
||||
showInfo={false}
|
||||
strokeColor='#32D74B'
|
||||
size={14}
|
||||
strokeWidth={14}
|
||||
percent={progressPercent}
|
||||
/>
|
||||
)}
|
||||
</div>
|
||||
</Flex>
|
||||
</Flex>
|
||||
</Result>
|
||||
</Flex>
|
||||
)
|
||||
}
|
||||
|
||||
HostOTP.propTypes = {
|
||||
id: PropTypes.string.isRequired
|
||||
}
|
||||
|
||||
export default HostOTP
|
||||
7
src/components/Icons/OTPIcon.jsx
Normal file
7
src/components/Icons/OTPIcon.jsx
Normal file
@ -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) => <Icon component={CustomIconSvg} {...props} />
|
||||
|
||||
export default OTPIcon
|
||||
@ -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
|
||||
}
|
||||
]
|
||||
}
|
||||
|
||||
Loading…
x
Reference in New Issue
Block a user