Upgrade UI Modal Terminal

This commit is contained in:
andrew.ng 2026-06-01 16:51:09 +07:00
parent 9fac74ccc0
commit 53d8f184d0
3 changed files with 702 additions and 607 deletions

View File

@ -0,0 +1,32 @@
import { Box, Flex, Text } from "@mantine/core";
interface GroupButtonProps {
title: string;
children: React.ReactNode;
}
export default function GroupButtonTerminal({
title,
children,
}: GroupButtonProps) {
return (
<Box
p="xs"
style={{
border: "1px solid var(--mantine-color-gray-3)",
borderRadius: "8px",
backgroundColor: "var(--mantine-color-gray-0)",
}}
>
<Flex justify="space-between" align="center" gap="md">
<Text fz={12} c="dimmed">
{title}
</Text>
<Flex gap="sm" wrap="wrap">
{children}
</Flex>
</Flex>
</Box>
);
}

View File

@ -2,6 +2,7 @@ import {
ActionIcon,
Box,
Button,
Card,
CloseButton,
Flex,
Grid,
@ -58,6 +59,7 @@ import ModalLineHistory from "./ModalLineHistory";
import AutoProgress from "../Components/AutoProgress";
import LoaderOverlay from "../Components/LoaderOverlay";
import { bodyDPELP, convertFromKilobytesString } from "../../untils/helper";
import GroupButtonTerminal from "../Components/GroupButtonTerminal";
const apiUrl = import.meta.env.VITE_BACKEND_URL;
const INIT_TICKET = {
@ -69,6 +71,83 @@ const INIT_TICKET = {
status: "open",
};
// Sub-component: Switch Connection Status
interface SwitchConnectionStatusProps {
line: TLine | undefined;
findSwitchPort: (portName: string) => SwitchPortsProps | null;
normalizePortName: (port: string) => string;
}
const SwitchConnectionStatus = ({
line,
findSwitchPort,
normalizePortName,
}: SwitchConnectionStatusProps) => {
const port = line?.interface ? findSwitchPort(line.interface) : null;
const isConnected = port?.status === "ON";
const portName = line?.interface ? normalizePortName(line.interface) : "";
return (
<Flex justify="center" mb="sm">
<IconCircleDot size={18} color={isConnected ? "green" : "red"} />
<Flex ml="xs" gap="xs">
<Text size="sm">Internet</Text>
{line?.interface ? (
<Text size="sm" c={isConnected ? "green" : "red"}>
{isConnected ? "Connected" : "Not Connected"} ({portName})
</Text>
) : (
<Text size="sm" c="red">
Not config
</Text>
)}
</Flex>
</Flex>
);
};
// Sub-component: Switch Control Buttons
interface SwitchControlButtonsProps {
isDisable: boolean;
hasInterface: boolean;
onSwitchOn: () => void;
onSwitchOff: () => void;
onSwitchRestart: () => void;
}
const SwitchControlButtons = ({
isDisable,
hasInterface,
onSwitchOn,
onSwitchOff,
onSwitchRestart,
}: SwitchControlButtonsProps) => {
const SWITCH_ACTIONS = [
{ label: "ON", color: "green", handler: onSwitchOn },
{ label: "OFF", color: "red", handler: onSwitchOff },
{ label: "Restart", color: "orange", handler: onSwitchRestart },
] as const;
return (
<Flex justify="space-around" gap="xs">
{SWITCH_ACTIONS.map(({ label, color, handler }) => (
<Button
key={label}
className={classes.buttonControl}
disabled={isDisable || !hasInterface}
fw={400}
variant="outline"
color={color}
size="xs"
onClick={handler}
>
{label}
</Button>
))}
</Flex>
);
};
const ModalTerminal = ({
opened,
onClose,
@ -817,6 +896,13 @@ const ModalTerminal = ({
<IconCircleCheckFilled color="green" fontSize={"18px"} />
)}
</Flex>
<Card
shadow="sm"
p="sm"
radius="md"
withBorder
h={"fit-content"}
>
<Flex
mt="4px"
align="center"
@ -958,7 +1044,9 @@ const ModalTerminal = ({
e.preventDefault();
e.stopPropagation();
if (!line?.inventory?.sn) return;
navigator.clipboard.writeText(line.inventory?.sn || "");
navigator.clipboard.writeText(
line.inventory?.sn || "",
);
}}
>
{line?.inventory?.sn || ""}
@ -988,12 +1076,22 @@ const ModalTerminal = ({
copiedColor="violet"
/>
</Flex>
</Card>
</Box>
<Tabs.Panel value="general" h={"95%"}>
<Flex
justify={"space-between"}
direction={"column"}
h={"95%"}
>
<ScrollArea h={"95%"}>
<Card
shadow="sm"
p="sm"
radius="md"
withBorder
h={"fit-content"}
mt={"4px"}
>
<Flex>
<Text size="md" mr={"6px"} fw={"bold"}>
@ -1082,7 +1180,15 @@ const ModalTerminal = ({
</Text>
</Flex>
</Flex>
<Box>
</Card>
<Card
shadow="sm"
p="sm"
radius="md"
withBorder
h={"fit-content"}
mt={"4px"}
>
<Flex justify={"space-between"} align={"center"}>
<Text size="md" mr={"sm"} fw={"bold"}>
Warning from test report: AI
@ -1118,7 +1224,7 @@ const ModalTerminal = ({
<Box>
<Textarea
disabled={isDisable}
rows={5}
rows={4}
size="sm"
placeholder="Report from AI"
value={valueIssue}
@ -1128,107 +1234,53 @@ const ModalTerminal = ({
/>
</Box>
</LoaderOverlay>
</Box>
<Box
</Card>
<Card
shadow="sm"
p="sm"
radius="md"
withBorder
h={"fit-content"}
mt={"4px"}
style={{
display: "flex",
justifyContent: "center",
flexDirection: "column",
alignItems: "center",
}}
>
<fieldset
style={{
width: "280px",
}}
<Box
component="fieldset"
w={280}
style={{ border: "none", padding: 0 }}
>
<Flex justify={"center"}>
<IconCircleDot
color={
line?.interface &&
findSwitchPort(line?.interface)?.status === "ON"
? "green"
: "red"
}
<SwitchConnectionStatus
line={line}
findSwitchPort={findSwitchPort}
normalizePortName={normalizePortName}
/>
<SwitchControlButtons
isDisable={isDisable}
hasInterface={!!line?.interface}
onSwitchOn={() => controlSwitch("on")}
onSwitchOff={() => controlSwitch("off")}
onSwitchRestart={() => controlSwitch("restart")}
/>
<Flex>
<Text size="sm" ml={"sm"}>
Internet
</Text>
{line?.interface ? (
findSwitchPort(line?.interface)?.status ===
"ON" ? (
<Text size="sm" ml={"4px"} c={"green"}>
Connected (
{normalizePortName(line?.interface)})
</Text>
) : (
<Text size="sm" ml={"4px"} c={"red"}>
Not Connected (
{normalizePortName(line?.interface)})
</Text>
)
) : (
<Text c={"red"} size="sm" ml={"4px"}>
Not config
</Text>
)}
</Flex>
</Flex>
<Flex justify={"space-around"} mt={"4px"}>
<Button
className={classes.buttonControl}
disabled={isDisable || !line?.interface}
fw={400}
variant="outline"
color="green"
size="xs"
onClick={() => {
controlSwitch("on");
}}
>
ON
</Button>
<Button
className={classes.buttonControl}
disabled={isDisable || !line?.interface}
fw={400}
variant="outline"
color="red"
size="xs"
onClick={() => {
controlSwitch("off");
}}
>
OFF
</Button>
<Button
className={classes.buttonControl}
disabled={isDisable || !line?.interface}
fw={400}
variant="outline"
color="orange"
size="xs"
onClick={() => {
controlSwitch("restart");
}}
>
Restart
</Button>
</Flex>
</fieldset>
</Box>
<Flex
justify={"center"}
style={{
borderTop: "1px solid #ccc",
borderBottom: "1px solid #ccc",
paddingTop: "12px",
paddingBottom: "12px",
marginTop: "8px",
width: "100%",
}}
>
<Button
disabled={isDisable}
fw={400}
w={"120px"}
w={"200px"}
variant="outline"
color="red"
size="xs"
@ -1254,6 +1306,8 @@ const ModalTerminal = ({
Clear line
</Button>
</Flex>
</Card>
</ScrollArea>
</Flex>
</Tabs.Panel>
@ -1438,7 +1492,8 @@ const ModalTerminal = ({
loadingClearTerminal={line?.loadingClearTerminal}
isClearKeepScrollBack={isClearKeepScrollBack}
/>
<Flex justify={"space-around"} mt={"md"} pt={"md"} pb={"md"}>
<Flex justify={"space-around"} mt={"xs"} pt={"xs"}>
<GroupButtonTerminal title="CONSOLE">
<Menu trigger="hover" withArrow shadow="md" position="right">
<Menu.Target>
<Button
@ -1494,6 +1549,28 @@ const ModalTerminal = ({
</Flex>
</Menu.Dropdown>
</Menu>
<Button
fw={400}
disabled={isDisable}
variant="filled"
color="orange"
size="xs"
onClick={() => {
socket?.emit("write_command_line_from_web", {
lineIds: [line?.id],
stationId: stationItem?.id,
command: "spam_break",
});
setIsDisable(true);
setTimeout(() => {
setIsDisable(false);
}, 5000);
}}
>
Send Break
</Button>
</GroupButtonTerminal>
<GroupButtonTerminal title="TEST">
<ButtonDPELP
socket={socket}
selectedLines={line ? [line] : []}
@ -1540,6 +1617,8 @@ const ModalTerminal = ({
>
Scenario
</Button>
</GroupButtonTerminal>
<GroupButtonTerminal title="CONFIG">
<Button
disabled={
isDisable ||
@ -1576,26 +1655,8 @@ const ModalTerminal = ({
>
Select License
</Button>
<Button
fw={400}
disabled={isDisable}
variant="filled"
color="orange"
size="xs"
onClick={() => {
socket?.emit("write_command_line_from_web", {
lineIds: [line?.id],
stationId: stationItem?.id,
command: "spam_break",
});
setIsDisable(true);
setTimeout(() => {
setIsDisable(false);
}, 5000);
}}
>
Send Break
</Button>
</GroupButtonTerminal>
<GroupButtonTerminal title="DEVICE">
<Menu trigger="hover" withArrow shadow="md" position="top">
<Menu.Target>
<Button
@ -1651,6 +1712,7 @@ const ModalTerminal = ({
</Flex>
</Menu.Dropdown>
</Menu>
</GroupButtonTerminal>
</Flex>
</Grid.Col>
<Grid.Col span={3} display={"none"}>

View File

@ -295,6 +295,7 @@ const TerminalCLI: React.FC<TerminalCLIProps> = ({
backgroundColor: "black",
paddingBottom: customStyle.paddingBottom ?? "10px",
maxHeight: customStyle.maxHeight ?? "70vh",
borderRadius: "8px",
}}
>
<div