Add: Auto ANL-Nummer bei Zuweisung, Server-Kategorie, Entra-Sync Verbesserungen
This commit is contained in:
@@ -177,6 +177,20 @@ class AssetController {
|
||||
req.ip
|
||||
);
|
||||
|
||||
// ANL-Nummer automatisch vergeben wenn noch keine vorhanden
|
||||
try {
|
||||
const Database = require('better-sqlite3');
|
||||
const db = new Database(process.env.DATABASE_PATH || './database.sqlite');
|
||||
const asset = db.prepare('SELECT inventory_number FROM assets WHERE id=?').get(parseInt(id));
|
||||
if (!asset?.inventory_number) {
|
||||
const maxRow = db.prepare("SELECT MAX(CAST(SUBSTR(inventory_number, 5) AS INTEGER)) as mx FROM assets WHERE inventory_number LIKE 'ANL-%'").get();
|
||||
const next = (maxRow?.mx || 0) + 1;
|
||||
const anl = `ANL-${String(next).padStart(4, '0')}`;
|
||||
db.prepare('UPDATE assets SET inventory_number=? WHERE id=?').run(anl, parseInt(id));
|
||||
}
|
||||
db.close();
|
||||
} catch (_) {}
|
||||
|
||||
res.json({
|
||||
status: 'success',
|
||||
data: assignment
|
||||
|
||||
@@ -80,12 +80,164 @@ async function syncEntraProfiles() {
|
||||
}
|
||||
}
|
||||
|
||||
const message = `${synced} Profile synchronisiert, ${photos} Fotos gespeichert, ${errors} Fehler`;
|
||||
// ── Schritt 2: Neue Entra-User → IT Nexus anlegen ──────────────────────
|
||||
let newUsers = 0;
|
||||
try {
|
||||
const allEntraUsersRes = await fetch(
|
||||
'https://graph.microsoft.com/v1.0/users?$select=id,displayName,givenName,surname,mail,userPrincipalName,jobTitle,department,mobilePhone,businessPhones,officeLocation,employeeHireDate&$top=999',
|
||||
{ headers: { Authorization: `Bearer ${token}` } }
|
||||
);
|
||||
const allEntraUsers = await allEntraUsersRes.json();
|
||||
|
||||
for (const eu of (allEntraUsers.value || [])) {
|
||||
if (!eu.mail && !eu.userPrincipalName) continue;
|
||||
const email = (eu.mail || eu.userPrincipalName).toLowerCase().trim();
|
||||
|
||||
// Prüfe ob User schon existiert (per azure_id oder email)
|
||||
const existing = db.prepare('SELECT id FROM users WHERE azure_id=? OR email=?').get(eu.id, email);
|
||||
if (existing) {
|
||||
// Falls azure_id noch nicht gesetzt, verknüpfen
|
||||
if (!db.prepare('SELECT azure_id FROM users WHERE id=?').get(existing.id)?.azure_id) {
|
||||
db.prepare('UPDATE users SET azure_id=? WHERE id=?').run(eu.id, existing.id);
|
||||
}
|
||||
continue;
|
||||
}
|
||||
|
||||
// Neuen User anlegen
|
||||
const benutzerRole = db.prepare("SELECT id FROM roles WHERE name='benutzer'").get();
|
||||
if (!benutzerRole) continue;
|
||||
|
||||
let username = email.split('@')[0].replace(/[^a-z0-9._-]/gi, '').toLowerCase();
|
||||
let suffix = 1;
|
||||
while (db.prepare('SELECT id FROM users WHERE username=?').get(username)) {
|
||||
username = `${email.split('@')[0].replace(/[^a-z0-9._-]/gi, '').toLowerCase()}${suffix++}`;
|
||||
}
|
||||
|
||||
db.prepare(`INSERT INTO users (username, email, password_hash, role_id, first_name, last_name, is_active, must_change_password, azure_id, department, position, phone, location, joined_date) VALUES (?,?,?,?,?,?,?,?,?,?,?,?,?,?)`).run(
|
||||
username, email, 'AZURE_SSO_NO_PASSWORD', benutzerRole.id,
|
||||
eu.givenName || null, eu.surname || null, 1, 0, eu.id,
|
||||
eu.department || null, eu.jobTitle || null,
|
||||
eu.mobilePhone || (eu.businessPhones?.[0]) || null,
|
||||
eu.officeLocation || null,
|
||||
eu.employeeHireDate ? eu.employeeHireDate.split('T')[0] : null
|
||||
);
|
||||
newUsers++;
|
||||
}
|
||||
console.log(`[Entra Sync] Schritt 2: ${newUsers} neue User angelegt`);
|
||||
} catch (err) {
|
||||
console.error('[Entra Sync] Schritt 2 (neue User) fehlgeschlagen:', err.message);
|
||||
errors++;
|
||||
}
|
||||
|
||||
// ── Schritt 3: Intune-Geräte → Assets sync ──────────────────────────────
|
||||
let newAssets = 0, updatedAssets = 0;
|
||||
try {
|
||||
const intuneRes = await fetch(
|
||||
'https://graph.microsoft.com/v1.0/deviceManagement/managedDevices?$select=id,deviceName,serialNumber,model,manufacturer,operatingSystem,osVersion,userDisplayName,userPrincipalName,lastSyncDateTime,managementState,complianceState&$top=999',
|
||||
{ headers: { Authorization: `Bearer ${token}` } }
|
||||
);
|
||||
const intuneDevices = await intuneRes.json();
|
||||
|
||||
for (const device of (intuneDevices.value || [])) {
|
||||
if (!device.serialNumber || device.serialNumber === 'unknown') continue;
|
||||
|
||||
// Asset-Typ bestimmen
|
||||
let assetType = 'sonstiges';
|
||||
const name = (device.deviceName || '').toLowerCase();
|
||||
const os = (device.operatingSystem || '').toLowerCase();
|
||||
if (os.includes('windows') && (name.includes('nb') || name.includes('laptop') || name.includes('book'))) assetType = 'Notebook';
|
||||
else if (os.includes('windows')) assetType = 'Notebook'; // Default Windows = Notebook
|
||||
|
||||
// Benutzer finden
|
||||
let assignedUserId = null;
|
||||
if (device.userPrincipalName) {
|
||||
const assignedUser = db.prepare('SELECT id FROM users WHERE email=? OR username=?')
|
||||
.get(device.userPrincipalName.toLowerCase(), device.userPrincipalName.split('@')[0].toLowerCase());
|
||||
assignedUserId = assignedUser?.id || null;
|
||||
}
|
||||
|
||||
const existingAsset = db.prepare('SELECT id, status FROM assets WHERE serial_number=? OR name=?').get(device.serialNumber, device.deviceName);
|
||||
|
||||
if (existingAsset) {
|
||||
// Bestehend updaten (OS, letzter Sync)
|
||||
db.prepare('UPDATE assets SET os=?, last_agent_sync=?, updated_at=CURRENT_TIMESTAMP WHERE id=?')
|
||||
.run(device.operatingSystem ? `${device.operatingSystem} ${device.osVersion || ''}`.trim() : null, device.lastSyncDateTime, existingAsset.id);
|
||||
updatedAssets++;
|
||||
} else {
|
||||
// Neues Asset anlegen
|
||||
db.prepare(`INSERT INTO assets (name, type, serial_number, model, manufacturer, os, status, department, assigned_to_user_id, last_agent_sync, created_at, updated_at) VALUES (?,?,?,?,?,?,?,?,?,?,CURRENT_TIMESTAMP,CURRENT_TIMESTAMP)`).run(
|
||||
device.deviceName || device.serialNumber,
|
||||
assetType,
|
||||
device.serialNumber,
|
||||
device.model || null,
|
||||
device.manufacturer || null,
|
||||
device.operatingSystem ? `${device.operatingSystem} ${device.osVersion || ''}`.trim() : null,
|
||||
assignedUserId ? 'zugewiesen' : 'verfuegbar',
|
||||
'IT',
|
||||
assignedUserId,
|
||||
device.lastSyncDateTime
|
||||
);
|
||||
newAssets++;
|
||||
}
|
||||
}
|
||||
console.log(`[Entra Sync] Schritt 3: ${newAssets} neue Assets, ${updatedAssets} aktualisiert`);
|
||||
} catch (err) {
|
||||
console.error('[Entra Sync] Schritt 3 (Intune Assets) fehlgeschlagen:', err.message);
|
||||
errors++;
|
||||
}
|
||||
|
||||
// ── Schritt 4: Entra-Geräte (Hybrid Join + Azure AD Join) → Assets sync ──
|
||||
try {
|
||||
const entraDevRes = await fetch(
|
||||
'https://graph.microsoft.com/v1.0/devices?$select=id,displayName,operatingSystem,operatingSystemVersion,manufacturer,model,deviceId,approximateLastSignInDateTime,registrationDateTime,trustType&$top=999',
|
||||
{ headers: { Authorization: `Bearer ${token}` } }
|
||||
);
|
||||
const entraDevices = await entraDevRes.json();
|
||||
|
||||
for (const device of (entraDevices.value || [])) {
|
||||
if (!device.displayName) continue;
|
||||
// Nur Windows-Geräte (Hybrid/Azure AD joined)
|
||||
const os = (device.operatingSystem || '').toLowerCase();
|
||||
if (!os.includes('windows')) continue;
|
||||
|
||||
// Prüfe ob Asset bereits existiert (per Name)
|
||||
const existingByName = db.prepare('SELECT id FROM assets WHERE name=?').get(device.displayName);
|
||||
if (existingByName) {
|
||||
// OS updaten falls nötig
|
||||
db.prepare('UPDATE assets SET os=?, updated_at=CURRENT_TIMESTAMP WHERE id=?')
|
||||
.run(device.operatingSystem ? `${device.operatingSystem} ${device.operatingSystemVersion || ''}`.trim() : null, existingByName.id);
|
||||
updatedAssets++;
|
||||
continue;
|
||||
}
|
||||
|
||||
// Typ bestimmen
|
||||
let assetType = 'Notebook';
|
||||
const name = device.displayName.toLowerCase();
|
||||
if (name.includes('srv') || name.includes('server')) assetType = 'Other';
|
||||
|
||||
db.prepare(`INSERT INTO assets (name, type, model, manufacturer, os, status, department, created_at, updated_at) VALUES (?,?,?,?,?,?,?,CURRENT_TIMESTAMP,CURRENT_TIMESTAMP)`).run(
|
||||
device.displayName,
|
||||
assetType,
|
||||
device.model || null,
|
||||
device.manufacturer || null,
|
||||
device.operatingSystem ? `${device.operatingSystem} ${device.operatingSystemVersion || ''}`.trim() : null,
|
||||
'verfuegbar',
|
||||
'IT'
|
||||
);
|
||||
newAssets++;
|
||||
}
|
||||
console.log(`[Entra Sync] Schritt 4: Entra-Geräte sync abgeschlossen`);
|
||||
} catch (err) {
|
||||
console.error('[Entra Sync] Schritt 4 (Entra Devices) fehlgeschlagen:', err.message);
|
||||
errors++;
|
||||
}
|
||||
|
||||
const message = `${synced} Profile sync, ${photos} Fotos, ${newUsers} neue User, ${newAssets} neue Assets, ${updatedAssets} Assets aktualisiert, ${errors} Fehler`;
|
||||
db.prepare(`UPDATE cron_logs SET status='success', finished_at=?, message=?, details=? WHERE id=?`)
|
||||
.run(new Date().toISOString(), message, JSON.stringify({ synced, photos, errors, total: users.length }), logId);
|
||||
.run(new Date().toISOString(), message, JSON.stringify({ synced, photos, newUsers, newAssets, updatedAssets, errors, total: users.length }), logId);
|
||||
|
||||
console.log(`[Entra Sync] ${message}`);
|
||||
return { synced, photos, errors };
|
||||
return { synced, photos, newUsers, newAssets, updatedAssets, errors };
|
||||
|
||||
} catch (err) {
|
||||
db.prepare(`UPDATE cron_logs SET status='error', finished_at=?, message=? WHERE id=?`)
|
||||
|
||||
@@ -18,6 +18,7 @@ const API = process.env.REACT_APP_API_URL || '/api';
|
||||
|
||||
const TYPE_ICONS = {
|
||||
notebook: '💻',
|
||||
server: '🖧',
|
||||
monitor: '🖥️',
|
||||
headset: '🎧',
|
||||
drucker: '🖨️',
|
||||
@@ -182,6 +183,7 @@ const EditModal = ({ asset, onClose, onSaved }) => {
|
||||
<select className="ap-form-input" value={form.type} onChange={e => set('type', e.target.value)}>
|
||||
<option value="">— Typ wählen —</option>
|
||||
<option value="notebook">Notebook</option>
|
||||
<option value="server">Server</option>
|
||||
<option value="monitor">Monitor</option>
|
||||
<option value="headset">Headset</option>
|
||||
<option value="drucker">Drucker</option>
|
||||
@@ -1128,6 +1130,7 @@ const AssetsPage = () => {
|
||||
{[
|
||||
['all', 'Alle'],
|
||||
['notebook', 'Notebook'],
|
||||
['server', 'Server'],
|
||||
['monitor', 'Monitor'],
|
||||
['headset', 'Headset'],
|
||||
['drucker', 'Drucker'],
|
||||
|
||||
Reference in New Issue
Block a user