From 840e4686f98698db0fc85517946377009c144547 Mon Sep 17 00:00:00 2001 From: Ahmed Darrazi Date: Thu, 1 Jan 2026 21:25:41 +0100 Subject: [PATCH] feat: highlight script content in normalized diff --- .../Resources/PolicyVersionResource.php | 5 +- .../entries/normalized-diff.blade.php | 123 +++++++++++++++++- specs/013-scripts-management/tasks.md | 1 + .../ScriptPoliciesNormalizedDisplayTest.php | 61 +++++++++ 4 files changed, 186 insertions(+), 4 deletions(-) diff --git a/app/Filament/Resources/PolicyVersionResource.php b/app/Filament/Resources/PolicyVersionResource.php index 91347ef..2b01621 100644 --- a/app/Filament/Resources/PolicyVersionResource.php +++ b/app/Filament/Resources/PolicyVersionResource.php @@ -115,7 +115,10 @@ public static function infolist(Schema $schema): Schema : []; $to = $normalizer->flattenForDiff($record->snapshot ?? [], $record->policy_type ?? '', $record->platform); - return $diff->compare($from, $to); + $result = $diff->compare($from, $to); + $result['policy_type'] = $record->policy_type; + + return $result; }), Infolists\Components\ViewEntry::make('diff_json') ->label('Raw diff (advanced)') diff --git a/resources/views/filament/infolists/entries/normalized-diff.blade.php b/resources/views/filament/infolists/entries/normalized-diff.blade.php index c57ebd1..6b5e516 100644 --- a/resources/views/filament/infolists/entries/normalized-diff.blade.php +++ b/resources/views/filament/infolists/entries/normalized-diff.blade.php @@ -1,6 +1,7 @@ @php $diff = $getState() ?? ['summary' => [], 'added' => [], 'removed' => [], 'changed' => []]; $summary = $diff['summary'] ?? []; + $policyType = $diff['policy_type'] ?? null; $groupByBlock = static function (array $items): array { $groups = []; @@ -50,6 +51,59 @@ return is_string($value) && strlen($value) > 160; }; + + $isScriptKey = static function (mixed $name): bool { + return in_array((string) $name, ['scriptContent', 'detectionScriptContent', 'remediationScriptContent'], true); + }; + + $canHighlightScripts = static function (?string $policyType): bool { + return (bool) config('tenantpilot.display.show_script_content', false) + && in_array($policyType, ['deviceManagementScript', 'deviceShellScript', 'deviceHealthScript'], true); + }; + + $selectGrammar = static function (?string $policyType, string $code): string { + if ($policyType === 'deviceShellScript') { + $firstLine = strtok($code, "\n") ?: ''; + $shebang = trim($firstLine); + + if (str_starts_with($shebang, '#!')) { + if (str_contains($shebang, 'zsh')) { + return 'zsh'; + } + + if (str_contains($shebang, 'bash')) { + return 'bash'; + } + + return 'sh'; + } + + return 'sh'; + } + + return 'powershell'; + }; + + $highlight = static function (?string $policyType, string $code, string $fallbackClass = '') use ($selectGrammar): ?string { + if (! class_exists(\Torchlight\Engine\Engine::class)) { + return null; + } + + try { + return (new \Torchlight\Engine\Engine())->codeToHtml( + code: $code, + grammar: $selectGrammar($policyType, $code), + theme: [ + 'light' => 'github-light', + 'dark' => 'github-dark', + ], + withGutter: false, + withWrapper: true, + ); + } catch (\Throwable $e) { + return null; + } + }; @endphp
@@ -103,6 +157,10 @@ $to = $value['to']; $fromText = $stringify($from); $toText = $stringify($to); + + $isScriptContent = $canHighlightScripts($policyType) && $isScriptKey($name); + $fromHighlight = $isScriptContent ? $highlight($policyType, (string) $fromText) : null; + $toHighlight = $isScriptContent ? $highlight($policyType, (string) $toText) : null; @endphp
@@ -115,7 +173,25 @@ View -
{{ $fromText }}
+ @if (is_string($fromHighlight) && $fromHighlight !== '') + + +
{!! $fromHighlight !!}
+ @else +
{{ $fromText }}
+ @endif @else
{{ $fromText }}
@@ -128,7 +204,25 @@ View -
{{ $toText }}
+ @if (is_string($toHighlight) && $toHighlight !== '') + + +
{!! $toHighlight !!}
+ @else +
{{ $toText }}
+ @endif @else
{{ $toText }}
@@ -149,7 +243,30 @@ View -
{{ $text }}
+ @php + $isScriptContent = $canHighlightScripts($policyType) && $isScriptKey($name); + $highlighted = $isScriptContent ? $highlight($policyType, (string) $text) : null; + @endphp + + @if (is_string($highlighted) && $highlighted !== '') + + +
{!! $highlighted !!}
+ @else +
{{ $text }}
+ @endif @else
{{ $text }}
diff --git a/specs/013-scripts-management/tasks.md b/specs/013-scripts-management/tasks.md index 4201d38..6ebabbd 100644 --- a/specs/013-scripts-management/tasks.md +++ b/specs/013-scripts-management/tasks.md @@ -20,6 +20,7 @@ ## Phase 4: Script Content Display (Safe) - [x] T008 Add opt-in display + base64 decoding for `scriptContent` in normalized settings. - [x] T009 Highlight script content with Torch (shebang-based shell + PowerShell default). - [x] T010 Hide script content behind a Show/Hide button (collapsed by default). +- [x] T011 Highlight script content in Normalized Diff view (From/To). ## Open TODOs (Follow-up) - None yet. diff --git a/tests/Feature/Filament/ScriptPoliciesNormalizedDisplayTest.php b/tests/Feature/Filament/ScriptPoliciesNormalizedDisplayTest.php index fdd05f7..092ec9e 100644 --- a/tests/Feature/Filament/ScriptPoliciesNormalizedDisplayTest.php +++ b/tests/Feature/Filament/ScriptPoliciesNormalizedDisplayTest.php @@ -61,3 +61,64 @@ ['deviceShellScript', '#microsoft.graph.deviceShellScript'], ['deviceHealthScript', '#microsoft.graph.deviceHealthScript'], ]); + +it('renders diff tab with highlighted script content for script policies', function () { + $originalEnv = getenv('INTUNE_TENANT_ID'); + putenv('INTUNE_TENANT_ID='); + + $this->actingAs(User::factory()->create()); + + config([ + 'tenantpilot.display.show_script_content' => true, + 'tenantpilot.display.max_script_content_chars' => 5000, + ]); + + $tenant = Tenant::factory()->create(); + putenv('INTUNE_TENANT_ID='.$tenant->tenant_id); + $tenant->makeCurrent(); + + $policy = \App\Models\Policy::factory()->create([ + 'tenant_id' => $tenant->getKey(), + 'policy_type' => 'deviceManagementScript', + 'platform' => 'windows', + ]); + + $scriptOne = "# test\n".str_repeat("Write-Host 'one'\n", 40); + $scriptTwo = "# test\n".str_repeat("Write-Host 'two'\n", 40); + + $v1 = \App\Models\PolicyVersion::factory()->create([ + 'policy_id' => $policy->getKey(), + 'tenant_id' => $tenant->getKey(), + 'version_number' => 1, + 'policy_type' => 'deviceManagementScript', + 'platform' => 'windows', + 'snapshot' => [ + '@odata.type' => '#microsoft.graph.deviceManagementScript', + 'displayName' => 'My script', + 'scriptContent' => base64_encode($scriptOne), + ], + ]); + + $v2 = \App\Models\PolicyVersion::factory()->create([ + 'policy_id' => $policy->getKey(), + 'tenant_id' => $tenant->getKey(), + 'version_number' => 2, + 'policy_type' => 'deviceManagementScript', + 'platform' => 'windows', + 'snapshot' => [ + '@odata.type' => '#microsoft.graph.deviceManagementScript', + 'displayName' => 'My script', + 'scriptContent' => base64_encode($scriptTwo), + ], + ]); + + $url = \App\Filament\Resources\PolicyVersionResource::getUrl('view', ['record' => $v2]); + + $this->get($url.'?tab=diff') + ->assertSuccessful() + ->assertSee('torchlight', false); + + $originalEnv !== false + ? putenv("INTUNE_TENANT_ID={$originalEnv}") + : putenv('INTUNE_TENANT_ID'); +});