304 lines
16 KiB
PHP
304 lines
16 KiB
PHP
<div class="space-y-6" wire:key="command-center">
|
|
{{-- Header --}}
|
|
<div class="flex items-center justify-between">
|
|
<div>
|
|
<flux:heading size="lg">Command Center</flux:heading>
|
|
<flux:subheading>Send and monitor device commands</flux:subheading>
|
|
</div>
|
|
<flux:button wire:click="$set('showSendCommandModal', true)" variant="primary" size="sm" icon="paper-airplane">
|
|
Send Command
|
|
</flux:button>
|
|
</div>
|
|
|
|
{{-- Command Stats --}}
|
|
<div class="grid grid-cols-1 md:grid-cols-4 gap-4">
|
|
<flux:card>
|
|
<div class="text-center">
|
|
<div class="text-2xl font-bold text-blue-600">{{ $this->stats['total_commands'] }}</div>
|
|
<div class="text-sm text-gray-600">Total Commands</div>
|
|
</div>
|
|
</flux:card>
|
|
<flux:card>
|
|
<div class="text-center">
|
|
<div class="text-2xl font-bold text-green-600">{{ $this->stats['successful_commands'] }}</div>
|
|
<div class="text-sm text-gray-600">Successful</div>
|
|
</div>
|
|
</flux:card>
|
|
<flux:card>
|
|
<div class="text-center">
|
|
<div class="text-2xl font-bold text-yellow-600">{{ $this->stats['pending_commands'] }}</div>
|
|
<div class="text-sm text-gray-600">Pending</div>
|
|
</div>
|
|
</flux:card>
|
|
<flux:card>
|
|
<div class="text-center">
|
|
<div class="text-2xl font-bold text-red-600">{{ $this->stats['failed_commands'] }}</div>
|
|
<div class="text-sm text-gray-600">Failed</div>
|
|
</div>
|
|
</flux:card>
|
|
</div>
|
|
|
|
{{-- Filters --}}
|
|
<flux:card>
|
|
<div class="grid grid-cols-1 md:grid-cols-5 gap-4">
|
|
<flux:input wire:model.live="filters.search" placeholder="Search commands..." icon="magnifying-glass" />
|
|
|
|
<flux:select wire:model.live="filters.status">
|
|
<option value="">All Status</option>
|
|
<option value="pending">Pending</option>
|
|
<option value="sent">Sent</option>
|
|
<option value="delivered">Delivered</option>
|
|
<option value="acknowledged">Acknowledged</option>
|
|
<option value="failed">Failed</option>
|
|
<option value="expired">Expired</option>
|
|
</flux:select>
|
|
|
|
<flux:select wire:model.live="filters.type">
|
|
<option value="">All Types</option>
|
|
<option value="position_request">Position Request</option>
|
|
<option value="engine_stop">Engine Stop</option>
|
|
<option value="engine_resume">Engine Resume</option>
|
|
<option value="alarm_arm">Alarm Arm</option>
|
|
<option value="alarm_disarm">Alarm Disarm</option>
|
|
<option value="reboot">Reboot Device</option>
|
|
<option value="custom">Custom</option>
|
|
</flux:select>
|
|
|
|
<flux:input wire:model.live="filters.device_id" placeholder="Device ID" />
|
|
|
|
<flux:select wire:model.live="filters.date_range">
|
|
<option value="">All Time</option>
|
|
<option value="today">Today</option>
|
|
<option value="week">This Week</option>
|
|
<option value="month">This Month</option>
|
|
</flux:select>
|
|
</div>
|
|
</flux:card>
|
|
|
|
{{-- Commands Table --}}
|
|
<flux:card>
|
|
<div class="overflow-x-auto">
|
|
<table class="min-w-full divide-y divide-gray-200">
|
|
<thead class="bg-gray-50">
|
|
<tr>
|
|
<th class="px-6 py-3 text-left text-xs font-medium text-gray-500 uppercase tracking-wider">
|
|
Command
|
|
</th>
|
|
<th class="px-6 py-3 text-left text-xs font-medium text-gray-500 uppercase tracking-wider">
|
|
Device
|
|
</th>
|
|
<th class="px-6 py-3 text-left text-xs font-medium text-gray-500 uppercase tracking-wider">
|
|
Status
|
|
</th>
|
|
<th class="px-6 py-3 text-left text-xs font-medium text-gray-500 uppercase tracking-wider">
|
|
Sent By
|
|
</th>
|
|
<th class="px-6 py-3 text-left text-xs font-medium text-gray-500 uppercase tracking-wider">
|
|
Timing
|
|
</th>
|
|
<th class="px-6 py-3 text-right text-xs font-medium text-gray-500 uppercase tracking-wider">
|
|
Actions
|
|
</th>
|
|
</tr>
|
|
</thead>
|
|
<tbody class="bg-white divide-y divide-gray-200">
|
|
@forelse($commands as $command)
|
|
<tr>
|
|
<td class="px-6 py-4 whitespace-nowrap">
|
|
<div class="text-sm font-medium text-gray-900">{{ $command->type }}</div>
|
|
<div class="text-sm text-gray-500">{{ $command->description }}</div>
|
|
@if($command->parameters)
|
|
<div class="text-xs text-gray-400 mt-1">
|
|
Parameters: {{ json_encode($command->parameters) }}
|
|
</div>
|
|
@endif
|
|
</td>
|
|
<td class="px-6 py-4 whitespace-nowrap">
|
|
<div class="text-sm font-medium text-gray-900">{{ $command->device_id }}</div>
|
|
@if($command->device_name)
|
|
<div class="text-sm text-gray-500">{{ $command->device_name }}</div>
|
|
@endif
|
|
</td>
|
|
<td class="px-6 py-4 whitespace-nowrap">
|
|
<span class="inline-flex px-2 py-1 text-xs font-semibold rounded-full
|
|
@if($command->status === 'pending') bg-yellow-100 text-yellow-800
|
|
@elseif($command->status === 'sent') bg-blue-100 text-blue-800
|
|
@elseif($command->status === 'delivered') bg-green-100 text-green-800
|
|
@elseif($command->status === 'acknowledged') bg-green-100 text-green-800
|
|
@elseif($command->status === 'failed') bg-red-100 text-red-800
|
|
@else bg-gray-100 text-gray-800 @endif">
|
|
{{ ucfirst($command->status) }}
|
|
</span>
|
|
@if($command->response_message)
|
|
<div class="text-xs text-gray-500 mt-1">{{ $command->response_message }}</div>
|
|
@endif
|
|
</td>
|
|
<td class="px-6 py-4 whitespace-nowrap">
|
|
<div class="text-sm text-gray-900">{{ $command->user->name ?? 'System' }}</div>
|
|
<div class="text-sm text-gray-500">{{ $command->user->email ?? 'system@auto' }}</div>
|
|
</td>
|
|
<td class="px-6 py-4 whitespace-nowrap text-sm text-gray-900">
|
|
<div>Sent: {{ $command->created_at->format('M j, Y H:i') }}</div>
|
|
@if($command->sent_at)
|
|
<div class="text-xs text-gray-500">Delivered: {{ $command->sent_at->format('M j, H:i') }}</div>
|
|
@endif
|
|
@if($command->acknowledged_at)
|
|
<div class="text-xs text-green-600">ACK: {{ $command->acknowledged_at->format('M j, H:i') }}</div>
|
|
@endif
|
|
@if($command->expires_at && $command->expires_at->isFuture())
|
|
<div class="text-xs text-yellow-600">Expires: {{ $command->expires_at->diffForHumans() }}</div>
|
|
@endif
|
|
</td>
|
|
<td class="px-6 py-4 whitespace-nowrap text-right text-sm font-medium space-x-2">
|
|
<flux:button wire:click="viewCommand({{ $command->id }})" variant="outline" size="xs">
|
|
Details
|
|
</flux:button>
|
|
@if($command->status === 'pending' || $command->status === 'sent')
|
|
<flux:button wire:click="cancelCommand({{ $command->id }})" variant="danger" size="xs">
|
|
Cancel
|
|
</flux:button>
|
|
@endif
|
|
@if($command->status === 'failed')
|
|
<flux:button wire:click="retryCommand({{ $command->id }})" variant="primary" size="xs">
|
|
Retry
|
|
</flux:button>
|
|
@endif
|
|
</td>
|
|
</tr>
|
|
@empty
|
|
<tr>
|
|
<td colspan="6" class="px-6 py-4 text-center text-gray-500">
|
|
No commands found matching your criteria.
|
|
</td>
|
|
</tr>
|
|
@endforelse
|
|
</tbody>
|
|
</table>
|
|
</div>
|
|
|
|
{{-- Pagination --}}
|
|
<div class="mt-4">
|
|
{{ $commands->links() }}
|
|
</div>
|
|
</flux:card>
|
|
|
|
{{-- Send Command Modal --}}
|
|
<flux:modal name="send-command" x-show="$wire.showSendCommandModal" class="md:max-w-lg">
|
|
<form wire:submit="sendCommand">
|
|
<div class="space-y-6">
|
|
<div>
|
|
<flux:heading size="lg">Send Device Command</flux:heading>
|
|
<flux:subheading>Execute a command on selected device(s)</flux:subheading>
|
|
</div>
|
|
|
|
<div class="space-y-4">
|
|
<flux:input wire:model="commandForm.device_id" label="Device ID" required placeholder="Enter device ID or select from list" />
|
|
|
|
<flux:select wire:model.live="commandForm.type" label="Command Type" required>
|
|
<option value="">Select Command</option>
|
|
<option value="position_request">Position Request</option>
|
|
<option value="engine_stop">Engine Stop</option>
|
|
<option value="engine_resume">Engine Resume</option>
|
|
<option value="alarm_arm">Alarm Arm</option>
|
|
<option value="alarm_disarm">Alarm Disarm</option>
|
|
<option value="reboot">Reboot Device</option>
|
|
<option value="custom">Custom Command</option>
|
|
</flux:select>
|
|
|
|
@if($commandForm['type'] === 'custom')
|
|
<flux:input wire:model="commandForm.custom_command" label="Custom Command" required />
|
|
@endif
|
|
|
|
<flux:textarea wire:model="commandForm.description" label="Description" rows="2" placeholder="Optional description for this command" />
|
|
|
|
@if(in_array($commandForm['type'], ['engine_stop', 'alarm_arm', 'reboot']))
|
|
<flux:input wire:model="commandForm.duration" label="Duration (minutes)" type="number" placeholder="Auto-reverse after duration (optional)" />
|
|
@endif
|
|
|
|
<flux:input wire:model="commandForm.expires_at" label="Expires At" type="datetime-local" />
|
|
|
|
<div class="bg-yellow-50 border border-yellow-200 rounded-lg p-4">
|
|
<div class="flex">
|
|
<flux:icon.exclamation-triangle class="h-5 w-5 text-yellow-400" />
|
|
<div class="ml-3">
|
|
<h3 class="text-sm font-medium text-yellow-800">Command Warning</h3>
|
|
<div class="mt-2 text-sm text-yellow-700">
|
|
<p>Some commands like engine stop can affect vehicle operation. Use with caution.</p>
|
|
</div>
|
|
</div>
|
|
</div>
|
|
</div>
|
|
</div>
|
|
|
|
<div class="flex justify-end space-x-2">
|
|
<flux:button type="button" variant="ghost" wire:click="closeSendCommandModal">Cancel</flux:button>
|
|
<flux:button type="submit" variant="primary">Send Command</flux:button>
|
|
</div>
|
|
</div>
|
|
</form>
|
|
</flux:modal>
|
|
|
|
{{-- Command Details Modal --}}
|
|
<flux:modal name="command-details" x-show="$wire.showDetailsModal" class="md:max-w-lg">
|
|
@if($selectedCommand)
|
|
<div class="space-y-6">
|
|
<div>
|
|
<flux:heading size="lg">Command Details</flux:heading>
|
|
<flux:subheading>{{ $selectedCommand->type }}</flux:subheading>
|
|
</div>
|
|
|
|
<div class="space-y-4">
|
|
<div class="grid grid-cols-2 gap-4">
|
|
<div>
|
|
<div class="text-sm font-medium text-gray-700">Device ID</div>
|
|
<div class="text-sm text-gray-900">{{ $selectedCommand->device_id }}</div>
|
|
</div>
|
|
<div>
|
|
<div class="text-sm font-medium text-gray-700">Status</div>
|
|
<div class="text-sm text-gray-900">{{ ucfirst($selectedCommand->status) }}</div>
|
|
</div>
|
|
</div>
|
|
|
|
<div>
|
|
<div class="text-sm font-medium text-gray-700">Description</div>
|
|
<div class="text-sm text-gray-900">{{ $selectedCommand->description ?? 'No description' }}</div>
|
|
</div>
|
|
|
|
@if($selectedCommand->parameters)
|
|
<div>
|
|
<div class="text-sm font-medium text-gray-700">Parameters</div>
|
|
<div class="text-sm text-gray-900 bg-gray-50 p-2 rounded">
|
|
<pre>{{ json_encode($selectedCommand->parameters, JSON_PRETTY_PRINT) }}</pre>
|
|
</div>
|
|
</div>
|
|
@endif
|
|
|
|
@if($selectedCommand->response_message)
|
|
<div>
|
|
<div class="text-sm font-medium text-gray-700">Response</div>
|
|
<div class="text-sm text-gray-900 bg-gray-50 p-2 rounded">{{ $selectedCommand->response_message }}</div>
|
|
</div>
|
|
@endif
|
|
|
|
<div class="grid grid-cols-2 gap-4 text-sm">
|
|
<div>
|
|
<div class="text-gray-700 font-medium">Created</div>
|
|
<div class="text-gray-900">{{ $selectedCommand->created_at->format('M j, Y H:i:s') }}</div>
|
|
</div>
|
|
@if($selectedCommand->sent_at)
|
|
<div>
|
|
<div class="text-gray-700 font-medium">Sent</div>
|
|
<div class="text-gray-900">{{ $selectedCommand->sent_at->format('M j, Y H:i:s') }}</div>
|
|
</div>
|
|
@endif
|
|
</div>
|
|
</div>
|
|
|
|
<div class="flex justify-end">
|
|
<flux:button wire:click="closeDetailsModal" variant="outline">Close</flux:button>
|
|
</div>
|
|
</div>
|
|
@endif
|
|
</flux:modal>
|
|
</div>
|