Add: Auto ANL-Nummer bei Zuweisung, Server-Kategorie, Entra-Sync Verbesserungen

This commit is contained in:
2026-06-03 12:15:58 +02:00
parent 859ab6b28d
commit 67059b551d
3 changed files with 172 additions and 3 deletions

View File

@@ -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

View File

@@ -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=?`)

View File

@@ -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'],