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 { class APCController {
private apc_number?: number public apc_number?: number
private apc_ip: string public apc_ip: string
private apc_port: number private apc_port: number
private apc_username: string private apc_username: string
private apc_password: string private apc_password: string
@ -100,7 +100,7 @@ class APCController {
this.buffer = '' this.buffer = ''
} }
} }
// appendLog(data, 0, 0, this.apc_number || 0) appendLog(data, 0, 0, this.apc_number || 0)
} }
private _handleClose(): void { private _handleClose(): void {
@ -130,7 +130,7 @@ class APCController {
setTimeout(() => { setTimeout(() => {
console.log('[ECONNRESET] Trying reconnect apc:', this.apc_ip) console.log('[ECONNRESET] Trying reconnect apc:', this.apc_ip)
this.reconnect() 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) { if (this.client.destroyed) {
console.log(`⚠️ Cannot send, line ${this.config.lineNumber} is closed`) console.log(`⚠️ Cannot send, line ${this.config.lineNumber} is closed`)
return return
@ -513,4 +513,17 @@ export default class LineConnection {
console.log(error) 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> { private _waitFor(prompt: string, timeout = 5000): Promise<string> {
return new Promise((resolve, reject) => { return new Promise((resolve, reject) => {
const timer = setTimeout(() => { const timer = setTimeout(() => {
reject(new Error(`Timeout waiting for: ${prompt}`)) resolve(`Timeout waiting for: ${prompt}`)
}, timeout) }, timeout)
this.promptCallbacks.push({ this.promptCallbacks.push({
@ -188,58 +188,74 @@ export default class SwitchController {
public async turnPortOff(port: string) { public async turnPortOff(port: string) {
await this.enterEnableMode() await this.enterEnableMode()
this._send(`configure terminal`) this._send(`configure terminal`)
await this._waitFor('(config)#') // await this._waitFor('(config)#')
await this.sleep(500)
this._send(`interface ${port}`) this._send(`interface ${port}`)
await this._waitFor('(config-if)#') // await this._waitFor('(config-if)#')
await this.sleep(500)
this._send(`shutdown`) this._send(`shutdown`)
await this._waitFor('(config-if)#') // await this._waitFor('(config-if)#')
await this.sleep(500)
this._send(`end`) this._send(`end`)
} }
public async turnPortOn(port: string) { public async turnPortOn(port: string) {
await this.enterEnableMode() await this.enterEnableMode()
this._send(`configure terminal`) this._send(`configure terminal`)
await this._waitFor('(config)#') // await this._waitFor('(config)#')
await this.sleep(500)
this._send(`interface ${port}`) this._send(`interface ${port}`)
await this._waitFor('(config-if)#') // await this._waitFor('(config-if)#')
await this.sleep(500)
this._send(`no shutdown`) this._send(`no shutdown`)
await this._waitFor('(config-if)#') // await this._waitFor('(config-if)#')
await this.sleep(500)
this._send(`end`) this._send(`end`)
} }
public async restartPort(port: string) { public async restartPort(port: string) {
await this.enterEnableMode() await this.enterEnableMode()
this._send(`configure terminal`) this._send(`configure terminal`)
await this._waitFor('(config)#') // await this._waitFor('(config)#')
await this.sleep(500)
this._send(`interface ${port}`) this._send(`interface ${port}`)
await this._waitFor('(config-if)#') // await this._waitFor('(config-if)#')
await this.sleep(500)
this._send(`shutdown`) this._send(`shutdown`)
await this._waitFor('(config-if)#') // await this._waitFor('(config-if)#')
await this.sleep(500)
await this.sleep(2000) await this.sleep(2000)
this._send(`no shutdown`) this._send(`no shutdown`)
await this._waitFor('(config-if)#') // await this._waitFor('(config-if)#')
await this.sleep(500)
this._send(`end`) this._send(`end`)
} }
public async disablePoE(port: string) { public async disablePoE(port: string) {
await this.enterEnableMode() await this.enterEnableMode()
this._send(`configure terminal`) this._send(`configure terminal`)
await this._waitFor('(config)#') // await this._waitFor('(config)#')
await this.sleep(500)
this._send(`interface ${port}`) this._send(`interface ${port}`)
await this._waitFor('(config-if)#') // await this._waitFor('(config-if)#')
await this.sleep(500)
this._send(`power inline never`) this._send(`power inline never`)
await this._waitFor('(config-if)#') // await this._waitFor('(config-if)#')
await this.sleep(500)
this._send(`end`) this._send(`end`)
} }
public async enablePoE(port: string) { public async enablePoE(port: string) {
await this.enterEnableMode() await this.enterEnableMode()
this._send(`configure terminal`) this._send(`configure terminal`)
await this._waitFor('(config)#') // await this._waitFor('(config)#')
await this.sleep(500)
this._send(`interface ${port}`) this._send(`interface ${port}`)
await this._waitFor('(config-if)#') // await this._waitFor('(config-if)#')
await this.sleep(500)
this._send(`power inline auto`) this._send(`power inline auto`)
await this._waitFor('(config-if)#') // await this._waitFor('(config-if)#')
await this.sleep(500)
this._send(`end`) this._send(`end`)
} }

View File

@ -63,12 +63,12 @@ export default class SocketIoProvider {
export class WebSocketIo { export class WebSocketIo {
intervalMap: { [key: string]: NodeJS.Timeout } = {} intervalMap: { [key: string]: NodeJS.Timeout } = {}
stationMap: Map<number, Station> = new Map()
lineMap: Map<number, LineConnection> = new Map() // key = lineId lineMap: Map<number, LineConnection> = new Map() // key = lineId
userConnecting: Map<number, { userId: number; userName: string }> = new Map() userConnecting: Map<number, { userId: number; userName: string }> = new Map()
apcsControl: Map<string, APCController> = new Map() apcsControl: Map<string, APCController> = new Map()
switchControl: Map<string, SwitchController> = new Map() switchControl: Map<string, SwitchController> = new Map()
lineConnecting: number[] = [] // key = lineId lineConnecting: number[] = [] // key = lineId
intervalKeepConnect: { [key: string]: NodeJS.Timeout } = {}
constructor(protected app: ApplicationService) {} constructor(protected app: ApplicationService) {}
@ -115,7 +115,16 @@ export class WebSocketIo {
listLineS.forEach((el) => { listLineS.forEach((el) => {
if (el?.userOpenCLI === userName) { if (el?.userOpenCLI === userName) {
const line = this.lineMap.get(el.id) 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(() => { setTimeout(() => {
@ -136,7 +145,8 @@ export class WebSocketIo {
io, io,
stationId, stationId,
lineIds, lineIds,
async (line) => line.writeCommand(command, true), async (line) =>
command === 'spam_break' ? line.breakSpam() : line.writeCommand(command, true),
{ command, timeout: 120000 } { command, timeout: 120000 }
) )
}) })
@ -159,21 +169,26 @@ export class WebSocketIo {
socket.on('open_cli', async (data) => { socket.on('open_cli', async (data) => {
const { lineId, userEmail, userName: name, stationId } = data const { lineId, userEmail, userName: name, stationId } = data
const line = this.lineMap.get(lineId) const line = this.lineMap.get(lineId)
if (line && line?.userOpenCLI) { if (line) {
line?.userOpenCLI({ userEmail, userName: name }) 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 { } else {
if (this.lineConnecting.includes(lineId)) return if (this.lineConnecting.includes(lineId)) return
const linesData = await Line.findBy('id', lineId) const linesData = await Line.findBy('id', lineId)
const stationData = await Station.findBy('id', stationId) const stationData = await Station.findBy('id', stationId)
if (linesData && stationData) { if (linesData && stationData) {
this.lineConnecting.push(lineId) this.lineConnecting.push(lineId)
await this.connectLine( await this.connectLine(io, [linesData], stationData)
io,
[linesData],
stationData,
line?.config?.output || '',
line?.config?.commands || []
)
const lineReconnect = this.lineMap.get(lineId) const lineReconnect = this.lineMap.get(lineId)
if (lineReconnect) { if (lineReconnect) {
lineReconnect.userOpenCLI({ userEmail, userName: name }) lineReconnect.userOpenCLI({ userEmail, userName: name })
@ -185,21 +200,25 @@ export class WebSocketIo {
socket.on('close_cli', async (data) => { socket.on('close_cli', async (data) => {
const { lineId, stationId } = data const { lineId, stationId } = data
const line = this.lineMap.get(lineId) const line = this.lineMap.get(lineId)
if (line && line?.userCloseCLI) { if (line) {
line?.userCloseCLI() 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 { } else {
if (this.lineConnecting.includes(lineId)) return if (this.lineConnecting.includes(lineId)) return
const linesData = await Line.findBy('id', lineId) const linesData = await Line.findBy('id', lineId)
const stationData = await Station.findBy('id', stationId) const stationData = await Station.findBy('id', stationId)
if (linesData && stationData) { if (linesData && stationData) {
this.lineConnecting.push(lineId) this.lineConnecting.push(lineId)
await this.connectLine( await this.connectLine(io, [linesData], stationData)
io,
[linesData],
stationData,
line?.config?.output || '',
line?.config?.commands || []
)
const lineReconnect = this.lineMap.get(lineId) const lineReconnect = this.lineMap.get(lineId)
if (lineReconnect) { if (lineReconnect) {
lineReconnect.userCloseCLI() lineReconnect.userCloseCLI()
@ -265,7 +284,10 @@ export class WebSocketIo {
if (!station) return if (!station) return
const apcIp = (station as any)[`${apcName}_ip`] as string const apcIp = (station as any)[`${apcName}_ip`] as string
const apc = this.apcsControl.get(apcIp) 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 { } else {
for (const outletNumber of outletNumbers) { for (const outletNumber of outletNumbers) {
if (!outletNumber || outletNumber < 0) return if (!outletNumber || outletNumber < 0) return
@ -282,7 +304,11 @@ export class WebSocketIo {
const apcIp = (station as any)[`${apcName}_ip`] as string const apcIp = (station as any)[`${apcName}_ip`] as string
const apc = this.apcsControl.get(apcIp) 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) { switch (action) {
case 'on': case 'on':
await apc?.turnOnOutlet(outletNumber) await apc?.turnOnOutlet(outletNumber)
@ -302,24 +328,6 @@ export class WebSocketIo {
setTimeout(() => { setTimeout(() => {
apc?.navigateToOutlets() apc?.navigateToOutlets()
}, 10000) }, 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, data: apc.output,
status: apc.status, status: apc.status,
}) })
this.keepConnectAPC(apcIp, io)
} else if (apc && apc.status !== 'CONNECTED') { } else if (apc && apc.status !== 'CONNECTED') {
await apc.reconnect() await apc.reconnect()
this.apcsControl.set(apcIp, apc)
this.keepConnectAPC(apcIp, io)
} else await this.connectApc(io, apcName, station) } else await this.connectApc(io, apcName, station)
} catch (error) { } catch (error) {
console.log(error) console.log(error)
@ -442,7 +453,6 @@ export class WebSocketIo {
commands: string[] = [] commands: string[] = []
) { ) {
try { try {
this.stationMap.set(station.id, station)
for (const line of lines) { for (const line of lines) {
const lineConn = new LineConnection( const lineConn = new LineConnection(
{ {
@ -584,6 +594,7 @@ export class WebSocketIo {
await apc.connect() await apc.connect()
await apc.login() await apc.login()
this.apcsControl.set(ip, apc) this.apcsControl.set(ip, apc)
this.keepConnectAPC(ip, socket)
} catch (error) { } catch (error) {
console.log(error) console.log(error)
} }
@ -603,7 +614,7 @@ export class WebSocketIo {
status: 'DISCONNECTED', status: 'DISCONNECTED',
message: `Missing Switch configuration`, message: `Missing Switch configuration`,
}) })
throw new Error(`Missing Switch configuration`) return
} }
// Tạo APC Controller instance // Tạo APC Controller instance
@ -633,7 +644,10 @@ export class WebSocketIo {
private async clearLineBeforeConnect(stationId: number, clearLine: number) { private async clearLineBeforeConnect(stationId: number, clearLine: number) {
const station = await Station.find(stationId) 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 // Kết nối tới station qua Telnet / Socket
const client = new net.Socket() const client = new net.Socket()
@ -679,7 +693,9 @@ export class WebSocketIo {
const newMap = new Map<number, LineConnection>() const newMap = new Map<number, LineConnection>()
this.lineMap.forEach((line, id) => { this.lineMap.forEach((line, id) => {
if (line && line.config) { 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) const parsed = JSON.parse(raw)
this.lineMap = new Map(parsed.lineMap) 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; margin: 0.1rem auto 0;
border-radius: 3px; 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 axios from "axios";
import CardLine from "./components/CardLine"; import CardLine from "./components/CardLine";
import { SocketProvider, useSocket } from "./context/SocketContext"; import { SocketProvider, useSocket } from "./context/SocketContext";
import { import // ButtonConnect,
// ButtonConnect, // ButtonControlApc,
ButtonControlApc, // ButtonCopy,
ButtonCopy, // ButtonDPELP,
ButtonDPELP, // ButtonScenario,
ButtonScenario, // ButtonSelect,
ButtonSelect, "./components/ButtonAction";
} from "./components/ButtonAction";
import StationSetting from "./components/FormAddEdit"; import StationSetting from "./components/FormAddEdit";
import DrawerScenario from "./components/DrawerScenario"; // import DrawerScenario from "./components/DrawerScenario";
import { Notifications } from "@mantine/notifications"; import { Notifications } from "@mantine/notifications";
import ModalTerminal from "./components/ModalTerminal"; import ModalTerminal from "./components/ModalTerminal";
import PageLogin from "./components/Authentication/LoginPage"; import PageLogin from "./components/Authentication/LoginPage";
import DrawerLogs from "./components/DrawerLogs"; // import DrawerLogs from "./components/DrawerLogs";
import DraggableTabs from "./components/DragTabs"; import DraggableTabs from "./components/DragTabs";
import { isJsonString } from "./untils/helper"; import { isJsonString } from "./untils/helper";
import BottomToolBar from "./components/BottomToolBar";
const apiUrl = import.meta.env.VITE_BACKEND_URL; const apiUrl = import.meta.env.VITE_BACKEND_URL;
@ -67,7 +67,6 @@ function App() {
const [stations, setStations] = useState<TStation[]>([]); const [stations, setStations] = useState<TStation[]>([]);
const [selectedLines, setSelectedLines] = useState<TLine[]>([]); const [selectedLines, setSelectedLines] = useState<TLine[]>([]);
const [activeTab, setActiveTab] = useState("0"); const [activeTab, setActiveTab] = useState("0");
const [showBottomShadow, setShowBottomShadow] = useState(false);
const [isDisable, setIsDisable] = useState(false); const [isDisable, setIsDisable] = useState(false);
const [isOpenAddStation, setIsOpenAddStation] = useState(false); const [isOpenAddStation, setIsOpenAddStation] = useState(false);
const [isEditStation, setIsEditStation] = useState(false); const [isEditStation, setIsEditStation] = useState(false);
@ -84,6 +83,29 @@ function App() {
const [testLogContent, setTestLogContent] = useState(""); const [testLogContent, setTestLogContent] = useState("");
const [isLogModalOpen, setIsLogModalOpen] = useState(false); 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 // function get list station
const getStation = async () => { const getStation = async () => {
try { try {
@ -91,6 +113,9 @@ function App() {
if (response.status) { if (response.status) {
if (Array.isArray(response.data)) { if (Array.isArray(response.data)) {
setStations(response.data); setStations(response.data);
response.data.forEach((station) => {
connectApcSwitch(station);
});
} }
} }
} catch (error) { } catch (error) {
@ -113,9 +138,10 @@ function App() {
}; };
useEffect(() => { useEffect(() => {
if (!socket) return;
getStation(); getStation();
getScenarios(); getScenarios();
}, []); }, [socket]);
useEffect(() => { useEffect(() => {
if (!socket || !stations?.length) return; if (!socket || !stations?.length) return;
@ -363,16 +389,6 @@ function App() {
setSelectedLine(data); setSelectedLine(data);
}; };
useEffect(() => {
return () => {
if (selectedLine)
socket?.emit("close_cli", {
lineId: selectedLine?.id,
stationId: selectedLine?.station_id,
});
};
}, []);
return ( return (
<Container w={"100%"} style={{ maxWidth: "100%" }}> <Container w={"100%"} style={{ maxWidth: "100%" }}>
<DraggableTabs <DraggableTabs
@ -389,37 +405,28 @@ function App() {
value={station.id.toString()} value={station.id.toString()}
pt="md" pt="md"
> >
<Flex className={classes.containerMain}>
<Grid> <Grid>
<Grid.Col <Grid.Col
span={11} span={12}
style={{ style={{
boxShadow: showBottomShadow
? "inset 0 -12px 10px -10px rgba(0, 0, 0, 0.2)"
: "none",
borderRadius: 8, borderRadius: 8,
}} }}
> >
<ScrollArea <ScrollArea h={"63vh"}>
h={"84vh"}
onScrollPositionChange={({ y }) => {
const el = document.querySelector(
".mantine-ScrollArea-viewport"
);
if (!el) return;
const maxScroll = el.scrollHeight - el.clientHeight;
setShowBottomShadow(y < maxScroll - 2);
}}
>
{station.lines.length > 8 ? ( {station.lines.length > 8 ? (
<Grid <Grid
style={{ style={{
marginLeft: "3%", marginLeft: "3%",
width: "90%", width: "95%",
display: "flex", display: "flex",
justifyContent: "center", justifyContent: "center",
}} }}
> >
<Grid.Col span={6}> <Grid.Col
span={6}
style={{ borderRight: "1px solid #ccc" }}
>
<Flex wrap="wrap" gap="sm" justify={"center"}> <Flex wrap="wrap" gap="sm" justify={"center"}>
{station.lines.slice(0, 8).map((line, i) => ( {station.lines.slice(0, 8).map((line, i) => (
<CardLine <CardLine
@ -460,7 +467,8 @@ function App() {
</Flex> </Flex>
</Grid.Col> </Grid.Col>
</Grid> </Grid>
) : station.lines.length <= 8 && station.lines.length > 0 ? ( ) : station.lines.length <= 8 &&
station.lines.length > 0 ? (
<Flex wrap="wrap" gap="sm" justify={"center"}> <Flex wrap="wrap" gap="sm" justify={"center"}>
{station.lines.map((line, i) => ( {station.lines.map((line, i) => (
<CardLine <CardLine
@ -485,114 +493,20 @@ function App() {
)} )}
</ScrollArea> </ScrollArea>
</Grid.Col> </Grid.Col>
<Grid.Col </Grid>
span={1} <BottomToolBar
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
selectedLines={selectedLines} selectedLines={selectedLines}
socket={socket}
setSelectedLines={setSelectedLines} setSelectedLines={setSelectedLines}
isDisable={isDisable}
setIsDisable={setIsDisable}
station={station} station={station}
/> testLogContent={testLogContent}
{/* <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}
isLogModalOpen={isLogModalOpen} isLogModalOpen={isLogModalOpen}
setIsLogModalOpen={setIsLogModalOpen} setIsLogModalOpen={setIsLogModalOpen}
testLogContent={testLogContent}
setTestLogContent={setTestLogContent} setTestLogContent={setTestLogContent}
/> />
</Flex> </Flex>
</Grid.Col>
</Grid>
</Tabs.Panel> </Tabs.Panel>
))} ))}
onChange={(id) => { 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 { .hideScrollBar::-webkit-scrollbar {
display: none; 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, ActionIcon,
Avatar, Avatar,
Box, Box,
Button,
CloseButton,
Flex, Flex,
Group, Group,
Input,
Menu, Menu,
Tabs, Tabs,
Text, Text,
@ -39,7 +36,6 @@ import {
import classes from "./Component.module.css"; import classes from "./Component.module.css";
import type { TStation, TUser } from "../untils/types"; import type { TStation, TUser } from "../untils/types";
import type { Socket } from "socket.io-client"; import type { Socket } from "socket.io-client";
import { DrawerAPCControl, DrawerSwitchControl } from "./DrawerControl";
interface DraggableTabsProps { interface DraggableTabsProps {
tabsData: TStation[]; tabsData: TStation[];
@ -87,6 +83,7 @@ function SortableTab({
transition, transition,
cursor: "grab", cursor: "grab",
userSelect: "none", userSelect: "none",
backgroundColor: active === tab.id.toString() ? "#deffde" : "",
}} }}
color={active === tab.id.toString() ? "green" : ""} color={active === tab.id.toString() ? "green" : ""}
fw={600} fw={600}
@ -116,7 +113,6 @@ export default function DraggableTabs({
setStationEdit, setStationEdit,
active, active,
setActive, setActive,
onSendCommand,
}: DraggableTabsProps) { }: DraggableTabsProps) {
const user = useMemo(() => { const user = useMemo(() => {
return localStorage.getItem("user") && return localStorage.getItem("user") &&
@ -127,12 +123,6 @@ export default function DraggableTabs({
const [tabs, setTabs] = useState<TStation[]>(tabsData); const [tabs, setTabs] = useState<TStation[]>(tabsData);
const [isChangeTab, setIsChangeTab] = useState<boolean>(false); const [isChangeTab, setIsChangeTab] = useState<boolean>(false);
const [isSetActive, setIsSetActive] = 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)); 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 ( return (
<DndContext <DndContext
sensors={sensors} sensors={sensors}
@ -259,69 +226,7 @@ export default function DraggableTabs({
w={w} w={w}
> >
<Flex justify={"space-between"}> <Flex justify={"space-between"}>
<Flex style={{ width: "400px" }} align={"center"}> <Flex></Flex>
<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>
<Tabs.List className={classes.list}> <Tabs.List className={classes.list}>
<SortableContext <SortableContext
items={tabs} items={tabs}
@ -429,36 +334,6 @@ export default function DraggableTabs({
{panels} {panels}
</Tabs> </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> </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 { IconRepeat, IconSection } from "@tabler/icons-react";
import { useEffect, useState } from "react"; import { useEffect, useState } from "react";
import classes from "./Component.module.css"; import classes from "./Component.module.css";
@ -8,10 +8,6 @@ import type { Socket } from "socket.io-client";
interface DrawerProps { interface DrawerProps {
stationAPI: TStation; stationAPI: TStation;
socket: Socket | null; socket: Socket | null;
open: boolean;
onClose: () => void;
openedSwitch?: () => void;
openedAPC?: () => void;
} }
type TSelectedOutlet = { type TSelectedOutlet = {
@ -24,9 +20,6 @@ type TSelectedOutlet = {
export const DrawerAPCControl: React.FC<DrawerProps> = ({ export const DrawerAPCControl: React.FC<DrawerProps> = ({
stationAPI, stationAPI,
socket, socket,
open,
onClose,
openedSwitch,
}) => { }) => {
const findLineByOutlet = (outlet: TSelectedOutlet) => { const findLineByOutlet = (outlet: TSelectedOutlet) => {
return stationAPI.lines.find( return stationAPI.lines.find(
@ -58,12 +51,6 @@ export const DrawerAPCControl: React.FC<DrawerProps> = ({
>([]); >([]);
const [isSubmit, setIsSubmit] = useState(true); const [isSubmit, setIsSubmit] = useState(true);
useEffect(() => {
if (!open) {
setListOutletSelected([]);
}
}, [open]);
const detectOutlet = ( const detectOutlet = (
apc: APCProps, apc: APCProps,
lines: string[], lines: string[],
@ -195,12 +182,12 @@ export const DrawerAPCControl: React.FC<DrawerProps> = ({
}; };
const RenderAPCStatus = (apc: APCProps) => { const RenderAPCStatus = (apc: APCProps) => {
switch (apc.status) { switch (apc?.status) {
case "CONNECTED": case "CONNECTED":
return ( return (
<> <>
<Text size="sm" fw={800} c="green"> <Text fw={800} c="green" fz={"12px"}>
{apc.status} {apc?.status}
</Text> </Text>
</> </>
); );
@ -208,26 +195,24 @@ export const DrawerAPCControl: React.FC<DrawerProps> = ({
case "DISCONNECTED": case "DISCONNECTED":
return ( return (
<> <>
<div></div> <Text fw={800} c="red" fz={"12px"}>
{apc?.status}
<Text size="sm" fw={800} c="red">
{apc.status}
</Text> </Text>
</> </>
); );
case "TIMEOUT": case "TIMEOUT":
return ( return (
<> <>
<Text size="sm" fw={800} c="yellow"> <Text fw={800} c="yellow" fz={"12px"}>
{apc.status} {apc?.status}
</Text> </Text>
</> </>
); );
default: default:
return ( return (
<> <>
{/* <Text size="sm" fw={800} c="blue"> {/* <Text fw={800} c="red" fz={"12px"}>
{line.status} WRONG CONFIG
</Text> */} </Text> */}
</> </>
); );
@ -235,48 +220,15 @@ export const DrawerAPCControl: React.FC<DrawerProps> = ({
}; };
return ( 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>
<Grid.Col span={6}> <Grid.Col span={6}>
<fieldset style={{ padding: "4px 6px" }}> <fieldset style={{ padding: "4px 6px" }}>
<legend> <legend>
<div className={classes.titleAPC}> <div className={classes.titleAPC}>
<Text fw={700} c={"#514d4d"} fz={"sm"}> <Text fw={700} c={"#514d4d"} fz={"xs"}>
APC 1 APC 1
</Text> </Text>
{dataStation?.apc1?.status && {RenderAPCStatus(dataStation?.apc1)}
RenderAPCStatus(dataStation?.apc1)}
{dataStation?.apc1?.status === "DISCONNECTED" || {dataStation?.apc1?.status === "DISCONNECTED" ||
dataStation?.apc1?.status === "TIMEOUT" ? ( dataStation?.apc1?.status === "TIMEOUT" ? (
<Button <Button
@ -320,12 +272,11 @@ export const DrawerAPCControl: React.FC<DrawerProps> = ({
className={`${isSubmit ? classes.isDisabled : ""}`} className={`${isSubmit ? classes.isDisabled : ""}`}
style={{ style={{
position: "relative", position: "relative",
width: "90px", width: "80px",
cursor: "pointer", cursor: "pointer",
textAlign: "center", textAlign: "center",
border: listOutletSelected.find( border: listOutletSelected.find(
(el) => (el) => el.name === outlet.name && el.apc === outlet.apc
el.name === outlet.name && el.apc === outlet.apc
)?.name )?.name
? "1px solid #0018ff" ? "1px solid #0018ff"
: "", : "",
@ -336,7 +287,7 @@ export const DrawerAPCControl: React.FC<DrawerProps> = ({
> >
<Text <Text
fw={500} fw={500}
fz={"14px"} fz={"12px"}
style={{ style={{
color: outlet.status === "ON" ? "#40c057" : "#f03e3e", color: outlet.status === "ON" ? "#40c057" : "#f03e3e",
}} }}
@ -352,7 +303,7 @@ export const DrawerAPCControl: React.FC<DrawerProps> = ({
style={{ style={{
display: "flex", display: "flex",
justifyContent: "left", justifyContent: "left",
marginTop: "20px", marginTop: "10px",
marginBottom: "10px", marginBottom: "10px",
gap: "20px", gap: "20px",
}} }}
@ -368,7 +319,7 @@ export const DrawerAPCControl: React.FC<DrawerProps> = ({
// mt={'xs'} // mt={'xs'}
miw={"80px"} miw={"80px"}
size="xs" size="xs"
fz={"sm"} fz={"xs"}
variant="filled" variant="filled"
onClick={() => { onClick={() => {
if ( if (
@ -410,7 +361,7 @@ export const DrawerAPCControl: React.FC<DrawerProps> = ({
// mt={'xs'} // mt={'xs'}
miw={"80px"} miw={"80px"}
size="xs" size="xs"
fz={"sm"} fz={"xs"}
variant="filled" variant="filled"
color="yellow" color="yellow"
onClick={() => { onClick={() => {
@ -454,7 +405,7 @@ export const DrawerAPCControl: React.FC<DrawerProps> = ({
// mt={'xs'} // mt={'xs'}
miw={"80px"} miw={"80px"}
size="xs" size="xs"
fz={"sm"} fz={"xs"}
variant="filled" variant="filled"
color="green" color="green"
onClick={() => { onClick={() => {
@ -498,7 +449,7 @@ export const DrawerAPCControl: React.FC<DrawerProps> = ({
// mt={'xs'} // mt={'xs'}
miw={"80px"} miw={"80px"}
size="xs" size="xs"
fz={"sm"} fz={"xs"}
variant="filled" variant="filled"
color="red" color="red"
onClick={() => { onClick={() => {
@ -531,11 +482,10 @@ export const DrawerAPCControl: React.FC<DrawerProps> = ({
<fieldset style={{ padding: "4px 6px" }}> <fieldset style={{ padding: "4px 6px" }}>
<legend> <legend>
<div className={classes.titleAPC}> <div className={classes.titleAPC}>
<Text fw={700} c={"#514d4d"} fz={"sm"}> <Text fw={700} c={"#514d4d"} fz={"xs"}>
APC 2 APC 2
</Text> </Text>
{dataStation?.apc2?.status && {RenderAPCStatus(dataStation?.apc2)}
RenderAPCStatus(dataStation?.apc2)}
{dataStation?.apc2?.status === "DISCONNECTED" || {dataStation?.apc2?.status === "DISCONNECTED" ||
dataStation?.apc2?.status === "TIMEOUT" ? ( dataStation?.apc2?.status === "TIMEOUT" ? (
<Button <Button
@ -579,12 +529,11 @@ export const DrawerAPCControl: React.FC<DrawerProps> = ({
className={`${isSubmit ? classes.isDisabled : ""}`} className={`${isSubmit ? classes.isDisabled : ""}`}
style={{ style={{
position: "relative", position: "relative",
width: "90px", width: "80px",
cursor: "pointer", cursor: "pointer",
textAlign: "center", textAlign: "center",
border: listOutletSelected.find( border: listOutletSelected.find(
(el) => (el) => el.name === outlet.name && el.apc === outlet.apc
el.name === outlet.name && el.apc === outlet.apc
)?.name )?.name
? "1px solid #0018ff" ? "1px solid #0018ff"
: "", : "",
@ -595,7 +544,7 @@ export const DrawerAPCControl: React.FC<DrawerProps> = ({
> >
<Text <Text
fw={500} fw={500}
fz={"14px"} fz={"12px"}
style={{ style={{
color: outlet.status === "ON" ? "#40c057" : "#f03e3e", color: outlet.status === "ON" ? "#40c057" : "#f03e3e",
}} }}
@ -611,7 +560,7 @@ export const DrawerAPCControl: React.FC<DrawerProps> = ({
style={{ style={{
display: "flex", display: "flex",
justifyContent: "left", justifyContent: "left",
marginTop: "20px", marginTop: "10px",
marginBottom: "10px", marginBottom: "10px",
gap: "20px", gap: "20px",
}} }}
@ -627,7 +576,7 @@ export const DrawerAPCControl: React.FC<DrawerProps> = ({
// mt={'xs'} // mt={'xs'}
miw={"80px"} miw={"80px"}
size="xs" size="xs"
fz={"sm"} fz={"xs"}
variant="filled" variant="filled"
onClick={() => { onClick={() => {
if ( if (
@ -669,7 +618,7 @@ export const DrawerAPCControl: React.FC<DrawerProps> = ({
// mt={'xs'} // mt={'xs'}
miw={"80px"} miw={"80px"}
size="xs" size="xs"
fz={"sm"} fz={"xs"}
variant="filled" variant="filled"
color="yellow" color="yellow"
onClick={() => { onClick={() => {
@ -714,7 +663,7 @@ export const DrawerAPCControl: React.FC<DrawerProps> = ({
// mt={'xs'} // mt={'xs'}
miw={"80px"} miw={"80px"}
size="xs" size="xs"
fz={"sm"} fz={"xs"}
variant="filled" variant="filled"
color="green" color="green"
onClick={() => { onClick={() => {
@ -758,7 +707,7 @@ export const DrawerAPCControl: React.FC<DrawerProps> = ({
// mt={'xs'} // mt={'xs'}
miw={"80px"} miw={"80px"}
size="xs" size="xs"
fz={"sm"} fz={"xs"}
variant="filled" variant="filled"
color="red" color="red"
onClick={() => { onClick={() => {
@ -788,16 +737,12 @@ export const DrawerAPCControl: React.FC<DrawerProps> = ({
</fieldset> </fieldset>
</Grid.Col> </Grid.Col>
</Grid> </Grid>
</Drawer>
); );
}; };
export const DrawerSwitchControl: React.FC<DrawerProps> = ({ export const DrawerSwitchControl: React.FC<DrawerProps> = ({
stationAPI, stationAPI,
socket, socket,
open,
onClose,
openedAPC,
}) => { }) => {
const [listPorts, setListPorts] = useState<SwitchPortsProps[][]>([]); const [listPorts, setListPorts] = useState<SwitchPortsProps[][]>([]);
const [dataStation, setDataStation] = useState<TStation>(stationAPI); const [dataStation, setDataStation] = useState<TStation>(stationAPI);
@ -903,100 +848,10 @@ export const DrawerSwitchControl: React.FC<DrawerProps> = ({
}); });
}; };
return ( return loading ? (
<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 ? (
<Box <Box
style={{ style={{
height: "300px", height: "10vh",
width: "100%", width: "100%",
display: "flex", display: "flex",
justifyContent: "center", justifyContent: "center",
@ -1007,107 +862,6 @@ export const DrawerSwitchControl: React.FC<DrawerProps> = ({
</Box> </Box>
) : ( ) : (
<Grid> <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 <div
style={{ style={{
display: "flex", display: "flex",
@ -1126,6 +880,9 @@ export const DrawerSwitchControl: React.FC<DrawerProps> = ({
gap: "20px", gap: "20px",
}} }}
> >
<Box ps={"8px"} pt={"4px"}>
{dataStation?.switch ? RenderAPCStatus(dataStation?.switch) : ""}
</Box>
<Button <Button
disabled={isSubmit} disabled={isSubmit}
title={ title={
@ -1136,7 +893,7 @@ export const DrawerSwitchControl: React.FC<DrawerProps> = ({
// mt={'xs'} // mt={'xs'}
miw={"80px"} miw={"80px"}
size="xs" size="xs"
fz={"sm"} fz={"xs"}
variant="filled" variant="filled"
onClick={() => { onClick={() => {
if (listPortsSelected.length === listPorts.flat().length) { if (listPortsSelected.length === listPorts.flat().length) {
@ -1169,7 +926,7 @@ export const DrawerSwitchControl: React.FC<DrawerProps> = ({
// mt={'xs'} // mt={'xs'}
miw={"80px"} miw={"80px"}
size="xs" size="xs"
fz={"sm"} fz={"xs"}
variant="filled" variant="filled"
color="yellow" color="yellow"
onClick={() => { onClick={() => {
@ -1177,9 +934,7 @@ export const DrawerSwitchControl: React.FC<DrawerProps> = ({
listPortsSelected?.length > 0 listPortsSelected?.length > 0
? listPortsSelected ? listPortsSelected
: listPorts.flat(); : listPorts.flat();
if ( if (listPortsRestart.filter((el) => el.poe !== "ON").length > 0)
listPortsRestart.filter((el) => el.poe !== "ON").length > 0
)
socket?.emit("control_switch", { socket?.emit("control_switch", {
ports: listPortsRestart ports: listPortsRestart
.filter((el) => el.poe !== "ON") .filter((el) => el.poe !== "ON")
@ -1188,9 +943,7 @@ export const DrawerSwitchControl: React.FC<DrawerProps> = ({
station: stationAPI, station: stationAPI,
ip: stationAPI?.switch_control_ip, ip: stationAPI?.switch_control_ip,
}); });
if ( if (listPortsRestart.filter((el) => el.poe === "ON").length > 0)
listPortsRestart.filter((el) => el.poe === "ON").length > 0
)
socket?.emit("control_switch", { socket?.emit("control_switch", {
ports: listPortsRestart ports: listPortsRestart
.filter((el) => el.poe === "ON") .filter((el) => el.poe === "ON")
@ -1229,7 +982,7 @@ export const DrawerSwitchControl: React.FC<DrawerProps> = ({
// mt={'xs'} // mt={'xs'}
miw={"80px"} miw={"80px"}
size="xs" size="xs"
fz={"sm"} fz={"xs"}
variant="filled" variant="filled"
color="green" color="green"
onClick={() => { onClick={() => {
@ -1237,9 +990,7 @@ export const DrawerSwitchControl: React.FC<DrawerProps> = ({
listPortsSelected?.length > 0 listPortsSelected?.length > 0
? listPortsSelected ? listPortsSelected
: listPorts.flat(); : listPorts.flat();
if ( if (listPortsRestart.filter((el) => el.poe !== "ON").length > 0)
listPortsRestart.filter((el) => el.poe !== "ON").length > 0
)
socket?.emit("control_switch", { socket?.emit("control_switch", {
ports: listPortsRestart ports: listPortsRestart
.filter((el) => el.poe !== "ON") .filter((el) => el.poe !== "ON")
@ -1248,9 +999,7 @@ export const DrawerSwitchControl: React.FC<DrawerProps> = ({
station: stationAPI, station: stationAPI,
ip: stationAPI?.switch_control_ip, ip: stationAPI?.switch_control_ip,
}); });
if ( if (listPortsRestart.filter((el) => el.poe === "ON").length > 0)
listPortsRestart.filter((el) => el.poe === "ON").length > 0
)
socket?.emit("control_switch", { socket?.emit("control_switch", {
ports: listPortsRestart ports: listPortsRestart
.filter((el) => el.poe === "ON") .filter((el) => el.poe === "ON")
@ -1289,7 +1038,7 @@ export const DrawerSwitchControl: React.FC<DrawerProps> = ({
// mt={'xs'} // mt={'xs'}
miw={"80px"} miw={"80px"}
size="xs" size="xs"
fz={"sm"} fz={"xs"}
variant="filled" variant="filled"
color="red" color="red"
onClick={() => { onClick={() => {
@ -1297,9 +1046,7 @@ export const DrawerSwitchControl: React.FC<DrawerProps> = ({
listPortsSelected?.length > 0 listPortsSelected?.length > 0
? listPortsSelected ? listPortsSelected
: listPorts.flat(); : listPorts.flat();
if ( if (listPortsRestart.filter((el) => el.poe !== "ON").length > 0)
listPortsRestart.filter((el) => el.poe !== "ON").length > 0
)
socket?.emit("control_switch", { socket?.emit("control_switch", {
ports: listPortsRestart ports: listPortsRestart
.filter((el) => el.poe !== "ON") .filter((el) => el.poe !== "ON")
@ -1308,9 +1055,7 @@ export const DrawerSwitchControl: React.FC<DrawerProps> = ({
station: stationAPI, station: stationAPI,
ip: stationAPI?.switch_control_ip, ip: stationAPI?.switch_control_ip,
}); });
if ( if (listPortsRestart.filter((el) => el.poe === "ON").length > 0)
listPortsRestart.filter((el) => el.poe === "ON").length > 0
)
socket?.emit("control_switch", { socket?.emit("control_switch", {
ports: listPortsRestart ports: listPortsRestart
.filter((el) => el.poe === "ON") .filter((el) => el.poe === "ON")
@ -1333,8 +1078,106 @@ export const DrawerSwitchControl: React.FC<DrawerProps> = ({
</Button> </Button>
</div> </div>
</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> </Grid>
</Box>
)} )}
</Drawer> </Grid.Col>
</Grid>
); );
}; };

View File

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

View File

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