[FEAT] 新增機台系統日誌列表與極簡奢華風 UI
All checks were successful
star-cloud-deploy-demo / deploy-demo (push) Successful in 44s

This commit is contained in:
2026-03-09 13:09:50 +08:00
parent 682a9e7ac3
commit 75a70a256d
4 changed files with 183 additions and 6 deletions

View File

@@ -34,4 +34,56 @@ class MachineController extends AdminController
return view('admin.machines.show', compact('machine')); return view('admin.machines.show', compact('machine'));
} }
/**
* 顯示所有機台日誌列表
*/
public function logs(Request $request): View
{
$logs = \App\Models\Machine\MachineLog::with('machine')
->when($request->level, function ($query, $level) {
return $query->where('level', $level);
})
->when($request->machine_id, function ($query, $machineId) {
return $query->where('machine_id', $machineId);
})
->latest()
->paginate(20);
$machines = Machine::select('id', 'name')->get();
return view('admin.machines.logs', compact('logs', 'machines'));
}
/**
* 機台權限設定 (開發中)
*/
public function permissions(Request $request): View
{
return view('admin.machines.index', ['machines' => Machine::paginate(1)]); // Placeholder
}
/**
* 機台使用率統計 (開發中)
*/
public function utilization(Request $request): View
{
return view('admin.machines.index', ['machines' => Machine::paginate(1)]); // Placeholder
}
/**
* 機台到期管理 (開發中)
*/
public function expiry(Request $request): View
{
return view('admin.machines.index', ['machines' => Machine::paginate(1)]); // Placeholder
}
/**
* 機台維護紀錄 (開發中)
*/
public function maintenance(Request $request): View
{
return view('admin.machines.index', ['machines' => Machine::paginate(1)]); // Placeholder
}
} }

View File

@@ -0,0 +1,11 @@
<?php
namespace App\Http\Controllers\Api\V1;
use App\Http\Controllers\Controller;
use Illuminate\Http\Request;
class MemberController extends Controller
{
//
}

View File

@@ -0,0 +1,119 @@
@extends('layouts.admin')
@section('header')
<div class="flex justify-between items-center">
<h2 class="font-semibold text-xl text-gray-800 leading-tight">
{{ __('所有機台日誌') }}
</h2>
</div>
@endsection
@section('content')
<div class="py-12">
<div class="max-w-7xl mx-auto sm:px-6 lg:px-8 space-y-6">
<!-- 篩選器 -->
<div class="luxury-card rounded-2xl p-6 animate-luxury-in">
<div class="flex items-center gap-x-2 mb-4">
<p class="text-xs font-semibold uppercase tracking-wider text-slate-400">
條件篩選
</p>
</div>
<form method="GET" action="{{ route('admin.machines.logs') }}" class="flex flex-wrap gap-4 items-end">
<div>
<label class="block text-sm font-medium text-slate-700 dark:text-slate-300 mb-1.5">機台</label>
<select name="machine_id" class="block w-48 rounded-md border-slate-300 shadow-sm focus:border-cyan-500 focus:ring focus:ring-cyan-500/20 text-sm dark:bg-slate-800 dark:border-slate-700 dark:text-white dark:focus:border-cyan-500">
<option value="">全部機台</option>
@foreach($machines as $machine)
<option value="{{ $machine->id }}" {{ request('machine_id') == $machine->id ? 'selected' : '' }}>
{{ $machine->name }}
</option>
@endforeach
</select>
</div>
<div>
<label class="block text-sm font-medium text-slate-700 dark:text-slate-300 mb-1.5">層級</label>
<select name="level" class="block w-32 rounded-md border-slate-300 shadow-sm focus:border-cyan-500 focus:ring focus:ring-cyan-500/20 text-sm dark:bg-slate-800 dark:border-slate-700 dark:text-white dark:focus:border-cyan-500">
<option value="">全部層級</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-center gap-2">
<button type="submit" class="btn-luxury-primary">
<svg class="w-4 h-4" xmlns="http://www.w3.org/2000/svg" width="24" height="24" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round"><circle cx="11" cy="11" r="8"/><path d="m21 21-4.3-4.3"/></svg>
<span>篩選</span>
</button>
<a href="{{ route('admin.machines.logs') }}" class="btn-luxury-ghost">
重設
</a>
</div>
</form>
</div>
<!-- 日誌清單 -->
<div class="luxury-card rounded-2xl p-6 animate-luxury-in overflow-hidden" style="animation-delay: 100ms">
<div class="flex justify-between items-center mb-6">
<h2 class="text-lg font-bold text-slate-800 dark:text-white">系統日誌清單</h2>
<span class="text-xs text-slate-400">所有時間為系統時區</span>
</div>
<div class="overflow-x-auto rounded-xl border border-slate-200 dark:border-slate-700 bg-white dark:bg-[#0f172a]">
<table class="min-w-full divide-y divide-slate-200 dark:divide-slate-700/50 font-mono text-xs">
<thead class="bg-slate-50 dark:bg-slate-800/80">
<tr>
<th class="px-4 py-3 text-left font-semibold text-slate-600 dark:text-slate-300">時間</th>
<th class="px-4 py-3 text-left font-semibold text-slate-600 dark:text-slate-300">機台</th>
<th class="px-4 py-3 text-left font-semibold text-slate-600 dark:text-slate-300">層級</th>
<th class="px-4 py-3 text-left font-semibold text-slate-600 dark:text-slate-300">訊息</th>
</tr>
</thead>
<tbody class="divide-y divide-slate-200 dark:divide-slate-700/50 bg-white dark:bg-transparent">
@forelse ($logs as $log)
<tr class="hover:bg-slate-50 dark:hover:bg-slate-800/50 transition-colors">
<td class="px-4 py-3 text-slate-500 dark:text-slate-400 whitespace-nowrap">{{ $log->created_at->format('Y-m-d H:i:s') }}</td>
<td class="px-4 py-3 text-slate-700 dark:text-slate-300 whitespace-nowrap">
<a href="{{ route('admin.machines.show', $log->machine_id) }}" class="hover:text-cyan-600 dark:hover:text-cyan-400 underline decoration-slate-300 dark:decoration-slate-600 underline-offset-2 transition-colors">
{{ $log->machine->name ?? '未知機台' }}
</a>
</td>
<td class="px-4 py-3 whitespace-nowrap">
@php
$levelClasses = [
'info' => 'text-cyan-600 dark:text-cyan-400 bg-cyan-50 dark:bg-cyan-500/20 border-cyan-200 dark:border-cyan-500/30',
'warning' => 'text-amber-600 dark:text-amber-400 bg-amber-50 dark:bg-amber-500/20 border-amber-200 dark:border-amber-500/30 font-semibold',
'error' => 'text-rose-600 dark:text-rose-400 bg-rose-50 dark:bg-rose-500/20 border-rose-200 dark:border-rose-500/30 font-bold',
];
@endphp
<span class="px-2 py-0.5 rounded border {{ $levelClasses[$log->level] ?? 'text-slate-500 bg-slate-100 border-slate-200 dark:text-slate-300 dark:bg-slate-800 dark:border-slate-700' }}">
{{ strtoupper($log->level) }}
</span>
</td>
<td class="px-4 py-3 text-slate-700 dark:text-slate-200 max-w-xl break-words">
{{ $log->message }}
@if($log->context)
<div class="text-[10px] text-slate-500 dark:text-slate-400 mt-2 max-h-24 overflow-y-auto bg-slate-100 dark:bg-[#0f172a] p-2 rounded-lg border border-slate-200 dark:border-slate-800/50 shadow-inner">
{{ json_encode($log->context, JSON_UNESCAPED_UNICODE) }}
</div>
@endif
</td>
</tr>
@empty
<tr>
<td colspan="4" class="px-4 py-12 text-center text-slate-500 dark:text-slate-400 italic">暫無相關日誌</td>
</tr>
@endforelse
</tbody>
</table>
</div>
@if($logs->hasPages())
<div class="mt-6">
{{ $logs->appends(request()->query())->links() }}
</div>
@endif
</div>
</div>
</div>
@endsection

View File

@@ -78,13 +78,8 @@
<!-- Form Group --> <!-- Form Group -->
<div> <div>
<div class="flex justify-between items-center"> <div class="flex items-center">
<label for="password" class="block text-sm mb-2 dark:text-white">密碼</label> <label for="password" class="block text-sm mb-2 dark:text-white">密碼</label>
@if (Route::has('password.request'))
<a class="text-sm text-blue-600 decoration-2 hover:underline font-medium dark:text-blue-500" href="{{ route('password.request') }}">
忘記密碼?
</a>
@endif
</div> </div>
<div class="relative"> <div class="relative">
<input type="password" id="password" name="password" class="py-3 px-4 block w-full border-gray-200 rounded-lg text-sm focus:border-blue-500 focus:ring-blue-500 disabled:opacity-50 disabled:pointer-events-none dark:bg-slate-900 dark:border-slate-700 dark:text-gray-400 dark:focus:ring-gray-600" required autocomplete="current-password"> <input type="password" id="password" name="password" class="py-3 px-4 block w-full border-gray-200 rounded-lg text-sm focus:border-blue-500 focus:ring-blue-500 disabled:opacity-50 disabled:pointer-events-none dark:bg-slate-900 dark:border-slate-700 dark:text-gray-400 dark:focus:ring-gray-600" required autocomplete="current-password">