Update config ram and check ram
This commit is contained in:
parent
20ef2732da
commit
0ce936b685
|
|
@ -0,0 +1,125 @@
|
||||||
|
import ConfigRam from '#models/config_ram'
|
||||||
|
import type { HttpContext } from '@adonisjs/core/http'
|
||||||
|
|
||||||
|
export default class ConfigRamController {
|
||||||
|
/**
|
||||||
|
* Display a list of resource
|
||||||
|
*/
|
||||||
|
async get({}: HttpContext) {
|
||||||
|
const configRams = await ConfigRam.all()
|
||||||
|
return { status: true, data: configRams }
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Display form to create a new record
|
||||||
|
*/
|
||||||
|
async create({ auth, request, response }: HttpContext) {
|
||||||
|
let payload = request.only([...Array.from(ConfigRam.$columnsDefinitions.keys())])
|
||||||
|
try {
|
||||||
|
// Check exist models
|
||||||
|
const inputModels: string[] = payload.models.map((s: string) => s.trim().toUpperCase())
|
||||||
|
const existedConfigs = await ConfigRam.query().select('id', 'models')
|
||||||
|
const duplicatedModels: string[] = []
|
||||||
|
for (const sc of existedConfigs) {
|
||||||
|
const scModels: string[] = JSON.parse(sc.models || '[]').map((s: string) =>
|
||||||
|
s.trim().toUpperCase()
|
||||||
|
)
|
||||||
|
for (const s of inputModels) {
|
||||||
|
if (scModels.includes(s)) {
|
||||||
|
duplicatedModels.push(s)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
if (duplicatedModels.length) {
|
||||||
|
return response.badRequest({
|
||||||
|
status: false,
|
||||||
|
message: 'Models already exists in another config',
|
||||||
|
duplicatedModels: [...new Set(duplicatedModels)],
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
|
const configRam = await ConfigRam.create({
|
||||||
|
...payload,
|
||||||
|
models: JSON.stringify(payload.models),
|
||||||
|
})
|
||||||
|
return response.created({
|
||||||
|
status: true,
|
||||||
|
message: 'Config created successfully',
|
||||||
|
data: configRam,
|
||||||
|
})
|
||||||
|
} catch (error) {
|
||||||
|
return response.badRequest({ error: error, message: 'Config create failed', status: false })
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
async update({ request, response, auth }: HttpContext) {
|
||||||
|
let payload = request.only(
|
||||||
|
Array.from(ConfigRam.$columnsDefinitions.keys()).filter(
|
||||||
|
(f) => f !== 'created_at' && f !== 'updated_at'
|
||||||
|
)
|
||||||
|
)
|
||||||
|
try {
|
||||||
|
const configRam = await ConfigRam.find(request.body().id)
|
||||||
|
if (!configRam) {
|
||||||
|
return response.status(404).json({ message: 'Config not found' })
|
||||||
|
}
|
||||||
|
|
||||||
|
// Check exist models
|
||||||
|
const inputModels: string[] = payload.models.map((s: string) => s.trim().toUpperCase())
|
||||||
|
const existedConfigRams = await ConfigRam.query().select('id', 'models')
|
||||||
|
const duplicatedModels: string[] = []
|
||||||
|
for (const sc of existedConfigRams) {
|
||||||
|
if (sc.id === configRam.id) continue
|
||||||
|
const scModels: string[] = JSON.parse(sc.models || '[]').map((s: string) =>
|
||||||
|
s.trim().toUpperCase()
|
||||||
|
)
|
||||||
|
|
||||||
|
for (const s of inputModels) {
|
||||||
|
if (scModels.includes(s)) {
|
||||||
|
duplicatedModels.push(s)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
if (duplicatedModels.length) {
|
||||||
|
return response.badRequest({
|
||||||
|
status: false,
|
||||||
|
message: 'Models already exists in another config',
|
||||||
|
duplicatedModels: [...new Set(duplicatedModels)],
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
|
Object.assign(configRam, { ...payload, models: JSON.stringify(payload.models) })
|
||||||
|
await configRam.save()
|
||||||
|
return response.ok({ status: true, message: 'Config update successfully', data: configRam })
|
||||||
|
} catch (error) {
|
||||||
|
return response.badRequest({ error: error, message: 'Config update failed', status: false })
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Delete record
|
||||||
|
*/
|
||||||
|
async delete({ auth, request, response }: HttpContext) {
|
||||||
|
try {
|
||||||
|
const configRam = await ConfigRam.find(request.body().id)
|
||||||
|
if (!configRam) {
|
||||||
|
return response.status(404).json({ message: 'Config not found' })
|
||||||
|
}
|
||||||
|
|
||||||
|
// Delete the configRam
|
||||||
|
await configRam.delete()
|
||||||
|
return response.ok({
|
||||||
|
status: true,
|
||||||
|
message: 'Config Ram delete successfully',
|
||||||
|
data: configRam,
|
||||||
|
})
|
||||||
|
} catch (error) {
|
||||||
|
return response.badRequest({
|
||||||
|
error: error,
|
||||||
|
message: 'Config Ram delete failed',
|
||||||
|
status: false,
|
||||||
|
})
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
@ -0,0 +1,22 @@
|
||||||
|
import { DateTime } from 'luxon'
|
||||||
|
import { BaseModel, column } from '@adonisjs/lucid/orm'
|
||||||
|
|
||||||
|
export default class ConfigRam extends BaseModel {
|
||||||
|
@column({ isPrimary: true })
|
||||||
|
declare id: number
|
||||||
|
|
||||||
|
@column()
|
||||||
|
declare models: string
|
||||||
|
|
||||||
|
@column()
|
||||||
|
declare ram: string
|
||||||
|
|
||||||
|
@column()
|
||||||
|
declare flash: string
|
||||||
|
|
||||||
|
@column.dateTime({ autoCreate: true })
|
||||||
|
declare createdAt: DateTime
|
||||||
|
|
||||||
|
@column.dateTime({ autoCreate: true, autoUpdate: true })
|
||||||
|
declare updatedAt: DateTime
|
||||||
|
}
|
||||||
|
|
@ -6,8 +6,10 @@ import {
|
||||||
buildBody,
|
buildBody,
|
||||||
classifyLog,
|
classifyLog,
|
||||||
cleanData,
|
cleanData,
|
||||||
|
detectConfigRamByModel,
|
||||||
detectScenarioByModel,
|
detectScenarioByModel,
|
||||||
escapeHtml,
|
escapeHtml,
|
||||||
|
isRamSufficient,
|
||||||
isValidJson,
|
isValidJson,
|
||||||
LogStreamBuffer,
|
LogStreamBuffer,
|
||||||
mapErrorsToRows,
|
mapErrorsToRows,
|
||||||
|
|
@ -29,6 +31,7 @@ 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'
|
||||||
import IosLicenseController from '#controllers/ios_license_controller'
|
import IosLicenseController from '#controllers/ios_license_controller'
|
||||||
|
import ConfigRam from '#models/config_ram'
|
||||||
|
|
||||||
type Inventory = {
|
type Inventory = {
|
||||||
pid: string
|
pid: string
|
||||||
|
|
@ -520,6 +523,9 @@ export default class LineConnection {
|
||||||
this.config.inventory = this.config.inventory
|
this.config.inventory = this.config.inventory
|
||||||
? { ...this.config.inventory, ...dataVer }
|
? { ...this.config.inventory, ...dataVer }
|
||||||
: dataVer
|
: dataVer
|
||||||
|
if (pid && dataVer?.MEMORY) {
|
||||||
|
await this.checkConfigRam(dataVer?.MEMORY, pid, cleanData(item.output))
|
||||||
|
}
|
||||||
}
|
}
|
||||||
if (
|
if (
|
||||||
item.command?.trim()?.includes('show env') ||
|
item.command?.trim()?.includes('show env') ||
|
||||||
|
|
@ -1591,4 +1597,24 @@ ${log}
|
||||||
}
|
}
|
||||||
return ''
|
return ''
|
||||||
}
|
}
|
||||||
|
|
||||||
|
async checkConfigRam(mem: string, pid: string, output: string) {
|
||||||
|
const configRam = await detectConfigRamByModel(pid)
|
||||||
|
if (configRam) {
|
||||||
|
const isWarningRAM = isRamSufficient(mem, configRam.ram)
|
||||||
|
if (isWarningRAM) {
|
||||||
|
const subject = `[ATC] - [${this.config.stationName} - Line: ${this.config.lineNumber}] - Warning RAM Configuration`
|
||||||
|
const body = `
|
||||||
|
<p>Station: <b>${this.config.stationName}</b></p>
|
||||||
|
<p>Line: <b>${this.config.lineNumber}</b></p>
|
||||||
|
<p>Model: <b>${pid}</b></p>
|
||||||
|
<p>RAM: <b>${mem + ' bytes'} (<span style="color: red;">default: ${configRam.ram}</span>)</b></p>
|
||||||
|
<hr />
|
||||||
|
<div style="white-space: break-spaces; background-color: #f5f5f5; color: black; padding: 8px; max-height: 500px; overflow-y: scroll; border: 1px #ccc solid;"><span style="color: black;">
|
||||||
|
${escapeHtml(output).replace('show ver', '').replace('sh ver', '').replace('show version', '').replace('sh version', '').replace(mem, `<span style="color: red;">${mem}</span>`)}</span></div>
|
||||||
|
`
|
||||||
|
await sendMessageToMail(subject, body)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -7,15 +7,16 @@ import { ErrorRow, LogRule, ParsedLog, TestError, TestResult } from './types.js'
|
||||||
import axios from 'axios'
|
import axios from 'axios'
|
||||||
import moment from 'moment'
|
import moment from 'moment'
|
||||||
import Station from '#models/station'
|
import Station from '#models/station'
|
||||||
|
import ConfigRam from '#models/config_ram'
|
||||||
|
|
||||||
const mailTo = 'andrew.ng@apactech.io'
|
const mailTo = 'andrew.ng@apactech.io'
|
||||||
const mailCC = [
|
// const mailCC = [
|
||||||
'ips@ipsupply.com.au',
|
// 'ips@ipsupply.com.au',
|
||||||
'kay@ipsupply.com.au',
|
// 'kay@ipsupply.com.au',
|
||||||
'joseph@apactech.io',
|
// 'joseph@apactech.io',
|
||||||
'kiet.phan@apactech.io',
|
// 'kiet.phan@apactech.io',
|
||||||
]
|
// ]
|
||||||
// const mailCC = ''
|
const mailCC = ''
|
||||||
|
|
||||||
type DetectAI = {
|
type DetectAI = {
|
||||||
status: string[]
|
status: string[]
|
||||||
|
|
@ -173,6 +174,8 @@ export function mapToLineFormat(input: InputData) {
|
||||||
sn: '',
|
sn: '',
|
||||||
ios: '',
|
ios: '',
|
||||||
mac: '',
|
mac: '',
|
||||||
|
ram: '',
|
||||||
|
flash: '',
|
||||||
license: [],
|
license: [],
|
||||||
issues: ['No data'],
|
issues: ['No data'],
|
||||||
summary: '',
|
summary: '',
|
||||||
|
|
@ -182,6 +185,8 @@ export function mapToLineFormat(input: InputData) {
|
||||||
// MAC
|
// MAC
|
||||||
let mac = ''
|
let mac = ''
|
||||||
let ios = ''
|
let ios = ''
|
||||||
|
let ram = ''
|
||||||
|
let flash = ''
|
||||||
const showVersion = input.data?.find(
|
const showVersion = input.data?.find(
|
||||||
(d) =>
|
(d) =>
|
||||||
d.command === 'show version' ||
|
d.command === 'show version' ||
|
||||||
|
|
@ -195,6 +200,12 @@ export function mapToLineFormat(input: InputData) {
|
||||||
if (showVersion?.textfsm?.[0]?.SOFTWARE_IMAGE) {
|
if (showVersion?.textfsm?.[0]?.SOFTWARE_IMAGE) {
|
||||||
ios = showVersion.textfsm[0].SOFTWARE_IMAGE + ' ' + (showVersion?.textfsm?.[0]?.VERSION || '')
|
ios = showVersion.textfsm[0].SOFTWARE_IMAGE + ' ' + (showVersion?.textfsm?.[0]?.VERSION || '')
|
||||||
}
|
}
|
||||||
|
if (showVersion?.textfsm?.[0]?.MEMORY) {
|
||||||
|
ram = showVersion.textfsm[0].MEMORY
|
||||||
|
}
|
||||||
|
if (showVersion?.textfsm?.[0]?.USB_FLASH) {
|
||||||
|
flash = showVersion.textfsm[0].USB_FLASH
|
||||||
|
}
|
||||||
|
|
||||||
// License
|
// License
|
||||||
const dataLicense = input.data?.find((comm) => comm.command?.trim() === 'show license')
|
const dataLicense = input.data?.find((comm) => comm.command?.trim() === 'show license')
|
||||||
|
|
@ -225,6 +236,8 @@ export function mapToLineFormat(input: InputData) {
|
||||||
sn,
|
sn,
|
||||||
ios,
|
ios,
|
||||||
mac,
|
mac,
|
||||||
|
ram,
|
||||||
|
flash,
|
||||||
license,
|
license,
|
||||||
issues,
|
issues,
|
||||||
summary,
|
summary,
|
||||||
|
|
@ -343,6 +356,32 @@ export const detectScenarioByModel = async (model: string, listScenarios: number
|
||||||
return matched?.scenario ? matched?.scenario : listScenarios.length === 0 ? scenarioDefault : null
|
return matched?.scenario ? matched?.scenario : listScenarios.length === 0 ? scenarioDefault : null
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// Catch scenario with key longer
|
||||||
|
export const detectConfigRamByModel = async (model: string) => {
|
||||||
|
let configsRam = await ConfigRam.query()
|
||||||
|
const normalizedModel = model.trim().toUpperCase()
|
||||||
|
let matched: { conf: ConfigRam; score: number } | null = null
|
||||||
|
|
||||||
|
for (const config of configsRam) {
|
||||||
|
const modelsList: string[] = Array.isArray(config.models)
|
||||||
|
? config.models
|
||||||
|
: JSON.parse(config.models || '[]')
|
||||||
|
|
||||||
|
for (const s of modelsList) {
|
||||||
|
const pattern = s.trim().toUpperCase()
|
||||||
|
if (normalizedModel.startsWith(pattern)) {
|
||||||
|
const score = pattern.length
|
||||||
|
|
||||||
|
if (!matched || score > matched.score) {
|
||||||
|
matched = { conf: config, score }
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return matched?.conf ? matched?.conf : null
|
||||||
|
}
|
||||||
|
|
||||||
export function classifyLog(line: string): ParsedLog {
|
export function classifyLog(line: string): ParsedLog {
|
||||||
if (/System Bootstrap|IOS XE Software|Booting/.test(line)) return { raw: line, category: 'BOOT' }
|
if (/System Bootstrap|IOS XE Software|Booting/.test(line)) return { raw: line, category: 'BOOT' }
|
||||||
|
|
||||||
|
|
@ -1403,3 +1442,37 @@ export async function checkStationActive(stationId: string): Promise<boolean> {
|
||||||
const station = await Station.find(stationId)
|
const station = await Station.find(stationId)
|
||||||
return station?.is_active || false
|
return station?.is_active || false
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// Kiểm tra RAM total lớn hơn RAM mặc định
|
||||||
|
export function isRamSufficient(deviceRam: string, defaultRamLimit: string): boolean {
|
||||||
|
if (!defaultRamLimit || !deviceRam) return false
|
||||||
|
|
||||||
|
const parts = deviceRam.split('/')
|
||||||
|
if (parts.length === 0) return false
|
||||||
|
|
||||||
|
const totalRamStr = parts[0].trim() // lấy phần total (thường là trước dấu '/')
|
||||||
|
const totalRamKB = convertToKilobytes(totalRamStr)
|
||||||
|
const defaultRamKB = convertToKilobytes(defaultRamLimit)
|
||||||
|
return totalRamKB >= defaultRamKB
|
||||||
|
}
|
||||||
|
|
||||||
|
export function convertToKilobytes(input: string): number {
|
||||||
|
const trimmed = input.trim().toUpperCase()
|
||||||
|
const match = trimmed.match(/^([\d.]+)\s*(K|M|G|T)?B?$/)
|
||||||
|
|
||||||
|
if (!match) {
|
||||||
|
return trimmed ? Number.parseFloat(trimmed) : 0
|
||||||
|
}
|
||||||
|
|
||||||
|
const value = Number.parseFloat(match[1])
|
||||||
|
const unit = match[2] || 'K' // default to KB if no unit
|
||||||
|
|
||||||
|
const unitMultipliers: { [key: string]: number } = {
|
||||||
|
K: 1,
|
||||||
|
M: 1024,
|
||||||
|
G: 1024 * 1024,
|
||||||
|
T: 1024 * 1024 * 1024,
|
||||||
|
}
|
||||||
|
|
||||||
|
return Math.round(value * unitMultipliers[unit])
|
||||||
|
}
|
||||||
|
|
|
||||||
|
|
@ -0,0 +1,19 @@
|
||||||
|
import { BaseSchema } from '@adonisjs/lucid/schema'
|
||||||
|
|
||||||
|
export default class extends BaseSchema {
|
||||||
|
protected tableName = 'config_rams'
|
||||||
|
|
||||||
|
async up() {
|
||||||
|
this.schema.createTable(this.tableName, (table) => {
|
||||||
|
table.increments('id')
|
||||||
|
table.text('models').notNullable()
|
||||||
|
table.string('ram')
|
||||||
|
table.string('flash')
|
||||||
|
table.timestamps()
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
|
async down() {
|
||||||
|
this.schema.dropTable(this.tableName)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
@ -112,3 +112,12 @@ router
|
||||||
router.get('/license', '#controllers/ios_license_controller.getLicense')
|
router.get('/license', '#controllers/ios_license_controller.getLicense')
|
||||||
})
|
})
|
||||||
.prefix('/api')
|
.prefix('/api')
|
||||||
|
|
||||||
|
router
|
||||||
|
.group(() => {
|
||||||
|
router.get('/', '#controllers/config_ram_controller.get')
|
||||||
|
router.post('/create', '#controllers/config_ram_controller.create')
|
||||||
|
router.post('/update', '#controllers/config_ram_controller.update')
|
||||||
|
router.post('/delete', '#controllers/config_ram_controller.delete')
|
||||||
|
})
|
||||||
|
.prefix('/api/config-ram')
|
||||||
|
|
|
||||||
|
|
@ -48,6 +48,7 @@ import type { Socket } from "socket.io-client";
|
||||||
import ModalHistory from "./Modal/ModalHistory";
|
import ModalHistory from "./Modal/ModalHistory";
|
||||||
import ModalConfig from "./Modal/ModalConfig";
|
import ModalConfig from "./Modal/ModalConfig";
|
||||||
import DrawerScenario from "./Modal/ModalScenario";
|
import DrawerScenario from "./Modal/ModalScenario";
|
||||||
|
import ModalConfigRamFlash from "./Modal/ModalConfigRamFlash";
|
||||||
|
|
||||||
interface DraggableTabsProps {
|
interface DraggableTabsProps {
|
||||||
tabsData: TStation[];
|
tabsData: TStation[];
|
||||||
|
|
@ -151,6 +152,7 @@ export default function DraggableTabs({
|
||||||
const [isHistoryModalOpen, setIsHistoryModalOpen] = useState<boolean>(false);
|
const [isHistoryModalOpen, setIsHistoryModalOpen] = useState<boolean>(false);
|
||||||
const [openConfig, setOpenConfig] = useState<boolean>(false);
|
const [openConfig, setOpenConfig] = useState<boolean>(false);
|
||||||
const [openDrawerScenario, setOpenDrawerScenario] = useState<boolean>(false);
|
const [openDrawerScenario, setOpenDrawerScenario] = useState<boolean>(false);
|
||||||
|
const [openConfigRam, setOpenConfigRam] = useState<boolean>(false);
|
||||||
|
|
||||||
const sensors = useSensors(useSensor(PointerSensor));
|
const sensors = useSensors(useSensor(PointerSensor));
|
||||||
|
|
||||||
|
|
@ -271,6 +273,15 @@ export default function DraggableTabs({
|
||||||
>
|
>
|
||||||
Scenario
|
Scenario
|
||||||
</Button>
|
</Button>
|
||||||
|
<Button
|
||||||
|
color="cyan"
|
||||||
|
variant="filled"
|
||||||
|
size="xs"
|
||||||
|
leftSection={<IconSettings size={16} />}
|
||||||
|
onClick={() => setOpenConfigRam(true)}
|
||||||
|
>
|
||||||
|
Config Ram
|
||||||
|
</Button>
|
||||||
</Flex>
|
</Flex>
|
||||||
<Tabs.List className={classes.list}>
|
<Tabs.List className={classes.list}>
|
||||||
<SortableContext
|
<SortableContext
|
||||||
|
|
@ -414,6 +425,11 @@ export default function DraggableTabs({
|
||||||
listBrands={listBrands}
|
listBrands={listBrands}
|
||||||
listCategories={listCategories}
|
listCategories={listCategories}
|
||||||
/>
|
/>
|
||||||
|
|
||||||
|
<ModalConfigRamFlash
|
||||||
|
opened={openConfigRam}
|
||||||
|
onClose={() => setOpenConfigRam(false)}
|
||||||
|
/>
|
||||||
</DndContext>
|
</DndContext>
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -0,0 +1,498 @@
|
||||||
|
import { useEffect, useState } from "react";
|
||||||
|
import {
|
||||||
|
Modal,
|
||||||
|
Button,
|
||||||
|
Grid,
|
||||||
|
ScrollArea,
|
||||||
|
Table,
|
||||||
|
Box,
|
||||||
|
Flex,
|
||||||
|
TextInput,
|
||||||
|
TagsInput,
|
||||||
|
Badge,
|
||||||
|
} from "@mantine/core";
|
||||||
|
import axios from "axios";
|
||||||
|
import type { ConfigRam } from "../../untils/types";
|
||||||
|
import { IconX } from "@tabler/icons-react";
|
||||||
|
import { notifications } from "@mantine/notifications";
|
||||||
|
import DialogConfirm from "../DialogConfirm";
|
||||||
|
|
||||||
|
const apiUrl = import.meta.env.VITE_BACKEND_URL;
|
||||||
|
|
||||||
|
interface Props {
|
||||||
|
opened: boolean;
|
||||||
|
onClose: () => void;
|
||||||
|
}
|
||||||
|
|
||||||
|
export default function ModalConfigRamFlash({ opened, onClose }: Props) {
|
||||||
|
const [configs, setConfigs] = useState<ConfigRam[]>([]);
|
||||||
|
const [dataConfig, setDataConfig] = useState<ConfigRam | null>(null);
|
||||||
|
const [newConfig, setNewConfig] = useState<ConfigRam>({
|
||||||
|
flash: "",
|
||||||
|
models: [],
|
||||||
|
ram: "",
|
||||||
|
});
|
||||||
|
const [inputModels, setInputModels] = useState<string>("");
|
||||||
|
const [disabled, setDisabled] = useState<boolean>(false);
|
||||||
|
const [openConfirm, setOpenConfirm] = useState<boolean>(false);
|
||||||
|
|
||||||
|
// get list config
|
||||||
|
const getListConfig = async () => {
|
||||||
|
try {
|
||||||
|
const response = await axios.get(apiUrl + "api/config-ram");
|
||||||
|
if (response.data.data && Array.isArray(response.data.data)) {
|
||||||
|
setConfigs(
|
||||||
|
response.data.data.map((item: ConfigRam) => ({
|
||||||
|
...item,
|
||||||
|
models:
|
||||||
|
typeof item.models === "string"
|
||||||
|
? JSON.parse(item.models)
|
||||||
|
: item.models,
|
||||||
|
}))
|
||||||
|
);
|
||||||
|
}
|
||||||
|
} catch (error) {
|
||||||
|
console.log("Error get config", error);
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
useEffect(() => {
|
||||||
|
if (opened) getListConfig();
|
||||||
|
}, [opened]);
|
||||||
|
|
||||||
|
const handleChange = (field: keyof ConfigRam, value: string | string[]) => {
|
||||||
|
if (!dataConfig) return;
|
||||||
|
setDataConfig({
|
||||||
|
...dataConfig,
|
||||||
|
[field]: value,
|
||||||
|
});
|
||||||
|
};
|
||||||
|
|
||||||
|
const handleAdd = async () => {
|
||||||
|
setDisabled(true);
|
||||||
|
try {
|
||||||
|
const response = await axios.post(apiUrl + `api/config-ram/create`, {
|
||||||
|
...newConfig,
|
||||||
|
models: newConfig.models,
|
||||||
|
});
|
||||||
|
|
||||||
|
// update local list
|
||||||
|
setConfigs((prev) => [
|
||||||
|
...prev,
|
||||||
|
{
|
||||||
|
...response.data.data,
|
||||||
|
models:
|
||||||
|
typeof response.data.data.models === "string"
|
||||||
|
? JSON.parse(response.data.data.models)
|
||||||
|
: response.data.data.models,
|
||||||
|
},
|
||||||
|
]);
|
||||||
|
notifications.show({
|
||||||
|
title: "Success",
|
||||||
|
message: response.data.message,
|
||||||
|
color: "green",
|
||||||
|
});
|
||||||
|
setNewConfig({
|
||||||
|
flash: "",
|
||||||
|
models: [],
|
||||||
|
ram: "",
|
||||||
|
});
|
||||||
|
setDisabled(false);
|
||||||
|
} catch (error) {
|
||||||
|
console.log("Error save config", error);
|
||||||
|
setDisabled(false);
|
||||||
|
if (axios.isAxiosError(error)) {
|
||||||
|
notifications.show({
|
||||||
|
title: "Error",
|
||||||
|
message: error?.response?.data?.message,
|
||||||
|
color: "red",
|
||||||
|
});
|
||||||
|
} else {
|
||||||
|
notifications.show({
|
||||||
|
title: "Error",
|
||||||
|
message: "Create fail, please try again!",
|
||||||
|
color: "red",
|
||||||
|
});
|
||||||
|
}
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
const handleSave = async () => {
|
||||||
|
setDisabled(true);
|
||||||
|
if (!dataConfig) return;
|
||||||
|
|
||||||
|
try {
|
||||||
|
const response = await axios.post(apiUrl + `api/config-ram/update`, {
|
||||||
|
...dataConfig,
|
||||||
|
models: dataConfig.models,
|
||||||
|
});
|
||||||
|
|
||||||
|
// update local list
|
||||||
|
setConfigs((prev) =>
|
||||||
|
prev.map((item) => (item.id === dataConfig.id ? dataConfig : item))
|
||||||
|
);
|
||||||
|
|
||||||
|
setDataConfig(null);
|
||||||
|
setDisabled(false);
|
||||||
|
notifications.show({
|
||||||
|
title: "Success",
|
||||||
|
message: response.data.message,
|
||||||
|
color: "green",
|
||||||
|
});
|
||||||
|
} catch (error) {
|
||||||
|
console.log("Error save config", error);
|
||||||
|
setDisabled(false);
|
||||||
|
if (axios.isAxiosError(error)) {
|
||||||
|
notifications.show({
|
||||||
|
title: "Error",
|
||||||
|
message: error?.response?.data?.message,
|
||||||
|
color: "red",
|
||||||
|
});
|
||||||
|
} else {
|
||||||
|
notifications.show({
|
||||||
|
title: "Error",
|
||||||
|
message: "Update fail, please try again!",
|
||||||
|
color: "red",
|
||||||
|
});
|
||||||
|
}
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
const handleDelete = async () => {
|
||||||
|
setDisabled(true);
|
||||||
|
if (!dataConfig) return;
|
||||||
|
|
||||||
|
try {
|
||||||
|
const response = await axios.post(apiUrl + `api/config-ram/delete`, {
|
||||||
|
...dataConfig,
|
||||||
|
});
|
||||||
|
|
||||||
|
// update local list
|
||||||
|
setConfigs((prev) => prev.filter((item) => item.id !== dataConfig.id));
|
||||||
|
|
||||||
|
setDataConfig(null);
|
||||||
|
setDisabled(false);
|
||||||
|
notifications.show({
|
||||||
|
title: "Success",
|
||||||
|
message: response.data.message,
|
||||||
|
color: "green",
|
||||||
|
});
|
||||||
|
} catch (error) {
|
||||||
|
console.log("Error save config", error);
|
||||||
|
setDisabled(false);
|
||||||
|
if (axios.isAxiosError(error)) {
|
||||||
|
notifications.show({
|
||||||
|
title: "Error",
|
||||||
|
message: error?.response?.data?.message,
|
||||||
|
color: "red",
|
||||||
|
});
|
||||||
|
} else {
|
||||||
|
notifications.show({
|
||||||
|
title: "Error",
|
||||||
|
message: "Delete fail, please try again!",
|
||||||
|
color: "red",
|
||||||
|
});
|
||||||
|
}
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
const handleCancel = () => {
|
||||||
|
setDataConfig(null);
|
||||||
|
};
|
||||||
|
|
||||||
|
const filteredConfigs = () => {
|
||||||
|
const listConfigs = [...configs].filter((el) => {
|
||||||
|
const models: string[] = el.models || [];
|
||||||
|
if (!models.length) return false;
|
||||||
|
return models.some((model) =>
|
||||||
|
model.toLowerCase().includes(inputModels.trim().toLowerCase())
|
||||||
|
);
|
||||||
|
});
|
||||||
|
return listConfigs;
|
||||||
|
};
|
||||||
|
|
||||||
|
return (
|
||||||
|
<Modal
|
||||||
|
size={"70%"}
|
||||||
|
style={{ position: "absolute", left: 0 }}
|
||||||
|
opened={opened}
|
||||||
|
onClose={onClose}
|
||||||
|
title="Config RAM Flash"
|
||||||
|
>
|
||||||
|
<Grid>
|
||||||
|
<Grid.Col span={12}>
|
||||||
|
<Box style={{ width: "300px", marginBottom: "10px" }}>
|
||||||
|
<TextInput
|
||||||
|
autoCapitalize="on"
|
||||||
|
label="Search model"
|
||||||
|
placeholder="Enter Model"
|
||||||
|
value={inputModels}
|
||||||
|
onChange={(e) => setInputModels(e.currentTarget.value)}
|
||||||
|
size="xs"
|
||||||
|
rightSection={
|
||||||
|
inputModels ? (
|
||||||
|
<IconX
|
||||||
|
size={14}
|
||||||
|
style={{ cursor: "pointer" }}
|
||||||
|
onClick={() => setInputModels("")}
|
||||||
|
/>
|
||||||
|
) : null
|
||||||
|
}
|
||||||
|
rightSectionPointerEvents="auto"
|
||||||
|
/>
|
||||||
|
</Box>
|
||||||
|
<hr />
|
||||||
|
<Flex
|
||||||
|
align={"center"}
|
||||||
|
justify={"space-between"}
|
||||||
|
style={{ marginBottom: "10px", marginTop: "10px" }}
|
||||||
|
>
|
||||||
|
<Flex align={"center"} gap={10}>
|
||||||
|
<TagsInput
|
||||||
|
autoCapitalize="words"
|
||||||
|
style={{ width: "500px" }}
|
||||||
|
label="Models"
|
||||||
|
placeholder="Enter models"
|
||||||
|
data={[]}
|
||||||
|
value={newConfig?.models || []}
|
||||||
|
onChange={(value: string[]) =>
|
||||||
|
setNewConfig({
|
||||||
|
...newConfig,
|
||||||
|
models: value.map((v) => v.toUpperCase()),
|
||||||
|
})
|
||||||
|
}
|
||||||
|
size="xs"
|
||||||
|
/>
|
||||||
|
<TextInput
|
||||||
|
label="Ram"
|
||||||
|
placeholder="Enter ram"
|
||||||
|
style={{ width: "200px" }}
|
||||||
|
value={newConfig?.ram}
|
||||||
|
onChange={(e) =>
|
||||||
|
setNewConfig({
|
||||||
|
...newConfig,
|
||||||
|
ram: e.currentTarget.value.toUpperCase(),
|
||||||
|
})
|
||||||
|
}
|
||||||
|
size="xs"
|
||||||
|
/>
|
||||||
|
<TextInput
|
||||||
|
autoCapitalize="characters"
|
||||||
|
label="Flash"
|
||||||
|
placeholder="Enter flash"
|
||||||
|
style={{ width: "200px" }}
|
||||||
|
value={newConfig?.flash}
|
||||||
|
onChange={(e) =>
|
||||||
|
setNewConfig({
|
||||||
|
...newConfig,
|
||||||
|
flash: e.currentTarget.value.toUpperCase(),
|
||||||
|
})
|
||||||
|
}
|
||||||
|
size="xs"
|
||||||
|
/>
|
||||||
|
</Flex>
|
||||||
|
<Button
|
||||||
|
disabled={
|
||||||
|
disabled || newConfig.models.length === 0 || !newConfig.ram
|
||||||
|
}
|
||||||
|
size="xs"
|
||||||
|
color="green"
|
||||||
|
onClick={() => handleAdd()}
|
||||||
|
variant="filled"
|
||||||
|
>
|
||||||
|
Add
|
||||||
|
</Button>
|
||||||
|
</Flex>
|
||||||
|
<Box>
|
||||||
|
<ScrollArea
|
||||||
|
h={"calc(75vh - 150px)"}
|
||||||
|
style={{ marginBottom: "20px" }}
|
||||||
|
>
|
||||||
|
<Table
|
||||||
|
stickyHeader
|
||||||
|
stickyHeaderOffset={-1}
|
||||||
|
striped
|
||||||
|
highlightOnHover
|
||||||
|
withRowBorders
|
||||||
|
withTableBorder
|
||||||
|
withColumnBorders
|
||||||
|
>
|
||||||
|
<Table.Thead style={{ zIndex: 100 }}>
|
||||||
|
<Table.Tr>
|
||||||
|
<Table.Th style={{ textAlign: "center" }}>Models</Table.Th>
|
||||||
|
<Table.Th w={150} style={{ textAlign: "center" }}>
|
||||||
|
Ram
|
||||||
|
</Table.Th>
|
||||||
|
<Table.Th w={150} style={{ textAlign: "center" }}>
|
||||||
|
Flash
|
||||||
|
</Table.Th>
|
||||||
|
<Table.Th w={200} style={{ textAlign: "center" }}>
|
||||||
|
Action
|
||||||
|
</Table.Th>
|
||||||
|
</Table.Tr>
|
||||||
|
</Table.Thead>
|
||||||
|
|
||||||
|
<Table.Tbody>
|
||||||
|
{filteredConfigs().length > 0 ? (
|
||||||
|
filteredConfigs().map((element) => {
|
||||||
|
const isEditing =
|
||||||
|
dataConfig?.id === element.id && !openConfirm;
|
||||||
|
|
||||||
|
return (
|
||||||
|
<Table.Tr key={element.id}>
|
||||||
|
{/* MODELS */}
|
||||||
|
<Table.Td>
|
||||||
|
{" "}
|
||||||
|
<Flex wrap={"wrap"} gap={"4px"}>
|
||||||
|
{isEditing ? (
|
||||||
|
<TagsInput
|
||||||
|
w={"100%"}
|
||||||
|
label=""
|
||||||
|
placeholder="Enter models"
|
||||||
|
data={[]}
|
||||||
|
value={dataConfig?.models || []}
|
||||||
|
onChange={(value: string[]) =>
|
||||||
|
handleChange(
|
||||||
|
"models",
|
||||||
|
value.map((v) => v.toUpperCase())
|
||||||
|
)
|
||||||
|
}
|
||||||
|
/>
|
||||||
|
) : element.models ? (
|
||||||
|
element.models?.map((el: string, i: number) => (
|
||||||
|
<Badge
|
||||||
|
key={i}
|
||||||
|
size="md"
|
||||||
|
color="orange"
|
||||||
|
variant="dot"
|
||||||
|
>
|
||||||
|
{el}
|
||||||
|
</Badge>
|
||||||
|
))
|
||||||
|
) : (
|
||||||
|
""
|
||||||
|
)}
|
||||||
|
</Flex>
|
||||||
|
</Table.Td>
|
||||||
|
|
||||||
|
{/* RAM */}
|
||||||
|
<Table.Td style={{ textAlign: "center" }}>
|
||||||
|
{isEditing ? (
|
||||||
|
<TextInput
|
||||||
|
value={dataConfig?.ram}
|
||||||
|
onChange={(e) =>
|
||||||
|
handleChange(
|
||||||
|
"ram",
|
||||||
|
e.currentTarget.value.toUpperCase()
|
||||||
|
)
|
||||||
|
}
|
||||||
|
size="xs"
|
||||||
|
/>
|
||||||
|
) : (
|
||||||
|
element.ram
|
||||||
|
)}
|
||||||
|
</Table.Td>
|
||||||
|
|
||||||
|
{/* FLASH */}
|
||||||
|
<Table.Td style={{ textAlign: "center" }}>
|
||||||
|
{isEditing ? (
|
||||||
|
<TextInput
|
||||||
|
value={dataConfig?.flash}
|
||||||
|
onChange={(e) =>
|
||||||
|
handleChange(
|
||||||
|
"flash",
|
||||||
|
e.currentTarget.value.toUpperCase()
|
||||||
|
)
|
||||||
|
}
|
||||||
|
size="xs"
|
||||||
|
/>
|
||||||
|
) : (
|
||||||
|
element.flash
|
||||||
|
)}
|
||||||
|
</Table.Td>
|
||||||
|
|
||||||
|
{/* ACTION */}
|
||||||
|
<Table.Td>
|
||||||
|
<Flex gap={10} justify="center">
|
||||||
|
{isEditing ? (
|
||||||
|
<>
|
||||||
|
<Button
|
||||||
|
disabled={disabled}
|
||||||
|
size="xs"
|
||||||
|
color="green"
|
||||||
|
onClick={handleSave}
|
||||||
|
variant="outline"
|
||||||
|
>
|
||||||
|
Save
|
||||||
|
</Button>
|
||||||
|
<Button
|
||||||
|
disabled={disabled}
|
||||||
|
size="xs"
|
||||||
|
color="gray"
|
||||||
|
onClick={handleCancel}
|
||||||
|
variant="outline"
|
||||||
|
>
|
||||||
|
Cancel
|
||||||
|
</Button>
|
||||||
|
</>
|
||||||
|
) : (
|
||||||
|
<>
|
||||||
|
<Button
|
||||||
|
disabled={disabled}
|
||||||
|
size="xs"
|
||||||
|
onClick={() =>
|
||||||
|
setDataConfig({ ...element })
|
||||||
|
}
|
||||||
|
variant="outline"
|
||||||
|
>
|
||||||
|
Edit
|
||||||
|
</Button>
|
||||||
|
<Button
|
||||||
|
disabled={disabled}
|
||||||
|
size="xs"
|
||||||
|
color={"red"}
|
||||||
|
onClick={() => {
|
||||||
|
setDataConfig({ ...element });
|
||||||
|
setOpenConfirm(true);
|
||||||
|
}}
|
||||||
|
variant="outline"
|
||||||
|
>
|
||||||
|
Delete
|
||||||
|
</Button>
|
||||||
|
</>
|
||||||
|
)}
|
||||||
|
</Flex>
|
||||||
|
</Table.Td>
|
||||||
|
</Table.Tr>
|
||||||
|
);
|
||||||
|
})
|
||||||
|
) : (
|
||||||
|
<Table.Tr>
|
||||||
|
<Table.Td colSpan={4} style={{ textAlign: "center" }}>
|
||||||
|
No configurations found.
|
||||||
|
</Table.Td>
|
||||||
|
</Table.Tr>
|
||||||
|
)}
|
||||||
|
</Table.Tbody>
|
||||||
|
</Table>
|
||||||
|
</ScrollArea>
|
||||||
|
</Box>
|
||||||
|
</Grid.Col>
|
||||||
|
</Grid>
|
||||||
|
|
||||||
|
<DialogConfirm
|
||||||
|
opened={openConfirm}
|
||||||
|
close={() => {
|
||||||
|
setDataConfig(null);
|
||||||
|
setOpenConfirm(false);
|
||||||
|
}}
|
||||||
|
message={"Are you sure delete this config?"}
|
||||||
|
handle={() => {
|
||||||
|
setOpenConfirm(false);
|
||||||
|
handleDelete();
|
||||||
|
}}
|
||||||
|
centered={true}
|
||||||
|
/>
|
||||||
|
</Modal>
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
@ -281,3 +281,10 @@ export type FileInfo = {
|
||||||
fileSize: number;
|
fileSize: number;
|
||||||
dateModify: string;
|
dateModify: string;
|
||||||
};
|
};
|
||||||
|
|
||||||
|
export type ConfigRam = {
|
||||||
|
id?: number;
|
||||||
|
models: string[];
|
||||||
|
ram: string;
|
||||||
|
flash: string;
|
||||||
|
};
|
||||||
|
|
|
||||||
Loading…
Reference in New Issue