Sync line config updates and improve APC control
Added socket event handling to update line configuration in real time between frontend and backend. Improved APC outlet control in the terminal modal, including validation and user feedback. Refactored BottomToolBar and related components to share active tab state, and fixed command formatting. Minor bug fixes and UI improvements for terminal and scenario actions.
This commit is contained in:
parent
c36b9f69df
commit
1e058636b2
|
|
@ -4,7 +4,6 @@ import {
|
|||
appendLog,
|
||||
cleanData,
|
||||
getLogWithTimeScenario,
|
||||
getPathLog,
|
||||
isValidJson,
|
||||
sleep,
|
||||
} from '../ultils/helper.js'
|
||||
|
|
@ -45,7 +44,7 @@ interface User {
|
|||
|
||||
export default class LineConnection {
|
||||
public client: net.Socket
|
||||
public readonly config: LineConfig
|
||||
public config: LineConfig
|
||||
public readonly socketIO: any
|
||||
private outputBuffer: string
|
||||
private isRunningScript: boolean
|
||||
|
|
@ -202,7 +201,7 @@ export default class LineConnection {
|
|||
return
|
||||
}
|
||||
|
||||
this.client.write(`${cmd}`)
|
||||
this.client.write(cmd)
|
||||
if (userName) {
|
||||
// appendLog(
|
||||
// `\n---${userName}---\n`,
|
||||
|
|
@ -545,7 +544,7 @@ export default class LineConnection {
|
|||
clearInterval(escInterval)
|
||||
return
|
||||
}
|
||||
this.writeCommand(Buffer.from([0xff, 0xf3])) // Ctrl + Break
|
||||
this.client.write(Buffer.from([0xff, 0xf3])) // Ctrl + Break
|
||||
count++
|
||||
}, 1)
|
||||
}
|
||||
|
|
|
|||
|
|
@ -452,6 +452,14 @@ export class WebSocketIo {
|
|||
socket.on('update_ticket', async (data) => {
|
||||
io.emit('update_ticket', data)
|
||||
})
|
||||
|
||||
socket.on('update_line_value', async (data) => {
|
||||
const { lineId, update } = data
|
||||
const line = this.lineMap.get(lineId)
|
||||
if (line) {
|
||||
line.config = { ...line.config, ...update }
|
||||
}
|
||||
})
|
||||
})
|
||||
|
||||
socketServer.listen(SOCKET_IO_PORT, () => {
|
||||
|
|
|
|||
|
|
@ -78,6 +78,7 @@ function App() {
|
|||
const [testLogContent, setTestLogContent] = useState("");
|
||||
const [isLogModalOpen, setIsLogModalOpen] = useState(false);
|
||||
const [expandedBottomBar, setExpandedBottomBar] = useState(true);
|
||||
const [activeTabBottom, setActiveTabBottom] = useState<string>("command");
|
||||
|
||||
const connectApcSwitch = (station: TStation) => {
|
||||
if (station?.apc_1_ip && station?.apc_1_port) {
|
||||
|
|
@ -507,11 +508,25 @@ function App() {
|
|||
setTestLogContent={setTestLogContent}
|
||||
scenarios={scenarios}
|
||||
setExpanded={setExpandedBottomBar}
|
||||
activeTabBottom={activeTabBottom}
|
||||
setActiveTabBottom={setActiveTabBottom}
|
||||
/>
|
||||
</Flex>
|
||||
</Tabs.Panel>
|
||||
))}
|
||||
onChange={(id) => {
|
||||
if (selectedLines.length > 0) {
|
||||
selectedLines.forEach((el) => {
|
||||
if (
|
||||
el?.userOpenCLI === user?.userName &&
|
||||
!selectedLines.find((value) => value.id === el?.id)
|
||||
)
|
||||
socket?.emit("close_cli", {
|
||||
lineId: el?.id,
|
||||
stationId: el?.station_id,
|
||||
});
|
||||
});
|
||||
}
|
||||
setActiveTab(id?.toString() || "0");
|
||||
setSelectedLines([]);
|
||||
setLoadingTerminal(false);
|
||||
|
|
@ -560,6 +575,7 @@ function App() {
|
|||
}, 100);
|
||||
}}
|
||||
stations={stations}
|
||||
socket={socket}
|
||||
/>
|
||||
|
||||
<ModalTerminal
|
||||
|
|
|
|||
|
|
@ -35,6 +35,8 @@ interface TabsProps {
|
|||
setTestLogContent: (value: React.SetStateAction<string>) => void;
|
||||
scenarios: IScenario[];
|
||||
setExpanded: (value: React.SetStateAction<boolean>) => void;
|
||||
activeTabBottom: string;
|
||||
setActiveTabBottom: (value: React.SetStateAction<string>) => void;
|
||||
}
|
||||
|
||||
const BottomToolBar = ({
|
||||
|
|
@ -50,6 +52,8 @@ const BottomToolBar = ({
|
|||
setTestLogContent,
|
||||
scenarios,
|
||||
setExpanded,
|
||||
setActiveTabBottom,
|
||||
activeTabBottom,
|
||||
}: TabsProps) => {
|
||||
const user = useMemo(() => {
|
||||
return localStorage.getItem("user") &&
|
||||
|
|
@ -58,7 +62,7 @@ const BottomToolBar = ({
|
|||
: null;
|
||||
}, []);
|
||||
const [valueInput, setValueInput] = useState<string>("");
|
||||
const [activeTabBottom, setActiveBottom] = useState<string>("command");
|
||||
// const [activeTabBottom, setActiveTabBottom] = useState<string>("command");
|
||||
const [isExpand, setIsExpand] = useState<boolean>(true);
|
||||
|
||||
return (
|
||||
|
|
@ -107,7 +111,7 @@ const BottomToolBar = ({
|
|||
orientation="vertical"
|
||||
value={activeTabBottom}
|
||||
onChange={(val) => {
|
||||
setActiveBottom(val || "command");
|
||||
setActiveTabBottom(val || "command");
|
||||
}}
|
||||
className={classes.containerBottom}
|
||||
style={{ height: "14vh" }}
|
||||
|
|
@ -238,7 +242,7 @@ const BottomToolBar = ({
|
|||
socket?.emit("write_command_line_from_web", {
|
||||
lineIds: listLine.map((line) => line.id),
|
||||
stationId: station.id,
|
||||
command: valueInput + "\n",
|
||||
command: valueInput + "\r\n",
|
||||
});
|
||||
// setTimeout(() => {
|
||||
// socket?.emit("write_command_line_from_web", {
|
||||
|
|
|
|||
|
|
@ -19,6 +19,7 @@ import DialogConfirm from "./DialogConfirm";
|
|||
import axios from "axios";
|
||||
import { notifications } from "@mantine/notifications";
|
||||
import { IconInputX } from "@tabler/icons-react";
|
||||
import type { Socket } from "socket.io-client";
|
||||
|
||||
const apiUrl = import.meta.env.VITE_BACKEND_URL;
|
||||
|
||||
|
|
@ -38,6 +39,7 @@ const StationSetting = ({
|
|||
setStations,
|
||||
setActiveTab,
|
||||
stations,
|
||||
socket,
|
||||
}: {
|
||||
isOpen: boolean;
|
||||
isEdit: boolean;
|
||||
|
|
@ -46,6 +48,7 @@ const StationSetting = ({
|
|||
setStations: (value: React.SetStateAction<TStation[]>) => void;
|
||||
setActiveTab: (value: string) => void;
|
||||
stations: TStation[];
|
||||
socket: Socket | null;
|
||||
}) => {
|
||||
const [lines, setLines] = useState<TLine[]>([lineInit]);
|
||||
const [openConfirm, setOpenConfirm] = useState<boolean>(false);
|
||||
|
|
@ -255,9 +258,10 @@ const StationSetting = ({
|
|||
});
|
||||
return;
|
||||
}
|
||||
const lineUpdate = lines.filter((el) => el.lineNumber && el.port);
|
||||
const payload = {
|
||||
...form.values,
|
||||
lines: lines.filter((el) => el.lineNumber && el.port),
|
||||
lines: lineUpdate,
|
||||
};
|
||||
if (isEdit) payload.id = dataStation?.id || 0;
|
||||
const url = isEdit ? "api/stations/update" : "api/stations/create";
|
||||
|
|
@ -268,10 +272,41 @@ const StationSetting = ({
|
|||
setStations((pre) =>
|
||||
isEdit
|
||||
? pre.map((el) =>
|
||||
el.id === station.id ? { ...el, ...station } : el
|
||||
el.id === station.id
|
||||
? {
|
||||
...el,
|
||||
...station,
|
||||
lines: dataStation?.lines?.map((el) =>
|
||||
lineUpdate?.find((value) => value?.id === el.id)
|
||||
? {
|
||||
...el,
|
||||
...lineUpdate?.find(
|
||||
(value) => value?.id === el.id
|
||||
),
|
||||
}
|
||||
: el
|
||||
),
|
||||
}
|
||||
: el
|
||||
)
|
||||
: [...pre, station]
|
||||
);
|
||||
if (isEdit) {
|
||||
lineUpdate.forEach((el) => {
|
||||
socket?.emit("update_line_value", {
|
||||
lineId: el.id,
|
||||
update: {
|
||||
port: el.port,
|
||||
lineNumber: el.lineNumber,
|
||||
apcName: el.apc_name || el.apcName,
|
||||
outlet: el.outlet,
|
||||
lineClear: el.lineClear,
|
||||
interface: el.interface,
|
||||
baud: el.baud,
|
||||
},
|
||||
});
|
||||
});
|
||||
}
|
||||
}
|
||||
notifications.show({
|
||||
title: "Success",
|
||||
|
|
|
|||
|
|
@ -272,6 +272,47 @@ const ModalTerminal = ({
|
|||
}
|
||||
};
|
||||
|
||||
const controlApc = (action: string) => {
|
||||
if (!line?.outlet) {
|
||||
notifications.show({
|
||||
title: "Error",
|
||||
message: "Hasn't config outlet number",
|
||||
color: "red",
|
||||
});
|
||||
return;
|
||||
}
|
||||
const apcName = line.apcName || line.apc_name;
|
||||
if (!apcName) {
|
||||
notifications.show({
|
||||
title: "Error",
|
||||
message: "Hasn't config apc",
|
||||
color: "red",
|
||||
});
|
||||
return;
|
||||
}
|
||||
if (
|
||||
(apcName === "apc_1" && !stationItem?.apc_1_ip) ||
|
||||
(apcName === "apc_2" && !stationItem?.apc_2_ip)
|
||||
) {
|
||||
notifications.show({
|
||||
title: "Error",
|
||||
message: "Hasn't config apc ip",
|
||||
color: "red",
|
||||
});
|
||||
return;
|
||||
}
|
||||
socket?.emit("control_apc", {
|
||||
outletNumbers: [line.outlet],
|
||||
station: stationItem,
|
||||
action: action,
|
||||
apcName: line.apcName || line.apc_name,
|
||||
});
|
||||
setIsDisable(true);
|
||||
setTimeout(() => {
|
||||
setIsDisable(false);
|
||||
}, 5000);
|
||||
};
|
||||
|
||||
return (
|
||||
<Box>
|
||||
<Modal
|
||||
|
|
@ -480,7 +521,7 @@ const ModalTerminal = ({
|
|||
<Menu.Target>
|
||||
<Button
|
||||
fw={400}
|
||||
disabled={isDisable || selectedLines.length === 0}
|
||||
disabled={isDisable}
|
||||
variant="filled"
|
||||
color="yellow"
|
||||
style={{ height: "30px", width: "100px" }}
|
||||
|
|
@ -503,19 +544,8 @@ const ModalTerminal = ({
|
|||
<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
|
||||
}
|
||||
selectedLines={line ? [line] : []}
|
||||
isDisable={isDisable}
|
||||
onClick={() => {
|
||||
// setSelectedLines([]);
|
||||
setIsDisable(true);
|
||||
|
|
@ -530,6 +560,7 @@ const ModalTerminal = ({
|
|||
</Menu.Dropdown>
|
||||
</Menu>
|
||||
<Button
|
||||
disabled={isDisable}
|
||||
fw={400}
|
||||
variant="filled"
|
||||
color="green"
|
||||
|
|
@ -539,6 +570,7 @@ const ModalTerminal = ({
|
|||
Select license
|
||||
</Button>
|
||||
<Button
|
||||
disabled={isDisable}
|
||||
fw={400}
|
||||
variant="filled"
|
||||
color="green"
|
||||
|
|
@ -569,31 +601,40 @@ const ModalTerminal = ({
|
|||
</Button>
|
||||
<Flex justify={"end"}>
|
||||
<Button
|
||||
disabled={isDisable}
|
||||
mr={"8px"}
|
||||
fw={400}
|
||||
variant="outline"
|
||||
color="green"
|
||||
size="xs"
|
||||
onClick={() => {}}
|
||||
onClick={() => {
|
||||
controlApc("on");
|
||||
}}
|
||||
>
|
||||
ON
|
||||
</Button>
|
||||
<Button
|
||||
disabled={isDisable}
|
||||
mr={"8px"}
|
||||
fw={400}
|
||||
variant="outline"
|
||||
color="red"
|
||||
size="xs"
|
||||
onClick={() => {}}
|
||||
onClick={() => {
|
||||
controlApc("off");
|
||||
}}
|
||||
>
|
||||
OFF
|
||||
</Button>
|
||||
<Button
|
||||
disabled={isDisable}
|
||||
fw={400}
|
||||
variant="outline"
|
||||
color="orange"
|
||||
size="xs"
|
||||
onClick={() => {}}
|
||||
onClick={() => {
|
||||
controlApc("restart");
|
||||
}}
|
||||
>
|
||||
Restart
|
||||
</Button>
|
||||
|
|
|
|||
|
|
@ -145,6 +145,8 @@ const TerminalCLI: React.FC<TerminalCLIProps> = ({
|
|||
setTimeout(() => {
|
||||
setLoading(false);
|
||||
}, 500);
|
||||
} else {
|
||||
setIsInit(false);
|
||||
}
|
||||
}, [cliOpened]);
|
||||
|
||||
|
|
|
|||
Loading…
Reference in New Issue