refactor(模型关系): 重构模型关联关系定义位置,从模型文件移至路由文件
This commit is contained in:
@@ -641,7 +641,7 @@ SOFTWARE.
|
|||||||
- **Gitee Issues**: https://gitee.com/zhang96110/idc_assest/issues
|
- **Gitee Issues**: https://gitee.com/zhang96110/idc_assest/issues
|
||||||
- **GitHub Issues**: https://github.com/gituib/idc_assest/issues
|
- **GitHub Issues**: https://github.com/gituib/idc_assest/issues
|
||||||
- 功能建议:提交 Issue 并标注 `feature-request`
|
- 功能建议:提交 Issue 并标注 `feature-request`
|
||||||
|
- QQ群:1081123775
|
||||||
---
|
---
|
||||||
|
|
||||||
**⭐ 如果这个项目对您有帮助,请给我们一个 Star!**
|
**⭐ 如果这个项目对您有帮助,请给我们一个 Star!**
|
||||||
|
|||||||
@@ -1,5 +1,6 @@
|
|||||||
{
|
{
|
||||||
"type": "image",
|
"opacity": 0.8,
|
||||||
"image": null,
|
"blur": 5,
|
||||||
"size": "contain"
|
"value": "/images/bg.jpg",
|
||||||
|
"type": "image"
|
||||||
}
|
}
|
||||||
@@ -116,9 +116,30 @@ const authMiddleware = async (req, res, next) => {
|
|||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
const user = await User.findByPk(decoded.userId);
|
// 验证用户是否存在,增加详细的错误日志
|
||||||
|
let user;
|
||||||
|
try {
|
||||||
|
user = await User.findByPk(decoded.userId);
|
||||||
|
} catch (dbError) {
|
||||||
|
// 数据库查询错误
|
||||||
|
console.error('[认证中间件] 数据库查询失败:', {
|
||||||
|
userId: decoded.userId,
|
||||||
|
error: dbError.message,
|
||||||
|
stack: dbError.stack,
|
||||||
|
timestamp: new Date().toISOString()
|
||||||
|
});
|
||||||
|
return res.status(500).json({
|
||||||
|
success: false,
|
||||||
|
message: '数据库查询失败,请稍后重试'
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
if (!user) {
|
if (!user) {
|
||||||
|
console.warn('[认证中间件] 用户不存在:', {
|
||||||
|
userId: decoded.userId,
|
||||||
|
username: decoded.username,
|
||||||
|
timestamp: new Date().toISOString()
|
||||||
|
});
|
||||||
return res.status(401).json({
|
return res.status(401).json({
|
||||||
success: false,
|
success: false,
|
||||||
message: '用户不存在'
|
message: '用户不存在'
|
||||||
@@ -126,6 +147,11 @@ const authMiddleware = async (req, res, next) => {
|
|||||||
}
|
}
|
||||||
|
|
||||||
if (user.status === 'locked') {
|
if (user.status === 'locked') {
|
||||||
|
console.warn('[认证中间件] 账户已被锁定:', {
|
||||||
|
userId: user.userId,
|
||||||
|
username: user.username,
|
||||||
|
timestamp: new Date().toISOString()
|
||||||
|
});
|
||||||
return res.status(403).json({
|
return res.status(403).json({
|
||||||
success: false,
|
success: false,
|
||||||
message: '账户已被锁定'
|
message: '账户已被锁定'
|
||||||
@@ -133,6 +159,11 @@ const authMiddleware = async (req, res, next) => {
|
|||||||
}
|
}
|
||||||
|
|
||||||
if (user.status === 'inactive') {
|
if (user.status === 'inactive') {
|
||||||
|
console.warn('[认证中间件] 账户已禁用:', {
|
||||||
|
userId: user.userId,
|
||||||
|
username: user.username,
|
||||||
|
timestamp: new Date().toISOString()
|
||||||
|
});
|
||||||
return res.status(403).json({
|
return res.status(403).json({
|
||||||
success: false,
|
success: false,
|
||||||
message: '账户已禁用'
|
message: '账户已禁用'
|
||||||
@@ -143,10 +174,18 @@ const authMiddleware = async (req, res, next) => {
|
|||||||
req.userModel = user;
|
req.userModel = user;
|
||||||
next();
|
next();
|
||||||
} catch (error) {
|
} catch (error) {
|
||||||
console.error('认证中间件错误:', error);
|
// 捕获未预期的错误
|
||||||
|
console.error('[认证中间件] 未预期的错误:', {
|
||||||
|
error: error.message,
|
||||||
|
stack: error.stack,
|
||||||
|
url: req?.url,
|
||||||
|
method: req?.method,
|
||||||
|
ip: req?.ip,
|
||||||
|
timestamp: new Date().toISOString()
|
||||||
|
});
|
||||||
return res.status(500).json({
|
return res.status(500).json({
|
||||||
success: false,
|
success: false,
|
||||||
message: '认证失败'
|
message: '认证失败,请稍后重试'
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
@@ -160,7 +199,21 @@ const optionalAuth = async (req, res, next) => {
|
|||||||
const decoded = verifyToken(token);
|
const decoded = verifyToken(token);
|
||||||
|
|
||||||
if (decoded) {
|
if (decoded) {
|
||||||
const user = await User.findByPk(decoded.userId);
|
let user;
|
||||||
|
try {
|
||||||
|
user = await User.findByPk(decoded.userId);
|
||||||
|
} catch (dbError) {
|
||||||
|
// 数据库错误时不中断请求,仅记录日志
|
||||||
|
console.warn('[可选认证] 数据库查询失败:', {
|
||||||
|
userId: decoded.userId,
|
||||||
|
error: dbError.message,
|
||||||
|
timestamp: new Date().toISOString()
|
||||||
|
});
|
||||||
|
// 继续执行,不设置用户信息
|
||||||
|
next();
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
if (user && user.status === 'active') {
|
if (user && user.status === 'active') {
|
||||||
req.user = decoded;
|
req.user = decoded;
|
||||||
req.userModel = user;
|
req.userModel = user;
|
||||||
@@ -170,6 +223,12 @@ const optionalAuth = async (req, res, next) => {
|
|||||||
|
|
||||||
next();
|
next();
|
||||||
} catch (error) {
|
} catch (error) {
|
||||||
|
// 可选认证失败不影响主流程,仅记录日志
|
||||||
|
console.warn('[可选认证] 认证失败(已忽略):', {
|
||||||
|
error: error.message,
|
||||||
|
url: req?.url,
|
||||||
|
timestamp: new Date().toISOString()
|
||||||
|
});
|
||||||
next();
|
next();
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
|
|||||||
@@ -3,49 +3,54 @@ const validate = (schema, source = 'body') => {
|
|||||||
const data = source === 'query' ? req.query : req.body;
|
const data = source === 'query' ? req.query : req.body;
|
||||||
|
|
||||||
try {
|
try {
|
||||||
let result;
|
let value;
|
||||||
|
|
||||||
if (schema.validate && typeof schema.validate === 'function') {
|
if (schema.validate && typeof schema.validate === 'function') {
|
||||||
if (schema.validate.constructor.name === 'AsyncFunction' ||
|
const result = schema.validate(data, {
|
||||||
schema.validate.length === 1) {
|
abortEarly: false,
|
||||||
result = await schema.validate(data, {
|
stripUnknown: true,
|
||||||
abortEarly: false,
|
allowUnknown: source === 'query'
|
||||||
stripUnknown: true,
|
});
|
||||||
allowUnknown: source === 'query'
|
|
||||||
|
if (result && typeof result.then === 'function') {
|
||||||
|
value = await result;
|
||||||
|
} else if (result && result.error) {
|
||||||
|
const errorMessages = result.error.details.map(detail => ({
|
||||||
|
field: detail.path.join('.'),
|
||||||
|
message: detail.message
|
||||||
|
}));
|
||||||
|
return res.status(400).json({
|
||||||
|
error: '参数验证失败',
|
||||||
|
details: errorMessages
|
||||||
});
|
});
|
||||||
|
} else if (result && result.value !== undefined) {
|
||||||
|
value = result.value;
|
||||||
} else {
|
} else {
|
||||||
result = schema.validate(data, {
|
value = result;
|
||||||
abortEarly: false,
|
|
||||||
stripUnknown: true,
|
|
||||||
allowUnknown: source === 'query'
|
|
||||||
});
|
|
||||||
}
|
}
|
||||||
} else if (schema.validateAsync) {
|
} else if (schema.validateAsync) {
|
||||||
result = await schema.validateAsync(data, {
|
value = await schema.validateAsync(data, {
|
||||||
abortEarly: false,
|
abortEarly: false,
|
||||||
stripUnknown: true,
|
stripUnknown: true,
|
||||||
allowUnknown: source === 'query'
|
allowUnknown: source === 'query'
|
||||||
});
|
});
|
||||||
} else {
|
} else {
|
||||||
result = schema.validate(data, {
|
const result = schema.validate(data, {
|
||||||
abortEarly: false,
|
abortEarly: false,
|
||||||
stripUnknown: true,
|
stripUnknown: true,
|
||||||
allowUnknown: source === 'query'
|
allowUnknown: source === 'query'
|
||||||
});
|
});
|
||||||
}
|
if (result.error) {
|
||||||
|
const errorMessages = result.error.details.map(detail => ({
|
||||||
const { error, value } = result;
|
field: detail.path.join('.'),
|
||||||
|
message: detail.message
|
||||||
if (error) {
|
}));
|
||||||
const errorMessages = error.details.map(detail => ({
|
return res.status(400).json({
|
||||||
field: detail.path.join('.'),
|
error: '参数验证失败',
|
||||||
message: detail.message
|
details: errorMessages
|
||||||
}));
|
});
|
||||||
|
}
|
||||||
return res.status(400).json({
|
value = result.value;
|
||||||
error: '参数验证失败',
|
|
||||||
details: errorMessages
|
|
||||||
});
|
|
||||||
}
|
}
|
||||||
|
|
||||||
if (source === 'query') {
|
if (source === 'query') {
|
||||||
|
|||||||
@@ -1,6 +1,5 @@
|
|||||||
const { DataTypes } = require('sequelize');
|
const { DataTypes } = require('sequelize');
|
||||||
const { sequelize } = require('../db');
|
const { sequelize } = require('../db');
|
||||||
const Rack = require('./Rack');
|
|
||||||
|
|
||||||
const Device = sequelize.define('Device', {
|
const Device = sequelize.define('Device', {
|
||||||
deviceId: {
|
deviceId: {
|
||||||
@@ -28,11 +27,7 @@ const Device = sequelize.define('Device', {
|
|||||||
},
|
},
|
||||||
rackId: {
|
rackId: {
|
||||||
type: DataTypes.STRING,
|
type: DataTypes.STRING,
|
||||||
allowNull: true,
|
allowNull: true
|
||||||
references: {
|
|
||||||
model: Rack,
|
|
||||||
key: 'rackId'
|
|
||||||
}
|
|
||||||
},
|
},
|
||||||
position: {
|
position: {
|
||||||
type: DataTypes.INTEGER,
|
type: DataTypes.INTEGER,
|
||||||
@@ -86,7 +81,4 @@ const Device = sequelize.define('Device', {
|
|||||||
]
|
]
|
||||||
});
|
});
|
||||||
|
|
||||||
Device.belongsTo(Rack, { foreignKey: 'rackId' });
|
|
||||||
Rack.hasMany(Device, { foreignKey: 'rackId' });
|
|
||||||
|
|
||||||
module.exports = Device;
|
module.exports = Device;
|
||||||
|
|||||||
@@ -1,7 +1,5 @@
|
|||||||
const { DataTypes } = require('sequelize');
|
const { DataTypes } = require('sequelize');
|
||||||
const { sequelize } = require('../db');
|
const { sequelize } = require('../db');
|
||||||
const Device = require('./Device');
|
|
||||||
const NetworkCard = require('./NetworkCard');
|
|
||||||
|
|
||||||
const DevicePort = sequelize.define('DevicePort', {
|
const DevicePort = sequelize.define('DevicePort', {
|
||||||
portId: {
|
portId: {
|
||||||
@@ -67,10 +65,4 @@ const DevicePort = sequelize.define('DevicePort', {
|
|||||||
]
|
]
|
||||||
});
|
});
|
||||||
|
|
||||||
DevicePort.belongsTo(Device, { foreignKey: 'deviceId', as: 'device' });
|
|
||||||
Device.hasMany(DevicePort, { foreignKey: 'deviceId', as: 'ports' });
|
|
||||||
|
|
||||||
DevicePort.belongsTo(NetworkCard, { foreignKey: 'nicId', as: 'networkCard' });
|
|
||||||
NetworkCard.hasMany(DevicePort, { foreignKey: 'nicId', as: 'ports' });
|
|
||||||
|
|
||||||
module.exports = DevicePort;
|
module.exports = DevicePort;
|
||||||
|
|||||||
@@ -1,6 +1,5 @@
|
|||||||
const { DataTypes } = require('sequelize');
|
const { DataTypes } = require('sequelize');
|
||||||
const { sequelize } = require('../db');
|
const { sequelize } = require('../db');
|
||||||
const Device = require('./Device');
|
|
||||||
|
|
||||||
const NetworkCard = sequelize.define('NetworkCard', {
|
const NetworkCard = sequelize.define('NetworkCard', {
|
||||||
nicId: {
|
nicId: {
|
||||||
@@ -63,7 +62,4 @@ const NetworkCard = sequelize.define('NetworkCard', {
|
|||||||
]
|
]
|
||||||
});
|
});
|
||||||
|
|
||||||
NetworkCard.belongsTo(Device, { foreignKey: 'deviceId', as: 'device' });
|
|
||||||
Device.hasMany(NetworkCard, { foreignKey: 'deviceId', as: 'networkCards' });
|
|
||||||
|
|
||||||
module.exports = NetworkCard;
|
module.exports = NetworkCard;
|
||||||
|
|||||||
@@ -3,13 +3,11 @@ const path = require('path');
|
|||||||
const fs = require('fs');
|
const fs = require('fs');
|
||||||
const router = express.Router();
|
const router = express.Router();
|
||||||
|
|
||||||
// 确保上传目录存在
|
|
||||||
const UPLOAD_DIR = path.join(__dirname, '../uploads');
|
const UPLOAD_DIR = path.join(__dirname, '../uploads');
|
||||||
if (!fs.existsSync(UPLOAD_DIR)) {
|
if (!fs.existsSync(UPLOAD_DIR)) {
|
||||||
fs.mkdirSync(UPLOAD_DIR, { recursive: true });
|
fs.mkdirSync(UPLOAD_DIR, { recursive: true });
|
||||||
}
|
}
|
||||||
|
|
||||||
// 确保背景设置文件存在
|
|
||||||
const SETTINGS_FILE = path.join(__dirname, '../backgroundSettings.json');
|
const SETTINGS_FILE = path.join(__dirname, '../backgroundSettings.json');
|
||||||
if (!fs.existsSync(SETTINGS_FILE)) {
|
if (!fs.existsSync(SETTINGS_FILE)) {
|
||||||
fs.writeFileSync(SETTINGS_FILE, JSON.stringify({
|
fs.writeFileSync(SETTINGS_FILE, JSON.stringify({
|
||||||
@@ -19,7 +17,33 @@ if (!fs.existsSync(SETTINGS_FILE)) {
|
|||||||
}, null, 2));
|
}, null, 2));
|
||||||
}
|
}
|
||||||
|
|
||||||
// 上传图片接口
|
router.get('/', (req, res) => {
|
||||||
|
try {
|
||||||
|
const settings = JSON.parse(fs.readFileSync(SETTINGS_FILE, 'utf8'));
|
||||||
|
res.json({
|
||||||
|
success: true,
|
||||||
|
data: settings
|
||||||
|
});
|
||||||
|
} catch (error) {
|
||||||
|
console.error('读取背景设置失败:', error);
|
||||||
|
res.status(500).json({ error: '读取背景设置失败' });
|
||||||
|
}
|
||||||
|
});
|
||||||
|
|
||||||
|
router.put('/', (req, res) => {
|
||||||
|
try {
|
||||||
|
const settings = req.body;
|
||||||
|
fs.writeFileSync(SETTINGS_FILE, JSON.stringify(settings, null, 2));
|
||||||
|
res.json({
|
||||||
|
success: true,
|
||||||
|
data: settings
|
||||||
|
});
|
||||||
|
} catch (error) {
|
||||||
|
console.error('保存背景设置失败:', error);
|
||||||
|
res.status(500).json({ error: '保存背景设置失败' });
|
||||||
|
}
|
||||||
|
});
|
||||||
|
|
||||||
router.post('/upload', (req, res) => {
|
router.post('/upload', (req, res) => {
|
||||||
try {
|
try {
|
||||||
if (!req.files || !req.files.file) {
|
if (!req.files || !req.files.file) {
|
||||||
@@ -30,14 +54,12 @@ router.post('/upload', (req, res) => {
|
|||||||
const fileName = `${Date.now()}_${file.name}`;
|
const fileName = `${Date.now()}_${file.name}`;
|
||||||
const filePath = path.join(UPLOAD_DIR, fileName);
|
const filePath = path.join(UPLOAD_DIR, fileName);
|
||||||
|
|
||||||
// 保存文件到服务器
|
|
||||||
file.mv(filePath, (err) => {
|
file.mv(filePath, (err) => {
|
||||||
if (err) {
|
if (err) {
|
||||||
console.error('文件保存失败:', err);
|
console.error('文件保存失败:', err);
|
||||||
return res.status(500).json({ error: '文件保存失败' });
|
return res.status(500).json({ error: '文件保存失败' });
|
||||||
}
|
}
|
||||||
|
|
||||||
// 返回文件路径
|
|
||||||
const fileUrl = `/uploads/${fileName}`;
|
const fileUrl = `/uploads/${fileName}`;
|
||||||
res.json({ path: fileUrl });
|
res.json({ path: fileUrl });
|
||||||
});
|
});
|
||||||
@@ -47,7 +69,6 @@ router.post('/upload', (req, res) => {
|
|||||||
}
|
}
|
||||||
});
|
});
|
||||||
|
|
||||||
// 获取背景设置
|
|
||||||
router.get('/settings', (req, res) => {
|
router.get('/settings', (req, res) => {
|
||||||
try {
|
try {
|
||||||
const settings = JSON.parse(fs.readFileSync(SETTINGS_FILE, 'utf8'));
|
const settings = JSON.parse(fs.readFileSync(SETTINGS_FILE, 'utf8'));
|
||||||
@@ -58,7 +79,6 @@ router.get('/settings', (req, res) => {
|
|||||||
}
|
}
|
||||||
});
|
});
|
||||||
|
|
||||||
// 保存背景设置
|
|
||||||
router.post('/settings', (req, res) => {
|
router.post('/settings', (req, res) => {
|
||||||
try {
|
try {
|
||||||
const settings = req.body;
|
const settings = req.body;
|
||||||
|
|||||||
@@ -3,6 +3,11 @@ const router = express.Router();
|
|||||||
const { Op } = require('sequelize');
|
const { Op } = require('sequelize');
|
||||||
const DevicePort = require('../models/DevicePort');
|
const DevicePort = require('../models/DevicePort');
|
||||||
const Device = require('../models/Device');
|
const Device = require('../models/Device');
|
||||||
|
const NetworkCard = require('../models/NetworkCard');
|
||||||
|
|
||||||
|
DevicePort.belongsTo(Device, { foreignKey: 'deviceId', as: 'device' });
|
||||||
|
Device.hasMany(DevicePort, { foreignKey: 'deviceId', as: 'ports' });
|
||||||
|
DevicePort.belongsTo(NetworkCard, { foreignKey: 'nicId', as: 'networkCard' });
|
||||||
|
|
||||||
router.get('/', async (req, res) => {
|
router.get('/', async (req, res) => {
|
||||||
try {
|
try {
|
||||||
|
|||||||
+33
-23
@@ -1,7 +1,7 @@
|
|||||||
const express = require('express');
|
const express = require('express');
|
||||||
const router = express.Router();
|
const router = express.Router();
|
||||||
const { Op } = require('sequelize');
|
const { Op } = require('sequelize');
|
||||||
const { sequelize, dbDialect } = require('../db'); // Import sequelize and dbDialect for transactions
|
const { sequelize, dbDialect } = require('../db');
|
||||||
const fs = require('fs');
|
const fs = require('fs');
|
||||||
const path = require('path');
|
const path = require('path');
|
||||||
const csv = require('csv-parser');
|
const csv = require('csv-parser');
|
||||||
@@ -12,9 +12,9 @@ const Rack = require('../models/Rack');
|
|||||||
const Room = require('../models/Room');
|
const Room = require('../models/Room');
|
||||||
const DeviceField = require('../models/DeviceField');
|
const DeviceField = require('../models/DeviceField');
|
||||||
const Ticket = require('../models/Ticket');
|
const Ticket = require('../models/Ticket');
|
||||||
const DevicePort = require('../models/DevicePort'); // Import DevicePort
|
const DevicePort = require('../models/DevicePort');
|
||||||
const Cable = require('../models/Cable'); // Import Cable
|
const Cable = require('../models/Cable');
|
||||||
const NetworkCard = require('../models/NetworkCard'); // Import NetworkCard
|
const NetworkCard = require('../models/NetworkCard');
|
||||||
const { validateBody, validateQuery } = require('../middleware/validation');
|
const { validateBody, validateQuery } = require('../middleware/validation');
|
||||||
const {
|
const {
|
||||||
createDeviceSchema,
|
createDeviceSchema,
|
||||||
@@ -25,7 +25,9 @@ const {
|
|||||||
queryDeviceSchema
|
queryDeviceSchema
|
||||||
} = require('../validation/deviceSchema');
|
} = require('../validation/deviceSchema');
|
||||||
|
|
||||||
// 获取所有设备(支持搜索和筛选)
|
Device.belongsTo(Rack, { foreignKey: 'rackId' });
|
||||||
|
Rack.hasMany(Device, { foreignKey: 'rackId' });
|
||||||
|
|
||||||
router.get('/', validateQuery(queryDeviceSchema), async (req, res) => {
|
router.get('/', validateQuery(queryDeviceSchema), async (req, res) => {
|
||||||
try {
|
try {
|
||||||
const { keyword, status, type, rackId, page = 1, pageSize = 10 } = req.query;
|
const { keyword, status, type, rackId, page = 1, pageSize = 10 } = req.query;
|
||||||
@@ -772,6 +774,16 @@ router.post('/import', async (req, res) => {
|
|||||||
} catch (error) {
|
} catch (error) {
|
||||||
await t.rollback();
|
await t.rollback();
|
||||||
console.error('导入设备数据失败:', error);
|
console.error('导入设备数据失败:', error);
|
||||||
|
|
||||||
|
// 清理临时文件
|
||||||
|
try {
|
||||||
|
if (filePath && fs.existsSync(filePath)) {
|
||||||
|
fs.unlinkSync(filePath);
|
||||||
|
}
|
||||||
|
} catch (fileErr) {
|
||||||
|
console.error('删除临时文件失败:', fileErr);
|
||||||
|
}
|
||||||
|
|
||||||
res.status(500).json({
|
res.status(500).json({
|
||||||
errors: [{ row: 0, error: error.message || '导入过程中发生未知错误' }]
|
errors: [{ row: 0, error: error.message || '导入过程中发生未知错误' }]
|
||||||
});
|
});
|
||||||
@@ -1224,7 +1236,22 @@ router.delete('/:deviceId', async (req, res) => {
|
|||||||
{ where: { deviceId: deviceId }, transaction: t }
|
{ where: { deviceId: deviceId }, transaction: t }
|
||||||
);
|
);
|
||||||
|
|
||||||
// 5. 删除设备 (Delete Device)
|
// 5. 更新机柜功率 (必须在删除设备之前)
|
||||||
|
if (device.rackId) {
|
||||||
|
try {
|
||||||
|
const rack = await Rack.findByPk(device.rackId, { transaction: t });
|
||||||
|
if (rack) {
|
||||||
|
await rack.update({
|
||||||
|
currentPower: Math.max(0, rack.currentPower - device.powerConsumption)
|
||||||
|
}, { transaction: t });
|
||||||
|
}
|
||||||
|
} catch (err) {
|
||||||
|
console.error('更新机柜功率失败:', err);
|
||||||
|
throw err; // 重新抛出错误,触发事务回滚
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// 6. 删除设备 (Delete Device)
|
||||||
await Device.destroy({
|
await Device.destroy({
|
||||||
where: { deviceId: deviceId },
|
where: { deviceId: deviceId },
|
||||||
transaction: t
|
transaction: t
|
||||||
@@ -1237,23 +1264,6 @@ router.delete('/:deviceId', async (req, res) => {
|
|||||||
console.log(`已删除 ${deletedCables} 条相关接线`);
|
console.log(`已删除 ${deletedCables} 条相关接线`);
|
||||||
}
|
}
|
||||||
|
|
||||||
// 更新机柜功率 (Update Rack power)
|
|
||||||
// 注意:设备已删除,不需要再减去功率?或者需要?
|
|
||||||
// 原逻辑是:rack.currentPower - device.powerConsumption
|
|
||||||
// 既然设备已经物理删除了,机柜的当前功率确实应该减少。
|
|
||||||
if (device.rackId) {
|
|
||||||
try {
|
|
||||||
const rack = await Rack.findByPk(device.rackId);
|
|
||||||
if (rack) {
|
|
||||||
await rack.update({
|
|
||||||
currentPower: Math.max(0, rack.currentPower - device.powerConsumption)
|
|
||||||
});
|
|
||||||
}
|
|
||||||
} catch (err) {
|
|
||||||
console.error('更新机柜功率失败:', err);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
res.status(200).json({
|
res.status(200).json({
|
||||||
message: '删除成功',
|
message: '删除成功',
|
||||||
deviceId: deviceId,
|
deviceId: deviceId,
|
||||||
|
|||||||
@@ -5,6 +5,10 @@ const NetworkCard = require('../models/NetworkCard');
|
|||||||
const Device = require('../models/Device');
|
const Device = require('../models/Device');
|
||||||
const DevicePort = require('../models/DevicePort');
|
const DevicePort = require('../models/DevicePort');
|
||||||
|
|
||||||
|
NetworkCard.belongsTo(Device, { foreignKey: 'deviceId', as: 'device' });
|
||||||
|
Device.hasMany(NetworkCard, { foreignKey: 'deviceId', as: 'networkCards' });
|
||||||
|
NetworkCard.hasMany(DevicePort, { foreignKey: 'nicId', as: 'ports' });
|
||||||
|
|
||||||
router.get('/', async (req, res) => {
|
router.get('/', async (req, res) => {
|
||||||
try {
|
try {
|
||||||
const { deviceId } = req.query;
|
const { deviceId } = req.query;
|
||||||
|
|||||||
@@ -180,6 +180,37 @@ app.use('/api/inventory', inventoryRoutes);
|
|||||||
|
|
||||||
app.use('/uploads', express.static('uploads'));
|
app.use('/uploads', express.static('uploads'));
|
||||||
|
|
||||||
|
app.get('/api', (req, res) => {
|
||||||
|
res.json({
|
||||||
|
name: 'IDC设备管理系统 API',
|
||||||
|
version: '1.0.0',
|
||||||
|
description: '数据中心设备管理平台后端服务',
|
||||||
|
endpoints: {
|
||||||
|
auth: '/api/auth',
|
||||||
|
rooms: '/api/rooms',
|
||||||
|
racks: '/api/racks',
|
||||||
|
devices: '/api/devices',
|
||||||
|
deviceFields: '/api/deviceFields',
|
||||||
|
devicePorts: '/api/device-ports',
|
||||||
|
networkCards: '/api/network-cards',
|
||||||
|
cables: '/api/cables',
|
||||||
|
tickets: '/api/tickets',
|
||||||
|
ticketCategories: '/api/ticket-categories',
|
||||||
|
ticketFields: '/api/ticket-fields',
|
||||||
|
consumables: '/api/consumables',
|
||||||
|
consumableRecords: '/api/consumable-records',
|
||||||
|
consumableCategories: '/api/consumable-categories',
|
||||||
|
users: '/api/users',
|
||||||
|
roles: '/api/roles',
|
||||||
|
systemSettings: '/api/system-settings',
|
||||||
|
background: '/api/background',
|
||||||
|
inventory: '/api/inventory'
|
||||||
|
},
|
||||||
|
health: '/health',
|
||||||
|
documentation: '/docs/api/README.md'
|
||||||
|
});
|
||||||
|
});
|
||||||
|
|
||||||
app.get('/health', (req, res) => {
|
app.get('/health', (req, res) => {
|
||||||
res.json({ status: 'ok', message: 'IDC设备管理系统后端服务正常运行' });
|
res.json({ status: 'ok', message: 'IDC设备管理系统后端服务正常运行' });
|
||||||
});
|
});
|
||||||
|
|||||||
@@ -1,168 +1,61 @@
|
|||||||
const Joi = require('joi');
|
const Joi = require('joi');
|
||||||
const DeviceField = require('../models/DeviceField');
|
|
||||||
|
|
||||||
const DEVICE_TYPES = ['server', 'switch', 'router', 'storage', 'other'];
|
const DEVICE_TYPES = ['server', 'switch', 'router', 'storage', 'other'];
|
||||||
const DEVICE_STATUS = ['running', 'maintenance', 'offline', 'fault'];
|
const DEVICE_STATUS = ['running', 'maintenance', 'offline', 'fault'];
|
||||||
|
|
||||||
const baseFieldSchemas = {
|
const createDeviceSchema = Joi.object({
|
||||||
deviceId: Joi.string()
|
name: Joi.string().required().max(100).messages({
|
||||||
.max(50)
|
'string.empty': '设备名称不能为空',
|
||||||
.pattern(/^[a-zA-Z0-9_-]+$/)
|
'string.max': '设备名称不能超过100个字符',
|
||||||
.allow('', null)
|
'any.required': '设备名称是必填字段'
|
||||||
.messages({
|
}),
|
||||||
'string.max': '设备ID不能超过50个字符',
|
type: Joi.string().required().valid(...DEVICE_TYPES).messages({
|
||||||
'string.pattern.base': '设备ID只能包含字母、数字、下划线和横线'
|
'any.only': `设备类型必须是以下之一: ${DEVICE_TYPES.join(', ')}`,
|
||||||
}),
|
'any.required': '设备类型是必填字段'
|
||||||
|
}),
|
||||||
name: Joi.string()
|
model: Joi.string().allow('', null).max(100),
|
||||||
.max(100)
|
serialNumber: Joi.string().required().max(100).messages({
|
||||||
.messages({
|
'string.empty': '序列号不能为空',
|
||||||
'string.empty': '设备名称不能为空',
|
'string.max': '序列号不能超过100个字符',
|
||||||
'string.max': '设备名称不能超过100个字符'
|
'any.required': '序列号是必填字段'
|
||||||
}),
|
}),
|
||||||
|
rackId: Joi.string().allow('', null).max(50),
|
||||||
type: Joi.string()
|
position: Joi.number().integer().min(1).max(100).allow(null),
|
||||||
.valid(...DEVICE_TYPES)
|
height: Joi.number().integer().min(1).max(50).allow(null),
|
||||||
.messages({
|
powerConsumption: Joi.number().min(0).max(100000).allow(null),
|
||||||
'string.empty': '设备类型不能为空',
|
ipAddress: Joi.string().allow('', null).max(50),
|
||||||
'any.only': `设备类型必须是以下之一: ${DEVICE_TYPES.join(', ')}`
|
status: Joi.string().valid(...DEVICE_STATUS).default('offline'),
|
||||||
}),
|
purchaseDate: Joi.date().allow(null),
|
||||||
|
warrantyExpiry: Joi.date().allow(null),
|
||||||
model: Joi.string()
|
description: Joi.string().allow('', null).max(500),
|
||||||
.max(100)
|
|
||||||
.allow('', null)
|
|
||||||
.messages({
|
|
||||||
'string.max': '型号不能超过100个字符'
|
|
||||||
}),
|
|
||||||
|
|
||||||
serialNumber: Joi.string()
|
|
||||||
.max(100)
|
|
||||||
.messages({
|
|
||||||
'string.empty': '序列号不能为空',
|
|
||||||
'string.max': '序列号不能超过100个字符'
|
|
||||||
}),
|
|
||||||
|
|
||||||
rackId: Joi.string()
|
|
||||||
.max(50)
|
|
||||||
.messages({
|
|
||||||
'string.empty': '机柜ID不能为空',
|
|
||||||
'string.max': '机柜ID不能超过50个字符'
|
|
||||||
}),
|
|
||||||
|
|
||||||
position: Joi.number()
|
|
||||||
.integer()
|
|
||||||
.min(1)
|
|
||||||
.max(100)
|
|
||||||
.messages({
|
|
||||||
'number.base': '位置必须是数字',
|
|
||||||
'number.integer': '位置必须是整数',
|
|
||||||
'number.min': '位置不能小于1',
|
|
||||||
'number.max': '位置不能大于100'
|
|
||||||
}),
|
|
||||||
|
|
||||||
height: Joi.number()
|
|
||||||
.integer()
|
|
||||||
.min(1)
|
|
||||||
.max(50)
|
|
||||||
.messages({
|
|
||||||
'number.base': '高度必须是数字',
|
|
||||||
'number.integer': '高度必须是整数',
|
|
||||||
'number.min': '高度不能小于1',
|
|
||||||
'number.max': '高度不能大于50'
|
|
||||||
}),
|
|
||||||
|
|
||||||
powerConsumption: Joi.number()
|
|
||||||
.min(0)
|
|
||||||
.max(100000)
|
|
||||||
.messages({
|
|
||||||
'number.base': '功率必须是数字',
|
|
||||||
'number.min': '功率不能小于0',
|
|
||||||
'number.max': '功率不能超过100000'
|
|
||||||
}),
|
|
||||||
|
|
||||||
ipAddress: Joi.string()
|
|
||||||
.ip({ version: ['ipv4', 'ipv6'] })
|
|
||||||
.allow('', null)
|
|
||||||
.messages({
|
|
||||||
'string.ip': 'IP地址格式无效'
|
|
||||||
}),
|
|
||||||
|
|
||||||
status: Joi.string()
|
|
||||||
.valid(...DEVICE_STATUS)
|
|
||||||
.default('offline')
|
|
||||||
.messages({
|
|
||||||
'any.only': `状态必须是以下之一: ${DEVICE_STATUS.join(', ')}`
|
|
||||||
}),
|
|
||||||
|
|
||||||
purchaseDate: Joi.date()
|
|
||||||
.allow(null)
|
|
||||||
.messages({
|
|
||||||
'date.base': '购买日期格式无效'
|
|
||||||
}),
|
|
||||||
|
|
||||||
warrantyExpiry: Joi.date()
|
|
||||||
.allow(null)
|
|
||||||
.messages({
|
|
||||||
'date.base': '保修到期日期格式无效'
|
|
||||||
}),
|
|
||||||
|
|
||||||
description: Joi.string()
|
|
||||||
.max(500)
|
|
||||||
.allow('', null)
|
|
||||||
.messages({
|
|
||||||
'string.max': '描述不能超过500个字符'
|
|
||||||
}),
|
|
||||||
|
|
||||||
customFields: Joi.object().allow(null)
|
customFields: Joi.object().allow(null)
|
||||||
};
|
});
|
||||||
|
|
||||||
async function buildDynamicSchema(isCreate = true) {
|
const updateDeviceSchema = Joi.object({
|
||||||
const fields = await DeviceField.findAll({
|
name: Joi.string().max(100).messages({
|
||||||
where: { isSystem: true },
|
'string.empty': '设备名称不能为空',
|
||||||
order: [['order', 'ASC']]
|
'string.max': '设备名称不能超过100个字符'
|
||||||
});
|
}),
|
||||||
|
type: Joi.string().valid(...DEVICE_TYPES).messages({
|
||||||
const schemaObj = {};
|
'any.only': `设备类型必须是以下之一: ${DEVICE_TYPES.join(', ')}`
|
||||||
|
}),
|
||||||
fields.forEach(field => {
|
model: Joi.string().allow('', null).max(100),
|
||||||
const baseSchema = baseFieldSchemas[field.fieldName];
|
serialNumber: Joi.string().max(100).messages({
|
||||||
if (baseSchema) {
|
'string.max': '序列号不能超过100个字符'
|
||||||
let fieldSchema = baseSchema.clone();
|
}),
|
||||||
|
rackId: Joi.string().allow('', null).max(50),
|
||||||
if (field.required && isCreate) {
|
position: Joi.number().integer().min(1).max(100).allow(null),
|
||||||
fieldSchema = fieldSchema.required();
|
height: Joi.number().integer().min(1).max(50).allow(null),
|
||||||
}
|
powerConsumption: Joi.number().min(0).max(100000).allow(null),
|
||||||
|
ipAddress: Joi.string().allow('', null).max(50),
|
||||||
schemaObj[field.fieldName] = fieldSchema;
|
status: Joi.string().valid(...DEVICE_STATUS),
|
||||||
}
|
purchaseDate: Joi.date().allow(null),
|
||||||
});
|
warrantyExpiry: Joi.date().allow(null),
|
||||||
|
description: Joi.string().allow('', null).max(500),
|
||||||
schemaObj.customFields = baseFieldSchemas.customFields;
|
customFields: Joi.object().allow(null)
|
||||||
|
}).min(1).messages({
|
||||||
return Joi.object(schemaObj).custom((value, helpers) => {
|
'object.min': '至少需要提供一个字段进行更新'
|
||||||
if (value.purchaseDate && value.warrantyExpiry) {
|
});
|
||||||
const purchase = new Date(value.purchaseDate);
|
|
||||||
const warranty = new Date(value.warrantyExpiry);
|
|
||||||
if (warranty <= purchase) {
|
|
||||||
return helpers.error('date.warrantyAfterPurchase');
|
|
||||||
}
|
|
||||||
}
|
|
||||||
return value;
|
|
||||||
}).messages({
|
|
||||||
'date.warrantyAfterPurchase': '保修到期日期必须晚于购买日期'
|
|
||||||
});
|
|
||||||
}
|
|
||||||
|
|
||||||
async function getCreateDeviceSchema() {
|
|
||||||
return buildDynamicSchema(true);
|
|
||||||
}
|
|
||||||
|
|
||||||
async function getUpdateDeviceSchema() {
|
|
||||||
const schema = await buildDynamicSchema(false);
|
|
||||||
return schema.min(1).messages({
|
|
||||||
'object.min': '至少需要提供一个字段进行更新'
|
|
||||||
});
|
|
||||||
}
|
|
||||||
|
|
||||||
const batchDeviceIdsSchema = Joi.object({
|
const batchDeviceIdsSchema = Joi.object({
|
||||||
deviceIds: Joi.array()
|
deviceIds: Joi.array()
|
||||||
@@ -233,24 +126,10 @@ const queryDeviceSchema = Joi.object({
|
|||||||
pageSize: Joi.number()
|
pageSize: Joi.number()
|
||||||
.integer()
|
.integer()
|
||||||
.min(1)
|
.min(1)
|
||||||
.max(100)
|
.max(10000)
|
||||||
.default(10)
|
.default(10)
|
||||||
});
|
});
|
||||||
|
|
||||||
const createDeviceSchema = {
|
|
||||||
validate: async (data, options = {}) => {
|
|
||||||
const schema = await getCreateDeviceSchema();
|
|
||||||
return schema.validate(data, options);
|
|
||||||
}
|
|
||||||
};
|
|
||||||
|
|
||||||
const updateDeviceSchema = {
|
|
||||||
validate: async (data, options = {}) => {
|
|
||||||
const schema = await getUpdateDeviceSchema();
|
|
||||||
return schema.validate(data, options);
|
|
||||||
}
|
|
||||||
};
|
|
||||||
|
|
||||||
module.exports = {
|
module.exports = {
|
||||||
createDeviceSchema,
|
createDeviceSchema,
|
||||||
updateDeviceSchema,
|
updateDeviceSchema,
|
||||||
@@ -259,7 +138,5 @@ module.exports = {
|
|||||||
batchMoveSchema,
|
batchMoveSchema,
|
||||||
queryDeviceSchema,
|
queryDeviceSchema,
|
||||||
DEVICE_TYPES,
|
DEVICE_TYPES,
|
||||||
DEVICE_STATUS,
|
DEVICE_STATUS
|
||||||
getCreateDeviceSchema,
|
|
||||||
getUpdateDeviceSchema
|
|
||||||
};
|
};
|
||||||
|
|||||||
+1146
-267
File diff suppressed because it is too large
Load Diff
@@ -13,20 +13,27 @@ const CableCreateModal = ({ visible, onClose, onSuccess, sourceDevice }) => {
|
|||||||
const [sourcePorts, setSourcePorts] = useState([]);
|
const [sourcePorts, setSourcePorts] = useState([]);
|
||||||
const [targetPorts, setTargetPorts] = useState([]);
|
const [targetPorts, setTargetPorts] = useState([]);
|
||||||
const [fetchingDevices, setFetchingDevices] = useState(false);
|
const [fetchingDevices, setFetchingDevices] = useState(false);
|
||||||
const prevVisibleRef = useRef(false);
|
const devicesRef = useRef([]);
|
||||||
|
|
||||||
const fetchDevices = useCallback(async (keyword = '') => {
|
const fetchDevices = useCallback(async (keyword = '') => {
|
||||||
try {
|
try {
|
||||||
setFetchingDevices(true);
|
setFetchingDevices(true);
|
||||||
const params = { pageSize: 50 };
|
const params = { pageSize: 100 };
|
||||||
if (keyword && keyword.trim()) {
|
if (keyword && keyword.trim()) {
|
||||||
params.keyword = keyword.trim();
|
params.keyword = keyword.trim();
|
||||||
}
|
}
|
||||||
|
console.log('[CableCreateModal] Fetching devices with params:', params);
|
||||||
const response = await axios.get('/api/devices', { params });
|
const response = await axios.get('/api/devices', { params });
|
||||||
setDevices(response.data.devices || []);
|
console.log('[CableCreateModal] API response:', response.data);
|
||||||
|
const deviceList = response.data.devices || [];
|
||||||
|
console.log('[CableCreateModal] Device list:', deviceList.length, 'devices');
|
||||||
|
setDevices(deviceList);
|
||||||
|
devicesRef.current = deviceList;
|
||||||
|
return deviceList;
|
||||||
} catch (error) {
|
} catch (error) {
|
||||||
console.error('Failed to fetch devices:', error);
|
console.error('[CableCreateModal] Failed to fetch devices:', error);
|
||||||
message.error('获取设备列表失败');
|
message.error('获取设备列表失败');
|
||||||
|
return [];
|
||||||
} finally {
|
} finally {
|
||||||
setFetchingDevices(false);
|
setFetchingDevices(false);
|
||||||
}
|
}
|
||||||
@@ -40,18 +47,32 @@ const CableCreateModal = ({ visible, onClose, onSuccess, sourceDevice }) => {
|
|||||||
);
|
);
|
||||||
|
|
||||||
useEffect(() => {
|
useEffect(() => {
|
||||||
if (visible && !prevVisibleRef.current) {
|
if (visible) {
|
||||||
|
console.log('[CableCreateModal] Modal opened, sourceDevice:', sourceDevice);
|
||||||
form.resetFields();
|
form.resetFields();
|
||||||
if (sourceDevice) {
|
setSourcePorts([]);
|
||||||
form.setFieldsValue({
|
setTargetPorts([]);
|
||||||
sourceDeviceId: sourceDevice.deviceId || sourceDevice.id,
|
setDevices([]);
|
||||||
});
|
devicesRef.current = [];
|
||||||
fetchDevicePorts(sourceDevice.deviceId || sourceDevice.id, 'source');
|
|
||||||
}
|
fetchDevices().then(deviceList => {
|
||||||
fetchDevices();
|
console.log('[CableCreateModal] Devices fetched:', deviceList.length);
|
||||||
|
const sourceDeviceId = sourceDevice?.deviceId || sourceDevice?.id;
|
||||||
|
console.log('[CableCreateModal] sourceDeviceId:', sourceDeviceId);
|
||||||
|
if (sourceDeviceId && deviceList.length > 0) {
|
||||||
|
const deviceExists = deviceList.some(d => d.deviceId === sourceDeviceId);
|
||||||
|
console.log('[CableCreateModal] Device exists in list:', deviceExists);
|
||||||
|
if (deviceExists) {
|
||||||
|
console.log('[CableCreateModal] Setting form value:', sourceDeviceId);
|
||||||
|
form.setFieldsValue({
|
||||||
|
sourceDeviceId: sourceDeviceId,
|
||||||
|
});
|
||||||
|
fetchDevicePorts(sourceDeviceId, 'source');
|
||||||
|
}
|
||||||
|
}
|
||||||
|
});
|
||||||
}
|
}
|
||||||
prevVisibleRef.current = visible;
|
}, [visible, sourceDevice?.deviceId, sourceDevice?.id, form, fetchDevices]);
|
||||||
}, [visible, sourceDevice, form, fetchDevices]);
|
|
||||||
|
|
||||||
const fetchDevicePorts = async (deviceId, type) => {
|
const fetchDevicePorts = async (deviceId, type) => {
|
||||||
if (!deviceId) {
|
if (!deviceId) {
|
||||||
@@ -138,9 +159,11 @@ const CableCreateModal = ({ visible, onClose, onSuccess, sourceDevice }) => {
|
|||||||
<Select
|
<Select
|
||||||
showSearch
|
showSearch
|
||||||
filterOption={false}
|
filterOption={false}
|
||||||
placeholder="搜索设备..."
|
placeholder={fetchingDevices ? '加载中...' : '搜索设备...'}
|
||||||
|
loading={fetchingDevices}
|
||||||
onSearch={handleDeviceSearch}
|
onSearch={handleDeviceSearch}
|
||||||
onChange={handleSourceDeviceChange}
|
onChange={handleSourceDeviceChange}
|
||||||
|
notFoundContent={fetchingDevices ? <Spin size="small" /> : '暂无数据'}
|
||||||
>
|
>
|
||||||
{devices.map(device => (
|
{devices.map(device => (
|
||||||
<Option key={device.deviceId} value={device.deviceId}>
|
<Option key={device.deviceId} value={device.deviceId}>
|
||||||
@@ -167,9 +190,11 @@ const CableCreateModal = ({ visible, onClose, onSuccess, sourceDevice }) => {
|
|||||||
<Select
|
<Select
|
||||||
showSearch
|
showSearch
|
||||||
filterOption={false}
|
filterOption={false}
|
||||||
placeholder="搜索设备..."
|
placeholder={fetchingDevices ? '加载中...' : '搜索设备...'}
|
||||||
|
loading={fetchingDevices}
|
||||||
onSearch={handleDeviceSearch}
|
onSearch={handleDeviceSearch}
|
||||||
onChange={handleTargetDeviceChange}
|
onChange={handleTargetDeviceChange}
|
||||||
|
notFoundContent={fetchingDevices ? <Spin size="small" /> : '暂无数据'}
|
||||||
>
|
>
|
||||||
{devices.map(device => (
|
{devices.map(device => (
|
||||||
<Option key={device.deviceId} value={device.deviceId}>
|
<Option key={device.deviceId} value={device.deviceId}>
|
||||||
|
|||||||
@@ -110,6 +110,17 @@ export const designTokens = {
|
|||||||
date: '#06b6d4',
|
date: '#06b6d4',
|
||||||
textarea: '#64748b',
|
textarea: '#64748b',
|
||||||
},
|
},
|
||||||
|
slot: {
|
||||||
|
empty: '#4b5563',
|
||||||
|
occupied: '#3b82f6',
|
||||||
|
warning: '#f59e0b',
|
||||||
|
error: '#ef4444',
|
||||||
|
},
|
||||||
|
metal: {
|
||||||
|
light: '#9ca3af',
|
||||||
|
medium: '#6b7280',
|
||||||
|
dark: '#374151',
|
||||||
|
},
|
||||||
},
|
},
|
||||||
shadows: {
|
shadows: {
|
||||||
small: '0 1px 2px 0 rgba(0, 0, 0, 0.05)',
|
small: '0 1px 2px 0 rgba(0, 0, 0, 0.05)',
|
||||||
|
|||||||
@@ -206,18 +206,36 @@ function CableManagement() {
|
|||||||
const fetchDevices = useCallback(async (keyword = '') => {
|
const fetchDevices = useCallback(async (keyword = '') => {
|
||||||
try {
|
try {
|
||||||
setDeviceSearching(true);
|
setDeviceSearching(true);
|
||||||
const params = { pageSize: 50 };
|
|
||||||
|
// 并行获取所有设备和交换机设备
|
||||||
|
const params = { pageSize: 1000 };
|
||||||
if (keyword && keyword.trim()) {
|
if (keyword && keyword.trim()) {
|
||||||
params.keyword = keyword.trim();
|
params.keyword = keyword.trim();
|
||||||
}
|
}
|
||||||
const response = await axios.get('/api/devices', { params });
|
|
||||||
const allDevices = response.data.devices || [];
|
const [allResponse, switchResponse] = await Promise.all([
|
||||||
const switches = allDevices.filter(device => device.type === 'switch');
|
axios.get('/api/devices', { params }),
|
||||||
|
axios.get('/api/devices', { params: { ...params, type: 'switch' } })
|
||||||
|
]);
|
||||||
|
|
||||||
|
const allDevices = allResponse.data.devices || [];
|
||||||
|
const switchDevices = switchResponse.data.devices || [];
|
||||||
|
|
||||||
|
// 统计各类型设备数量
|
||||||
|
const typeCount = {};
|
||||||
|
allDevices.forEach(d => {
|
||||||
|
const t = d.type || 'undefined';
|
||||||
|
typeCount[t] = (typeCount[t] || 0) + 1;
|
||||||
|
});
|
||||||
|
console.log('[CableManagement] 设备类型统计:', typeCount);
|
||||||
|
console.log('[CableManagement] All devices:', allDevices.length);
|
||||||
|
console.log('[CableManagement] Switch devices (from API):', switchDevices.length);
|
||||||
|
|
||||||
setDevices(allDevices);
|
setDevices(allDevices);
|
||||||
setSwitchDevices(switches);
|
setSwitchDevices(switchDevices);
|
||||||
} catch (error) {
|
} catch (error) {
|
||||||
message.error('获取设备列表失败');
|
message.error('获取设备列表失败');
|
||||||
console.error('获取设备列表失败:', error);
|
console.error('[CableManagement] 获取设备列表失败:', error);
|
||||||
} finally {
|
} finally {
|
||||||
setDeviceSearching(false);
|
setDeviceSearching(false);
|
||||||
}
|
}
|
||||||
@@ -1244,7 +1262,7 @@ function CableManagement() {
|
|||||||
rules={[{ required: true, message: '请选择源设备' }]}
|
rules={[{ required: true, message: '请选择源设备' }]}
|
||||||
>
|
>
|
||||||
<Select
|
<Select
|
||||||
placeholder="输入关键词搜索源设备"
|
placeholder="输入关键词搜索交换机"
|
||||||
showSearch
|
showSearch
|
||||||
loading={deviceSearching}
|
loading={deviceSearching}
|
||||||
filterOption={false}
|
filterOption={false}
|
||||||
|
|||||||
Reference in New Issue
Block a user