Quelltext des aktuellen Standes
<?php $this->layout('layouts/admin', [
'page_title' => $page_title,
'current_admin_page' => $current_admin_page ?? 'assets-glpi-import',
'breadcrumbs' => [['label' => 'GLPI Import']],
]); ?>
<style>
/* Modul-spezifische Stile – Tabellen/Pills kommen aus app.css */
.data-table tr.exists td { opacity: 0.6; background: var(--color-background,var(--gray-50)); }
/* Drucker-Pill (rosa, keine Standard-Variante) */
.glpi-pill-printer { background: var(--pink-100); color: var(--pink-800); }
.glpi-result-grid { display: grid; grid-template-columns: repeat(auto-fill, minmax(180px, 1fr)); gap: 1rem; margin: 0 0 1.25rem; }
.glpi-result-card { padding: 1.25rem 1.5rem; border-radius: 0.5rem; text-align: center; }
.glpi-result-card .num { font-size: 2.25rem; font-weight: 800; line-height: 1; }
.glpi-result-card .lbl { font-size: 0.75rem; font-weight: 600; text-transform: uppercase; letter-spacing: 0.05em; margin-top: 0.25rem; }
.glpi-rc-imported { background: var(--emerald-100); color: var(--emerald-800); }
.glpi-rc-skipped { background: var(--amber-100); color: var(--amber-800); }
.glpi-rc-errors { background: var(--red-100); color: var(--red-800); }
.glpi-error-list { background: var(--red-50); border: 1px solid var(--red-200); border-radius: 0.5rem; padding: 0.75rem 1rem; font-size: 0.82rem; color: var(--red-900); }
.glpi-error-list li { margin-bottom: 0.25rem; }
.glpi-radio-group { display: flex; gap: 1.25rem; flex-wrap: wrap; margin-top: 0.35rem; }
.glpi-radio-group label { font-size: 0.88rem; cursor: pointer; display: flex; align-items: center; gap: 0.4rem; }
.glpi-check-group { display: flex; flex-wrap: wrap; gap: 0.75rem; margin-top: 0.35rem; }
.glpi-check-group label { display: flex; align-items: center; gap: 0.4rem; font-size: 0.88rem; cursor: pointer; }
</style>
<?php if (!empty($error)): ?>
<div class="alert alert-danger" style="margin-bottom:1rem;">
<strong><?= $this->te('Fehler:') ?></strong> <?= $this->e($error) ?>
</div>
<?php endif; ?>
<?php $flashes = $this->flash(); foreach ($flashes as $flash): ?>
<div class="alert alert-<?= $flash['type'] === 'error' ? 'danger' : $flash['type'] ?>" style="margin-bottom:1rem;">
<?= $this->e($flash['message']) ?>
</div>
<?php endforeach; ?>
<!-- ================================================================
SCHRITT 1: Konfigurationsformular
================================================================ -->
<?php if ($step === 'form'):
$sc = $saved_credentials ?? [];
$hasSaved = !empty($sc['has_saved']);
$savedAuth = $sc['auth_method'] ?? 'token';
// Effective auth method: form_data overrides saved
$effectiveAuth = $form_data['auth_method'] ?? ($hasSaved ? $savedAuth : 'token');
?>
<?php if ($hasSaved): ?>
<div style="background:var(--emerald-50);border:1px solid var(--emerald-300);border-radius:0.5rem;padding:0.75rem 1rem;margin-bottom:1rem;display:flex;align-items:center;gap:0.75rem;flex-wrap:wrap;">
<span style="font-size:1.1rem;">🔒</span>
<span style="font-size:0.88rem;color:var(--emerald-800);flex:1;">
<?= $this->t('<strong>Zugangsdaten gespeichert</strong> – Verbindung zu <code>{url}</code> ist hinterlegt. Leere Felder werden automatisch mit den gespeicherten Daten aufgefüllt.', ['url' => $this->e($sc['glpi_url'])]) ?>
</span>
<form method="POST" action="/admin/inventory/import/glpi/clear-credentials" style="margin:0;">
<input type="hidden" name="_csrf" value="<?= $this->e($csrf_token) ?>">
<button type="submit" class="btn btn-default" style="font-size:0.78rem;padding:0.25rem 0.65rem;border-color:var(--red-600);color:var(--red-600);"
onclick="return confirm('Gespeicherte GLPI-Zugangsdaten wirklich l\u00f6schen?')">
<?= $this->te('🗑 Zugangsdaten löschen') ?>
</button>
</form>
</div>
<?php endif; ?>
<div class="card card-outline card-primary mb-3">
<div class="card-header">
<h3 class="card-title"><i class="fas fa-sync-alt"></i> <?= $this->te('GLPI-Verbindung konfigurieren') ?></h3>
<p class="text-muted small mb-0"><?= $this->te('Importiert Computer, Netzwerkgeräte, Drucker und Smartphones aus GLPI in das lokale Inventar.') ?></p>
</div>
<div class="card-body">
<form method="POST" action="/admin/inventory/import/glpi/connect">
<input type="hidden" name="_csrf" value="<?= $this->e($csrf_token) ?>">
<div class="form-row-2">
<div class="form-group">
<label for="glpi_url"><?= $this->te('GLPI URL') ?> <span class="text-danger">*</span></label>
<input type="url" id="glpi_url" name="glpi_url" class="form-control"
placeholder="https://glpi.example.com"
value="<?= $this->e($form_data['glpi_url'] ?? $glpi_url ?? '') ?>"
required>
<small class="form-text text-muted"><?= $this->t('Basis-URL Ihrer GLPI-Installation (ohne <code>/apirest.php</code>)') ?></small>
</div>
<div class="form-group">
<label for="app_token"><?= $this->te('App Token') ?> <span style="font-weight:400;font-size:0.8rem;"><?= $this->te('(optional)') ?></span></label>
<input type="password" id="app_token" name="app_token" class="form-control"
placeholder="<?= $hasSaved && $sc['app_token'] !== '' ? '(gespeichert – leer lassen um beizubehalten)' : 'Falls in GLPI unter Administration → API konfiguriert' ?>"
value="">
<small class="form-text text-muted"><?= $this->t('Kann in GLPI unter <em>Administration → API</em> angelegt werden. Wird verschlüsselt gespeichert.') ?></small>
</div>
</div>
<hr style="border:none;border-top:1px solid var(--color-border,var(--gray-200));margin:1.25rem 0;">
<div class="form-group">
<label><?= $this->te('Authentifizierungsmethode') ?> <span class="text-danger">*</span></label>
<div class="glpi-radio-group">
<label>
<input type="radio" name="auth_method" value="token"
<?= $effectiveAuth !== 'credentials' ? 'checked' : '' ?>
onchange="toggleAuth(this.value)">
<?= $this->te('User Token') ?>
</label>
<label>
<input type="radio" name="auth_method" value="credentials"
<?= $effectiveAuth === 'credentials' ? 'checked' : '' ?>
onchange="toggleAuth(this.value)">
<?= $this->te('Benutzername / Passwort') ?>
</label>
</div>
</div>
<div id="auth-token" class="form-group" <?= $effectiveAuth === 'credentials' ? 'style="display:none;"' : '' ?>>
<label for="user_token"><?= $this->te('User Token') ?></label>
<input type="password" id="user_token" name="user_token" class="form-control"
placeholder="<?= $hasSaved && $sc['user_token'] !== '' ? '(gespeichert – leer lassen um beizubehalten)' : 'GLPI User Token aus Benutzerprofil → API Token' ?>"
value="">
<small class="form-text text-muted"><?= $this->t('Zu finden in GLPI unter <em>Benutzerprofil → API Token</em>. Wird verschlüsselt gespeichert.') ?></small>
</div>
<div id="auth-credentials" <?= $effectiveAuth === 'credentials' ? '' : 'style="display:none;"' ?>>
<div class="form-row-2">
<div class="form-group">
<label for="username"><?= $this->te('Benutzername') ?></label>
<input type="text" id="username" name="username" class="form-control"
placeholder="<?= $this->te('GLPI Benutzername') ?>"
value="<?= $this->e($form_data['username'] ?? $sc['username'] ?? '') ?>">
</div>
<div class="form-group">
<label for="password"><?= $this->te('Passwort') ?></label>
<input type="password" id="password" name="password" class="form-control"
placeholder="<?= $hasSaved && $sc['password'] !== '' ? '(gespeichert – leer lassen um beizubehalten)' : 'GLPI Passwort' ?>">
</div>
</div>
<small class="form-text text-muted" style="display:block;margin-top:-0.5rem;margin-bottom:1rem;"><?= $this->te('Benutzername und Passwort werden verschlüsselt gespeichert.') ?></small>
</div>
<hr style="border:none;border-top:1px solid var(--color-border,var(--gray-200));margin:1.25rem 0;">
<div class="form-row-2">
<div class="form-group">
<label for="default_status"><?= $this->te('Standard-Status für importierte Assets') ?></label>
<select name="default_status" id="default_status" class="form-control">
<option value="active"><?= $this->te('Aktiv') ?></option>
<option value="storage"><?= $this->te('Lager') ?></option>
<option value="decommissioned"><?= $this->te('Ausgemustert') ?></option>
</select>
<small class="form-text text-muted"><?= $this->te('Wird verwendet, wenn GLPI keinen auswertbaren Status liefert.') ?></small>
</div>
<div class="form-group" style="align-self:center;padding-top:1.25rem;">
<label class="checkbox-inline">
<input type="checkbox" name="skip_existing" value="1" checked>
<?= $this->te('Bereits vorhandene Assets überspringen') ?>
</label>
<small class="form-text text-muted"><?= $this->te('Erkennung per Seriennummer.') ?></small>
</div>
</div>
<div class="form-actions" style="flex-wrap:wrap;gap:0.75rem;">
<button type="submit" formaction="/admin/inventory/import/glpi/connect" class="btn btn-primary">
<?= $this->te('🔌 Verbinden & Gerätetypen laden') ?>
</button>
<button type="submit" formaction="/admin/inventory/import/glpi/locations-preview" class="btn btn-default" style="border-color:var(--blue-600);color:var(--blue-600);">
<?= $this->te('📍 Standorte aus GLPI importieren') ?>
</button>
<a href="/inventory" class="btn btn-default"><?= $this->te('Abbrechen') ?></a>
</div>
</form>
</div>
</div>
<!-- ================================================================
SCHRITT 1b: Typ-Zuordnung
================================================================ -->
<?php elseif ($step === 'type_mapping'):
$stm = $saved_type_mapping ?? [];
$ssm = $saved_status_mapping ?? [];
$hasSavedMapping = !empty($stm);
?>
<div class="card card-outline card-primary mb-3">
<div class="card-header">
<h3 class="card-title"><i class="fas fa-layer-group"></i> <?= $this->te('Gerätetypen zuordnen') ?></h3>
<p class="text-muted small mb-0">
<?= $this->te('Wählen Sie für jeden GLPI-Typ die entsprechende Inventar-Kategorie. Typen ohne Zuordnung werden nicht importiert.') ?>
<?php if ($hasSavedMapping): ?>
<span class="text-success"><i class="fas fa-check"></i> <?= $this->te('Gespeicherte Zuordnung vorausgefüllt.') ?></span>
<?php endif; ?>
</p>
</div>
<div class="card-body table-responsive p-0">
<form method="POST" action="/admin/inventory/import/glpi/preview">
<input type="hidden" name="_csrf" value="<?= $this->e($csrf_token) ?>">
<table class="data-table">
<thead>
<tr>
<th scope="col" style="width:35%;"><?= $this->te('GLPI-Typ') ?></th>
<th scope="col" style="width:10%;text-align:right;"><?= $this->te('Geräte') ?></th>
<th scope="col" style="width:55%;"><?= $this->te('Inventar-Kategorie') ?></th>
</tr>
</thead>
<tbody>
<?php foreach ($available_types as $glpiType => $count):
$label = $known_type_labels[$glpiType] ?? $glpiType;
$savedCatId = isset($stm[$glpiType]) ? (int)$stm[$glpiType] : 0;
?>
<tr>
<td style="font-weight:500;"><?= $this->e($label) ?></td>
<td style="text-align:right;font-variant-numeric:tabular-nums;color:var(--color-text-muted,var(--gray-500));"><?= number_format($count, 0, ',', '.') ?></td>
<td>
<select name="type_mapping[<?= $this->e($glpiType) ?>]" class="form-control form-control-sm">
<option value="0"<?= $savedCatId === 0 ? ' selected' : '' ?>><?= $this->te('— nicht importieren —') ?></option>
<?php foreach ($categories as $cat): ?>
<option value="<?= (int)$cat['id'] ?>"<?= $savedCatId === (int)$cat['id'] ? ' selected' : '' ?>>
<?= $this->e($cat['name']) ?>
</option>
<?php endforeach; ?>
</select>
</td>
</tr>
<?php endforeach; ?>
</tbody>
</table>
<?php if (!empty($available_states)): ?>
<div style="border-top:2px solid var(--color-border,var(--gray-200));padding:1.25rem 1.5rem 0.5rem;">
<h3 style="margin:0 0 0.25rem;font-size:0.95rem;font-weight:600;"><?= $this->te('Status zuordnen') ?></h3>
<p style="margin:0 0 1rem;font-size:0.82rem;color:var(--color-text-muted,var(--gray-500));">
<?= $this->te('Wählen Sie für jeden GLPI-Status den entsprechenden lokalen Status. Nicht zugeordnete Status verwenden den Standard-Status aus dem Formular.') ?>
</p>
</div>
<table class="data-table">
<thead>
<tr>
<th scope="col" style="width:50%;"><?= $this->te('GLPI-Status') ?></th>
<th scope="col" style="width:50%;"><?= $this->te('Lokaler Status') ?></th>
</tr>
</thead>
<tbody>
<?php foreach ($available_states as $stateName): ?>
<?php $savedStatus = $ssm[$stateName] ?? ''; ?>
<tr>
<td style="font-weight:500;"><?= $this->e($stateName) ?></td>
<td>
<select name="status_mapping[<?= $this->e($stateName) ?>]" class="form-control form-control-sm">
<option value=""<?= $savedStatus === '' ? ' selected' : '' ?>><?= $this->te('Standard (aus Formular)') ?></option>
<option value="active"<?= $savedStatus === 'active' ? ' selected' : '' ?>><?= $this->te('Aktiv') ?></option>
<option value="storage"<?= $savedStatus === 'storage' ? ' selected' : '' ?>><?= $this->te('Lager') ?></option>
<option value="decommissioned"<?= $savedStatus === 'decommissioned' ? ' selected' : '' ?>><?= $this->te('Ausgemustert') ?></option>
</select>
</td>
</tr>
<?php endforeach; ?>
</tbody>
</table>
<?php endif; ?>
<div style="display:flex;gap:0.75rem;align-items:center;flex-wrap:wrap;padding:1.25rem 1.5rem;border-top:1px solid var(--color-border,var(--gray-200));">
<button type="submit" class="btn btn-primary"><?= $this->te('🔍 Vorschau laden') ?></button>
<a href="/admin/inventory/import/glpi" class="btn btn-default"><?= $this->te('← Zurück') ?></a>
</div>
</form>
</div>
</div>
<!-- ================================================================
SCHRITT 2: Vorschau
================================================================ -->
<?php elseif ($step === 'preview'): ?>
<div class="card card-outline card-primary mb-3">
<div class="card-header">
<h3 class="card-title"><i class="fas fa-clipboard-list"></i> <?= $this->te('Import-Vorschau') ?></h3>
<p class="text-muted small mb-0">
<?= $this->t('Gefunden: <strong>{anzahl}</strong> Geräte – <strong class="text-success">{neu} neu</strong>, <strong class="text-warning">{vorhanden} bereits vorhanden</strong>', ['anzahl' => count($items), 'neu' => $new_count, 'vorhanden' => $existing_count]) ?>
<?php if ($skip_existing): ?><?= $this->te('(werden übersprungen)') ?><?php endif; ?>
·
<?= $this->t('Personen-Match: <strong>{treffer}</strong> von {anzahl}', ['treffer' => $person_match_count, 'anzahl' => count($items)]) ?>
</p>
</div>
<div class="card-body table-responsive p-0">
<?php if (empty($items)): ?>
<div class="empty-state">
<p><?= $this->te('Keine Geräte in GLPI gefunden.') ?></p>
</div>
<?php else: ?>
<div class="table-wrapper" style="border-radius:0;border:none;border-top:1px solid var(--color-border,var(--gray-200));">
<table class="data-table">
<thead>
<tr>
<th scope="col"><?= $this->te('Gerätename') ?></th>
<th scope="col"><?= $this->te('Inventarnummer') ?></th>
<th scope="col"><?= $this->te('Seriennummer') ?></th>
<th scope="col"><?= $this->te('Hersteller / Modell') ?></th>
<th scope="col"><?= $this->te('Typ') ?></th>
<th scope="col"><?= $this->te('Status') ?></th>
<th scope="col"><?= $this->te('Person (Verzeichnis)') ?></th>
<th scope="col"></th>
</tr>
</thead>
<tbody>
<?php foreach ($items as $item): ?>
<tr class="<?= $item['already_exists'] ? 'exists' : '' ?>">
<td><?= $this->e($item['name']) ?: '<span style="color:var(--color-text-muted,var(--gray-400));">—</span>' ?></td>
<td style="font-family:monospace;font-size:0.8rem;">
<?php if ($item['inventory_number'] !== ''): ?>
<?= $this->e($item['inventory_number']) ?>
<?php if ($item['inv_number_conflict']): ?>
<br><span style="font-family:sans-serif;font-size:0.7rem;color:var(--amber-800);" title="<?= $this->te('Nummer bereits vergeben – wird neu generiert') ?>"><?= $this->te('⚠ Konflikt') ?></span>
<?php endif; ?>
<?php else: ?>
<span style="color:var(--color-text-muted,var(--gray-400));font-family:sans-serif;"><?= $this->te('wird generiert') ?></span>
<?php endif; ?>
</td>
<td style="font-family:monospace;font-size:0.8rem;"><?= $this->e($item['serial_number']) ?: '<span style="color:var(--color-text-muted,var(--gray-400));">—</span>' ?></td>
<td>
<?= $this->e($item['manufacturer']) ?: '<span style="color:var(--color-text-muted,var(--gray-400));">—</span>' ?>
<?php if ($item['model']): ?>
<br><span style="color:var(--color-text-muted,var(--gray-500));font-size:0.78rem;"><?= $this->e($item['model']) ?></span>
<?php endif; ?>
</td>
<td><?= match($item['glpi_type']) {
'Computer' => '<span class="status-pill status-pill--info">Computer</span>',
'NetworkEquipment' => '<span class="status-pill status-pill--purple">Netzwerk</span>',
'Printer' => '<span class="status-pill glpi-pill-printer">Drucker</span>',
'Phone' => '<span class="status-pill status-pill--success">Smartphone</span>',
default => '<span class="status-pill status-pill--neutral">' . $this->e($item['glpi_type']) . '</span>',
} ?></td>
<td><?php
$statusLabels = ['active' => 'Aktiv', 'storage' => 'Lager', 'decommissioned' => 'Ausgemustert'];
echo $this->e($statusLabels[$item['status']] ?? $item['status']);
?></td>
<td><?php if ($item['person_match_id'] !== null): ?>
<span style="color:var(--emerald-800);font-size:0.82rem;">✓ <?= $this->e($item['person_match_name']) ?></span>
<?php elseif ($item['glpi_user_name'] !== ''): ?>
<span style="color:var(--amber-800);font-size:0.82rem;" title="<?= $this->te('GLPI: {benutzer}', ['benutzer' => $item['glpi_user_name']]) ?>">
✗ <?= $this->e(mb_substr($item['glpi_user_name'], 0, 30)) ?>
</span>
<?php else: ?>
<span style="color:var(--color-text-muted,var(--gray-400));">—</span>
<?php endif; ?></td>
<td><?php if ($item['already_exists']): ?>
<span class="status-pill status-pill--warning"><?= $this->te('Vorhanden') ?></span>
<?php else: ?>
<span class="status-pill status-pill--success"><?= $this->te('Neu') ?></span>
<?php endif; ?></td>
</tr>
<?php endforeach; ?>
</tbody>
</table>
</div>
<div style="display:flex;gap:0.75rem;align-items:center;flex-wrap:wrap;padding:1.25rem 1.5rem;border-top:1px solid var(--color-border,var(--gray-200));">
<form method="POST" action="/admin/inventory/import/glpi/run" style="display:inline;">
<input type="hidden" name="_csrf" value="<?= $this->e($csrf_token) ?>">
<button type="submit" class="btn btn-success">
✓ <?= $this->te($new_count === 1 ? '{anzahl} Asset importieren' : '{anzahl} Assets importieren', ['anzahl' => $new_count]) ?>
</button>
</form>
<form method="POST" action="/admin/inventory/import/glpi/dry-run" style="display:inline;">
<input type="hidden" name="_csrf" value="<?= $this->e($csrf_token) ?>">
<button type="submit" class="btn btn-default" style="background:var(--violet-600);color:var(--color-on-accent);border-color:var(--violet-600);">
<?= $this->te('🔬 Trockenlauf') ?>
</button>
</form>
<a href="/admin/inventory/import/glpi" class="btn btn-default"><?= $this->te('← Zurück') ?></a>
<?php if ($skip_existing && $existing_count > 0): ?>
<span style="font-size:0.8rem;color:var(--color-text-muted,var(--gray-500));">
<?= $this->te($existing_count === 1 ? '{anzahl} vorhandenes Asset wird übersprungen.' : '{anzahl} vorhandene Assets werden übersprungen.', ['anzahl' => $existing_count]) ?>
</span>
<?php endif; ?>
</div>
<?php endif; ?>
</div>
</div>
<!-- ================================================================
SCHRITT 2b: Trockenlauf-Ergebnis
================================================================ -->
<?php elseif ($step === 'dryrun'): ?>
<div class="card card-outline card-primary mb-3">
<div class="card-header">
<h3 class="card-title"><i class="fas fa-vial"></i> <?= $this->te('Trockenlauf-Ergebnis') ?></h3>
<p class="text-muted small mb-0">
<?= $this->t('Simulation ohne Datenbankänderung – <strong class="text-success">{neu} würden erstellt</strong>, <strong class="text-primary">{aktualisiert} würden aktualisiert</strong>, <strong class="text-muted">{uebersprungen} würden übersprungen</strong>.', ['neu' => $stats['create'], 'aktualisiert' => $stats['update'], 'uebersprungen' => $stats['skip']]) ?>
<?= $this->te('Keine Daten wurden gespeichert.') ?>
</p>
</div>
<div class="card-body table-responsive p-0">
<div class="table-wrapper" style="border-radius:0;border:none;border-top:1px solid var(--color-border,var(--gray-200));">
<table class="data-table">
<thead>
<tr>
<th scope="col"><?= $this->te('Aktion') ?></th>
<th scope="col"><?= $this->te('Gerätename') ?></th>
<th scope="col"><?= $this->te('Inventarnummer') ?></th>
<th scope="col"><?= $this->te('Seriennummer') ?></th>
<th scope="col"><?= $this->te('Hersteller / Modell') ?></th>
<th scope="col"><?= $this->te('Typ') ?></th>
<th scope="col"><?= $this->te('Status') ?></th>
<th scope="col"><?= $this->te('Person') ?></th>
<th scope="col"><?= $this->te('Hinweis') ?></th>
</tr>
</thead>
<tbody>
<?php foreach ($dry_run_rows as $row):
$item = $row['item'];
$actionLabel = match($row['action']) {
'create' => '<span class="status-pill status-pill--success">Erstellen</span>',
'update' => '<span class="status-pill status-pill--info">Aktualisieren</span>',
'skip' => '<span class="status-pill status-pill--neutral">Überspringen</span>',
default => $row['action'],
};
?>
<tr<?= $row['action'] === 'skip' ? ' style="opacity:0.55;"' : '' ?>>
<td><?= $actionLabel ?></td>
<td><?= $this->e($item['name']) ?: '<span style="color:var(--color-text-muted,var(--gray-400));">—</span>' ?></td>
<td style="font-family:monospace;font-size:0.8rem;"><?= $this->e($row['inventory_number']) ?: '<span style="color:var(--color-text-muted,var(--gray-400));">—</span>' ?></td>
<td style="font-family:monospace;font-size:0.8rem;"><?= $this->e($item['serial_number']) ?: '<span style="color:var(--color-text-muted,var(--gray-400));">—</span>' ?></td>
<td>
<?= $this->e($item['manufacturer']) ?: '<span style="color:var(--color-text-muted,var(--gray-400));">—</span>' ?>
<?php if ($item['model']): ?>
<br><span style="color:var(--color-text-muted,var(--gray-500));font-size:0.78rem;"><?= $this->e($item['model']) ?></span>
<?php endif; ?>
</td>
<td><?= match($item['glpi_type']) {
'Computer' => '<span class="status-pill status-pill--info">Computer</span>',
'NetworkEquipment' => '<span class="status-pill status-pill--purple">Netzwerk</span>',
'Printer' => '<span class="status-pill glpi-pill-printer">Drucker</span>',
'Phone' => '<span class="status-pill status-pill--success">Smartphone</span>',
default => '<span class="status-pill status-pill--neutral">' . $this->e($item['glpi_type']) . '</span>',
} ?></td>
<td><?php
$statusLabels = ['active' => 'Aktiv', 'storage' => 'Lager', 'decommissioned' => 'Ausgemustert'];
echo $this->e($statusLabels[$item['status']] ?? $item['status']);
?></td>
<td><?php if ($item['person_match_id'] !== null): ?>
<span style="color:var(--emerald-800);font-size:0.82rem;">✓ <?= $this->e($item['person_match_name']) ?></span>
<?php elseif ($item['glpi_user_name'] !== ''): ?>
<span style="color:var(--amber-800);font-size:0.82rem;" title="<?= $this->te('GLPI: {benutzer}', ['benutzer' => $item['glpi_user_name']]) ?>">
✗ <?= $this->e(mb_substr($item['glpi_user_name'], 0, 28)) ?>
</span>
<?php else: ?>
<span style="color:var(--color-text-muted,var(--gray-400));">—</span>
<?php endif; ?></td>
<td style="font-size:0.78rem;color:var(--color-text-muted,var(--gray-500));"><?= $this->e($row['reason']) ?></td>
</tr>
<?php endforeach; ?>
</tbody>
</table>
</div>
<div style="display:flex;gap:0.75rem;align-items:center;flex-wrap:wrap;padding:1.25rem 1.5rem;border-top:1px solid var(--color-border,var(--gray-200));">
<form method="POST" action="/admin/inventory/import/glpi/run" style="display:inline;">
<input type="hidden" name="_csrf" value="<?= $this->e($csrf_token) ?>">
<button type="submit" class="btn btn-success">
✓ <?= $this->t('Jetzt wirklich importieren') ?> <?= $this->te('({anzahl} Assets)', ['anzahl' => $stats['create'] + $stats['update']]) ?>
</button>
</form>
<a href="/admin/inventory/import/glpi" class="btn btn-default"><?= $this->te('← Zurück zum Formular') ?></a>
</div>
</div>
</div>
<!-- ================================================================
SCHRITT 3: Ergebnis
================================================================ -->
<?php elseif ($step === 'result'): ?>
<div class="card card-outline card-primary mb-3">
<div class="card-header">
<h3 class="card-title"><i class="fas fa-check-circle text-success"></i> <?= $this->te('Import abgeschlossen') ?></h3>
<p class="text-muted small mb-0"><?= $this->te('{anzahl} Assets importiert, {uebersprungen} übersprungen', ['anzahl' => $imported, 'uebersprungen' => $skipped]) ?><?= $errors > 0 ? $this->te(', {anzahl} Fehler', ['anzahl' => $errors]) : '' ?>.</p>
</div>
<div class="card-body">
<div class="glpi-result-grid">
<div class="glpi-result-card glpi-rc-imported">
<div class="num"><?= $imported ?></div>
<div class="lbl"><?= $this->te('Importiert') ?></div>
</div>
<div class="glpi-result-card glpi-rc-skipped">
<div class="num"><?= $skipped ?></div>
<div class="lbl"><?= $this->te('Übersprungen') ?></div>
</div>
<?php if ($errors > 0): ?>
<div class="glpi-result-card glpi-rc-errors">
<div class="num"><?= $errors ?></div>
<div class="lbl"><?= $this->te('Fehler') ?></div>
</div>
<?php endif; ?>
<?php if (($location_matched ?? 0) + ($location_missing ?? 0) > 0): ?>
<div class="glpi-result-card" style="background:var(--blue-50);color:var(--blue-800);">
<div class="num"><?= $location_matched ?? 0 ?></div>
<div class="lbl"><?= $this->te('Standorte gesetzt') ?></div>
</div>
<?php if (($location_missing ?? 0) > 0): ?>
<div class="glpi-result-card" style="background:var(--amber-100);color:var(--amber-800);">
<div class="num"><?= $location_missing ?? 0 ?></div>
<div class="lbl"><?= $this->te('Standort nicht gefunden') ?></div>
</div>
<?php endif; ?>
<?php endif; ?>
</div>
<?php if (!empty($error_messages)): ?>
<div class="glpi-error-list">
<strong><?= $this->te('Fehlerdetails:') ?></strong>
<ul style="margin:0.5rem 0 0;padding-left:1.25rem;">
<?php foreach ($error_messages as $msg): ?>
<li><?= $this->e($msg) ?></li>
<?php endforeach; ?>
</ul>
</div>
<?php endif; ?>
<div class="form-actions">
<a href="/inventory" class="btn btn-primary"><?= $this->te('Zum Inventar') ?></a>
<a href="/admin/inventory/import/glpi" class="btn btn-default"><?= $this->te('Erneut importieren') ?></a>
</div>
</div>
</div>
<!-- ================================================================
STANDORTE – Vorschau
================================================================ -->
<?php elseif ($step === 'locations_preview'): ?>
<div class="card card-outline card-primary mb-3">
<div class="card-header">
<h3 class="card-title"><i class="fas fa-map-marker-alt"></i> <?= $this->te('Standorte aus GLPI – Vorschau') ?></h3>
<p class="text-muted small mb-0">
<?= $this->t('{gesamt} Standorte gefunden – <strong class="text-success">{neu} neu</strong>, <strong class="text-warning">{vorhanden} bereits vorhanden</strong> (werden nicht verändert). Wählen Sie, welche Standorte angelegt werden sollen.', ['gesamt' => count($location_rows), 'neu' => $new_count, 'vorhanden' => $existing_count]) ?>
</p>
</div>
<div class="card-body table-responsive p-0">
<?php if (empty($location_rows)): ?>
<div class="empty-state">
<p><?= $this->te('Keine Standorte in GLPI gefunden.') ?></p>
</div>
<?php else: ?>
<form method="POST" action="/admin/inventory/import/glpi/locations-run">
<input type="hidden" name="_csrf" value="<?= $this->e($csrf_token) ?>">
<div class="table-wrapper" style="border-radius:0;border:none;border-top:1px solid var(--color-border,var(--gray-200));">
<table class="data-table">
<thead>
<tr>
<th scope="col" style="width:2.5rem;">
<input type="checkbox" id="loc-select-all" title="<?= $this->te('Alle (de)selektieren') ?>" checked>
</th>
<th scope="col"><?= $this->te('Standort') ?></th>
<th scope="col"><?= $this->te('Vollständiger Pfad') ?></th>
<th scope="col"></th>
</tr>
</thead>
<tbody>
<?php foreach ($location_rows as $row): ?>
<tr<?= $row['already_exists'] ? ' style="opacity:0.55;"' : '' ?>>
<td>
<?php if (!$row['already_exists']): ?>
<input type="checkbox" name="selected_locations[]"
value="<?= (int)$row['glpi_id'] ?>"
class="loc-checkbox" checked>
<?php endif; ?>
</td>
<td style="padding-left: <?= 0.85 + $row['depth'] * 1.5 ?>rem; font-weight: <?= $row['depth'] === 0 ? '600' : '400' ?>;">
<?php if ($row['depth'] > 0): ?>
<span style="color:var(--color-text-muted,var(--gray-400));margin-right:0.25rem;"><?= str_repeat('└ ', 1) ?></span>
<?php endif; ?>
<?= $this->e($row['name']) ?>
</td>
<td style="color:var(--color-text-muted,var(--gray-500));font-size:0.78rem;">
<?= $this->e($row['completename']) ?>
</td>
<td>
<?php if ($row['already_exists']): ?>
<span class="status-pill status-pill--warning"><?= $this->te('Vorhanden') ?></span>
<?php else: ?>
<span class="status-pill status-pill--success"><?= $this->te('Neu') ?></span>
<?php endif; ?>
</td>
</tr>
<?php endforeach; ?>
</tbody>
</table>
</div>
<div style="display:flex;gap:0.75rem;align-items:center;flex-wrap:wrap;padding:1.25rem 1.5rem;border-top:1px solid var(--color-border,var(--gray-200));">
<button type="submit" class="btn btn-success">
📍 <?= $this->te($new_count === 1 ? '{anzahl} Standort anlegen' : '{anzahl} Standorte anlegen', ['anzahl' => $new_count]) ?>
</button>
<a href="/admin/inventory/import/glpi" class="btn btn-default"><?= $this->te('← Zurück') ?></a>
<span style="font-size:0.8rem;color:var(--color-text-muted,var(--gray-500));">
<?= $this->te('Bereits vorhandene Standorte werden nicht verändert. Eltern-Standorte müssen mit ihren Kindern zusammen ausgewählt werden.') ?>
</span>
</div>
</form>
<?php endif; ?>
</div>
</div>
<!-- ================================================================
STANDORTE – Ergebnis
================================================================ -->
<?php elseif ($step === 'locations_result'): ?>
<div class="card card-outline card-primary mb-3">
<div class="card-header">
<h3 class="card-title"><i class="fas fa-map-marker-alt text-success"></i> <?= $this->te('Standorte importiert') ?></h3>
<p class="text-muted small mb-0">
<?= $this->te($imported === 1 ? '{anzahl} Standort angelegt' : '{anzahl} Standorte angelegt', ['anzahl' => $imported]) ?>,
<?= $this->te('{anzahl} übersprungen', ['anzahl' => $skipped]) ?><?= $errors > 0 ? $this->te(', {anzahl} Fehler', ['anzahl' => $errors]) : '' ?>.
</p>
</div>
<div class="card-body">
<div class="glpi-result-grid">
<div class="glpi-result-card glpi-rc-imported">
<div class="num"><?= $imported ?></div>
<div class="lbl"><?= $this->te('Angelegt') ?></div>
</div>
<div class="glpi-result-card glpi-rc-skipped">
<div class="num"><?= $skipped ?></div>
<div class="lbl"><?= $this->te('Übersprungen') ?></div>
</div>
<?php if ($errors > 0): ?>
<div class="glpi-result-card glpi-rc-errors">
<div class="num"><?= $errors ?></div>
<div class="lbl"><?= $this->te('Fehler') ?></div>
</div>
<?php endif; ?>
</div>
<?php if (!empty($error_messages)): ?>
<div class="glpi-error-list">
<strong><?= $this->te('Fehlerdetails:') ?></strong>
<ul style="margin:0.5rem 0 0;padding-left:1.25rem;">
<?php foreach ($error_messages as $msg): ?>
<li><?= $this->e($msg) ?></li>
<?php endforeach; ?>
</ul>
</div>
<?php endif; ?>
<div class="form-actions">
<a href="/admin/locations" class="btn btn-primary"><?= $this->te('Zu den Standorten') ?></a>
<a href="/admin/inventory/import/glpi" class="btn btn-default"><?= $this->te('Assets importieren') ?></a>
</div>
</div>
</div>
<?php endif; ?>
<script>
function toggleAuth(value) {
document.getElementById('auth-token').style.display = value === 'token' ? '' : 'none';
document.getElementById('auth-credentials').style.display = value === 'credentials' ? '' : 'none';
}
(function () {
var checked = document.querySelector('input[name="auth_method"]:checked');
if (checked) toggleAuth(checked.value);
var selectAll = document.getElementById('loc-select-all');
if (selectAll) {
selectAll.addEventListener('change', function () {
document.querySelectorAll('.loc-checkbox').forEach(function (cb) {
cb.checked = selectAll.checked;
});
});
}
})();
</script>