013-scripts-management #19

Merged
ahmido merged 17 commits from 013-scripts-management into dev 2026-01-01 22:02:30 +00:00
4 changed files with 186 additions and 4 deletions
Showing only changes of commit 840e4686f9 - Show all commits

View File

@ -115,7 +115,10 @@ public static function infolist(Schema $schema): Schema
: []; : [];
$to = $normalizer->flattenForDiff($record->snapshot ?? [], $record->policy_type ?? '', $record->platform); $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') Infolists\Components\ViewEntry::make('diff_json')
->label('Raw diff (advanced)') ->label('Raw diff (advanced)')

View File

@ -1,6 +1,7 @@
@php @php
$diff = $getState() ?? ['summary' => [], 'added' => [], 'removed' => [], 'changed' => []]; $diff = $getState() ?? ['summary' => [], 'added' => [], 'removed' => [], 'changed' => []];
$summary = $diff['summary'] ?? []; $summary = $diff['summary'] ?? [];
$policyType = $diff['policy_type'] ?? null;
$groupByBlock = static function (array $items): array { $groupByBlock = static function (array $items): array {
$groups = []; $groups = [];
@ -50,6 +51,59 @@
return is_string($value) && strlen($value) > 160; 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 @endphp
<div class="space-y-4"> <div class="space-y-4">
@ -103,6 +157,10 @@
$to = $value['to']; $to = $value['to'];
$fromText = $stringify($from); $fromText = $stringify($from);
$toText = $stringify($to); $toText = $stringify($to);
$isScriptContent = $canHighlightScripts($policyType) && $isScriptKey($name);
$fromHighlight = $isScriptContent ? $highlight($policyType, (string) $fromText) : null;
$toHighlight = $isScriptContent ? $highlight($policyType, (string) $toText) : null;
@endphp @endphp
<div class="grid grid-cols-1 gap-2 sm:grid-cols-3"> <div class="grid grid-cols-1 gap-2 sm:grid-cols-3">
<div class="text-sm font-medium text-gray-900 dark:text-white"> <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"> <summary class="cursor-pointer text-sm text-gray-700 dark:text-gray-200">
View View
</summary> </summary>
@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> <pre class="mt-2 overflow-x-auto text-xs text-gray-800 dark:text-gray-200">{{ $fromText }}</pre>
@endif
</details> </details>
@else @else
<div class="mt-1">{{ $fromText }}</div> <div class="mt-1">{{ $fromText }}</div>
@ -128,7 +204,25 @@
<summary class="cursor-pointer text-sm text-gray-700 dark:text-gray-200"> <summary class="cursor-pointer text-sm text-gray-700 dark:text-gray-200">
View View
</summary> </summary>
@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> <pre class="mt-2 overflow-x-auto text-xs text-gray-800 dark:text-gray-200">{{ $toText }}</pre>
@endif
</details> </details>
@else @else
<div class="mt-1">{{ $toText }}</div> <div class="mt-1">{{ $toText }}</div>
@ -149,7 +243,30 @@
<summary class="cursor-pointer text-sm text-gray-700 dark:text-gray-200"> <summary class="cursor-pointer text-sm text-gray-700 dark:text-gray-200">
View View
</summary> </summary>
@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> <pre class="mt-2 overflow-x-auto text-xs text-gray-800 dark:text-gray-200">{{ $text }}</pre>
@endif
</details> </details>
@else @else
<div class="break-words">{{ $text }}</div> <div class="break-words">{{ $text }}</div>

View File

@ -20,6 +20,7 @@ ## Phase 4: Script Content Display (Safe)
- [x] T008 Add opt-in display + base64 decoding for `scriptContent` in normalized settings. - [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] 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] 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) ## Open TODOs (Follow-up)
- None yet. - None yet.

View File

@ -61,3 +61,64 @@
['deviceShellScript', '#microsoft.graph.deviceShellScript'], ['deviceShellScript', '#microsoft.graph.deviceShellScript'],
['deviceHealthScript', '#microsoft.graph.deviceHealthScript'], ['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');
});