Kontext / Ziel
Diese PR liefert den einzigen kanonischen Onboarding-Entry unter /admin/onboarding (workspace-first, tenantless bis zur Aktivierung) und ergänzt einen tenantless OperationRun-Viewer unter /admin/operations/{run} mit membership→404 Semantik.
Was ist enthalten?
Single entry point: /admin/onboarding ist der einzige Einstieg; Legacy Entry Points liefern echte 404 (keine Redirects).
Wizard v1 (Enterprise): idempotentes Identifizieren eines Managed Tenants (per Entra Tenant ID), resumable Session-Flow.
Provider Connection Step: Auswahl oder Erstellung, Secrets werden nie erneut gerendert / nicht in Session-State persistiert.
Verification als OperationRun: async/queued, DB-only Rendering im Wizard (keine Graph-Calls beim Rendern).
Tenantless Run Viewing: /admin/operations/{run} funktioniert ohne ausgewählten Workspace/Tenant, aber bleibt über Workspace-Mitgliedschaft autorisiert (non-member → 404).
RBAC-UX Semantik: non-member → 404, member ohne Capability → UI disabled + tooltip, server-side Action → 403.
Auditability: Aktivierung/Overrides sind auditierbar, stable action IDs, keine Secrets.
Tech / Version-Safety
Filament v5 / Livewire v4.0+ kompatibel.
Laravel 11+: Panel Provider Registrierung in providers.php (unverändert).
Tests / Format
vendor/bin/sail bin pint --dirty
Full suite: vendor/bin/sail artisan test --no-ansi → 984 passed, 5 skipped (exit 0)
Ops / Deployment Notes
Keine zusätzlichen Services vorausgesetzt.
Falls Assets registriert wurden: Deployment weiterhin mit php artisan filament:assets (wie üblich im Projekt).
Co-authored-by: Ahmed Darrazi <ahmeddarrazi@adsmac.fritz.box>
Co-authored-by: Ahmed Darrazi <ahmeddarrazi@MacBookPro.fritz.box>
Reviewed-on: #90
137 lines
6.0 KiB
PHP
137 lines
6.0 KiB
PHP
@php
|
|
$fieldWrapperView = $getFieldWrapperView();
|
|
|
|
$run = $run ?? null;
|
|
$run = is_array($run) ? $run : null;
|
|
|
|
$runUrl = $runUrl ?? null;
|
|
$runUrl = is_string($runUrl) && $runUrl !== '' ? $runUrl : null;
|
|
|
|
$status = $run['status'] ?? null;
|
|
$status = is_string($status) ? $status : null;
|
|
|
|
$outcome = $run['outcome'] ?? null;
|
|
$outcome = is_string($outcome) ? $outcome : null;
|
|
|
|
$targetScope = $run['target_scope'] ?? [];
|
|
$targetScope = is_array($targetScope) ? $targetScope : [];
|
|
|
|
$failures = $run['failures'] ?? [];
|
|
$failures = is_array($failures) ? $failures : [];
|
|
|
|
$completedAt = $run['completed_at'] ?? null;
|
|
$completedAt = is_string($completedAt) && $completedAt !== '' ? $completedAt : null;
|
|
|
|
$completedAtLabel = null;
|
|
|
|
if ($completedAt !== null) {
|
|
try {
|
|
$completedAtLabel = \Carbon\CarbonImmutable::parse($completedAt)->format('Y-m-d H:i');
|
|
} catch (\Throwable) {
|
|
$completedAtLabel = $completedAt;
|
|
}
|
|
}
|
|
@endphp
|
|
|
|
<x-dynamic-component :component="$fieldWrapperView" :field="$field">
|
|
<div class="space-y-4">
|
|
<x-filament::section
|
|
heading="Verification report"
|
|
:description="$completedAtLabel ? ('Completed: ' . $completedAtLabel) : 'Stored details for the latest verification run.'"
|
|
>
|
|
@if ($run === null)
|
|
<div class="text-sm text-gray-600 dark:text-gray-300">
|
|
No verification run has been started yet.
|
|
</div>
|
|
@elseif ($status !== 'completed')
|
|
<div class="text-sm text-gray-600 dark:text-gray-300">
|
|
Report unavailable while the run is in progress. Use “Refresh” to re-check stored status.
|
|
</div>
|
|
@elseif ($outcome === 'succeeded')
|
|
<div class="text-sm text-gray-700 dark:text-gray-200">
|
|
All verification checks passed.
|
|
</div>
|
|
@elseif ($failures === [])
|
|
<div class="text-sm text-gray-600 dark:text-gray-300">
|
|
Report unavailable. The run completed, but no failure details were recorded.
|
|
</div>
|
|
@else
|
|
<div class="space-y-3">
|
|
<div class="text-sm font-medium text-gray-900 dark:text-white">
|
|
Findings
|
|
</div>
|
|
|
|
<ul class="space-y-2 text-sm text-gray-700 dark:text-gray-200">
|
|
@foreach ($failures as $failure)
|
|
@php
|
|
$reasonCode = is_array($failure) ? ($failure['reason_code'] ?? null) : null;
|
|
$message = is_array($failure) ? ($failure['message'] ?? null) : null;
|
|
|
|
$reasonCode = is_string($reasonCode) && $reasonCode !== '' ? $reasonCode : null;
|
|
$message = is_string($message) && $message !== '' ? $message : null;
|
|
@endphp
|
|
|
|
@if ($reasonCode !== null || $message !== null)
|
|
<li class="rounded-lg border border-gray-200 bg-white p-3 dark:border-gray-800 dark:bg-gray-900">
|
|
@if ($reasonCode !== null)
|
|
<div class="text-xs font-semibold uppercase tracking-wide text-gray-500 dark:text-gray-400">
|
|
{{ $reasonCode }}
|
|
</div>
|
|
@endif
|
|
@if ($message !== null)
|
|
<div class="mt-1 text-sm text-gray-700 dark:text-gray-200">
|
|
{{ $message }}
|
|
</div>
|
|
@endif
|
|
</li>
|
|
@endif
|
|
@endforeach
|
|
</ul>
|
|
</div>
|
|
@endif
|
|
|
|
@if ($targetScope !== [])
|
|
<div class="mt-4">
|
|
<div class="text-xs font-semibold uppercase tracking-wide text-gray-500 dark:text-gray-400">
|
|
Target scope
|
|
</div>
|
|
<div class="mt-2 flex flex-col gap-1 text-sm text-gray-700 dark:text-gray-200">
|
|
@php
|
|
$entraTenantId = $targetScope['entra_tenant_id'] ?? null;
|
|
$entraTenantName = $targetScope['entra_tenant_name'] ?? null;
|
|
|
|
$entraTenantId = is_string($entraTenantId) && $entraTenantId !== '' ? $entraTenantId : null;
|
|
$entraTenantName = is_string($entraTenantName) && $entraTenantName !== '' ? $entraTenantName : null;
|
|
@endphp
|
|
|
|
@if ($entraTenantName !== null)
|
|
<div>
|
|
<span class="text-gray-500 dark:text-gray-400">Entra tenant:</span>
|
|
<span class="font-medium text-gray-900 dark:text-gray-100">{{ $entraTenantName }}</span>
|
|
</div>
|
|
@endif
|
|
|
|
@if ($entraTenantId !== null)
|
|
<div>
|
|
<span class="text-gray-500 dark:text-gray-400">Entra tenant ID:</span>
|
|
<span class="font-medium text-gray-900 dark:text-gray-100">{{ $entraTenantId }}</span>
|
|
</div>
|
|
@endif
|
|
</div>
|
|
</div>
|
|
@endif
|
|
|
|
@if ($runUrl !== null)
|
|
<div class="mt-4">
|
|
<a
|
|
href="{{ $runUrl }}"
|
|
class="text-sm font-medium text-primary-600 hover:underline dark:text-primary-400"
|
|
>
|
|
Open run details
|
|
</a>
|
|
</div>
|
|
@endif
|
|
</x-filament::section>
|
|
</div>
|
|
</x-dynamic-component>
|