Files
star-cloud/resources/views/admin/machines/logs.blade.php
sky121113 3ce88ed342 [FEAT] 重構機台日誌 UI 與增加多語系支援,並整合 IoT API 核心架構
- 機台日誌:對齊 Luxury UI 規範,實作整合式佈局與分頁組件。
- 多語系:完成機台日誌繁、英、日三語系翻譯與動態處理。
- UI 規範:更新 SKILL.md 定義「標準列表 Bible」。
- 後端:完善 TenantScoped 隔離邏輯,修復儀表板死循環與 User Model 缺失。
- IoT:擴展機台、會員 Model 並建立交易、商品、狀態等核心表結構。
- 基礎設施:設置台北時區與 Docker 環境變數同步。
2026-03-16 17:29:15 +08:00

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