[FEAT] 實作機台智慧連線監控與儀表板告警同步優化
1. [Machine Model] 實作智慧抖動偵測邏輯 (Jitter Detection),判定 1 小時內斷線 3 次以上為不穩定機台。
2. [Machine Model] 優化 calculated_status,機台在線時自動忽略歷史斷線警告,並修正 scopeHasAnyAlert 排除連線類告警。
3. [ProcessStatus Job] 實作自動消警機制 (Auto-resolve),機台恢復連線時自動處理舊的斷線告警。
4. [Dashboard] 同步儀表板「待處理告警」統計,確保與機台列表狀態一致。
5. [UI] 於機台列表與儀表板同步新增琥珀色閃電圖示 (⚡) 與不穩定提示 Tooltip。
6. [i18n] 新增連線不穩定相關之繁體中文翻譯詞條。
This commit is contained in:
parent
61b5e422ee
commit
3d1ddd5b91
@ -20,7 +20,7 @@ class DashboardController extends Controller
|
||||
// 從資料庫獲取真實統計數據
|
||||
$activeMachines = Machine::online()->count();
|
||||
$offlineMachines = Machine::offline()->count();
|
||||
$alertsPending = Machine::hasError()->count();
|
||||
$alertsPending = Machine::hasAnyAlert()->count();
|
||||
$memberCount = \App\Models\Member\Member::count();
|
||||
|
||||
// 交易營收統計 (真實數據)
|
||||
|
||||
@ -52,6 +52,14 @@ class ProcessStatus implements ShouldQueue
|
||||
$updateData = ['status' => $status];
|
||||
if ($status === 'online') {
|
||||
$updateData['last_heartbeat_at'] = now();
|
||||
|
||||
// 自動消警:當機台恢復連線時,自動標記之前的「斷線 (LWT)」警告為已解決
|
||||
$machine->logs()
|
||||
->where('type', 'status')
|
||||
->where('level', 'warning')
|
||||
->where('message', 'Connection lost (LWT)')
|
||||
->where('is_resolved', false)
|
||||
->update(['is_resolved' => true]);
|
||||
}
|
||||
|
||||
$machine->update($updateData);
|
||||
|
||||
@ -92,10 +92,10 @@ class Machine extends Model
|
||||
'cash_module_enabled',
|
||||
];
|
||||
|
||||
protected $appends = ['image_urls', 'calculated_status'];
|
||||
protected $appends = ['image_urls', 'calculated_status', 'hardware_status', 'latest_status_log_time', 'latest_hardware_log_time', 'is_unstable', 'recent_disconnection_count'];
|
||||
|
||||
/**
|
||||
* 動態計算機台當前狀態
|
||||
* 動態計算機台當前狀態 (僅限連線與系統層級)
|
||||
* 優先讀取資料庫 status 欄位
|
||||
* 若在線,則進一步檢查過去 15 分鐘是否為異常
|
||||
*/
|
||||
@ -106,29 +106,92 @@ class Machine extends Model
|
||||
return 'offline';
|
||||
}
|
||||
|
||||
// 判定異常 (檢查過去 15 分鐘內是否有 未解決的 error 日誌)
|
||||
// 判定異常 (僅檢查 type = 'status' 的未解決日誌)
|
||||
$hasRecentErrors = $this->logs()
|
||||
->where('type', 'status')
|
||||
->where('level', 'error')
|
||||
->where('is_resolved', false)
|
||||
->where('created_at', '>=', now()->subMinutes(15))
|
||||
->exists();
|
||||
|
||||
if ($hasRecentErrors) {
|
||||
return 'error';
|
||||
}
|
||||
|
||||
// 判定警告 (檢查過去 15 分鐘內是否有 未解決的 warning 日誌)
|
||||
// 判定警告 (僅檢查 type = 'status' 的未解決日誌)
|
||||
// 優化:當機台在線時,自動忽略「斷線 (LWT)」警告,因為狀態已恢復
|
||||
$hasRecentWarnings = $this->logs()
|
||||
->where('type', 'status')
|
||||
->where('level', 'warning')
|
||||
->where('is_resolved', false)
|
||||
->where('created_at', '>=', now()->subMinutes(15))
|
||||
->where('message', '!=', 'Connection lost (LWT)')
|
||||
->exists();
|
||||
|
||||
if ($hasRecentWarnings) {
|
||||
return 'warning';
|
||||
}
|
||||
|
||||
return 'online';
|
||||
return $this->status ?? 'online';
|
||||
}
|
||||
|
||||
/**
|
||||
* 下位機/硬體健康狀態 (基於 type = 'submachine' 日誌)
|
||||
*/
|
||||
public function getHardwareStatusAttribute(): string
|
||||
{
|
||||
// 判定異常
|
||||
$hasErrors = $this->logs()
|
||||
->where('type', 'submachine')
|
||||
->where('level', 'error')
|
||||
->where('is_resolved', false)
|
||||
->exists();
|
||||
|
||||
if ($hasErrors) {
|
||||
return 'error';
|
||||
}
|
||||
|
||||
// 判定警告
|
||||
$hasWarnings = $this->logs()
|
||||
->where('type', 'submachine')
|
||||
->where('level', 'warning')
|
||||
->where('is_resolved', false)
|
||||
->exists();
|
||||
|
||||
if ($hasWarnings) {
|
||||
return 'warning';
|
||||
}
|
||||
|
||||
return 'normal';
|
||||
}
|
||||
|
||||
/**
|
||||
* 取得最新一筆未解決的系統狀態日誌時間
|
||||
*/
|
||||
public function getLatestStatusLogTimeAttribute()
|
||||
{
|
||||
$query = $this->logs()
|
||||
->where('type', 'status')
|
||||
->whereIn('level', ['error', 'warning'])
|
||||
->where('is_resolved', false);
|
||||
|
||||
// 優化:若在線,不顯示已恢復的斷線時間
|
||||
if ($this->status === 'online') {
|
||||
$query->where('message', '!=', 'Connection lost (LWT)');
|
||||
}
|
||||
|
||||
return $query->latest()->first()?->created_at;
|
||||
}
|
||||
|
||||
/**
|
||||
* 取得最新一筆未解決的下位機硬體日誌時間
|
||||
*/
|
||||
public function getLatestHardwareLogTimeAttribute()
|
||||
{
|
||||
return $this->logs()
|
||||
->where('type', 'submachine')
|
||||
->whereIn('level', ['error', 'warning'])
|
||||
->where('is_resolved', false)
|
||||
->latest()
|
||||
->first()?->created_at;
|
||||
}
|
||||
|
||||
/**
|
||||
@ -147,33 +210,57 @@ class Machine extends Model
|
||||
return $query->where('status', 'offline');
|
||||
}
|
||||
|
||||
/**
|
||||
* Scope: 判定異常 (過去 15 分鐘內有錯誤或警告日誌)
|
||||
*/
|
||||
public function scopeHasError($query)
|
||||
{
|
||||
return $query->whereExists(function ($q) {
|
||||
$q->select(\Illuminate\Support\Facades\DB::raw(1))
|
||||
->from('machine_logs')
|
||||
->whereColumn('machine_logs.machine_id', 'machines.id')
|
||||
->whereIn('level', ['error', 'warning'])
|
||||
->where('is_resolved', false)
|
||||
->where('created_at', '>=', now()->subMinutes(15));
|
||||
return $query->whereHas('logs', function ($q) {
|
||||
$q->where('is_resolved', false)
|
||||
->where('level', 'error');
|
||||
});
|
||||
}
|
||||
|
||||
/**
|
||||
* Scope: 判定運行中 (在線且無近期異常)
|
||||
* Scope: 判定是否有任何待處理的異常 (排除掉單純的連線中斷)
|
||||
* 待處理告警應專注於「功能性異常」(如硬體錯誤、APP 報錯),而非連線狀態。
|
||||
*/
|
||||
public function scopeHasAnyAlert($query)
|
||||
{
|
||||
return $query->whereHas('logs', function ($q) {
|
||||
$q->where(function($sub) {
|
||||
$sub->where('is_resolved', false)
|
||||
->orWhereNull('is_resolved');
|
||||
})
|
||||
->whereIn('level', ['error', 'warning'])
|
||||
->where('message', '!=', 'Connection lost (LWT)');
|
||||
});
|
||||
}
|
||||
|
||||
public function scopeHasStatusError($query)
|
||||
{
|
||||
return $query->whereHas('logs', function ($q) {
|
||||
$q->where('is_resolved', false)
|
||||
->where('type', 'status')
|
||||
->where('level', 'error');
|
||||
});
|
||||
}
|
||||
|
||||
public function scopeHasHardwareError($query)
|
||||
{
|
||||
return $query->whereHas('logs', function ($q) {
|
||||
$q->where('is_resolved', false)
|
||||
->where('type', 'submachine')
|
||||
->where('level', 'error');
|
||||
});
|
||||
}
|
||||
|
||||
/**
|
||||
* Scope: 判定運行中 (在線且無近期系統異常)
|
||||
*/
|
||||
public function scopeRunning($query)
|
||||
{
|
||||
return $query->online()->whereNotExists(function ($q) {
|
||||
$q->select(\Illuminate\Support\Facades\DB::raw(1))
|
||||
->from('machine_logs')
|
||||
->whereColumn('machine_logs.machine_id', 'machines.id')
|
||||
->whereIn('level', ['error', 'warning'])
|
||||
->where('is_resolved', false)
|
||||
->where('created_at', '>=', now()->subMinutes(15));
|
||||
return $query->online()->whereDoesntHave('logs', function ($q) {
|
||||
$q->where('is_resolved', false)
|
||||
->where('type', 'status')
|
||||
->whereIn('level', ['error', 'warning']);
|
||||
});
|
||||
}
|
||||
|
||||
@ -283,6 +370,21 @@ class Machine extends Model
|
||||
return __($label);
|
||||
}
|
||||
|
||||
public function getIsUnstableAttribute(): bool
|
||||
{
|
||||
// 抖動偵測:1 小時內斷線次數 >= 3
|
||||
return $this->recent_disconnection_count >= 3;
|
||||
}
|
||||
|
||||
public function getRecentDisconnectionCountAttribute(): int
|
||||
{
|
||||
return $this->logs()
|
||||
->where('type', 'status')
|
||||
->where('message', 'Connection lost (LWT)')
|
||||
->where('created_at', '>=', now()->subHour())
|
||||
->count();
|
||||
}
|
||||
|
||||
public function users()
|
||||
{
|
||||
return $this->belongsToMany(\App\Models\System\User::class);
|
||||
|
||||
@ -303,6 +303,7 @@
|
||||
"Connected": "通訊中",
|
||||
"Connecting...": "連線中",
|
||||
"Connection error": "連線錯誤",
|
||||
"Connection is unstable (disconnected :count times in the last hour)": "連線不穩定(過去 1 小時內中斷 :count 次)",
|
||||
"Connection lost (LWT)": "連線中斷",
|
||||
"Connection restored": "連線恢復",
|
||||
"Connectivity Status": "連線狀態概況",
|
||||
@ -775,7 +776,6 @@
|
||||
"Loading Data": "載入資料中",
|
||||
"Loading failed": "載入失敗",
|
||||
"Loading machines...": "正在載入機台...",
|
||||
"Loading...": "載入中...",
|
||||
"Location": "位置",
|
||||
"Location found!": "已成功獲取座標!",
|
||||
"Location not found": "找不到位置,您可以嘗試使用地標名稱(例如:花蓮縣政府)",
|
||||
@ -1043,6 +1043,7 @@
|
||||
"Normal": "正常",
|
||||
"Not Used": "不使用",
|
||||
"Not Used Description": "不使用第三方支付介接",
|
||||
"Normal": "正常",
|
||||
"Note": "備註",
|
||||
"Note (optional)": "備註 (選填)",
|
||||
"Notes": "備註",
|
||||
@ -1580,6 +1581,7 @@
|
||||
"Sub Account Roles": "子帳號角色",
|
||||
"Sub Accounts": "子帳號",
|
||||
"Sub-actions": "子項目",
|
||||
"Sub-machine Status": "下位機狀態",
|
||||
"Sub-machine Status Request": "下位機狀態回傳",
|
||||
"Submit Record": "提交紀錄",
|
||||
"Subtotal": "小計",
|
||||
@ -1806,7 +1808,7 @@
|
||||
"Warehouse to warehouse transfers": "倉庫對倉庫調撥",
|
||||
"Warehouse updated successfully": "倉庫更新成功",
|
||||
"Warehouse updated successfully.": "倉庫已成功更新。",
|
||||
"Warning": "即將過期",
|
||||
"Warning": "警告",
|
||||
"Warning: You are editing your own role!": "警告:您正在編輯目前使用的角色!",
|
||||
"Warranty": "保固",
|
||||
"Warranty End": "保固結束",
|
||||
|
||||
@ -193,6 +193,7 @@
|
||||
<tr class="bg-slate-50/50 dark:bg-slate-900/30">
|
||||
<th class="px-6 py-4 text-[12px] font-black text-slate-400 dark:text-slate-500 uppercase tracking-widest border-b border-slate-100 dark:border-slate-800">{{ __('Machine Info') }}</th>
|
||||
<th class="px-6 py-4 text-[12px] font-black text-slate-400 dark:text-slate-500 uppercase tracking-widest border-b border-slate-100 dark:border-slate-800 text-center">{{ __('Running Status') }}</th>
|
||||
<th class="px-6 py-4 text-[12px] font-black text-slate-400 dark:text-slate-500 uppercase tracking-widest border-b border-slate-100 dark:border-slate-800 text-center">{{ __('Sub-machine Status') }}</th>
|
||||
<th class="px-6 py-4 text-[12px] font-black text-slate-400 dark:text-slate-500 uppercase tracking-widest border-b border-slate-100 dark:border-slate-800 text-center">{{ __('Today Cumulative Sales') }}</th>
|
||||
<th class="px-6 py-4 text-[12px] font-black text-slate-400 dark:text-slate-500 uppercase tracking-widest border-b border-slate-100 dark:border-slate-800 text-center">{{ __('Current Stock') }}</th>
|
||||
<th class="px-6 py-4 text-[12px] font-black text-slate-400 dark:text-slate-500 uppercase tracking-widest border-b border-slate-100 dark:border-slate-800 text-center">{{ __('Last Signal') }}</th>
|
||||
@ -217,25 +218,63 @@
|
||||
</td>
|
||||
<td class="px-6 py-6 text-center">
|
||||
@php $cStatus = $machine->calculated_status; @endphp
|
||||
@if($cStatus === 'online')
|
||||
<div class="flex items-center justify-center gap-1.5 px-3 py-1.5 rounded-full bg-emerald-500/10 border border-emerald-500/20">
|
||||
<div class="relative flex h-2 w-2">
|
||||
<span class="animate-ping absolute inline-flex h-full w-full rounded-full bg-emerald-400 opacity-75"></span>
|
||||
<span class="relative inline-flex rounded-full h-2 w-2 bg-emerald-500"></span>
|
||||
<div class="flex flex-col items-center gap-0.5">
|
||||
@if($cStatus === 'online')
|
||||
<div class="flex items-center justify-center gap-1.5 px-3 py-1.5 rounded-full bg-emerald-500/10 border border-emerald-500/20 tooltip"
|
||||
title="{{ $machine->is_unstable ? __('Connection is unstable (disconnected :count times in the last hour)', ['count' => $machine->recent_disconnection_count]) : __('Machine connection is normal') }}">
|
||||
<div class="relative flex h-2 w-2">
|
||||
<span class="animate-ping absolute inline-flex h-full w-full rounded-full bg-emerald-400 opacity-75"></span>
|
||||
<span class="relative inline-flex rounded-full h-2 w-2 bg-emerald-500"></span>
|
||||
</div>
|
||||
<span class="text-[10px] font-black text-emerald-600 dark:text-emerald-400 tracking-widest uppercase">{{ __('Online') }}</span>
|
||||
@if($machine->is_unstable)
|
||||
<div class="ml-1 text-amber-500 animate-pulse">
|
||||
<svg class="w-3 h-3" fill="currentColor" viewBox="0 0 24 24">
|
||||
<path d="M13 10V3L4 14h7v7l9-11h-7z" />
|
||||
</svg>
|
||||
</div>
|
||||
@endif
|
||||
</div>
|
||||
<span class="text-[10px] font-black text-emerald-600 dark:text-emerald-400 tracking-widest uppercase">{{ __('Online') }}</span>
|
||||
@elseif($cStatus === 'warning')
|
||||
<div class="flex items-center justify-center gap-1.5 px-3 py-1.5 rounded-full bg-amber-500/10 border border-amber-500/20">
|
||||
<div class="h-2 w-2 rounded-full bg-amber-500 animate-pulse"></div>
|
||||
<span class="text-[10px] font-black text-amber-600 dark:text-amber-400 tracking-widest uppercase">{{ __('Warning') }}</span>
|
||||
</div>
|
||||
@elseif($cStatus === 'error')
|
||||
<div class="flex items-center justify-center gap-1.5 px-3 py-1.5 rounded-full bg-rose-500/10 border border-rose-500/20">
|
||||
<div class="h-2 w-2 rounded-full bg-rose-500 animate-pulse"></div>
|
||||
<span class="text-[10px] font-black text-rose-600 dark:text-rose-400 tracking-widest uppercase">{{ __('Error') }}</span>
|
||||
</div>
|
||||
@else
|
||||
<div class="flex items-center justify-center gap-1.5 px-3 py-1.5 rounded-full bg-slate-500/10 border border-slate-500/20">
|
||||
<div class="h-2 w-2 rounded-full bg-slate-400"></div>
|
||||
<span class="text-[10px] font-black text-slate-600 dark:text-slate-400 tracking-widest uppercase">{{ __('Offline') }}</span>
|
||||
</div>
|
||||
@endif
|
||||
@if($machine->latest_status_log_time && in_array($cStatus, ['error', 'warning']))
|
||||
<span class="text-[9px] font-bold text-slate-400">{{ $machine->latest_status_log_time->diffForHumans() }}</span>
|
||||
@endif
|
||||
</div>
|
||||
@elseif($cStatus === 'error')
|
||||
<div class="flex items-center justify-center gap-1.5 px-3 py-1.5 rounded-full bg-rose-500/10 border border-rose-500/20">
|
||||
<div class="h-2 w-2 rounded-full bg-rose-500 animate-pulse"></div>
|
||||
<span class="text-[10px] font-black text-rose-600 dark:text-rose-400 tracking-widest uppercase">{{ __('Error') }}</span>
|
||||
</td>
|
||||
<td class="px-6 py-6 text-center">
|
||||
@php $hStatus = $machine->hardware_status; @endphp
|
||||
<div class="flex flex-col items-center gap-0.5">
|
||||
<div class="flex items-center justify-center gap-1.5">
|
||||
<span class="w-1.5 h-1.5 rounded-full {{
|
||||
$hStatus === 'error' ? 'bg-rose-500 animate-pulse' :
|
||||
($hStatus === 'warning' ? 'bg-amber-500 animate-pulse' : 'bg-emerald-500/50')
|
||||
}}"></span>
|
||||
<span class="text-[10px] font-black {{
|
||||
$hStatus === 'error' ? 'text-rose-500' :
|
||||
($hStatus === 'warning' ? 'text-amber-500' : 'text-slate-400')
|
||||
}} uppercase tracking-widest">
|
||||
{{ $hStatus === 'error' ? __('Error') : ($hStatus === 'warning' ? __('Warning') : __('Normal')) }}
|
||||
</span>
|
||||
</div>
|
||||
@if($machine->latest_hardware_log_time && $hStatus !== 'normal')
|
||||
<span class="text-[9px] font-bold text-slate-400">{{ $machine->latest_hardware_log_time->diffForHumans() }}</span>
|
||||
@endif
|
||||
</div>
|
||||
@else
|
||||
<div class="flex items-center justify-center gap-1.5 px-3 py-1.5 rounded-full bg-slate-500/10 border border-slate-500/20">
|
||||
<div class="h-2 w-2 rounded-full bg-slate-400"></div>
|
||||
<span class="text-[10px] font-black text-slate-600 dark:text-slate-400 tracking-widest uppercase">{{ __('Offline') }}</span>
|
||||
</div>
|
||||
@endif
|
||||
</td>
|
||||
<td class="px-6 py-6 text-center">
|
||||
<span class="text-base font-extrabold text-slate-900 dark:text-slate-100">$ {{ number_format($machine->today_sales_sum ?? 0, 0) }}</span>
|
||||
@ -309,6 +348,13 @@
|
||||
@php $cStatus = $machine->calculated_status; @endphp
|
||||
<div class="flex items-center">
|
||||
@if($cStatus === 'online')
|
||||
@if($machine->is_unstable)
|
||||
<div class="text-amber-500 animate-pulse mr-1">
|
||||
<svg class="w-3.5 h-3.5" fill="currentColor" viewBox="0 0 24 24">
|
||||
<path d="M13 10V3L4 14h7v7l9-11h-7z" />
|
||||
</svg>
|
||||
</div>
|
||||
@endif
|
||||
<div class="flex h-2.5 w-2.5 relative">
|
||||
<span class="animate-ping absolute inline-flex h-full w-full rounded-full bg-emerald-400 opacity-75"></span>
|
||||
<span class="relative inline-flex rounded-full h-2.5 w-2.5 bg-emerald-500"></span>
|
||||
@ -329,15 +375,40 @@
|
||||
</div>
|
||||
<div>
|
||||
<p class="text-[10px] font-black text-slate-400 dark:text-slate-500 uppercase tracking-widest mb-1">{{ __('Status') }}</p>
|
||||
<p class="text-sm font-bold text-slate-700 dark:text-slate-300 capitalize">
|
||||
@if($cStatus === 'online')
|
||||
<span class="text-emerald-500">{{ __('Online') }}</span>
|
||||
@elseif($cStatus === 'error')
|
||||
<span class="text-rose-500">{{ __('Error') }}</span>
|
||||
@else
|
||||
<span class="text-slate-400">{{ __('Offline') }}</span>
|
||||
<div class="flex flex-col">
|
||||
<p class="text-sm font-bold text-slate-700 dark:text-slate-300 capitalize">
|
||||
@if($cStatus === 'online')
|
||||
<span class="text-emerald-500">{{ __('Online') }}</span>
|
||||
@elseif($cStatus === 'warning')
|
||||
<span class="text-amber-500">{{ __('Warning') }}</span>
|
||||
@elseif($cStatus === 'error')
|
||||
<span class="text-rose-500">{{ __('Error') }}</span>
|
||||
@else
|
||||
<span class="text-slate-400">{{ __('Offline') }}</span>
|
||||
@endif
|
||||
</p>
|
||||
@if($machine->latest_status_log_time && in_array($cStatus, ['error', 'warning']))
|
||||
<span class="text-[9px] font-bold text-slate-400">{{ $machine->latest_status_log_time->diffForHumans() }}</span>
|
||||
@endif
|
||||
</p>
|
||||
</div>
|
||||
</div>
|
||||
<div>
|
||||
<p class="text-[10px] font-black text-slate-400 dark:text-slate-500 uppercase tracking-widest mb-1">{{ __('Sub-machine Status') }}</p>
|
||||
@php $hStatus = $machine->hardware_status; @endphp
|
||||
<div class="flex flex-col">
|
||||
<p class="text-sm font-bold text-slate-700 dark:text-slate-300 capitalize">
|
||||
@if($hStatus === 'error')
|
||||
<span class="text-rose-500">{{ __('Error') }}</span>
|
||||
@elseif($hStatus === 'warning')
|
||||
<span class="text-amber-500">{{ __('Warning') }}</span>
|
||||
@else
|
||||
<span class="text-emerald-500">{{ __('Normal') }}</span>
|
||||
@endif
|
||||
</p>
|
||||
@if($machine->latest_hardware_log_time && $hStatus !== 'normal')
|
||||
<span class="text-[9px] font-bold text-slate-400">{{ $machine->latest_hardware_log_time->diffForHumans() }}</span>
|
||||
@endif
|
||||
</div>
|
||||
</div>
|
||||
<div class="col-span-2">
|
||||
<p class="text-[10px] font-black text-slate-400 dark:text-slate-500 uppercase tracking-widest mb-1">{{ __('Last Signal') }}</p>
|
||||
|
||||
@ -230,9 +230,6 @@
|
||||
<th
|
||||
class="px-6 py-4 text-xs font-bold text-slate-500 dark:text-slate-400 uppercase tracking-[0.15em] border-b border-slate-100 dark:border-slate-800 text-center">
|
||||
{{ __('Sub / Card / Scan') }}</th>
|
||||
<th
|
||||
class="px-6 py-4 text-xs font-bold text-slate-500 dark:text-slate-400 uppercase tracking-[0.15em] border-b border-slate-100 dark:border-slate-800 text-center">
|
||||
{{ __('Last Page') }}</th>
|
||||
<th
|
||||
class="px-6 py-4 text-xs font-bold text-slate-500 dark:text-slate-400 uppercase tracking-[0.15em] border-b border-slate-100 dark:border-slate-800 text-center">
|
||||
{{ __('APP Version') }}</th>
|
||||
@ -284,7 +281,7 @@
|
||||
|
||||
@if($cStatus === 'online')
|
||||
<div class="flex items-center justify-center gap-1.5 px-3 py-1.5 rounded-full bg-emerald-500/10 border border-emerald-500/20 tooltip mx-auto w-fit"
|
||||
title="{{ __('Machine is heartbeat normal') }}">
|
||||
title="{{ $machine->is_unstable ? __('Connection is unstable (disconnected :count times in the last hour)', ['count' => $machine->recent_disconnection_count]) : __('Machine connection is normal') }}">
|
||||
<div class="relative flex h-2 w-2">
|
||||
<span
|
||||
class="animate-ping absolute inline-flex h-full w-full rounded-full bg-emerald-400 opacity-75"></span>
|
||||
@ -293,22 +290,40 @@
|
||||
<span
|
||||
class="text-xs font-black text-emerald-600 dark:text-emerald-400 tracking-widest uppercase">{{
|
||||
__('Online') }}</span>
|
||||
|
||||
@if($machine->is_unstable)
|
||||
<div class="ml-1 text-amber-500 animate-pulse">
|
||||
<svg class="w-3.5 h-3.5" fill="currentColor" viewBox="0 0 24 24">
|
||||
<path d="M13 10V3L4 14h7v7l9-11h-7z" />
|
||||
</svg>
|
||||
</div>
|
||||
@endif
|
||||
</div>
|
||||
@elseif($cStatus === 'warning')
|
||||
<div class="flex items-center justify-center gap-1.5 px-3 py-1.5 rounded-full bg-amber-500/10 border border-amber-500/20 tooltip mx-auto w-fit"
|
||||
title="{{ __('Recently reported warnings in logs') }}">
|
||||
<div class="h-2 w-2 rounded-full bg-amber-500 animate-pulse"></div>
|
||||
<span
|
||||
class="text-xs font-black text-amber-600 dark:text-amber-400 tracking-widest uppercase">{{
|
||||
__('Warning') }}</span>
|
||||
<div class="flex flex-col items-center gap-0.5 px-3 py-1.5 rounded-full bg-amber-500/10 border border-amber-500/20 tooltip mx-auto w-fit"
|
||||
title="{{ __('Connection warnings') }}">
|
||||
<div class="flex items-center gap-1.5">
|
||||
<div class="h-2 w-2 rounded-full bg-amber-500 animate-pulse"></div>
|
||||
<span
|
||||
class="text-xs font-black text-amber-600 dark:text-amber-400 tracking-widest uppercase">{{
|
||||
__('Warning') }}</span>
|
||||
</div>
|
||||
@if($machine->latest_status_log_time)
|
||||
<span class="text-[9px] font-bold text-amber-500/80 block leading-none">{{ $machine->latest_status_log_time->diffForHumans() }}</span>
|
||||
@endif
|
||||
</div>
|
||||
@elseif($cStatus === 'error')
|
||||
<div class="flex items-center justify-center gap-1.5 px-3 py-1.5 rounded-full bg-rose-500/10 border border-rose-500/20 tooltip mx-auto w-fit"
|
||||
title="{{ __('Recently reported errors or warnings in logs') }}">
|
||||
<div class="h-2 w-2 rounded-full bg-rose-500 animate-pulse"></div>
|
||||
<span
|
||||
class="text-xs font-black text-rose-600 dark:text-rose-400 tracking-widest uppercase">{{
|
||||
__('Error') }}</span>
|
||||
<div class="flex flex-col items-center gap-0.5 px-3 py-1.5 rounded-full bg-rose-500/10 border border-rose-500/20 tooltip mx-auto w-fit"
|
||||
title="{{ __('Connection or system errors') }}">
|
||||
<div class="flex items-center gap-1.5">
|
||||
<div class="h-2 w-2 rounded-full bg-rose-500 animate-pulse"></div>
|
||||
<span
|
||||
class="text-xs font-black text-rose-600 dark:text-rose-400 tracking-widest uppercase">{{
|
||||
__('Error') }}</span>
|
||||
</div>
|
||||
@if($machine->latest_status_log_time)
|
||||
<span class="text-[9px] font-bold text-rose-500/80 block leading-none">{{ $machine->latest_status_log_time->diffForHumans() }}</span>
|
||||
@endif
|
||||
</div>
|
||||
@else
|
||||
<div class="flex items-center justify-center gap-1.5 px-3 py-1.5 rounded-full bg-slate-500/10 border border-slate-500/20 tooltip mx-auto w-fit"
|
||||
@ -325,27 +340,40 @@
|
||||
$machine->temperature ?? '--' }}°C</span>
|
||||
</td>
|
||||
<td class="px-6 py-6 text-center">
|
||||
@php
|
||||
$hStatus = $machine->hardware_status;
|
||||
@endphp
|
||||
<div class="flex items-center justify-center gap-4">
|
||||
<div class="flex items-center gap-1.5">
|
||||
{{-- 下位機 --}}
|
||||
<div class="flex items-center gap-1.5 tooltip" title="{{ __('Sub-machine Status') }}">
|
||||
<span class="w-1.5 h-1.5 rounded-full {{
|
||||
$hStatus === 'error' ? 'bg-rose-500 animate-pulse' :
|
||||
($hStatus === 'warning' ? 'bg-amber-500 animate-pulse' : 'bg-emerald-500/50')
|
||||
}}"></span>
|
||||
<span class="text-xs font-black {{
|
||||
$hStatus === 'error' ? 'text-rose-500' :
|
||||
($hStatus === 'warning' ? 'text-amber-500' : 'text-slate-400')
|
||||
}} uppercase tracking-widest">
|
||||
{{ $hStatus === 'error' ? __('Error') : ($hStatus === 'warning' ? __('Warning') : __('Normal')) }}
|
||||
@if($machine->latest_hardware_log_time && $hStatus !== 'normal')
|
||||
<span class="text-[9px] lowercase opacity-70 block -mt-0.5 ml-3 font-bold">{{ $machine->latest_hardware_log_time->diffForHumans() }}</span>
|
||||
@endif
|
||||
</span>
|
||||
</div>
|
||||
{{-- 刷卡機 --}}
|
||||
<div class="flex items-center gap-1.5 opacity-50">
|
||||
<span class="w-1.5 h-1.5 rounded-full bg-emerald-500/50"></span>
|
||||
<span class="text-xs font-black text-slate-400 uppercase tracking-widest">{{
|
||||
__('Normal') }}</span>
|
||||
</div>
|
||||
<div class="flex items-center gap-1.5">
|
||||
<span class="w-1.5 h-1.5 rounded-full bg-emerald-500/50"></span>
|
||||
<span class="text-xs font-black text-slate-400 uppercase tracking-widest">{{
|
||||
__('Normal') }}</span>
|
||||
</div>
|
||||
<div class="flex items-center gap-1.5">
|
||||
{{-- 掃碼機 --}}
|
||||
<div class="flex items-center gap-1.5 opacity-50">
|
||||
<span class="w-1.5 h-1.5 rounded-full bg-emerald-500/50"></span>
|
||||
<span class="text-xs font-black text-slate-400 uppercase tracking-widest">{{
|
||||
__('Normal') }}</span>
|
||||
</div>
|
||||
</div>
|
||||
</td>
|
||||
<td class="px-6 py-6 font-bold text-slate-600 dark:text-slate-400 text-center">
|
||||
{{ $machine->current_page_label ?: '-' }}
|
||||
</td>
|
||||
<td
|
||||
class="px-6 py-6 font-mono text-xs font-black text-slate-400 dark:text-slate-500 uppercase tracking-widest text-center">
|
||||
{{ $machine->firmware_version ?: '-' }}
|
||||
@ -454,13 +482,6 @@
|
||||
<p class="text-sm font-bold text-slate-700 dark:text-slate-300 font-mono">{{
|
||||
$machine->firmware_version ?: '-' }}</p>
|
||||
</div>
|
||||
<div>
|
||||
<p
|
||||
class="text-[10px] font-black text-slate-400 dark:text-slate-500 uppercase tracking-widest mb-1">
|
||||
{{ __('Last Page') }}</p>
|
||||
<p class="text-sm font-bold text-slate-700 dark:text-slate-300">{{
|
||||
$machine->current_page_label ?: '-' }}</p>
|
||||
</div>
|
||||
<div>
|
||||
<p
|
||||
class="text-[10px] font-black text-slate-400 dark:text-slate-500 uppercase tracking-widest mb-1">
|
||||
@ -469,6 +490,9 @@
|
||||
<x-status-badge
|
||||
:status="$cStatus === 'online' ? 'online' : ($cStatus === 'error' ? 'error' : 'offline')"
|
||||
size="sm" />
|
||||
@if($machine->latest_status_log_time && in_array($cStatus, ['error', 'warning']))
|
||||
<p class="text-[9px] font-bold text-slate-400 mt-1">{{ $machine->latest_status_log_time->diffForHumans() }}</p>
|
||||
@endif
|
||||
</div>
|
||||
<div class="col-span-2">
|
||||
<p
|
||||
|
||||
Loading…
Reference in New Issue
Block a user