feat: Sistema MDM completo implementado
- Rotas API para gerenciamento de políticas (/api/policies) - Rotas MDM para cliente (/api/mdm) - Tabelas PostgreSQL para políticas e execuções - Cliente Python com executor MDM (CLIENT_MDM.py) - Suporte a 8 tipos de políticas: * Windows Update (forçar atualizações) * Instalar/Desinstalar Software * Scripts PowerShell * Modificar Registro * Reiniciar dispositivos * Limpeza de sistema * Configurar Firewall - Templates pré-configurados - Histórico de execuções - Documentação completa (SISTEMA_MDM.md) - Exemplo de integração Sistema similar ao JumpCloud MDM, permitindo gerenciamento remoto completo de dispositivos Windows.
This commit is contained in:
314
backend/routes/policies.js
Normal file
314
backend/routes/policies.js
Normal file
@@ -0,0 +1,314 @@
|
||||
// Rotas para gerenciamento de políticas MDM
|
||||
const express = require('express');
|
||||
const router = express.Router();
|
||||
const { Pool } = require('pg');
|
||||
const pool = new Pool({ connectionString: process.env.DATABASE_URL });
|
||||
|
||||
// Listar todas as políticas
|
||||
router.get('/', async (req, res) => {
|
||||
try {
|
||||
const result = await pool.query(`
|
||||
SELECT
|
||||
p.*,
|
||||
COUNT(DISTINCT dp.device_id) as devices_count,
|
||||
COUNT(DISTINCT pe.id) FILTER (WHERE pe.status = 'success') as success_count,
|
||||
COUNT(DISTINCT pe.id) FILTER (WHERE pe.status = 'failed') as failed_count
|
||||
FROM policies p
|
||||
LEFT JOIN device_policies dp ON p.id = dp.policy_id
|
||||
LEFT JOIN policy_executions pe ON p.id = pe.policy_id
|
||||
GROUP BY p.id
|
||||
ORDER BY p.created_at DESC
|
||||
`);
|
||||
|
||||
res.json({ success: true, policies: result.rows });
|
||||
} catch (error) {
|
||||
console.error('Erro ao listar políticas:', error);
|
||||
res.status(500).json({ success: false, message: error.message });
|
||||
}
|
||||
});
|
||||
|
||||
// Criar nova política
|
||||
router.post('/', async (req, res) => {
|
||||
try {
|
||||
const {
|
||||
name,
|
||||
description,
|
||||
type,
|
||||
config,
|
||||
enabled,
|
||||
schedule,
|
||||
priority
|
||||
} = req.body;
|
||||
|
||||
const result = await pool.query(`
|
||||
INSERT INTO policies (
|
||||
name, description, type, config, enabled, schedule, priority
|
||||
) VALUES ($1, $2, $3, $4, $5, $6, $7)
|
||||
RETURNING *
|
||||
`, [name, description, type, JSON.stringify(config), enabled, schedule, priority || 5]);
|
||||
|
||||
console.log(`✅ Política criada: ${name} (${type})`);
|
||||
res.json({ success: true, policy: result.rows[0] });
|
||||
} catch (error) {
|
||||
console.error('Erro ao criar política:', error);
|
||||
res.status(500).json({ success: false, message: error.message });
|
||||
}
|
||||
});
|
||||
|
||||
// Atualizar política
|
||||
router.put('/:id', async (req, res) => {
|
||||
try {
|
||||
const { id } = req.params;
|
||||
const {
|
||||
name,
|
||||
description,
|
||||
type,
|
||||
config,
|
||||
enabled,
|
||||
schedule,
|
||||
priority
|
||||
} = req.body;
|
||||
|
||||
const result = await pool.query(`
|
||||
UPDATE policies
|
||||
SET name = $1,
|
||||
description = $2,
|
||||
type = $3,
|
||||
config = $4,
|
||||
enabled = $5,
|
||||
schedule = $6,
|
||||
priority = $7,
|
||||
updated_at = NOW()
|
||||
WHERE id = $8
|
||||
RETURNING *
|
||||
`, [name, description, type, JSON.stringify(config), enabled, schedule, priority, id]);
|
||||
|
||||
if (result.rows.length === 0) {
|
||||
return res.status(404).json({ success: false, message: 'Política não encontrada' });
|
||||
}
|
||||
|
||||
console.log(`✅ Política atualizada: ${name}`);
|
||||
res.json({ success: true, policy: result.rows[0] });
|
||||
} catch (error) {
|
||||
console.error('Erro ao atualizar política:', error);
|
||||
res.status(500).json({ success: false, message: error.message });
|
||||
}
|
||||
});
|
||||
|
||||
// Deletar política
|
||||
router.delete('/:id', async (req, res) => {
|
||||
try {
|
||||
const { id } = req.params;
|
||||
|
||||
// Remover associações com dispositivos
|
||||
await pool.query('DELETE FROM device_policies WHERE policy_id = $1', [id]);
|
||||
|
||||
// Remover política
|
||||
const result = await pool.query('DELETE FROM policies WHERE id = $1 RETURNING *', [id]);
|
||||
|
||||
if (result.rows.length === 0) {
|
||||
return res.status(404).json({ success: false, message: 'Política não encontrada' });
|
||||
}
|
||||
|
||||
console.log(`✅ Política deletada: ${result.rows[0].name}`);
|
||||
res.json({ success: true, message: 'Política deletada com sucesso' });
|
||||
} catch (error) {
|
||||
console.error('Erro ao deletar política:', error);
|
||||
res.status(500).json({ success: false, message: error.message });
|
||||
}
|
||||
});
|
||||
|
||||
// Associar política a dispositivos
|
||||
router.post('/:id/devices', async (req, res) => {
|
||||
try {
|
||||
const { id } = req.params;
|
||||
const { device_ids } = req.body; // Array de device_ids
|
||||
|
||||
if (!Array.isArray(device_ids) || device_ids.length === 0) {
|
||||
return res.status(400).json({ success: false, message: 'device_ids deve ser um array não vazio' });
|
||||
}
|
||||
|
||||
// Inserir associações
|
||||
const values = device_ids.map(device_id => `('${device_id}', ${id})`).join(',');
|
||||
await pool.query(`
|
||||
INSERT INTO device_policies (device_id, policy_id)
|
||||
VALUES ${values}
|
||||
ON CONFLICT (device_id, policy_id) DO NOTHING
|
||||
`);
|
||||
|
||||
console.log(`✅ Política ${id} associada a ${device_ids.length} dispositivo(s)`);
|
||||
res.json({ success: true, message: `Política aplicada a ${device_ids.length} dispositivo(s)` });
|
||||
} catch (error) {
|
||||
console.error('Erro ao associar política:', error);
|
||||
res.status(500).json({ success: false, message: error.message });
|
||||
}
|
||||
});
|
||||
|
||||
// Remover política de dispositivos
|
||||
router.delete('/:id/devices', async (req, res) => {
|
||||
try {
|
||||
const { id } = req.params;
|
||||
const { device_ids } = req.body;
|
||||
|
||||
if (!Array.isArray(device_ids) || device_ids.length === 0) {
|
||||
return res.status(400).json({ success: false, message: 'device_ids deve ser um array não vazio' });
|
||||
}
|
||||
|
||||
const result = await pool.query(`
|
||||
DELETE FROM device_policies
|
||||
WHERE policy_id = $1 AND device_id = ANY($2)
|
||||
`, [id, device_ids]);
|
||||
|
||||
console.log(`✅ Política ${id} removida de ${result.rowCount} dispositivo(s)`);
|
||||
res.json({ success: true, message: `Política removida de ${result.rowCount} dispositivo(s)` });
|
||||
} catch (error) {
|
||||
console.error('Erro ao remover política:', error);
|
||||
res.status(500).json({ success: false, message: error.message });
|
||||
}
|
||||
});
|
||||
|
||||
// Executar política agora (forçar execução imediata)
|
||||
router.post('/:id/execute', async (req, res) => {
|
||||
try {
|
||||
const { id } = req.params;
|
||||
const { device_ids } = req.body; // Se não fornecido, executa em todos os dispositivos com a política
|
||||
|
||||
// Buscar política
|
||||
const policyResult = await pool.query('SELECT * FROM policies WHERE id = $1', [id]);
|
||||
if (policyResult.rows.length === 0) {
|
||||
return res.status(404).json({ success: false, message: 'Política não encontrada' });
|
||||
}
|
||||
|
||||
const policy = policyResult.rows[0];
|
||||
|
||||
// Buscar dispositivos
|
||||
let devicesQuery;
|
||||
if (device_ids && device_ids.length > 0) {
|
||||
devicesQuery = await pool.query(`
|
||||
SELECT DISTINCT d.device_id, d.device_name
|
||||
FROM devices d
|
||||
WHERE d.device_id = ANY($1) AND d.status = 'online'
|
||||
`, [device_ids]);
|
||||
} else {
|
||||
devicesQuery = await pool.query(`
|
||||
SELECT DISTINCT d.device_id, d.device_name
|
||||
FROM devices d
|
||||
INNER JOIN device_policies dp ON d.device_id = dp.device_id
|
||||
WHERE dp.policy_id = $1 AND d.status = 'online'
|
||||
`, [id]);
|
||||
}
|
||||
|
||||
if (devicesQuery.rows.length === 0) {
|
||||
return res.json({
|
||||
success: false,
|
||||
message: 'Nenhum dispositivo online encontrado para executar a política'
|
||||
});
|
||||
}
|
||||
|
||||
// Criar comandos de execução para cada dispositivo
|
||||
const commands = [];
|
||||
for (const device of devicesQuery.rows) {
|
||||
const commandResult = await pool.query(`
|
||||
INSERT INTO policy_commands (
|
||||
device_id,
|
||||
policy_id,
|
||||
command_type,
|
||||
command_data,
|
||||
status
|
||||
) VALUES ($1, $2, $3, $4, 'pending')
|
||||
RETURNING id
|
||||
`, [
|
||||
device.device_id,
|
||||
id,
|
||||
policy.type,
|
||||
JSON.stringify(policy.config)
|
||||
]);
|
||||
|
||||
commands.push({
|
||||
command_id: commandResult.rows[0].id,
|
||||
device_id: device.device_id,
|
||||
device_name: device.device_name
|
||||
});
|
||||
}
|
||||
|
||||
console.log(`✅ Política ${policy.name} enfileirada para ${commands.length} dispositivo(s)`);
|
||||
res.json({
|
||||
success: true,
|
||||
message: `Política enfileirada para ${commands.length} dispositivo(s)`,
|
||||
commands
|
||||
});
|
||||
} catch (error) {
|
||||
console.error('Erro ao executar política:', error);
|
||||
res.status(500).json({ success: false, message: error.message });
|
||||
}
|
||||
});
|
||||
|
||||
// Ver histórico de execuções de uma política
|
||||
router.get('/:id/executions', async (req, res) => {
|
||||
try {
|
||||
const { id } = req.params;
|
||||
const { limit = 50, offset = 0 } = req.query;
|
||||
|
||||
const result = await pool.query(`
|
||||
SELECT
|
||||
pe.*,
|
||||
d.device_name,
|
||||
d.hostname
|
||||
FROM policy_executions pe
|
||||
INNER JOIN devices d ON pe.device_id = d.device_id
|
||||
WHERE pe.policy_id = $1
|
||||
ORDER BY pe.executed_at DESC
|
||||
LIMIT $2 OFFSET $3
|
||||
`, [id, limit, offset]);
|
||||
|
||||
const countResult = await pool.query(
|
||||
'SELECT COUNT(*) FROM policy_executions WHERE policy_id = $1',
|
||||
[id]
|
||||
);
|
||||
|
||||
res.json({
|
||||
success: true,
|
||||
executions: result.rows,
|
||||
total: parseInt(countResult.rows[0].count)
|
||||
});
|
||||
} catch (error) {
|
||||
console.error('Erro ao buscar execuções:', error);
|
||||
res.status(500).json({ success: false, message: error.message });
|
||||
}
|
||||
});
|
||||
|
||||
// Ver dispositivos com a política aplicada
|
||||
router.get('/:id/devices', async (req, res) => {
|
||||
try {
|
||||
const { id } = req.params;
|
||||
|
||||
const result = await pool.query(`
|
||||
SELECT
|
||||
d.*,
|
||||
dp.assigned_at,
|
||||
pe.last_execution,
|
||||
pe.last_status
|
||||
FROM devices d
|
||||
INNER JOIN device_policies dp ON d.device_id = dp.device_id
|
||||
LEFT JOIN LATERAL (
|
||||
SELECT
|
||||
executed_at as last_execution,
|
||||
status as last_status
|
||||
FROM policy_executions
|
||||
WHERE device_id = d.device_id AND policy_id = $1
|
||||
ORDER BY executed_at DESC
|
||||
LIMIT 1
|
||||
) pe ON true
|
||||
WHERE dp.policy_id = $1
|
||||
ORDER BY d.device_name
|
||||
`, [id]);
|
||||
|
||||
res.json({ success: true, devices: result.rows });
|
||||
} catch (error) {
|
||||
console.error('Erro ao buscar dispositivos:', error);
|
||||
res.status(500).json({ success: false, message: error.message });
|
||||
}
|
||||
});
|
||||
|
||||
module.exports = router;
|
||||
|
||||
Reference in New Issue
Block a user