Enhance switch and APC controls, UI improvements

Refactored switch port restart logic to use turnPortOff/on methods. Improved socket communication for switch port status and clear line actions. Updated DrawerControl and ModalTerminal to streamline APC and switch controls, added filtering and persistent view options for switch ports, and improved UI consistency. Fixed ticket creation logic and enhanced terminal and toolbar layouts for better usability.
This commit is contained in:
nguyentrungthat 2025-11-20 14:39:38 +07:00
parent 1e058636b2
commit f695062ec4
9 changed files with 659 additions and 542 deletions

View File

@ -123,14 +123,17 @@ class APCController {
}
}
private _handleError(err: NodeJS.ErrnoException): void {
private async _handleError(err: NodeJS.ErrnoException): Promise<void> {
this.output += `\r\n\r\n[ERROR] ${err.message}`
this.onData(this.output, this.status)
if (err.code === 'ECONNRESET') {
setTimeout(() => {
console.log('[ECONNRESET] Trying reconnect apc:', this.apc_ip)
this.reconnect()
}, 15000)
console.log('[ECONNRESET] Trying reconnect apc:', this.apc_ip)
if (this.retryConnect <= 5) {
await this.sleep(15000)
console.log('Retry connect times', this.retryConnect)
this.retryConnect += 1
await this.reconnect()
}
}
}

View File

@ -147,7 +147,7 @@ export default class LineConnection {
this.socketIO.emit('line_error', {
stationId,
lineId: id,
error: '\n' + err.message + '\n',
error: '\r\n' + err.message + '\r\n',
})
resolve()
})

View File

@ -214,21 +214,26 @@ export default class SwitchController {
}
public async restartPort(port: string) {
await this.enterEnableMode()
this._send(`configure terminal`)
// await this._waitFor('(config)#')
await this.sleep(500)
this._send(`interface ${port}`)
// await this._waitFor('(config-if)#')
await this.sleep(500)
this._send(`shutdown`)
// await this._waitFor('(config-if)#')
await this.sleep(500)
await this.sleep(2000)
this._send(`no shutdown`)
// await this._waitFor('(config-if)#')
await this.sleep(500)
this._send(`end`)
// await this.enterEnableMode()
// this._send(`configure terminal`)
// // await this._waitFor('(config)#')
// await this.sleep(500)
// this._send(`interface ${port}`)
// // await this._waitFor('(config-if)#')
// await this.sleep(500)
// this._send(`shutdown`)
// // await this._waitFor('(config-if)#')
// await this.sleep(500)
// await this.sleep(2000)
// this._send(`no shutdown`)
// // await this._waitFor('(config-if)#')
// await this.sleep(500)
// this._send(`end`)
await this.turnPortOff(port)
await this.sleep(300)
await this.getPorts()
await this.sleep(300)
await this.turnPortOn(port)
}
public async disablePoE(port: string) {

View File

@ -380,6 +380,13 @@ export class WebSocketIo {
portGroups: element.portGroups,
status: element.status,
})
socket.emit('switch_ports_status', {
stationId: station.id,
ports:
Array.isArray(element.portGroups) && element.portGroups?.length > 0
? element.portGroups?.flat()
: [],
})
} else if (element && element.status !== 'CONNECTED') {
await element.reconnect()
} else await this.connectSwitch(io, station)
@ -460,6 +467,11 @@ export class WebSocketIo {
line.config = { ...line.config, ...update }
}
})
socket.on('clear_line', async (data) => {
const { stationId, lineClear } = data
await this.clearLineBeforeConnect(stationId, lineClear)
})
})
socketServer.listen(SOCKET_IO_PORT, () => {
@ -659,6 +671,10 @@ export class WebSocketIo {
portGroups: ports,
status,
})
socket.emit('switch_ports_status', {
stationId: station.id,
ports: Array.isArray(ports) && ports?.length > 0 ? ports?.flat() : [],
})
},
})
// Connect và login

View File

@ -411,7 +411,7 @@ function App() {
borderRadius: 8,
}}
>
<ScrollArea h={expandedBottomBar ? "73vh" : "85vh"}>
<ScrollArea h={expandedBottomBar ? "70vh" : "85vh"}>
{station.lines.length > 8 ? (
<Grid
style={{
@ -517,13 +517,11 @@ function App() {
onChange={(id) => {
if (selectedLines.length > 0) {
selectedLines.forEach((el) => {
if (
el?.userOpenCLI === user?.userName &&
!selectedLines.find((value) => value.id === el?.id)
)
console.log(el?.userOpenCLI, user?.userName);
if (el?.userOpenCLI === user?.userName)
socket?.emit("close_cli", {
lineId: el?.id,
stationId: el?.station_id,
stationId: Number(activeTab),
});
});
}
@ -532,7 +530,7 @@ function App() {
setLoadingTerminal(false);
setTimeout(() => {
setLoadingTerminal(true);
}, 100);
}, 500);
}}
setActive={setActiveTab}
active={activeTab}

View File

@ -69,7 +69,7 @@ const BottomToolBar = ({
<motion.div
initial={false}
animate={{
height: isExpand ? "15vh" : 0,
height: isExpand ? "18vh" : 0,
y: 0, // đẩy xuống khi thu nhỏ
}}
transition={{ type: "spring", stiffness: 180, damping: 20 }}
@ -114,7 +114,7 @@ const BottomToolBar = ({
setActiveTabBottom(val || "command");
}}
className={classes.containerBottom}
style={{ height: "14vh" }}
style={{ height: "18vh" }}
>
<Tabs.List>
<Tabs.Tab
@ -156,7 +156,7 @@ const BottomToolBar = ({
<Tabs.Panel value="command" p={"xs"}>
<Flex justify={"space-between"}>
<ScrollArea h={"10vh"}>
<ScrollArea h={"13vh"}>
<Flex wrap={"wrap"} gap={"xs"} w={"400px"}>
{selectedLines.map((el) => (
<Box
@ -314,7 +314,12 @@ const BottomToolBar = ({
}, 5000);
}}
/>
<Menu shadow="md" position="top">
<Menu
trigger="hover"
withArrow
shadow="md"
position="top"
>
<Menu.Target>
<Button
fw={400}

View File

@ -4,7 +4,9 @@ import {
Card,
Flex,
Grid,
Group,
Loader,
Radio,
ScrollArea,
Text,
} from "@mantine/core";
@ -254,7 +256,7 @@ export const DrawerAPCControl: React.FC<DrawerProps> = ({
onClick={() => {
socket?.emit("control_apc", {
outletNumbers: [],
station: stationAPI,
station: { ...stationAPI, lines: [] },
action: "reconnect",
apcName: "apc_1",
});
@ -270,10 +272,144 @@ export const DrawerAPCControl: React.FC<DrawerProps> = ({
) : (
<div style={{ height: "24px" }}></div>
)}
<Button
disabled={isSubmit}
title={
listOutletSelected.filter((el) => el.apc === 1).length ===
listOutlet.length
? "Deselect All"
: "Select All"
}
// mt={'xs'}
className={classes.buttonMenuTool}
variant="filled"
onClick={() => {
if (
listOutletSelected.filter((el) => el.apc === 1).length ===
listOutlet.filter((el) => el.apc === 1).length
) {
setListOutletSelected([]);
} else {
setListOutletSelected(
listOutlet
.filter((el) => el.apc === 1)
.map((el) => ({ ...el, apc: 1 }))
);
}
}}
>
{listOutletSelected.filter((el) => el.apc === 1).length ===
listOutlet.filter((el) => el.apc === 1).length
? "Deselect"
: "Select All"}
</Button>
<Button
className={classes.buttonMenuTool}
disabled={
isSubmit ||
listOutlet.filter((el) => el.apc === 1).length === 0 ||
listOutletSelected.filter((el) => el.apc === 1).length ===
0 ||
dataStation?.apc1?.status === "DISCONNECTED" ||
dataStation?.apc1?.status === "TIMEOUT"
}
title={"Restart"}
variant="filled"
color="yellow"
onClick={() => {
socket?.emit("control_apc", {
outletNumbers: listOutletSelected
.filter((el) => el.apc === 1)
.map((el) => el.number),
station: { ...stationAPI, lines: [] },
action: "restart",
apcName: "apc_1",
});
setListOutletSelected([]);
setIsSubmit(true);
setTimeout(() => {
setIsSubmit(false);
}, 5000);
}}
>
Restart
</Button>
<Button
disabled={
isSubmit ||
listOutlet.filter((el) => el.apc === 1).length === 0 ||
listOutletSelected.filter((el) => el.apc === 1).length ===
0 ||
dataStation?.apc1?.status === "DISCONNECTED" ||
dataStation?.apc1?.status === "TIMEOUT"
}
title={"Turn On"}
// mt={'xs'}
className={classes.buttonMenuTool}
variant="filled"
color="green"
onClick={() => {
socket?.emit("control_apc", {
outletNumbers: listOutletSelected
.filter((el) => el.apc === 1)
.map((el) => el.number),
station: { ...stationAPI, lines: [] },
action: "on",
apcName: "apc_1",
});
setListOutletSelected([]);
setIsSubmit(true);
setTimeout(() => {
setIsSubmit(false);
}, 5000);
}}
>
Turn On
</Button>
<Button
disabled={
isSubmit ||
listOutlet.filter((el) => el.apc === 1).length === 0 ||
listOutletSelected.filter((el) => el.apc === 1).length ===
0 ||
dataStation?.apc1?.status === "DISCONNECTED" ||
dataStation?.apc1?.status === "TIMEOUT"
}
title={"Turn Off"}
// mt={'xs'}
className={classes.buttonMenuTool}
variant="filled"
color="red"
onClick={() => {
socket?.emit("control_apc", {
outletNumbers: listOutletSelected
.filter((el) => el.apc === 1)
.map((el) => el.number),
station: { ...stationAPI, lines: [] },
action: "off",
apcName: "apc_1",
});
setListOutletSelected([]);
setIsSubmit(true);
setTimeout(() => {
setIsSubmit(false);
}, 5000);
}}
>
Turn Off
</Button>
</div>
</legend>
<Box>
<Box style={{ display: "flex", flexWrap: "wrap", gap: "10px" }}>
<Box
style={{
display: "flex",
flexWrap: "wrap",
gap: "10px",
paddingTop: "8px",
paddingBottom: "8px",
}}
>
{listOutlet
.filter((el) => el.apc === 1)
.map((outlet, i) => (
@ -315,182 +451,6 @@ export const DrawerAPCControl: React.FC<DrawerProps> = ({
</Card>
))}
</Box>
<div
style={{
display: "flex",
justifyContent: "left",
marginTop: "10px",
// marginBottom: "10px",
gap: "20px",
}}
>
<Button
disabled={isSubmit}
title={
listOutletSelected.filter((el) => el.apc === 1).length ===
listOutlet.length
? "Deselect All"
: "Select All"
}
// mt={'xs'}
miw={"80px"}
size="xs"
fz={"xs"}
variant="filled"
onClick={() => {
if (
listOutletSelected.filter((el) => el.apc === 1).length ===
listOutlet.filter((el) => el.apc === 1).length
) {
setListOutletSelected([]);
} else {
setListOutletSelected(
listOutlet
.filter((el) => el.apc === 1)
.map((el) => ({ ...el, apc: 1 }))
);
}
}}
>
{listOutletSelected.filter((el) => el.apc === 1).length ===
listOutlet.filter((el) => el.apc === 1).length
? "Deselect All"
: "Select All"}{" "}
{"(" + listOutlet.filter((el) => el.apc === 1).length + ")"}
</Button>
<Button
disabled={
isSubmit ||
listOutlet.filter((el) => el.apc === 1).length === 0 ||
listOutletSelected.filter((el) => el.apc === 1).length ===
0 ||
dataStation?.apc1?.status === "DISCONNECTED" ||
dataStation?.apc1?.status === "TIMEOUT"
}
title={
listOutletSelected.filter((el) => el.apc === 1).length ===
listOutlet.filter((el) => el.apc === 1).length ||
listOutletSelected.filter((el) => el.apc === 1).length === 0
? "Restart All"
: "Restart Selected"
}
// mt={'xs'}
miw={"80px"}
size="xs"
fz={"xs"}
variant="filled"
color="yellow"
onClick={() => {
socket?.emit("control_apc", {
outletNumbers: listOutletSelected
.filter((el) => el.apc === 1)
.map((el) => el.number),
station: stationAPI,
action: "restart",
apcName: "apc_1",
});
setListOutletSelected([]);
setIsSubmit(true);
setTimeout(() => {
setIsSubmit(false);
}, 5000);
}}
>
{listOutletSelected.filter((el) => el.apc === 1).length ===
listOutlet.filter((el) => el.apc === 1).length ||
listOutletSelected.filter((el) => el.apc === 1).length === 0
? "Restart All"
: "Restart Selected"}
</Button>
<Button
disabled={
isSubmit ||
listOutlet.filter((el) => el.apc === 1).length === 0 ||
listOutletSelected.filter((el) => el.apc === 1).length ===
0 ||
dataStation?.apc1?.status === "DISCONNECTED" ||
dataStation?.apc1?.status === "TIMEOUT"
}
title={
listOutletSelected.filter((el) => el.apc === 1).length ===
listOutlet.filter((el) => el.apc === 1).length ||
listOutletSelected.filter((el) => el.apc === 1).length === 0
? "Turn On All"
: "Turn On Selected"
}
// mt={'xs'}
miw={"80px"}
size="xs"
fz={"xs"}
variant="filled"
color="green"
onClick={() => {
socket?.emit("control_apc", {
outletNumbers: listOutletSelected
.filter((el) => el.apc === 1)
.map((el) => el.number),
station: stationAPI,
action: "on",
apcName: "apc_1",
});
setListOutletSelected([]);
setIsSubmit(true);
setTimeout(() => {
setIsSubmit(false);
}, 5000);
}}
>
{listOutletSelected.filter((el) => el.apc === 1).length ===
listOutlet.filter((el) => el.apc === 1).length ||
listOutletSelected.filter((el) => el.apc === 1).length === 0
? "Turn On All"
: "Turn On Selected"}
</Button>
<Button
disabled={
isSubmit ||
listOutlet.filter((el) => el.apc === 1).length === 0 ||
listOutletSelected.filter((el) => el.apc === 1).length ===
0 ||
dataStation?.apc1?.status === "DISCONNECTED" ||
dataStation?.apc1?.status === "TIMEOUT"
}
title={
listOutletSelected.filter((el) => el.apc === 1).length ===
listOutlet.filter((el) => el.apc === 1).length ||
listOutletSelected.filter((el) => el.apc === 1).length === 0
? "Turn Off All"
: "Turn Off Selected"
}
// mt={'xs'}
miw={"80px"}
size="xs"
fz={"xs"}
variant="filled"
color="red"
onClick={() => {
socket?.emit("control_apc", {
outletNumbers: listOutletSelected
.filter((el) => el.apc === 1)
.map((el) => el.number),
station: stationAPI,
action: "off",
apcName: "apc_1",
});
setListOutletSelected([]);
setIsSubmit(true);
setTimeout(() => {
setIsSubmit(false);
}, 5000);
}}
>
{listOutletSelected.filter((el) => el.apc === 1).length ===
listOutlet.filter((el) => el.apc === 1).length ||
listOutletSelected.filter((el) => el.apc === 1).length === 0
? "Turn Off All"
: "Turn Off Selected"}
</Button>
</div>
</Box>
</fieldset>
</Grid.Col>
@ -512,7 +472,7 @@ export const DrawerAPCControl: React.FC<DrawerProps> = ({
onClick={() => {
socket?.emit("control_apc", {
outletNumbers: [],
station: stationAPI,
station: { ...stationAPI, lines: [] },
action: "reconnect",
apcName: "apc_2",
});
@ -528,10 +488,145 @@ export const DrawerAPCControl: React.FC<DrawerProps> = ({
) : (
<div style={{ height: "24px" }}></div>
)}
<Button
disabled={isSubmit}
title={
listOutletSelected.filter((el) => el.apc === 2).length ===
listOutlet.length
? "Deselect All"
: "Select All"
}
className={classes.buttonMenuTool}
variant="filled"
onClick={() => {
if (
listOutletSelected.filter((el) => el.apc === 2).length ===
listOutlet.filter((el) => el.apc === 2).length
) {
setListOutletSelected([]);
} else {
setListOutletSelected(
listOutlet
.filter((el) => el.apc === 2)
.map((el) => ({ ...el }))
);
}
}}
>
{listOutletSelected.filter((el) => el.apc === 2).length ===
listOutlet.filter((el) => el.apc === 2).length
? "Deselect"
: "Select All"}
</Button>
<Button
disabled={
isSubmit ||
listOutlet.filter((el) => el.apc === 2).length === 0 ||
listOutletSelected.filter((el) => el.apc === 2).length ===
0 ||
dataStation?.apc2?.status === "DISCONNECTED" ||
dataStation?.apc2?.status === "TIMEOUT"
}
title={"Restart"}
className={classes.buttonMenuTool}
variant="filled"
color="yellow"
onClick={() => {
socket?.emit("control_apc", {
outletNumbers: listOutletSelected
.filter((el) => el.apc === 2)
.map((el) => el.number),
station: { ...stationAPI, lines: [] },
action: "restart",
apcName: "apc_2",
});
setListOutletSelected([]);
setIsSubmit(true);
setTimeout(() => {
setIsSubmit(false);
}, 5000);
}}
>
Restart
</Button>
<Button
disabled={
isSubmit ||
listOutlet.filter((el) => el.apc === 2).length === 0 ||
listOutletSelected.filter((el) => el.apc === 2).length ===
0 ||
dataStation?.apc2?.status === "DISCONNECTED" ||
dataStation?.apc2?.status === "TIMEOUT"
}
title={"Turn On All"}
className={classes.buttonMenuTool}
variant="filled"
color="green"
onClick={() => {
socket?.emit("control_apc", {
outletNumbers: listOutletSelected
.filter((el) => el.apc === 2)
.map((el) => el.number),
station: { ...stationAPI, lines: [] },
action: "on",
apcName: "apc_2",
});
setListOutletSelected([]);
setIsSubmit(true);
setTimeout(() => {
setIsSubmit(false);
}, 5000);
}}
>
Turn On
</Button>
<Button
disabled={
isSubmit ||
listOutlet.filter((el) => el.apc === 2).length === 0 ||
listOutletSelected.filter((el) => el.apc === 2).length ===
0 ||
dataStation?.apc2?.status === "DISCONNECTED" ||
dataStation?.apc2?.status === "TIMEOUT"
}
className={classes.buttonMenuTool}
title={"Turn Off"}
// mt={'xs'}
miw={"80px"}
size="xs"
fz={"xs"}
variant="filled"
color="red"
onClick={() => {
socket?.emit("control_apc", {
outletNumbers: listOutletSelected
.filter((el) => el.apc === 2)
.map((el) => el.number),
station: { ...stationAPI, lines: [] },
action: "off",
apcName: "apc_2",
});
setListOutletSelected([]);
setIsSubmit(true);
setTimeout(() => {
setIsSubmit(false);
}, 5000);
}}
>
Turn Off
</Button>
</div>
</legend>
<Box>
<Box style={{ display: "flex", flexWrap: "wrap", gap: "10px" }}>
<Box
style={{
display: "flex",
flexWrap: "wrap",
gap: "10px",
paddingTop: "8px",
paddingBottom: "8px",
}}
>
{listOutlet
.filter((el) => el.apc === 2)
.map((outlet, i) => (
@ -573,183 +668,6 @@ export const DrawerAPCControl: React.FC<DrawerProps> = ({
</Card>
))}
</Box>
<div
style={{
display: "flex",
justifyContent: "left",
marginTop: "10px",
// marginBottom: "10px",
gap: "20px",
}}
>
<Button
disabled={isSubmit}
title={
listOutletSelected.filter((el) => el.apc === 2).length ===
listOutlet.length
? "Deselect All"
: "Select All"
}
// mt={'xs'}
miw={"80px"}
size="xs"
fz={"xs"}
variant="filled"
onClick={() => {
if (
listOutletSelected.filter((el) => el.apc === 2).length ===
listOutlet.filter((el) => el.apc === 2).length
) {
setListOutletSelected([]);
} else {
setListOutletSelected(
listOutlet
.filter((el) => el.apc === 2)
.map((el) => ({ ...el }))
);
}
}}
>
{listOutletSelected.filter((el) => el.apc === 2).length ===
listOutlet.filter((el) => el.apc === 2).length
? "Deselect All"
: "Select All"}{" "}
{"(" + listOutlet.filter((el) => el.apc === 2).length + ")"}
</Button>
<Button
disabled={
isSubmit ||
listOutlet.filter((el) => el.apc === 2).length === 0 ||
listOutletSelected.filter((el) => el.apc === 2).length ===
0 ||
dataStation?.apc2?.status === "DISCONNECTED" ||
dataStation?.apc2?.status === "TIMEOUT"
}
title={
listOutletSelected.filter((el) => el.apc === 2).length ===
listOutlet.filter((el) => el.apc === 2).length ||
listOutletSelected.filter((el) => el.apc === 2).length === 0
? "Restart All"
: "Restart Selected"
}
// mt={'xs'}
miw={"80px"}
size="xs"
fz={"xs"}
variant="filled"
color="yellow"
onClick={() => {
socket?.emit("control_apc", {
outletNumbers: listOutletSelected
.filter((el) => el.apc === 2)
.map((el) => el.number),
station: stationAPI,
action: "restart",
apcName: "apc_2",
});
setListOutletSelected([]);
setIsSubmit(true);
setTimeout(() => {
setIsSubmit(false);
}, 5000);
}}
>
{listOutletSelected.filter((el) => el.apc === 2).length === 0
? "Restart All"
: "Restart Selected"}
</Button>
<Button
disabled={
isSubmit ||
listOutlet.filter((el) => el.apc === 2).length === 0 ||
listOutletSelected.filter((el) => el.apc === 2).length ===
0 ||
dataStation?.apc2?.status === "DISCONNECTED" ||
dataStation?.apc2?.status === "TIMEOUT"
}
title={
listOutletSelected.filter((el) => el.apc === 2).length ===
listOutlet.filter((el) => el.apc === 2).length ||
listOutletSelected.filter((el) => el.apc === 2).length ===
0 ||
dataStation?.apc2?.status === "DISCONNECTED" ||
dataStation?.apc2?.status === "TIMEOUT"
? "Turn On All"
: "Turn On Selected"
}
// mt={'xs'}
miw={"80px"}
size="xs"
fz={"xs"}
variant="filled"
color="green"
onClick={() => {
socket?.emit("control_apc", {
outletNumbers: listOutletSelected
.filter((el) => el.apc === 2)
.map((el) => el.number),
station: stationAPI,
action: "on",
apcName: "apc_2",
});
setListOutletSelected([]);
setIsSubmit(true);
setTimeout(() => {
setIsSubmit(false);
}, 5000);
}}
>
{listOutletSelected.filter((el) => el.apc === 2).length ===
listOutlet.filter((el) => el.apc === 2).length ||
listOutletSelected.filter((el) => el.apc === 2).length === 0
? "Turn On All"
: "Turn On Selected"}
</Button>
<Button
disabled={
isSubmit ||
listOutlet.filter((el) => el.apc === 2).length === 0 ||
listOutletSelected.filter((el) => el.apc === 2).length ===
0 ||
dataStation?.apc2?.status === "DISCONNECTED" ||
dataStation?.apc2?.status === "TIMEOUT"
}
title={
listOutletSelected.filter((el) => el.apc === 2).length ===
listOutlet.filter((el) => el.apc === 2).length ||
listOutletSelected.filter((el) => el.apc === 2).length === 0
? "Turn Off All"
: "Turn Off Selected"
}
// mt={'xs'}
miw={"80px"}
size="xs"
fz={"xs"}
variant="filled"
color="red"
onClick={() => {
socket?.emit("control_apc", {
outletNumbers: listOutletSelected
.filter((el) => el.apc === 2)
.map((el) => el.number),
station: stationAPI,
action: "off",
apcName: "apc_2",
});
setListOutletSelected([]);
setIsSubmit(true);
setTimeout(() => {
setIsSubmit(false);
}, 5000);
}}
>
{listOutletSelected.filter((el) => el.apc === 2).length ===
listOutlet.filter((el) => el.apc === 2).length ||
listOutletSelected.filter((el) => el.apc === 2).length === 0
? "Turn Off All"
: "Turn Off Selected"}
</Button>
</div>
</Box>
</fieldset>
</Grid.Col>
@ -768,6 +686,7 @@ export const DrawerSwitchControl: React.FC<DrawerProps> = ({
>([]);
const [isSubmit, setIsSubmit] = useState(false);
const [loading, setLoading] = useState(true);
const [checkedActive, setCheckedActive] = useState("all");
useEffect(() => {
if (!open) {
@ -776,6 +695,13 @@ export const DrawerSwitchControl: React.FC<DrawerProps> = ({
}
}, [open]);
useEffect(() => {
const value = localStorage.getItem("show-switch-port");
if (value) {
setCheckedActive(value);
}
}, []);
useEffect(() => {
if (loading)
setTimeout(() => {
@ -881,6 +807,11 @@ export const DrawerSwitchControl: React.FC<DrawerProps> = ({
return `${type}${last}`;
};
const changeShowPort = (status: string) => {
localStorage.setItem("show-switch-port", status);
setCheckedActive(status);
};
return loading ? (
<Box
style={{
@ -932,7 +863,7 @@ export const DrawerSwitchControl: React.FC<DrawerProps> = ({
socket?.emit("control_switch", {
ports: [],
command: "reconnect",
station: stationAPI,
station: { ...stationAPI, lines: [] },
ip: stationAPI?.switch_control_ip,
});
setIsSubmit(true);
@ -1002,7 +933,7 @@ export const DrawerSwitchControl: React.FC<DrawerProps> = ({
.filter((el) => el.poe !== "ON")
.map((el) => el.name),
command: "restart",
station: stationAPI,
station: { ...stationAPI, lines: [] },
ip: stationAPI?.switch_control_ip,
});
if (listPortsRestart.filter((el) => el.poe === "ON").length > 0)
@ -1011,7 +942,7 @@ export const DrawerSwitchControl: React.FC<DrawerProps> = ({
.filter((el) => el.poe === "ON")
.map((el) => el.name),
command: "restart-poe",
station: stationAPI,
station: { ...stationAPI, lines: [] },
ip: stationAPI?.switch_control_ip,
});
setListPortsSelected([]);
@ -1058,7 +989,7 @@ export const DrawerSwitchControl: React.FC<DrawerProps> = ({
.filter((el) => el.poe !== "ON")
.map((el) => el.name),
command: "on",
station: stationAPI,
station: { ...stationAPI, lines: [] },
ip: stationAPI?.switch_control_ip,
});
if (listPortsRestart.filter((el) => el.poe === "ON").length > 0)
@ -1067,7 +998,7 @@ export const DrawerSwitchControl: React.FC<DrawerProps> = ({
.filter((el) => el.poe === "ON")
.map((el) => el.name),
command: "on-poe",
station: stationAPI,
station: { ...stationAPI, lines: [] },
ip: stationAPI?.switch_control_ip,
});
setListPortsSelected([]);
@ -1114,7 +1045,7 @@ export const DrawerSwitchControl: React.FC<DrawerProps> = ({
.filter((el) => el.poe !== "ON")
.map((el) => el.name),
command: "off",
station: stationAPI,
station: { ...stationAPI, lines: [] },
ip: stationAPI?.switch_control_ip,
});
if (listPortsRestart.filter((el) => el.poe === "ON").length > 0)
@ -1123,7 +1054,7 @@ export const DrawerSwitchControl: React.FC<DrawerProps> = ({
.filter((el) => el.poe === "ON")
.map((el) => el.name),
command: "off-poe",
station: stationAPI,
station: { ...stationAPI, lines: [] },
ip: stationAPI?.switch_control_ip,
});
setListPortsSelected([]);
@ -1138,6 +1069,26 @@ export const DrawerSwitchControl: React.FC<DrawerProps> = ({
? "Turn Off All"
: "Turn Off Selected"}
</Button>
<Group ms="xs" style={{ display: "flex", gap: "8px" }}>
<Radio
value="all"
label="All"
checked={checkedActive === "all"}
onChange={() => changeShowPort("all")}
/>
<Radio
value="on"
label="On"
checked={checkedActive === "on"}
onChange={() => changeShowPort("on")}
/>
<Radio
value="off"
label="Off"
checked={checkedActive === "off"}
onChange={() => changeShowPort("off")}
/>
</Group>
</div>
</div>
@ -1155,10 +1106,17 @@ export const DrawerSwitchControl: React.FC<DrawerProps> = ({
span={isLarge ? 11 : isMini ? 1 : 12}
>
{isLarge ? (
<ScrollArea h={"7vh"} w={"68vw"}>
<ScrollArea h={"12vh"} w={"68vw"}>
<Flex gap={"8px"} wrap={"nowrap"}>
{sortedPorts(group)
.slice(0, sortedPorts(group).length / 2)
.filter((el) => {
if (checkedActive === "all") return true;
if (checkedActive === "on")
return el.status === "ON";
if (checkedActive === "off")
return el.status === "OFF";
})
?.map((port, i) => (
<Card
key={i}
@ -1212,6 +1170,13 @@ export const DrawerSwitchControl: React.FC<DrawerProps> = ({
sortedPorts(group).length / 2,
sortedPorts(group).length
)
.filter((el) => {
if (checkedActive === "all") return true;
if (checkedActive === "on")
return el.status === "ON";
if (checkedActive === "off")
return el.status === "OFF";
})
?.map((port, i) => (
<Card
key={i}
@ -1268,49 +1233,59 @@ export const DrawerSwitchControl: React.FC<DrawerProps> = ({
justifyContent: "center",
gap: "10px",
overflow: "auto",
maxHeight: "7vh",
maxHeight: "12vh",
maxWidth: "70vw",
borderLeft: "1px solid #dedede",
}}
>
{sortedPorts(group)?.map((port, i) => (
<Card
key={i}
shadow="sm"
padding="xs"
radius="md"
withBorder
style={{
position: "relative",
width: "50px",
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
{sortedPorts(group)
?.filter((el) => {
if (checkedActive === "all") return true;
if (checkedActive === "on")
return el.status === "ON";
if (checkedActive === "off")
return el.status === "OFF";
})
?.map((port, i) => (
<Card
key={i}
shadow="sm"
padding="xs"
radius="md"
withBorder
style={{
display: "flex",
alignItems: "center",
gap: "2px",
flexDirection: "column",
flexWrap: "wrap",
position: "relative",
width: "50px",
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);
}}
>
{/* <IconSection
<Box
style={{
display: "flex",
alignItems: "center",
gap: "2px",
flexDirection: "column",
flexWrap: "wrap",
}}
>
{/* <IconSection
size={"12px"}
color={
port.poe === "ON"
@ -1320,12 +1295,12 @@ export const DrawerSwitchControl: React.FC<DrawerProps> = ({
: "#b8b8b8"
}
/> */}
<Text fw={500} fz={"11px"}>
{normalizePortName(port.name)}
</Text>
</Box>
</Card>
))}
<Text fw={500} fz={"11px"}>
{normalizePortName(port.name)}
</Text>
</Box>
</Card>
))}
</Box>
)}
</Grid.Col>

View File

@ -13,6 +13,7 @@ import {
} from "@mantine/core";
import type {
IScenario,
SwitchPortsProps,
TDataTicket,
THistoryTicket,
TLine,
@ -32,6 +33,15 @@ import axios from "axios";
import { notifications } from "@mantine/notifications";
const apiUrl = import.meta.env.VITE_BACKEND_URL;
const INIT_TICKET = {
description: "",
sn: "",
model: "",
station_id: 0,
history: "",
status: "open",
};
const ModalTerminal = ({
opened,
onClose,
@ -57,31 +67,32 @@ const ModalTerminal = ({
}, []);
const [isDisable, setIsDisable] = useState<boolean>(false);
const [isDisableTicket, setIsDisableTicket] = useState<boolean>(false);
const [latestTicket, setLatestTicket] = useState<TDataTicket>({
description: "",
sn: "",
model: "",
station_id: 0,
history: "",
status: "open",
});
const [dataTicket, setDataTicket] = useState<TDataTicket>({
description: "",
sn: "",
model: "",
station_id: 0,
history: "",
status: "open",
});
const [listPorts, setListPorts] = useState<SwitchPortsProps[]>([]);
const [latestTicket, setLatestTicket] = useState<TDataTicket>(INIT_TICKET);
const [dataTicket, setDataTicket] = useState<TDataTicket>(INIT_TICKET);
useEffect(() => {
if (opened && line?.tickets && line?.tickets?.length > 0) {
const data = line?.tickets[0];
setLatestTicket(data);
setDataTicket({ ...data, description: "" });
} else {
setLatestTicket(INIT_TICKET);
setDataTicket(INIT_TICKET);
}
}, [opened, line?.tickets]);
useEffect(() => {
socket?.on("switch_ports_status", (data) => {
if (data.stationId !== stationItem?.id) return;
if (data?.ports && data?.ports.length > 0)
setListPorts(data?.ports || []);
});
return () => {
socket?.off("switch_ports_status");
};
}, [socket, stationItem]);
const renderHistory = (data: TDataTicket) => {
const latest = JSON.parse(latestTicket?.history || "[]");
const list =
@ -165,8 +176,8 @@ const ModalTerminal = ({
const payload = {
id: dataTicket.id || 0,
description: dataTicket.description.trim(),
model: dataTicket.model.trim(),
sn: dataTicket.sn.trim(),
model: line?.inventory?.pid.trim(),
sn: line?.inventory?.sn.trim(),
station_id: Number(stationItem?.id),
line_id: Number(line?.id),
status: "open",
@ -313,6 +324,35 @@ const ModalTerminal = ({
}, 5000);
};
const controlSwitch = (action: string) => {
if (!line?.interface) {
notifications.show({
title: "Error",
message: "Hasn't config interface",
color: "red",
});
return;
}
socket?.emit("control_switch", {
ports: [line?.interface],
command: action,
station: { ...stationItem, lines: [] },
ip: stationItem?.switch_control_ip,
});
setIsDisable(true);
setTimeout(() => {
setIsDisable(false);
}, 5000);
};
const findSwitchPort = (portName: string): SwitchPortsProps | null => {
if (listPorts?.length > 0) {
const port = listPorts.find((el) => el.name === portName);
if (port) return port;
}
return null;
};
return (
<Box>
<Modal
@ -428,55 +468,109 @@ const ModalTerminal = ({
</Flex>
<Flex>
<Text size="md" mr={"sm"} fw={"bold"}>
Warning from test report:
Warning from test report: AI
</Text>
<Text size="md">{""}</Text>
</Flex>
<Box>
<Flex justify={"center"}>
<IconCircleDot color="green" />
<Text size="md" ml={"sm"}>
Internet Connected
</Text>
</Flex>
<Flex justify={"space-around"} mt={"4px"}>
<Button
fw={400}
variant="outline"
color="green"
size="xs"
onClick={() => {}}
>
ON
</Button>
<Button
fw={400}
variant="outline"
color="red"
size="xs"
onClick={() => {}}
>
OFF
</Button>
<Button
fw={400}
variant="outline"
color="orange"
size="xs"
onClick={() => {}}
>
Restart
</Button>
</Flex>
<fieldset>
<Flex justify={"center"}>
<IconCircleDot color="green" />
<Flex>
<Text size="sm" ml={"sm"}>
Internet
</Text>
{line?.interface ? (
findSwitchPort(line?.interface)?.status === "ON" ? (
<Text size="sm" ml={"4px"} c={"green"}>
Connected ({line?.interface})
</Text>
) : (
<Text size="sm" ml={"4px"} c={"red"}>
Not Connected ({line?.interface})
</Text>
)
) : (
<Text c={"red"} size="sm" ml={"4px"}>
Not Connected
</Text>
)}
</Flex>
</Flex>
<Flex justify={"space-around"} mt={"4px"}>
<Button
disabled={isDisable}
fw={400}
variant="outline"
color="green"
size="xs"
onClick={() => {
controlSwitch("on");
}}
>
ON
</Button>
<Button
disabled={isDisable}
fw={400}
variant="outline"
color="red"
size="xs"
onClick={() => {
controlSwitch("off");
}}
>
OFF
</Button>
<Button
disabled={isDisable}
fw={400}
variant="outline"
color="orange"
size="xs"
onClick={() => {
controlSwitch("restart");
}}
>
Restart
</Button>
</Flex>
</fieldset>
</Box>
<Flex justify={"center"}>
<Flex
justify={"center"}
style={{
borderTop: "1px solid #ccc",
borderBottom: "1px solid #ccc",
paddingTop: "12px",
paddingBottom: "12px",
}}
>
<Button
disabled={isDisable}
fw={400}
w={"120px"}
variant="outline"
color="green"
size="xs"
onClick={() => {}}
onClick={() => {
if (!line?.lineClear && !line?.line_clear) {
notifications.show({
title: "Error",
message: "Clear line has not been configured",
color: "red",
});
return;
}
socket?.emit("clear_line", {
lineClear: line?.lineClear || line?.line_clear,
stationId: stationItem?.id,
});
setIsDisable(true);
setTimeout(() => {
setIsDisable(false);
}, 5000);
}}
>
Clear line
</Button>
@ -505,7 +599,7 @@ const ModalTerminal = ({
}
line_status={line?.status || ""}
/>
<Flex justify={"space-between"} mt={"md"} pt={"md"} pb={"md"}>
<Flex justify={"space-around"} mt={"md"} pt={"md"} pb={"md"}>
<ButtonDPELP
socket={socket}
selectedLines={line ? [line] : []}
@ -517,7 +611,7 @@ const ModalTerminal = ({
}, 10000);
}}
/>
<Menu shadow="md" position="top">
<Menu trigger="hover" withArrow shadow="md" position="top">
<Menu.Target>
<Button
fw={400}
@ -560,7 +654,7 @@ const ModalTerminal = ({
</Menu.Dropdown>
</Menu>
<Button
disabled={isDisable}
disabled={true}
fw={400}
variant="filled"
color="green"
@ -570,7 +664,7 @@ const ModalTerminal = ({
Select license
</Button>
<Button
disabled={isDisable}
disabled={true}
fw={400}
variant="filled"
color="green"
@ -599,46 +693,61 @@ const ModalTerminal = ({
>
Send Break
</Button>
<Flex justify={"end"}>
<Button
disabled={isDisable}
mr={"8px"}
fw={400}
variant="outline"
color="green"
size="xs"
onClick={() => {
controlApc("on");
}}
>
ON
</Button>
<Button
disabled={isDisable}
mr={"8px"}
fw={400}
variant="outline"
color="red"
size="xs"
onClick={() => {
controlApc("off");
}}
>
OFF
</Button>
<Button
disabled={isDisable}
fw={400}
variant="outline"
color="orange"
size="xs"
onClick={() => {
controlApc("restart");
}}
>
Restart
</Button>
</Flex>
<Menu trigger="hover" withArrow shadow="md" position="top">
<Menu.Target>
<Button
fw={400}
disabled={isDisable}
variant="filled"
style={{ height: "30px", width: "100px" }}
onClick={() => {}}
>
Power
</Button>
</Menu.Target>
<Menu.Dropdown>
<Flex gap={"4px"} p={"4px"}>
<Button
disabled={isDisable}
mr={"8px"}
fw={400}
variant="outline"
color="green"
size="xs"
onClick={() => {
controlApc("on");
}}
>
ON
</Button>
<Button
disabled={isDisable}
mr={"8px"}
fw={400}
variant="outline"
color="red"
size="xs"
onClick={() => {
controlApc("off");
}}
>
OFF
</Button>
<Button
disabled={isDisable}
fw={400}
variant="outline"
color="orange"
size="xs"
onClick={() => {
controlApc("restart");
}}
>
Restart
</Button>
</Flex>
</Menu.Dropdown>
</Menu>
</Flex>
</Grid.Col>
<Grid.Col span={3}>
@ -722,7 +831,7 @@ const ModalTerminal = ({
onKeyDown={(event) => {
if (event.key === "Enter" && dataTicket.description) {
setDataTicket((pre) => ({ ...pre, description: "" }));
if (dataTicket?.status === "closed") {
if (dataTicket?.status === "closed" || !dataTicket.id) {
handleCreate();
} else handleUpdate("open");
setIsDisableTicket(true);
@ -747,7 +856,7 @@ const ModalTerminal = ({
</Box>
<Box mt={"8px"}>
<Flex justify={"end"} mt={"4px"}>
{dataTicket?.status === "closed" ? (
{dataTicket?.status === "closed" || !dataTicket.id ? (
<Button
disabled={isDisableTicket || !dataTicket.description}
mr={"8px"}
@ -787,6 +896,7 @@ const ModalTerminal = ({
disabled={
isDisableTicket ||
dataTicket?.status === "closed" ||
!dataTicket.id ||
!dataTicket.description
}
mr={"8px"}
@ -806,7 +916,11 @@ const ModalTerminal = ({
Issue
</Button>
<Button
disabled={isDisableTicket || dataTicket?.status === "closed"}
disabled={
isDisableTicket ||
!dataTicket.id ||
dataTicket?.status === "closed"
}
fw={400}
variant="outline"
color="red"

View File

@ -145,6 +145,7 @@ const TerminalCLI: React.FC<TerminalCLIProps> = ({
setTimeout(() => {
setLoading(false);
}, 500);
if (fitRef.current) fitRef.current?.fit();
} else {
setIsInit(false);
}