diff --git a/package-lock.json b/package-lock.json index 4b4b54e..fd7e81b 100644 --- a/package-lock.json +++ b/package-lock.json @@ -85,9 +85,9 @@ } }, "node_modules/@adobe/css-tools": { - "version": "4.3.1", - "resolved": "https://registry.npmjs.org/@adobe/css-tools/-/css-tools-4.3.1.tgz", - "integrity": "sha512-/62yikz7NLScCGAAST5SHdnjaDJQBDq0M2muyRTpf2VQhw6StBg2ALiu73zSJQ4fMVLA+0uBhBHAle7Wg+2kSg==", + "version": "4.3.2", + "resolved": "https://registry.npmjs.org/@adobe/css-tools/-/css-tools-4.3.2.tgz", + "integrity": "sha512-DA5a1C0gD/pLOvhv33YMrbf2FK3oUzwNl9oOJqE4XVjuEtt6XIakRcsd7eLiOSPkp1kTRQGICTA8cKra/vFbjw==", "dev": true }, "node_modules/@ampproject/remapping": { @@ -19709,9 +19709,9 @@ "dev": true }, "@adobe/css-tools": { - "version": "4.3.1", - "resolved": "https://registry.npmjs.org/@adobe/css-tools/-/css-tools-4.3.1.tgz", - "integrity": "sha512-/62yikz7NLScCGAAST5SHdnjaDJQBDq0M2muyRTpf2VQhw6StBg2ALiu73zSJQ4fMVLA+0uBhBHAle7Wg+2kSg==", + "version": "4.3.2", + "resolved": "https://registry.npmjs.org/@adobe/css-tools/-/css-tools-4.3.2.tgz", + "integrity": "sha512-DA5a1C0gD/pLOvhv33YMrbf2FK3oUzwNl9oOJqE4XVjuEtt6XIakRcsd7eLiOSPkp1kTRQGICTA8cKra/vFbjw==", "dev": true }, "@ampproject/remapping": { diff --git a/packages/client/src/app/status/statusPage.tsx b/packages/client/src/app/status/statusPage.tsx index 0b7ba77..72cd2ed 100644 --- a/packages/client/src/app/status/statusPage.tsx +++ b/packages/client/src/app/status/statusPage.tsx @@ -1,6 +1,7 @@ import { useQuery } from '@tanstack/react-query'; -import { Loading, Spacer, Text } from '@nextui-org/react'; +import { Button, Grid, Loading, Spacer, Text } from '@nextui-org/react'; import { StatusDTO } from '@rotom/types'; +import { useCallback, useState } from 'react'; import { StatusCard } from './statusCard'; import { MemoizedWorkersTable as WorkersTable } from './workersTable'; @@ -12,6 +13,21 @@ export const StatusPage = (): JSX.Element => { const { isLoading, isFetching, error, data, isSuccess } = useQuery(['status'], fetchStatus, { refetchInterval: 5000, }); + const [delLoading, setDelLoading] = useState(false); + + const handleRemoveDead = useCallback(async () => { + setDelLoading(true); + const cancel = new AbortController(); + const timer = setTimeout(() => cancel.abort(), 5000); + try { + await fetch('/api/device', { method: 'DELETE', signal: cancel.signal }); + } catch (e) { + console.error(e); + } finally { + setDelLoading(false); + clearTimeout(timer); + } + }, []); if (isLoading) { return ; @@ -30,7 +46,16 @@ export const StatusPage = (): JSX.Element => { - Devices + + + Devices + + + + + Workers diff --git a/packages/server/src/index.ts b/packages/server/src/index.ts index 9569fa9..4c0fd3e 100644 --- a/packages/server/src/index.ts +++ b/packages/server/src/index.ts @@ -464,6 +464,22 @@ const routes = async (fastifyInstance: FastifyInstance) => { } }); + fastifyInstance.delete('/api/device', async (request, reply) => { + log.info('Received delete all devices request'); + const deviceIds = Object.keys(controlConnections); + let deleted = 0; + for (const deviceId of deviceIds) { + const device = controlConnections[deviceId]; + if (!device.isAlive) { + delete deviceInformation[deviceId]; + delete controlConnections[deviceId]; + deleted++; + } + } + log.info(`Deleted ${deleted} devices`); + return reply.code(200).send({ status: 'ok', error: `Deleted ${deleted} devices` }); + }); + interface ActionExecuteParams { deviceId: keyof typeof controlConnections; action: 'restart' | 'reboot' | 'getLogcat' | 'delete';