Update
This commit is contained in:
parent
fb1554d857
commit
1682a28029
|
|
@ -5,39 +5,39 @@ export default class AuthController {
|
||||||
// Đăng ký
|
// Đăng ký
|
||||||
async register({ request, response }: HttpContext) {
|
async register({ request, response }: HttpContext) {
|
||||||
try {
|
try {
|
||||||
const data = request.only(['email', 'password', 'full_name'])
|
const data = request.only(['email', 'password', 'user_name'])
|
||||||
|
|
||||||
const user = await User.query().where('email', data.email).first()
|
const user = await User.query().where('user_name', data.user_name).first()
|
||||||
|
|
||||||
if (user) {
|
if (user) {
|
||||||
return response.status(401).json({ status: false, message: 'Email is exist' })
|
return response.status(401).json({ status: false, message: 'Username is exist' })
|
||||||
}
|
}
|
||||||
|
|
||||||
const newUser = await User.create(data)
|
const newUser = await User.create(data)
|
||||||
return response.json({ status: true, message: 'User created', user: newUser })
|
return response.json({ status: true, message: 'User created', user: newUser })
|
||||||
} catch (error) {
|
} catch (error) {
|
||||||
return response.status(401).json({ status: false, message: 'Invalid credentials' })
|
return response.status(401).json({ error, status: false, message: 'Invalid credentials' })
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
// Đăng nhập
|
// Đăng nhập
|
||||||
async login({ request, auth, response }: HttpContext) {
|
async login({ request, auth, response }: HttpContext) {
|
||||||
const { email, password } = request.only(['email', 'password'])
|
const { user_name: userName, password } = request.only(['user_name', 'password'])
|
||||||
const user = await User.query().where('email', email).first()
|
const user = await User.query().where('user_name', userName).first()
|
||||||
|
|
||||||
if (!user) {
|
if (!user) {
|
||||||
return response.status(401).json({ message: 'Invalid email or password' })
|
return response.status(401).json({ message: 'Invalid Username or password' })
|
||||||
}
|
}
|
||||||
|
|
||||||
try {
|
try {
|
||||||
// So sánh password
|
// So sánh password
|
||||||
if (user.password !== password) {
|
if (user.password !== password) {
|
||||||
return response.status(401).json({ message: 'Invalid email or password' })
|
return response.status(401).json({ message: 'Invalid username or password' })
|
||||||
}
|
}
|
||||||
|
|
||||||
return response.json({
|
return response.json({
|
||||||
message: 'Login successful',
|
message: 'Login successful',
|
||||||
user: { id: user.id, email: user.email, fullName: user.fullName },
|
user: { id: user.id, email: user.email, userName: user.userName },
|
||||||
})
|
})
|
||||||
} catch {
|
} catch {
|
||||||
return response.status(401).json({ message: 'Invalid credentials' })
|
return response.status(401).json({ message: 'Invalid credentials' })
|
||||||
|
|
|
||||||
|
|
@ -71,13 +71,6 @@ export default class StationsController {
|
||||||
let lines: Line[] = request.body().lines || []
|
let lines: Line[] = request.body().lines || []
|
||||||
|
|
||||||
try {
|
try {
|
||||||
// Kiểm tra trùng name hoặc ip
|
|
||||||
if (await Station.findBy('name', payload.name))
|
|
||||||
return response.status(400).json({ message: 'Station name exist!' })
|
|
||||||
|
|
||||||
if (await Station.findBy('ip', payload.ip))
|
|
||||||
return response.status(400).json({ message: 'Ip of station is exist!' })
|
|
||||||
|
|
||||||
const station = await Station.find(request.body().id)
|
const station = await Station.find(request.body().id)
|
||||||
|
|
||||||
// If the station does not exist, return a 404 response
|
// If the station does not exist, return a 404 response
|
||||||
|
|
|
||||||
|
|
@ -25,19 +25,19 @@ export default class UsersController {
|
||||||
|
|
||||||
async store({ request, response }: HttpContext) {
|
async store({ request, response }: HttpContext) {
|
||||||
try {
|
try {
|
||||||
const data = request.only(['full_name', 'email', 'password'])
|
const data = request.only(['user_name', 'email', 'password'])
|
||||||
|
|
||||||
// Check if email already exists
|
// Check if email already exists
|
||||||
const existingUser = await User.findBy('email', data.email)
|
const existingUser = await User.findBy('user_name', data.user_name)
|
||||||
if (existingUser) {
|
if (existingUser) {
|
||||||
return response.conflict({
|
return response.conflict({
|
||||||
status: false,
|
status: false,
|
||||||
message: 'Email already exists',
|
message: 'Username already exists',
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
|
|
||||||
const user = await User.create({
|
const user = await User.create({
|
||||||
fullName: data.full_name,
|
userName: data.user_name,
|
||||||
email: data.email,
|
email: data.email,
|
||||||
password: data.password,
|
password: data.password,
|
||||||
})
|
})
|
||||||
|
|
@ -55,16 +55,16 @@ export default class UsersController {
|
||||||
async update({ params, request, response }: HttpContext) {
|
async update({ params, request, response }: HttpContext) {
|
||||||
try {
|
try {
|
||||||
const user = await User.findOrFail(params.id)
|
const user = await User.findOrFail(params.id)
|
||||||
const data = request.only(['full_name', 'email', 'password']) // Include password
|
const data = request.only(['user_name', 'email', 'password']) // Include password
|
||||||
|
|
||||||
// Check if email already exists for another user
|
// Check if email already exists for another user
|
||||||
if (data.email)
|
if (data.email)
|
||||||
if (data.email !== user.email) {
|
if (data.email !== user.email) {
|
||||||
const existingUser = await User.findBy('email', data.email)
|
const existingUser = await User.findBy('user_name', data.user_name)
|
||||||
if (existingUser) {
|
if (existingUser) {
|
||||||
return response.conflict({
|
return response.conflict({
|
||||||
status: false,
|
status: false,
|
||||||
message: 'Email already exists',
|
message: 'Username already exists',
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -6,7 +6,7 @@ export default class User extends BaseModel {
|
||||||
declare id: number
|
declare id: number
|
||||||
|
|
||||||
@column()
|
@column()
|
||||||
declare fullName: string | null
|
declare userName: string
|
||||||
|
|
||||||
@column()
|
@column()
|
||||||
declare email: string
|
declare email: string
|
||||||
|
|
|
||||||
|
|
@ -23,6 +23,11 @@ interface LineConfig {
|
||||||
openCLI: boolean
|
openCLI: boolean
|
||||||
userEmailOpenCLI: string
|
userEmailOpenCLI: string
|
||||||
userOpenCLI: string
|
userOpenCLI: string
|
||||||
|
inventory?: string
|
||||||
|
latestScenario?: {
|
||||||
|
name: string
|
||||||
|
time: number
|
||||||
|
}
|
||||||
data: {
|
data: {
|
||||||
command: string
|
command: string
|
||||||
output: string
|
output: string
|
||||||
|
|
@ -184,6 +189,10 @@ export default class LineConnection {
|
||||||
this.config.lineNumber,
|
this.config.lineNumber,
|
||||||
this.config.port
|
this.config.port
|
||||||
)
|
)
|
||||||
|
this.config.latestScenario = {
|
||||||
|
name: script?.title,
|
||||||
|
time: now,
|
||||||
|
}
|
||||||
const steps = typeof script?.body === 'string' ? JSON.parse(script?.body) : []
|
const steps = typeof script?.body === 'string' ? JSON.parse(script?.body) : []
|
||||||
let stepIndex = 0
|
let stepIndex = 0
|
||||||
|
|
||||||
|
|
@ -228,10 +237,17 @@ export default class LineConnection {
|
||||||
if (err) return
|
if (err) return
|
||||||
|
|
||||||
const logScenarios = getLogWithTimeScenario(content, now) || ''
|
const logScenarios = getLogWithTimeScenario(content, now) || ''
|
||||||
const data = await textfsmResults(logScenarios, '')
|
const data = textfsmResults(logScenarios, '')
|
||||||
try {
|
try {
|
||||||
data.forEach((item) => {
|
data.forEach((item) => {
|
||||||
if (item?.textfsm && isValidJson(item?.textfsm)) {
|
if (item?.textfsm && isValidJson(item?.textfsm)) {
|
||||||
|
if (
|
||||||
|
['show inventory', 'sh inventory', 'show inv', 'sh inv'].includes(
|
||||||
|
item.command
|
||||||
|
)
|
||||||
|
) {
|
||||||
|
this.config.inventory = JSON.parse(item.textfsm)[0]
|
||||||
|
}
|
||||||
item.textfsm = JSON.parse(item.textfsm)
|
item.textfsm = JSON.parse(item.textfsm)
|
||||||
}
|
}
|
||||||
})
|
})
|
||||||
|
|
@ -240,6 +256,8 @@ export default class LineConnection {
|
||||||
stationId: this.config.stationId,
|
stationId: this.config.stationId,
|
||||||
lineId: this.config.id,
|
lineId: this.config.id,
|
||||||
data,
|
data,
|
||||||
|
inventory: this.config.inventory || null,
|
||||||
|
latestScenario: this.config.latestScenario || null,
|
||||||
})
|
})
|
||||||
} catch (error) {
|
} catch (error) {
|
||||||
console.log(error)
|
console.log(error)
|
||||||
|
|
|
||||||
|
|
@ -0,0 +1,23 @@
|
||||||
|
import { BaseSchema } from '@adonisjs/lucid/schema'
|
||||||
|
|
||||||
|
export default class extends BaseSchema {
|
||||||
|
protected tableName = 'users'
|
||||||
|
|
||||||
|
public async up() {
|
||||||
|
this.schema.alterTable(this.tableName, (table) => {
|
||||||
|
table.renameColumn('full_name', 'user_name')
|
||||||
|
})
|
||||||
|
this.schema.alterTable(this.tableName, (table) => {
|
||||||
|
table.string('user_name').notNullable().unique().alter()
|
||||||
|
table.dropUnique(['email'])
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
|
public async down() {
|
||||||
|
this.schema.alterTable(this.tableName, (table) => {
|
||||||
|
table.unique(['email'])
|
||||||
|
table.string('user_name').nullable().alter()
|
||||||
|
table.renameColumn('user_name', 'full_name')
|
||||||
|
})
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
@ -78,15 +78,19 @@ export class WebSocketIo {
|
||||||
this.userConnecting.set(userId, { userId, userName })
|
this.userConnecting.set(userId, { userId, userName })
|
||||||
|
|
||||||
setTimeout(() => {
|
setTimeout(() => {
|
||||||
io.emit('user_connecting', Array.from(this.userConnecting.values()))
|
const listUser = Array.from(this.userConnecting.values())
|
||||||
}, 200)
|
if (!listUser.find((el) => el.userId === userId)) {
|
||||||
|
listUser.push({ userId, userName })
|
||||||
|
}
|
||||||
|
io.emit('user_connecting', listUser)
|
||||||
|
}, 500)
|
||||||
|
|
||||||
setTimeout(() => {
|
setTimeout(() => {
|
||||||
io.to(socket.id).emit(
|
io.to(socket.id).emit(
|
||||||
'init',
|
'init',
|
||||||
Array.from(this.lineMap.values()).map((el) => el.config)
|
Array.from(this.lineMap.values()).map((el) => el.config)
|
||||||
)
|
)
|
||||||
}, 200)
|
}, 500)
|
||||||
|
|
||||||
socket.on('disconnect', () => {
|
socket.on('disconnect', () => {
|
||||||
console.log(`FE disconnected: ${socket.id}`)
|
console.log(`FE disconnected: ${socket.id}`)
|
||||||
|
|
|
||||||
|
|
@ -0,0 +1,4 @@
|
||||||
|
VITE_BACKEND_URL=http://localhost:3333/
|
||||||
|
VITE_SOCKET_SERVER=http://localhost:8989/
|
||||||
|
VITE_LOCALSTORAGE_VARIABLE=au_ma_te_da
|
||||||
|
VITE_DOMAIN=http://localhost:5173/
|
||||||
|
|
@ -13,7 +13,6 @@ import {
|
||||||
MantineProvider,
|
MantineProvider,
|
||||||
Grid,
|
Grid,
|
||||||
ScrollArea,
|
ScrollArea,
|
||||||
Button,
|
|
||||||
LoadingOverlay,
|
LoadingOverlay,
|
||||||
} from "@mantine/core";
|
} from "@mantine/core";
|
||||||
import type {
|
import type {
|
||||||
|
|
@ -22,7 +21,6 @@ import type {
|
||||||
LineConfig,
|
LineConfig,
|
||||||
ReceivedFile,
|
ReceivedFile,
|
||||||
ResponseData,
|
ResponseData,
|
||||||
TextFSM,
|
|
||||||
TLine,
|
TLine,
|
||||||
TStation,
|
TStation,
|
||||||
TUser,
|
TUser,
|
||||||
|
|
@ -30,7 +28,13 @@ 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 { ButtonDPELP, ButtonScenario } from "./components/ButtonAction";
|
import {
|
||||||
|
ButtonConnect,
|
||||||
|
ButtonCopy,
|
||||||
|
ButtonDPELP,
|
||||||
|
ButtonScenario,
|
||||||
|
ButtonSelect,
|
||||||
|
} 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";
|
||||||
|
|
@ -230,6 +234,8 @@ function App() {
|
||||||
setTimeout(() => {
|
setTimeout(() => {
|
||||||
updateValueLineStation(data.lineId, {
|
updateValueLineStation(data.lineId, {
|
||||||
data: data.data,
|
data: data.data,
|
||||||
|
inventory: data.inventory,
|
||||||
|
latestScenario: data.latestScenario,
|
||||||
});
|
});
|
||||||
}, 100);
|
}, 100);
|
||||||
});
|
});
|
||||||
|
|
@ -319,19 +325,19 @@ function App() {
|
||||||
if (!line.userEmailOpenCLI) {
|
if (!line.userEmailOpenCLI) {
|
||||||
data.cliOpened = true;
|
data.cliOpened = true;
|
||||||
data.userEmailOpenCLI = user?.email;
|
data.userEmailOpenCLI = user?.email;
|
||||||
data.userOpenCLI = user?.fullName;
|
data.userOpenCLI = user?.userName;
|
||||||
socket?.emit("open_cli", {
|
socket?.emit("open_cli", {
|
||||||
lineId: line.id,
|
lineId: line.id,
|
||||||
stationId: line.station_id,
|
stationId: line.station_id,
|
||||||
userEmail: user?.email,
|
userEmail: user?.email,
|
||||||
userName: user?.fullName,
|
userName: user?.userName,
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
setSelectedLine(data);
|
setSelectedLine(data);
|
||||||
};
|
};
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<Container py="md" w={"100%"} style={{ maxWidth: "100%" }}>
|
<Container w={"100%"} style={{ maxWidth: "100%" }}>
|
||||||
<DraggableTabs
|
<DraggableTabs
|
||||||
socket={socket}
|
socket={socket}
|
||||||
usersConnecting={usersConnecting}
|
usersConnecting={usersConnecting}
|
||||||
|
|
@ -405,100 +411,36 @@ function App() {
|
||||||
<Flex
|
<Flex
|
||||||
direction={"column"}
|
direction={"column"}
|
||||||
align={"center"}
|
align={"center"}
|
||||||
gap={"xs"}
|
gap={"6px"}
|
||||||
wrap={"wrap"}
|
wrap={"wrap"}
|
||||||
>
|
>
|
||||||
<Button
|
<ButtonSelect
|
||||||
variant="filled"
|
selectedLines={selectedLines}
|
||||||
style={{ height: "30px", width: "100px" }}
|
setSelectedLines={setSelectedLines}
|
||||||
onClick={() => {
|
station={station}
|
||||||
if (selectedLines.length !== station.lines.length)
|
|
||||||
setSelectedLines(station.lines);
|
|
||||||
else setSelectedLines([]);
|
|
||||||
}}
|
|
||||||
>
|
|
||||||
{selectedLines.length !== station.lines.length
|
|
||||||
? "Select All"
|
|
||||||
: "Deselect All"}
|
|
||||||
</Button>
|
|
||||||
<Button
|
|
||||||
disabled={
|
|
||||||
selectedLines.filter((el) => el.status !== "connected")
|
|
||||||
.length === 0
|
|
||||||
}
|
|
||||||
variant="outline"
|
|
||||||
style={{ height: "30px", width: "100px" }}
|
|
||||||
onClick={() => {
|
|
||||||
const lines = selectedLines.filter(
|
|
||||||
(el) => el.status !== "connected"
|
|
||||||
);
|
|
||||||
socket?.emit("connect_lines", {
|
|
||||||
stationData: station,
|
|
||||||
linesData: lines,
|
|
||||||
});
|
|
||||||
setSelectedLines([]);
|
|
||||||
}}
|
|
||||||
>
|
|
||||||
Connect
|
|
||||||
</Button>
|
|
||||||
<Button
|
|
||||||
disabled={selectedLines.length === 0}
|
|
||||||
variant="outline"
|
|
||||||
style={{ height: "30px", width: "100px" }}
|
|
||||||
onClick={() => {
|
|
||||||
if (selectedLines?.length > 0) {
|
|
||||||
const value = selectedLines
|
|
||||||
?.map((el) => {
|
|
||||||
// Get data platform
|
|
||||||
const dataPlatform = el.data?.find(
|
|
||||||
(comm: TextFSM) =>
|
|
||||||
comm.command?.trim() === "show platform"
|
|
||||||
);
|
|
||||||
const DPELP =
|
|
||||||
dataPlatform &&
|
|
||||||
!dataPlatform?.output?.includes("Incomplete")
|
|
||||||
? true
|
|
||||||
: false;
|
|
||||||
|
|
||||||
// Get data license
|
|
||||||
const dataLicense = el.data?.find(
|
|
||||||
(comm: TextFSM) =>
|
|
||||||
comm.command?.trim() === "show license" ||
|
|
||||||
comm.command?.trim() === "sh license"
|
|
||||||
);
|
|
||||||
const listLicense =
|
|
||||||
dataLicense?.textfsm &&
|
|
||||||
Array.isArray(dataLicense?.textfsm)
|
|
||||||
? dataLicense?.textfsm
|
|
||||||
?.map(
|
|
||||||
(val: { FEATURE: string }) =>
|
|
||||||
val.FEATURE
|
|
||||||
)
|
|
||||||
.join(", ")
|
|
||||||
: "";
|
|
||||||
|
|
||||||
return `Line ${el.line_number ?? ""}: PID: ${
|
|
||||||
el.inventory?.pid ?? ""
|
|
||||||
}, SN: ${el.inventory?.sn ?? ""}, VID: ${
|
|
||||||
el.inventory?.vid ?? ""
|
|
||||||
}, Tested mode: ${
|
|
||||||
DPELP ? "DPELP" : "DPEL"
|
|
||||||
}, License: ${listLicense}`;
|
|
||||||
})
|
|
||||||
.join("\n");
|
|
||||||
|
|
||||||
navigator.clipboard.writeText(value);
|
|
||||||
setSelectedLines([]);
|
|
||||||
}
|
|
||||||
}}
|
|
||||||
>
|
|
||||||
Copy
|
|
||||||
</Button>
|
|
||||||
<hr style={{ width: "100%" }} />
|
|
||||||
<DrawerScenario
|
|
||||||
scenarios={scenarios}
|
|
||||||
setScenarios={setScenarios}
|
|
||||||
/>
|
/>
|
||||||
|
<ButtonConnect
|
||||||
|
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
|
<ButtonDPELP
|
||||||
socket={socket}
|
socket={socket}
|
||||||
selectedLines={selectedLines}
|
selectedLines={selectedLines}
|
||||||
|
|
@ -511,26 +453,35 @@ function App() {
|
||||||
}, 10000);
|
}, 10000);
|
||||||
}}
|
}}
|
||||||
/>
|
/>
|
||||||
{scenarios.map((el, i) => (
|
<ScrollArea h={"60vh"} style={{ paddingBottom: "12px" }}>
|
||||||
<ButtonScenario
|
<Flex
|
||||||
key={i}
|
w={"100%"}
|
||||||
socket={socket}
|
direction={"column"}
|
||||||
selectedLines={selectedLines.filter(
|
wrap={"wrap"}
|
||||||
(el) =>
|
gap={"6px"}
|
||||||
typeof el?.userEmailOpenCLI === "undefined" ||
|
>
|
||||||
el?.userEmailOpenCLI === user?.email
|
{scenarios.map((el, i) => (
|
||||||
)}
|
<ButtonScenario
|
||||||
isDisable={isDisable || selectedLines.length === 0}
|
key={i}
|
||||||
onClick={() => {
|
socket={socket}
|
||||||
setSelectedLines([]);
|
selectedLines={selectedLines.filter(
|
||||||
setIsDisable(true);
|
(el) =>
|
||||||
setTimeout(() => {
|
typeof el?.userEmailOpenCLI === "undefined" ||
|
||||||
setIsDisable(false);
|
el?.userEmailOpenCLI === user?.email
|
||||||
}, 10000);
|
)}
|
||||||
}}
|
isDisable={isDisable || selectedLines.length === 0}
|
||||||
scenario={el}
|
onClick={() => {
|
||||||
/>
|
setSelectedLines([]);
|
||||||
))}
|
setIsDisable(true);
|
||||||
|
setTimeout(() => {
|
||||||
|
setIsDisable(false);
|
||||||
|
}, 10000);
|
||||||
|
}}
|
||||||
|
scenario={el}
|
||||||
|
/>
|
||||||
|
))}
|
||||||
|
</Flex>
|
||||||
|
</ScrollArea>
|
||||||
</Flex>
|
</Flex>
|
||||||
<DrawerLogs
|
<DrawerLogs
|
||||||
socket={socket}
|
socket={socket}
|
||||||
|
|
@ -554,6 +505,25 @@ function App() {
|
||||||
}}
|
}}
|
||||||
setActive={setActiveTab}
|
setActive={setActiveTab}
|
||||||
active={activeTab}
|
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);
|
||||||
|
}
|
||||||
|
}}
|
||||||
/>
|
/>
|
||||||
|
|
||||||
<StationSetting
|
<StationSetting
|
||||||
|
|
|
||||||
|
|
@ -6,18 +6,18 @@ import axios from "axios";
|
||||||
const apiUrl = import.meta.env.VITE_BACKEND_URL;
|
const apiUrl = import.meta.env.VITE_BACKEND_URL;
|
||||||
|
|
||||||
type TLogin = {
|
type TLogin = {
|
||||||
email: string;
|
user_name: string;
|
||||||
password: string;
|
password: string;
|
||||||
};
|
};
|
||||||
|
|
||||||
const Login = () => {
|
const Login = () => {
|
||||||
const formLogin = useForm<TLogin>({
|
const formLogin = useForm<TLogin>({
|
||||||
initialValues: {
|
initialValues: {
|
||||||
email: "",
|
user_name: "",
|
||||||
password: "",
|
password: "",
|
||||||
},
|
},
|
||||||
validate: (values) => ({
|
validate: (values) => ({
|
||||||
email: values.email === "" ? "Email is required" : null,
|
user_name: values.user_name === "" ? "Email is required" : null,
|
||||||
|
|
||||||
password: values.password === "" ? "Password is required" : null,
|
password: values.password === "" ? "Password is required" : null,
|
||||||
}),
|
}),
|
||||||
|
|
@ -25,10 +25,10 @@ const Login = () => {
|
||||||
|
|
||||||
const handleLogin = async () => {
|
const handleLogin = async () => {
|
||||||
try {
|
try {
|
||||||
if (!formLogin.values.email) {
|
if (!formLogin.values.user_name) {
|
||||||
notifications.show({
|
notifications.show({
|
||||||
title: "Error",
|
title: "Error",
|
||||||
message: "Email is required",
|
message: "Username is required",
|
||||||
color: "red",
|
color: "red",
|
||||||
});
|
});
|
||||||
return;
|
return;
|
||||||
|
|
@ -42,7 +42,7 @@ const Login = () => {
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
const payload = {
|
const payload = {
|
||||||
email: formLogin.values.email,
|
user_name: formLogin.values.user_name,
|
||||||
password: formLogin.values.password,
|
password: formLogin.values.password,
|
||||||
};
|
};
|
||||||
const response = await axios.post(apiUrl + "api/auth/login", payload);
|
const response = await axios.post(apiUrl + "api/auth/login", payload);
|
||||||
|
|
@ -69,12 +69,12 @@ const Login = () => {
|
||||||
onSubmit={formLogin.onSubmit(handleLogin)}
|
onSubmit={formLogin.onSubmit(handleLogin)}
|
||||||
>
|
>
|
||||||
<TextInput
|
<TextInput
|
||||||
label={"Email address"}
|
label={"Username"}
|
||||||
placeholder="hello@gmail.com"
|
placeholder="Your username"
|
||||||
value={formLogin.values.email}
|
value={formLogin.values.user_name}
|
||||||
error={formLogin.errors.email}
|
error={formLogin.errors.user_name}
|
||||||
onChange={(e) => {
|
onChange={(e) => {
|
||||||
formLogin.setFieldValue("email", e.target.value!);
|
formLogin.setFieldValue("user_name", e.target.value!);
|
||||||
}}
|
}}
|
||||||
required
|
required
|
||||||
size="md"
|
size="md"
|
||||||
|
|
|
||||||
|
|
@ -10,13 +10,13 @@ type TRegister = {
|
||||||
email: string;
|
email: string;
|
||||||
password: string;
|
password: string;
|
||||||
confirm_password: string;
|
confirm_password: string;
|
||||||
full_name: string;
|
user_name: string;
|
||||||
};
|
};
|
||||||
|
|
||||||
function Register() {
|
function Register() {
|
||||||
const [formRegister, setFormRegister] = useState<TRegister>({
|
const [formRegister, setFormRegister] = useState<TRegister>({
|
||||||
email: "",
|
email: "",
|
||||||
full_name: "",
|
user_name: "",
|
||||||
password: "",
|
password: "",
|
||||||
confirm_password: "",
|
confirm_password: "",
|
||||||
});
|
});
|
||||||
|
|
@ -42,12 +42,12 @@ function Register() {
|
||||||
const payload = {
|
const payload = {
|
||||||
email: formRegister.email,
|
email: formRegister.email,
|
||||||
password: formRegister.password,
|
password: formRegister.password,
|
||||||
full_name: formRegister.full_name,
|
user_name: formRegister.user_name,
|
||||||
};
|
};
|
||||||
const response = await axios.post(apiUrl + "api/auth/register", payload);
|
const response = await axios.post(apiUrl + "api/auth/register", payload);
|
||||||
if (response.data.user) {
|
if (response.data.user) {
|
||||||
const user = response.data.user;
|
const user = response.data.user;
|
||||||
user.fullName = user.full_name;
|
user.userName = user.user_name;
|
||||||
localStorage.setItem("user", JSON.stringify(user));
|
localStorage.setItem("user", JSON.stringify(user));
|
||||||
window.location.href = "/";
|
window.location.href = "/";
|
||||||
} else {
|
} else {
|
||||||
|
|
@ -86,18 +86,17 @@ function Register() {
|
||||||
onChange={(e) => {
|
onChange={(e) => {
|
||||||
setFormRegister({ ...formRegister, email: e.target.value });
|
setFormRegister({ ...formRegister, email: e.target.value });
|
||||||
}}
|
}}
|
||||||
required
|
|
||||||
size="md"
|
size="md"
|
||||||
mb="md"
|
mb="md"
|
||||||
/>
|
/>
|
||||||
|
|
||||||
<TextInput
|
<TextInput
|
||||||
mb="md"
|
mb="md"
|
||||||
label="Full name"
|
label="Username"
|
||||||
placeholder="Bill Gates"
|
placeholder="Bill Gates"
|
||||||
value={formRegister.full_name}
|
value={formRegister.user_name}
|
||||||
onChange={(e) => {
|
onChange={(e) => {
|
||||||
setFormRegister({ ...formRegister, full_name: e.target.value });
|
setFormRegister({ ...formRegister, user_name: e.target.value });
|
||||||
}}
|
}}
|
||||||
required
|
required
|
||||||
size="md"
|
size="md"
|
||||||
|
|
|
||||||
|
|
@ -1,5 +1,5 @@
|
||||||
import type { Socket } from "socket.io-client";
|
import type { Socket } from "socket.io-client";
|
||||||
import type { IScenario, TLine } from "../untils/types";
|
import type { IScenario, TextFSM, TLine, TStation } from "../untils/types";
|
||||||
import { Button } from "@mantine/core";
|
import { Button } from "@mantine/core";
|
||||||
import classes from "./Component.module.css";
|
import classes from "./Component.module.css";
|
||||||
|
|
||||||
|
|
@ -27,6 +27,13 @@ export const ButtonDPELP = ({
|
||||||
onClick();
|
onClick();
|
||||||
selectedLines?.forEach((el) => {
|
selectedLines?.forEach((el) => {
|
||||||
const body = [
|
const body = [
|
||||||
|
{
|
||||||
|
expect: "",
|
||||||
|
send: " show inventory",
|
||||||
|
delay: "1000",
|
||||||
|
repeat: "1",
|
||||||
|
note: "",
|
||||||
|
},
|
||||||
{
|
{
|
||||||
expect: "",
|
expect: "",
|
||||||
send: " show diag",
|
send: " show diag",
|
||||||
|
|
@ -149,7 +156,7 @@ export const ButtonScenario = ({
|
||||||
<Button
|
<Button
|
||||||
disabled={isDisable}
|
disabled={isDisable}
|
||||||
miw={"100px"}
|
miw={"100px"}
|
||||||
style={{ minHeight: "24px", height: "auto" }}
|
style={{ minHeight: "28px", height: "auto" }}
|
||||||
mr={"5px"}
|
mr={"5px"}
|
||||||
variant="outline"
|
variant="outline"
|
||||||
color="#00a164"
|
color="#00a164"
|
||||||
|
|
@ -170,3 +177,119 @@ export const ButtonScenario = ({
|
||||||
</Button>
|
</Button>
|
||||||
);
|
);
|
||||||
};
|
};
|
||||||
|
|
||||||
|
export const ButtonCopy = ({
|
||||||
|
selectedLines,
|
||||||
|
setSelectedLines,
|
||||||
|
}: {
|
||||||
|
setSelectedLines: (value: React.SetStateAction<TLine[]>) => void;
|
||||||
|
selectedLines: TLine[];
|
||||||
|
}) => {
|
||||||
|
return (
|
||||||
|
<Button
|
||||||
|
disabled={selectedLines.length === 0}
|
||||||
|
variant="outline"
|
||||||
|
style={{ height: "30px", width: "100px" }}
|
||||||
|
onClick={() => {
|
||||||
|
if (selectedLines?.length > 0) {
|
||||||
|
const value = selectedLines
|
||||||
|
?.map((el) => {
|
||||||
|
// Get data platform
|
||||||
|
const dataPlatform = el.data?.find(
|
||||||
|
(comm: TextFSM) => comm.command?.trim() === "show platform"
|
||||||
|
);
|
||||||
|
const DPELP =
|
||||||
|
dataPlatform && !dataPlatform?.output?.includes("Incomplete")
|
||||||
|
? true
|
||||||
|
: false;
|
||||||
|
|
||||||
|
// Get data license
|
||||||
|
const dataLicense = el.data?.find(
|
||||||
|
(comm: TextFSM) =>
|
||||||
|
comm.command?.trim() === "show license" ||
|
||||||
|
comm.command?.trim() === "sh license"
|
||||||
|
);
|
||||||
|
const listLicense =
|
||||||
|
dataLicense?.textfsm && Array.isArray(dataLicense?.textfsm)
|
||||||
|
? dataLicense?.textfsm
|
||||||
|
?.map((val: { FEATURE: string }) => val.FEATURE)
|
||||||
|
.join(", ")
|
||||||
|
: "";
|
||||||
|
|
||||||
|
return `Line ${el.line_number ?? ""}: PID: ${
|
||||||
|
el.inventory?.pid ?? ""
|
||||||
|
}, SN: ${el.inventory?.sn ?? ""}, VID: ${
|
||||||
|
el.inventory?.vid ?? ""
|
||||||
|
}, Tested mode: ${
|
||||||
|
DPELP ? "DPELP" : "DPEL"
|
||||||
|
}, License: ${listLicense}`;
|
||||||
|
})
|
||||||
|
.join("\n");
|
||||||
|
|
||||||
|
navigator.clipboard.writeText(value);
|
||||||
|
setSelectedLines([]);
|
||||||
|
}
|
||||||
|
}}
|
||||||
|
>
|
||||||
|
Copy
|
||||||
|
</Button>
|
||||||
|
);
|
||||||
|
};
|
||||||
|
|
||||||
|
export const ButtonSelect = ({
|
||||||
|
selectedLines,
|
||||||
|
setSelectedLines,
|
||||||
|
station,
|
||||||
|
}: {
|
||||||
|
setSelectedLines: (value: React.SetStateAction<TLine[]>) => void;
|
||||||
|
selectedLines: TLine[];
|
||||||
|
station: TStation;
|
||||||
|
}) => {
|
||||||
|
return (
|
||||||
|
<Button
|
||||||
|
variant="filled"
|
||||||
|
style={{ height: "30px", width: "100px" }}
|
||||||
|
onClick={() => {
|
||||||
|
if (selectedLines.length !== station.lines.length)
|
||||||
|
setSelectedLines(station.lines);
|
||||||
|
else setSelectedLines([]);
|
||||||
|
}}
|
||||||
|
>
|
||||||
|
{selectedLines.length !== station.lines.length
|
||||||
|
? "Select All"
|
||||||
|
: "Deselect"}
|
||||||
|
</Button>
|
||||||
|
);
|
||||||
|
};
|
||||||
|
|
||||||
|
export const ButtonConnect = ({
|
||||||
|
selectedLines,
|
||||||
|
setSelectedLines,
|
||||||
|
station,
|
||||||
|
socket,
|
||||||
|
}: {
|
||||||
|
setSelectedLines: (value: React.SetStateAction<TLine[]>) => void;
|
||||||
|
selectedLines: TLine[];
|
||||||
|
station: TStation;
|
||||||
|
socket: Socket | null;
|
||||||
|
}) => {
|
||||||
|
return (
|
||||||
|
<Button
|
||||||
|
disabled={
|
||||||
|
selectedLines.filter((el) => el.status !== "connected").length === 0
|
||||||
|
}
|
||||||
|
variant="outline"
|
||||||
|
style={{ height: "30px", width: "100px" }}
|
||||||
|
onClick={() => {
|
||||||
|
const lines = selectedLines.filter((el) => el.status !== "connected");
|
||||||
|
socket?.emit("connect_lines", {
|
||||||
|
stationData: station,
|
||||||
|
linesData: lines,
|
||||||
|
});
|
||||||
|
setSelectedLines([]);
|
||||||
|
}}
|
||||||
|
>
|
||||||
|
Connect
|
||||||
|
</Button>
|
||||||
|
);
|
||||||
|
};
|
||||||
|
|
|
||||||
|
|
@ -5,6 +5,7 @@ import TerminalCLI from "./TerminalXTerm";
|
||||||
import type { Socket } from "socket.io-client";
|
import type { Socket } from "socket.io-client";
|
||||||
import { IconCircleCheckFilled } from "@tabler/icons-react";
|
import { IconCircleCheckFilled } from "@tabler/icons-react";
|
||||||
import { memo, useMemo } from "react";
|
import { memo, useMemo } from "react";
|
||||||
|
import { convertTimestampToDate } from "../untils/helper";
|
||||||
|
|
||||||
const CardLine = ({
|
const CardLine = ({
|
||||||
line,
|
line,
|
||||||
|
|
@ -62,7 +63,10 @@ const CardLine = ({
|
||||||
// align={"center"}
|
// align={"center"}
|
||||||
>
|
>
|
||||||
<Flex justify={"space-between"}>
|
<Flex justify={"space-between"}>
|
||||||
<Text fw={600} style={{ display: "flex", gap: "4px" }}>
|
<Text
|
||||||
|
fw={600}
|
||||||
|
style={{ display: "flex", gap: "4px", fontSize: "15px" }}
|
||||||
|
>
|
||||||
Line: {line.lineNumber || line.line_number} - {line.port}{" "}
|
Line: {line.lineNumber || line.line_number} - {line.port}{" "}
|
||||||
{line.status === "connected" && (
|
{line.status === "connected" && (
|
||||||
<IconCircleCheckFilled color="green" />
|
<IconCircleCheckFilled color="green" />
|
||||||
|
|
@ -80,9 +84,17 @@ const CardLine = ({
|
||||||
{line?.userOpenCLI ? line?.userOpenCLI + " is using" : ""}
|
{line?.userOpenCLI ? line?.userOpenCLI + " is using" : ""}
|
||||||
</div>
|
</div>
|
||||||
</Flex>
|
</Flex>
|
||||||
{/* <Text className={classes.info_line}>PID: WS-C3560CG-8PC-S</Text>
|
<Flex justify={"space-between"}>
|
||||||
<div className={classes.info_line}>SN: FGL2240307M</div>
|
<div className={classes.info_line}>
|
||||||
<div className={classes.info_line}>VID: V01</div> */}
|
PID: {line?.inventory?.pid || ""}
|
||||||
|
</div>
|
||||||
|
<div className={classes.info_line}>
|
||||||
|
SN: {line?.inventory?.sn || ""}
|
||||||
|
</div>
|
||||||
|
<div className={classes.info_line} style={{ minWidth: "50px" }}>
|
||||||
|
VID: {line?.inventory?.vid || ""}
|
||||||
|
</div>
|
||||||
|
</Flex>
|
||||||
<Box
|
<Box
|
||||||
onClick={(e) => {
|
onClick={(e) => {
|
||||||
e.preventDefault();
|
e.preventDefault();
|
||||||
|
|
@ -118,6 +130,16 @@ const CardLine = ({
|
||||||
}}
|
}}
|
||||||
/>
|
/>
|
||||||
</Box>
|
</Box>
|
||||||
|
<Box>
|
||||||
|
<div className={classes.info_line}>
|
||||||
|
Latest: {line?.latestScenario?.name || ""}
|
||||||
|
<Text style={{ fontStyle: "italic", fontSize: "11px" }}>
|
||||||
|
{line?.latestScenario?.time
|
||||||
|
? "(" + convertTimestampToDate(line?.latestScenario?.time) + ")"
|
||||||
|
: ""}
|
||||||
|
</Text>
|
||||||
|
</div>
|
||||||
|
</Box>
|
||||||
</Flex>
|
</Flex>
|
||||||
</Card>
|
</Card>
|
||||||
);
|
);
|
||||||
|
|
|
||||||
|
|
@ -1,6 +1,6 @@
|
||||||
.card_line {
|
.card_line {
|
||||||
width: 320px;
|
width: 320px;
|
||||||
height: 220px;
|
height: 250px;
|
||||||
padding: 8px;
|
padding: 8px;
|
||||||
gap: 8px;
|
gap: 8px;
|
||||||
cursor: pointer;
|
cursor: pointer;
|
||||||
|
|
@ -8,10 +8,10 @@
|
||||||
|
|
||||||
.info_line {
|
.info_line {
|
||||||
color: dimgrey;
|
color: dimgrey;
|
||||||
font-size: 12px;
|
font-size: 11px;
|
||||||
display: flex;
|
display: flex;
|
||||||
gap: 4px;
|
gap: 4px;
|
||||||
margin-top: 4px;
|
/* margin-top: 4px; */
|
||||||
height: 20px;
|
height: 20px;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -2,11 +2,15 @@ import {
|
||||||
ActionIcon,
|
ActionIcon,
|
||||||
Avatar,
|
Avatar,
|
||||||
Box,
|
Box,
|
||||||
Button,
|
CloseButton,
|
||||||
Flex,
|
Flex,
|
||||||
|
Group,
|
||||||
|
Input,
|
||||||
|
Menu,
|
||||||
Tabs,
|
Tabs,
|
||||||
Text,
|
Text,
|
||||||
Tooltip,
|
Tooltip,
|
||||||
|
UnstyledButton,
|
||||||
} from "@mantine/core";
|
} from "@mantine/core";
|
||||||
import {
|
import {
|
||||||
DndContext,
|
DndContext,
|
||||||
|
|
@ -24,7 +28,13 @@ import {
|
||||||
} from "@dnd-kit/sortable";
|
} from "@dnd-kit/sortable";
|
||||||
import { CSS } from "@dnd-kit/utilities";
|
import { CSS } from "@dnd-kit/utilities";
|
||||||
import { useEffect, useMemo, useState, type JSX } from "react";
|
import { useEffect, useMemo, useState, type JSX } from "react";
|
||||||
import { IconEdit, IconSettingsPlus } from "@tabler/icons-react";
|
import {
|
||||||
|
IconChevronRight,
|
||||||
|
IconEdit,
|
||||||
|
IconLogout,
|
||||||
|
IconSettingsPlus,
|
||||||
|
IconUsersGroup,
|
||||||
|
} from "@tabler/icons-react";
|
||||||
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";
|
||||||
|
|
@ -43,6 +53,7 @@ interface DraggableTabsProps {
|
||||||
setStationEdit: (value: React.SetStateAction<TStation | undefined>) => void;
|
setStationEdit: (value: React.SetStateAction<TStation | undefined>) => void;
|
||||||
active: string;
|
active: string;
|
||||||
setActive: (value: React.SetStateAction<string>) => void;
|
setActive: (value: React.SetStateAction<string>) => void;
|
||||||
|
onSendCommand: (value: string) => void;
|
||||||
}
|
}
|
||||||
|
|
||||||
function SortableTab({
|
function SortableTab({
|
||||||
|
|
@ -103,6 +114,7 @@ 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") &&
|
||||||
|
|
@ -113,6 +125,7 @@ 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 [active, setActive] = useState<string | null>(
|
// const [active, setActive] = useState<string | null>(
|
||||||
// tabsData?.length > 0 ? tabsData[0]?.id.toString() : null
|
// tabsData?.length > 0 ? tabsData[0]?.id.toString() : null
|
||||||
// );
|
// );
|
||||||
|
|
@ -219,14 +232,33 @@ export default function DraggableTabs({
|
||||||
w={w}
|
w={w}
|
||||||
>
|
>
|
||||||
<Flex justify={"space-between"}>
|
<Flex justify={"space-between"}>
|
||||||
<Flex wrap={"wrap"} style={{ maxWidth: "250px" }} gap={"4px"}>
|
<Flex style={{ width: "300px" }} align={"center"}>
|
||||||
{usersConnecting.map((el) => (
|
<Input
|
||||||
<Tooltip label={el.userName} key={el.userId}>
|
style={{
|
||||||
<Avatar color="cyan" radius="xl" size={"md"}>
|
width: "300px",
|
||||||
{el.userName.slice(0, 2)}
|
boxShadow: "0px 0px 10px rgba(0, 0, 0, 0.1)",
|
||||||
</Avatar>
|
}}
|
||||||
</Tooltip>
|
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>
|
</Flex>
|
||||||
<Tabs.List className={classes.list}>
|
<Tabs.List className={classes.list}>
|
||||||
<SortableContext
|
<SortableContext
|
||||||
|
|
@ -280,20 +312,56 @@ export default function DraggableTabs({
|
||||||
</ActionIcon>
|
</ActionIcon>
|
||||||
</Flex>
|
</Flex>
|
||||||
</Tabs.List>
|
</Tabs.List>
|
||||||
<Flex gap={"sm"} align={"baseline"}>
|
<Flex align={"center"}>
|
||||||
<Text className={classes.userName}>{user?.fullName}</Text>
|
<Tooltip
|
||||||
<Button
|
withArrow
|
||||||
variant="outline"
|
label={usersConnecting.map((el) => (
|
||||||
color="red"
|
<Text key={el.userId}>{el.userName}</Text>
|
||||||
style={{ height: "30px", width: "100px" }}
|
))}
|
||||||
onClick={() => {
|
|
||||||
localStorage.removeItem("user");
|
|
||||||
window.location.href = "/";
|
|
||||||
socket?.disconnect();
|
|
||||||
}}
|
|
||||||
>
|
>
|
||||||
Logout
|
<Avatar radius="xl" me={"sm"}>
|
||||||
</Button>
|
<IconUsersGroup color="green" />
|
||||||
|
</Avatar>
|
||||||
|
</Tooltip>
|
||||||
|
<Menu withArrow>
|
||||||
|
<Menu.Target>
|
||||||
|
<UnstyledButton
|
||||||
|
style={{
|
||||||
|
padding: "var(--mantine-spacing-md)",
|
||||||
|
color: "var(--mantine-color-text)",
|
||||||
|
borderRadius: "var(--mantine-radius-sm)",
|
||||||
|
}}
|
||||||
|
>
|
||||||
|
<Group>
|
||||||
|
<div style={{ flex: 1 }}>
|
||||||
|
<Text size="sm" fw={500}>
|
||||||
|
{user?.userName || user?.user_name || ""}
|
||||||
|
</Text>
|
||||||
|
|
||||||
|
<Text c="dimmed" size="xs">
|
||||||
|
{user?.email}
|
||||||
|
</Text>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<IconChevronRight size={16} />
|
||||||
|
</Group>
|
||||||
|
</UnstyledButton>
|
||||||
|
</Menu.Target>
|
||||||
|
<Menu.Dropdown>
|
||||||
|
<Menu.Item
|
||||||
|
style={{ width: "150px" }}
|
||||||
|
onClick={() => {
|
||||||
|
localStorage.removeItem("user");
|
||||||
|
window.location.href = "/";
|
||||||
|
socket?.disconnect();
|
||||||
|
}}
|
||||||
|
color="red"
|
||||||
|
leftSection={<IconLogout size={16} stroke={1.5} />}
|
||||||
|
>
|
||||||
|
Logout
|
||||||
|
</Menu.Item>
|
||||||
|
</Menu.Dropdown>
|
||||||
|
</Menu>
|
||||||
</Flex>
|
</Flex>
|
||||||
</Flex>
|
</Flex>
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -1,7 +1,6 @@
|
||||||
import { useDisclosure } from "@mantine/hooks";
|
import { useDisclosure } from "@mantine/hooks";
|
||||||
import {
|
import {
|
||||||
Drawer,
|
Drawer,
|
||||||
ActionIcon,
|
|
||||||
Box,
|
Box,
|
||||||
ScrollArea,
|
ScrollArea,
|
||||||
Table,
|
Table,
|
||||||
|
|
@ -9,6 +8,7 @@ import {
|
||||||
TextInput,
|
TextInput,
|
||||||
Button,
|
Button,
|
||||||
Checkbox,
|
Checkbox,
|
||||||
|
Text,
|
||||||
} from "@mantine/core";
|
} from "@mantine/core";
|
||||||
import { IconSettingsPlus } from "@tabler/icons-react";
|
import { IconSettingsPlus } from "@tabler/icons-react";
|
||||||
import TableRows from "./Scenario/TableRows";
|
import TableRows from "./Scenario/TableRows";
|
||||||
|
|
@ -361,17 +361,25 @@ function DrawerScenario({
|
||||||
</Grid.Col>
|
</Grid.Col>
|
||||||
</Grid>
|
</Grid>
|
||||||
</Drawer>
|
</Drawer>
|
||||||
|
<Text
|
||||||
<ActionIcon
|
fw={700}
|
||||||
title="Add Scenario"
|
c={"#747474"}
|
||||||
variant="outline"
|
style={{
|
||||||
color="green"
|
fontSize: "14px",
|
||||||
onClick={() => {
|
display: "flex",
|
||||||
open();
|
alignItems: "center",
|
||||||
|
gap: "6px",
|
||||||
}}
|
}}
|
||||||
>
|
>
|
||||||
<IconSettingsPlus />
|
Scenarios
|
||||||
</ActionIcon>
|
<IconSettingsPlus
|
||||||
|
color="green"
|
||||||
|
style={{ cursor: "pointer", width: "18px", height: "18px" }}
|
||||||
|
onClick={() => {
|
||||||
|
open();
|
||||||
|
}}
|
||||||
|
/>
|
||||||
|
</Text>
|
||||||
|
|
||||||
<DialogConfirm
|
<DialogConfirm
|
||||||
opened={openConfirm}
|
opened={openConfirm}
|
||||||
|
|
|
||||||
|
|
@ -315,7 +315,7 @@ const StationSetting = ({
|
||||||
)}
|
)}
|
||||||
</div>
|
</div>
|
||||||
}
|
}
|
||||||
size={"60%"}
|
size={"80%"}
|
||||||
style={{ position: "absolute", left: 0 }}
|
style={{ position: "absolute", left: 0 }}
|
||||||
centered
|
centered
|
||||||
opened={isOpen}
|
opened={isOpen}
|
||||||
|
|
|
||||||
|
|
@ -1,5 +1,6 @@
|
||||||
import { Modal, Text } from "@mantine/core";
|
import { Modal, Text } from "@mantine/core";
|
||||||
import classes from "./Component.module.css";
|
import classes from "./Component.module.css";
|
||||||
|
import { convertTimestampToDate } from "../untils/helper";
|
||||||
|
|
||||||
const ModalLog = ({
|
const ModalLog = ({
|
||||||
opened,
|
opened,
|
||||||
|
|
@ -25,8 +26,8 @@ const ModalLog = ({
|
||||||
const colorPhysicalStart = "#7fffd4";
|
const colorPhysicalStart = "#7fffd4";
|
||||||
const colorPhysicalEnd = "#ffa589";
|
const colorPhysicalEnd = "#ffa589";
|
||||||
return logText
|
return logText
|
||||||
.replace(/^---split-point-scenario---.*$/gm, "") // Remove split-point lines
|
.replace(/^---scenario---.*$/gm, "") // Remove split-point lines
|
||||||
.replace(/^---split-point---.*$/gm, "") // Remove split-point lines
|
.replace(/^---send-command---.*$/gm, "") // Remove split-point lines
|
||||||
.replace(
|
.replace(
|
||||||
/^(---start-testing---|---end-testing---|---start-scenarios---|---end-scenarios---)(\d+)(---.*)?$/gm,
|
/^(---start-testing---|---end-testing---|---start-scenarios---|---end-scenarios---)(\d+)(---.*)?$/gm,
|
||||||
(_, prefix, timestamp, suffix = "") => {
|
(_, prefix, timestamp, suffix = "") => {
|
||||||
|
|
@ -48,12 +49,6 @@ const ModalLog = ({
|
||||||
);
|
);
|
||||||
};
|
};
|
||||||
|
|
||||||
// Function to convert timestamp to date
|
|
||||||
const convertTimestampToDate = (timestamp: number) => {
|
|
||||||
const date = new Date(Number(timestamp));
|
|
||||||
return date.toLocaleString();
|
|
||||||
};
|
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<Modal
|
<Modal
|
||||||
style={{ position: "absolute", left: 0 }}
|
style={{ position: "absolute", left: 0 }}
|
||||||
|
|
|
||||||
|
|
@ -71,7 +71,7 @@ const ModalTerminal = ({
|
||||||
lineId: line?.id,
|
lineId: line?.id,
|
||||||
stationId: line?.station_id,
|
stationId: line?.station_id,
|
||||||
userEmail: user?.email,
|
userEmail: user?.email,
|
||||||
userName: user?.fullName,
|
userName: user?.userName,
|
||||||
});
|
});
|
||||||
socket?.emit("request_take_over", {
|
socket?.emit("request_take_over", {
|
||||||
station_id: line?.station_id,
|
station_id: line?.station_id,
|
||||||
|
|
@ -202,7 +202,7 @@ const ModalTerminal = ({
|
||||||
socket?.emit("request_take_over", {
|
socket?.emit("request_take_over", {
|
||||||
line_id: line?.id,
|
line_id: line?.id,
|
||||||
station_id: Number(line?.station_id),
|
station_id: Number(line?.station_id),
|
||||||
userName: user?.fullName?.trim() || "",
|
userName: user?.userName?.trim() || "",
|
||||||
userEmail: user?.email || "",
|
userEmail: user?.email || "",
|
||||||
});
|
});
|
||||||
setDisableRequestTakeOver(true);
|
setDisableRequestTakeOver(true);
|
||||||
|
|
|
||||||
|
|
@ -2,7 +2,6 @@ import { useEffect, useRef, useState } from "react";
|
||||||
import { Terminal } from "xterm";
|
import { Terminal } from "xterm";
|
||||||
import "xterm/css/xterm.css";
|
import "xterm/css/xterm.css";
|
||||||
import { FitAddon } from "@xterm/addon-fit";
|
import { FitAddon } from "@xterm/addon-fit";
|
||||||
import { SOCKET_EVENTS } from "../untils/constanst";
|
|
||||||
import type { Socket } from "socket.io-client";
|
import type { Socket } from "socket.io-client";
|
||||||
|
|
||||||
interface TerminalCLIProps {
|
interface TerminalCLIProps {
|
||||||
|
|
@ -76,7 +75,7 @@ const TerminalCLI: React.FC<TerminalCLIProps> = ({
|
||||||
|
|
||||||
// Gửi input từ người dùng lên server
|
// Gửi input từ người dùng lên server
|
||||||
terminal.current.onData((data) => {
|
terminal.current.onData((data) => {
|
||||||
socket?.emit(SOCKET_EVENTS.CLI.WRITE_COMMAND_FROM_WEB, {
|
socket?.emit("write_command_line_from_web", {
|
||||||
lineIds: [line_id],
|
lineIds: [line_id],
|
||||||
stationId: station_id,
|
stationId: station_id,
|
||||||
command: data,
|
command: data,
|
||||||
|
|
|
||||||
|
|
@ -34,7 +34,7 @@ export const SocketProvider: React.FC<{ children: React.ReactNode }> = ({
|
||||||
const newSocket = io(SOCKET_URL, {
|
const newSocket = io(SOCKET_URL, {
|
||||||
auth: {
|
auth: {
|
||||||
userId: user?.id,
|
userId: user?.id,
|
||||||
userName: user?.fullName,
|
userName: user?.userName,
|
||||||
},
|
},
|
||||||
});
|
});
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -26,3 +26,9 @@ export function mergeArray(array: any[], key: string) {
|
||||||
.flat()
|
.flat()
|
||||||
.filter((el) => Object.keys(el).length > 0);
|
.filter((el) => Object.keys(el).length > 0);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// Function to convert timestamp to date
|
||||||
|
export const convertTimestampToDate = (timestamp: number) => {
|
||||||
|
const date = new Date(Number(timestamp));
|
||||||
|
return date.toLocaleString();
|
||||||
|
};
|
||||||
|
|
|
||||||
|
|
@ -83,6 +83,10 @@ export type TLine = {
|
||||||
userOpenCLI?: string;
|
userOpenCLI?: string;
|
||||||
userEmailOpenCLI?: string;
|
userEmailOpenCLI?: string;
|
||||||
statusTicket?: string;
|
statusTicket?: string;
|
||||||
|
latestScenario?: {
|
||||||
|
name: string;
|
||||||
|
time: number;
|
||||||
|
};
|
||||||
};
|
};
|
||||||
|
|
||||||
export type TUser = {
|
export type TUser = {
|
||||||
|
|
|
||||||
Loading…
Reference in New Issue