diff --git a/BACKEND/app/controllers/auth_controller.ts b/BACKEND/app/controllers/auth_controller.ts
index b423cc5..22cabea 100644
--- a/BACKEND/app/controllers/auth_controller.ts
+++ b/BACKEND/app/controllers/auth_controller.ts
@@ -6,7 +6,7 @@ export default class AuthController {
// Đăng ký
async register({ request, response }: HttpContext) {
try {
- const data = request.only(['email', 'password', 'user_name'])
+ const data = request.only(['email', 'password', 'user_name', 'first_name', 'last_name'])
const user = await User.query().where('user_name', data.user_name).first()
@@ -23,7 +23,12 @@ export default class AuthController {
// Đăng nhập
async login({ request, auth, response }: HttpContext) {
- const { user_name: userName, password } = request.only(['user_name', 'password'])
+ const { user_name: userName, password } = request.only([
+ 'user_name',
+ 'password',
+ 'first_name',
+ 'last_name',
+ ])
const user = await User.query().where('user_name', userName).first()
if (!user) {
@@ -47,10 +52,18 @@ export default class AuthController {
email: remoteUser.userEmail,
userName: userName,
password: password,
+ firstName: remoteUser?.firstName || null,
+ lastName: remoteUser?.lastName || null,
})
return response.json({
message: 'Login successful',
- user: { id: newUser.id, email: newUser.email, userName: newUser.userName },
+ user: {
+ id: newUser.id,
+ email: newUser.email,
+ userName: newUser.userName,
+ firstName: newUser.firstName,
+ lastName: newUser.lastName,
+ },
})
}
@@ -62,7 +75,13 @@ export default class AuthController {
return response.json({
message: 'Login successful',
- user: { id: user.id, email: user.email, userName: user.userName },
+ user: {
+ id: user.id,
+ email: user.email,
+ userName: user.userName,
+ firstName: user.firstName,
+ lastName: user.lastName,
+ },
})
} catch {
return response.status(401).json({ message: 'Invalid credentials' })
diff --git a/BACKEND/app/models/user.ts b/BACKEND/app/models/user.ts
index e5068e2..ce897bf 100644
--- a/BACKEND/app/models/user.ts
+++ b/BACKEND/app/models/user.ts
@@ -14,6 +14,12 @@ export default class User extends BaseModel {
@column()
declare password: string
+ @column()
+ declare firstName: string | null
+
+ @column()
+ declare lastName: string | null
+
@column.dateTime({ autoCreate: true })
declare createdAt: DateTime
diff --git a/BACKEND/app/services/line_connection.ts b/BACKEND/app/services/line_connection.ts
index 78a39f9..a442953 100644
--- a/BACKEND/app/services/line_connection.ts
+++ b/BACKEND/app/services/line_connection.ts
@@ -145,6 +145,11 @@ export default class LineConnection {
private debounceSendSummaryReport: NodeJS.Timeout | null = null
private isPingToServer: boolean
private outputPingToServer: string
+ private outputTestLog: string
+ private userTest: {
+ dpelp: { name: string; time: number }
+ physical: { name: string; time: number }
+ }
constructor(config: LineConfig, socketIO: any, handleClearLine: () => void) {
this.config = config
@@ -180,6 +185,8 @@ export default class LineConnection {
this.outputTestingPortPoE = ''
this.isPingToServer = false
this.outputPingToServer = ''
+ this.outputTestLog = ''
+ this.userTest = { dpelp: { name: '', time: 0 }, physical: { name: '', time: 0 } }
}
/**
* Connect to line with socket
@@ -225,6 +232,7 @@ export default class LineConnection {
this.waitingScenario = true
this.outputBuffer += message
this.outputScenario += message
+ this.outputTestLog += cleanData(data.toString())
if (!this.config.inventory)
this.outputInventory = this.outputInventory.slice(-3000) + message
}
@@ -439,6 +447,10 @@ export default class LineConnection {
})
if (script?.send_result || script?.sendResult) {
this.dataDPELP = ''
+ this.userTest = {
+ ...this.userTest,
+ dpelp: { name: userName || '', time: Date.now() },
+ }
// this.config.inventory = ''
}
@@ -542,10 +554,11 @@ export default class LineConnection {
if (
['show inventory', 'sh inventory', 'show inv', 'sh inv'].includes(item.command)
) {
- const dataInventory = JSON.parse(item.textfsm)[0]
+ const listInventory = JSON.parse(item.textfsm)
+ const dataInventory = listInventory[0]
this.config.inventory = this.config.inventory
- ? { ...this.config.inventory, ...dataInventory }
- : dataInventory
+ ? { ...this.config.inventory, ...dataInventory, listInventory }
+ : { ...dataInventory, listInventory }
pid = dataInventory?.pid || ''
this.addHistory(this.config.stationId, this.config.id, {
id: this.config.id,
@@ -804,9 +817,10 @@ export default class LineConnection {
data.forEach((item) => {
if (item?.textfsm && isValidJson(item?.textfsm)) {
if (['show inventory', 'sh inventory', 'show inv', 'sh inv'].includes(item.command)) {
- const dataInventory = JSON.parse(item.textfsm)[0]
+ const listInventory = JSON.parse(item.textfsm)
+ const dataInventory = listInventory[0]
this.config.inventory = this.config.inventory
- ? { ...this.config.inventory, ...dataInventory }
+ ? { ...this.config.inventory, ...dataInventory, listInventory }
: dataInventory
}
item.textfsm = JSON.parse(item.textfsm)
@@ -1161,7 +1175,7 @@ Ports Missing/Down: ${missing.length}\n\n`
/**
* Starting physical test (PoE ports testing)
*/
- async runPhysicalTest() {
+ async runPhysicalTest(userName?: string) {
if (this.config.runningPhysical) {
console.log('Running physical test')
return
@@ -1173,6 +1187,7 @@ Ports Missing/Down: ${missing.length}\n\n`
this.config.reasonSkipPhysical = ''
this.testingPortPoE = true
this.outputTestingPortPoE = ''
+ this.userTest = { ...this.userTest, physical: { name: userName || '', time: Date.now() } }
const listPorts = await this.getPorts()
this.socketIO.emit('running_scenario', {
stationId: this.config.stationId,
@@ -1895,6 +1910,522 @@ Ports Missing/Down: ${missing.length}\n\n`
})
}
+ /**
+ * Send summary report using the new "Equipment Receiving & Testing Report" template.
+ * Email-safe HTML: table-based layout, inline styles, no external CSS or web fonts.
+ */
+ sendReportSummaryV2 = async (snapshot?: {
+ snapConfig: LineConfig
+ snapPhysical: PhysicalPortTest
+ reason: string
+ outputTestLog: string
+ userTest: {
+ dpelp: { name: string; time: number }
+ physical: { name: string; time: number }
+ }
+ }) => {
+ if (this.debounceSendSummaryReport) clearTimeout(this.debounceSendSummaryReport)
+ const timeZone = process.env.TIME_ZONE || 'Australia/Sydney'
+ const physicalTest = snapshot?.snapPhysical ? snapshot?.snapPhysical : this.physicalTest
+ const config = snapshot?.snapConfig ? snapshot?.snapConfig : this.config
+ const portPhysical = Array.from(physicalTest.ports.values())
+ const missing = portPhysical.filter((p) => !p.tested)
+ const missingPoE = missing.filter((p) => !p.name.includes('SFP'))
+ const missingSFP = missing.filter((p) => p.name.includes('SFP'))
+ const tested = portPhysical.filter((p) => p.tested)
+ const testedPoE = tested.filter((p) => !p.name.includes('SFP'))
+ const testedSFP = tested.filter((p) => p.name.includes('SFP'))
+ const totalPoE = testedPoE.length + missingPoE.length
+ const totalSFP = testedSFP.length + missingSFP.length
+
+ const showVersion = config?.data?.find(
+ (d) => d.command?.trim()?.includes('show ver') || d.command?.trim()?.includes('sh ver')
+ )
+ const dataShowVersion =
+ showVersion?.textfsm && (showVersion?.textfsm as any)?.[0]
+ ? (showVersion?.textfsm as any)?.[0]
+ : config?.inventory
+
+ const showLicense = config?.data?.find(
+ (d) => d.command?.trim()?.includes('show lic') || d.command?.trim()?.includes('sh lic')
+ )
+ const dataShowLic =
+ showLicense?.textfsm && Array.isArray(showLicense?.textfsm)
+ ? (showLicense?.textfsm as any[])
+ : null
+
+ const issues: string[] = config?.latestScenario?.detectAI?.issue || []
+ const skipReason = this.config.reasonSkipPhysical || snapshot?.reason || ''
+ const isSkipped = typeof skipReason === 'string' && skipReason.trim().length > 0
+
+ const verdictPass = missing.length === 0 && !isSkipped
+ const verdictLabel = verdictPass ? 'PASSED' : 'NEEDS REVIEW'
+ const verdictMsg = verdictPass
+ ? 'All tests passed — Ready for deployment'
+ : 'Issues detected — review required before deployment'
+ const verdictBg = verdictPass ? '#ecfdf5' : '#fef2f2'
+ const verdictBd = verdictPass ? '#a7f3d0' : '#fecaca'
+ const verdictTx = verdictPass ? '#065f46' : '#991b1b'
+
+ const reportId = `RPT-${momentTZ().tz(timeZone).format('YYYY-MMDD')}`
+ const reportDate = momentTZ().tz(timeZone).format('DD MMM YYYY')
+
+ const memText = dataShowVersion?.MEMORY
+ ? convertFromKilobytesString(dataShowVersion.MEMORY)
+ : '—'
+ const flashText = dataShowVersion?.USB_FLASH
+ ? convertFromKilobytesString(dataShowVersion.USB_FLASH)
+ : '—'
+
+ // ---- Template-fallback values (use file's hardcoded content when no real data) ----
+ const productName = escapeHtml(String(config?.inventory?.name || ''))
+ const productPN = escapeHtml(String(config?.inventory?.pid || ''))
+ const productSN = escapeHtml(String(config?.inventory?.sn || ''))
+ const productVid = escapeHtml(String(config?.inventory?.vid || ''))
+ const iosVersion = escapeHtml(String(dataShowVersion?.VERSION || ''))
+ const macAddress = escapeHtml(String(dataShowVersion?.MAC_ADDRESS || ''))
+ const memDisplay = escapeHtml(memText !== '—' ? memText : '-')
+ const flashDisplay = escapeHtml(flashText !== '—' ? flashText : '-')
+ const configRam = await detectConfigRamByModel(config?.inventory?.pid)
+
+ // AI issue rows (one per real AI issue, fall back to file's hardcoded row when none)
+ const aiIssueRowsHtml =
+ issues.length > 0
+ ? issues
+ .slice(0, 1)
+ .map(
+ (issue) =>
+ `
★ AI ${escapeHtml(issue)} Investigate
`
+ )
+ .join('')
+ : `★ AI Potential intermittent power instability. PSU #1 POST logs show 3 retries before handshake. Investigate
`
+
+ // License boxes (real licenses if available, else file's hardcoded boxes)
+ const licenseBoxesHtml =
+ dataShowLic && dataShowLic.length > 0
+ ? dataShowLic
+ .filter((l) => l.LICENSE_TYPE && l.FEATURE)
+ .map(
+ (l: any) =>
+ `${escapeHtml(String(l.FEATURE || ''))}
${escapeHtml(String(l.LICENSE_TYPE || ''))}${l.STATUS ? ' · ' + escapeHtml(String(l.STATUS)) : ''}
`
+ )
+ .join('')
+ : ``
+
+ // Port stat values (real numbers if any port data, else file's defaults)
+ const hasPortData = portPhysical.length > 0
+ const poeText = hasPortData ? `${testedPoE.length}/${totalPoE}` : '0/0'
+ const sfpText = hasPortData ? `${testedSFP.length}/${totalSFP}` : '0/0'
+ const poeColor =
+ !hasPortData || (totalPoE > 0 && testedPoE.length === totalPoE) ? '#10b981' : '#f59e0b'
+ const sfpColor =
+ !hasPortData || (totalSFP > 0 && testedSFP.length === totalSFP) ? '#10b981' : '#f59e0b'
+
+ // Missing-port detail blocks (only when there is something to show)
+ const missingParts: string[] = []
+ if (missingPoE.length) {
+ missingParts.push(
+ `Missing PoE (${missingPoE.length}): ${missingPoE.map((p) => escapeHtml(physicalTest.normalizePortName(p.name))).join(', ')}
`
+ )
+ }
+ if (missingSFP.length) {
+ missingParts.push(
+ `Missing SFP (${missingSFP.length}): ${missingSFP.map((p) => escapeHtml(physicalTest.normalizePortName(p.name))).join(', ')}
`
+ )
+ }
+ if (isSkipped) {
+ missingParts.push(
+ `User Skipped Physical Test: ${escapeHtml(skipReason)}
`
+ )
+ }
+ const missingDetailsHtml = missingParts.join('')
+
+ // Verdict checkmark / cross path
+ const verdictPathSvg = verdictPass
+ ? ' '
+ : ' '
+
+ // Physical Check checklist
+ const checklistItems: Array<[string, string]> = [
+ ['ok', 'Packaging intact — no damage to box or foam'],
+ ['ok', 'No physical damage — chassis, fans, PSU'],
+ ['ok', `S/N matches label — ${productSN} verified`],
+ ['ok', 'All 48 GigE + 4 SFP+ ports clean'],
+ ['ok', 'Accessories — power cable, rack ears, console cable'],
+ ['warn', 'Minor scratch on top chassis (2cm) — cosmetic only'],
+ ]
+ const checklistRowsHtml = checklistItems
+ .map(([k, t]) =>
+ k === 'ok'
+ ? ``
+ : ``
+ )
+ .join('')
+
+ // Physical Check photo placeholder cell (4 of these in the photo grid)
+ const photoCellHtml = (label: string) =>
+ ``
+
+ // ---- Body: full template mirroring index.html, table-based + inline styles ----
+ const body = `
+
+
+
+Equipment Report — Mail Summary
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+ PROLOGY IT
+ Equipment Receiving & Testing Report
+
+
+
+
+
+ #${escapeHtml(reportId)}
+ ${escapeHtml(reportDate)}
+
+
+
+
+
+
+
+ ${verdictPathSvg}
+ ${verdictLabel}
+ ${escapeHtml(verdictMsg)}
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+ Product Info
+
+ Name ${productName}
+ P/N ${productPN}
+ S/N ${productSN}
+ MAC ${macAddress}
+ Type --
+ Cond. --
+ Supplier -
+ Warranty -
+
+
+
+
+
+
+
+ Technical Specs
+
+
+ Specification
+ Actual
+ Default
+
+
+ IOS-XE Version
+ ${iosVersion}
+ -
+
+
+ System RAM
+ ${memDisplay}
+ ${configRam?.ram || '-'}
+
+
+ Flash Storage
+ ${flashDisplay}
+ ${configRam?.flash || '-'}
+
+
+ Uplink Module
+ -
+ -
+
+
+ PSU Model
+ -
+ -
+
+
+ PoE Budget
+ -
+ -
+
+
+
+
+
+
+
+
+
+
+
+
+
+ Issues Found
+ ${aiIssueRowsHtml}
+
+
+ COSMETIC Minor scratch on top chassis (2cm) — non-functional
+ Accepted
+
+
+
+
+ MINOR Fan #2 at 48dB under stress (spec 45dB) — within rack tolerance
+ Monitor
+
+
+ 0 Critical · 0 Major · 1 Minor · 1 Cosmetic
+
+
+
+
+
+
+
+
+ Receiving & Inspection Notes
+
+
⚠ Warning from Warehouse
+
Box arrived with slight indentation on the left corner. Internal foam was still intact. Serial number on box was partially obscured by shipping label but verified upon unboxing.
+
+
+
Accessory Checklist
+
+
+ Rackmount
+ PSU (Internal)
+ Console Cable
+ Documents
+ Original Box
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+ ✓
+
+
+ Received
+
+
+ Unknown
+
+
+ ${momentTZ().tz(timeZone).format('DD MMM')}
+
+
+
+
+
+ ✓
+
+
+ Software Test
+
+
+ ${snapshot?.userTest?.dpelp?.name || ''}
+
+
+ ${momentTZ(snapshot?.userTest?.dpelp?.time).tz(timeZone).format('DD MMM, HH:mm')}
+
+
+
+
+
+ ✓
+
+
+
+ Physical Check
+
+
+ ${snapshot?.userTest?.physical?.name || ''}
+
+
+ ${momentTZ(snapshot?.userTest?.physical?.time).tz(timeZone).format('DD MMM, HH:mm')}
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+ Physical Check
+
+ ${snapshot?.userTest?.physical?.name || ''} · ${momentTZ(snapshot?.userTest?.physical?.time).tz(timeZone).format('DD MMM, HH:mm')}
+
+
+
+
+
+
+
+ ${photoCellHtml('Front')}
+ ${photoCellHtml('Rear')}
+
+
+ ${photoCellHtml('S/N Label')}
+ ${photoCellHtml('Package')}
+
+
+
+
+ ${checklistRowsHtml}
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+ Software Check
+
+ ${snapshot?.userTest?.dpelp?.name || ''} · ${momentTZ(snapshot?.userTest?.dpelp?.time).tz(timeZone).format('DD MMM, HH:mm')}
+
+
+
+
+
+ Hardware Inventory
+
+ ${
+ this.config?.inventory?.listInventory
+ ?.map(
+ (item: any) => `
+ ${item.pid} ${item.sn} `
+ )
+ .join('') || ''
+ }
+
+
+
+ System & License
+ ${licenseBoxesHtml}
+
+
+ Port Test Summary
+
+
+ ${escapeHtml(poeText)}
${hasPortData ? 'PoE UP' : 'GigE UP'}
+ ${escapeHtml(sfpText)}
SFP+ UP
+
+
+ ${missingSFP.length > 0 || missingPoE.length > 0 ? 'WARN' : 'PASS'}
PoE+ Test
+ ${totalPoE + totalSFP === 0 ? 100 : Math.round(((totalPoE + totalSFP - (missingPoE.length + missingSFP.length)) / (totalPoE + totalSFP)) * 100)}%
Throughput
+
+
+ ${missingDetailsHtml}
+
+
+
+
+
+
+ CONSOLE RAW OUTPUT (Boot Log snippet)
+ ${snapshot?.outputTestLog || 'No test log available'}
+
+
+
+
+
+
+ Prology IT — Equipment QA System · Confidential — Internal Use Only
+
+
+
+`
+
+ this.updateNote(config?.inventory?.sn, this.dataDPELP as DataDPELP)
+ await sendMessageToMail(
+ `[ATC] - [${config.stationName} - Line: ${config.lineNumber}] - [${this.config.inventory?.pid}] - [${this.config.inventory?.sn}] - Summary of Testing Results`,
+ body
+ )
+ this.socketIO.emit('summary_tested', {
+ stationId: this.config.stationId,
+ lineId: this.config.id,
+ body: body,
+ title: `[${config.stationName} - Line: ${config.lineNumber}] - Summary of Testing Results`,
+ })
+ }
+
/**
* Reset config information of line
*/
@@ -1933,6 +2464,8 @@ Ports Missing/Down: ${missing.length}\n\n`
snapConfig: this.config,
snapPhysical: this.physicalTest,
reason: '',
+ outputTestLog: this.outputTestLog,
+ userTest: this.userTest,
}
this.debounceSendSummaryReport = setTimeout(() => {
if (!this.config.listFeatureTested?.includes('PHYSICAL')) {
@@ -1942,7 +2475,9 @@ Ports Missing/Down: ${missing.length}\n\n`
}
this.config.listFeatureTested = ['DPELP', 'PHYSICAL', 'SUMMARY']
this.sendFeatureTested()
- this.sendReportSummary(snapshot)
+ this.sendReportSummaryV2(snapshot)
+ this.outputTestLog = ''
+ this.userTest = { dpelp: { name: '', time: 0 }, physical: { name: '', time: 0 } }
}, timeout)
}
diff --git a/BACKEND/app/ultils/templates/show_version.ts b/BACKEND/app/ultils/templates/show_version.ts
index a72d19e..556e5c9 100644
--- a/BACKEND/app/ultils/templates/show_version.ts
+++ b/BACKEND/app/ultils/templates/show_version.ts
@@ -3,9 +3,11 @@ import XRegExp from 'xregexp'
// Parser function
const parseLog = (data: string) => {
const patterns = [
- XRegExp('^.*Software.*\\((?\\S+)\\),\\s+Version\\s+(?[\\w\\.-]+)'),
XRegExp(
- '^\\*?\\s*\\d+\\s+\\d+\\s+[\\w-]+\\s+(?\\d[\\w\\.-]+)\\s+(?[\\w-]+)\\s+(?:BUNDLE|INSTALL)'
+ '^.*Software.*\\((?\\S+)\\),\\s+Version\\s+(?[\\w\\.\\(\\)\\-]+)'
+ ),
+ XRegExp(
+ '^\\*?\\s*\\d+\\s+\\d+\\s+[\\w-]+\\s+(?\\d[\\w\\.\\(\\)\\-]+)\\s+(?[\\w-]+)\\s+(?:BUNDLE|INSTALL)'
),
XRegExp('System\\s+image\\s+file\\s+is\\s+"(?:[^:]*:)?(?[^"]+)"'),
XRegExp('Active-image:\\s+(?\\S+)'),
diff --git a/BACKEND/database/migrations/1778550000000_add_first_name_last_name_to_users_table.ts b/BACKEND/database/migrations/1778550000000_add_first_name_last_name_to_users_table.ts
new file mode 100644
index 0000000..800c368
--- /dev/null
+++ b/BACKEND/database/migrations/1778550000000_add_first_name_last_name_to_users_table.ts
@@ -0,0 +1,19 @@
+import { BaseSchema } from '@adonisjs/lucid/schema'
+
+export default class extends BaseSchema {
+ protected tableName = 'users'
+
+ async up() {
+ this.schema.alterTable(this.tableName, (table) => {
+ table.string('first_name').nullable().after('user_name')
+ table.string('last_name').nullable().after('first_name')
+ })
+ }
+
+ async down() {
+ this.schema.alterTable(this.tableName, (table) => {
+ table.dropColumn('first_name')
+ table.dropColumn('last_name')
+ })
+ }
+}
diff --git a/BACKEND/providers/socket_io_provider.ts b/BACKEND/providers/socket_io_provider.ts
index ac70af9..f8a3df5 100644
--- a/BACKEND/providers/socket_io_provider.ts
+++ b/BACKEND/providers/socket_io_provider.ts
@@ -203,6 +203,7 @@ export class WebSocketIo {
socket.on('run_scenario', async (data) => {
const lineId = data.id
const scenario = data.scenario
+ const name = data.userName || userName
// Check station is active
const activeStation = await checkStationActive(data.stationId)
if (!activeStation) return
@@ -211,7 +212,7 @@ export class WebSocketIo {
io,
data.stationId,
[lineId],
- async (line) => line.runScript(scenario, userName),
+ async (line) => line.runScript(scenario, name),
{
scenario,
}
@@ -728,7 +729,7 @@ export class WebSocketIo {
})
socket.on('run_physical_test', async (data) => {
- const { stationId, lineId } = data
+ const { stationId, lineId, userName: name } = data
// Check station is active
const activeStation = await checkStationActive(stationId)
if (!activeStation) return
@@ -737,7 +738,7 @@ export class WebSocketIo {
io,
stationId,
[lineId],
- async (lineCon) => lineCon.runPhysicalTest(),
+ async (lineCon) => lineCon.runPhysicalTest(name || userName),
{}
)
})
@@ -907,7 +908,7 @@ export class WebSocketIo {
stationId,
[lineId],
async (lineCon) => {
- lineCon.sendReportSummary()
+ lineCon.sendReportSummaryV2()
},
{}
)
diff --git a/FRONTEND/src/components/BottomToolBar.tsx b/FRONTEND/src/components/BottomToolBar.tsx
index 61032c3..c5da472 100644
--- a/FRONTEND/src/components/BottomToolBar.tsx
+++ b/FRONTEND/src/components/BottomToolBar.tsx
@@ -223,8 +223,8 @@ const BottomToolBar = ({
onClick={() => {
setSelectedLines(
selectedLines.filter(
- (line) => line.id !== el.id
- )
+ (line) => line.id !== el.id,
+ ),
);
socket?.emit("close_cli", {
lineId: el?.id,
@@ -288,7 +288,7 @@ const BottomToolBar = ({
const lines = station.lines.filter(
(line) =>
!line?.userOpenCLI ||
- line?.userOpenCLI === user?.userName
+ line?.userOpenCLI === user?.userName,
);
if (selectedLines.length !== lines.length) {
setSelectedLines(lines);
@@ -365,7 +365,7 @@ const BottomToolBar = ({
selectedLines={selectedLines}
isDisable={isDisable || selectedLines.length === 0}
dataDPELP={scenarios?.find(
- (el) => el.title.toUpperCase() === "DPELP"
+ (el) => el.title.toUpperCase() === "DPELP",
)}
onClick={() => {
if (selectedLines.length > 0) {
@@ -380,6 +380,11 @@ const BottomToolBar = ({
setIsDisable(false);
}, 5000);
}}
+ userName={
+ user?.firstName
+ ? `${user.firstName} ${user.lastName || ""}`
+ : user?.userName || "Unknown User"
+ }
/>
{
if (
selectedLines?.filter(
- (line) => line?.runningPhysical
+ (line) => line?.runningPhysical,
)?.length > 0
) {
selectedLines
@@ -430,7 +435,7 @@ const BottomToolBar = ({
}}
>
{selectedLines?.filter(
- (line) => line?.runningPhysical
+ (line) => line?.runningPhysical,
)?.length > 0
? "Done/End"
: "Physical"}
@@ -545,8 +550,8 @@ const BottomToolBar = ({
onClick={() => {
setSelectedLines(
selectedLines.filter(
- (line) => line.id !== el.id
- )
+ (line) => line.id !== el.id,
+ ),
);
socket?.emit("close_cli", {
lineId: el?.id,
@@ -645,7 +650,7 @@ const BottomToolBar = ({
const lines = station.lines.filter(
(line) =>
!line?.userOpenCLI ||
- line?.userOpenCLI === user?.userName
+ line?.userOpenCLI === user?.userName,
);
if (selectedLines.length !== lines.length) {
setSelectedLines(lines);
diff --git a/FRONTEND/src/components/ButtonAction.tsx b/FRONTEND/src/components/ButtonAction.tsx
index 8c223be..137f581 100644
--- a/FRONTEND/src/components/ButtonAction.tsx
+++ b/FRONTEND/src/components/ButtonAction.tsx
@@ -18,6 +18,7 @@ export const ButtonDPELP = ({
dataDPELP,
text = "DPELP",
color = "#00a164",
+ userName = "Unknown User",
}: {
socket: Socket | null;
isDisable: boolean;
@@ -27,6 +28,7 @@ export const ButtonDPELP = ({
dataDPELP?: IScenario;
text?: string;
color?: string;
+ userName?: string;
}) => {
return (
{
return (
el.outlet &&
Number(el.outlet) > 0 &&
- (el.apcName === "apc_1" || el.apc_name === "apc_1")
+ (el.apcName === "apc_1" || el.apc_name === "apc_1"),
)
?.map((el) => el.outlet);
const lineApc2 = selectedLines
@@ -214,7 +219,7 @@ export const ButtonScenario = ({
(el) =>
el.outlet &&
Number(el.outlet) > 0 &&
- (el.apcName === "apc_2" || el.apc_name === "apc_2")
+ (el.apcName === "apc_2" || el.apc_name === "apc_2"),
)
?.map((el) => el.outlet);
if (lineApc1.length > 0)
@@ -250,7 +255,8 @@ export const ButtonScenario = ({
? scenario?.isReboot
: scenario?.is_reboot,
},
- })
+ userName: userName,
+ }),
);
});
}}
@@ -278,7 +284,7 @@ export const ButtonCopy = ({
?.map((el) => {
// Get data platform
const dataPlatform = el.data?.find(
- (comm: TextFSM) => comm.command?.trim() === "show platform"
+ (comm: TextFSM) => comm.command?.trim() === "show platform",
);
const DPELP =
dataPlatform && !dataPlatform?.output?.includes("Incomplete")
@@ -289,7 +295,7 @@ export const ButtonCopy = ({
const dataLicense = el.data?.find(
(comm: TextFSM) =>
comm.command?.trim() === "show license" ||
- comm.command?.trim() === "sh license"
+ comm.command?.trim() === "sh license",
);
const listLicense =
dataLicense?.textfsm && Array.isArray(dataLicense?.textfsm)
@@ -341,7 +347,7 @@ export const ButtonSelect = ({
>
{selectedLines.length !==
station.lines.filter(
- (line) => !line?.userOpenCLI || line?.userOpenCLI === userName
+ (line) => !line?.userOpenCLI || line?.userOpenCLI === userName,
).length
? "Select All"
: "Deselect"}
diff --git a/FRONTEND/src/components/CardLine.tsx b/FRONTEND/src/components/CardLine.tsx
index c313b5a..c4c0333 100644
--- a/FRONTEND/src/components/CardLine.tsx
+++ b/FRONTEND/src/components/CardLine.tsx
@@ -596,6 +596,11 @@ const CardLine = ({
setIsDisabled(false);
}, 5000);
}}
+ userName={
+ user?.firstName
+ ? `${user.firstName} ${user.lastName || ""}`
+ : user?.userName || "Unknown User"
+ }
/>
))}
diff --git a/FRONTEND/src/components/Modal/ModalConfirmRunPhysicalTest.tsx b/FRONTEND/src/components/Modal/ModalConfirmRunPhysicalTest.tsx
index ffb7a69..6bb5f6d 100644
--- a/FRONTEND/src/components/Modal/ModalConfirmRunPhysicalTest.tsx
+++ b/FRONTEND/src/components/Modal/ModalConfirmRunPhysicalTest.tsx
@@ -1,4 +1,4 @@
-import { useEffect, useState } from "react";
+import { useEffect, useMemo, useState } from "react";
import {
Modal,
Box,
@@ -33,6 +33,12 @@ export default function ModalConfirmRunPhysical({
socket,
station,
}: Props) {
+ const user = useMemo(() => {
+ return localStorage.getItem("user") &&
+ typeof localStorage.getItem("user") === "string"
+ ? JSON.parse(localStorage.getItem("user") || "")
+ : null;
+ }, []);
const [dataLines, setDataLines] = useState([]);
const [isDisabled, setIsDisabled] = useState(false);
@@ -48,7 +54,7 @@ export default function ModalConfirmRunPhysical({
sn: line?.inventory?.sn,
vid: line?.inventory?.vid,
checked: true,
- }))
+ })),
);
}
}, [listLines]);
@@ -100,8 +106,8 @@ export default function ModalConfirmRunPhysical({
...el,
checked: e.target.checked,
}
- : el
- )
+ : el,
+ ),
)
}
/>
@@ -133,13 +139,16 @@ export default function ModalConfirmRunPhysical({
socket?.emit("run_physical_test", {
lineId: line?.id,
stationId: Number(station?.id),
+ userName: user?.firstName
+ ? `${user.firstName} ${user.lastName || ""}`
+ : user?.userName || "Unknown User",
});
});
setDataLines(listNotChecked);
setListLines(
listLines.filter((el) =>
- listNotChecked.find((li) => li.id === el.id)
- )
+ listNotChecked.find((li) => li.id === el.id),
+ ),
);
setIsDisabled(true);
setTimeout(() => {
diff --git a/FRONTEND/src/components/Modal/ModalTerminal.tsx b/FRONTEND/src/components/Modal/ModalTerminal.tsx
index 0a0f464..61a6886 100644
--- a/FRONTEND/src/components/Modal/ModalTerminal.tsx
+++ b/FRONTEND/src/components/Modal/ModalTerminal.tsx
@@ -664,6 +664,9 @@ const ModalTerminal = ({
timeout: 360000,
body: JSON.stringify(bodyDPELP),
},
+ userName: user?.firstName
+ ? `${user.firstName} ${user.lastName || ""}`
+ : user?.userName || "Unknown User",
}),
);
socket?.emit("run_all_dpelp", {
@@ -687,17 +690,17 @@ const ModalTerminal = ({
setIsDisable(false);
}, 15000);
}
- if (activeStep >= 2 && !isDisable) {
- setActiveStep(3);
- setIsDisable(true);
- setTimeout(() => {
- setIsDisable(false);
- }, 5000);
- socket?.emit("send_summary_report", {
- lineId: line?.id,
- stationId: line?.station_id || line?.stationId,
- });
- }
+ // if (activeStep >= 2 && !isDisable) {
+ // setActiveStep(3);
+ // setIsDisable(true);
+ // setTimeout(() => {
+ // setIsDisable(false);
+ // }, 5000);
+ // socket?.emit("send_summary_report", {
+ // lineId: line?.id,
+ // stationId: line?.station_id || line?.stationId,
+ // });
+ // }
}}
>
@@ -1492,6 +1491,11 @@ const ModalTerminal = ({
dataDPELP={scenarios?.find(
(el) => el.title.toUpperCase() === "DPELP",
)}
+ userName={
+ user?.firstName
+ ? `${user.firstName} ${user.lastName || ""}`
+ : user?.userName || "Unknown User"
+ }
/>