1071 lines
35 KiB
TypeScript
1071 lines
35 KiB
TypeScript
import "@mantine/core/styles.css";
|
|
import "@mantine/dates/styles.css";
|
|
import "@mantine/notifications/styles.css";
|
|
import "./App.css";
|
|
import classes from "./App.module.css";
|
|
import componentClasses from "./components/Component.module.css";
|
|
|
|
import {
|
|
Suspense,
|
|
useCallback,
|
|
useEffect,
|
|
useMemo,
|
|
useRef,
|
|
useState,
|
|
} from "react";
|
|
import {
|
|
Tabs,
|
|
Text,
|
|
Container,
|
|
Flex,
|
|
MantineProvider,
|
|
Grid,
|
|
ScrollArea,
|
|
LoadingOverlay,
|
|
Loader,
|
|
Box,
|
|
} from "@mantine/core";
|
|
import type {
|
|
DataSummaryTested,
|
|
FileInfo,
|
|
IScenario,
|
|
ReceivedFile,
|
|
ResponseData,
|
|
TBrands,
|
|
TCategories,
|
|
TLine,
|
|
TStation,
|
|
TUser,
|
|
} from "./untils/types";
|
|
import axios from "axios";
|
|
import CardLine from "./components/CardLine";
|
|
import { SocketProvider, useSocket } from "./context/SocketContext";
|
|
import // ButtonConnect,
|
|
// ButtonControlApc,
|
|
// ButtonCopy,
|
|
// ButtonDPELP,
|
|
// ButtonScenario,
|
|
// ButtonSelect,
|
|
"./components/ButtonAction";
|
|
import StationSetting from "./components/FormAddEdit";
|
|
// import DrawerScenario from "./components/DrawerScenario";
|
|
import { Notifications } from "@mantine/notifications";
|
|
import ModalTerminal from "./components/Modal/ModalTerminal";
|
|
import PageLogin from "./components/Authentication/LoginPage";
|
|
// import DrawerLogs from "./components/DrawerLogs";
|
|
import DraggableTabs from "./components/DragTabs";
|
|
import { isJsonString } from "./untils/helper";
|
|
import BottomToolBar from "./components/BottomToolBar";
|
|
import ModalConfirmSkipTestPort from "./components/Modal/ModalConfirmSkipTestPort";
|
|
import ModalConfirmRunPhysical from "./components/Modal/ModalConfirmRunPhysicalTest";
|
|
import ModalSummaryTested from "./components/Modal/ModalSummaryTested";
|
|
// import ModalConfirmRunScenario from "./components/Modal/ModalConfirmRunScenario";
|
|
|
|
const apiUrl = import.meta.env.VITE_BACKEND_URL;
|
|
|
|
// Helper: chia mảng thành các chunk theo size
|
|
const chunkArray = <T,>(array: T[], size: number): T[][] => {
|
|
const result: T[][] = [];
|
|
for (let i = 0; i < array.length; i += size) {
|
|
result.push(array.slice(i, i + size));
|
|
}
|
|
return result;
|
|
};
|
|
|
|
/**
|
|
* Main Component
|
|
*/
|
|
function App() {
|
|
const user = useMemo(() => {
|
|
return localStorage.getItem("user") &&
|
|
isJsonString(localStorage.getItem("user"))
|
|
? JSON.parse(localStorage.getItem("user") || "")
|
|
: null;
|
|
}, []);
|
|
|
|
if (!user) {
|
|
localStorage.removeItem("user");
|
|
window.location.href = "/";
|
|
}
|
|
|
|
document.title = "Automation Test";
|
|
const { socket } = useSocket();
|
|
const [stations, setStations] = useState<TStation[]>([]);
|
|
const [selectedLines, setSelectedLines] = useState<TLine[]>([]);
|
|
const [activeTab, setActiveTab] = useState("0");
|
|
const [isDisable, setIsDisable] = useState(false);
|
|
const [isOpenAddStation, setIsOpenAddStation] = useState(false);
|
|
const [isEditStation, setIsEditStation] = useState(false);
|
|
const [stationEdit, setStationEdit] = useState<TStation | undefined>();
|
|
const [scenarios, setScenarios] = useState<IScenario[]>([]);
|
|
const [openModalTerminal, setOpenModalTerminal] = useState(false);
|
|
const [selectedLine, setSelectedLine] = useState<TLine | undefined>();
|
|
const [loadingTerminal, setLoadingTerminal] = useState(false);
|
|
const [usersConnecting, setUsersConnecting] = useState<TUser[]>([]);
|
|
const [testLogContent, setTestLogContent] = useState("");
|
|
const [isLogModalOpen, setIsLogModalOpen] = useState(false);
|
|
const [expandedBottomBar, setExpandedBottomBar] = useState(true);
|
|
const [activeTabBottom, setActiveTabBottom] = useState<string>("command");
|
|
const lineBuffersRef = useRef(new Map<number, string>());
|
|
const flushScheduledRef = useRef(false);
|
|
const [listBrands, setListBrands] = useState<TBrands[]>([]);
|
|
const [listCategories, setListCategories] = useState<TCategories[]>([]);
|
|
const [listIos, setListIos] = useState<FileInfo[]>([]);
|
|
const [listLicense, setListLicense] = useState<FileInfo[]>([]);
|
|
const [isLoading, setIsLoading] = useState(true);
|
|
const [linesConfirmSkipPort, setLinesConfirmSkipPort] = useState<TLine[]>([]);
|
|
const [linesConfirmRunPhysical, setLinesConfirmRunPhysical] = useState<
|
|
TLine[]
|
|
>([]);
|
|
const [dataSummaryTested, setDataSummaryTested] =
|
|
useState<DataSummaryTested | null>(null);
|
|
|
|
const connectApcSwitch = (station: TStation) => {
|
|
if (!station?.is_active) return;
|
|
if (station?.apc_1_ip && station?.apc_1_port) {
|
|
socket?.emit("connect_apc", {
|
|
station: station,
|
|
apcIp: station?.apc_1_ip,
|
|
apcName: "apc_1",
|
|
});
|
|
}
|
|
if (station?.apc_2_ip && station?.apc_2_port) {
|
|
socket?.emit("connect_apc", {
|
|
station: station,
|
|
apcIp: station?.apc_2_ip,
|
|
apcName: "apc_2",
|
|
});
|
|
}
|
|
if (station?.switch_control_ip && station?.switch_control_port) {
|
|
socket?.emit("connect_switch", {
|
|
station: station,
|
|
ip: station?.switch_control_ip,
|
|
});
|
|
}
|
|
};
|
|
|
|
// function get list station
|
|
const getStation = async () => {
|
|
try {
|
|
const response = await axios.get(apiUrl + "api/stations");
|
|
if (response.status) {
|
|
if (Array.isArray(response.data)) {
|
|
setStations(
|
|
response.data.map((station) => {
|
|
connectApcSwitch(station);
|
|
const lines = (station?.lines || []).sort(
|
|
(a: TLine, b: TLine) => a?.lineNumber - b?.lineNumber
|
|
);
|
|
return { ...station, lines };
|
|
})
|
|
);
|
|
}
|
|
}
|
|
} catch (error) {
|
|
console.log("Error get station", error);
|
|
}
|
|
};
|
|
|
|
// function get list station
|
|
const getScenarios = async () => {
|
|
try {
|
|
const response = await axios.get(apiUrl + "api/scenarios");
|
|
if (response.data.status) {
|
|
if (Array.isArray(response.data.data.data)) {
|
|
setScenarios(response.data.data.data);
|
|
}
|
|
}
|
|
} catch (error) {
|
|
console.log("Error get station", error);
|
|
}
|
|
};
|
|
|
|
// function get list brand
|
|
const getBrands = async () => {
|
|
try {
|
|
const response = await axios.get(apiUrl + "api/brands");
|
|
if (response.data) {
|
|
if (response.data && Array.isArray(response.data)) {
|
|
setListBrands(response.data);
|
|
}
|
|
}
|
|
} catch (error) {
|
|
console.log("Error get brand", error);
|
|
}
|
|
};
|
|
|
|
// function get list brand
|
|
const getCategories = async () => {
|
|
try {
|
|
const response = await axios.get(apiUrl + "api/categories");
|
|
if (response.data && Array.isArray(response.data)) {
|
|
setListCategories(response.data);
|
|
}
|
|
} catch (error) {
|
|
console.log("Error get brand", error);
|
|
}
|
|
};
|
|
|
|
// function get list ios
|
|
const getListIos = async () => {
|
|
try {
|
|
const response = await axios.get(apiUrl + "api/ios");
|
|
if (response.data && Array.isArray(response.data)) {
|
|
setListIos(response.data);
|
|
}
|
|
} catch (error) {
|
|
console.log("Error get ios", error);
|
|
}
|
|
};
|
|
|
|
// function get list license
|
|
const getListLicense = async () => {
|
|
try {
|
|
const response = await axios.get(apiUrl + "api/license");
|
|
if (response.data && Array.isArray(response.data)) {
|
|
setListLicense(response.data);
|
|
}
|
|
} catch (error) {
|
|
console.log("Error get ios", error);
|
|
}
|
|
};
|
|
|
|
useEffect(() => {
|
|
if (!socket) return;
|
|
getStation();
|
|
getScenarios();
|
|
getBrands();
|
|
getCategories();
|
|
getListIos();
|
|
getListLicense();
|
|
}, [socket]);
|
|
|
|
useEffect(() => {
|
|
setTimeout(() => {
|
|
setIsLoading(false);
|
|
}, 2000);
|
|
}, []);
|
|
|
|
useEffect(() => {
|
|
if (!socket || !stations?.length) return;
|
|
|
|
socket.on("line_connected", (data) =>
|
|
updateValueLineStation(
|
|
data?.lineId,
|
|
{ status: data.status, connecting: false },
|
|
data?.stationId
|
|
)
|
|
);
|
|
|
|
socket.on("line_disconnected", (data) =>
|
|
updateValueLineStation(
|
|
data?.lineId,
|
|
{
|
|
status: data.status,
|
|
connecting: false,
|
|
netOutput: "[CLEAR_TERMINAL_SCROLL_BACK]",
|
|
output: "[CLEAR_TERMINAL_SCROLL_BACK]",
|
|
listFeatureTested: [],
|
|
latestScenario: undefined,
|
|
isReady: false,
|
|
},
|
|
data?.stationId
|
|
)
|
|
);
|
|
|
|
socket?.on("line_output", (data) => {
|
|
const { lineId, data: text } = data;
|
|
// updateValueLineStation(
|
|
// data?.lineId,
|
|
// { isReady: data.isReady },
|
|
// data?.stationId
|
|
// );
|
|
|
|
const buf = lineBuffersRef.current.get(lineId) || "";
|
|
lineBuffersRef.current.set(lineId, buf + text);
|
|
|
|
if (!flushScheduledRef.current) {
|
|
flushScheduledRef.current = true;
|
|
setTimeout(() => flushBuffers(), 50);
|
|
}
|
|
});
|
|
|
|
socket?.on("update_status_ready", (data) => {
|
|
const { isReady } = data;
|
|
updateValueLineStation(
|
|
data?.lineId,
|
|
{ isReady: isReady },
|
|
data?.stationId
|
|
);
|
|
});
|
|
|
|
socket?.on("line_error", (data) => {
|
|
updateValueLineStation(
|
|
data?.lineId,
|
|
{ netOutput: data.error, connecting: false },
|
|
data?.stationId
|
|
);
|
|
});
|
|
|
|
socket?.on("init", (data) => {
|
|
if (Array.isArray(data)) {
|
|
// console.log(data);
|
|
setLoadingTerminal(true);
|
|
data.forEach((value) => {
|
|
updateValueLineStation(
|
|
value?.id,
|
|
{ ...value, netOutput: value.output },
|
|
value?.stationId
|
|
);
|
|
});
|
|
}
|
|
});
|
|
|
|
socket?.on("user_connecting", (data) => {
|
|
if (Array.isArray(data)) {
|
|
setUsersConnecting(data);
|
|
}
|
|
});
|
|
|
|
socket?.on("user_open_cli", (data) => {
|
|
setTimeout(() => {
|
|
updateValueLineStation(
|
|
data.lineId,
|
|
{
|
|
cliOpened: true,
|
|
userEmailOpenCLI: data.userEmailOpenCLI,
|
|
userOpenCLI: data.userOpenCLI,
|
|
},
|
|
data?.stationId
|
|
);
|
|
}, 100);
|
|
});
|
|
|
|
socket?.on("user_close_cli", (data) => {
|
|
setTimeout(() => {
|
|
updateValueLineStation(
|
|
data.lineId,
|
|
{
|
|
cliOpened: false,
|
|
userEmailOpenCLI: undefined,
|
|
userOpenCLI: undefined,
|
|
},
|
|
data?.stationId
|
|
);
|
|
}, 100);
|
|
});
|
|
|
|
const receivedFiles: Record<string, ReceivedFile> = {};
|
|
socket?.on("response_content_log", (data: ResponseData) => {
|
|
if (!data.chunk) {
|
|
const decoder = new TextDecoder("utf-8");
|
|
const str = decoder.decode(data as unknown as ArrayBuffer);
|
|
setTestLogContent(str);
|
|
return;
|
|
}
|
|
|
|
const { fileId, chunkIndex, totalChunks, chunk } = data.chunk;
|
|
|
|
if (!receivedFiles[fileId]) {
|
|
receivedFiles[fileId] = {
|
|
chunks: [],
|
|
receivedChunks: 0,
|
|
totalChunks,
|
|
};
|
|
}
|
|
|
|
let bufferChunk: Buffer;
|
|
|
|
if (chunk instanceof ArrayBuffer) {
|
|
bufferChunk = Buffer.from(new Uint8Array(chunk)); // ✅ convert properly
|
|
} else if (chunk instanceof Uint8Array) {
|
|
bufferChunk = Buffer.from(chunk); // ✅ direct support
|
|
} else {
|
|
bufferChunk = chunk as Buffer; // fallback if server sends Buffer
|
|
}
|
|
|
|
receivedFiles[fileId].chunks[chunkIndex] = bufferChunk;
|
|
receivedFiles[fileId].receivedChunks++;
|
|
|
|
if (receivedFiles[fileId].receivedChunks === totalChunks) {
|
|
const fileBuffer = Buffer.concat(receivedFiles[fileId].chunks);
|
|
const decoder = new TextDecoder("utf-8");
|
|
const str = decoder.decode(fileBuffer);
|
|
|
|
setTestLogContent(str);
|
|
delete receivedFiles[fileId]; // cleanup ✅
|
|
}
|
|
});
|
|
|
|
socket?.on("data_textfsm", (data) => {
|
|
setTimeout(() => {
|
|
updateValueLineStation(
|
|
data.lineId,
|
|
{
|
|
data: data.data,
|
|
inventory: data.inventory,
|
|
latestScenario: data.latestScenario,
|
|
},
|
|
data?.stationId
|
|
);
|
|
}, 100);
|
|
});
|
|
|
|
socket?.on("update_ticket", (data) => {
|
|
setTimeout(() => {
|
|
updateValueLineStation(
|
|
data.lineId,
|
|
{
|
|
tickets: data.data,
|
|
},
|
|
data?.stationId
|
|
);
|
|
}, 100);
|
|
});
|
|
|
|
socket?.on("update_baud", (data) => {
|
|
setTimeout(() => {
|
|
updateValueLineStation(
|
|
data.lineId,
|
|
{
|
|
baud: data.data,
|
|
},
|
|
data?.stationId
|
|
);
|
|
}, 100);
|
|
});
|
|
|
|
socket?.on("line_connecting", (data) => {
|
|
setTimeout(() => {
|
|
updateValueLineStation(
|
|
data?.lineId,
|
|
{ connecting: true },
|
|
data?.stationId
|
|
);
|
|
}, 100);
|
|
});
|
|
|
|
socket?.on("running_scenario", (data) => {
|
|
setTimeout(() => {
|
|
updateValueLineStation(
|
|
data?.lineId,
|
|
{
|
|
runningScenario: data?.title || "",
|
|
runningPhysical: data?.physical || false,
|
|
ports: data?.ports || [],
|
|
listPortsPhysical: [],
|
|
},
|
|
data?.stationId
|
|
);
|
|
}, 100);
|
|
});
|
|
|
|
socket?.on("user_clear_terminal", (data) => {
|
|
updateValueLineStation(
|
|
data?.lineId,
|
|
{ netOutput: "", output: "", loadingClearTerminal: true },
|
|
data?.stationId
|
|
);
|
|
});
|
|
|
|
socket?.on("test_port_physical", (data) => {
|
|
if (data?.data && data?.data.length > 0)
|
|
updateValueLineStation(
|
|
data?.lineId,
|
|
{ listPortsPhysical: data?.data },
|
|
data?.stationId
|
|
);
|
|
});
|
|
|
|
socket?.on("feature_tested", (data) => {
|
|
if (data?.listFeatureTested) {
|
|
updateValueLineStation(
|
|
data?.lineId,
|
|
{
|
|
listFeatureTested: data?.listFeatureTested,
|
|
isSkipPhysical: data?.isSkipPhysical,
|
|
reasonSkipPhysical: data?.reasonSkipPhysical,
|
|
},
|
|
data?.stationId
|
|
);
|
|
if (data?.isSkipPhysical && !data?.reasonSkipPhysical) {
|
|
const valueLine = findLineByLineId(data?.lineId, data?.stationId);
|
|
if (valueLine) setLinesConfirmSkipPort((pre) => [...pre, valueLine]);
|
|
}
|
|
if (
|
|
!data?.listFeatureTested?.includes("PHYSICAL") &&
|
|
data?.listFeatureTested?.includes("DPELP")
|
|
) {
|
|
const valueLine = findLineByLineId(data?.lineId, data?.stationId);
|
|
if (valueLine && openModalTerminal)
|
|
setLinesConfirmRunPhysical((pre) => [...pre, valueLine]);
|
|
}
|
|
}
|
|
});
|
|
|
|
socket?.on("summary_tested", (data) => {
|
|
if (data?.body && openModalTerminal) {
|
|
setDataSummaryTested({
|
|
body: data?.body || "",
|
|
title: data?.title || "",
|
|
});
|
|
}
|
|
});
|
|
|
|
// ✅ cleanup on unmount or when socket changes
|
|
return () => {
|
|
socket.off("init");
|
|
socket.off("line_output");
|
|
socket.off("line_error");
|
|
socket.off("line_connected");
|
|
socket.off("line_disconnected");
|
|
socket.off("user_connecting");
|
|
socket.off("user_open_cli");
|
|
socket.off("user_close_cli");
|
|
socket.off("response_content_log");
|
|
socket.off("data_textfsm");
|
|
socket.off("update_ticket");
|
|
socket.off("update_baud");
|
|
socket.off("line_connecting");
|
|
socket.off("running_scenario");
|
|
socket.off("user_clear_terminal");
|
|
socket.off("test_port_physical");
|
|
socket.off("feature_tested");
|
|
socket.off("summary_tested");
|
|
};
|
|
}, [socket, stations, selectedLine]);
|
|
|
|
const flushBuffers = useCallback(() => {
|
|
setStations((prev) =>
|
|
prev.map((station) => ({
|
|
...station,
|
|
lines: station.lines.map((line) => {
|
|
const buffered = lineBuffersRef.current.get(line.id || 0);
|
|
if (!buffered) return line; // không có update
|
|
const data = {
|
|
...line,
|
|
netOutput: (line.netOutput || "") + buffered,
|
|
output: buffered,
|
|
loadingOutput: line.loadingOutput ? false : true,
|
|
loadingClearTerminal: false,
|
|
};
|
|
updateValueSelectedLine(line?.id || 0, data);
|
|
return data;
|
|
}),
|
|
}))
|
|
);
|
|
// clear
|
|
lineBuffersRef.current.clear();
|
|
flushScheduledRef.current = false;
|
|
}, []);
|
|
|
|
const updateValueLineStation = useCallback(
|
|
(lineId: number, updates: Partial<TLine>, stationId?: number) => {
|
|
setStations((prevStations) =>
|
|
prevStations?.map((station: TStation) =>
|
|
station.id === stationId
|
|
? {
|
|
...station,
|
|
lines: station.lines?.map((lineItem: TLine) => {
|
|
if (lineItem.id !== lineId) return lineItem;
|
|
|
|
const isNetOutput = typeof updates?.netOutput !== "undefined";
|
|
|
|
return {
|
|
...lineItem,
|
|
...updates,
|
|
lineNumber: lineItem.lineNumber,
|
|
line_number: lineItem.line_number,
|
|
ports:
|
|
updates?.ports && updates?.ports?.length > 0
|
|
? updates?.ports
|
|
: lineItem.ports || [],
|
|
...(isNetOutput && {
|
|
netOutput: updates?.loadingClearTerminal
|
|
? ""
|
|
: (lineItem.netOutput || "") +
|
|
(updates.netOutput || ""),
|
|
output: updates.netOutput, // Nếu netOutput thì update luôn output
|
|
loadingOutput: lineItem.loadingOutput ? false : true,
|
|
loadingClearTerminal: updates?.loadingClearTerminal
|
|
? updates?.loadingClearTerminal
|
|
: false,
|
|
}),
|
|
};
|
|
}),
|
|
}
|
|
: station
|
|
)
|
|
);
|
|
|
|
// Update selectedLine nếu nó đang được chọn
|
|
setSelectedLine((prevSelected) => {
|
|
if (!prevSelected || prevSelected.id !== lineId) return prevSelected;
|
|
|
|
const isNetOutput = typeof updates?.netOutput !== "undefined";
|
|
|
|
return {
|
|
...prevSelected,
|
|
...updates,
|
|
ports:
|
|
updates?.ports && updates?.ports?.length > 0
|
|
? updates?.ports
|
|
: prevSelected.ports || [],
|
|
...(isNetOutput && {
|
|
netOutput: updates?.loadingClearTerminal
|
|
? ""
|
|
: (prevSelected.netOutput || "") + (updates.netOutput || ""),
|
|
output: updates.netOutput,
|
|
loadingOutput: prevSelected.loadingOutput ? false : true,
|
|
loadingClearTerminal: updates?.loadingClearTerminal
|
|
? updates?.loadingClearTerminal
|
|
: false,
|
|
}),
|
|
};
|
|
});
|
|
|
|
updateValueSelectedLines(lineId, updates);
|
|
},
|
|
[]
|
|
);
|
|
|
|
const updateValueSelectedLines = (
|
|
lineId: number,
|
|
updates: Partial<TLine>
|
|
) => {
|
|
// Update selectedLine nếu nó đang được chọn
|
|
setSelectedLines((prevSelected) =>
|
|
prevSelected?.map((line) => {
|
|
if (line.id === lineId) {
|
|
return { ...line, ...updates };
|
|
}
|
|
return line;
|
|
})
|
|
);
|
|
};
|
|
|
|
const updateValueSelectedLine = (lineId: number, updates: Partial<TLine>) => {
|
|
// Update selectedLine nếu nó đang được chọn
|
|
setSelectedLine((prevSelected) => {
|
|
if (!prevSelected || prevSelected.id !== lineId) return prevSelected;
|
|
return {
|
|
...prevSelected,
|
|
...updates,
|
|
};
|
|
});
|
|
};
|
|
|
|
// const getLine = (lineId: number, stationId: number) => {
|
|
// const station = stations?.find((sta) => sta.id === stationId);
|
|
// if (station) {
|
|
// const line = station.lines?.find((li) => li.id === lineId);
|
|
// return line;
|
|
// } else return null;
|
|
// };
|
|
|
|
const openTerminal = (line: TLine) => {
|
|
setOpenModalTerminal(true);
|
|
const data = { ...line };
|
|
if (!line.userOpenCLI) {
|
|
data.cliOpened = true;
|
|
data.userEmailOpenCLI = user?.email;
|
|
data.userOpenCLI = user?.userName;
|
|
socket?.emit("open_cli", {
|
|
lineId: line.id,
|
|
stationId: line.stationId || line.station_id,
|
|
userEmail: user?.email,
|
|
userName: user?.userName,
|
|
});
|
|
}
|
|
setSelectedLine(data);
|
|
};
|
|
|
|
useEffect(() => {
|
|
if (!expandedBottomBar) {
|
|
setActiveTabBottom("command");
|
|
}
|
|
}, [expandedBottomBar]);
|
|
|
|
const findLineByLineId = (lineId: number, stationId?: number) => {
|
|
const valueStation = stations.find((el) => el.id === stationId);
|
|
if (!valueStation || !stationId) return null;
|
|
const valueLine = valueStation?.lines?.find((el) => el.id === lineId);
|
|
return valueLine;
|
|
};
|
|
|
|
return (
|
|
<Container w={"100%"} style={{ maxWidth: "100%" }}>
|
|
<DraggableTabs
|
|
socket={socket}
|
|
usersConnecting={usersConnecting}
|
|
setIsEditStation={setIsEditStation}
|
|
setIsOpenAddStation={setIsOpenAddStation}
|
|
setStationEdit={setStationEdit}
|
|
tabsData={stations}
|
|
scenarios={scenarios}
|
|
setScenarios={setScenarios}
|
|
panels={stations.map((station) => (
|
|
<Tabs.Panel
|
|
className={classes.content}
|
|
key={station.id}
|
|
value={station.id.toString()}
|
|
pt="md"
|
|
>
|
|
<Flex className={classes.containerMain}>
|
|
<Grid>
|
|
<Grid.Col
|
|
span={12}
|
|
style={{
|
|
borderRadius: 8,
|
|
}}
|
|
>
|
|
<ScrollArea
|
|
// h={expandedBottomBar ? "80vh" : "85vh"}
|
|
h={expandedBottomBar ? "73vh" : "83vh"}
|
|
type="scroll"
|
|
scrollbars="y"
|
|
style={{ overflowX: "hidden" }}
|
|
className={componentClasses.hideScrollBar}
|
|
>
|
|
{isLoading ? (
|
|
<Box
|
|
style={{
|
|
height: "15vh",
|
|
width: "100%",
|
|
display: "flex",
|
|
justifyContent: "center",
|
|
alignItems: "center",
|
|
}}
|
|
>
|
|
<Loader color="blue" />
|
|
</Box>
|
|
) : station.lines.length > 0 ? (
|
|
station.lines.length < 9 ? (
|
|
<Grid
|
|
gutter="md"
|
|
style={{
|
|
margin: 0,
|
|
width: "100%",
|
|
}}
|
|
>
|
|
{station.lines.map((line, i) => (
|
|
<Grid.Col
|
|
key={line.id ?? i}
|
|
span={3}
|
|
style={{
|
|
display: "flex",
|
|
height: "100%",
|
|
maxWidth: "100%",
|
|
overflow: "hidden",
|
|
}}
|
|
>
|
|
<CardLine
|
|
socket={socket}
|
|
stationItem={station}
|
|
line={line}
|
|
selectedLines={selectedLines}
|
|
setSelectedLines={setSelectedLines}
|
|
openTerminal={openTerminal}
|
|
loadTerminal={
|
|
loadingTerminal &&
|
|
Number(station.id) === Number(activeTab)
|
|
}
|
|
scenarios={scenarios}
|
|
/>
|
|
</Grid.Col>
|
|
))}
|
|
</Grid>
|
|
) : (
|
|
// >= 9 lines: chia làm 2 cột, mỗi cột chứa 1/2 số line,
|
|
// mỗi cột hiển thị 2 item trên một "hàng" như ví dụ yêu cầu
|
|
(() => {
|
|
// const total = station.lines.length;
|
|
const half = 8;
|
|
const leftLines = station.lines.slice(0, half);
|
|
const rightLines = station.lines.slice(half);
|
|
|
|
const leftChunks = chunkArray(leftLines, 2);
|
|
const rightChunks = chunkArray(rightLines, 2);
|
|
const numRows = Math.max(
|
|
leftChunks.length,
|
|
rightChunks.length
|
|
);
|
|
|
|
return (
|
|
<>
|
|
{Array.from({ length: numRows }).map(
|
|
(_, rowIndex) => {
|
|
const leftRow = leftChunks[rowIndex] || [];
|
|
const rightRow = rightChunks[rowIndex] || [];
|
|
|
|
return (
|
|
<Grid
|
|
key={rowIndex}
|
|
gutter="md"
|
|
style={{
|
|
margin: 0,
|
|
width: "100%",
|
|
}}
|
|
>
|
|
{/* Cột A */}
|
|
<Grid.Col span={6}>
|
|
<Grid gutter="md">
|
|
{leftRow.map((line, i) => (
|
|
<Grid.Col
|
|
key={
|
|
line.id ?? `L-${rowIndex}-${i}`
|
|
}
|
|
span={6}
|
|
style={{
|
|
display: "flex",
|
|
height: "100%",
|
|
maxWidth: "100%",
|
|
overflow: "hidden",
|
|
}}
|
|
>
|
|
<CardLine
|
|
socket={socket}
|
|
stationItem={station}
|
|
line={line}
|
|
selectedLines={selectedLines}
|
|
setSelectedLines={
|
|
setSelectedLines
|
|
}
|
|
openTerminal={openTerminal}
|
|
loadTerminal={
|
|
loadingTerminal &&
|
|
Number(station.id) ===
|
|
Number(activeTab)
|
|
}
|
|
scenarios={scenarios}
|
|
/>
|
|
</Grid.Col>
|
|
))}
|
|
</Grid>
|
|
</Grid.Col>
|
|
|
|
{/* Cột B */}
|
|
<Grid.Col span={6}>
|
|
<Grid gutter="md">
|
|
{rightRow.map((line, i) => (
|
|
<Grid.Col
|
|
key={
|
|
line.id ?? `R-${rowIndex}-${i}`
|
|
}
|
|
span={6}
|
|
style={{
|
|
display: "flex",
|
|
height: "100%",
|
|
maxWidth: "100%",
|
|
overflow: "hidden",
|
|
}}
|
|
>
|
|
<CardLine
|
|
socket={socket}
|
|
stationItem={station}
|
|
line={line}
|
|
selectedLines={selectedLines}
|
|
setSelectedLines={
|
|
setSelectedLines
|
|
}
|
|
openTerminal={openTerminal}
|
|
loadTerminal={
|
|
loadingTerminal &&
|
|
Number(station.id) ===
|
|
Number(activeTab)
|
|
}
|
|
scenarios={scenarios}
|
|
/>
|
|
</Grid.Col>
|
|
))}
|
|
</Grid>
|
|
</Grid.Col>
|
|
</Grid>
|
|
);
|
|
}
|
|
)}
|
|
</>
|
|
);
|
|
})()
|
|
)
|
|
) : (
|
|
<Text ta="center" c="dimmed" mt="lg">
|
|
No lines configured
|
|
</Text>
|
|
)}
|
|
</ScrollArea>
|
|
</Grid.Col>
|
|
</Grid>
|
|
<BottomToolBar
|
|
selectedLines={selectedLines}
|
|
socket={socket}
|
|
setSelectedLines={setSelectedLines}
|
|
isDisable={isDisable}
|
|
setIsDisable={setIsDisable}
|
|
station={station}
|
|
stationId={Number(activeTab)}
|
|
testLogContent={testLogContent}
|
|
isLogModalOpen={isLogModalOpen}
|
|
setIsLogModalOpen={setIsLogModalOpen}
|
|
setTestLogContent={setTestLogContent}
|
|
scenarios={scenarios}
|
|
setScenarios={setScenarios}
|
|
setExpanded={setExpandedBottomBar}
|
|
activeTabBottom={activeTabBottom}
|
|
setActiveTabBottom={setActiveTabBottom}
|
|
isExpand={expandedBottomBar}
|
|
listBrands={listBrands}
|
|
listCategories={listCategories}
|
|
/>
|
|
</Flex>
|
|
</Tabs.Panel>
|
|
))}
|
|
onChange={(id) => {
|
|
const station = stations.find((el) => el.id === Number(activeTab));
|
|
if (station) {
|
|
(station?.lines || [])?.forEach((el) => {
|
|
if (el?.userOpenCLI === user?.userName)
|
|
socket?.emit("close_cli", {
|
|
lineId: el?.id,
|
|
stationId: Number(activeTab),
|
|
});
|
|
});
|
|
}
|
|
setActiveTab(id?.toString() || "0");
|
|
setSelectedLines([]);
|
|
setLoadingTerminal(false);
|
|
setTimeout(() => {
|
|
setLoadingTerminal(true);
|
|
}, 500);
|
|
}}
|
|
setActive={setActiveTab}
|
|
active={activeTab}
|
|
onSendCommand={(value) => {
|
|
const listLine = selectedLines.length
|
|
? selectedLines
|
|
: stations.find((el) => el.id === Number(activeTab))?.lines;
|
|
if (listLine?.length) {
|
|
socket?.emit("write_command_line_from_web", {
|
|
lineIds: listLine.map((line) => line.id),
|
|
stationId: Number(activeTab),
|
|
command: value + "\n",
|
|
});
|
|
setTimeout(() => {
|
|
socket?.emit("write_command_line_from_web", {
|
|
lineIds: listLine.map((line) => line.id),
|
|
stationId: Number(activeTab),
|
|
command: " \n",
|
|
});
|
|
}, 1000);
|
|
}
|
|
}}
|
|
listBrands={listBrands}
|
|
listCategories={listCategories}
|
|
/>
|
|
|
|
<StationSetting
|
|
dataStation={stationEdit}
|
|
isOpen={isOpenAddStation}
|
|
onClose={() => {
|
|
setIsOpenAddStation(false);
|
|
setIsEditStation(false);
|
|
setStationEdit(undefined);
|
|
}}
|
|
isEdit={isEditStation}
|
|
setStations={setStations}
|
|
setActiveTab={(id: string) => {
|
|
setActiveTab(id);
|
|
setLoadingTerminal(false);
|
|
setTimeout(() => {
|
|
setLoadingTerminal(true);
|
|
}, 100);
|
|
}}
|
|
stations={stations}
|
|
socket={socket}
|
|
/>
|
|
|
|
<ModalTerminal
|
|
selectedLines={selectedLines}
|
|
opened={openModalTerminal}
|
|
onClose={() => {
|
|
setOpenModalTerminal(false);
|
|
setSelectedLine(undefined);
|
|
}}
|
|
line={selectedLine}
|
|
socket={socket}
|
|
stationItem={stations.find((el) => el.id === Number(activeTab))}
|
|
scenarios={scenarios}
|
|
listIos={listIos}
|
|
listLicense={listLicense}
|
|
getListIos={getListIos}
|
|
getListLicense={getListLicense}
|
|
setScenarios={setScenarios}
|
|
listBrands={listBrands}
|
|
listCategories={listCategories}
|
|
setLinesConfirmSkipPort={setLinesConfirmSkipPort}
|
|
linesConfirmSkipPort={linesConfirmSkipPort}
|
|
dataSummaryTested={dataSummaryTested}
|
|
/>
|
|
|
|
{/* <ModalConfirmRunScenario
|
|
socket={socket}
|
|
station={stations.find((el) => el.id === Number(activeTab))}
|
|
scenarios={scenarios}
|
|
/> */}
|
|
|
|
<ModalConfirmSkipTestPort
|
|
listLines={linesConfirmSkipPort}
|
|
setListLines={setLinesConfirmSkipPort}
|
|
socket={socket}
|
|
station={stations.find((el) => el.id === Number(activeTab))}
|
|
/>
|
|
|
|
<ModalConfirmRunPhysical
|
|
listLines={linesConfirmRunPhysical}
|
|
setListLines={setLinesConfirmRunPhysical}
|
|
socket={socket}
|
|
station={stations.find((el) => el.id === Number(activeTab))}
|
|
/>
|
|
|
|
<ModalSummaryTested
|
|
data={dataSummaryTested}
|
|
setData={setDataSummaryTested}
|
|
/>
|
|
</Container>
|
|
);
|
|
}
|
|
|
|
export default function Main() {
|
|
const user = useMemo(() => {
|
|
return localStorage.getItem("user") &&
|
|
isJsonString(localStorage.getItem("user"))
|
|
? JSON.parse(localStorage.getItem("user") || "")
|
|
: null;
|
|
}, []);
|
|
|
|
return (
|
|
<MantineProvider>
|
|
<SocketProvider>
|
|
<Suspense
|
|
fallback={
|
|
<LoadingOverlay
|
|
visible={true}
|
|
zIndex={1000}
|
|
overlayProps={{ radius: "sm", blur: 1 }}
|
|
/>
|
|
}
|
|
>
|
|
<Notifications position="top-right" autoClose={5000} />
|
|
{user ? (
|
|
<App />
|
|
) : (
|
|
<Container w={"100%"} style={{ maxWidth: "100%", padding: 0 }}>
|
|
<PageLogin />
|
|
</Container>
|
|
)}
|
|
</Suspense>
|
|
</SocketProvider>
|
|
</MantineProvider>
|
|
);
|
|
}
|