feat: 修复BUG
This commit is contained in:
@@ -22,6 +22,11 @@ const {
|
||||
updateAutoBackupSettings,
|
||||
executeBackupNow,
|
||||
} = require('../utils/autoBackupScheduler');
|
||||
const {
|
||||
getBackupLogs,
|
||||
getBackupLogById,
|
||||
deleteOldLogs,
|
||||
} = require('../utils/backupLog');
|
||||
const {
|
||||
getAllTargets,
|
||||
getTarget,
|
||||
@@ -30,6 +35,7 @@ const {
|
||||
deleteTarget,
|
||||
getGlobalSettings,
|
||||
updateGlobalSettings,
|
||||
getEnabledTargets,
|
||||
} = require('../utils/remoteBackupConfig');
|
||||
const { testRemoteConnection, PROTOCOL_TYPES, PROTOCOL_LABELS } = require('../utils/remoteBackup');
|
||||
|
||||
@@ -446,6 +452,7 @@ router.post('/auto/settings', (req, res) => {
|
||||
compress,
|
||||
maxCount,
|
||||
maxAgeDays,
|
||||
backupType,
|
||||
} = req.body;
|
||||
|
||||
const newSettings = {};
|
||||
@@ -468,6 +475,7 @@ router.post('/auto/settings', (req, res) => {
|
||||
if (compress !== undefined) newSettings.compress = compress;
|
||||
if (maxCount !== undefined) newSettings.maxCount = maxCount;
|
||||
if (maxAgeDays !== undefined) newSettings.maxAgeDays = maxAgeDays;
|
||||
if (backupType !== undefined) newSettings.backupType = backupType;
|
||||
|
||||
const success = updateAutoBackupSettings(newSettings);
|
||||
if (success) {
|
||||
@@ -496,12 +504,13 @@ router.post('/auto/settings', (req, res) => {
|
||||
// 立即执行备份
|
||||
router.post('/auto/execute', async (req, res) => {
|
||||
try {
|
||||
const { description, includeFiles, compress } = req.body;
|
||||
const { description, includeFiles, compress, backupType } = req.body;
|
||||
|
||||
const result = await executeBackupNow({
|
||||
description: description || '手动触发备份',
|
||||
includeFiles,
|
||||
compress,
|
||||
backupType,
|
||||
});
|
||||
|
||||
if (result.success) {
|
||||
@@ -854,4 +863,80 @@ router.post('/remote/upload', async (req, res) => {
|
||||
}
|
||||
});
|
||||
|
||||
// ==================== 备份日志接口 ====================
|
||||
|
||||
// 获取备份日志列表
|
||||
router.get('/logs', async (req, res) => {
|
||||
try {
|
||||
const { page = 1, pageSize = 20, logType, status } = req.query;
|
||||
|
||||
const result = await getBackupLogs({
|
||||
page: parseInt(page),
|
||||
pageSize: parseInt(pageSize),
|
||||
logType,
|
||||
status,
|
||||
});
|
||||
|
||||
res.json({
|
||||
success: true,
|
||||
data: result,
|
||||
});
|
||||
} catch (error) {
|
||||
console.error('获取备份日志失败:', error);
|
||||
res.status(500).json({
|
||||
success: false,
|
||||
message: '获取备份日志失败',
|
||||
error: error.message,
|
||||
});
|
||||
}
|
||||
});
|
||||
|
||||
// 获取备份日志详情
|
||||
router.get('/logs/:id', async (req, res) => {
|
||||
try {
|
||||
const { id } = req.params;
|
||||
const log = await getBackupLogById(parseInt(id));
|
||||
|
||||
if (!log) {
|
||||
return res.status(404).json({
|
||||
success: false,
|
||||
message: '备份日志不存在',
|
||||
});
|
||||
}
|
||||
|
||||
res.json({
|
||||
success: true,
|
||||
data: log,
|
||||
});
|
||||
} catch (error) {
|
||||
console.error('获取备份日志详情失败:', error);
|
||||
res.status(500).json({
|
||||
success: false,
|
||||
message: '获取备份日志详情失败',
|
||||
error: error.message,
|
||||
});
|
||||
}
|
||||
});
|
||||
|
||||
// 清理旧日志
|
||||
router.delete('/logs/clean', async (req, res) => {
|
||||
try {
|
||||
const { days = 30 } = req.body;
|
||||
const deletedCount = await deleteOldLogs(parseInt(days));
|
||||
|
||||
res.json({
|
||||
success: true,
|
||||
message: '清理完成',
|
||||
data: { deletedCount },
|
||||
});
|
||||
} catch (error) {
|
||||
console.error('清理旧日志失败:', error);
|
||||
res.status(500).json({
|
||||
success: false,
|
||||
message: '清理旧日志失败',
|
||||
error: error.message,
|
||||
});
|
||||
}
|
||||
});
|
||||
|
||||
module.exports = router;
|
||||
|
||||
@@ -34,7 +34,7 @@ router.get('/', async (req, res) => {
|
||||
const { count, rows } = await ConsumableRecord.findAndCountAll({
|
||||
where,
|
||||
include: [
|
||||
{ model: Consumable, attributes: ['name', 'category', 'unit'] }
|
||||
{ model: Consumable, as: 'consumable', attributes: ['name', 'category', 'unit'] }
|
||||
],
|
||||
offset,
|
||||
limit: parseInt(pageSize),
|
||||
@@ -123,17 +123,36 @@ router.post('/', async (req, res) => {
|
||||
|
||||
router.get('/statistics', async (req, res) => {
|
||||
try {
|
||||
const { startDate, endDate } = req.query;
|
||||
const { startDate, endDate, category } = req.query;
|
||||
|
||||
const dateWhere = {};
|
||||
if (startDate && endDate) {
|
||||
// 修复日期范围查询:startDate 从 00:00:00 开始,endDate 到 23:59:59 结束
|
||||
const startDateTime = new Date(startDate);
|
||||
const endDateTime = new Date(endDate);
|
||||
endDateTime.setHours(23, 59, 59, 999); // 设置 endDate 为当天最后一刻
|
||||
|
||||
dateWhere.createdAt = {
|
||||
[Op.between]: [new Date(startDate), new Date(endDate)]
|
||||
[Op.gte]: startDateTime,
|
||||
[Op.lte]: endDateTime
|
||||
};
|
||||
}
|
||||
|
||||
const consumableWhere = {};
|
||||
if (category) {
|
||||
consumableWhere.category = category;
|
||||
}
|
||||
|
||||
const records = await ConsumableRecord.findAll({
|
||||
where: dateWhere,
|
||||
include: [
|
||||
{
|
||||
model: Consumable,
|
||||
as: 'consumable',
|
||||
attributes: ['name', 'category'],
|
||||
where: Object.keys(consumableWhere).length > 0 ? consumableWhere : undefined
|
||||
}
|
||||
],
|
||||
attributes: ['type', 'quantity']
|
||||
});
|
||||
|
||||
@@ -163,7 +182,12 @@ router.get('/statistics', async (req, res) => {
|
||||
const recentRecords = await ConsumableRecord.findAll({
|
||||
where: dateWhere,
|
||||
include: [
|
||||
{ model: Consumable, attributes: ['name', 'category'] }
|
||||
{
|
||||
model: Consumable,
|
||||
as: 'consumable',
|
||||
attributes: ['name', 'category'],
|
||||
where: Object.keys(consumableWhere).length > 0 ? consumableWhere : undefined
|
||||
}
|
||||
],
|
||||
order: [['createdAt', 'DESC']],
|
||||
limit: 10
|
||||
@@ -180,7 +204,17 @@ router.get('/statistics', async (req, res) => {
|
||||
totalQuantity: data.totalQuantity,
|
||||
count: data.count
|
||||
})),
|
||||
recentRecords
|
||||
recentRecords: recentRecords.map(record => ({
|
||||
recordId: record.recordId,
|
||||
type: record.type,
|
||||
quantity: record.quantity,
|
||||
operator: record.operator,
|
||||
createdAt: record.createdAt,
|
||||
consumableId: record.consumableId,
|
||||
consumableName: record.consumable?.name || '未知耗材',
|
||||
category: record.consumable?.category || null,
|
||||
unit: record.consumable?.unit || '个'
|
||||
}))
|
||||
});
|
||||
} catch (error) {
|
||||
res.status(500).json({ error: error.message });
|
||||
|
||||
@@ -223,12 +223,17 @@ router.get('/low-stock', async (req, res) => {
|
||||
try {
|
||||
const consumables = await Consumable.findAll({
|
||||
where: {
|
||||
status: 'active',
|
||||
[Op.and]: [
|
||||
sequelize.where(sequelize.col('currentStock'), {
|
||||
[Op.lte]: sequelize.col('minStock')
|
||||
}),
|
||||
sequelize.where(sequelize.col('minStock'), {
|
||||
[Op.gt]: 0
|
||||
})
|
||||
]
|
||||
}
|
||||
},
|
||||
order: [['currentStock', 'ASC']]
|
||||
});
|
||||
res.json(consumables);
|
||||
} catch (error) {
|
||||
@@ -239,35 +244,41 @@ router.get('/low-stock', async (req, res) => {
|
||||
router.get('/statistics/summary', async (req, res) => {
|
||||
try {
|
||||
const consumables = await Consumable.findAll({
|
||||
attributes: ['currentStock', 'unitPrice', 'category']
|
||||
attributes: ['currentStock', 'unitPrice', 'category', 'minStock', 'status']
|
||||
});
|
||||
|
||||
let total = consumables.length;
|
||||
let total = 0;
|
||||
let lowStock = 0;
|
||||
let totalValue = 0;
|
||||
const categoryMap = {};
|
||||
|
||||
consumables.forEach(item => {
|
||||
if (item.status === 'inactive') return;
|
||||
|
||||
total++;
|
||||
const currentStock = parseFloat(item.currentStock) || 0;
|
||||
const unitPrice = parseFloat(item.unitPrice) || 0;
|
||||
const minStockValue = parseFloat(item.minStock) || 0;
|
||||
|
||||
totalValue += currentStock * unitPrice;
|
||||
|
||||
if (currentStock <= (parseFloat(item.minStock) || 0)) {
|
||||
if (minStockValue > 0 && currentStock <= minStockValue) {
|
||||
lowStock++;
|
||||
}
|
||||
|
||||
if (item.category) {
|
||||
if (!categoryMap[item.category]) {
|
||||
categoryMap[item.category] = 0;
|
||||
categoryMap[item.category] = { count: 0, totalQuantity: 0 };
|
||||
}
|
||||
categoryMap[item.category]++;
|
||||
categoryMap[item.category].count++;
|
||||
categoryMap[item.category].totalQuantity += currentStock;
|
||||
}
|
||||
});
|
||||
|
||||
const byCategory = Object.entries(categoryMap).map(([category, count]) => ({
|
||||
const byCategory = Object.entries(categoryMap).map(([category, data]) => ({
|
||||
category,
|
||||
count
|
||||
count: data.count,
|
||||
totalQuantity: data.totalQuantity
|
||||
}));
|
||||
|
||||
res.json({
|
||||
@@ -292,7 +303,7 @@ router.get('/inout/records', async (req, res) => {
|
||||
order: [['createdAt', 'DESC']],
|
||||
include: [{
|
||||
model: Consumable,
|
||||
as: 'Consumable',
|
||||
as: 'consumable',
|
||||
attributes: ['consumableId', 'name', 'category']
|
||||
}]
|
||||
});
|
||||
|
||||
@@ -60,6 +60,68 @@ router.get('/device/:deviceId', async (req, res) => {
|
||||
}
|
||||
});
|
||||
|
||||
router.get('/device/:deviceId/with-ports', async (req, res) => {
|
||||
try {
|
||||
const { deviceId } = req.params;
|
||||
|
||||
const networkCards = await NetworkCard.findAll({
|
||||
where: { deviceId },
|
||||
order: [['slotNumber', 'ASC'], ['name', 'ASC']]
|
||||
});
|
||||
|
||||
const cardsWithPorts = await Promise.all(
|
||||
networkCards.map(async (card) => {
|
||||
const ports = await DevicePort.findAll({
|
||||
where: { nicId: card.nicId },
|
||||
order: [['portName', 'ASC']]
|
||||
});
|
||||
|
||||
const freeCount = ports.filter(p => p.status === 'free').length;
|
||||
const occupiedCount = ports.filter(p => p.status === 'occupied').length;
|
||||
const faultCount = ports.filter(p => p.status === 'fault').length;
|
||||
|
||||
return {
|
||||
...card.toJSON(),
|
||||
ports,
|
||||
stats: {
|
||||
total: ports.length,
|
||||
free: freeCount,
|
||||
occupied: occupiedCount,
|
||||
fault: faultCount
|
||||
}
|
||||
};
|
||||
})
|
||||
);
|
||||
|
||||
const ungroupedPorts = await DevicePort.findAll({
|
||||
where: { deviceId, nicId: null },
|
||||
order: [['portName', 'ASC']]
|
||||
});
|
||||
|
||||
if (ungroupedPorts.length > 0) {
|
||||
cardsWithPorts.push({
|
||||
nicId: '_ungrouped',
|
||||
name: '未分组端口',
|
||||
description: '未分配到网卡的端口',
|
||||
portCount: ungroupedPorts.length,
|
||||
isUngrouped: true,
|
||||
ports: ungroupedPorts,
|
||||
stats: {
|
||||
total: ungroupedPorts.length,
|
||||
free: ungroupedPorts.filter(p => p.status === 'free').length,
|
||||
occupied: ungroupedPorts.filter(p => p.status === 'occupied').length,
|
||||
fault: ungroupedPorts.filter(p => p.status === 'fault').length
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
res.json(cardsWithPorts);
|
||||
} catch (error) {
|
||||
console.error('获取网卡及端口失败:', error);
|
||||
res.status(500).json({ error: error.message });
|
||||
}
|
||||
});
|
||||
|
||||
router.get('/:nicId', async (req, res) => {
|
||||
try {
|
||||
const networkCard = await NetworkCard.findByPk(req.params.nicId, {
|
||||
@@ -198,66 +260,4 @@ router.delete('/:nicId', async (req, res) => {
|
||||
}
|
||||
});
|
||||
|
||||
router.get('/device/:deviceId/with-ports', async (req, res) => {
|
||||
try {
|
||||
const { deviceId } = req.params;
|
||||
|
||||
const networkCards = await NetworkCard.findAll({
|
||||
where: { deviceId },
|
||||
order: [['slotNumber', 'ASC'], ['name', 'ASC']]
|
||||
});
|
||||
|
||||
const cardsWithPorts = await Promise.all(
|
||||
networkCards.map(async (card) => {
|
||||
const ports = await DevicePort.findAll({
|
||||
where: { nicId: card.nicId },
|
||||
order: [['portName', 'ASC']]
|
||||
});
|
||||
|
||||
const freeCount = ports.filter(p => p.status === 'free').length;
|
||||
const occupiedCount = ports.filter(p => p.status === 'occupied').length;
|
||||
const faultCount = ports.filter(p => p.status === 'fault').length;
|
||||
|
||||
return {
|
||||
...card.toJSON(),
|
||||
ports,
|
||||
stats: {
|
||||
total: ports.length,
|
||||
free: freeCount,
|
||||
occupied: occupiedCount,
|
||||
fault: faultCount
|
||||
}
|
||||
};
|
||||
})
|
||||
);
|
||||
|
||||
const ungroupedPorts = await DevicePort.findAll({
|
||||
where: { deviceId, nicId: null },
|
||||
order: [['portName', 'ASC']]
|
||||
});
|
||||
|
||||
if (ungroupedPorts.length > 0) {
|
||||
cardsWithPorts.push({
|
||||
nicId: '_ungrouped',
|
||||
name: '未分组端口',
|
||||
description: '未分配到网卡的端口',
|
||||
portCount: ungroupedPorts.length,
|
||||
isUngrouped: true,
|
||||
ports: ungroupedPorts,
|
||||
stats: {
|
||||
total: ungroupedPorts.length,
|
||||
free: ungroupedPorts.filter(p => p.status === 'free').length,
|
||||
occupied: ungroupedPorts.filter(p => p.status === 'occupied').length,
|
||||
fault: ungroupedPorts.filter(p => p.status === 'fault').length
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
res.json(cardsWithPorts);
|
||||
} catch (error) {
|
||||
console.error('获取网卡及端口失败:', error);
|
||||
res.status(500).json({ error: error.message });
|
||||
}
|
||||
});
|
||||
|
||||
module.exports = router;
|
||||
|
||||
Reference in New Issue
Block a user