- 機台日誌:對齊 Luxury UI 規範,實作整合式佈局與分頁組件。 - 多語系:完成機台日誌繁、英、日三語系翻譯與動態處理。 - UI 規範:更新 SKILL.md 定義「標準列表 Bible」。 - 後端:完善 TenantScoped 隔離邏輯,修復儀表板死循環與 User Model 缺失。 - IoT:擴展機台、會員 Model 並建立交易、商品、狀態等核心表結構。 - 基礎設施:設置台北時區與 Docker 環境變數同步。
129 lines
9.5 KiB
PHP
129 lines
9.5 KiB
PHP
@extends('layouts.admin')
|
|
|
|
@section('title', __('Machine Logs'))
|
|
|
|
@section('content')
|
|
<div class="space-y-10 pb-20">
|
|
<!-- Header -->
|
|
<div class="flex flex-col md:flex-row md:items-center md:justify-between gap-6">
|
|
<div>
|
|
<h1 class="text-3xl font-black text-slate-800 dark:text-white tracking-tight font-display">{{ __('Machine Logs') }}</h1>
|
|
<p class="text-sm font-bold text-slate-500 dark:text-slate-400 mt-1 uppercase tracking-widest">{{ __('Monitor events and system activity across your vending fleet.') }}</p>
|
|
</div>
|
|
</div>
|
|
|
|
<!-- Machine Logs Content (Integrated Card - Same as Roles) -->
|
|
<div class="luxury-card rounded-3xl p-8 animate-luxury-in">
|
|
<!-- Toolbar (Integrated Filters) -->
|
|
<div class="flex flex-col md:flex-row md:items-center justify-between gap-6 mb-10">
|
|
<form method="GET" action="{{ route('admin.machines.logs') }}" class="flex flex-wrap items-center gap-4 group">
|
|
<div class="space-y-1">
|
|
<label class="text-[10px] font-black text-slate-400 uppercase tracking-widest px-1">{{ __('Machine') }}</label>
|
|
<select name="machine_id" class="luxury-select text-xs h-9 py-0" onchange="this.form.submit()">
|
|
<option value="">{{ __('All Machines') }}</option>
|
|
@foreach($machines as $machine)
|
|
<option value="{{ $machine->id }}" {{ request('machine_id') == $machine->id ? 'selected' : '' }}>
|
|
{{ $machine->name }}
|
|
</option>
|
|
@endforeach
|
|
</select>
|
|
</div>
|
|
|
|
<div class="space-y-1">
|
|
<label class="text-[10px] font-black text-slate-400 uppercase tracking-widest px-1">{{ __('Level') }}</label>
|
|
<select name="level" class="luxury-select text-xs h-9 py-0" onchange="this.form.submit()">
|
|
<option value="">{{ __('All Levels') }}</option>
|
|
<option value="info" {{ request('level') == 'info' ? 'selected' : '' }}>{{ __('Info') }}</option>
|
|
<option value="warning" {{ request('level') == 'warning' ? 'selected' : '' }}>{{ __('Warning') }}</option>
|
|
<option value="error" {{ request('level') == 'error' ? 'selected' : '' }}>{{ __('Error') }}</option>
|
|
</select>
|
|
</div>
|
|
|
|
<div class="flex items-end gap-2 mt-5">
|
|
<button type="submit" class="p-2 rounded-xl bg-slate-50 dark:bg-slate-800 text-slate-400 hover:text-cyan-500 transition-colors border border-slate-200 dark:border-slate-700">
|
|
<svg class="h-4 w-4" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="3" stroke-linecap="round" stroke-linejoin="round"><circle cx="11" cy="11" r="8"/><path d="m21 21-4.3-4.3"/></svg>
|
|
</button>
|
|
<a href="{{ route('admin.machines.logs') }}" class="p-2 rounded-xl bg-slate-50 dark:bg-slate-800 text-slate-400 hover:text-rose-500 transition-colors border border-slate-200 dark:border-slate-700">
|
|
<svg class="w-4 h-4" xmlns="http://www.w3.org/2000/svg" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2.5" stroke-linecap="round" stroke-linejoin="round"><path d="M3 12a9 9 0 1 0 9-9 9.75 9.75 0 0 0-6.74 2.74L3 8"/><path d="M3 3v5h5"/></svg>
|
|
</a>
|
|
</div>
|
|
<input type="hidden" name="per_page" value="{{ request('per_page', 10) }}">
|
|
</form>
|
|
</div>
|
|
|
|
<div class="overflow-x-auto">
|
|
<table class="w-full text-left border-separate border-spacing-y-0">
|
|
<thead>
|
|
<tr class="bg-slate-50/50 dark:bg-slate-900/10">
|
|
<th class="px-6 py-4 text-[11px] font-black text-slate-400 dark:text-slate-500 uppercase tracking-[0.2em] border-b border-slate-100 dark:border-slate-800">{{ __('Timestamp') }}</th>
|
|
<th class="px-6 py-4 text-[11px] font-black text-slate-400 dark:text-slate-500 uppercase tracking-[0.2em] border-b border-slate-100 dark:border-slate-800">{{ __('Machine') }}</th>
|
|
<th class="px-6 py-4 text-[11px] font-black text-slate-400 dark:text-slate-500 uppercase tracking-[0.2em] border-b border-slate-100 dark:border-slate-800">{{ __('Level') }}</th>
|
|
<th class="px-6 py-4 text-[11px] font-black text-slate-400 dark:text-slate-500 uppercase tracking-[0.2em] border-b border-slate-100 dark:border-slate-800">{{ __('Message Content') }}</th>
|
|
</tr>
|
|
</thead>
|
|
<tbody class="divide-y divide-slate-50 dark:divide-slate-800/80">
|
|
@forelse ($logs as $log)
|
|
<tr class="group hover:bg-slate-50/80 dark:hover:bg-slate-800/40 transition-all duration-300">
|
|
<td class="px-6 py-6 transition-colors">
|
|
<div class="text-[13px] font-bold font-display tracking-widest text-slate-600 dark:text-slate-300">
|
|
{{ $log->created_at->format('Y-m-d') }}
|
|
</div>
|
|
<div class="text-[11px] font-bold text-slate-400 dark:text-slate-500 tracking-wider mt-0.5 uppercase">
|
|
{{ $log->created_at->format('H:i:s') }}
|
|
</div>
|
|
</td>
|
|
<td class="px-6 py-6 transition-colors">
|
|
<a href="{{ route('admin.machines.show', $log->machine_id) }}" class="inline-flex items-center gap-2 group/link">
|
|
<span class="text-base font-extrabold text-slate-800 dark:text-slate-100 group-hover/link:text-cyan-500 transition-colors">
|
|
{{ $log->machine->name ?? __('Unknown') }}
|
|
</span>
|
|
<svg class="w-3.5 h-3.5 text-slate-300 dark:text-slate-600 opacity-0 group-hover/link:opacity-100 transition-all -translate-x-2 group-hover/link:translate-x-0" fill="none" stroke="currentColor" viewBox="0 0 24 24" stroke-width="3"><path stroke-linecap="round" stroke-linejoin="round" d="M13.5 4.5L21 12m0 0l-7.5 7.5M21 12H3"/></svg>
|
|
</a>
|
|
</td>
|
|
<td class="px-6 py-6 transition-colors">
|
|
@php
|
|
$badgeStyles = [
|
|
'info' => 'bg-cyan-500/10 text-cyan-600 dark:text-cyan-400 border-cyan-500/20',
|
|
'warning' => 'bg-amber-500/10 text-amber-600 dark:text-amber-400 border-amber-500/20',
|
|
'error' => 'bg-rose-500/10 text-rose-600 dark:text-rose-400 border-rose-500/20',
|
|
];
|
|
$currentStyle = $badgeStyles[$log->level] ?? 'bg-slate-500/10 text-slate-600 border-slate-500/20';
|
|
@endphp
|
|
<span class="inline-flex items-center px-3 py-1 rounded-lg border text-[11px] font-black uppercase tracking-wider {{ $currentStyle }}">
|
|
<span class="w-1.5 h-1.5 rounded-full bg-current mr-2 animate-pulse"></span>
|
|
{{ __(ucfirst($log->level)) }}
|
|
</span>
|
|
</td>
|
|
<td class="px-6 py-6 transition-colors">
|
|
<p class="text-[14px] font-medium text-slate-700 dark:text-slate-200 leading-relaxed max-w-xl">
|
|
{{ $log->message }}
|
|
</p>
|
|
@if($log->context)
|
|
<div class="mt-3 p-4 rounded-xl bg-slate-50/50 dark:bg-[#0f172a]/50 border border-slate-100 dark:border-slate-800/50 group-hover:bg-white dark:group-hover:bg-[#0f172a] transition-colors">
|
|
<pre class="text-[10px] font-bold text-slate-400 dark:text-slate-500 whitespace-pre-wrap break-all">{{ json_encode($log->context, JSON_UNESCAPED_UNICODE | JSON_PRETTY_PRINT) }}</pre>
|
|
</div>
|
|
@endif
|
|
</td>
|
|
</tr>
|
|
@empty
|
|
<tr>
|
|
<td colspan="4" class="px-6 py-24 text-center">
|
|
<div class="flex flex-col items-center">
|
|
<div class="p-4 rounded-full bg-slate-50 dark:bg-slate-800/50 mb-4 border border-slate-100 dark:border-slate-800/50">
|
|
<svg class="w-8 h-8 text-slate-300 dark:text-slate-600" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="1.5"><path d="M21 7v10c0 1.1-.9 2-2 2H5c-1.1 0-2-.9-2-2V7c0-1.1.9-2 2-2h14c1.1 0 2 .9 2 2z"/><path d="M12 11l4-4"/><path d="M8 15l4-4"/></svg>
|
|
</div>
|
|
<span class="text-sm font-bold text-slate-400 dark:text-slate-500 uppercase tracking-widest">{{ __('No matching logs found') }}</span>
|
|
</div>
|
|
</td>
|
|
</tr>
|
|
@endforelse
|
|
</tbody>
|
|
</table>
|
|
</div>
|
|
|
|
<div class="mt-8 border-t border-slate-100 dark:border-slate-800 pt-6">
|
|
{{ $logs->links('vendor.pagination.luxury') }}
|
|
</div>
|
|
</div>
|
|
</div>
|
|
@endsection |