feat: highlight script content in normalized diff
This commit is contained in:
parent
17bfc2f17e
commit
840e4686f9
@ -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)')
|
||||
|
||||
@ -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
|
||||
|
||||
<div class="space-y-4">
|
||||
@ -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
|
||||
<div class="grid grid-cols-1 gap-2 sm:grid-cols-3">
|
||||
<div class="text-sm font-medium text-gray-900 dark:text-white">
|
||||
@ -115,7 +173,25 @@
|
||||
<summary class="cursor-pointer text-sm text-gray-700 dark:text-gray-200">
|
||||
View
|
||||
</summary>
|
||||
<pre class="mt-2 overflow-x-auto text-xs text-gray-800 dark:text-gray-200">{{ $fromText }}</pre>
|
||||
@if (is_string($fromHighlight) && $fromHighlight !== '')
|
||||
<style>
|
||||
html.dark code.torchlight {
|
||||
background-color: var(--phiki-dark-background-color) !important;
|
||||
}
|
||||
|
||||
html.dark .phiki,
|
||||
html.dark .phiki span {
|
||||
color: var(--phiki-dark-color) !important;
|
||||
font-style: var(--phiki-dark-font-style) !important;
|
||||
font-weight: var(--phiki-dark-font-weight) !important;
|
||||
text-decoration: var(--phiki-dark-text-decoration) !important;
|
||||
}
|
||||
</style>
|
||||
|
||||
<div class="mt-2 overflow-x-auto">{!! $fromHighlight !!}</div>
|
||||
@else
|
||||
<pre class="mt-2 overflow-x-auto text-xs text-gray-800 dark:text-gray-200">{{ $fromText }}</pre>
|
||||
@endif
|
||||
</details>
|
||||
@else
|
||||
<div class="mt-1">{{ $fromText }}</div>
|
||||
@ -128,7 +204,25 @@
|
||||
<summary class="cursor-pointer text-sm text-gray-700 dark:text-gray-200">
|
||||
View
|
||||
</summary>
|
||||
<pre class="mt-2 overflow-x-auto text-xs text-gray-800 dark:text-gray-200">{{ $toText }}</pre>
|
||||
@if (is_string($toHighlight) && $toHighlight !== '')
|
||||
<style>
|
||||
html.dark code.torchlight {
|
||||
background-color: var(--phiki-dark-background-color) !important;
|
||||
}
|
||||
|
||||
html.dark .phiki,
|
||||
html.dark .phiki span {
|
||||
color: var(--phiki-dark-color) !important;
|
||||
font-style: var(--phiki-dark-font-style) !important;
|
||||
font-weight: var(--phiki-dark-font-weight) !important;
|
||||
text-decoration: var(--phiki-dark-text-decoration) !important;
|
||||
}
|
||||
</style>
|
||||
|
||||
<div class="mt-2 overflow-x-auto">{!! $toHighlight !!}</div>
|
||||
@else
|
||||
<pre class="mt-2 overflow-x-auto text-xs text-gray-800 dark:text-gray-200">{{ $toText }}</pre>
|
||||
@endif
|
||||
</details>
|
||||
@else
|
||||
<div class="mt-1">{{ $toText }}</div>
|
||||
@ -149,7 +243,30 @@
|
||||
<summary class="cursor-pointer text-sm text-gray-700 dark:text-gray-200">
|
||||
View
|
||||
</summary>
|
||||
<pre class="mt-2 overflow-x-auto text-xs text-gray-800 dark:text-gray-200">{{ $text }}</pre>
|
||||
@php
|
||||
$isScriptContent = $canHighlightScripts($policyType) && $isScriptKey($name);
|
||||
$highlighted = $isScriptContent ? $highlight($policyType, (string) $text) : null;
|
||||
@endphp
|
||||
|
||||
@if (is_string($highlighted) && $highlighted !== '')
|
||||
<style>
|
||||
html.dark code.torchlight {
|
||||
background-color: var(--phiki-dark-background-color) !important;
|
||||
}
|
||||
|
||||
html.dark .phiki,
|
||||
html.dark .phiki span {
|
||||
color: var(--phiki-dark-color) !important;
|
||||
font-style: var(--phiki-dark-font-style) !important;
|
||||
font-weight: var(--phiki-dark-font-weight) !important;
|
||||
text-decoration: var(--phiki-dark-text-decoration) !important;
|
||||
}
|
||||
</style>
|
||||
|
||||
<div class="mt-2 overflow-x-auto">{!! $highlighted !!}</div>
|
||||
@else
|
||||
<pre class="mt-2 overflow-x-auto text-xs text-gray-800 dark:text-gray-200">{{ $text }}</pre>
|
||||
@endif
|
||||
</details>
|
||||
@else
|
||||
<div class="break-words">{{ $text }}</div>
|
||||
|
||||
@ -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.
|
||||
|
||||
@ -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');
|
||||
});
|
||||
|
||||
Loading…
Reference in New Issue
Block a user