Update UI Bottom Tool Bar

This commit is contained in:
nguyentrungthat 2025-11-12 16:44:46 +07:00
parent f04bc0b4c1
commit 31036ff7da
12 changed files with 1358 additions and 1373 deletions

View File

@ -17,8 +17,8 @@ interface PromptCallback {
}
class APCController {
private apc_number?: number
private apc_ip: string
public apc_number?: number
public apc_ip: string
private apc_port: number
private apc_username: string
private apc_password: string
@ -100,7 +100,7 @@ class APCController {
this.buffer = ''
}
}
// appendLog(data, 0, 0, this.apc_number || 0)
appendLog(data, 0, 0, this.apc_number || 0)
}
private _handleClose(): void {
@ -130,7 +130,7 @@ class APCController {
setTimeout(() => {
console.log('[ECONNRESET] Trying reconnect apc:', this.apc_ip)
this.reconnect()
}, 10000)
}, 15000)
}
}

View File

@ -187,7 +187,7 @@ export default class LineConnection {
})
}
writeCommand(cmd: string, isWrite = false) {
writeCommand(cmd: string | Buffer<ArrayBuffer>, isWrite = false) {
if (this.client.destroyed) {
console.log(`⚠️ Cannot send, line ${this.config.lineNumber} is closed`)
return
@ -513,4 +513,17 @@ export default class LineConnection {
console.log(error)
}
}
// Gửi nhiều ký tự ESC để vào ROMMON
breakSpam() {
let count = 0
const escInterval = setInterval(() => {
if (count >= 100) {
clearInterval(escInterval)
return
}
this.writeCommand(Buffer.from([0xff, 0xf3])) // Ctrl + Break
count++
}, 1)
}
}

View File

@ -83,7 +83,7 @@ export default class SwitchController {
private _waitFor(prompt: string, timeout = 5000): Promise<string> {
return new Promise((resolve, reject) => {
const timer = setTimeout(() => {
reject(new Error(`Timeout waiting for: ${prompt}`))
resolve(`Timeout waiting for: ${prompt}`)
}, timeout)
this.promptCallbacks.push({
@ -188,58 +188,74 @@ export default class SwitchController {
public async turnPortOff(port: string) {
await this.enterEnableMode()
this._send(`configure terminal`)
await this._waitFor('(config)#')
// await this._waitFor('(config)#')
await this.sleep(500)
this._send(`interface ${port}`)
await this._waitFor('(config-if)#')
// await this._waitFor('(config-if)#')
await this.sleep(500)
this._send(`shutdown`)
await this._waitFor('(config-if)#')
// await this._waitFor('(config-if)#')
await this.sleep(500)
this._send(`end`)
}
public async turnPortOn(port: string) {
await this.enterEnableMode()
this._send(`configure terminal`)
await this._waitFor('(config)#')
// await this._waitFor('(config)#')
await this.sleep(500)
this._send(`interface ${port}`)
await this._waitFor('(config-if)#')
// await this._waitFor('(config-if)#')
await this.sleep(500)
this._send(`no shutdown`)
await this._waitFor('(config-if)#')
// await this._waitFor('(config-if)#')
await this.sleep(500)
this._send(`end`)
}
public async restartPort(port: string) {
await this.enterEnableMode()
this._send(`configure terminal`)
await this._waitFor('(config)#')
// await this._waitFor('(config)#')
await this.sleep(500)
this._send(`interface ${port}`)
await this._waitFor('(config-if)#')
// await this._waitFor('(config-if)#')
await this.sleep(500)
this._send(`shutdown`)
await this._waitFor('(config-if)#')
// await this._waitFor('(config-if)#')
await this.sleep(500)
await this.sleep(2000)
this._send(`no shutdown`)
await this._waitFor('(config-if)#')
// await this._waitFor('(config-if)#')
await this.sleep(500)
this._send(`end`)
}
public async disablePoE(port: string) {
await this.enterEnableMode()
this._send(`configure terminal`)
await this._waitFor('(config)#')
// await this._waitFor('(config)#')
await this.sleep(500)
this._send(`interface ${port}`)
await this._waitFor('(config-if)#')
// await this._waitFor('(config-if)#')
await this.sleep(500)
this._send(`power inline never`)
await this._waitFor('(config-if)#')
// await this._waitFor('(config-if)#')
await this.sleep(500)
this._send(`end`)
}
public async enablePoE(port: string) {
await this.enterEnableMode()
this._send(`configure terminal`)
await this._waitFor('(config)#')
// await this._waitFor('(config)#')
await this.sleep(500)
this._send(`interface ${port}`)
await this._waitFor('(config-if)#')
// await this._waitFor('(config-if)#')
await this.sleep(500)
this._send(`power inline auto`)
await this._waitFor('(config-if)#')
// await this._waitFor('(config-if)#')
await this.sleep(500)
this._send(`end`)
}

View File

@ -63,12 +63,12 @@ export default class SocketIoProvider {
export class WebSocketIo {
intervalMap: { [key: string]: NodeJS.Timeout } = {}
stationMap: Map<number, Station> = new Map()
lineMap: Map<number, LineConnection> = new Map() // key = lineId
userConnecting: Map<number, { userId: number; userName: string }> = new Map()
apcsControl: Map<string, APCController> = new Map()
switchControl: Map<string, SwitchController> = new Map()
lineConnecting: number[] = [] // key = lineId
intervalKeepConnect: { [key: string]: NodeJS.Timeout } = {}
constructor(protected app: ApplicationService) {}
@ -115,7 +115,16 @@ export class WebSocketIo {
listLineS.forEach((el) => {
if (el?.userOpenCLI === userName) {
const line = this.lineMap.get(el.id)
if (line && line?.userCloseCLI()) line?.userCloseCLI()
if (line) {
line.config.openCLI = false
line.config.userEmailOpenCLI = ''
line.config.userOpenCLI = ''
io.emit('user_close_cli', {
stationId: line.config.stationId,
lineId: line.config.id,
userEmailOpenCLI: '',
})
}
}
})
setTimeout(() => {
@ -136,7 +145,8 @@ export class WebSocketIo {
io,
stationId,
lineIds,
async (line) => line.writeCommand(command, true),
async (line) =>
command === 'spam_break' ? line.breakSpam() : line.writeCommand(command, true),
{ command, timeout: 120000 }
)
})
@ -159,21 +169,26 @@ export class WebSocketIo {
socket.on('open_cli', async (data) => {
const { lineId, userEmail, userName: name, stationId } = data
const line = this.lineMap.get(lineId)
if (line && line?.userOpenCLI) {
line?.userOpenCLI({ userEmail, userName: name })
if (line) {
if (line?.userOpenCLI) line?.userOpenCLI({ userEmail, userName: name })
else {
line.config.openCLI = true
line.config.userEmailOpenCLI = userEmail
line.config.userOpenCLI = userName
io.emit('user_open_cli', {
stationId: line.config.stationId,
lineId: line.config.id,
userEmailOpenCLI: userEmail,
userOpenCLI: userName,
})
}
} else {
if (this.lineConnecting.includes(lineId)) return
const linesData = await Line.findBy('id', lineId)
const stationData = await Station.findBy('id', stationId)
if (linesData && stationData) {
this.lineConnecting.push(lineId)
await this.connectLine(
io,
[linesData],
stationData,
line?.config?.output || '',
line?.config?.commands || []
)
await this.connectLine(io, [linesData], stationData)
const lineReconnect = this.lineMap.get(lineId)
if (lineReconnect) {
lineReconnect.userOpenCLI({ userEmail, userName: name })
@ -185,21 +200,25 @@ export class WebSocketIo {
socket.on('close_cli', async (data) => {
const { lineId, stationId } = data
const line = this.lineMap.get(lineId)
if (line && line?.userCloseCLI) {
line?.userCloseCLI()
if (line) {
if (line?.userCloseCLI) line?.userCloseCLI()
else {
line.config.openCLI = false
line.config.userEmailOpenCLI = ''
line.config.userOpenCLI = ''
io.emit('user_close_cli', {
stationId: line.config.stationId,
lineId: line.config.id,
userEmailOpenCLI: '',
})
}
} else {
if (this.lineConnecting.includes(lineId)) return
const linesData = await Line.findBy('id', lineId)
const stationData = await Station.findBy('id', stationId)
if (linesData && stationData) {
this.lineConnecting.push(lineId)
await this.connectLine(
io,
[linesData],
stationData,
line?.config?.output || '',
line?.config?.commands || []
)
await this.connectLine(io, [linesData], stationData)
const lineReconnect = this.lineMap.get(lineId)
if (lineReconnect) {
lineReconnect.userCloseCLI()
@ -265,7 +284,10 @@ export class WebSocketIo {
if (!station) return
const apcIp = (station as any)[`${apcName}_ip`] as string
const apc = this.apcsControl.get(apcIp)
if (apc) await apc.reconnect()
if (apc) {
await apc.reconnect()
this.keepConnectAPC(apcIp, io)
} else await this.connectApc(io, apcName, station)
} else {
for (const outletNumber of outletNumbers) {
if (!outletNumber || outletNumber < 0) return
@ -282,7 +304,11 @@ export class WebSocketIo {
const apcIp = (station as any)[`${apcName}_ip`] as string
const apc = this.apcsControl.get(apcIp)
if (apc && apc.status === 'CONNECTED') {
if (apc && apc.status !== 'CONNECTED') {
await apc.reconnect()
this.keepConnectAPC(apcIp, io)
}
if (apc) {
switch (action) {
case 'on':
await apc?.turnOnOutlet(outletNumber)
@ -302,24 +328,6 @@ export class WebSocketIo {
setTimeout(() => {
apc?.navigateToOutlets()
}, 10000)
} else if (apc && apc.status !== 'CONNECTED') {
await apc.reconnect()
switch (action) {
case 'on':
await apc?.turnOnOutlet(outletNumber)
break
case 'off':
await apc?.turnOffOutlet(outletNumber)
break
case 'restart':
await apc?.restartOutlet(outletNumber)
break
default:
break
}
setTimeout(() => {
apc?.navigateToOutlets()
}, 10000)
}
}
}
@ -336,8 +344,11 @@ export class WebSocketIo {
data: apc.output,
status: apc.status,
})
this.keepConnectAPC(apcIp, io)
} else if (apc && apc.status !== 'CONNECTED') {
await apc.reconnect()
this.apcsControl.set(apcIp, apc)
this.keepConnectAPC(apcIp, io)
} else await this.connectApc(io, apcName, station)
} catch (error) {
console.log(error)
@ -442,7 +453,6 @@ export class WebSocketIo {
commands: string[] = []
) {
try {
this.stationMap.set(station.id, station)
for (const line of lines) {
const lineConn = new LineConnection(
{
@ -584,6 +594,7 @@ export class WebSocketIo {
await apc.connect()
await apc.login()
this.apcsControl.set(ip, apc)
this.keepConnectAPC(ip, socket)
} catch (error) {
console.log(error)
}
@ -603,7 +614,7 @@ export class WebSocketIo {
status: 'DISCONNECTED',
message: `Missing Switch configuration`,
})
throw new Error(`Missing Switch configuration`)
return
}
// Tạo APC Controller instance
@ -633,7 +644,10 @@ export class WebSocketIo {
private async clearLineBeforeConnect(stationId: number, clearLine: number) {
const station = await Station.find(stationId)
if (!station) throw new Error(`Station ${stationId} not found`)
if (!station) {
console.log('[ERROR connect station] Not found!')
return
}
// Kết nối tới station qua Telnet / Socket
const client = new net.Socket()
@ -679,7 +693,9 @@ export class WebSocketIo {
const newMap = new Map<number, LineConnection>()
this.lineMap.forEach((line, id) => {
if (line && line.config) {
newMap.set(id, { config: { ...line.config, status: 'disconnected' } } as LineConnection)
newMap.set(id, {
config: { ...line.config, status: 'disconnected' },
} as LineConnection)
}
})
@ -695,4 +711,19 @@ export class WebSocketIo {
const parsed = JSON.parse(raw)
this.lineMap = new Map(parsed.lineMap)
}
private keepConnectAPC = (ip: string, io: any) => {
if (this.intervalKeepConnect[`${ip}`]) {
clearInterval(this.intervalKeepConnect[`${ip}`])
delete this.intervalKeepConnect[`${ip}`]
}
const interval = setInterval(() => {
const apcConnect = this.apcsControl.get(ip)
if (apcConnect && apcConnect.status === 'CONNECTED') {
apcConnect._send('ENTER')
}
}, 40000)
this.intervalKeepConnect[`${ip}`] = interval
}
}

View File

@ -70,3 +70,10 @@ body {
margin: 0.1rem auto 0;
border-radius: 3px;
}
.containerMain {
display: flex;
flex-direction: column;
justify-content: space-between;
height: 88vh;
}

View File

@ -27,22 +27,22 @@ import type {
import axios from "axios";
import CardLine from "./components/CardLine";
import { SocketProvider, useSocket } from "./context/SocketContext";
import {
// ButtonConnect,
ButtonControlApc,
ButtonCopy,
ButtonDPELP,
ButtonScenario,
ButtonSelect,
} from "./components/ButtonAction";
import // ButtonConnect,
// ButtonControlApc,
// ButtonCopy,
// ButtonDPELP,
// ButtonScenario,
// ButtonSelect,
"./components/ButtonAction";
import StationSetting from "./components/FormAddEdit";
import DrawerScenario from "./components/DrawerScenario";
// import DrawerScenario from "./components/DrawerScenario";
import { Notifications } from "@mantine/notifications";
import ModalTerminal from "./components/ModalTerminal";
import PageLogin from "./components/Authentication/LoginPage";
import DrawerLogs from "./components/DrawerLogs";
// import DrawerLogs from "./components/DrawerLogs";
import DraggableTabs from "./components/DragTabs";
import { isJsonString } from "./untils/helper";
import BottomToolBar from "./components/BottomToolBar";
const apiUrl = import.meta.env.VITE_BACKEND_URL;
@ -67,7 +67,6 @@ function App() {
const [stations, setStations] = useState<TStation[]>([]);
const [selectedLines, setSelectedLines] = useState<TLine[]>([]);
const [activeTab, setActiveTab] = useState("0");
const [showBottomShadow, setShowBottomShadow] = useState(false);
const [isDisable, setIsDisable] = useState(false);
const [isOpenAddStation, setIsOpenAddStation] = useState(false);
const [isEditStation, setIsEditStation] = useState(false);
@ -84,6 +83,29 @@ function App() {
const [testLogContent, setTestLogContent] = useState("");
const [isLogModalOpen, setIsLogModalOpen] = useState(false);
const connectApcSwitch = (station: TStation) => {
if (station?.apc_1_ip && station?.apc_1_port) {
socket?.emit("connect_apc", {
station: station,
apcIp: station?.apc_1_ip,
apcName: "apc_1",
});
}
if (station?.apc_2_ip && station?.apc_2_port) {
socket?.emit("connect_apc", {
station: station,
apcIp: station?.apc_2_ip,
apcName: "apc_2",
});
}
if (station?.switch_control_ip && station?.switch_control_port) {
socket?.emit("connect_switch", {
station: station,
ip: station?.switch_control_ip,
});
}
};
// function get list station
const getStation = async () => {
try {
@ -91,6 +113,9 @@ function App() {
if (response.status) {
if (Array.isArray(response.data)) {
setStations(response.data);
response.data.forEach((station) => {
connectApcSwitch(station);
});
}
}
} catch (error) {
@ -113,9 +138,10 @@ function App() {
};
useEffect(() => {
if (!socket) return;
getStation();
getScenarios();
}, []);
}, [socket]);
useEffect(() => {
if (!socket || !stations?.length) return;
@ -363,16 +389,6 @@ function App() {
setSelectedLine(data);
};
useEffect(() => {
return () => {
if (selectedLine)
socket?.emit("close_cli", {
lineId: selectedLine?.id,
stationId: selectedLine?.station_id,
});
};
}, []);
return (
<Container w={"100%"} style={{ maxWidth: "100%" }}>
<DraggableTabs
@ -389,37 +405,28 @@ function App() {
value={station.id.toString()}
pt="md"
>
<Flex className={classes.containerMain}>
<Grid>
<Grid.Col
span={11}
span={12}
style={{
boxShadow: showBottomShadow
? "inset 0 -12px 10px -10px rgba(0, 0, 0, 0.2)"
: "none",
borderRadius: 8,
}}
>
<ScrollArea
h={"84vh"}
onScrollPositionChange={({ y }) => {
const el = document.querySelector(
".mantine-ScrollArea-viewport"
);
if (!el) return;
const maxScroll = el.scrollHeight - el.clientHeight;
setShowBottomShadow(y < maxScroll - 2);
}}
>
<ScrollArea h={"63vh"}>
{station.lines.length > 8 ? (
<Grid
style={{
marginLeft: "3%",
width: "90%",
width: "95%",
display: "flex",
justifyContent: "center",
}}
>
<Grid.Col span={6}>
<Grid.Col
span={6}
style={{ borderRight: "1px solid #ccc" }}
>
<Flex wrap="wrap" gap="sm" justify={"center"}>
{station.lines.slice(0, 8).map((line, i) => (
<CardLine
@ -460,7 +467,8 @@ function App() {
</Flex>
</Grid.Col>
</Grid>
) : station.lines.length <= 8 && station.lines.length > 0 ? (
) : station.lines.length <= 8 &&
station.lines.length > 0 ? (
<Flex wrap="wrap" gap="sm" justify={"center"}>
{station.lines.map((line, i) => (
<CardLine
@ -485,114 +493,20 @@ function App() {
)}
</ScrollArea>
</Grid.Col>
<Grid.Col
span={1}
style={{ backgroundColor: "#f1f1f1", borderRadius: 8 }}
>
<Flex
direction={"column"}
justify={"space-between"}
align={"center"}
h={"100%"}
>
<Flex
direction={"column"}
align={"center"}
gap={"6px"}
wrap={"wrap"}
>
<ButtonSelect
</Grid>
<BottomToolBar
selectedLines={selectedLines}
socket={socket}
setSelectedLines={setSelectedLines}
isDisable={isDisable}
setIsDisable={setIsDisable}
station={station}
/>
{/* <ButtonConnect
selectedLines={selectedLines}
setSelectedLines={setSelectedLines}
station={station}
socket={socket}
/> */}
<ButtonControlApc
selectedLines={selectedLines}
setSelectedLines={setSelectedLines}
station={station}
socket={socket}
/>
<ButtonCopy
selectedLines={selectedLines}
setSelectedLines={setSelectedLines}
/>
<Flex
w={"100%"}
direction={"column"}
align={"center"}
wrap={"wrap"}
>
<hr style={{ width: "100%" }} />
<DrawerScenario
scenarios={scenarios}
setScenarios={setScenarios}
/>
</Flex>
<ButtonDPELP
socket={socket}
selectedLines={selectedLines}
isDisable={isDisable || selectedLines.length === 0}
onClick={() => {
setSelectedLines([]);
setIsDisable(true);
setTimeout(() => {
setIsDisable(false);
}, 5000);
}}
/>
<ScrollArea h={"60vh"} style={{ paddingBottom: "12px" }}>
<Flex
w={"100%"}
direction={"column"}
wrap={"wrap"}
gap={"6px"}
>
{scenarios.map((el, i) => (
<ButtonScenario
key={i}
socket={socket}
selectedLines={selectedLines.filter(
(el) =>
!el?.userEmailOpenCLI ||
el?.userEmailOpenCLI === user?.email
)}
isDisable={
isDisable ||
selectedLines.filter(
(el) =>
!el?.userEmailOpenCLI ||
el?.userEmailOpenCLI === user?.email
).length === 0
}
onClick={() => {
setSelectedLines([]);
setIsDisable(true);
setTimeout(() => {
setIsDisable(false);
}, 5000);
}}
scenario={el}
/>
))}
</Flex>
</ScrollArea>
</Flex>
<DrawerLogs
socket={socket}
testLogContent={testLogContent}
isLogModalOpen={isLogModalOpen}
setIsLogModalOpen={setIsLogModalOpen}
testLogContent={testLogContent}
setTestLogContent={setTestLogContent}
/>
</Flex>
</Grid.Col>
</Grid>
</Tabs.Panel>
))}
onChange={(id) => {

View File

@ -0,0 +1,278 @@
import {
Box,
Button,
CloseButton,
Flex,
Input,
ScrollArea,
Tabs,
Text,
} from "@mantine/core";
import { useState } from "react";
import classes from "./Component.module.css";
import type { TLine, TStation } from "../untils/types";
import type { Socket } from "socket.io-client";
import { ButtonDPELP, ButtonSelect } from "./ButtonAction";
import DrawerLogs from "./DrawerLogs";
import { DrawerAPCControl, DrawerSwitchControl } from "./DrawerControl";
interface TabsProps {
selectedLines: TLine[];
socket: Socket | null;
setSelectedLines: (lines: React.SetStateAction<TLine[]>) => void;
isDisable: boolean;
station: TStation;
setIsDisable: (lines: React.SetStateAction<boolean>) => void;
testLogContent: string;
isLogModalOpen: boolean;
setIsLogModalOpen: (lines: React.SetStateAction<boolean>) => void;
setTestLogContent: (lines: React.SetStateAction<string>) => void;
}
const BottomToolBar = ({
selectedLines,
socket,
setSelectedLines,
isDisable,
station,
setIsDisable,
testLogContent,
isLogModalOpen,
setIsLogModalOpen,
setTestLogContent,
}: TabsProps) => {
const [valueInput, setValueInput] = useState<string>("");
const [activeTabBottom, setActiveBottom] = useState<string>("command");
return (
<Tabs
defaultValue="command"
orientation="vertical"
value={activeTabBottom}
onChange={(val) => {
setActiveBottom(val || "command");
}}
className={classes.containerBottom}
>
<Tabs.List>
<Tabs.Tab
style={{
backgroundColor: activeTabBottom === "command" ? "#c8d9fd" : "",
}}
value="command"
>
Command Line
</Tabs.Tab>
<Tabs.Tab
style={{
backgroundColor: activeTabBottom === "apc" ? "#c8d9fd" : "",
}}
value="apc"
>
APC
</Tabs.Tab>
<Tabs.Tab
style={{
backgroundColor: activeTabBottom === "switch" ? "#c8d9fd" : "",
}}
value="switch"
>
Switch
</Tabs.Tab>
</Tabs.List>
<Tabs.Panel value="command" p={"xs"}>
<Flex justify={"space-between"}>
<ScrollArea h={"17vh"}>
<Flex wrap={"wrap"} gap={"xs"} w={"400px"}>
{selectedLines.map((el) => (
<Box
key={el.id}
style={{
paddingLeft: "4px",
height: "30px",
width: "80px",
backgroundColor: "#d4e3ff",
borderRadius: "8px",
}}
>
<Flex align={"center"} justify={"center"} gap={"4px"}>
<Text fz={"12px"}>Line {el.lineNumber}</Text>
<CloseButton
style={{ minWidth: "24px" }}
aria-label="Clear input"
onClick={() =>
setSelectedLines(
selectedLines.filter((line) => line.id !== el.id)
)
}
/>
</Flex>
</Box>
))}
</Flex>
</ScrollArea>
<Box pl={"md"} pr={"md"}>
<Flex justify={"space-between"} mb={"xs"}>
<Flex></Flex>
<Button
disabled={isDisable || selectedLines.length === 0}
variant="filled"
color="orange"
size="xs"
radius="md"
onClick={() => {
const listLine = selectedLines.length
? selectedLines
: station?.lines;
if (listLine.length) {
socket?.emit("write_command_line_from_web", {
lineIds: listLine.map((line) => line.id),
stationId: station.id,
command: " \n",
});
setIsDisable(true);
setTimeout(() => {
setIsDisable(false);
}, 5000);
}
}}
>
Send Break
</Button>
</Flex>
<Box>
<Input
style={{
width: "600px",
boxShadow: "0px 0px 10px rgba(0, 0, 0, 0.1)",
}}
placeholder={"Send command to port(s)"}
value={valueInput}
onChange={(event) => {
const newValue = event.currentTarget.value;
setValueInput(newValue);
}}
onKeyDown={(event) => {
if (event.key === "Enter") {
const listLine = selectedLines.length
? selectedLines
: station?.lines;
if (listLine?.length) {
socket?.emit("write_command_line_from_web", {
lineIds: listLine.map((line) => line.id),
stationId: station.id,
command: valueInput + "\n",
});
setTimeout(() => {
socket?.emit("write_command_line_from_web", {
lineIds: listLine.map((line) => line.id),
stationId: station.id,
command: " \n",
});
}, 1000);
}
setValueInput("");
}
}}
rightSectionPointerEvents="all"
rightSection={
<CloseButton
aria-label="Clear input"
onClick={() => setValueInput("")}
style={{
display: valueInput ? undefined : "none",
}}
/>
}
/>
</Box>
</Box>
<Box style={{ width: "220px" }}>
<Flex align={"center"} wrap={"wrap"} gap={"xs"}>
<ButtonSelect
selectedLines={selectedLines}
setSelectedLines={setSelectedLines}
station={station}
/>
<ButtonDPELP
socket={socket}
selectedLines={selectedLines}
isDisable={isDisable || selectedLines.length === 0}
onClick={() => {
setSelectedLines([]);
setIsDisable(true);
setTimeout(() => {
setIsDisable(false);
}, 5000);
}}
/>
<Button
disabled={isDisable || selectedLines.length === 0}
variant="outline"
color="green"
style={{ height: "30px", width: "100px" }}
onClick={() => {
if (selectedLines.length !== station.lines.length)
setSelectedLines(station.lines);
else setSelectedLines([]);
}}
>
Scenario
</Button>
{/* <Flex
w={"100%"}
direction={"column"}
wrap={"wrap"}
gap={"6px"}
>
{scenarios.map((el, i) => (
<ButtonScenario
key={i}
socket={socket}
selectedLines={selectedLines.filter(
(el) =>
!el?.userEmailOpenCLI ||
el?.userEmailOpenCLI === user?.email
)}
isDisable={
isDisable ||
selectedLines.filter(
(el) =>
!el?.userEmailOpenCLI ||
el?.userEmailOpenCLI === user?.email
).length === 0
}
onClick={() => {
setSelectedLines([]);
setIsDisable(true);
setTimeout(() => {
setIsDisable(false);
}, 5000);
}}
scenario={el}
/>
))}
</Flex> */}
<DrawerLogs
socket={socket}
isLogModalOpen={isLogModalOpen}
setIsLogModalOpen={setIsLogModalOpen}
testLogContent={testLogContent}
setTestLogContent={setTestLogContent}
/>
</Flex>
</Box>
</Flex>
</Tabs.Panel>
<Tabs.Panel value="apc" ps={"xs"}>
<DrawerAPCControl socket={socket} stationAPI={station} />
</Tabs.Panel>
<Tabs.Panel value="switch" ps={"xs"}>
<DrawerSwitchControl socket={socket} stationAPI={station} />
</Tabs.Panel>
</Tabs>
);
};
export default BottomToolBar;

View File

@ -111,3 +111,11 @@
.hideScrollBar::-webkit-scrollbar {
display: none;
}
.containerBottom {
height: 22vh;
padding: 8px;
border: 1px solid #ccc;
border-radius: 8px;
background-color: #f3f3f38c;
}

View File

@ -2,11 +2,8 @@ import {
ActionIcon,
Avatar,
Box,
Button,
CloseButton,
Flex,
Group,
Input,
Menu,
Tabs,
Text,
@ -39,7 +36,6 @@ import {
import classes from "./Component.module.css";
import type { TStation, TUser } from "../untils/types";
import type { Socket } from "socket.io-client";
import { DrawerAPCControl, DrawerSwitchControl } from "./DrawerControl";
interface DraggableTabsProps {
tabsData: TStation[];
@ -87,6 +83,7 @@ function SortableTab({
transition,
cursor: "grab",
userSelect: "none",
backgroundColor: active === tab.id.toString() ? "#deffde" : "",
}}
color={active === tab.id.toString() ? "green" : ""}
fw={600}
@ -116,7 +113,6 @@ export default function DraggableTabs({
setStationEdit,
active,
setActive,
onSendCommand,
}: DraggableTabsProps) {
const user = useMemo(() => {
return localStorage.getItem("user") &&
@ -127,12 +123,6 @@ export default function DraggableTabs({
const [tabs, setTabs] = useState<TStation[]>(tabsData);
const [isChangeTab, setIsChangeTab] = useState<boolean>(false);
const [isSetActive, setIsSetActive] = useState<boolean>(false);
const [valueInput, setValueInput] = useState<string>("");
const [openedAPC, setOpenedAPC] = useState(false);
const [openedSwitch, setOpenedSwitch] = useState(false);
// const [active, setActive] = useState<string | null>(
// tabsData?.length > 0 ? tabsData[0]?.id.toString() : null
// );
const sensors = useSensors(useSensor(PointerSensor));
@ -220,29 +210,6 @@ export default function DraggableTabs({
};
}, []);
const connectApcSwitch = (station: TStation) => {
if (station?.apc_1_ip && station?.apc_1_port) {
socket?.emit("connect_apc", {
station: station,
apcIp: station?.apc_1_ip,
apcName: "apc_1",
});
}
if (station?.apc_2_ip && station?.apc_2_port) {
socket?.emit("connect_apc", {
station: station,
apcIp: station?.apc_2_ip,
apcName: "apc_2",
});
}
if (station?.switch_control_ip && station?.switch_control_port) {
socket?.emit("connect_switch", {
station: station,
ip: station?.switch_control_ip,
});
}
};
return (
<DndContext
sensors={sensors}
@ -259,69 +226,7 @@ export default function DraggableTabs({
w={w}
>
<Flex justify={"space-between"}>
<Flex style={{ width: "400px" }} align={"center"}>
<Input
style={{
width: "300px",
boxShadow: "0px 0px 10px rgba(0, 0, 0, 0.1)",
}}
placeholder={"Chat to Port/All"}
value={valueInput}
onChange={(event) => {
const newValue = event.currentTarget.value;
setValueInput(newValue);
}}
onKeyDown={(event) => {
if (event.key === "Enter") {
onSendCommand(valueInput);
setValueInput("");
}
}}
rightSectionPointerEvents="all"
rightSection={
<CloseButton
aria-label="Clear input"
onClick={() => setValueInput("")}
style={{ display: valueInput ? undefined : "none" }}
/>
}
/>
<Flex gap={"xs"} ms={"md"}>
<Button
miw={"80px"}
size="xs"
fz={"sm"}
variant="filled"
onClick={() => {
const station = tabs.find(
(el) => el.id.toString() === active
);
if (!station) return;
setOpenedAPC(true);
connectApcSwitch(station);
}}
>
APC
</Button>
<Button
miw={"80px"}
size="xs"
fz={"sm"}
variant="filled"
color="yellow"
onClick={() => {
const station = tabs.find(
(el) => el.id.toString() === active
);
if (!station) return;
setOpenedSwitch(true);
connectApcSwitch(station);
}}
>
Switch
</Button>
</Flex>
</Flex>
<Flex></Flex>
<Tabs.List className={classes.list}>
<SortableContext
items={tabs}
@ -429,36 +334,6 @@ export default function DraggableTabs({
{panels}
</Tabs>
{tabs.find((el) => el.id.toString() === active) && (
<DrawerAPCControl
open={openedAPC}
onClose={() => {
setOpenedAPC(false);
}}
socket={socket}
stationAPI={tabs.find((el) => el.id.toString() === active) || tabs[0]}
openedSwitch={() => {
setOpenedAPC(false);
setOpenedSwitch(true);
}}
/>
)}
{tabs.find((el) => el.id.toString() === active) && (
<DrawerSwitchControl
open={openedSwitch}
onClose={() => {
setOpenedSwitch(false);
}}
socket={socket}
stationAPI={tabs.find((el) => el.id.toString() === active) || tabs[0]}
openedAPC={() => {
setOpenedSwitch(false);
setOpenedAPC(true);
}}
/>
)}
</DndContext>
);
}

View File

@ -1,4 +1,4 @@
import { Box, Button, Card, Drawer, Grid, Loader, Text } from "@mantine/core";
import { Box, Button, Card, Grid, Loader, Text } from "@mantine/core";
import { IconRepeat, IconSection } from "@tabler/icons-react";
import { useEffect, useState } from "react";
import classes from "./Component.module.css";
@ -8,10 +8,6 @@ import type { Socket } from "socket.io-client";
interface DrawerProps {
stationAPI: TStation;
socket: Socket | null;
open: boolean;
onClose: () => void;
openedSwitch?: () => void;
openedAPC?: () => void;
}
type TSelectedOutlet = {
@ -24,9 +20,6 @@ type TSelectedOutlet = {
export const DrawerAPCControl: React.FC<DrawerProps> = ({
stationAPI,
socket,
open,
onClose,
openedSwitch,
}) => {
const findLineByOutlet = (outlet: TSelectedOutlet) => {
return stationAPI.lines.find(
@ -58,12 +51,6 @@ export const DrawerAPCControl: React.FC<DrawerProps> = ({
>([]);
const [isSubmit, setIsSubmit] = useState(true);
useEffect(() => {
if (!open) {
setListOutletSelected([]);
}
}, [open]);
const detectOutlet = (
apc: APCProps,
lines: string[],
@ -195,12 +182,12 @@ export const DrawerAPCControl: React.FC<DrawerProps> = ({
};
const RenderAPCStatus = (apc: APCProps) => {
switch (apc.status) {
switch (apc?.status) {
case "CONNECTED":
return (
<>
<Text size="sm" fw={800} c="green">
{apc.status}
<Text fw={800} c="green" fz={"12px"}>
{apc?.status}
</Text>
</>
);
@ -208,26 +195,24 @@ export const DrawerAPCControl: React.FC<DrawerProps> = ({
case "DISCONNECTED":
return (
<>
<div></div>
<Text size="sm" fw={800} c="red">
{apc.status}
<Text fw={800} c="red" fz={"12px"}>
{apc?.status}
</Text>
</>
);
case "TIMEOUT":
return (
<>
<Text size="sm" fw={800} c="yellow">
{apc.status}
<Text fw={800} c="yellow" fz={"12px"}>
{apc?.status}
</Text>
</>
);
default:
return (
<>
{/* <Text size="sm" fw={800} c="blue">
{line.status}
{/* <Text fw={800} c="red" fz={"12px"}>
WRONG CONFIG
</Text> */}
</>
);
@ -235,48 +220,15 @@ export const DrawerAPCControl: React.FC<DrawerProps> = ({
};
return (
<Drawer
style={{ position: "absolute", left: 0 }}
opened={open}
onClose={onClose}
title={
<div
style={{
display: "flex",
justifyContent: "space-between",
alignItems: "center",
}}
>
APC Control
<div style={{ marginLeft: "12px" }}>
<Button
miw={"80px"}
size="xs"
fz={"sm"}
variant="filled"
color="blue"
onClick={() => {
if (openedSwitch) openedSwitch();
}}
>
Switch Control
</Button>
</div>
</div>
}
size="xs"
position="bottom"
>
<Grid>
<Grid.Col span={6}>
<fieldset style={{ padding: "4px 6px" }}>
<legend>
<div className={classes.titleAPC}>
<Text fw={700} c={"#514d4d"} fz={"sm"}>
<Text fw={700} c={"#514d4d"} fz={"xs"}>
APC 1
</Text>
{dataStation?.apc1?.status &&
RenderAPCStatus(dataStation?.apc1)}
{RenderAPCStatus(dataStation?.apc1)}
{dataStation?.apc1?.status === "DISCONNECTED" ||
dataStation?.apc1?.status === "TIMEOUT" ? (
<Button
@ -320,12 +272,11 @@ export const DrawerAPCControl: React.FC<DrawerProps> = ({
className={`${isSubmit ? classes.isDisabled : ""}`}
style={{
position: "relative",
width: "90px",
width: "80px",
cursor: "pointer",
textAlign: "center",
border: listOutletSelected.find(
(el) =>
el.name === outlet.name && el.apc === outlet.apc
(el) => el.name === outlet.name && el.apc === outlet.apc
)?.name
? "1px solid #0018ff"
: "",
@ -336,7 +287,7 @@ export const DrawerAPCControl: React.FC<DrawerProps> = ({
>
<Text
fw={500}
fz={"14px"}
fz={"12px"}
style={{
color: outlet.status === "ON" ? "#40c057" : "#f03e3e",
}}
@ -352,7 +303,7 @@ export const DrawerAPCControl: React.FC<DrawerProps> = ({
style={{
display: "flex",
justifyContent: "left",
marginTop: "20px",
marginTop: "10px",
marginBottom: "10px",
gap: "20px",
}}
@ -368,7 +319,7 @@ export const DrawerAPCControl: React.FC<DrawerProps> = ({
// mt={'xs'}
miw={"80px"}
size="xs"
fz={"sm"}
fz={"xs"}
variant="filled"
onClick={() => {
if (
@ -410,7 +361,7 @@ export const DrawerAPCControl: React.FC<DrawerProps> = ({
// mt={'xs'}
miw={"80px"}
size="xs"
fz={"sm"}
fz={"xs"}
variant="filled"
color="yellow"
onClick={() => {
@ -454,7 +405,7 @@ export const DrawerAPCControl: React.FC<DrawerProps> = ({
// mt={'xs'}
miw={"80px"}
size="xs"
fz={"sm"}
fz={"xs"}
variant="filled"
color="green"
onClick={() => {
@ -498,7 +449,7 @@ export const DrawerAPCControl: React.FC<DrawerProps> = ({
// mt={'xs'}
miw={"80px"}
size="xs"
fz={"sm"}
fz={"xs"}
variant="filled"
color="red"
onClick={() => {
@ -531,11 +482,10 @@ export const DrawerAPCControl: React.FC<DrawerProps> = ({
<fieldset style={{ padding: "4px 6px" }}>
<legend>
<div className={classes.titleAPC}>
<Text fw={700} c={"#514d4d"} fz={"sm"}>
<Text fw={700} c={"#514d4d"} fz={"xs"}>
APC 2
</Text>
{dataStation?.apc2?.status &&
RenderAPCStatus(dataStation?.apc2)}
{RenderAPCStatus(dataStation?.apc2)}
{dataStation?.apc2?.status === "DISCONNECTED" ||
dataStation?.apc2?.status === "TIMEOUT" ? (
<Button
@ -579,12 +529,11 @@ export const DrawerAPCControl: React.FC<DrawerProps> = ({
className={`${isSubmit ? classes.isDisabled : ""}`}
style={{
position: "relative",
width: "90px",
width: "80px",
cursor: "pointer",
textAlign: "center",
border: listOutletSelected.find(
(el) =>
el.name === outlet.name && el.apc === outlet.apc
(el) => el.name === outlet.name && el.apc === outlet.apc
)?.name
? "1px solid #0018ff"
: "",
@ -595,7 +544,7 @@ export const DrawerAPCControl: React.FC<DrawerProps> = ({
>
<Text
fw={500}
fz={"14px"}
fz={"12px"}
style={{
color: outlet.status === "ON" ? "#40c057" : "#f03e3e",
}}
@ -611,7 +560,7 @@ export const DrawerAPCControl: React.FC<DrawerProps> = ({
style={{
display: "flex",
justifyContent: "left",
marginTop: "20px",
marginTop: "10px",
marginBottom: "10px",
gap: "20px",
}}
@ -627,7 +576,7 @@ export const DrawerAPCControl: React.FC<DrawerProps> = ({
// mt={'xs'}
miw={"80px"}
size="xs"
fz={"sm"}
fz={"xs"}
variant="filled"
onClick={() => {
if (
@ -669,7 +618,7 @@ export const DrawerAPCControl: React.FC<DrawerProps> = ({
// mt={'xs'}
miw={"80px"}
size="xs"
fz={"sm"}
fz={"xs"}
variant="filled"
color="yellow"
onClick={() => {
@ -714,7 +663,7 @@ export const DrawerAPCControl: React.FC<DrawerProps> = ({
// mt={'xs'}
miw={"80px"}
size="xs"
fz={"sm"}
fz={"xs"}
variant="filled"
color="green"
onClick={() => {
@ -758,7 +707,7 @@ export const DrawerAPCControl: React.FC<DrawerProps> = ({
// mt={'xs'}
miw={"80px"}
size="xs"
fz={"sm"}
fz={"xs"}
variant="filled"
color="red"
onClick={() => {
@ -788,16 +737,12 @@ export const DrawerAPCControl: React.FC<DrawerProps> = ({
</fieldset>
</Grid.Col>
</Grid>
</Drawer>
);
};
export const DrawerSwitchControl: React.FC<DrawerProps> = ({
stationAPI,
socket,
open,
onClose,
openedAPC,
}) => {
const [listPorts, setListPorts] = useState<SwitchPortsProps[][]>([]);
const [dataStation, setDataStation] = useState<TStation>(stationAPI);
@ -903,100 +848,10 @@ export const DrawerSwitchControl: React.FC<DrawerProps> = ({
});
};
return (
<Drawer
style={{ position: "absolute", left: 0 }}
opened={open}
onClose={onClose}
title={
dataStation?.switch ? (
<div className={classes.titleAPC}>
<Text fw={700} c={"#514d4d"} fz={"sm"}>
Switch Control
</Text>
{dataStation?.switch?.status &&
RenderAPCStatus(dataStation?.switch)}
{dataStation?.switch?.status === "DISCONNECTED" ||
dataStation?.switch?.status === "TIMEOUT" ? (
<Button
size="xs"
disabled={isSubmit}
variant="filled"
color="yellow"
onClick={() => {
socket?.emit("control_switch", {
ports:
listPortsSelected?.length > 0
? listPortsSelected.map((el) => el.name)
: listPorts.flat().map((el) => el.name),
command: "reconnect",
station: stationAPI,
ip: stationAPI?.switch_control_ip,
});
setIsSubmit(true);
setTimeout(() => {
setIsSubmit(false);
}, 10000);
}}
>
<IconRepeat size={14} />
</Button>
) : (
""
)}
<div
style={{
display: "flex",
justifyContent: "space-between",
}}
>
<Button
miw={"80px"}
size="xs"
fz={"sm"}
variant="filled"
color="blue"
onClick={() => {
if (openedAPC) openedAPC();
}}
>
APC Control
</Button>
</div>
</div>
) : (
<div
style={{
display: "flex",
justifyContent: "space-between",
alignItems: "center",
}}
>
Switch Control
<div style={{ marginLeft: "12px" }}>
<Button
miw={"80px"}
size="xs"
fz={"xs"}
variant="filled"
color="blue"
onClick={() => {
if (openedAPC) openedAPC();
}}
>
APC Control
</Button>
</div>
</div>
)
}
size="sm"
position="bottom"
>
{loading ? (
return loading ? (
<Box
style={{
height: "300px",
height: "10vh",
width: "100%",
display: "flex",
justifyContent: "center",
@ -1007,107 +862,6 @@ export const DrawerSwitchControl: React.FC<DrawerProps> = ({
</Box>
) : (
<Grid>
<Grid.Col span={12}>
{listPorts?.length > 0 && (
<Box>
<Grid>
{listPorts?.map((group, key) => (
<Grid.Col
key={key}
span={
group?.length > 20
? 11
: group?.length > 0 && group?.length < 4
? 1
: 12
}
>
<fieldset
style={{
padding: "4px 6px",
paddingBottom: "12px",
height: "-webkit-fill-available",
}}
>
<legend>
<Text fw={700} c={"#514d4d"} fz={"sm"}>
{group[0]?.name.substring(0, 2) || ""}
</Text>
</legend>
<Box
style={{
display: "flex",
flexWrap: "wrap",
justifyContent: "center",
gap: "10px",
overflowY: "auto",
maxHeight: "300px",
}}
>
{group?.length > 0 &&
sortedPorts(group)?.map((port, i) => (
<Card
key={i}
shadow="sm"
padding="xs"
radius="md"
withBorder
style={{
position: "relative",
width: "60px",
backgroundColor:
port.poe === "ON"
? "#f2dcf8"
: port.status === "ON"
? "#d4f1d3"
: "#f5f5f5",
cursor: "pointer",
border: listPortsSelected.find(
(el) => el.name === port.name
)?.name
? "1px solid #0018ff"
: "",
}}
className={`${
isSubmit ? classes.isDisabled : ""
}`}
onClick={() => {
toggleSelect(port);
}}
>
<Box
style={{
display: "flex",
alignItems: "center",
gap: "2px",
flexDirection: "column",
flexWrap: "wrap",
}}
>
<IconSection
size={"18px"}
color={
port.poe === "ON"
? "#b722d4"
: port.status === "ON"
? "#40c057"
: "#b8b8b8"
}
/>
<Text fw={500} fz={"12px"}>
{port.name}
</Text>
</Box>
</Card>
))}
</Box>
</fieldset>
</Grid.Col>
))}
</Grid>
</Box>
)}
</Grid.Col>
<div
style={{
display: "flex",
@ -1126,6 +880,9 @@ export const DrawerSwitchControl: React.FC<DrawerProps> = ({
gap: "20px",
}}
>
<Box ps={"8px"} pt={"4px"}>
{dataStation?.switch ? RenderAPCStatus(dataStation?.switch) : ""}
</Box>
<Button
disabled={isSubmit}
title={
@ -1136,7 +893,7 @@ export const DrawerSwitchControl: React.FC<DrawerProps> = ({
// mt={'xs'}
miw={"80px"}
size="xs"
fz={"sm"}
fz={"xs"}
variant="filled"
onClick={() => {
if (listPortsSelected.length === listPorts.flat().length) {
@ -1169,7 +926,7 @@ export const DrawerSwitchControl: React.FC<DrawerProps> = ({
// mt={'xs'}
miw={"80px"}
size="xs"
fz={"sm"}
fz={"xs"}
variant="filled"
color="yellow"
onClick={() => {
@ -1177,9 +934,7 @@ export const DrawerSwitchControl: React.FC<DrawerProps> = ({
listPortsSelected?.length > 0
? listPortsSelected
: listPorts.flat();
if (
listPortsRestart.filter((el) => el.poe !== "ON").length > 0
)
if (listPortsRestart.filter((el) => el.poe !== "ON").length > 0)
socket?.emit("control_switch", {
ports: listPortsRestart
.filter((el) => el.poe !== "ON")
@ -1188,9 +943,7 @@ export const DrawerSwitchControl: React.FC<DrawerProps> = ({
station: stationAPI,
ip: stationAPI?.switch_control_ip,
});
if (
listPortsRestart.filter((el) => el.poe === "ON").length > 0
)
if (listPortsRestart.filter((el) => el.poe === "ON").length > 0)
socket?.emit("control_switch", {
ports: listPortsRestart
.filter((el) => el.poe === "ON")
@ -1229,7 +982,7 @@ export const DrawerSwitchControl: React.FC<DrawerProps> = ({
// mt={'xs'}
miw={"80px"}
size="xs"
fz={"sm"}
fz={"xs"}
variant="filled"
color="green"
onClick={() => {
@ -1237,9 +990,7 @@ export const DrawerSwitchControl: React.FC<DrawerProps> = ({
listPortsSelected?.length > 0
? listPortsSelected
: listPorts.flat();
if (
listPortsRestart.filter((el) => el.poe !== "ON").length > 0
)
if (listPortsRestart.filter((el) => el.poe !== "ON").length > 0)
socket?.emit("control_switch", {
ports: listPortsRestart
.filter((el) => el.poe !== "ON")
@ -1248,9 +999,7 @@ export const DrawerSwitchControl: React.FC<DrawerProps> = ({
station: stationAPI,
ip: stationAPI?.switch_control_ip,
});
if (
listPortsRestart.filter((el) => el.poe === "ON").length > 0
)
if (listPortsRestart.filter((el) => el.poe === "ON").length > 0)
socket?.emit("control_switch", {
ports: listPortsRestart
.filter((el) => el.poe === "ON")
@ -1289,7 +1038,7 @@ export const DrawerSwitchControl: React.FC<DrawerProps> = ({
// mt={'xs'}
miw={"80px"}
size="xs"
fz={"sm"}
fz={"xs"}
variant="filled"
color="red"
onClick={() => {
@ -1297,9 +1046,7 @@ export const DrawerSwitchControl: React.FC<DrawerProps> = ({
listPortsSelected?.length > 0
? listPortsSelected
: listPorts.flat();
if (
listPortsRestart.filter((el) => el.poe !== "ON").length > 0
)
if (listPortsRestart.filter((el) => el.poe !== "ON").length > 0)
socket?.emit("control_switch", {
ports: listPortsRestart
.filter((el) => el.poe !== "ON")
@ -1308,9 +1055,7 @@ export const DrawerSwitchControl: React.FC<DrawerProps> = ({
station: stationAPI,
ip: stationAPI?.switch_control_ip,
});
if (
listPortsRestart.filter((el) => el.poe === "ON").length > 0
)
if (listPortsRestart.filter((el) => el.poe === "ON").length > 0)
socket?.emit("control_switch", {
ports: listPortsRestart
.filter((el) => el.poe === "ON")
@ -1333,8 +1078,106 @@ export const DrawerSwitchControl: React.FC<DrawerProps> = ({
</Button>
</div>
</div>
<Grid.Col span={12} pt={0}>
{listPorts?.length > 0 && (
<Box>
<Grid>
{listPorts?.map((group, key) => (
<Grid.Col
key={key}
span={
group?.length > 20
? 11
: group?.length > 0 && group?.length < 4
? 1
: 12
}
>
<fieldset
style={{
padding: "4px 6px",
paddingBottom: "12px",
height: "-webkit-fill-available",
}}
>
<legend>
<Text fw={700} c={"#514d4d"} fz={"xs"}>
{group[0]?.name.substring(0, 2) || ""}
</Text>
</legend>
<Box
style={{
display: "flex",
flexWrap: "wrap",
justifyContent: "center",
gap: "10px",
overflowY: "auto",
maxHeight: "100px",
}}
>
{group?.length > 0 &&
sortedPorts(group)?.map((port, i) => (
<Card
key={i}
shadow="sm"
padding="xs"
radius="md"
withBorder
style={{
position: "relative",
width: "60px",
backgroundColor:
port.poe === "ON"
? "#f2dcf8"
: port.status === "ON"
? "#d4f1d3"
: "#f5f5f5",
cursor: "pointer",
border: listPortsSelected.find(
(el) => el.name === port.name
)?.name
? "1px solid #0018ff"
: "",
}}
className={`${isSubmit ? classes.isDisabled : ""}`}
onClick={() => {
toggleSelect(port);
}}
>
<Box
style={{
display: "flex",
alignItems: "center",
gap: "2px",
flexDirection: "column",
flexWrap: "wrap",
}}
>
<IconSection
size={"12px"}
color={
port.poe === "ON"
? "#b722d4"
: port.status === "ON"
? "#40c057"
: "#b8b8b8"
}
/>
<Text fw={500} fz={"11px"}>
{port.name}
</Text>
</Box>
</Card>
))}
</Box>
</fieldset>
</Grid.Col>
))}
</Grid>
</Box>
)}
</Drawer>
</Grid.Col>
</Grid>
);
};

View File

@ -177,6 +177,7 @@ function DrawerLogs({
</Drawer>
<Button
style={{ height: "30px", width: "100px" }}
title="Add Scenario"
variant="outline"
// color="green"

View File

@ -46,7 +46,6 @@ const TerminalCLI: React.FC<TerminalCLIProps> = ({
loadingContent = false,
onFocus,
onBlur,
line,
}) => {
const xtermRef = useRef<HTMLDivElement>(null);
const terminal = useRef<Terminal>(null);