app V1 completada
This commit is contained in:
@@ -104,6 +104,136 @@ router.get('/requests.csv', (_req, res) => {
|
||||
res.send(csv);
|
||||
});
|
||||
|
||||
// Export CSV: users, devices, vlans
|
||||
router.get('/users.csv', async (_req, res) => {
|
||||
const items = await readUsersFromDb();
|
||||
const cols = ['username','password','vlan','disabled','etiquetas','created_at','updated_at','habilitado_since'];
|
||||
const esc = (v) => {
|
||||
const s = v == null ? '' : Array.isArray(v) ? v.join(';') : String(v);
|
||||
return /[",\n]/.test(s) ? '"' + s.replace(/"/g, '""') + '"' : s;
|
||||
};
|
||||
const lines = [cols.join(',')];
|
||||
for (const u of items) {
|
||||
lines.push([u.username,u.password,u.vlan,u.disabled ? 'true':'false',u.etiquetas||[],u.created_at||'',u.updated_at||'',u.habilitado_since||''].map(esc).join(','));
|
||||
}
|
||||
res.setHeader('Content-Type','text/csv; charset=utf-8');
|
||||
res.setHeader('Content-Disposition','attachment; filename="users.csv"');
|
||||
res.send(lines.join('\n'));
|
||||
});
|
||||
|
||||
router.get('/devices.csv', async (_req, res) => {
|
||||
const r = await pool.query('SELECT id,mac,nombre,descripcion,vendor,first_seen,last_seen FROM dispositivos ORDER BY id ASC');
|
||||
const cols = ['id','mac','nombre','descripcion','vendor','first_seen','last_seen'];
|
||||
const esc = (v) => {
|
||||
const s = v == null ? '' : String(v);
|
||||
return /[",\n]/.test(s) ? '"' + s.replace(/"/g, '""') + '"' : s;
|
||||
};
|
||||
const lines = [cols.join(',')];
|
||||
for (const d of r.rows) lines.push(cols.map(c => esc(d[c])).join(','));
|
||||
res.setHeader('Content-Type','text/csv; charset=utf-8');
|
||||
res.setHeader('Content-Disposition','attachment; filename="devices.csv"');
|
||||
res.send(lines.join('\n'));
|
||||
});
|
||||
|
||||
router.get('/vlans.csv', async (_req, res) => {
|
||||
const r = await pool.query('SELECT id,nombre,descripcion FROM vlans ORDER BY id ASC');
|
||||
const cols = ['id','nombre','descripcion'];
|
||||
const esc = (v) => {
|
||||
const s = v == null ? '' : String(v);
|
||||
return /[",\n]/.test(s) ? '"' + s.replace(/"/g, '""') + '"' : s;
|
||||
};
|
||||
const lines = [cols.join(',')];
|
||||
for (const d of r.rows) lines.push(cols.map(c => esc(d[c])).join(','));
|
||||
res.setHeader('Content-Type','text/csv; charset=utf-8');
|
||||
res.setHeader('Content-Disposition','attachment; filename="vlans.csv"');
|
||||
res.send(lines.join('\n'));
|
||||
});
|
||||
|
||||
// Import CSV: expect JSON { csv: string }
|
||||
function parseCsv(str){
|
||||
const rows=[]; let i=0, cur='', row=[], inq=false; for(; i<str.length; i++){ const ch=str[i]; if(ch==='"'){ if(inq && str[i+1]==='"'){ cur+='"'; i++; } else { inq=!inq; } } else if(ch===',' && !inq){ row.push(cur); cur=''; } else if((ch==='\n' || ch==='\r') && !inq){ if(ch==='\r' && str[i+1]==='\n') i++; row.push(cur); rows.push(row); row=[]; cur=''; } else { cur+=ch; } } if(cur.length||row.length) { row.push(cur); rows.push(row); } return rows; }
|
||||
|
||||
router.post('/users/import', async (req, res) => {
|
||||
try {
|
||||
const csv = String((req.body && req.body.csv) || '');
|
||||
const rows = parseCsv(csv).filter(r => r.length>1);
|
||||
if (rows.length === 0) return res.json({ ok: true, count: 0 });
|
||||
const header = rows[0].map(s=>s.trim().toLowerCase());
|
||||
const idx = (k) => header.indexOf(k);
|
||||
let count=0;
|
||||
for (let r of rows.slice(1)) {
|
||||
const user = {
|
||||
username: r[idx('username')]?.trim(),
|
||||
password: r[idx('password')] ?? '',
|
||||
vlan: r[idx('vlan')] ?? '',
|
||||
disabled: /^(true|1|yes)$/i.test(r[idx('disabled')] || ''),
|
||||
};
|
||||
const tags = r[idx('etiquetas')] || '';
|
||||
if (tags) user.etiquetas = String(tags).split(/[;,|]/).map(s=>s.trim()).filter(Boolean).slice(0,100);
|
||||
if (user.username && user.password) { await upsertUserToDb(user); count++; }
|
||||
}
|
||||
res.json({ ok: true, count });
|
||||
} catch (e) {
|
||||
console.error('POST /api/users/import error:', e?.message || e);
|
||||
res.status(500).json({ ok: false, error: 'import_error' });
|
||||
}
|
||||
});
|
||||
|
||||
router.post('/devices/import', async (req, res) => {
|
||||
try {
|
||||
const csv = String((req.body && req.body.csv) || '');
|
||||
const rows = parseCsv(csv).filter(r => r.length>1);
|
||||
if (rows.length === 0) return res.json({ ok: true, count: 0 });
|
||||
const header = rows[0].map(s=>s.trim().toLowerCase());
|
||||
const idx = (k) => header.indexOf(k);
|
||||
let count=0;
|
||||
for (let r of rows.slice(1)) {
|
||||
const mac = r[idx('mac')]?.trim();
|
||||
if (!mac) continue;
|
||||
const nombre = r[idx('nombre')] || null;
|
||||
const descripcion = r[idx('descripcion')] || null;
|
||||
const vendor = r[idx('vendor')] || null;
|
||||
await pool.query(
|
||||
`INSERT INTO dispositivos (mac,nombre,descripcion,vendor) VALUES ($1,$2,$3,$4)
|
||||
ON CONFLICT (mac) DO UPDATE SET nombre=EXCLUDED.nombre, descripcion=EXCLUDED.descripcion, vendor=EXCLUDED.vendor, last_seen=NOW()`,
|
||||
[mac, nombre, descripcion, vendor]
|
||||
);
|
||||
count++;
|
||||
}
|
||||
res.json({ ok: true, count });
|
||||
} catch (e) {
|
||||
console.error('POST /api/devices/import error:', e?.message || e);
|
||||
res.status(500).json({ ok: false, error: 'import_error' });
|
||||
}
|
||||
});
|
||||
|
||||
router.post('/vlans/import', async (req, res) => {
|
||||
try {
|
||||
const csv = String((req.body && req.body.csv) || '');
|
||||
const rows = parseCsv(csv).filter(r => r.length>1);
|
||||
if (rows.length === 0) return res.json({ ok: true, count: 0 });
|
||||
const header = rows[0].map(s=>s.trim().toLowerCase());
|
||||
const idx = (k) => header.indexOf(k);
|
||||
let count=0;
|
||||
for (let r of rows.slice(1)) {
|
||||
const id = parseInt(r[idx('id')]||'',10);
|
||||
if (!Number.isInteger(id)) continue;
|
||||
const nombre = r[idx('nombre')] || null;
|
||||
const descripcion = r[idx('descripcion')] || null;
|
||||
await pool.query(
|
||||
`INSERT INTO vlans (id,nombre,descripcion) VALUES ($1,$2,$3)
|
||||
ON CONFLICT (id) DO UPDATE SET nombre=EXCLUDED.nombre, descripcion=EXCLUDED.descripcion`,
|
||||
[id, nombre, descripcion]
|
||||
);
|
||||
count++;
|
||||
}
|
||||
res.json({ ok: true, count });
|
||||
} catch (e) {
|
||||
console.error('POST /api/vlans/import error:', e?.message || e);
|
||||
res.status(500).json({ ok: false, error: 'import_error' });
|
||||
}
|
||||
});
|
||||
|
||||
// SSE events
|
||||
router.get('/events', (req, res) => {
|
||||
registerSse(req, res, {});
|
||||
|
||||
Reference in New Issue
Block a user