013-scripts-management #19
@ -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)')
|
||||||
|
|||||||
@ -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>
|
||||||
<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>
|
</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>
|
||||||
<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>
|
</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>
|
||||||
<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>
|
</details>
|
||||||
@else
|
@else
|
||||||
<div class="break-words">{{ $text }}</div>
|
<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] 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.
|
||||||
|
|||||||
@ -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');
|
||||||
|
});
|
||||||
|
|||||||
Loading…
Reference in New Issue
Block a user