tenantpilot.php/graph_contracts.php include the new policy type, Graph contract, and /assign assignment flow (deviceHealthScriptAssignments payload key). ScriptsPolicyNormalizer now supports deviceComplianceScript (more metadata + script display), and InteractsWithODataTypes knows the new type. UI diff view highlights detection-script changes (same logic as other script policies) once tenantpilot.display.show_script_content is enabled. Added regression coverage in tests/Feature/Filament/ScriptPoliciesNormalizedDisplayTest plus new feature test DeviceComplianceScriptPolicyTypeTest. Runs: ScriptPoliciesNormalizedDisplayTest.php, ./vendor/bin/pint --dirty. Co-authored-by: Ahmed Darrazi <ahmeddarrazi@adsmac.local> Reviewed-on: #29
788 lines
43 KiB
PHP
788 lines
43 KiB
PHP
@php
|
|
$diff = $getState() ?? ['summary' => [], 'added' => [], 'removed' => [], 'changed' => []];
|
|
$summary = $diff['summary'] ?? [];
|
|
$policyType = $diff['policy_type'] ?? null;
|
|
|
|
$groupByBlock = static function (array $items): array {
|
|
$groups = [];
|
|
|
|
foreach ($items as $path => $value) {
|
|
if (! is_string($path) || $path === '') {
|
|
continue;
|
|
}
|
|
|
|
$parts = explode(' > ', $path, 2);
|
|
|
|
if (count($parts) === 2) {
|
|
[$group, $label] = $parts;
|
|
} else {
|
|
$group = 'Other';
|
|
$label = $path;
|
|
}
|
|
|
|
$groups[$group][$label] = $value;
|
|
}
|
|
|
|
ksort($groups);
|
|
|
|
return $groups;
|
|
};
|
|
|
|
$stringify = static function (mixed $value): string {
|
|
if ($value === null) {
|
|
return '—';
|
|
}
|
|
|
|
if (is_bool($value)) {
|
|
return $value ? 'Enabled' : 'Disabled';
|
|
}
|
|
|
|
if (is_scalar($value)) {
|
|
return (string) $value;
|
|
}
|
|
|
|
return json_encode($value, JSON_PRETTY_PRINT | JSON_UNESCAPED_SLASHES | JSON_UNESCAPED_UNICODE) ?: '';
|
|
};
|
|
|
|
$isExpandable = static function (mixed $value): bool {
|
|
if (is_array($value)) {
|
|
return true;
|
|
}
|
|
|
|
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', 'deviceComplianceScript'], 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;
|
|
}
|
|
};
|
|
|
|
$highlightInline = static function (?string $policyType, string $code) use ($selectGrammar): ?string {
|
|
if (! class_exists(\Torchlight\Engine\Engine::class)) {
|
|
return null;
|
|
}
|
|
|
|
if ($code === '') {
|
|
return '';
|
|
}
|
|
|
|
try {
|
|
$html = (new \Torchlight\Engine\Engine())->codeToHtml(
|
|
code: $code,
|
|
grammar: $selectGrammar($policyType, $code),
|
|
theme: [
|
|
'light' => 'github-light',
|
|
'dark' => 'github-dark',
|
|
],
|
|
withGutter: false,
|
|
withWrapper: false,
|
|
);
|
|
|
|
$html = (string) preg_replace('/<!--\s*Syntax highlighted by[^>]*-->/', '', $html);
|
|
|
|
if (! preg_match('/<code\b[^>]*>.*?<\\/code>/s', $html, $matches)) {
|
|
return null;
|
|
}
|
|
|
|
return trim((string) ($matches[0] ?? ''));
|
|
} catch (\Throwable $e) {
|
|
return null;
|
|
}
|
|
};
|
|
|
|
$splitLines = static function (string $text): array {
|
|
$text = str_replace(["\r\n", "\r"], "\n", $text);
|
|
|
|
return $text === '' ? [] : explode("\n", $text);
|
|
};
|
|
|
|
$myersLineDiff = static function (array $a, array $b): array {
|
|
$n = count($a);
|
|
$m = count($b);
|
|
$max = $n + $m;
|
|
|
|
$v = [1 => 0];
|
|
$trace = [];
|
|
|
|
for ($d = 0; $d <= $max; $d++) {
|
|
$trace[$d] = $v;
|
|
|
|
for ($k = -$d; $k <= $d; $k += 2) {
|
|
$kPlus = $v[$k + 1] ?? 0;
|
|
$kMinus = $v[$k - 1] ?? 0;
|
|
|
|
if ($k === -$d || ($k !== $d && $kMinus < $kPlus)) {
|
|
$x = $kPlus;
|
|
} else {
|
|
$x = $kMinus + 1;
|
|
}
|
|
|
|
$y = $x - $k;
|
|
|
|
while ($x < $n && $y < $m && $a[$x] === $b[$y]) {
|
|
$x++;
|
|
$y++;
|
|
}
|
|
|
|
$v[$k] = $x;
|
|
|
|
if ($x >= $n && $y >= $m) {
|
|
break 2;
|
|
}
|
|
}
|
|
}
|
|
|
|
$ops = [];
|
|
$x = $n;
|
|
$y = $m;
|
|
|
|
for ($d = count($trace) - 1; $d >= 0; $d--) {
|
|
$v = $trace[$d];
|
|
$k = $x - $y;
|
|
|
|
$kPlus = $v[$k + 1] ?? 0;
|
|
$kMinus = $v[$k - 1] ?? 0;
|
|
|
|
if ($k === -$d || ($k !== $d && $kMinus < $kPlus)) {
|
|
$prevK = $k + 1;
|
|
} else {
|
|
$prevK = $k - 1;
|
|
}
|
|
|
|
$prevX = $v[$prevK] ?? 0;
|
|
$prevY = $prevX - $prevK;
|
|
|
|
while ($x > $prevX && $y > $prevY) {
|
|
$ops[] = ['type' => 'equal', 'line' => $a[$x - 1]];
|
|
$x--;
|
|
$y--;
|
|
}
|
|
|
|
if ($d === 0) {
|
|
break;
|
|
}
|
|
|
|
if ($x === $prevX) {
|
|
$ops[] = ['type' => 'insert', 'line' => $b[$y - 1] ?? ''];
|
|
$y--;
|
|
} else {
|
|
$ops[] = ['type' => 'delete', 'line' => $a[$x - 1] ?? ''];
|
|
$x--;
|
|
}
|
|
}
|
|
|
|
return array_reverse($ops);
|
|
};
|
|
|
|
$scriptLineDiff = static function (string $fromText, string $toText) use ($splitLines, $myersLineDiff): array {
|
|
return $myersLineDiff($splitLines($fromText), $splitLines($toText));
|
|
};
|
|
@endphp
|
|
|
|
<div class="space-y-4">
|
|
<x-filament::section
|
|
heading="Normalized diff"
|
|
:description="$summary['message'] ?? sprintf('%d added, %d removed, %d changed', $summary['added'] ?? 0, $summary['removed'] ?? 0, $summary['changed'] ?? 0)"
|
|
>
|
|
<div class="flex flex-wrap gap-2">
|
|
<x-filament::badge color="success">
|
|
{{ (int) ($summary['added'] ?? 0) }} added
|
|
</x-filament::badge>
|
|
<x-filament::badge color="danger">
|
|
{{ (int) ($summary['removed'] ?? 0) }} removed
|
|
</x-filament::badge>
|
|
<x-filament::badge color="warning">
|
|
{{ (int) ($summary['changed'] ?? 0) }} changed
|
|
</x-filament::badge>
|
|
</div>
|
|
</x-filament::section>
|
|
|
|
@foreach (['changed' => ['label' => 'Changed', 'collapsed' => false], 'added' => ['label' => 'Added', 'collapsed' => true], 'removed' => ['label' => 'Removed', 'collapsed' => true]] as $key => $meta)
|
|
@php
|
|
$items = $diff[$key] ?? [];
|
|
$groups = $groupByBlock(is_array($items) ? $items : []);
|
|
@endphp
|
|
|
|
@if ($groups !== [])
|
|
<x-filament::section
|
|
:heading="$meta['label']"
|
|
collapsible
|
|
:collapsed="$meta['collapsed']"
|
|
>
|
|
<div class="space-y-6">
|
|
@foreach ($groups as $group => $groupItems)
|
|
<div>
|
|
<div class="flex items-center justify-between">
|
|
<div class="text-sm font-medium text-gray-900 dark:text-white">
|
|
{{ $group }}
|
|
</div>
|
|
<x-filament::badge size="sm" color="gray">
|
|
{{ count($groupItems) }}
|
|
</x-filament::badge>
|
|
</div>
|
|
|
|
<div class="mt-2 divide-y divide-gray-200 rounded-lg border border-gray-200 dark:divide-white/10 dark:border-white/10">
|
|
@foreach ($groupItems as $name => $value)
|
|
<div class="px-4 py-3">
|
|
@if ($key === 'changed' && is_array($value) && array_key_exists('from', $value) && array_key_exists('to', $value))
|
|
@php
|
|
$from = $value['from'];
|
|
$to = $value['to'];
|
|
$fromText = $stringify($from);
|
|
$toText = $stringify($to);
|
|
|
|
$isScriptContent = $canHighlightScripts($policyType) && $isScriptKey($name);
|
|
$ops = $isScriptContent ? $scriptLineDiff((string) $fromText, (string) $toText) : [];
|
|
$useTorchlight = $isScriptContent && class_exists(\Torchlight\Engine\Engine::class);
|
|
|
|
$rows = [];
|
|
if ($isScriptContent) {
|
|
$count = count($ops);
|
|
|
|
for ($i = 0; $i < $count; $i++) {
|
|
$op = $ops[$i];
|
|
$next = $ops[$i + 1] ?? null;
|
|
$type = $op['type'] ?? null;
|
|
$line = (string) ($op['line'] ?? '');
|
|
|
|
if ($type === 'equal') {
|
|
$rows[] = [
|
|
'left' => ['type' => 'equal', 'line' => $line],
|
|
'right' => ['type' => 'equal', 'line' => $line],
|
|
];
|
|
continue;
|
|
}
|
|
|
|
if ($type === 'delete' && is_array($next) && ($next['type'] ?? null) === 'insert') {
|
|
$rows[] = [
|
|
'left' => ['type' => 'delete', 'line' => $line],
|
|
'right' => ['type' => 'insert', 'line' => (string) ($next['line'] ?? '')],
|
|
];
|
|
$i++;
|
|
continue;
|
|
}
|
|
|
|
if ($type === 'delete') {
|
|
$rows[] = [
|
|
'left' => ['type' => 'delete', 'line' => $line],
|
|
'right' => ['type' => 'blank', 'line' => ''],
|
|
];
|
|
continue;
|
|
}
|
|
|
|
if ($type === 'insert') {
|
|
$rows[] = [
|
|
'left' => ['type' => 'blank', 'line' => ''],
|
|
'right' => ['type' => 'insert', 'line' => $line],
|
|
];
|
|
continue;
|
|
}
|
|
}
|
|
}
|
|
@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">
|
|
{{ (string) $name }}
|
|
</div>
|
|
|
|
@if ($isScriptContent)
|
|
<div class="text-sm text-gray-600 dark:text-gray-300 sm:col-span-2">
|
|
<span class="text-xs font-semibold uppercase tracking-wide text-gray-500 dark:text-gray-400">Script</span>
|
|
<details class="mt-1" x-data="{ fullscreenOpen: false }">
|
|
<summary class="cursor-pointer text-sm text-gray-700 dark:text-gray-200">
|
|
View
|
|
</summary>
|
|
|
|
<div x-data="{ tab: 'diff' }" class="mt-2 space-y-3">
|
|
<div class="flex flex-wrap items-center gap-2">
|
|
<x-filament::button size="xs" color="gray" type="button" x-on:click="tab = 'diff'" x-bind:class="tab === 'diff' ? 'ring-1 ring-gray-300 dark:ring-white/20' : ''">
|
|
Diff
|
|
</x-filament::button>
|
|
<x-filament::button size="xs" color="gray" type="button" x-on:click="tab = 'before'" x-bind:class="tab === 'before' ? 'ring-1 ring-gray-300 dark:ring-white/20' : ''">
|
|
Before
|
|
</x-filament::button>
|
|
<x-filament::button size="xs" color="gray" type="button" x-on:click="tab = 'after'" x-bind:class="tab === 'after' ? 'ring-1 ring-gray-300 dark:ring-white/20' : ''">
|
|
After
|
|
</x-filament::button>
|
|
|
|
<x-filament::button size="xs" color="gray" type="button" x-on:click="fullscreenOpen = true">
|
|
⤢ Fullscreen
|
|
</x-filament::button>
|
|
</div>
|
|
|
|
<div x-show="tab === 'diff'" x-cloak>
|
|
<div class="grid grid-cols-1 gap-3 sm:grid-cols-2">
|
|
<div>
|
|
<div class="text-xs font-semibold uppercase tracking-wide text-gray-500 dark:text-gray-400">Old</div>
|
|
<pre class="mt-1 max-h-96 overflow-auto font-mono text-xs text-gray-800 dark:text-gray-200 whitespace-pre">@php
|
|
foreach ($rows as $row) {
|
|
$left = $row['left'];
|
|
$leftType = $left['type'];
|
|
$leftLine = (string) ($left['line'] ?? '');
|
|
|
|
$leftHighlighted = $useTorchlight ? $highlightInline($policyType, $leftLine) : null;
|
|
$leftRendered = (is_string($leftHighlighted) && $leftHighlighted !== '') ? $leftHighlighted : e($leftLine);
|
|
|
|
if ($leftType === 'equal') {
|
|
if ($useTorchlight) {
|
|
@endphp
|
|
@once
|
|
@include('filament.partials.torchlight-dark-overrides')
|
|
<style>
|
|
.tp-script-diff-line code.torchlight {
|
|
background-color: transparent !important;
|
|
}
|
|
</style>
|
|
@endonce
|
|
@php
|
|
}
|
|
|
|
echo '<span class="tp-script-diff-line">'.$leftRendered."</span>\n";
|
|
continue;
|
|
}
|
|
|
|
if ($leftType === 'delete') {
|
|
if ($useTorchlight) {
|
|
@endphp
|
|
@once
|
|
@include('filament.partials.torchlight-dark-overrides')
|
|
<style>
|
|
.tp-script-diff-line code.torchlight {
|
|
background-color: transparent !important;
|
|
}
|
|
</style>
|
|
@endonce
|
|
@php
|
|
}
|
|
|
|
echo '<span class="block tp-script-diff-line bg-danger-50 text-danger-700 dark:bg-danger-950/40 dark:text-danger-200">- '.$leftRendered."</span>\n";
|
|
continue;
|
|
}
|
|
|
|
echo "\n";
|
|
}
|
|
@endphp</pre>
|
|
</div>
|
|
|
|
<div>
|
|
<div class="text-xs font-semibold uppercase tracking-wide text-gray-500 dark:text-gray-400">New</div>
|
|
<pre class="mt-1 max-h-96 overflow-auto font-mono text-xs text-gray-800 dark:text-gray-200 whitespace-pre">@php
|
|
foreach ($rows as $row) {
|
|
$right = $row['right'];
|
|
$rightType = $right['type'];
|
|
$rightLine = (string) ($right['line'] ?? '');
|
|
|
|
$rightHighlighted = $useTorchlight ? $highlightInline($policyType, $rightLine) : null;
|
|
$rightRendered = (is_string($rightHighlighted) && $rightHighlighted !== '') ? $rightHighlighted : e($rightLine);
|
|
|
|
if ($rightType === 'equal') {
|
|
if ($useTorchlight) {
|
|
@endphp
|
|
@once
|
|
@include('filament.partials.torchlight-dark-overrides')
|
|
<style>
|
|
.tp-script-diff-line code.torchlight {
|
|
background-color: transparent !important;
|
|
}
|
|
</style>
|
|
@endonce
|
|
@php
|
|
}
|
|
|
|
echo '<span class="tp-script-diff-line">'.$rightRendered."</span>\n";
|
|
continue;
|
|
}
|
|
|
|
if ($rightType === 'insert') {
|
|
if ($useTorchlight) {
|
|
@endphp
|
|
@once
|
|
@include('filament.partials.torchlight-dark-overrides')
|
|
<style>
|
|
.tp-script-diff-line code.torchlight {
|
|
background-color: transparent !important;
|
|
}
|
|
</style>
|
|
@endonce
|
|
@php
|
|
}
|
|
|
|
echo '<span class="block tp-script-diff-line bg-success-50 text-success-700 dark:bg-success-950/40 dark:text-success-200">+ '.$rightRendered."</span>\n";
|
|
continue;
|
|
}
|
|
|
|
echo "\n";
|
|
}
|
|
@endphp</pre>
|
|
</div>
|
|
</div>
|
|
</div>
|
|
|
|
<div x-show="tab === 'before'" x-cloak>
|
|
<div class="text-xs font-semibold uppercase tracking-wide text-gray-500 dark:text-gray-400">Before</div>
|
|
@php
|
|
$highlightedBefore = $useTorchlight ? $highlight($policyType, (string) $fromText) : null;
|
|
@endphp
|
|
|
|
@if (is_string($highlightedBefore) && $highlightedBefore !== '')
|
|
@once
|
|
@include('filament.partials.torchlight-dark-overrides')
|
|
@endonce
|
|
|
|
<div class="mt-1 max-h-96 overflow-auto">{!! $highlightedBefore !!}</div>
|
|
@else
|
|
<pre class="mt-1 max-h-96 overflow-auto font-mono text-xs text-gray-800 dark:text-gray-200 whitespace-pre">{{ (string) $fromText }}</pre>
|
|
@endif
|
|
</div>
|
|
|
|
<div x-show="tab === 'after'" x-cloak>
|
|
<div class="text-xs font-semibold uppercase tracking-wide text-gray-500 dark:text-gray-400">After</div>
|
|
@php
|
|
$highlightedAfter = $useTorchlight ? $highlight($policyType, (string) $toText) : null;
|
|
@endphp
|
|
|
|
@if (is_string($highlightedAfter) && $highlightedAfter !== '')
|
|
@once
|
|
@include('filament.partials.torchlight-dark-overrides')
|
|
@endonce
|
|
|
|
<div class="mt-1 max-h-96 overflow-auto">{!! $highlightedAfter !!}</div>
|
|
@else
|
|
<pre class="mt-1 max-h-96 overflow-auto font-mono text-xs text-gray-800 dark:text-gray-200 whitespace-pre">{{ (string) $toText }}</pre>
|
|
@endif
|
|
</div>
|
|
</div>
|
|
|
|
<div
|
|
x-show="fullscreenOpen"
|
|
x-cloak
|
|
x-on:keydown.escape.window="fullscreenOpen = false"
|
|
class="fixed inset-0 z-50"
|
|
>
|
|
<div class="absolute inset-0 bg-gray-950/50"></div>
|
|
<div class="relative flex h-full w-full flex-col bg-white dark:bg-gray-900">
|
|
<div class="flex items-center justify-between gap-3 border-b border-gray-200 px-4 py-3 dark:border-white/10">
|
|
<div class="text-sm font-medium text-gray-900 dark:text-white">Script diff</div>
|
|
<div class="flex items-center gap-2">
|
|
<x-filament::button size="sm" color="gray" type="button" x-on:click="fullscreenOpen = false">
|
|
Close
|
|
</x-filament::button>
|
|
</div>
|
|
</div>
|
|
|
|
<div class="flex-1 overflow-hidden p-4">
|
|
<div
|
|
x-data="{
|
|
tab: 'diff',
|
|
syncing: false,
|
|
syncHorizontal: true,
|
|
sync(from, to) {
|
|
if (this.syncing) return;
|
|
this.syncing = true;
|
|
|
|
to.scrollTop = from.scrollTop;
|
|
|
|
const bothHorizontal = this.syncHorizontal
|
|
&& from.scrollWidth > from.clientWidth
|
|
&& to.scrollWidth > to.clientWidth;
|
|
|
|
if (bothHorizontal) {
|
|
to.scrollLeft = from.scrollLeft;
|
|
}
|
|
|
|
requestAnimationFrame(() => { this.syncing = false; });
|
|
},
|
|
}"
|
|
x-init="$nextTick(() => {
|
|
const left = $refs.left;
|
|
const right = $refs.right;
|
|
|
|
if (!left || !right) return;
|
|
|
|
left.addEventListener('scroll', () => sync(left, right), { passive: true });
|
|
right.addEventListener('scroll', () => sync(right, left), { passive: true });
|
|
})"
|
|
class="h-full space-y-3"
|
|
>
|
|
<div class="flex flex-wrap items-center gap-2">
|
|
<x-filament::button size="sm" color="gray" type="button" x-on:click="tab = 'diff'" x-bind:class="tab === 'diff' ? 'ring-1 ring-gray-300 dark:ring-white/20' : ''">
|
|
Diff
|
|
</x-filament::button>
|
|
<x-filament::button size="sm" color="gray" type="button" x-on:click="tab = 'before'" x-bind:class="tab === 'before' ? 'ring-1 ring-gray-300 dark:ring-white/20' : ''">
|
|
Before
|
|
</x-filament::button>
|
|
<x-filament::button size="sm" color="gray" type="button" x-on:click="tab = 'after'" x-bind:class="tab === 'after' ? 'ring-1 ring-gray-300 dark:ring-white/20' : ''">
|
|
After
|
|
</x-filament::button>
|
|
</div>
|
|
|
|
<div x-show="tab === 'diff'" x-cloak class="h-[calc(100%-3rem)]">
|
|
<div class="grid h-full grid-cols-1 gap-4 lg:grid-cols-2">
|
|
<div class="flex h-full flex-col">
|
|
<div class="text-xs font-semibold uppercase tracking-wide text-gray-500 dark:text-gray-400">Old</div>
|
|
<pre x-ref="left" class="mt-2 flex-1 overflow-auto font-mono text-xs text-gray-800 dark:text-gray-200 whitespace-pre">@php
|
|
foreach ($rows as $row) {
|
|
$left = $row['left'];
|
|
$leftType = $left['type'];
|
|
$leftLine = (string) ($left['line'] ?? '');
|
|
|
|
$leftHighlighted = $useTorchlight ? $highlightInline($policyType, $leftLine) : null;
|
|
$leftRendered = (is_string($leftHighlighted) && $leftHighlighted !== '') ? $leftHighlighted : e($leftLine);
|
|
|
|
if ($leftType === 'equal') {
|
|
if ($useTorchlight) {
|
|
@endphp
|
|
@once
|
|
@include('filament.partials.torchlight-dark-overrides')
|
|
<style>
|
|
.tp-script-diff-line code.torchlight {
|
|
background-color: transparent !important;
|
|
}
|
|
</style>
|
|
@endonce
|
|
@php
|
|
}
|
|
|
|
echo '<span class="tp-script-diff-line">'.$leftRendered."</span>\n";
|
|
continue;
|
|
}
|
|
|
|
if ($leftType === 'delete') {
|
|
if ($useTorchlight) {
|
|
@endphp
|
|
@once
|
|
@include('filament.partials.torchlight-dark-overrides')
|
|
<style>
|
|
.tp-script-diff-line code.torchlight {
|
|
background-color: transparent !important;
|
|
}
|
|
</style>
|
|
@endonce
|
|
@php
|
|
}
|
|
|
|
echo '<span class="block tp-script-diff-line bg-danger-50 text-danger-700 dark:bg-danger-950/40 dark:text-danger-200">- '.$leftRendered."</span>\n";
|
|
continue;
|
|
}
|
|
|
|
echo "\n";
|
|
}
|
|
@endphp</pre>
|
|
</div>
|
|
|
|
<div class="flex h-full flex-col">
|
|
<div class="text-xs font-semibold uppercase tracking-wide text-gray-500 dark:text-gray-400">New</div>
|
|
<pre x-ref="right" class="mt-2 flex-1 overflow-auto font-mono text-xs text-gray-800 dark:text-gray-200 whitespace-pre">@php
|
|
foreach ($rows as $row) {
|
|
$right = $row['right'];
|
|
$rightType = $right['type'];
|
|
$rightLine = (string) ($right['line'] ?? '');
|
|
|
|
$rightHighlighted = $useTorchlight ? $highlightInline($policyType, $rightLine) : null;
|
|
$rightRendered = (is_string($rightHighlighted) && $rightHighlighted !== '') ? $rightHighlighted : e($rightLine);
|
|
|
|
if ($rightType === 'equal') {
|
|
if ($useTorchlight) {
|
|
@endphp
|
|
@once
|
|
@include('filament.partials.torchlight-dark-overrides')
|
|
<style>
|
|
.tp-script-diff-line code.torchlight {
|
|
background-color: transparent !important;
|
|
}
|
|
</style>
|
|
@endonce
|
|
@php
|
|
}
|
|
|
|
echo '<span class="tp-script-diff-line">'.$rightRendered."</span>\n";
|
|
continue;
|
|
}
|
|
|
|
if ($rightType === 'insert') {
|
|
if ($useTorchlight) {
|
|
@endphp
|
|
@once
|
|
@include('filament.partials.torchlight-dark-overrides')
|
|
<style>
|
|
.tp-script-diff-line code.torchlight {
|
|
background-color: transparent !important;
|
|
}
|
|
</style>
|
|
@endonce
|
|
@php
|
|
}
|
|
|
|
echo '<span class="block tp-script-diff-line bg-success-50 text-success-700 dark:bg-success-950/40 dark:text-success-200">+ '.$rightRendered."</span>\n";
|
|
continue;
|
|
}
|
|
|
|
echo "\n";
|
|
}
|
|
@endphp</pre>
|
|
</div>
|
|
</div>
|
|
</div>
|
|
|
|
<div x-show="tab === 'before'" x-cloak class="h-[calc(100%-3rem)]">
|
|
<div class="text-xs font-semibold uppercase tracking-wide text-gray-500 dark:text-gray-400">Before</div>
|
|
@php
|
|
$highlightedBeforeFullscreen = $useTorchlight ? $highlight($policyType, (string) $fromText) : null;
|
|
@endphp
|
|
|
|
@if (is_string($highlightedBeforeFullscreen) && $highlightedBeforeFullscreen !== '')
|
|
@once
|
|
@include('filament.partials.torchlight-dark-overrides')
|
|
@endonce
|
|
|
|
<div class="mt-2 h-full overflow-auto">{!! $highlightedBeforeFullscreen !!}</div>
|
|
@else
|
|
<pre class="mt-2 h-full overflow-auto font-mono text-xs text-gray-800 dark:text-gray-200 whitespace-pre">{{ (string) $fromText }}</pre>
|
|
@endif
|
|
</div>
|
|
|
|
<div x-show="tab === 'after'" x-cloak class="h-[calc(100%-3rem)]">
|
|
<div class="text-xs font-semibold uppercase tracking-wide text-gray-500 dark:text-gray-400">After</div>
|
|
@php
|
|
$highlightedAfterFullscreen = $useTorchlight ? $highlight($policyType, (string) $toText) : null;
|
|
@endphp
|
|
|
|
@if (is_string($highlightedAfterFullscreen) && $highlightedAfterFullscreen !== '')
|
|
@once
|
|
@include('filament.partials.torchlight-dark-overrides')
|
|
@endonce
|
|
|
|
<div class="mt-2 h-full overflow-auto">{!! $highlightedAfterFullscreen !!}</div>
|
|
@else
|
|
<pre class="mt-2 h-full overflow-auto font-mono text-xs text-gray-800 dark:text-gray-200 whitespace-pre">{{ (string) $toText }}</pre>
|
|
@endif
|
|
</div>
|
|
</div>
|
|
</div>
|
|
</div>
|
|
</div>
|
|
</details>
|
|
</div>
|
|
@else
|
|
<div class="text-sm text-gray-600 dark:text-gray-300">
|
|
<span class="text-xs font-semibold uppercase tracking-wide text-gray-500 dark:text-gray-400">From</span>
|
|
@if ($isExpandable($from))
|
|
<details class="mt-1">
|
|
<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>
|
|
</details>
|
|
@else
|
|
<div class="mt-1">{{ $fromText }}</div>
|
|
@endif
|
|
</div>
|
|
<div class="text-sm text-gray-600 dark:text-gray-300">
|
|
<span class="text-xs font-semibold uppercase tracking-wide text-gray-500 dark:text-gray-400">To</span>
|
|
@if ($isExpandable($to))
|
|
<details class="mt-1">
|
|
<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>
|
|
</details>
|
|
@else
|
|
<div class="mt-1">{{ $toText }}</div>
|
|
@endif
|
|
</div>
|
|
@endif
|
|
</div>
|
|
@else
|
|
@php
|
|
$text = $stringify($value);
|
|
@endphp
|
|
<div class="flex flex-col gap-2 sm:flex-row sm:items-start sm:justify-between">
|
|
<div class="text-sm font-medium text-gray-900 dark:text-white">
|
|
{{ (string) $name }}
|
|
</div>
|
|
<div class="text-sm text-gray-700 dark:text-gray-200 sm:max-w-[70%]">
|
|
@if ($isExpandable($value))
|
|
<details>
|
|
<summary class="cursor-pointer text-sm text-gray-700 dark:text-gray-200">
|
|
View
|
|
</summary>
|
|
@php
|
|
$isScriptContent = $canHighlightScripts($policyType) && $isScriptKey($name);
|
|
$highlighted = $isScriptContent ? $highlight($policyType, (string) $text) : null;
|
|
@endphp
|
|
|
|
@if (is_string($highlighted) && $highlighted !== '')
|
|
@once
|
|
@include('filament.partials.torchlight-dark-overrides')
|
|
@endonce
|
|
|
|
<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>
|
|
@endif
|
|
</div>
|
|
</div>
|
|
@endif
|
|
</div>
|
|
@endforeach
|
|
</div>
|
|
</div>
|
|
@endforeach
|
|
</div>
|
|
</x-filament::section>
|
|
@endif
|
|
@endforeach
|
|
</div>
|