fedi-feed-router/resources/views/livewire/routes.blade.php

380 lines
24 KiB
PHP

<div class="p-6">
<div class="mb-8 flex items-center justify-between">
<div>
<h1 class="text-2xl font-bold text-gray-900">Routes</h1>
<p class="mt-1 text-sm text-gray-500">
Manage connections between your feeds and channels
</p>
</div>
<button
wire:click="openCreateModal"
class="inline-flex items-center px-4 py-2 border border-transparent text-sm font-medium rounded-md text-white bg-blue-600 hover:bg-blue-700 focus:outline-none focus:ring-2 focus:ring-offset-2 focus:ring-blue-500"
>
<svg class="h-4 w-4 mr-2" fill="none" viewBox="0 0 24 24" stroke-width="1.5" stroke="currentColor">
<path stroke-linecap="round" stroke-linejoin="round" d="M12 4.5v15m7.5-7.5h-15" />
</svg>
Create Route
</button>
</div>
<div class="space-y-6">
@forelse ($routes as $route)
<div class="bg-white rounded-lg shadow p-6" wire:key="route-{{ $route->feed_id }}-{{ $route->platform_channel_id }}">
<div class="flex items-start justify-between">
<div class="flex-1 min-w-0">
<div class="flex items-center space-x-3 mb-2">
<h3 class="text-lg font-medium text-gray-900">
{{ $route->feed?->name }} {{ $route->platformChannel?->display_name ?? $route->platformChannel?->name }}
</h3>
@if ($route->is_active)
<span class="inline-flex items-center px-2.5 py-0.5 rounded-full text-xs font-medium bg-green-100 text-green-800">
<svg class="h-3 w-3 mr-1" fill="none" viewBox="0 0 24 24" stroke-width="1.5" stroke="currentColor">
<path stroke-linecap="round" stroke-linejoin="round" d="M9 12.75 11.25 15 15 9.75M21 12a9 9 0 1 1-18 0 9 9 0 0 1 18 0Z" />
</svg>
Active
</span>
@else
<span class="inline-flex items-center px-2.5 py-0.5 rounded-full text-xs font-medium bg-red-100 text-red-800">
<svg class="h-3 w-3 mr-1" fill="none" viewBox="0 0 24 24" stroke-width="1.5" stroke="currentColor">
<path stroke-linecap="round" stroke-linejoin="round" d="m9.75 9.75 4.5 4.5m0-4.5-4.5 4.5M21 12a9 9 0 1 1-18 0 9 9 0 0 1 18 0Z" />
</svg>
Inactive
</span>
@endif
</div>
<div class="flex items-center space-x-4 text-sm text-gray-600">
<span>Priority: {{ $route->priority }}</span>
<span>&bull;</span>
<span>Feed: {{ $route->feed?->name }}</span>
<span>&bull;</span>
<span>Channel: {{ $route->platformChannel?->display_name ?? $route->platformChannel?->name }}</span>
<span>&bull;</span>
<span>Created: {{ $route->created_at->format('M d, Y') }}</span>
</div>
@if ($route->platformChannel?->description)
<p class="mt-2 text-sm text-gray-500">
{{ $route->platformChannel->description }}
</p>
@endif
@if ($route->keywords->isNotEmpty())
<div class="mt-3">
<div class="flex items-center space-x-2 mb-2">
<svg class="h-4 w-4 text-gray-500" fill="none" viewBox="0 0 24 24" stroke-width="1.5" stroke="currentColor">
<path stroke-linecap="round" stroke-linejoin="round" d="M9.568 3H5.25A2.25 2.25 0 0 0 3 5.25v4.318c0 .597.237 1.17.659 1.591l9.581 9.581c.699.699 1.78.872 2.607.33a18.095 18.095 0 0 0 5.223-5.223c.542-.827.369-1.908-.33-2.607L11.16 3.66A2.25 2.25 0 0 0 9.568 3Z" />
<path stroke-linecap="round" stroke-linejoin="round" d="M6 6h.008v.008H6V6Z" />
</svg>
<span class="text-sm font-medium text-gray-700">Keywords</span>
</div>
<div class="flex flex-wrap gap-2">
@foreach ($route->keywords as $keyword)
<span class="inline-flex items-center px-2.5 py-0.5 rounded-full text-xs font-medium {{ $keyword->is_active ? 'bg-blue-100 text-blue-800' : 'bg-gray-100 text-gray-500' }}">
{{ $keyword->keyword }}
</span>
@endforeach
</div>
</div>
@else
<div class="mt-3 text-sm text-gray-400 italic">
No keyword filters - matches all articles
</div>
@endif
</div>
<div class="flex items-center space-x-2 ml-4">
<button
wire:click="openEditModal({{ $route->feed_id }}, {{ $route->platform_channel_id }})"
class="p-2 text-gray-400 hover:text-gray-600 rounded-md"
title="Edit route"
>
<svg class="h-4 w-4" fill="none" viewBox="0 0 24 24" stroke-width="1.5" stroke="currentColor">
<path stroke-linecap="round" stroke-linejoin="round" d="m16.862 4.487 1.687-1.688a1.875 1.875 0 1 1 2.652 2.652L10.582 16.07a4.5 4.5 0 0 1-1.897 1.13L6 18l.8-2.685a4.5 4.5 0 0 1 1.13-1.897l8.932-8.931Zm0 0L19.5 7.125M18 14v4.75A2.25 2.25 0 0 1 15.75 21H5.25A2.25 2.25 0 0 1 3 18.75V8.25A2.25 2.25 0 0 1 5.25 6H10" />
</svg>
</button>
<button
wire:click="toggle({{ $route->feed_id }}, {{ $route->platform_channel_id }})"
class="p-2 text-gray-400 hover:text-gray-600 rounded-md"
title="{{ $route->is_active ? 'Deactivate route' : 'Activate route' }}"
>
@if ($route->is_active)
<svg class="h-4 w-4 text-green-600" fill="none" viewBox="0 0 24 24" stroke-width="1.5" stroke="currentColor">
<path stroke-linecap="round" stroke-linejoin="round" d="M5.636 5.636a9 9 0 1 0 12.728 0M12 3v9" />
</svg>
@else
<svg class="h-4 w-4" fill="none" viewBox="0 0 24 24" stroke-width="1.5" stroke="currentColor">
<path stroke-linecap="round" stroke-linejoin="round" d="M5.636 5.636a9 9 0 1 0 12.728 0M12 3v9" />
</svg>
@endif
</button>
<button
wire:click="delete({{ $route->feed_id }}, {{ $route->platform_channel_id }})"
wire:confirm="Are you sure you want to delete this route?"
class="p-2 text-gray-400 hover:text-red-600 rounded-md"
title="Delete route"
>
<svg class="h-4 w-4" fill="none" viewBox="0 0 24 24" stroke-width="1.5" stroke="currentColor">
<path stroke-linecap="round" stroke-linejoin="round" d="m14.74 9-.346 9m-4.788 0L9.26 9m9.968-3.21c.342.052.682.107 1.022.166m-1.022-.165L18.16 19.673a2.25 2.25 0 0 1-2.244 2.077H8.084a2.25 2.25 0 0 1-2.244-2.077L4.772 5.79m14.456 0a48.108 48.108 0 0 0-3.478-.397m-12 .562c.34-.059.68-.114 1.022-.165m0 0a48.11 48.11 0 0 1 3.478-.397m7.5 0v-.916c0-1.18-.91-2.164-2.09-2.201a51.964 51.964 0 0 0-3.32 0c-1.18.037-2.09 1.022-2.09 2.201v.916m7.5 0a48.667 48.667 0 0 0-7.5 0" />
</svg>
</button>
</div>
</div>
</div>
@empty
<div class="text-center py-12">
<svg class="mx-auto h-12 w-12 text-gray-400" fill="none" viewBox="0 0 24 24" stroke-width="1.5" stroke="currentColor">
<path stroke-linecap="round" stroke-linejoin="round" d="M13.5 6H5.25A2.25 2.25 0 0 0 3 8.25v10.5A2.25 2.25 0 0 0 5.25 21h10.5A2.25 2.25 0 0 0 18 18.75V10.5m-10.5 6L21 3m0 0h-5.25M21 3v5.25" />
</svg>
<h3 class="mt-2 text-sm font-medium text-gray-900">No routes</h3>
<p class="mt-1 text-sm text-gray-500">
Get started by creating a new route to connect feeds with channels.
</p>
<div class="mt-6">
<button
wire:click="openCreateModal"
class="inline-flex items-center px-4 py-2 border border-transparent text-sm font-medium rounded-md text-white bg-blue-600 hover:bg-blue-700 focus:outline-none focus:ring-2 focus:ring-offset-2 focus:ring-blue-500"
>
<svg class="h-4 w-4 mr-2" fill="none" viewBox="0 0 24 24" stroke-width="1.5" stroke="currentColor">
<path stroke-linecap="round" stroke-linejoin="round" d="M12 4.5v15m7.5-7.5h-15" />
</svg>
Create Route
</button>
</div>
</div>
@endforelse
</div>
<!-- Create Route Modal -->
@if ($showCreateModal)
<div class="fixed inset-0 z-50 overflow-y-auto" aria-labelledby="modal-title" role="dialog" aria-modal="true">
<div class="flex items-end justify-center min-h-screen pt-4 px-4 pb-20 text-center sm:block sm:p-0">
<div class="fixed inset-0 bg-gray-500 bg-opacity-75 transition-opacity" wire:click="closeCreateModal"></div>
<span class="hidden sm:inline-block sm:align-middle sm:h-screen" aria-hidden="true">&#8203;</span>
<div class="inline-block align-bottom bg-white rounded-lg px-4 pt-5 pb-4 text-left overflow-hidden shadow-xl transform transition-all sm:my-8 sm:align-middle sm:max-w-lg sm:w-full sm:p-6">
<h3 class="text-lg font-medium text-gray-900 mb-4">Create New Route</h3>
<form wire:submit="createRoute" class="space-y-4">
<div>
<label for="newFeedId" class="block text-sm font-medium text-gray-700 mb-1">
Feed
</label>
<select
id="newFeedId"
wire:model="newFeedId"
class="w-full px-3 py-2 border border-gray-300 rounded-md focus:outline-none focus:ring-2 focus:ring-blue-500"
required
>
<option value="">Select a feed</option>
@foreach ($feeds as $feed)
<option value="{{ $feed->id }}">{{ $feed->name }}</option>
@endforeach
</select>
@error('newFeedId') <span class="text-red-500 text-sm">{{ $message }}</span> @enderror
</div>
<div>
<label for="newChannelId" class="block text-sm font-medium text-gray-700 mb-1">
Channel
</label>
<select
id="newChannelId"
wire:model="newChannelId"
class="w-full px-3 py-2 border border-gray-300 rounded-md focus:outline-none focus:ring-2 focus:ring-blue-500"
required
>
<option value="">Select a channel</option>
@foreach ($channels as $channel)
<option value="{{ $channel->id }}">{{ $channel->display_name ?? $channel->name }}</option>
@endforeach
</select>
@error('newChannelId') <span class="text-red-500 text-sm">{{ $message }}</span> @enderror
</div>
<div>
<label for="newPriority" class="block text-sm font-medium text-gray-700 mb-1">
Priority
</label>
<input
type="number"
id="newPriority"
wire:model="newPriority"
min="0"
class="w-full px-3 py-2 border border-gray-300 rounded-md focus:outline-none focus:ring-2 focus:ring-blue-500"
>
<p class="text-sm text-gray-500 mt-1">Higher priority routes are processed first</p>
</div>
<div class="flex justify-end space-x-3 pt-4">
<button
type="button"
wire:click="closeCreateModal"
class="px-4 py-2 text-sm font-medium text-gray-700 bg-white border border-gray-300 rounded-md hover:bg-gray-50"
>
Cancel
</button>
<button
type="submit"
class="px-4 py-2 text-sm font-medium text-white bg-blue-600 border border-transparent rounded-md hover:bg-blue-700"
>
Create Route
</button>
</div>
</form>
</div>
</div>
</div>
@endif
<!-- Edit Route Modal -->
@if ($editingRoute)
<div class="fixed inset-0 z-50 overflow-y-auto" aria-labelledby="modal-title" role="dialog" aria-modal="true">
<div class="flex items-end justify-center min-h-screen pt-4 px-4 pb-20 text-center sm:block sm:p-0">
<div class="fixed inset-0 bg-gray-500 bg-opacity-75 transition-opacity" wire:click="closeEditModal"></div>
<span class="hidden sm:inline-block sm:align-middle sm:h-screen" aria-hidden="true">&#8203;</span>
<div class="inline-block align-bottom bg-white rounded-lg px-4 pt-5 pb-4 text-left overflow-hidden shadow-xl transform transition-all sm:my-8 sm:align-middle sm:max-w-lg sm:w-full sm:p-6">
<div class="flex items-center justify-between mb-4">
<h3 class="text-lg font-medium text-gray-900">Edit Route</h3>
<button wire:click="closeEditModal" class="text-gray-400 hover:text-gray-600">
<svg class="h-6 w-6" fill="none" viewBox="0 0 24 24" stroke-width="1.5" stroke="currentColor">
<path stroke-linecap="round" stroke-linejoin="round" d="M6 18 18 6M6 6l12 12" />
</svg>
</button>
</div>
<div class="mb-4 p-3 bg-gray-50 rounded-md">
<p class="text-sm text-gray-600">
<strong>Feed:</strong> {{ $editingRoute->feed?->name }}
</p>
<p class="text-sm text-gray-600">
<strong>Channel:</strong> {{ $editingRoute->platformChannel?->display_name ?? $editingRoute->platformChannel?->name }}
</p>
</div>
<form wire:submit="updateRoute" class="space-y-4">
<div>
<label for="editPriority" class="block text-sm font-medium text-gray-700 mb-1">
Priority
</label>
<input
type="number"
id="editPriority"
wire:model="editPriority"
min="0"
class="w-full px-3 py-2 border border-gray-300 rounded-md focus:outline-none focus:ring-2 focus:ring-blue-500"
>
<p class="text-sm text-gray-500 mt-1">Higher priority routes are processed first</p>
</div>
<!-- Keyword Management -->
<div class="border-t pt-4">
<div class="flex items-center space-x-2 mb-3">
<svg class="h-4 w-4 text-gray-500" fill="none" viewBox="0 0 24 24" stroke-width="1.5" stroke="currentColor">
<path stroke-linecap="round" stroke-linejoin="round" d="M9.568 3H5.25A2.25 2.25 0 0 0 3 5.25v4.318c0 .597.237 1.17.659 1.591l9.581 9.581c.699.699 1.78.872 2.607.33a18.095 18.095 0 0 0 5.223-5.223c.542-.827.369-1.908-.33-2.607L11.16 3.66A2.25 2.25 0 0 0 9.568 3Z" />
<path stroke-linecap="round" stroke-linejoin="round" d="M6 6h.008v.008H6V6Z" />
</svg>
<span class="text-sm font-medium text-gray-700">Keywords</span>
<button
type="button"
wire:click="$set('showKeywordInput', true)"
class="inline-flex items-center p-1 text-gray-400 hover:text-gray-600 rounded"
title="Add keyword"
>
<svg class="h-4 w-4" fill="none" viewBox="0 0 24 24" stroke-width="1.5" stroke="currentColor">
<path stroke-linecap="round" stroke-linejoin="round" d="M12 4.5v15m7.5-7.5h-15" />
</svg>
</button>
</div>
@if ($showKeywordInput)
<div class="flex space-x-2 mb-3">
<input
type="text"
wire:model="newKeyword"
wire:keydown.enter.prevent="addKeyword"
placeholder="Enter keyword..."
class="flex-1 px-2 py-1 border border-gray-300 rounded text-sm focus:outline-none focus:ring-2 focus:ring-blue-500"
autofocus
>
<button
type="button"
wire:click="addKeyword"
class="px-3 py-1 bg-blue-600 text-white text-sm rounded hover:bg-blue-700"
>
Add
</button>
<button
type="button"
wire:click="$set('showKeywordInput', false)"
class="px-3 py-1 bg-gray-200 text-gray-700 text-sm rounded hover:bg-gray-300"
>
Cancel
</button>
</div>
@endif
@if ($editingKeywords->isNotEmpty())
<div class="space-y-2">
@foreach ($editingKeywords as $keyword)
<div class="flex items-center justify-between px-3 py-2 rounded border {{ $keyword->is_active ? 'border-blue-200 bg-blue-50' : 'border-gray-200 bg-gray-50' }}">
<div class="flex items-center space-x-2">
<span class="inline-flex items-center px-2 py-1 rounded-full text-xs font-medium {{ $keyword->is_active ? 'bg-blue-100 text-blue-800' : 'bg-gray-100 text-gray-500' }}">
{{ $keyword->keyword }}
</span>
<span class="text-xs text-gray-500">
{{ $keyword->is_active ? 'Active' : 'Inactive' }}
</span>
</div>
<div class="flex space-x-1">
<button
type="button"
wire:click="toggleKeyword({{ $keyword->id }})"
class="text-sm text-blue-600 hover:text-blue-800"
title="{{ $keyword->is_active ? 'Deactivate keyword' : 'Activate keyword' }}"
>
{{ $keyword->is_active ? 'Deactivate' : 'Activate' }}
</button>
<button
type="button"
wire:click="deleteKeyword({{ $keyword->id }})"
wire:confirm="Are you sure you want to delete this keyword?"
class="text-red-600 hover:text-red-800"
title="Delete keyword"
>
<svg class="h-4 w-4" fill="none" viewBox="0 0 24 24" stroke-width="1.5" stroke="currentColor">
<path stroke-linecap="round" stroke-linejoin="round" d="M6 18 18 6M6 6l12 12" />
</svg>
</button>
</div>
</div>
@endforeach
</div>
@else
@if (!$showKeywordInput)
<div class="text-sm text-gray-500 italic p-2 border border-gray-200 rounded">
No keywords defined. This route will match all articles.
</div>
@endif
@endif
</div>
<div class="flex justify-end space-x-3 pt-4 border-t">
<button
type="button"
wire:click="closeEditModal"
class="px-4 py-2 text-sm font-medium text-gray-700 bg-white border border-gray-300 rounded-md hover:bg-gray-50"
>
Cancel
</button>
<button
type="submit"
class="px-4 py-2 text-sm font-medium text-white bg-blue-600 border border-transparent rounded-md hover:bg-blue-700"
>
Update Route
</button>
</div>
</form>
</div>
</div>
</div>
@endif
</div>