фронт для дискавери отображения
This commit is contained in:
parent
e355c12ffa
commit
23da2fab55
4 changed files with 85 additions and 6 deletions
|
|
@ -26,6 +26,7 @@ from app.schemas.object import (
|
||||||
ObjectUpdate,
|
ObjectUpdate,
|
||||||
SyncResult,
|
SyncResult,
|
||||||
DiscoveryStarted,
|
DiscoveryStarted,
|
||||||
|
DiscoveryStatus,
|
||||||
)
|
)
|
||||||
from app.services import crypto
|
from app.services import crypto
|
||||||
from app.services.database import AsyncSessionLocal
|
from app.services.database import AsyncSessionLocal
|
||||||
|
|
@ -36,6 +37,22 @@ from app.services.sse import sse_manager, SSEEvent
|
||||||
|
|
||||||
router = APIRouter(prefix="/api/v1/objects", tags=["objects"])
|
router = APIRouter(prefix="/api/v1/objects", tags=["objects"])
|
||||||
|
|
||||||
|
_discovery_tasks: dict[int, asyncio.Task] = {}
|
||||||
|
|
||||||
|
|
||||||
|
def _discovery_task_id(obj_id: int) -> str:
|
||||||
|
return f"disc-{obj_id}"
|
||||||
|
|
||||||
|
|
||||||
|
def _is_discovery_running(obj_id: int) -> bool:
|
||||||
|
task = _discovery_tasks.get(obj_id)
|
||||||
|
return task is not None and not task.done()
|
||||||
|
|
||||||
|
|
||||||
|
def _forget_discovery_task(obj_id: int, task: asyncio.Task) -> None:
|
||||||
|
if _discovery_tasks.get(obj_id) is task:
|
||||||
|
_discovery_tasks.pop(obj_id, None)
|
||||||
|
|
||||||
|
|
||||||
def _apply_credentials(obj: Object, db_password: str | None, ssh_password: str | None) -> None:
|
def _apply_credentials(obj: Object, db_password: str | None, ssh_password: str | None) -> None:
|
||||||
if db_password is not None:
|
if db_password is not None:
|
||||||
|
|
@ -300,9 +317,24 @@ async def discover_devices(
|
||||||
):
|
):
|
||||||
"""SSH into each rack PC, parse caps config, discover network devices."""
|
"""SSH into each rack PC, parse caps config, discover network devices."""
|
||||||
await _load_object(db, obj_id)
|
await _load_object(db, obj_id)
|
||||||
task_id = f"disc-{obj_id}"
|
task_id = _discovery_task_id(obj_id)
|
||||||
asyncio.create_task(_run_discovery_background(obj_id, task_id))
|
if _is_discovery_running(obj_id):
|
||||||
return DiscoveryStarted(task_id=task_id)
|
return DiscoveryStarted(task_id=task_id, running=True, already_running=True)
|
||||||
|
|
||||||
|
task = asyncio.create_task(_run_discovery_background(obj_id, task_id))
|
||||||
|
_discovery_tasks[obj_id] = task
|
||||||
|
task.add_done_callback(lambda done_task: _forget_discovery_task(obj_id, done_task))
|
||||||
|
return DiscoveryStarted(task_id=task_id, running=True, already_running=False)
|
||||||
|
|
||||||
|
|
||||||
|
@router.get("/{obj_id}/discover/status", response_model=DiscoveryStatus)
|
||||||
|
async def discovery_status(
|
||||||
|
obj_id: int,
|
||||||
|
db: AsyncSession = Depends(get_db),
|
||||||
|
_: User = Depends(get_current_user),
|
||||||
|
):
|
||||||
|
await _load_object(db, obj_id)
|
||||||
|
return DiscoveryStatus(task_id=_discovery_task_id(obj_id), running=_is_discovery_running(obj_id))
|
||||||
|
|
||||||
|
|
||||||
@router.post("/import/preview", response_model=ObjectImportPreview)
|
@router.post("/import/preview", response_model=ObjectImportPreview)
|
||||||
|
|
|
||||||
|
|
@ -145,3 +145,10 @@ class SyncResult(BaseModel):
|
||||||
|
|
||||||
class DiscoveryStarted(BaseModel):
|
class DiscoveryStarted(BaseModel):
|
||||||
task_id: str
|
task_id: str
|
||||||
|
running: bool = True
|
||||||
|
already_running: bool = False
|
||||||
|
|
||||||
|
|
||||||
|
class DiscoveryStatus(BaseModel):
|
||||||
|
task_id: str
|
||||||
|
running: bool
|
||||||
|
|
|
||||||
|
|
@ -250,6 +250,13 @@ export const useSyncObject = (objId: number) => {
|
||||||
|
|
||||||
export interface DiscoveryStarted {
|
export interface DiscoveryStarted {
|
||||||
task_id: string
|
task_id: string
|
||||||
|
running: boolean
|
||||||
|
already_running: boolean
|
||||||
|
}
|
||||||
|
|
||||||
|
export interface DiscoveryStatus {
|
||||||
|
task_id: string
|
||||||
|
running: boolean
|
||||||
}
|
}
|
||||||
|
|
||||||
export const useDiscoverObject = (objId: number) =>
|
export const useDiscoverObject = (objId: number) =>
|
||||||
|
|
@ -258,6 +265,15 @@ export const useDiscoverObject = (objId: number) =>
|
||||||
api.post<DiscoveryStarted>(`/api/v1/objects/${objId}/discover`).then((r) => r.data),
|
api.post<DiscoveryStarted>(`/api/v1/objects/${objId}/discover`).then((r) => r.data),
|
||||||
})
|
})
|
||||||
|
|
||||||
|
export const useDiscoveryStatus = (objId: number) =>
|
||||||
|
useQuery({
|
||||||
|
queryKey: ['discovery-status', objId],
|
||||||
|
queryFn: () =>
|
||||||
|
api.get<DiscoveryStatus>(`/api/v1/objects/${objId}/discover/status`).then((r) => r.data),
|
||||||
|
enabled: !!objId,
|
||||||
|
refetchInterval: (query) => (query.state.data?.running ? 2_000 : false),
|
||||||
|
})
|
||||||
|
|
||||||
export interface SheetsSyncPayload {
|
export interface SheetsSyncPayload {
|
||||||
spreadsheet_id: string
|
spreadsheet_id: string
|
||||||
sheet_name?: string
|
sheet_name?: string
|
||||||
|
|
|
||||||
|
|
@ -38,6 +38,7 @@ import {
|
||||||
useDeleteDevice,
|
useDeleteDevice,
|
||||||
useDeleteAllDevices,
|
useDeleteAllDevices,
|
||||||
useDiscoverObject,
|
useDiscoverObject,
|
||||||
|
useDiscoveryStatus,
|
||||||
useDevices,
|
useDevices,
|
||||||
useObject,
|
useObject,
|
||||||
type DeviceItem,
|
type DeviceItem,
|
||||||
|
|
@ -148,6 +149,7 @@ export function ObjectDetailPage({ defaultMode = 'inventory' }: { defaultMode?:
|
||||||
const { data: actions } = useActions()
|
const { data: actions } = useActions()
|
||||||
|
|
||||||
const discoverMutation = useDiscoverObject(objId)
|
const discoverMutation = useDiscoverObject(objId)
|
||||||
|
const discoveryStatus = useDiscoveryStatus(objId)
|
||||||
const deleteDevice = useDeleteDevice(objId)
|
const deleteDevice = useDeleteDevice(objId)
|
||||||
const createRack = useCreateRack(objId)
|
const createRack = useCreateRack(objId)
|
||||||
const updateRack = useUpdateRack(objId)
|
const updateRack = useUpdateRack(objId)
|
||||||
|
|
@ -173,12 +175,31 @@ export function ObjectDetailPage({ defaultMode = 'inventory' }: { defaultMode?:
|
||||||
const [editRackForm] = Form.useForm()
|
const [editRackForm] = Form.useForm()
|
||||||
|
|
||||||
const currentAction = actions?.find((a) => a.name === selectedAction)
|
const currentAction = actions?.find((a) => a.name === selectedAction)
|
||||||
|
const discoveryRunning = discoverMutation.isPending || !!discoveryTaskId || !!discoveryStatus.data?.running
|
||||||
|
|
||||||
|
useEffect(() => {
|
||||||
|
if (discoveryStatus.data?.running) {
|
||||||
|
setDiscoveryTaskId(discoveryStatus.data.task_id)
|
||||||
|
} else if (discoveryStatus.data && discoveryTaskId === discoveryStatus.data.task_id) {
|
||||||
|
setDiscoveryTaskId(null)
|
||||||
|
}
|
||||||
|
}, [discoveryStatus.data, discoveryTaskId])
|
||||||
|
|
||||||
const handleDiscover = async () => {
|
const handleDiscover = async () => {
|
||||||
|
if (discoveryRunning) {
|
||||||
|
setDiscoveryTaskId(discoveryTaskId ?? discoveryStatus.data?.task_id ?? `disc-${objId}`)
|
||||||
|
setDiscoveryOpen(true)
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
try {
|
try {
|
||||||
const { task_id } = await discoverMutation.mutateAsync()
|
const { task_id, already_running } = await discoverMutation.mutateAsync()
|
||||||
setDiscoveryTaskId(task_id)
|
setDiscoveryTaskId(task_id)
|
||||||
setDiscoveryOpen(true)
|
setDiscoveryOpen(true)
|
||||||
|
queryClient.invalidateQueries({ queryKey: ['discovery-status', objId] })
|
||||||
|
if (already_running) {
|
||||||
|
message.info('SSH discovery уже выполняется')
|
||||||
|
}
|
||||||
} catch {
|
} catch {
|
||||||
message.error('Ошибка SSH discovery')
|
message.error('Ошибка SSH discovery')
|
||||||
}
|
}
|
||||||
|
|
@ -186,6 +207,8 @@ export function ObjectDetailPage({ defaultMode = 'inventory' }: { defaultMode?:
|
||||||
|
|
||||||
const handleDiscoveryComplete = () => {
|
const handleDiscoveryComplete = () => {
|
||||||
queryClient.invalidateQueries({ queryKey: ['devices', objId] })
|
queryClient.invalidateQueries({ queryKey: ['devices', objId] })
|
||||||
|
queryClient.invalidateQueries({ queryKey: ['discovery-status', objId] })
|
||||||
|
setDiscoveryTaskId(null)
|
||||||
}
|
}
|
||||||
|
|
||||||
const handleDeleteDevice = async (row: DeviceItem) => {
|
const handleDeleteDevice = async (row: DeviceItem) => {
|
||||||
|
|
@ -630,9 +653,10 @@ export function ObjectDetailPage({ defaultMode = 'inventory' }: { defaultMode?:
|
||||||
<Col>
|
<Col>
|
||||||
{mode === 'inventory' ? (
|
{mode === 'inventory' ? (
|
||||||
<Space wrap>
|
<Space wrap>
|
||||||
<Button icon={<ApiOutlined />} loading={discoverMutation.isPending} onClick={handleDiscover}>
|
<Button icon={<ApiOutlined />} loading={discoveryRunning} onClick={handleDiscover}>
|
||||||
SSH Discovery
|
{discoveryRunning ? 'SSH Discovery...' : 'SSH Discovery'}
|
||||||
</Button>
|
</Button>
|
||||||
|
{discoveryRunning && <Tag color="processing">выполняется</Tag>}
|
||||||
<Button icon={<PlusOutlined />} type="primary" onClick={() => { setEditDevice(null); setDeviceModal(true) }}>
|
<Button icon={<PlusOutlined />} type="primary" onClick={() => { setEditDevice(null); setDeviceModal(true) }}>
|
||||||
Добавить устройство
|
Добавить устройство
|
||||||
</Button>
|
</Button>
|
||||||
|
|
|
||||||
Loading…
Add table
Reference in a new issue