## Summary - introduce the Provider Connection Filament resource (list/create/edit) with DB-only controls, grouped action dropdowns, and badge-driven status/health rendering - wire up the provider foundation stack (migrations, models, policies, providers, operations, badges, and audits) plus the required spec docs/checklists - standardize Inventory Sync notifications so the job no longer writes its own DB rows; terminal notifications now flow exclusively through OperationRunCompleted while the start surface still shows the queued toast ## Testing - ./vendor/bin/sail php ./vendor/bin/pint --dirty - ./vendor/bin/sail artisan test tests/Unit/Badges/ProviderConnectionBadgesTest.php - ./vendor/bin/sail artisan test tests/Feature/ProviderConnections tests/Feature/Filament/ProviderConnectionsDbOnlyTest.php - ./vendor/bin/sail artisan test tests/Feature/Inventory/RunInventorySyncJobTest.php tests/Feature/Inventory/InventorySyncStartSurfaceTest.php Co-authored-by: Ahmed Darrazi <ahmeddarrazi@MacBookPro.fritz.box> Reviewed-on: #73
138 lines
3.6 KiB
PHP
138 lines
3.6 KiB
PHP
<?php
|
|
|
|
namespace App\Services\Providers;
|
|
|
|
use App\Models\ProviderConnection;
|
|
use App\Services\Graph\GraphContractRegistry;
|
|
use App\Services\Graph\GraphResponse;
|
|
use App\Services\Providers\Contracts\ProviderComplianceCollector;
|
|
use RuntimeException;
|
|
|
|
final class MicrosoftComplianceSnapshotService implements ProviderComplianceCollector
|
|
{
|
|
private const int MAX_PAGES = 50;
|
|
|
|
public function __construct(
|
|
private readonly ProviderGateway $gateway,
|
|
private readonly GraphContractRegistry $contracts,
|
|
) {}
|
|
|
|
public function snapshot(ProviderConnection $connection): array
|
|
{
|
|
$resource = $this->contracts->resourcePath('managedDevices');
|
|
|
|
if (! is_string($resource) || $resource === '') {
|
|
throw new RuntimeException('Graph contract missing for managed devices.');
|
|
}
|
|
|
|
$queryInput = [
|
|
'$top' => 999,
|
|
'$select' => 'id,complianceState',
|
|
];
|
|
|
|
$sanitized = $this->contracts->sanitizeQuery('managedDevices', $queryInput);
|
|
$query = $sanitized['query'];
|
|
|
|
$counts = [
|
|
'total' => 0,
|
|
'compliant' => 0,
|
|
'noncompliant' => 0,
|
|
'unknown' => 0,
|
|
];
|
|
|
|
$path = $resource;
|
|
$pages = 0;
|
|
|
|
while (true) {
|
|
$pages++;
|
|
|
|
if ($pages > self::MAX_PAGES) {
|
|
throw new RuntimeException('Graph pagination exceeded maximum page limit.');
|
|
}
|
|
|
|
$options = $query === [] ? [] : ['query' => $query];
|
|
|
|
$response = $this->gateway->request(
|
|
connection: $connection,
|
|
method: 'GET',
|
|
path: $path,
|
|
options: $options,
|
|
);
|
|
|
|
$payload = $this->requireSuccess($response);
|
|
|
|
$items = $payload['value'] ?? [];
|
|
if (! is_array($items)) {
|
|
$items = [];
|
|
}
|
|
|
|
foreach ($items as $item) {
|
|
if (! is_array($item)) {
|
|
continue;
|
|
}
|
|
|
|
$counts['total']++;
|
|
|
|
$state = strtolower((string) ($item['complianceState'] ?? ''));
|
|
|
|
if ($state === 'compliant') {
|
|
$counts['compliant']++;
|
|
} elseif ($state === 'noncompliant') {
|
|
$counts['noncompliant']++;
|
|
} else {
|
|
$counts['unknown']++;
|
|
}
|
|
}
|
|
|
|
$nextLink = $payload['@odata.nextLink'] ?? null;
|
|
|
|
if (! is_string($nextLink) || $nextLink === '') {
|
|
break;
|
|
}
|
|
|
|
$path = $nextLink;
|
|
$query = [];
|
|
}
|
|
|
|
return $counts;
|
|
}
|
|
|
|
/**
|
|
* @return array<string, mixed>
|
|
*/
|
|
private function requireSuccess(GraphResponse $response): array
|
|
{
|
|
if ($response->successful()) {
|
|
$data = $response->data;
|
|
|
|
return is_array($data) ? $data : [];
|
|
}
|
|
|
|
$message = $this->messageForResponse($response);
|
|
$status = (int) ($response->status ?? 0);
|
|
|
|
throw new RuntimeException("Graph request failed (status {$status}): {$message}");
|
|
}
|
|
|
|
private function messageForResponse(GraphResponse $response): string
|
|
{
|
|
$error = $response->errors[0] ?? null;
|
|
|
|
if (is_string($error)) {
|
|
return $error;
|
|
}
|
|
|
|
if (is_array($error)) {
|
|
$message = $error['message'] ?? null;
|
|
|
|
if (is_string($message) && $message !== '') {
|
|
return $message;
|
|
}
|
|
|
|
return json_encode($error, JSON_UNESCAPED_SLASHES | JSON_UNESCAPED_UNICODE) ?: 'Request failed.';
|
|
}
|
|
|
|
return 'Request failed.';
|
|
}
|
|
}
|