Add: Auto ANL-Nummer bei Zuweisung, Server-Kategorie, Entra-Sync Verbesserungen
This commit is contained in:
@@ -177,6 +177,20 @@ class AssetController {
|
|||||||
req.ip
|
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({
|
res.json({
|
||||||
status: 'success',
|
status: 'success',
|
||||||
data: assignment
|
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=?`)
|
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}`);
|
console.log(`[Entra Sync] ${message}`);
|
||||||
return { synced, photos, errors };
|
return { synced, photos, newUsers, newAssets, updatedAssets, errors };
|
||||||
|
|
||||||
} catch (err) {
|
} catch (err) {
|
||||||
db.prepare(`UPDATE cron_logs SET status='error', finished_at=?, message=? WHERE id=?`)
|
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 = {
|
const TYPE_ICONS = {
|
||||||
notebook: '💻',
|
notebook: '💻',
|
||||||
|
server: '🖧',
|
||||||
monitor: '🖥️',
|
monitor: '🖥️',
|
||||||
headset: '🎧',
|
headset: '🎧',
|
||||||
drucker: '🖨️',
|
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)}>
|
<select className="ap-form-input" value={form.type} onChange={e => set('type', e.target.value)}>
|
||||||
<option value="">— Typ wählen —</option>
|
<option value="">— Typ wählen —</option>
|
||||||
<option value="notebook">Notebook</option>
|
<option value="notebook">Notebook</option>
|
||||||
|
<option value="server">Server</option>
|
||||||
<option value="monitor">Monitor</option>
|
<option value="monitor">Monitor</option>
|
||||||
<option value="headset">Headset</option>
|
<option value="headset">Headset</option>
|
||||||
<option value="drucker">Drucker</option>
|
<option value="drucker">Drucker</option>
|
||||||
@@ -1128,6 +1130,7 @@ const AssetsPage = () => {
|
|||||||
{[
|
{[
|
||||||
['all', 'Alle'],
|
['all', 'Alle'],
|
||||||
['notebook', 'Notebook'],
|
['notebook', 'Notebook'],
|
||||||
|
['server', 'Server'],
|
||||||
['monitor', 'Monitor'],
|
['monitor', 'Monitor'],
|
||||||
['headset', 'Headset'],
|
['headset', 'Headset'],
|
||||||
['drucker', 'Drucker'],
|
['drucker', 'Drucker'],
|
||||||
|
|||||||
Reference in New Issue
Block a user