Update view history log, tested ports
This commit is contained in:
parent
507f228888
commit
d4ea801bef
|
|
@ -30,7 +30,7 @@ import axios from 'axios'
|
||||||
import redis from '@adonisjs/redis/services/main'
|
import redis from '@adonisjs/redis/services/main'
|
||||||
import Line from '#models/line'
|
import Line from '#models/line'
|
||||||
import PromptAi from '#models/prompt_ai'
|
import PromptAi from '#models/prompt_ai'
|
||||||
import { CustomSocket, ErrorRow, TestResult } from '../ultils/types.js'
|
import { CustomSocket, ErrorRow, PortState, TestResult } from '../ultils/types.js'
|
||||||
import momentTZ from 'moment-timezone'
|
import momentTZ from 'moment-timezone'
|
||||||
import { PhysicalPortTest } from './physical_test_service.js'
|
import { PhysicalPortTest } from './physical_test_service.js'
|
||||||
import Station from '#models/station'
|
import Station from '#models/station'
|
||||||
|
|
@ -948,41 +948,67 @@ export default class LineConnection {
|
||||||
/**
|
/**
|
||||||
* Add cache to list history devices on this line
|
* Add cache to list history devices on this line
|
||||||
*/
|
*/
|
||||||
async addHistory(stationId: number, lineId: number, item: HistoryItem) {
|
async addHistory(
|
||||||
if (!item.pid || !item.sn) return
|
stationId: number,
|
||||||
|
lineId: number,
|
||||||
|
item: HistoryItem,
|
||||||
|
outputLog?: string,
|
||||||
|
portPhysical?: PortState[]
|
||||||
|
) {
|
||||||
|
if (!item.pid || !item.sn) return false
|
||||||
const key = `station:${stationId}:line:${lineId}:history`
|
const key = `station:${stationId}:line:${lineId}:history`
|
||||||
const now = Date.now()
|
const now = Date.now()
|
||||||
|
|
||||||
const newItem = JSON.stringify({
|
// Tạo object chứa các field mở rộng nếu được truyền vào
|
||||||
...item,
|
const extendedFields: any = {}
|
||||||
timestamp: now,
|
if (outputLog !== undefined) extendedFields.output = outputLog
|
||||||
})
|
if (portPhysical !== undefined) extendedFields.portPhysical = portPhysical
|
||||||
|
|
||||||
// Lấy phần tử cuối
|
// Lấy phần tử cuối cùng trong ZSET mang tính timeline
|
||||||
const lastItems = await redis.zrevrange(key, 0, 0)
|
const lastItems = await redis.zrevrange(key, 0, 0)
|
||||||
|
|
||||||
if (lastItems.length > 0) {
|
if (lastItems.length > 0) {
|
||||||
const last = JSON.parse(lastItems[0])
|
const last = JSON.parse(lastItems[0])
|
||||||
|
|
||||||
|
// TRƯỜNG HỢP 1: Trùng pid và sn -> Cập nhật lại bản ghi cũ
|
||||||
if (last.pid === item.pid && last.sn === item.sn) {
|
if (last.pid === item.pid && last.sn === item.sn) {
|
||||||
return false // không thay đổi
|
const updatedItemObj = {
|
||||||
|
...last,
|
||||||
|
...extendedFields,
|
||||||
|
}
|
||||||
|
const updatedItemStr = JSON.stringify(updatedItemObj)
|
||||||
|
|
||||||
|
// Nếu dữ liệu mới không khác gì dữ liệu cũ thì không cần làm gì cả
|
||||||
|
if (lastItems[0] === updatedItemStr) {
|
||||||
|
return false
|
||||||
|
}
|
||||||
|
|
||||||
|
await redis.multi().zrem(key, lastItems[0]).zadd(key, last.timestamp, updatedItemStr).exec()
|
||||||
|
|
||||||
|
return true
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
const line = await Line.find(lineId)
|
const line = await Line.find(lineId)
|
||||||
if (line) {
|
if (line) {
|
||||||
const listHistory = line.history ? JSON.parse(line.history) : []
|
const listHistory = line.history ? JSON.parse(line.history) : []
|
||||||
listHistory.unshift(newItem)
|
listHistory.unshift({ ...item, timestamp: now })
|
||||||
line.history = JSON.stringify(listHistory)
|
line.history = JSON.stringify(listHistory)
|
||||||
await line.save()
|
await line.save()
|
||||||
}
|
}
|
||||||
|
|
||||||
// Add vào ZSET
|
|
||||||
await redis.zadd(key, now, newItem)
|
|
||||||
|
|
||||||
// Tự động xóa item > 96h
|
// Tự động xóa item > 96h
|
||||||
const expireTime = now - 96 * 60 * 60 * 1000
|
// const expireTime = now - 96 * 60 * 60 * 1000
|
||||||
await redis.zremrangebyscore(key, 0, expireTime)
|
// await redis.zremrangebyscore(key, 0, expireTime)
|
||||||
|
|
||||||
|
// TRƯỜNG HỢP 2: Sản phẩm mới hoàn toàn -> Thêm mới vào ZSET
|
||||||
|
const newItem = JSON.stringify({
|
||||||
|
...item,
|
||||||
|
...extendedFields,
|
||||||
|
timestamp: now,
|
||||||
|
})
|
||||||
|
|
||||||
|
await redis.zadd(key, now, newItem)
|
||||||
return true
|
return true
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
@ -1184,7 +1210,7 @@ Ports Missing/Down: ${missing.length}\n\n`
|
||||||
console.log('Running physical test')
|
console.log('Running physical test')
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
this.setTimeoutSendSummaryReport(600000)
|
this.setTimeoutSendSummaryReport(1200000)
|
||||||
this.config.runningPhysical = true
|
this.config.runningPhysical = true
|
||||||
this.config.runningScenario = 'Physical Test'
|
this.config.runningScenario = 'Physical Test'
|
||||||
this.config.isSkipPhysical = false
|
this.config.isSkipPhysical = false
|
||||||
|
|
@ -2495,7 +2521,22 @@ Ports Missing/Down: ${missing.length}\n\n`
|
||||||
console.error(`Failed to save report for SN ${reportSN}:`, err)
|
console.error(`Failed to save report for SN ${reportSN}:`, err)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
this.addHistory(
|
||||||
|
this.config.stationId,
|
||||||
|
this.config.id,
|
||||||
|
{
|
||||||
|
id: this.config.id,
|
||||||
|
number: this.config.lineNumber,
|
||||||
|
stationId: this.config.stationId,
|
||||||
|
pid: productPN,
|
||||||
|
sn: productSN,
|
||||||
|
vid: productVid,
|
||||||
|
scenario: '',
|
||||||
|
timestamp: Date.now(),
|
||||||
|
},
|
||||||
|
this.outputTestLog,
|
||||||
|
portPhysical
|
||||||
|
)
|
||||||
this.updateNote(config?.inventory?.sn, this.dataDPELP as DataDPELP)
|
this.updateNote(config?.inventory?.sn, this.dataDPELP as DataDPELP)
|
||||||
await sendMessageToMail(
|
await sendMessageToMail(
|
||||||
`[ATC] - [${config.stationName} - L${config.lineNumber}] - ${this.config.inventory?.pid} - ${this.config.inventory?.sn} - Test Summary`,
|
`[ATC] - [${config.stationName} - L${config.lineNumber}] - ${this.config.inventory?.pid} - ${this.config.inventory?.sn} - Test Summary`,
|
||||||
|
|
|
||||||
|
|
@ -1,5 +1,6 @@
|
||||||
import { useEffect, useState } from "react";
|
import { useEffect, useState } from "react";
|
||||||
import {
|
import {
|
||||||
|
ActionIcon,
|
||||||
Box,
|
Box,
|
||||||
CloseButton,
|
CloseButton,
|
||||||
Flex,
|
Flex,
|
||||||
|
|
@ -11,6 +12,9 @@ import {
|
||||||
import moment from "moment";
|
import moment from "moment";
|
||||||
import type { Socket } from "socket.io-client";
|
import type { Socket } from "socket.io-client";
|
||||||
import classes from "../Component.module.css";
|
import classes from "../Component.module.css";
|
||||||
|
import { IconFileText, IconListCheck } from "@tabler/icons-react";
|
||||||
|
import ModalPortPhysicalTest from "./ModalPortPhysicalTest";
|
||||||
|
import ModalLog from "./ModalLog";
|
||||||
|
|
||||||
interface LineHistoryItem {
|
interface LineHistoryItem {
|
||||||
id: number;
|
id: number;
|
||||||
|
|
@ -20,6 +24,13 @@ interface LineHistoryItem {
|
||||||
vid: string;
|
vid: string;
|
||||||
sn: string;
|
sn: string;
|
||||||
scenario: string;
|
scenario: string;
|
||||||
|
output?: string;
|
||||||
|
portPhysical?: [
|
||||||
|
{
|
||||||
|
name: string;
|
||||||
|
tested: boolean;
|
||||||
|
},
|
||||||
|
];
|
||||||
timestamp: number;
|
timestamp: number;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
@ -44,6 +55,10 @@ const ModalLineHistory = ({
|
||||||
}: ModalLineHistoryProps) => {
|
}: ModalLineHistoryProps) => {
|
||||||
const [history, setHistory] = useState<LineHistoryItem[]>([]);
|
const [history, setHistory] = useState<LineHistoryItem[]>([]);
|
||||||
const [loading, setLoading] = useState(false);
|
const [loading, setLoading] = useState(false);
|
||||||
|
const [openPortPhysical, setOpenPortPhysical] = useState(false);
|
||||||
|
const [selectedHistory, setSelectedHistory] =
|
||||||
|
useState<LineHistoryItem | null>(null);
|
||||||
|
const [openLog, setOpenLog] = useState(false);
|
||||||
|
|
||||||
useEffect(() => {
|
useEffect(() => {
|
||||||
if (!socket || !opened) return;
|
if (!socket || !opened) return;
|
||||||
|
|
@ -77,104 +92,157 @@ const ModalLineHistory = ({
|
||||||
const sorted = [...history].sort((a, b) => b.timestamp - a.timestamp);
|
const sorted = [...history].sort((a, b) => b.timestamp - a.timestamp);
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<div
|
<>
|
||||||
style={{
|
|
||||||
position: "fixed",
|
|
||||||
top: 0,
|
|
||||||
left: 0,
|
|
||||||
right: 0,
|
|
||||||
bottom: 0,
|
|
||||||
backgroundColor: "rgba(0,0,0,0.6)",
|
|
||||||
zIndex: 100000,
|
|
||||||
display: "flex",
|
|
||||||
alignItems: "center",
|
|
||||||
justifyContent: "center",
|
|
||||||
backdropFilter: "blur(3px)",
|
|
||||||
}}
|
|
||||||
onClick={(e) => {
|
|
||||||
e.stopPropagation();
|
|
||||||
onClose();
|
|
||||||
}}
|
|
||||||
>
|
|
||||||
<div
|
<div
|
||||||
style={{
|
style={{
|
||||||
background: "white",
|
position: "fixed",
|
||||||
borderRadius: "12px",
|
top: 0,
|
||||||
width: "70%",
|
left: 0,
|
||||||
maxWidth: "1000px",
|
right: 0,
|
||||||
maxHeight: "80vh",
|
bottom: 0,
|
||||||
|
backgroundColor: "rgba(0,0,0,0.6)",
|
||||||
|
zIndex: 100000,
|
||||||
display: "flex",
|
display: "flex",
|
||||||
flexDirection: "column",
|
alignItems: "center",
|
||||||
boxShadow: "0 20px 60px rgba(0,0,0,0.3)",
|
justifyContent: "center",
|
||||||
overflow: "hidden",
|
backdropFilter: "blur(3px)",
|
||||||
|
}}
|
||||||
|
onClick={(e) => {
|
||||||
|
e.stopPropagation();
|
||||||
|
onClose();
|
||||||
}}
|
}}
|
||||||
onClick={(e) => e.stopPropagation()}
|
|
||||||
>
|
>
|
||||||
<Flex
|
<div
|
||||||
justify="space-between"
|
style={{
|
||||||
align="center"
|
background: "white",
|
||||||
p="lg"
|
borderRadius: "12px",
|
||||||
style={{ borderBottom: "1px solid #e9ecef", flexShrink: 0 }}
|
width: "70%",
|
||||||
|
maxWidth: "1000px",
|
||||||
|
maxHeight: "80vh",
|
||||||
|
display: "flex",
|
||||||
|
flexDirection: "column",
|
||||||
|
boxShadow: "0 20px 60px rgba(0,0,0,0.3)",
|
||||||
|
overflow: "hidden",
|
||||||
|
}}
|
||||||
|
onClick={(e) => e.stopPropagation()}
|
||||||
>
|
>
|
||||||
<Text fw={700} size="lg">
|
<Flex
|
||||||
🕘 Line history
|
justify="space-between"
|
||||||
{lineNumber ? ` — Line ${lineNumber}` : ""}
|
align="center"
|
||||||
{stationName ? ` (${stationName})` : ""}
|
p="lg"
|
||||||
</Text>
|
style={{ borderBottom: "1px solid #e9ecef", flexShrink: 0 }}
|
||||||
<CloseButton size="lg" onClick={onClose} />
|
>
|
||||||
</Flex>
|
<Text fw={700} size="lg">
|
||||||
|
🕘 Line history
|
||||||
|
{lineNumber ? ` — Line ${lineNumber}` : ""}
|
||||||
|
{stationName ? ` (${stationName})` : ""}
|
||||||
|
</Text>
|
||||||
|
<CloseButton size="lg" onClick={onClose} />
|
||||||
|
</Flex>
|
||||||
|
|
||||||
<Box p="md" style={{ flex: 1, overflow: "hidden" }}>
|
<Box p="md" style={{ flex: 1, overflow: "hidden" }}>
|
||||||
{loading ? (
|
{loading ? (
|
||||||
<Flex justify="center" align="center" h="40vh">
|
<Flex justify="center" align="center" h="40vh">
|
||||||
<Loader />
|
<Loader />
|
||||||
</Flex>
|
</Flex>
|
||||||
) : sorted.length === 0 ? (
|
) : sorted.length === 0 ? (
|
||||||
<Flex justify="center" align="center" h="40vh">
|
<Flex justify="center" align="center" h="40vh">
|
||||||
<Text c="dimmed">No history data available</Text>
|
<Text c="dimmed">No history data available</Text>
|
||||||
</Flex>
|
</Flex>
|
||||||
) : (
|
) : (
|
||||||
<ScrollArea h="60vh" className={classes.hideScrollBar}>
|
<ScrollArea h="60vh" className={classes.hideScrollBar}>
|
||||||
<Table striped highlightOnHover withTableBorder>
|
<Table striped highlightOnHover withTableBorder>
|
||||||
<Table.Thead
|
<Table.Thead
|
||||||
style={{
|
style={{
|
||||||
position: "sticky",
|
position: "sticky",
|
||||||
top: 0,
|
top: 0,
|
||||||
background: "#f1f3f5",
|
background: "#f1f3f5",
|
||||||
zIndex: 1,
|
zIndex: 1,
|
||||||
}}
|
}}
|
||||||
>
|
>
|
||||||
<Table.Tr>
|
<Table.Tr>
|
||||||
<Table.Th>PID</Table.Th>
|
<Table.Th>PID</Table.Th>
|
||||||
<Table.Th>VID</Table.Th>
|
<Table.Th>VID</Table.Th>
|
||||||
<Table.Th>SN</Table.Th>
|
<Table.Th>SN</Table.Th>
|
||||||
<Table.Th>Scenario</Table.Th>
|
<Table.Th>Scenario</Table.Th>
|
||||||
<Table.Th>Time</Table.Th>
|
<Table.Th>Log</Table.Th>
|
||||||
</Table.Tr>
|
<Table.Th>Time</Table.Th>
|
||||||
</Table.Thead>
|
|
||||||
<Table.Tbody>
|
|
||||||
{sorted.map((item, i) => (
|
|
||||||
<Table.Tr key={`${item.timestamp}-${item.sn || ""}-${i}`}>
|
|
||||||
<Table.Td style={{ fontWeight: 600 }}>
|
|
||||||
{item.pid || "-"}
|
|
||||||
</Table.Td>
|
|
||||||
<Table.Td>{item.vid || "-"}</Table.Td>
|
|
||||||
<Table.Td>{item.sn || "-"}</Table.Td>
|
|
||||||
<Table.Td>{item.scenario || "-"}</Table.Td>
|
|
||||||
<Table.Td c="dimmed" style={{ fontSize: "12px" }}>
|
|
||||||
{item.timestamp
|
|
||||||
? moment(item.timestamp).format("DD/MM/YYYY HH:mm:ss")
|
|
||||||
: "-"}
|
|
||||||
</Table.Td>
|
|
||||||
</Table.Tr>
|
</Table.Tr>
|
||||||
))}
|
</Table.Thead>
|
||||||
</Table.Tbody>
|
<Table.Tbody>
|
||||||
</Table>
|
{sorted.map((item, i) => (
|
||||||
</ScrollArea>
|
<Table.Tr key={`${item.timestamp}-${item.sn || ""}-${i}`}>
|
||||||
)}
|
<Table.Td style={{ fontWeight: 600 }}>
|
||||||
</Box>
|
{item.pid || "-"}
|
||||||
|
</Table.Td>
|
||||||
|
<Table.Td>{item.vid || "-"}</Table.Td>
|
||||||
|
<Table.Td>{item.sn || "-"}</Table.Td>
|
||||||
|
<Table.Td>{item.scenario || "-"}</Table.Td>
|
||||||
|
<Table.Td>
|
||||||
|
<Flex align="center" gap="xs">
|
||||||
|
{item.output ? (
|
||||||
|
<ActionIcon
|
||||||
|
variant="outline"
|
||||||
|
onClick={() => {
|
||||||
|
setSelectedHistory(item);
|
||||||
|
setOpenLog(true);
|
||||||
|
}}
|
||||||
|
>
|
||||||
|
<IconFileText size={16} />
|
||||||
|
</ActionIcon>
|
||||||
|
) : (
|
||||||
|
""
|
||||||
|
)}
|
||||||
|
{item.portPhysical ? (
|
||||||
|
<ActionIcon
|
||||||
|
variant="outline"
|
||||||
|
onClick={() => {
|
||||||
|
setSelectedHistory(item);
|
||||||
|
setOpenPortPhysical(true);
|
||||||
|
}}
|
||||||
|
>
|
||||||
|
<IconListCheck size={16} />
|
||||||
|
</ActionIcon>
|
||||||
|
) : (
|
||||||
|
""
|
||||||
|
)}
|
||||||
|
</Flex>
|
||||||
|
</Table.Td>
|
||||||
|
<Table.Td c="dimmed" style={{ fontSize: "12px" }}>
|
||||||
|
{item.timestamp
|
||||||
|
? moment(item.timestamp).format(
|
||||||
|
"DD/MM/YYYY HH:mm:ss",
|
||||||
|
)
|
||||||
|
: "-"}
|
||||||
|
</Table.Td>
|
||||||
|
</Table.Tr>
|
||||||
|
))}
|
||||||
|
</Table.Tbody>
|
||||||
|
</Table>
|
||||||
|
</ScrollArea>
|
||||||
|
)}
|
||||||
|
</Box>
|
||||||
|
</div>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
<ModalPortPhysicalTest
|
||||||
|
opened={openPortPhysical}
|
||||||
|
onClose={() => {
|
||||||
|
setSelectedHistory(null);
|
||||||
|
setOpenPortPhysical(false);
|
||||||
|
}}
|
||||||
|
selectedHistory={selectedHistory}
|
||||||
|
lineNumber={lineNumber}
|
||||||
|
stationName={stationName}
|
||||||
|
/>
|
||||||
|
<ModalLog
|
||||||
|
opened={openLog}
|
||||||
|
onClose={() => {
|
||||||
|
setSelectedHistory(null);
|
||||||
|
setOpenLog(false);
|
||||||
|
}}
|
||||||
|
testLogContent={selectedHistory?.output || ""}
|
||||||
|
/>
|
||||||
|
</>
|
||||||
);
|
);
|
||||||
};
|
};
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -35,7 +35,7 @@ const ModalLog = ({
|
||||||
return `<span style="background-color: ${
|
return `<span style="background-color: ${
|
||||||
prefix.includes("start") ? colorStart : colorEnd
|
prefix.includes("start") ? colorStart : colorEnd
|
||||||
}" title="${date}">${prefix}${timestamp}${suffix}</span>`;
|
}" title="${date}">${prefix}${timestamp}${suffix}</span>`;
|
||||||
}
|
},
|
||||||
)
|
)
|
||||||
// Highlight full ---User---
|
// Highlight full ---User---
|
||||||
.replace(/^-------([^-\n]+)-------$/gm, (match) => {
|
.replace(/^-------([^-\n]+)-------$/gm, (match) => {
|
||||||
|
|
@ -51,6 +51,7 @@ const ModalLog = ({
|
||||||
onClose={() => {
|
onClose={() => {
|
||||||
onClose();
|
onClose();
|
||||||
}}
|
}}
|
||||||
|
zIndex={100001}
|
||||||
title={
|
title={
|
||||||
<Text fz={"lg"} fw={"bolder"}>
|
<Text fz={"lg"} fw={"bolder"}>
|
||||||
Log Content
|
Log Content
|
||||||
|
|
|
||||||
|
|
@ -0,0 +1,131 @@
|
||||||
|
import {
|
||||||
|
ActionIcon,
|
||||||
|
Box,
|
||||||
|
Flex,
|
||||||
|
Modal,
|
||||||
|
ScrollArea,
|
||||||
|
Table,
|
||||||
|
Text,
|
||||||
|
} from "@mantine/core";
|
||||||
|
import { IconCheck, IconX } from "@tabler/icons-react";
|
||||||
|
|
||||||
|
interface LineHistoryItem {
|
||||||
|
id: number;
|
||||||
|
number: number;
|
||||||
|
stationId: number;
|
||||||
|
pid: string;
|
||||||
|
vid: string;
|
||||||
|
sn: string;
|
||||||
|
scenario: string;
|
||||||
|
output?: string;
|
||||||
|
portPhysical?: [
|
||||||
|
{
|
||||||
|
name: string;
|
||||||
|
tested: boolean;
|
||||||
|
},
|
||||||
|
];
|
||||||
|
timestamp: number;
|
||||||
|
}
|
||||||
|
|
||||||
|
interface ModalPortPhysicalTestProps {
|
||||||
|
opened: boolean;
|
||||||
|
onClose: () => void;
|
||||||
|
selectedHistory: LineHistoryItem | null;
|
||||||
|
lineNumber?: number;
|
||||||
|
stationName?: string;
|
||||||
|
}
|
||||||
|
|
||||||
|
const ModalPortPhysicalTest = ({
|
||||||
|
opened,
|
||||||
|
onClose,
|
||||||
|
selectedHistory,
|
||||||
|
lineNumber,
|
||||||
|
stationName,
|
||||||
|
}: ModalPortPhysicalTestProps) => {
|
||||||
|
return (
|
||||||
|
<Modal
|
||||||
|
opened={opened}
|
||||||
|
onClose={onClose}
|
||||||
|
title={
|
||||||
|
<Flex align="center" gap="sm">
|
||||||
|
<Text fw={700} size="lg">
|
||||||
|
🔌 Port Physical Test Status
|
||||||
|
{lineNumber ? ` — Line ${lineNumber}` : ""}
|
||||||
|
{stationName ? ` (${stationName})` : ""}
|
||||||
|
</Text>
|
||||||
|
</Flex>
|
||||||
|
}
|
||||||
|
size="lg"
|
||||||
|
style={{ position: "absolute", left: 0 }}
|
||||||
|
centered
|
||||||
|
zIndex={100001}
|
||||||
|
>
|
||||||
|
<Flex mb="md" gap={"sm"}>
|
||||||
|
<Text fw={"bold"}>PID: {selectedHistory?.pid}</Text>
|
||||||
|
<Text fw={"bold"}>{selectedHistory?.vid}</Text>
|
||||||
|
<Text fw={"bold"}>SN: {selectedHistory?.sn}</Text>
|
||||||
|
</Flex>
|
||||||
|
<Box>
|
||||||
|
{selectedHistory?.portPhysical &&
|
||||||
|
selectedHistory.portPhysical.length > 0 ? (
|
||||||
|
<ScrollArea h="75vh">
|
||||||
|
<Table striped highlightOnHover withTableBorder>
|
||||||
|
<Table.Thead>
|
||||||
|
<Table.Tr>
|
||||||
|
<Table.Th>Port Name</Table.Th>
|
||||||
|
<Table.Th>Status</Table.Th>
|
||||||
|
</Table.Tr>
|
||||||
|
</Table.Thead>
|
||||||
|
<Table.Tbody>
|
||||||
|
{selectedHistory?.portPhysical?.map((port, index) => (
|
||||||
|
<Table.Tr key={index}>
|
||||||
|
<Table.Td style={{ fontWeight: 600 }}>{port.name}</Table.Td>
|
||||||
|
<Table.Td>
|
||||||
|
<Flex align="center" gap="xs">
|
||||||
|
{port.tested ? (
|
||||||
|
<>
|
||||||
|
<ActionIcon
|
||||||
|
variant="filled"
|
||||||
|
color="green"
|
||||||
|
size="sm"
|
||||||
|
disabled
|
||||||
|
>
|
||||||
|
<IconCheck size={16} />
|
||||||
|
</ActionIcon>
|
||||||
|
<Text size="sm" c="green" fw={500}>
|
||||||
|
Tested
|
||||||
|
</Text>
|
||||||
|
</>
|
||||||
|
) : (
|
||||||
|
<>
|
||||||
|
<ActionIcon
|
||||||
|
variant="filled"
|
||||||
|
color="red"
|
||||||
|
size="sm"
|
||||||
|
disabled
|
||||||
|
>
|
||||||
|
<IconX size={16} />
|
||||||
|
</ActionIcon>
|
||||||
|
<Text size="sm" c="red" fw={500}>
|
||||||
|
Not Tested
|
||||||
|
</Text>
|
||||||
|
</>
|
||||||
|
)}
|
||||||
|
</Flex>
|
||||||
|
</Table.Td>
|
||||||
|
</Table.Tr>
|
||||||
|
))}
|
||||||
|
</Table.Tbody>
|
||||||
|
</Table>
|
||||||
|
</ScrollArea>
|
||||||
|
) : (
|
||||||
|
<Flex justify="center" align="center" h="20vh">
|
||||||
|
<Text c="dimmed">No port data available</Text>
|
||||||
|
</Flex>
|
||||||
|
)}
|
||||||
|
</Box>
|
||||||
|
</Modal>
|
||||||
|
);
|
||||||
|
};
|
||||||
|
|
||||||
|
export default ModalPortPhysicalTest;
|
||||||
Loading…
Reference in New Issue