907 lines
52 KiB
Markdown
907 lines
52 KiB
Markdown
---
|
||
description: "Task list for TenantPilot v1 implementation"
|
||
---
|
||
|
||
# Tasks: TenantPilot v1
|
||
|
||
**Input**: Design documents from `.specify/spec.md` and `.specify/plan.md`
|
||
**Prerequisites**: plan.md (complete), spec.md (complete)
|
||
|
||
**Status snapshot**
|
||
- Done: Phases 1–13 (US1–US4, Settings normalization/display, Highlander, US6 permissions/health, housekeeping/UX, ops)
|
||
- Next up: Phase 14 (US7) delegated Intune RBAC onboarding wizard (synchronous)
|
||
- Upcoming: Phase 15 (US8) Graph Contract Registry & Drift Guard
|
||
|
||
---
|
||
|
||
## Phase 1: Setup (Shared Infrastructure)
|
||
|
||
- [x] T001 [P] [Shared] Confirm Sail/Env ready; ensure `.env` has PostgreSQL settings for Sail and Filament admin user seeded (if missing) in `database/seeders/`.
|
||
- [x] T002 [P] [Shared] Add baseline docs for local dev and staging promotion notes in `README.md` (Sail commands, staging-before-prod reminder).
|
||
|
||
## Phase 2: Foundational (Blocking Prerequisites)
|
||
|
||
- [x] T003 [Shared] Add tenant-aware migrations for `tenants`, `policies`, `policy_versions`, `backup_sets`, `backup_items`, `restore_runs`, `audit_logs` with JSONB payloads and FK/time indexes in `database/migrations/`.
|
||
- [x] T004 [Shared] Create models with relationships and guarded attributes for the above entities in `app/Models/`.
|
||
- [x] T005 [Shared] Implement Graph abstraction contracts (`GraphClientInterface`, error mapping, logging hooks) in `app/Services/Graph/` with a mockable adapter.
|
||
- [x] T006 [Shared] Add audit logging service/helper to capture actor, tenant, operation, resources, outcome in `app/Services/Intune/AuditLogger.php`.
|
||
- [x] T007 [Shared] Seed supported policy types/metadata for initial scope in `database/seeders/PoliciesSeeder.php` and ensure tenant scoping.
|
||
|
||
## Phase 3: User Story 1 - Policy inventory listing (Priority: P1)
|
||
|
||
### Tests for User Story 1
|
||
|
||
- [x] T008 [P] [US1] Feature test for Filament policy listing and filtering (tenant-scoped) in `tests/Feature/Filament/PolicyListingTest.php` using mocked Graph sync.
|
||
- [x] T176 [Scope][US1] Add Settings Catalog Policies as first-class type (`settingsCatalogPolicy`)
|
||
- **Goal**: Intune **Settings Catalog Policies** werden als **eigener Typ** synchronisiert, angezeigt und sind für Backup/Version/Diff/Preview/Restore (gemäß Matrix) korrekt routbar.
|
||
- **Why**: Settings Catalog Policies liegen in Graph unter `deviceManagement/configurationPolicies` (nicht unter `deviceManagement/deviceConfigurations`). Aktuell erscheinen sie daher nicht (oder nur unvollständig).
|
||
|
||
## Implementation
|
||
1) **Config: supported_types erweitern (Single Source of Truth)**
|
||
- In `config/tenantpilot.php` (oder eurem zentralen Type-Registry-File) neuen Typ hinzufügen:
|
||
- `key`: `settingsCatalogPolicy`
|
||
- `name`: `Settings Catalog Policy`
|
||
- `graph_resource`: `deviceManagement/configurationPolicies`
|
||
- `category`: `Configuration`
|
||
- `platform`: `windows` *(oder `all` + später per snapshot/@odata ableiten – je nach eurer Modelllogik)*
|
||
- UI-Label so wählen, dass Admin sofort erkennt: **“Settings Catalog”** (z. B. Badge/Label).
|
||
|
||
2) **Restore-Matrix erweitern**
|
||
- In eurer Restore-Konfig (`scope.restore_matrix` bzw. config-driven Matrix):
|
||
- `settingsCatalogPolicy: backup: full, restore: enabled, risk: medium` *(optional `medium-high` falls ihr strenger sein wollt)*
|
||
- Restore-Warnungen/Badges müssen den neuen Typ korrekt anzeigen.
|
||
|
||
3) **Graph Contract Registry erweitern**
|
||
- In `config/graph_contracts.php` Contract für `settingsCatalogPolicy` hinzufügen:
|
||
- Resource paths (collection + single item)
|
||
- `allowed_select`/`allowed_expand` (konservativ starten)
|
||
- `type_family` / erlaubte `@odata.type` Werte für diesen Typ
|
||
- Create/Update routing (`POST`/`PATCH` wie bei euren anderen Typen)
|
||
- Sicherstellen, dass **capability fallback** (downgrade ohne `$select/$expand`) auch hier greift.
|
||
|
||
4) **PolicySyncService erweitern**
|
||
- Sync-Pipeline muss zusätzlich `deviceManagement/configurationPolicies` abfragen und upserten:
|
||
- `policies.type_key = settingsCatalogPolicy`
|
||
- `external_id = Graph id`
|
||
- `display_name`, `description`, `last_modified`, etc.
|
||
- Tenant-scoping beibehalten.
|
||
- **No duplicates**: gleiche `external_id` darf nicht in zwei Typen landen (Unique/Guard prüfen).
|
||
|
||
5) **Snapshots / Settings availability**
|
||
- Für die Spalte/Badge **“Settings”** (Available/Missing):
|
||
- Snapshot-Fetch muss für `settingsCatalogPolicy` über den neuen Endpoint laufen (single item fetch).
|
||
- Normalizer/Validator:
|
||
- `@odata.type` muss für diesen Typ als kompatibel erkannt werden (über Contract/type-family).
|
||
|
||
6) **UI (Filament)**
|
||
- `PolicyResource`:
|
||
- Type/Category Filter um `Settings Catalog Policy` erweitern
|
||
- Optional: Category bleibt `Configuration`, aber Typ klar `Settings Catalog`
|
||
- Detailseite:
|
||
- Normalized Settings anzeigen (wenn euer Normalizer Settings Catalog schon kann)
|
||
- sonst mind.: **Raw JSON + Hinweis** “Settings Catalog normalization pending” (kein silent fail).
|
||
|
||
7) **Permissions/Health**
|
||
- Verify/Permissions-Liste prüfen, ob für `deviceManagement/configurationPolicies` zusätzliche Graph-Permissions nötig sind.
|
||
- Falls ja:
|
||
- `config/intune_permissions.php` ergänzen
|
||
- Health Panel zeigt fehlende Permission sauber an.
|
||
|
||
## Tests (Pest)
|
||
- **Unit**:
|
||
- Contract Registry erkennt `settingsCatalogPolicy`
|
||
- type-family ok (derived `@odata.type` accepted)
|
||
- fallback ok (capability downgrade)
|
||
- **Feature**:
|
||
- Policy Sync importiert `configurationPolicies` als `settingsCatalogPolicy` und listet sie in der UI
|
||
- Settings badge wird **Available**, sobald Snapshot vorhanden ist
|
||
- **Regression**:
|
||
- `deviceConfiguration` Sync bleibt unverändert (keine Vermischung)
|
||
|
||
## Verification
|
||
- `./vendor/bin/pest tests/Feature/Filament/PolicyListingTest.php`
|
||
- ggf. neue Tests:
|
||
- `./vendor/bin/pest tests/Feature/Filament/SettingsCatalogPolicySyncTest.php`
|
||
- Registry Tests erweitern:
|
||
- `./vendor/bin/pest tests/Unit/GraphContractRegistryTest.php`
|
||
|
||
## Acceptance Criteria
|
||
- In der Policies-Liste erscheinen Intune **Settings Catalog Policies** als eigener Typ **Settings Catalog Policy**.
|
||
- Admin kann danach **Backup/Version/Preview/Restore** (gemäß Matrix) für diesen Typ nutzen.
|
||
- **Keine Duplikate/Überlappung** mit `deviceConfiguration`.
|
||
- [x] T177 [US4][Bugfix] Settings Catalog Restore: Graph-Fehlerdetails speichern + PATCH-Payload sanitizen (contract-driven)
|
||
- **Goal**: Restore von `settingsCatalogPolicy` soll nicht mehr als generisches `400 Graph apply failed` enden, sondern:
|
||
1) echte Graph-Fehlerdetails persistieren + im UI sichtbar machen
|
||
2) beim PATCH nur ein zulässiges Payload senden (read-only/meta Felder raus, whitelist/contract-driven)
|
||
- **Why**: `deviceManagement/configurationPolicies` akzeptiert beim PATCH i. d. R. keinen vollständigen Snapshot → read-only Felder führen zu 400.
|
||
|
||
**Implementation**
|
||
1) **RestoreRun Results verbessern (Fehlerdetails persistieren)**
|
||
- In `RestoreService` (oder zentralem Graph-Apply Catch):
|
||
- Bei Graph-Exception zusätzlich in `restore_run_item_results`/`results` JSON speichern:
|
||
- `graph_error_code`
|
||
- `graph_error_message`
|
||
- optional (falls vorhanden): `graph_request_id`, `graph_client_request_id`, `graph_date`
|
||
- UI (RestoreRun Detail) soll bei failed Items neben `code/reason` auch `graph_error_message` anzeigen (kurz) + “Details” (expand/collapsible) für request ids.
|
||
|
||
2) **Contract Registry: update sanitizer für settingsCatalogPolicy**
|
||
- In `config/graph_contracts.php` bei `settingsCatalogPolicy` ergänzen:
|
||
- entweder `update_whitelist` (preferred) **oder** `update_strip_keys`
|
||
- `update_whitelist` konservativ starten (nur Felder, die PATCH typischerweise akzeptiert), z. B.:
|
||
- `name`, `description`, `settings`, `technologies`, `platforms`, `roleScopeTagIds`
|
||
- `assignments` **nur** wenn Restore wirklich Assignments patcht (sonst weglassen)
|
||
- In `GraphContractRegistry` (oder äquivalent) Methode bereitstellen:
|
||
- `sanitizeUpdatePayload(string $typeKey, array $snapshot): array`
|
||
- Entfernt immer: `id`, `createdDateTime`, `lastModifiedDateTime`, `@odata.*`, `version`, `roleScopeTagIds@odata.*`, sowie unbekannte Keys
|
||
- In `RestoreService` beim UPDATE/PATCH:
|
||
- für `settingsCatalogPolicy` vor dem Graph PATCH immer `sanitizeUpdatePayload()` verwenden.
|
||
|
||
3) **Graph apply: bessere Diagnose im Audit**
|
||
- Audit-Event (z. B. `restore.item.failed`) soll zusätzlich `graph_error_code` + `graph_request_id` enthalten (keine Tokens/payloads).
|
||
|
||
**Tests (Pest)**
|
||
- Unit: `GraphContractRegistry` sanitizer
|
||
- Given snapshot mit read-only/meta Feldern → sanitized payload enthält nur whitelist
|
||
- Feature: Restore execution für settingsCatalogPolicy mit “bad payload”
|
||
- Mock Graph 400 mit error body → RestoreRun result speichert `graph_error_message` + IDs
|
||
- UI assertion: Fehlermeldung sichtbar (kurz) + Details optional
|
||
|
||
**Verification**
|
||
- `./vendor/bin/pest tests/Unit/GraphContractRegistryTest.php`
|
||
- `./vendor/bin/pest tests/Feature/Filament/RestoreExecutionTest.php` (oder neues `SettingsCatalogPolicyRestoreTest.php`)
|
||
- Manuell: RestoreRun detail zeigt bei 400 die echte Graph-Fehlermeldung + request-id; kein generisches “apply failed” ohne Details.
|
||
|
||
**Acceptance Criteria**
|
||
- Restore von `settingsCatalogPolicy` nutzt PATCH mit sanitiziertem Payload.
|
||
- Bei Fehlern ist im RestoreRun klar ersichtlich *warum* (Graph error message), inkl. request ids für Support.
|
||
|
||
- [x] T178 [US4][Bugfix] Settings Catalog Restore: PATCH strikt auf {name, description, settings} begrenzen + Property-Mapping (displayName→name) + case-insensitive strip
|
||
- **Problem**:
|
||
- Restore von `settingsCatalogPolicy` schlägt mit 400 fehl:
|
||
- “Invalid patch, attempting to patch property Platforms is not allowed. Valid properties are Name, Description, and Settings.”
|
||
- Sanitizer lässt `platforms/Platforms` noch durch und/oder es wird `displayName` statt `name` gepatcht.
|
||
- **Implementation**:
|
||
1) **Contract fix** (`config/graph_contracts.php`)
|
||
- Für `settingsCatalogPolicy` `update_whitelist` auf exakt:
|
||
- `name`, `description`, `settings`
|
||
- Optional: `update_map` definieren:
|
||
- `displayName` → `name`
|
||
- (und ggf. `Description`/`Settings` casing normalisieren)
|
||
2) **Sanitizer hardening** (`app/Services/Graph/GraphContractRegistry.php`)
|
||
- Whitelist/Strip **case-insensitive** anwenden (z. B. `Platforms`, `platforms`, `PlatformS` immer entfernen).
|
||
- Vor dem Final-Payload:
|
||
- Mapping anwenden (displayName→name)
|
||
- Blocklist zusätzlich hart erzwingen: `platforms`, `technologies`, `templateReference`, `id`, `@odata.*`, `createdDateTime`, `lastModifiedDateTime`
|
||
- Ergebnis-Payload für update muss **nur** `name/description/settings` enthalten.
|
||
3) **RestoreService** (`app/Services/Intune/RestoreService.php`)
|
||
- Sicherstellen, dass für `settingsCatalogPolicy` Update-Payload aus Sanitizer kommt (kein “merge back” später).
|
||
- Bei leerem Payload: als `noop`/`skipped` behandeln statt PATCH.
|
||
- **Tests (Pest)**:
|
||
- Unit: Sanitizer entfernt `platforms/Platforms` zuverlässig + mapping `displayName→name`:
|
||
- `tests/Unit/GraphContractRegistryTest.php` (erweitern)
|
||
- Feature: Restore Settings Catalog erzeugt PATCH ohne platforms und läuft durch (Graph mocked):
|
||
- `tests/Feature/Filament/SettingsCatalogRestoreTest.php` (happy-path ergänzen)
|
||
- **Verification**:
|
||
- `./vendor/bin/pest tests/Unit/GraphContractRegistryTest.php tests/Feature/Filament/SettingsCatalogRestoreTest.php`
|
||
- `./vendor/bin/pint --dirty`
|
||
- **Acceptance**:
|
||
- Restore von `settingsCatalogPolicy` scheitert nicht mehr an `Platforms`.
|
||
- Results zeigen bei Fehlern weiterhin request-id/client-request-id (bleibt wie T177).
|
||
|
||
- [ ] T179 [US1b][Scope][settingsCatalogPolicy] Hydrate Settings Catalog “Configuration settings” for snapshots + normalized display
|
||
|
||
- **Goal:** Für `settingsCatalogPolicy` sollen die **Configuration settings** (wie im Intune Portal unter *Configuration settings*) im System sichtbar sein:
|
||
- in **Policy Version Raw JSON** enthalten
|
||
- im **Normalized settings** Abschnitt als verständliche Liste/Tabelle dargestellt
|
||
- damit Diff/Preview/Restore auf den relevanten Settings basiert (nicht nur “General” Metadaten).
|
||
|
||
- **Why:** `deviceManagement/configurationPolicies` liefert im Base-Entity oft nur Metadaten (`name`, `platforms`, `technologies`, `settingCount`, `templateReference` …). Die eigentlichen Settings liegen typischerweise in einem **Subresource** (z. B. `.../configurationPolicies/{id}/settings`). Aktuell zeigt TenantPilot daher nicht die relevanten Werte (PIN length, biometrics, etc.).
|
||
|
||
---
|
||
|
||
## Implementation
|
||
|
||
### 1) Graph Contract Registry erweitern (Hydration Strategy)
|
||
- In `config/graph_contracts.php` beim Contract für `settingsCatalogPolicy` ergänzen:
|
||
- `member_hydration_strategy: 'subresource_settings'`
|
||
- `subresources`:
|
||
- `settings`:
|
||
- `path`: `deviceManagement/configurationPolicies/{id}/settings`
|
||
- `collection`: true
|
||
- `paging`: true
|
||
- `allowed_select`: konservativ (oder leer → fallback)
|
||
- `allowed_expand`: leer
|
||
|
||
> Erwartung: Registry definiert, wie der Snapshot “vollständig” gemacht wird.
|
||
|
||
### 2) Snapshot Capture für settingsCatalogPolicy hydrieren
|
||
- In dem Service, der Snapshots für **Version/Backup/Restore-Preview** lädt (z. B. `BackupService`, `VersionService`, `RestoreService` oder zentraler “PolicySnapshotService” falls vorhanden):
|
||
- Wenn `type_key === settingsCatalogPolicy`:
|
||
1. `GET deviceManagement/configurationPolicies/{id}` (Base entity)
|
||
2. `GET deviceManagement/configurationPolicies/{id}/settings` (paged)
|
||
3. Im Snapshot speichern als **entweder**:
|
||
- `snapshot['settingsCatalog'] = ['settings' => [...]]`
|
||
- **oder** `snapshot['settings'] = [...]` (wenn konsistenter mit Normalizer)
|
||
- Wichtig: **Keine Secrets** loggen, Payload bleibt JSONB.
|
||
- Pagination: `$top` + `@odata.nextLink` sauber abarbeiten.
|
||
|
||
### 3) PolicyNormalizer erweitern (Settings Catalog wirklich anzeigen)
|
||
- In `app/Services/Intune/PolicyNormalizer.php`:
|
||
- Für `settingsCatalogPolicy` nicht nur Metadaten anzeigen, sondern:
|
||
- `settings` / `settingsCatalog.settings` interpretieren
|
||
- pro Setting mindestens:
|
||
- Setting-Name/Display (wenn vorhanden)
|
||
- Setting-Path/DefinitionId (wenn vorhanden)
|
||
- Value (aktueller Wert)
|
||
- Ausgabe als Tabelle/RepeatableEntry (“Key/Value”), gruppiert nach Kategorie (z. B. “Windows Hello for Business”), wenn ableitbar.
|
||
|
||
### 4) “Settings available” Badge korrekt setzen
|
||
- Stelle sicher, dass die Logik “Settings available” bei `settingsCatalogPolicy` erst dann **Available** zeigt, wenn der Snapshot **Settings** enthält (nicht nur Base entity).
|
||
- Optional: Status “Partial”, wenn Base ok ist, aber Settings fetch fehlgeschlagen.
|
||
|
||
### 5) Diff & Restore Preview profitieren lassen (Ziel, kein Muss)
|
||
- Diff/Preview soll aus dem hydrierten Snapshot arbeiten → Änderungen an Settings werden sichtbar.
|
||
- Falls Diff aktuell nur top-level vergleicht: sicherstellen, dass `settings` Teil des Snapshot-Diffs ist.
|
||
|
||
### 6) Permissions/Health prüfen
|
||
- Prüfen, ob der `.../settings` Endpoint zusätzliche Permissions braucht.
|
||
- Falls ja: `config/intune_permissions.php` ergänzen + Verify/Health zeigt Missing sauber an.
|
||
|
||
---
|
||
|
||
## Tests (Pest)
|
||
|
||
### Feature: `tests/Feature/Filament/SettingsCatalogPolicyHydrationTest.php`
|
||
- Seed Policy vom Typ `settingsCatalogPolicy`
|
||
- Mock Graph:
|
||
- Base entity call liefert Metadaten
|
||
- `/settings` liefert 2–3 Settings Objekte (mit Value)
|
||
- Trigger Snapshot Capture (Version oder Backup)
|
||
- Assert:
|
||
- Snapshot enthält `settings` / `settingsCatalog.settings`
|
||
- Policy Version Detail zeigt Normalized settings mit diesen Einträgen
|
||
|
||
### Unit: `tests/Unit/PolicyNormalizerSettingsCatalogTest.php`
|
||
- Input: Snapshot mit `settings` Array
|
||
- Assert: Normalizer liefert strukturierte Key/Value Ausgabe, nicht nur Metadaten
|
||
|
||
### Regression
|
||
- `deviceConfiguration` / `deviceCompliancePolicy` etc. bleiben unverändert.
|
||
|
||
---
|
||
|
||
## Verification
|
||
|
||
- `./vendor/bin/pest tests/Feature/Filament/SettingsCatalogPolicyHydrationTest.php`
|
||
- `./vendor/bin/pest tests/Unit/PolicyNormalizerSettingsCatalogTest.php`
|
||
- `./vendor/bin/pint --dirty`
|
||
|
||
|
||
|
||
- [ ] T180 [US1b][Bug][settingsCatalogPolicy] Hydrate Settings Catalog settings in Version capture + Policy detail uses hydrated snapshot
|
||
|
||
- **Goal:** `settingsCatalogPolicy` soll die *Configuration settings* nicht nur in Backups, sondern auch in **Policy Versions** enthalten, damit **Policy Detail**, Diff/Preview/Restore auf den echten Settings basieren.
|
||
- **Why:** Aktuell hydriert nur `BackupService`, aber Policy Detail/Versions zeigen weiterhin nur Base-Metadaten.
|
||
|
||
### Implementation
|
||
1) **VersionService (oder zentraler SnapshotFetcher) erweitern**
|
||
- Beim Version-Capture für `settingsCatalogPolicy`:
|
||
- `GET deviceManagement/configurationPolicies/{id}`
|
||
- `GET deviceManagement/configurationPolicies/{id}/settings` (paging + nextLink)
|
||
- Merge in Snapshot unter **dem Key den Normalizer nutzt**:
|
||
- bevorzugt: `snapshot['settings'] = [...];`
|
||
- Keine Secrets loggen; nur IDs/Status im Audit.
|
||
|
||
2) **Policy Detail nutzt latest Version Snapshot**
|
||
- Sicherstellen, dass `PolicyResource -> ViewPolicy` / Normalizer den **latest policy_version snapshot** nimmt (nicht nur Policy-Metadaten).
|
||
- Falls bereits so: nur sicherstellen, dass der Snapshot-Key konsistent ist (`settings`).
|
||
|
||
3) **PolicyNormalizer: settingsCatalogPolicy Rendering**
|
||
- Falls bereits vorhanden: Normalizer liest `snapshot['settings']`.
|
||
- Falls nicht: ergänzen, damit in der UI eine Tabelle/Liste entsteht:
|
||
- Setting name / definitionId / value (mindestens)
|
||
|
||
4) **Settings Badge Logik**
|
||
- Badge “Settings available” soll bei settingsCatalogPolicy nur **Available** sein, wenn `snapshot['settings']` **nicht leer** ist.
|
||
- Optional: “Partial”, wenn Base ok aber settings fetch fehlgeschlagen.
|
||
|
||
### Tests (Pest)
|
||
- **Feature:** `tests/Feature/Filament/SettingsCatalogPolicyVersionHydrationTest.php`
|
||
- Mock Graph base entity + `/settings`
|
||
- Trigger **Version capture**
|
||
- Assert: Version Raw JSON enthält `settings`
|
||
- Assert: Policy Detail “Normalized settings” zeigt konkrete Settings (z. B. PIN length / biometrics)
|
||
- **Unit:** erweitere `PolicyNormalizerSettingsCatalogTest.php` falls nötig (Key + rendering)
|
||
|
||
### Verification
|
||
- `./vendor/bin/pest tests/Feature/Filament/SettingsCatalogPolicyVersionHydrationTest.php`
|
||
- `./vendor/bin/pest tests/Unit/PolicyNormalizerSettingsCatalogTest.php`
|
||
- `./vendor/bin/pint --dirty`
|
||
|
||
### Acceptance Criteria
|
||
- Policy Version Raw JSON enthält Settings (nicht nur Metadaten).
|
||
- Policy Detail zeigt konkrete Settings (z. B. “Minimum PIN Length: 12”, “Allow biometrics: True”).
|
||
- Settings Badge ist **Available**, sobald hydrierte Settings im Snapshot vorhanden sind.
|
||
- Keine Regression bei bestehenden Typen.
|
||
---
|
||
|
||
## Acceptance Criteria
|
||
|
||
- Settings Catalog Policies zeigen im **Policy Version Raw JSON** die **Settings** (nicht nur Metadaten).
|
||
- Im **Normalized settings** Bereich erscheinen konkrete Werte (z. B. “Minimum PIN Length: 12”, “Allow biometrics: True”).
|
||
- “Settings” Badge ist **Available**, sobald hydrierte Settings im Snapshot vorhanden sind.
|
||
- Keine Änderungen/Regressions bei bestehenden Typen.
|
||
|
||
|
||
|
||
|
||
|
||
- [x] T182 [US1b][settingsCatalogPolicy][UX] Dynamic normalization of Settings Catalog “settings” (generic flatten + readable labels)
|
||
|
||
- **Goal:** `settingsCatalogPolicy` soll im **Normalized settings** Bereich nicht mehr nur “setting -” anzeigen, sondern die hydrierten `settings[]` **generisch** (ohne hartes Mapping pro Setting) als verständliche Liste/Tabelle darstellen:
|
||
- pro Setting: **SettingDefinitionId**, **Instance Type**, **Value** (und ggf. Choice-Value)
|
||
- nested `children` / group collections werden **rekursiv geflattet**
|
||
- optional: einfache Gruppierung (z. B. nach Prefix der definitionId oder “group root”)
|
||
- **Why:** Microsoft hat unzählige Settings. Wir brauchen eine **dynamische** Darstellung, die immer funktioniert – auch für neue Settings, ohne dass wir jedes Setting kennen.
|
||
|
||
---
|
||
|
||
## Implementation
|
||
|
||
### 1) PolicyNormalizer: settingsCatalogPolicy → generic flatten
|
||
- In `app/Services/Intune/PolicyNormalizer.php`:
|
||
- Bei `policyType === settingsCatalogPolicy`:
|
||
- Wenn `snapshot['settings']` existiert:
|
||
- Erzeuge eine Normalizer-Sektion `Settings` als Tabelle/Repeatable:
|
||
- `definitionId` (string)
|
||
- `instanceType` (string, aus `settingInstance['@odata.type']`)
|
||
- `value` (string/number/bool/json; aus `simpleSettingValue.value` oder `choiceSettingValue.value`)
|
||
- `path` (optional): zusammengesetzter Pfad zur Einordnung (z. B. parentDefinitionId > childDefinitionId)
|
||
- Implementiere `flattenSettingsCatalogSettingInstances(array $settings): array`:
|
||
- Iteriere `settings[]` Einträge
|
||
- Extrahiere `settingInstance`
|
||
- Unterstütze generisch (mindestens):
|
||
- `deviceManagementConfigurationSimpleSettingInstance` → `simpleSettingValue.value`
|
||
- `deviceManagementConfigurationChoiceSettingInstance` → `choiceSettingValue.value`
|
||
- `deviceManagementConfigurationGroupSettingCollectionInstance`:
|
||
- iteriere `groupSettingCollectionValue[]`
|
||
- rekursiv `children[]`
|
||
- Fallback: wenn unbekannt → `value = json_encode(settingInstance)` (kurz/gekürzt)
|
||
- Für Rekursion: maximal Depth (z. B. 8) + Schutz gegen Zyklen/zu große Payloads.
|
||
- Optional: wenn Value ein “enum-like” String ist, zusätzlich `displayValue` = letzter Token nach `_` (nur für bessere Lesbarkeit, ohne Semantik zu behaupten).
|
||
- Wenn `settings` fehlt:
|
||
- Zeige Banner “Settings not hydrated” (oder “Partial snapshot”) und nur Metadaten.
|
||
|
||
### 2) Filament View: bessere Darstellung (Table statt “setting -”)
|
||
- In `PolicyResource/ViewPolicy` und `PolicyVersionResource/ViewPolicyVersion`:
|
||
- Stelle sicher, dass die Normalizer-Ausgabe für `Settings` als Tabelle angezeigt wird:
|
||
- Spalten: `Definition`, `Type`, `Value`, optional `Path`
|
||
- Lange Values: truncated mit “copy” möglich (oder expand/collapse).
|
||
|
||
### 3) Diff: Fokus auf echte Settings (optional, aber empfohlen)
|
||
- In der diff-summary Logik (falls vorhanden):
|
||
- Wenn `policyType=settingsCatalogPolicy` und `settings` vorhanden:
|
||
- Summary soll zumindest sagen: “X setting values changed/added/removed”
|
||
- (Die JSON diff bleibt weiterhin verfügbar.)
|
||
|
||
### 4) Performance & Safety
|
||
- Guardrails:
|
||
- max rows (z. B. 1000) → danach “truncated”
|
||
- value length max (z. B. 500 chars) → danach “truncated”
|
||
- depth limit
|
||
- Keine Secrets loggen; Normalizer arbeitet nur auf Snapshot JSONB.
|
||
|
||
---
|
||
|
||
## Tests (Pest)
|
||
|
||
### Unit: `tests/Unit/PolicyNormalizerSettingsCatalogFlattenTest.php`
|
||
- Input Snapshot mit:
|
||
- simpleSettingInstance (int)
|
||
- choiceSettingInstance (string)
|
||
- groupSettingCollectionInstance mit children (mix)
|
||
- Assert:
|
||
- Normalizer liefert `Settings` Sektion mit mehreren Zeilen
|
||
- jede Zeile hat `definitionId`, `instanceType`, `value`
|
||
- rekursive children werden als eigene Zeilen enthalten
|
||
- Unknown instance type fällt auf fallback (json string) ohne crash
|
||
|
||
### Feature: `tests/Feature/Filament/SettingsCatalogPolicyNormalizedDisplayTest.php`
|
||
- Erzeuge PolicyVersion (settingsCatalogPolicy) mit Snapshot inkl. `settings[]`
|
||
- Öffne Version-Detail und Policy-Detail
|
||
- Assert:
|
||
- In Normalized settings existiert Sektion “Settings”
|
||
- Tabelle enthält erwartete definitionIds und Werte (z. B. minimumpinlength=12, usebiometrics=true)
|
||
- Keine “setting -” Platzhalter mehr für diesen Snapshot
|
||
|
||
### Regression
|
||
- Bestehende Normalizer-Ausgaben für andere Typen bleiben unverändert.
|
||
|
||
---
|
||
|
||
## Verification
|
||
|
||
- `./vendor/bin/pest tests/Unit/PolicyNormalizerSettingsCatalogFlattenTest.php`
|
||
- `./vendor/bin/pest tests/Feature/Filament/SettingsCatalogPolicyNormalizedDisplayTest.php`
|
||
- `./vendor/bin/pint --dirty`
|
||
|
||
---
|
||
|
||
## Acceptance Criteria
|
||
|
||
- Settings Catalog Policy zeigt im **Normalized settings** Bereich eine verständliche **Settings-Tabelle** (DefinitionId/Type/Value) statt generischem “setting -”.
|
||
- Rekursive Group/Children-Settings werden sichtbar (nicht verloren).
|
||
- Darstellung ist **dynamisch** (kein hardcoded mapping pro Setting).
|
||
- Guardrails verhindern UI/Memory Explosion bei sehr großen Policies.
|
||
|
||
|
||
- [x] T183 [US1b][UX] Make Policy Version detail readable (Tabs + scroll-safe tables)
|
||
|
||
- **Goal:** Policy Version Detail (und optional Policy Detail) soll für Admins **lesbar** sein:
|
||
- **Normalized Settings** ist Default/primär sichtbar
|
||
- **Raw JSON** ist weiterhin verfügbar, aber UI zerbricht nicht durch riesige Payloads
|
||
- Settings Catalog Tabellen/Paths/IDs werden sauber dargestellt (kein “Textsalat”)
|
||
|
||
- **Why:** Aktuell verdrängt Raw JSON + lange SettingDefinitionIds/Paths die gesamte Seite. Admins sehen nicht mehr “was geändert wurde”, sondern nur Datenmüll.
|
||
|
||
---
|
||
|
||
## Implementation
|
||
|
||
### 1) UI Layout: Tabs (Normalized default, Raw JSON secondary)
|
||
- In `app/Filament/Resources/PolicyVersionResource/Pages/ViewPolicyVersion.php` (und optional `ViewPolicy.php`)
|
||
- ersetze die aktuelle Darstellung durch **Tabs**:
|
||
- Tab 1: **Normalized settings** (Default)
|
||
- Tab 2: **Raw JSON** (mit Copy Button)
|
||
- optional Tab 3: **Diff** (falls vorhanden)
|
||
- Falls Filament Infolist-Komponenten keine Tabs erlauben:
|
||
- nutze eine `ViewEntry` und rendere Tabs in Blade via `x-filament::tabs`.
|
||
|
||
### 2) Raw JSON: Max height + scroll + monospace
|
||
- In der Raw JSON Blade-View (z.B. `resources/views/filament/infolists/entries/raw-json.blade.php` oder bestehende View)
|
||
- Wrap `<pre>` mit:
|
||
- `class="max-h-[520px] overflow-auto rounded-lg border bg-gray-50 p-3 text-xs font-mono leading-relaxed"`
|
||
- optional: “Expand” action (modal/slideOver) für Vollbildansicht.
|
||
|
||
### 3) Normalized settings tables: horizontal scroll + readable columns
|
||
- In `resources/views/filament/infolists/entries/normalized-settings.blade.php`
|
||
- Table container:
|
||
- `class="overflow-x-auto rounded-lg border"`
|
||
- Table:
|
||
- `class="min-w-[900px] table-fixed"`
|
||
- Cells:
|
||
- Definition/Path: `font-mono text-xs break-all whitespace-normal`
|
||
- Value: `break-words whitespace-normal`
|
||
- Column widths:
|
||
- Definition: `w-[35%]`, Type: `w-[20%]`, Value: `w-[25%]`, Path: `w-[20%]`
|
||
- Long values: clamp (optional):
|
||
- `line-clamp-2` + “Show more” (details/modal)
|
||
|
||
### 4) Optional: Search within Settings (nice-to-have)
|
||
- Add a small client-side filter input (Alpine) above settings table:
|
||
- filters rows by DefinitionId/Value/Path
|
||
- Keep it optional if you want minimal change in v1.
|
||
|
||
---
|
||
|
||
## Tests (Pest)
|
||
|
||
### Feature: `tests/Feature/Filament/PolicyVersionReadableLayoutTest.php`
|
||
- Given a `settingsCatalogPolicy` version with long `settings` payload
|
||
- Assert:
|
||
- Tabs render (Normalized + Raw JSON)
|
||
- Raw JSON container has max-height/overflow classes
|
||
- Normalized table wrapper uses overflow-x
|
||
- Page does not contain extremely long unbroken lines without wrappers (basic assertion on classes)
|
||
|
||
---
|
||
|
||
## Verification
|
||
|
||
- `./vendor/bin/pest tests/Feature/Filament/PolicyVersionReadableLayoutTest.php`
|
||
- `./vendor/bin/pint --dirty`
|
||
|
||
---
|
||
|
||
## Acceptance Criteria
|
||
|
||
- Policy Version page is readable on normal screen widths:
|
||
- Normalized settings are immediately visible without scrolling past raw JSON
|
||
- Raw JSON is accessible in second tab and scrolls inside its container
|
||
- Settings table does not break layout; long IDs/paths wrap/scroll cleanly
|
||
- No regressions for other policy types (deviceConfiguration/compliance/scripts).
|
||
|
||
|
||
|
||
- [x] T184 [US1b][UX] Use Filament Tables for Settings Catalog settings (Policy + Version) with responsive layout + SlideOver details
|
||
|
||
- **Goal:** `settingsCatalogPolicy` Settings sollen **lesbar, scannbar und bedienbar** sein:
|
||
- als echte **Filament Table** (nicht “pseudo table” im Infolist-Blade)
|
||
- mit **truncate + tooltip**, horizontal scroll, sticky header
|
||
- mit **Details** (SlideOver) + Copy pro Row
|
||
- identisch nutzbar in **Policy Detail** und **Policy Version Detail**
|
||
|
||
- **Why:** Die aktuelle Darstellung bricht Layout/Spaltenbreiten (Definition/Type/Value laufen ineinander). Filament Tables lösen genau diese Probleme (fixed layout, responsive, actions, search).
|
||
|
||
---
|
||
|
||
## Implementation
|
||
|
||
### 1) Introduce reusable Livewire component for Settings Catalog settings table
|
||
- New: `app/Livewire/SettingsCatalogSettingsTable.php`
|
||
- Props:
|
||
- `array $settingsRows` (aus PolicyNormalizer Output oder direkt aus Snapshot `settings`)
|
||
- `string $context` (`policy|version`) optional
|
||
- Intern: build a Filament `Table` with columns:
|
||
- **Definition** (`TextColumn::make('definition')`)
|
||
- `wrap(false)`, `searchable()`, `tooltip(fn($state) => $state)`, `limit(60)`
|
||
- **Type** (`TextColumn::make('type')`)
|
||
- `wrap(false)`, `toggleable()`, `limit(50)`, `tooltip(...)`
|
||
- **Value** (`TextColumn::make('value')`)
|
||
- `wrap(false)`, `limit(60)`, `tooltip(...)`
|
||
- render `(group)` badge for group rows
|
||
- **Path** (`TextColumn::make('path')`)
|
||
- `toggleable(isToggledHiddenByDefault: true)`, `limit(80)`, `tooltip(...)`
|
||
- Table config:
|
||
- `paginated([25, 50, 100])` (default 25)
|
||
- `searchPlaceholder('Search definition/value…')`
|
||
- `striped()`, `deferLoading()`
|
||
- Row Action:
|
||
- `Action::make('details')->label('Details')->icon('heroicon-m-eye')`
|
||
- opens **SlideOver**
|
||
- shows full Definition/Type/Value/Path + optional raw setting JSON (pretty)
|
||
- Copy buttons for Definition/Value
|
||
|
||
### 2) Embed component via ViewEntry in Policy + PolicyVersion detail
|
||
- Policy detail (`app/Filament/Resources/PolicyResource/Pages/ViewPolicy.php`)
|
||
- For `settingsCatalogPolicy`:
|
||
- render `SettingsCatalogSettingsTable` (instead of current table block)
|
||
- pass rows from Normalizer (`normalize()` should expose a stable rows array)
|
||
- PolicyVersion detail (`app/Filament/Resources/PolicyVersionResource/Pages/ViewPolicyVersion.php`)
|
||
- same embedding for `settingsCatalogPolicy`
|
||
|
||
> Rule: Nur für `settingsCatalogPolicy` auf Table UI umstellen. Andere Typen bleiben Infolist/KeyValue.
|
||
|
||
### 3) Tailwind/Filament styling guardrails (no layout break)
|
||
- Ensure table container is responsive:
|
||
- wrap table in `div class="overflow-x-auto"`
|
||
- set columns non-wrapping by default (truncate)
|
||
- Sticky header:
|
||
- enable sticky header in table (Filament supports sticky header via table wrapper CSS; if needed add a small CSS utility class in your Filament theme)
|
||
|
||
### 4) Normalizer output contract (stable)
|
||
- Ensure `PolicyNormalizer` returns for settingsCatalogPolicy:
|
||
- `['settings_table' => ['columns' => [...], 'rows' => [...]]]`
|
||
- rows fields: `definition`, `type`, `value`, `path`, `raw` (optional)
|
||
- Table uses **rows**, not parsing raw snapshot again (single source).
|
||
|
||
---
|
||
|
||
## Tests (Pest)
|
||
|
||
### Feature: `tests/Feature/Filament/SettingsCatalogSettingsTableRenderTest.php` (new)
|
||
- Create a policy + version with hydrated `settings`
|
||
- Visit Policy detail and PolicyVersion detail
|
||
- Assert:
|
||
- table headers visible (Definition/Type/Value)
|
||
- at least one known definition appears
|
||
- “Details” action exists
|
||
|
||
### Unit (optional): `tests/Unit/PolicyNormalizerSettingsCatalogRowsTest.php`
|
||
- Given snapshot with nested settings instances
|
||
- Assert normalizer returns rows with `definition/type/value/path`
|
||
|
||
---
|
||
|
||
## Verification
|
||
|
||
- `./vendor/bin/pest tests/Feature/Filament/SettingsCatalogSettingsTableRenderTest.php`
|
||
- `./vendor/bin/pest tests/Unit/PolicyNormalizerSettingsCatalogRowsTest.php`
|
||
- `./vendor/bin/pint --dirty`
|
||
|
||
---
|
||
|
||
## Acceptance Criteria
|
||
|
||
- In **Policy Detail** und **Policy Version Detail** sind Settings Catalog Settings als **Filament Table** sichtbar (lesbar, nicht überlappend).
|
||
- Lange Werte sind **truncated** aber per Tooltip/Details vollständig erreichbar.
|
||
- Pro Row gibt es **Details SlideOver** + Copy.
|
||
- Kein Layout-Bruch auf typischen Screenbreiten (Laptop/FullHD).
|
||
|
||
|
||
|
||
- [ ]T185 [UX][US1b][settingsCatalogPolicy] Make Settings Catalog settings readable (label/value parsing + table ergonomics)
|
||
|
||
- **Goal:** Settings Catalog Policies sollen im Policy/Version Detail **für Admins lesbar** sein, ohne dass wir “alle Settings kennen müssen”.
|
||
- Tabelle zeigt **sprechende Bezeichnung** + **kompakte Werte**
|
||
- Lange IDs bleiben verfügbar (Tooltip/Copy/Details), aber dominieren nicht die UI
|
||
|
||
- **Why:** Aktuell sind `definitionId` und `choiceSettingValue.value` so lang, dass sie in der Tabelle abgeschnitten werden und der Admin weder Setting noch Wert versteht.
|
||
|
||
### Discovery → Decision
|
||
|
||
- Checked available Graph paths and contract registry: `configurationPolicies` exposes a subresource at `deviceManagement/configurationPolicies/{id}/settings` which is the supported method to add/update settings for Settings Catalog policies. There is no special action; the supported mechanism is a POST to the settings subresource (collection) or the collection resource when creating a new policy. Therefore the restore flow will:
|
||
- PATCH top-level metadata (`name`, `description`) via the policy resource
|
||
- POST settings to `deviceManagement/configurationPolicies/{id}/settings` when present
|
||
- If the tenant/API rejects the settings POST (NotSupported/ModelValidationFailure), the restore item will be marked `manual_required` with Graph request IDs and a clear admin message.
|
||
|
||
---
|
||
|
||
## Implementation
|
||
|
||
### 1) Presentation layer: generate human-friendly labels (no registry needed)
|
||
- Add helper in `PolicyNormalizer` (oder kleiner `SettingsCatalogPresenter`):
|
||
- `labelFromDefinitionId(string $definitionId): string`
|
||
- remove common prefixes: `device_vendor_msft_`, `user_vendor_msft_`, `policy_config_`, `admx_`
|
||
- replace `_` with spaces
|
||
- keep last 2–4 segments if string is huge
|
||
- replace `{tenantid}` with `{tenant}`
|
||
- Output example:
|
||
- `user_vendor_msft_policy_config_admx_desktop_nomydocumentsico...` → `Desktop: No My Documents Icon` (heuristic), fallback: last segments nicely spaced
|
||
|
||
> Heuristic only. If no good split possible, fallback to “last segments” label.
|
||
|
||
### 2) Parse values into a short “effective value”
|
||
- Implement `valuePreview(array $settingInstance): string`:
|
||
- For `SimpleSettingValue`: return scalar (`12`, `0`, `true/false`)
|
||
- For `ChoiceSettingValue.value`: return last token after last `_` OR map known boolean patterns:
|
||
- suffix `_true`/`_false` → `True`/`False`
|
||
- suffix `_0`/`_1` for allowed/blocked → show `0`/`1` but also tag `Allowed/Blocked` if detectable
|
||
- For group instances: show `(group)` and put children into details view only
|
||
|
||
### 3) Improve table ergonomics (Filament Table / Livewire)
|
||
- In `SettingsCatalogSettingsTable`:
|
||
- Columns:
|
||
1) **Setting** (human label) + small muted secondary line showing truncated definitionId
|
||
2) **Value** (valuePreview)
|
||
3) **Type** (badge: Choice/Simple/Group)
|
||
4) Optional: **Path** (toggleable, hidden by default)
|
||
- Add:
|
||
- `->searchable()` should search both label + raw definitionId + raw value
|
||
- `->wrap()` / `->limit()` for long strings
|
||
- tooltips showing full definitionId/value on hover
|
||
- “Copy” icon action in row details (SlideOver) for Definition + Raw JSON
|
||
- Ensure horizontal scroll only inside table container:
|
||
- wrapper `div` with `overflow-x-auto` + `max-w-full`
|
||
- table layout fixed where possible (`table-fixed`) to prevent column blowouts
|
||
|
||
### 4) Keep Raw JSON accessible but not primary
|
||
- In PolicyVersion view:
|
||
- Put Raw JSON into collapsible section or separate tab.
|
||
- Normalized Settings tab becomes default for settingsCatalogPolicy.
|
||
|
||
---
|
||
|
||
## Tests (Pest)
|
||
|
||
### Unit: `tests/Unit/SettingsCatalogPresenterTest.php`
|
||
- labelFromDefinitionId() produces readable output and stable fallback
|
||
- valuePreview() returns expected previews for:
|
||
- choice true/false, numeric, group
|
||
|
||
### Feature: `tests/Feature/Filament/SettingsCatalogSettingsTableUsabilityTest.php`
|
||
- Render policy detail with one very long definition + long choice value
|
||
- Assert:
|
||
- label column shows shortened readable label (not the full raw string)
|
||
- value column shows preview (e.g., `True`, `12`, `Never`)
|
||
- details slide-over contains full raw definition/value + copy UI
|
||
|
||
---
|
||
|
||
## Verification
|
||
- `./vendor/bin/pest tests/Unit/SettingsCatalogPresenterTest.php tests/Feature/Filament/SettingsCatalogSettingsTableUsabilityTest.php`
|
||
- `./vendor/bin/pint --dirty`
|
||
|
||
---
|
||
|
||
## Acceptance Criteria
|
||
- In Policy Detail, Settings table shows:
|
||
- **Readable Setting name** (not a cut-off vendor string)
|
||
- **Readable Value preview** (True/False/12/etc.)
|
||
|
||
- [ ] T186 [US4][Bugfix][settingsCatalogPolicy] Fix settings_apply payload typing (@odata.type) + body shape for configurationPolicies/{id}/settings
|
||
|
||
**Goal:** Restore für `settingsCatalogPolicy` soll Settings zuverlässig anwenden können, ohne ModelValidationFailure wegen fehlender/entfernter `@odata.type`.
|
||
|
||
**Why:** Aktuell schlägt `settings_apply` fehl mit „choiceSettingValue does not exist on type …SettingInstance“ → typischerweise fehlt `@odata.type` in `settingInstance` (oder in nested children) nach Sanitizing/Mapping.
|
||
|
||
### Implementation
|
||
1. **Contract:** Ensure `settings_apply` schema is explicit in `config/graph_contracts.php` (method = `POST`, path = `deviceManagement/configurationPolicies/{id}/settings`, `body_shape` = `collection`).
|
||
2. **Sanitizer:** In `GraphContractRegistry` allow and preserve `@odata.type` inside `settingInstance` and nested children (recursively); continue to strip read-only/meta fields and `id`.
|
||
3. **RestoreService:** Build `settingsPayload = sanitizeSettingsApplyPayload(snapshot['settings'])` and `POST` to the contract path; on failure mark item `manual_required` and persist Graph meta (`request_id`, `client_request_id`, error message).
|
||
4. **UI:** RestoreRun Results view shows clear admin message when `manual_required` due to settings_apply, including request ids.
|
||
|
||
### Tests (Pest)
|
||
- Unit: `tests/Unit/GraphContractRegistrySettingsApplySanitizerTest.php` (preserve `@odata.type`, strip ids)
|
||
- Feature: `tests/Feature/Filament/SettingsCatalogRestoreApplySettingsTest.php` (mock Graph, assert POST body includes `@odata.type` and success/failure flows)
|
||
|
||
### Verification
|
||
- `./vendor/bin/pest tests/Unit/GraphContractRegistrySettingsApplySanitizerTest.php`
|
||
- `./vendor/bin/pest tests/Feature/Filament/SettingsCatalogRestoreApplySettingsTest.php`
|
||
- `./vendor/bin/pint --dirty`
|
||
|
||
### Acceptance Criteria
|
||
- RestoreRun for `settingsCatalogPolicy` no longer fails with `choiceSettingValue does not exist …` when Graph supports settings POST.
|
||
- POST `.../settings` includes `settingInstance.@odata.type` (recursive) and is accepted by Graph, or the restore item is marked `manual_required` with request IDs visible.
|
||
- No regressions for other restore types.
|
||
- Full raw definitionId and raw value remain accessible via tooltip and SlideOver + copy button.
|
||
- No layout overflow/broken columns on common laptop viewport widths.
|
||
|
||
|
||
|
||
### Implementation for User Story 1
|
||
|
||
- [x] T009 [US1] Implement policy sync/import orchestrator using Graph abstraction in `app/Services/Intune/PolicySyncService.php` (no direct Graph in UI).
|
||
- [x] T010 [US1] Create Filament resource/table for policies with filters and metadata columns in `app/Filament/Resources/PolicyResource.php`.
|
||
- [x] T011 [US1] Add command/job to sync policies (queues-ready) in `app/Console/Commands/SyncPolicies.php` and queue job under `app/Jobs/`.
|
||
|
||
## Phase 4: User Story 2 - Backup creation and browsing (Priority: P1)
|
||
|
||
### Tests for User Story 2
|
||
|
||
- [x] T012 [P] [US2] Feature test for creating backup sets with multiple policies and verifying immutable JSONB snapshots + audit log in `tests/Feature/Filament/BackupCreationTest.php`.
|
||
|
||
### Implementation for User Story 2
|
||
|
||
- [x] T013 [US2] Implement backup domain service to assemble snapshots from policies with Graph payload retrieval in `app/Services/Intune/BackupService.php`.
|
||
- [x] T014 [US2] Add Filament resource/pages for backup sets and items (list/detail) in `app/Filament/Resources/BackupSetResource.php`.
|
||
|
||
- [x] T131 [UX] [US2] Refactor BackupSet policy selection to RelationManager:
|
||
- Remove the multi-select policy picker from the BackupSet **Create** form (keep Create minimal: name/description).
|
||
- After create, redirect to BackupSet **Edit/View** where items can be managed.
|
||
- Add `BackupItemsRelationManager` to `BackupSetResource` showing a table with columns: Policy Name, Type (badge), Restore (badge), Risk (badge).
|
||
- Add header action “Policies hinzufügen” (searchable, multiple) that adds items/attaches policies **tenant-scoped** and prevents duplicates per BackupSet.
|
||
- Provide a remove action (detach/soft-delete as per domain rules).
|
||
|
||
- [x] T132 [P] [US2] Update/extend `tests/Feature/Filament/BackupCreationTest.php` to cover the new UX flow:
|
||
- Create BackupSet without policies.
|
||
- Add multiple policies via RelationManager action.
|
||
- Verify immutable JSONB snapshots + audit log behavior remains correct.
|
||
|
||
- [x] T015 [US2] Wire audit logging for backup creation events in `app/Services/Intune/BackupService.php` using `AuditLogger`.
|
||
|
||
## Phase 5: User Story 3 - Version history and diff (Priority: P1)
|
||
|
||
### Tests for User Story 3
|
||
|
||
- [x] T016 [P] [US3] Feature test for version capture and timeline display in `tests/Feature/Filament/PolicyVersionTest.php`.
|
||
- [x] T017 [P] [US3] Unit test for diff generation (human summary + JSON diff) in `tests/Unit/VersionDiffTest.php`.
|
||
|
||
### Implementation for User Story 3
|
||
|
||
- [x] T018 [US3] Implement version capture service with immutable JSONB writes in `app/Services/Intune/VersionService.php`.
|
||
- [x] T019 [US3] Create diff helper (summary + structured JSON) in `app/Services/Intune/VersionDiff.php` and surface in Filament version compare view in `app/Filament/Resources/PolicyVersionResource.php`.
|
||
- [x] T020 [US3] Hook version capture into relevant flows (manual trigger + backup/restore hooks) ensuring audit logging.
|
||
|
||
## Phase 6: User Story 4 - Restore with preview and confirmation (Priority: P1)
|
||
|
||
### Tests for User Story 4
|
||
|
||
- [x] T021 [P] [US4] Feature test for restore preview (change summary, conflicts, selective items) in `tests/Feature/Filament/RestorePreviewTest.php`.
|
||
- [x] T022 [P] [US4] Feature test for confirmed restore execution capturing audit logs and per-item outcomes in `tests/Feature/Filament/RestoreExecutionTest.php`.
|
||
|
||
### Implementation for User Story 4
|
||
|
||
- [x] T023 [US4] Implement restore service with preview/dry-run and selective item application in `app/Services/Intune/RestoreService.php`, integrating Graph adapter and conflict detection.
|
||
- [x] T024 [US4] Add Filament restore UI (wizard or pages) showing preview, warnings, and confirmation gate in `app/Filament/Resources/RestoreRunResource.php`.
|
||
- [x] T025 [US4] Record restore run lifecycle (start, per-item result, completion) and audit events in `restore_runs` and `audit_logs`.
|
||
- [ ] T156 [US4][UX] Add “Rerun” action to RestoreRun row actions (ActionGroup): creates a new RestoreRun cloned from selected run (same backup_set_id, same selected items, same dry_run flag), enforces same safety gates/confirmations as original execution path, writes audit event restore_run.rerun_created with source_restore_run_id.
|
||
|
||
|
||
## Phase 7: User Story 5 - Operational readiness and environments (Priority: P2)
|
||
|
||
- [x] T026 [US5] Document Dokploy staging→production promotion steps, required env vars, queue/worker expectations, and migration safety notes in `README.md` or `docs/deploy.md`.
|
||
- [x] T027 [US5] Add quick Sail commands and test invocation notes to `README.md` (e.g., `./vendor/bin/sail artisan test`) and ensure sample env entries for Graph credentials.
|
||
|
||
## Phase 8: Tenant Management (Tenant hinzufügen, App-Setup, Verify) (Priority: P1)
|
||
|
||
> Hinweis: Diese Phase ist “Tenant Management” und **nicht** US6, damit US6 sauber “Permissions/Health” bleibt.
|
||
|
||
- [x] T030 [TENANT] Migration für `tenants` ergänzen/prüfen (name, tenant_id, domain, app_client_id, app_status, app_notes, timestamps).
|
||
- [x] T031 [TENANT] Eloquent Model `Tenant` (Beziehungen, tenant-aware scopes).
|
||
- [x] T032 [TENANT] Filament `TenantResource` (list/create/edit/detail; Actions: Open in Entra, Copy consent URL optional).
|
||
- [x] T033 [TENANT] `TenantConfigService` / Graph connectivity check.
|
||
- [x] T034 [TENANT] Action „Verify configuration“ + Audit (`tenant.config.verified`).
|
||
- [x] T035 [TENANT] Tenant-Kontext in Policy/Backup/Restore/Audit Services (tenant_id überall setzen).
|
||
- [x] T036 [TENANT] Feature-Test `TenantSetupTest` (ok/error + Audit).
|
||
- [x] T037 [TENANT] Admin-Consent Callback Route (state → tenant mapping, status update, UI page).
|
||
|
||
## Phase 9: User Story 6 - Berechtigungsübersicht & Health-Status (Priority: P1)
|
||
|
||
- [x] T040 [P] [US6] Zentrale Permissions-Liste `config/intune_permissions.php` (+ optional `docs/permissions.md`).
|
||
- [x] T041 [US6] Datenmodell Tenant-Berechtigungen (JSONB `granted_permissions` oder `tenant_permissions` Tabelle; status ok/missing/error).
|
||
- [x] T042 [US6] `TenantPermissionService` (required, granted, compare DTO).
|
||
- [x] T043 [US6] Tenant-Detail UI Panel „Permissions“ (required list + status).
|
||
- [x] T044 [US6] Verify erweitern: compare + persist + Audit `tenant.permissions.checked`.
|
||
- [x] T045 [US6] Tests: Unit compare + Feature Tenant detail status + Verify updates.
|
||
|
||
## Phase 9b: Scope-Ausrichtung auf neue Objekttypen
|
||
|
||
- [x] T028 [Scope] `config/tenantpilot.php` auf `scope.supported_types` erweitern; single source for sync/backup/restore.
|
||
- [x] T029 [Scope] Filament-UI an neue Typen anpassen (Filter/Grouping + Restore-Level Hinweise).
|
||
|
||
## Phase 10: Housekeeping – Delete-Funktionen für Backups & Versions
|
||
|
||
- [x] T060 [HK] BackupSets soft deletable + Guard gegen RestoreRuns.
|
||
- [x] T061 [HK] Filament Delete BackupSets + Confirmation + Audit (`backup.deleted`) + Guard.
|
||
- [x] T062 [HK] PolicyVersions soft deletable + Queries/Resources default non-deleted.
|
||
- [x] T063 [HK] Filament Delete PolicyVersions + Audit (`policy_version.deleted`).
|
||
- [x] T064 [HK] Tests Housekeeping (BackupSet delete ok/block + PolicyVersion delete).
|
||
|
||
## Phase 11: Housekeeping – Tenant löschen/deaktivieren
|
||
|
||
- [x] T070 [HK] Tenants soft deletable (optional status active/archived).
|
||
- [x] T071 [HK] Tenant deactivate/archive action + Audit (`tenant.archived`).
|
||
- [x] T072 [HK] Block operations for deactivated tenants (sync/backup/restore early fail).
|
||
- [x] T073 [HK] RestoreRuns soft deletable (optional) + Audit (`restore_run.deleted`).
|
||
- [x] T074 [HK] Tests Tenant delete/deactivate behavior + clear errors, no Graph calls.
|
||
|
||
## Phase 12: Housekeeping – Hard Deletes (Force Delete)
|
||
|
||
- [x] T075 [HK] Force-Delete-Actions (only in trashed; guards; audit before delete) + tests.
|
||
|
||
## Phase 12b: Single current tenant ("Highlander")
|
||
|
||
- [x] T120 [TENANT] Migration add `is_current` + partial unique index.
|
||
- [x] T121 [TENANT] Tenant::current() + makeCurrent() + remove implicit defaults.
|
||
- [x] T122 [TENANT] Data cleanup (mark one current; archive local-tenant).
|
||
- [x] T123 [TENANT] Filament UI badge + “Make current” action.
|
||
- [x] T124 [TENANT] Consumers refactor to `Tenant::current()` or explicit tenant.
|
||
- [x] T125 [TENANT] Tests for current selection + “Make current”.
|
||
|
||
- [x] T130 [UX] Tabellen-Aktionen in Dropdown bündeln (ActionGroup) in TenantResource (+ optional others).
|
||
|
||
## Phase 13: Settings Normalization & Display (Priority: P1)
|
||
|
||
- [x] T140 [P] [US1b] Unit test PolicyNormalizer.
|
||
- [x] T141 [P] [US1b] Feature test Policy Settings section.
|
||
- [x] T142 [P] [US1b] Feature test Version detail pretty JSON + normalized.
|
||
- [x] T143 [P] [Edge] Feature test malformed snapshot warning.
|
||
- [x] T144 [P] [Edge] Feature test @odata.type mismatch flag + restore exec block.
|
||
|
||
- [x] T145 [US1b] PolicyNormalizer service.
|
||
- [x] T146 [US1b] Settings infolist in PolicyResource.
|
||
- [x] T147 [US1b] PolicyVersion view pretty JSON + normalized.
|
||
- [x] T148 [US1b] Integrations (list badge, optional diff enhancements, tenant scoping).
|
||
- [x] T149 [Edge] SnapshotValidator helper.
|
||
- [x] T150 [Edge] @odata.type validator (policy/backup/restore gates).
|
||
- [x] T151 [Edge] UI warnings + restore execution gating (preview may show).
|
||
- [x] T152 [US1b] README docs for settings display.
|
||
- [x] T153 [US1b] Inline docs in PolicyNormalizer.
|
||
|
||
## Phase 14: User Story 7 – Intune RBAC Onboarding Wizard (Delegated, Synchronous)
|
||
|
||
**Scope**: FR-023 to FR-030; delegated login and grant run synchronously in Filament (no queue for grant). Optional jobs/CLI only for CHECK/REPORT (no grant).
|
||
|
||
- [x] T160 [US7] Add TenantResource ActionGroup entry “Setup Intune RBAC”: visible for active tenants with `app_client_id`; sits alongside Admin consent/Verify; guarded by Highlander rules. Verified by: `./vendor/bin/pest tests/Feature/Filament/TenantRbacWizardTest.php`.
|
||
- [x] T161 [US7] Wizard UI (Filament): Step 1 preconditions/summary with inputs for Role, Scope, Group mode; least-privilege warnings; review screen of planned changes. Verified by: `./vendor/bin/pest tests/Feature/Filament/TenantRbacWizardTest.php`.
|
||
- [x] T162 [US7] Delegated auth step: initiate delegated login; stop with clear error + audit on failure; token not persisted. Verified by: `./vendor/bin/pest tests/Feature/Filament/TenantRbacWizardTest.php`.
|
||
- [x] T163 [US7] Execution service (sync) with audit per step: resolve SP by `app_client_id`; ensure/create security group (`securityEnabled=true`); add SP as member (idempotent); ensure/create/update Intune role assignment; persist IDs on tenant for idempotency. Verified by: `./vendor/bin/pest tests/Feature/Filament/TenantRbacWizardTest.php`.
|
||
|
||
- [x] T164 [US7] Post-check (mandatory): clear app token cache / force fresh token acquisition and run canary reads:
|
||
- `GET /deviceManagement/deviceConfigurations?$top=1`
|
||
- `GET /deviceManagement/deviceCompliancePolicies?$top=1`
|
||
- optional CA canary only if CA features enabled
|
||
- update tenant health + audit verify outcome.
|
||
Verified by: `./vendor/bin/pest tests/Feature/Filament/TenantRbacWizardTest.php`.
|
||
|
||
- [x] T165 [US7] Tests (Pest, mocked Graph): happy path; rerun idempotent; missing permissions error mapping; scope-limited warning; delegated login failure path; non-security-enabled group failure. Verified by: `./vendor/bin/pest tests/Feature/Filament/TenantRbacWizardTest.php`.
|
||
- [x] T166 [US7] Documentation: README note for wizard behavior (delegated, sync), least-privilege defaults, audit expectations, rerun safety. Verified by: manual review of README.md update.
|
||
- [ ] T167 [US7-Optional] CLI/Job for CHECK/REPORT only (no grant), explicitly exclude async grant.
|
||
- [x] T168 [US7] Extend Verify configuration / Health panel to include “Intune RBAC status” (OK/Missing/Error) + CTA “Run Setup Intune RBAC”, persist last_checked_at + reason; Audit `tenant.rbac.checked`. Verified by: `./vendor/bin/pest tests/Feature/Filament/TenantRbacWizardTest.php`.
|
||
- [x] T169 [US7] Persist RBAC artifacts on Tenant for idempotency:
|
||
- migration add nullable columns: `rbac_group_id`, `rbac_group_name`, `rbac_role_assignment_id`, `rbac_role_key`, `rbac_scope_mode`, `rbac_scope_id`
|
||
- prefer stored IDs on reruns; discovery fallback.
|
||
Verified by: `./vendor/bin/pest tests/Feature/Filament/TenantRbacWizardTest.php`.
|
||
|
||
## Phase 15: User Story 8 – Graph Contract Registry & Drift Guard
|
||
|
||
**Scope**: FR-031 to FR-034; contract registry per type, type-family handling, capability fallbacks, drift checks.
|
||
|
||
- [x] T170 [US8] Add contract registry artifact (e.g., `config/graph_contracts.php`) capturing per supported type: resource paths, allowed `$select`/`$expand`, allowed @odata.type family, create/update methods, id field, hydration strategy. Verified by: manual review.
|
||
- [x] T171 [US8] Implement registry service + integration in Graph client to enforce allowed capabilities and downgrade on capability errors (retry without expand/select), logging warnings/audit entries. Verified by: `./vendor/bin/pest tests/Unit/GraphContractFallbackTest.php`.
|
||
- [x] T172 [US8] Implement type-family handling so derived @odata.type within a family routes correctly for preview/restore (no odata_mismatch) while still blocking unknown types. Verified by: `./vendor/bin/pest tests/Feature/Filament/ODataTypeMismatchTest.php`.
|
||
- [x] T173 [US8] Add verification command `php artisan graph:contract:check` (staging/CI) to probe endpoints, detect drift, and emit actionable diff/log output; make prod opt-in/guarded. Verified by: manual review.
|
||
- [x] T174 [US8] Tests (Pest/unit/integration): registry lookups, fallback selection on capability errors, derived type acceptance, drift-check command behavior. Verified by: `./vendor/bin/pest tests/Unit/GraphContractRegistryTest.php tests/Unit/GraphContractFallbackTest.php`.
|
||
- [x] T175 [US8] Documentation: describe registry format/update process, fallback behavior, and how/when to run `graph:contract:check`. Verified by: manual review of README update.
|