diff --git a/app/Http/Controllers/Admin/BasicSettings/DiscordNotificationController.php b/app/Http/Controllers/Admin/BasicSettings/DiscordNotificationController.php index 7441543..213eae1 100644 --- a/app/Http/Controllers/Admin/BasicSettings/DiscordNotificationController.php +++ b/app/Http/Controllers/Admin/BasicSettings/DiscordNotificationController.php @@ -128,16 +128,37 @@ class DiscordNotificationController extends AdminController $request->validate([ 'discord_webhook_url' => 'nullable|url|max:500', - 'temp_alert_enabled' => 'required|string|in:inherit,enabled,disabled', + 'notify_types_mode' => 'required|string|in:inherit,custom', + 'discord_notify_types' => 'nullable|array', + 'discord_notify_types.*' => 'string|in:submachine,status,temperature', 'temp_upper_limit' => 'nullable|numeric|min:-50|max:100', 'temp_lower_limit' => 'nullable|numeric|min:-50|max:100', ]); $settings = $machine->settings ?? []; $settings['discord_webhook_url'] = $request->input('discord_webhook_url'); - $settings['temp_alert_enabled'] = $request->input('temp_alert_enabled'); - $settings['temp_upper_limit'] = $request->filled('temp_upper_limit') ? (float)$request->temp_upper_limit : null; - $settings['temp_lower_limit'] = $request->filled('temp_lower_limit') ? (float)$request->temp_lower_limit : null; + + $notifyTypesMode = $request->input('notify_types_mode', 'inherit'); + $notifyTypes = $request->input('discord_notify_types', []); + $hasTemperature = in_array('temperature', $notifyTypes, true); + + if ($notifyTypesMode === 'custom') { + $settings['discord_notify_types'] = array_values($notifyTypes); + if ($hasTemperature) { + $settings['temp_alert_enabled'] = 'enabled'; + $settings['temp_upper_limit'] = $request->filled('temp_upper_limit') ? (float)$request->temp_upper_limit : null; + $settings['temp_lower_limit'] = $request->filled('temp_lower_limit') ? (float)$request->temp_lower_limit : null; + } else { + $settings['temp_alert_enabled'] = 'disabled'; + $settings['temp_upper_limit'] = null; + $settings['temp_lower_limit'] = null; + } + } else { + unset($settings['discord_notify_types']); + $settings['temp_alert_enabled'] = 'inherit'; + $settings['temp_upper_limit'] = null; + $settings['temp_lower_limit'] = null; + } $machine->settings = $settings; $machine->save(); diff --git a/app/Jobs/Machine/ProcessHeartbeat.php b/app/Jobs/Machine/ProcessHeartbeat.php index f196d9d..b4fac1e 100644 --- a/app/Jobs/Machine/ProcessHeartbeat.php +++ b/app/Jobs/Machine/ProcessHeartbeat.php @@ -178,16 +178,8 @@ class ProcessHeartbeat implements ShouldQueue $activeStatus = \Illuminate\Support\Facades\Cache::get($activeKey); if ($temp > $upperLimit) { - // 若從低溫直接跳到高溫,先恢復低溫 + // 若從低溫直接跳到高溫,清除低溫告警冷卻快取,但不發送恢復通知 if ($activeStatus === 'low') { - \App\Jobs\Machine\ProcessStateLog::dispatch( - $machine->id, - $machine->company_id, - "Temperature restored: :temp°C", - 'info', - ['temp' => $temp], - 'temperature' - ); \Illuminate\Support\Facades\Cache::forget($cooldownKey); } @@ -206,16 +198,8 @@ class ProcessHeartbeat implements ShouldQueue \Illuminate\Support\Facades\Cache::put($activeKey, 'high', 86400); } } elseif ($temp < $lowerLimit) { - // 若從高溫直接跳到低溫,先恢復高溫 + // 若從高溫直接跳到低溫,清除高溫告警冷卻快取,但不發送恢復通知 if ($activeStatus === 'high') { - \App\Jobs\Machine\ProcessStateLog::dispatch( - $machine->id, - $machine->company_id, - "Temperature restored: :temp°C", - 'info', - ['temp' => $temp], - 'temperature' - ); \Illuminate\Support\Facades\Cache::forget($cooldownKey); } diff --git a/lang/en.json b/lang/en.json index 7b3c227..64c203b 100644 --- a/lang/en.json +++ b/lang/en.json @@ -2131,5 +2131,11 @@ "Default Temp Alert Upper Limit (°C)": "Default Temp Alert Upper Limit (°C)", "Default Temp Alert Lower Limit (°C)": "Default Temp Alert Lower Limit (°C)", "Default Temp Alert Limits": "Default Temp Alert Limits", + "Alert Types Source": "Alert Types Source", + "Inherit Company Alert Types": "Inherit Company Alert Types", + "Custom Alert Types": "Custom Alert Types", + "Effective Alert Types": "Effective Alert Types", + "Machine Alert Settings": "Machine Alert Settings", + "Configure alert types and temperature thresholds": "Configure alert types and temperature thresholds", "Machine model saved successfully.": "Machine model saved successfully." -} \ No newline at end of file +} diff --git a/lang/ja.json b/lang/ja.json index b8f87c1..986fcbf 100644 --- a/lang/ja.json +++ b/lang/ja.json @@ -2134,5 +2134,11 @@ "Default Temp Alert Upper Limit (°C)": "デフォルト温度アラート上限 (°C)", "Default Temp Alert Lower Limit (°C)": "デフォルト温度アラート下限 (°C)", "Default Temp Alert Limits": "デフォルト温度アラート範囲", + "Alert Types Source": "アラート種別ソース", + "Inherit Company Alert Types": "会社のアラート種別を継承", + "Custom Alert Types": "カスタムアラート種別", + "Effective Alert Types": "有効なアラート種別", + "Machine Alert Settings": "機台アラート設定", + "Configure alert types and temperature thresholds": "アラート種別と温度しきい値を設定", "Machine model saved successfully.": "機器モデルが正常に追加されました。" -} \ No newline at end of file +} diff --git a/lang/zh_TW.json b/lang/zh_TW.json index 5d9639b..3170218 100644 --- a/lang/zh_TW.json +++ b/lang/zh_TW.json @@ -2190,5 +2190,11 @@ "Default Temp Alert Upper Limit (°C)": "預設溫度告警上限 (°C)", "Default Temp Alert Lower Limit (°C)": "預設溫度告警下限 (°C)", "Default Temp Alert Limits": "預設溫度告警範圍", + "Alert Types Source": "告警類型來源", + "Inherit Company Alert Types": "繼承公司告警類型", + "Custom Alert Types": "自訂告警類型", + "Effective Alert Types": "目前生效的告警類型", + "Machine Alert Settings": "機台告警設定", + "Configure alert types and temperature thresholds": "設定告警類型與溫度門檻", "Machine model saved successfully.": "機台型號已成功新增。" -} \ No newline at end of file +} diff --git a/resources/views/admin/basic-settings/discord-notifications/index.blade.php b/resources/views/admin/basic-settings/discord-notifications/index.blade.php index 2c02d00..f1e54b0 100644 --- a/resources/views/admin/basic-settings/discord-notifications/index.blade.php +++ b/resources/views/admin/basic-settings/discord-notifications/index.blade.php @@ -262,7 +262,8 @@ {{ __('Company') }} @endif {{ __('Webhook Status') }} - {{ __('Temp Alert State') }} + {{ __('Configured Alert Types') }} + {{ __('Temp Alert Range') }} {{ __('Actions') }} @@ -273,6 +274,11 @@ $mWebhook = $mSettings['discord_webhook_url'] ?? null; $cSettings = $machine->company->settings ?? []; $cWebhook = $cSettings['discord_webhook_url'] ?? null; + $cNotifyTypes = $cSettings['discord_notify_types'] ?? []; + $mNotifyTypes = array_key_exists('discord_notify_types', $mSettings) && is_array($mSettings['discord_notify_types']) ? $mSettings['discord_notify_types'] : null; + $effectiveNotifyTypes = $mNotifyTypes ?? $cNotifyTypes; + $isTypeInherited = $mNotifyTypes === null; + $hasTemperatureNotify = in_array('temperature', $effectiveNotifyTypes, true); $mTempAlert = $mSettings['temp_alert_enabled'] ?? 'inherit'; @@ -291,11 +297,11 @@ $finalLower = $sysLower; $tempSource = __('System Default'); - if ($mTempAlert === 'enabled' && ($mUpper !== null || $mLower !== null)) { + if ($mTempAlert === 'enabled' && $hasTemperatureNotify && ($mUpper !== null || $mLower !== null)) { $finalUpper = $mUpper !== null ? $mUpper : $sysUpper; $finalLower = $mLower !== null ? $mLower : $sysLower; $tempSource = __('Custom'); - } elseif ($modelUpper !== null || $modelLower !== null) { + } elseif ($hasTemperatureNotify && ($modelUpper !== null || $modelLower !== null)) { $finalUpper = $modelUpper !== null ? $modelUpper : $sysUpper; $finalLower = $modelLower !== null ? $modelLower : $sysLower; $tempSource = __('Model Default'); @@ -331,36 +337,48 @@ {{ __('Machine Specific') }} @elseif($cWebhook) {{ Str::limit($cWebhook, 30) }} - {{ __('Inherit Company') }} + {{ __('Inherit Company') }} @else {{ __('Not Configured') }} @endif -
-
- @if($mTempAlert === 'enabled') - - @elseif($mTempAlert === 'disabled') - - @else - - {{ __('Inherit') }} - +
+ @if($isTypeInherited) + {{ __('Inherit Company') }} + @endif +
+ @foreach($effectiveNotifyTypes as $type) + @php + $label = match($type) { + 'submachine' => __('Device Exception'), + 'status' => __('Connectivity Logs'), + 'temperature' => __('Temperature Alert'), + default => $type + }; + @endphp + {{ $label }} + @endforeach + @if(empty($effectiveNotifyTypes)) + {{ __('Not Configured') }} @endif
- @if($mTempAlert !== 'disabled') -
- - {{ number_format($finalLower, 0) }}°C ~ {{ number_format($finalUpper, 0) }}°C - - - ({{ $tempSource }}) - -
- @endif
+ + @if($mTempAlert !== 'disabled' && $hasTemperatureNotify) +
+ + {{ number_format($finalLower, 0) }}°C ~ {{ number_format($finalUpper, 0) }}°C + + + ({{ $tempSource }}) + +
+ @else + + @endif +
- @if($mTempAlert === 'enabled') - - @elseif($mTempAlert === 'disabled') - - @else - - {{ __('Inherit') }} - - @endif + {{-- Temperature status is implied by configured alert types; show only range below. --}}
@@ -451,12 +466,34 @@ {{ __('Machine Specific') }} @elseif($cWebhook)

{{ $cWebhook }}

- {{ __('Inherit Company') }} + {{ __('Inherit Company') }} @else

{{ __('Not Configured') }}

@endif
- @if($mTempAlert !== 'disabled') +
+

{{ __('Configured Alert Types') }}

+ @if($isTypeInherited) + {{ __('Inherit Company') }} + @endif +
+ @foreach($effectiveNotifyTypes as $type) + @php + $label = match($type) { + 'submachine' => __('Device Exception'), + 'status' => __('Connectivity Logs'), + 'temperature' => __('Temperature Alert'), + default => $type + }; + @endphp + {{ $label }} + @endforeach + @if(empty($effectiveNotifyTypes)) +

{{ __('Not Configured') }}

+ @endif +
+
+ @if($mTempAlert !== 'disabled' && $hasTemperatureNotify)

{{ __('Temp Alert Range') }}

@@ -624,19 +661,34 @@
-
@@ -713,7 +765,7 @@ @csrf @method('PUT') -
+
@@ -793,33 +845,74 @@
-
+ +
-

{{ __('Temperature Alert Settings') }}

-

{{ __('Define custom temperature limits or inherit from model') }}

+

{{ __('Machine Alert Settings') }}

+

{{ __('Configure alert types and temperature thresholds') }}

-
- - - - - + + + +
+
+ +
+ + + +
+
+ +
+ +
+ +
+
+ -
+ class="grid grid-cols-1 sm:grid-cols-2 gap-4 animate-luxury-in">
@@ -903,6 +996,8 @@ }, machineForm: { discord_webhook_url: '', + notify_types_mode: 'inherit', + discord_notify_types: [], temp_alert_enabled: 'inherit', temp_upper_limit: '', temp_lower_limit: '' @@ -936,23 +1031,63 @@ openMachineEdit(machine) { const settings = machine.settings || {}; + const machineNotifyTypes = Array.isArray(settings.discord_notify_types) ? settings.discord_notify_types : null; this.currentMachine = machine; this.machineForm.discord_webhook_url = settings.discord_webhook_url || ''; - this.machineForm.temp_alert_enabled = settings.temp_alert_enabled || 'inherit'; + this.machineForm.notify_types_mode = machineNotifyTypes === null ? 'inherit' : 'custom'; + this.machineForm.discord_notify_types = machineNotifyTypes === null ? [] : [...machineNotifyTypes]; + this.machineForm.temp_alert_enabled = settings.temp_alert_enabled || (this.isTemperatureChecked() ? 'enabled' : 'disabled'); this.machineForm.temp_upper_limit = settings.temp_upper_limit !== undefined && settings.temp_upper_limit !== null ? Math.round(parseFloat(settings.temp_upper_limit)).toString() : ''; this.machineForm.temp_lower_limit = settings.temp_lower_limit !== undefined && settings.temp_lower_limit !== null ? Math.round(parseFloat(settings.temp_lower_limit)).toString() : ''; + this.syncTempAlertState(); this.testStatus = null; this.testMessage = ''; this.showMachineModal = true; this.$nextTick(() => { - const alertSel = window.HSSelect?.getInstance('#machine-alert-select'); - if (alertSel) alertSel.setValue(this.machineForm.temp_alert_enabled); + const modeSel = window.HSSelect?.getInstance('#machine-notify-mode-select'); + if (modeSel) modeSel.setValue(this.machineForm.notify_types_mode); this.rebuildCopySelect(); }); }, + notifyTypeLabel(type) { + if (type === 'submachine') return '{{ __('Submachine Alert') }}'; + if (type === 'status') return '{{ __('Machine Connection Alert') }}'; + if (type === 'temperature') return '{{ __('Temperature Alert') }}'; + return type; + }, + + effectiveNotifyTypes() { + if (this.machineForm.notify_types_mode === 'custom') { + return this.machineForm.discord_notify_types; + } + return this.currentMachine?.company?.settings?.discord_notify_types || []; + }, + + isTemperatureChecked() { + return this.effectiveNotifyTypes().includes('temperature'); + }, + + syncTempAlertState() { + if (this.machineForm.notify_types_mode === 'inherit') { + this.machineForm.temp_alert_enabled = 'inherit'; + this.machineForm.temp_upper_limit = ''; + this.machineForm.temp_lower_limit = ''; + return; + } + + if (this.machineForm.discord_notify_types.includes('temperature')) { + this.machineForm.temp_alert_enabled = 'enabled'; + return; + } + + this.machineForm.temp_alert_enabled = 'disabled'; + this.machineForm.temp_upper_limit = ''; + this.machineForm.temp_lower_limit = ''; + }, + rebuildCopySelect() { this.$nextTick(() => { const wrapper = document.getElementById('copy-select-wrapper'); @@ -1039,18 +1174,28 @@ }, handleCopyMachine(value) { - if (!value) return; + if (!value || !String(value).trim()) return; const source = this.copyableMachines.find(m => m.id == value); if (source) { const s = source.settings || {}; + const sourceNotifyTypes = Array.isArray(s.discord_notify_types) ? s.discord_notify_types : null; this.machineForm.discord_webhook_url = s.discord_webhook_url || ''; - this.machineForm.temp_alert_enabled = s.temp_alert_enabled || 'inherit'; + this.machineForm.notify_types_mode = sourceNotifyTypes === null ? 'inherit' : 'custom'; + this.machineForm.discord_notify_types = sourceNotifyTypes === null ? [] : [...sourceNotifyTypes]; + this.machineForm.temp_alert_enabled = s.temp_alert_enabled || (this.isTemperatureChecked() ? 'enabled' : 'disabled'); this.machineForm.temp_upper_limit = s.temp_upper_limit !== undefined && s.temp_upper_limit !== null ? Math.round(parseFloat(s.temp_upper_limit)).toString() : ''; this.machineForm.temp_lower_limit = s.temp_lower_limit !== undefined && s.temp_lower_limit !== null ? Math.round(parseFloat(s.temp_lower_limit)).toString() : ''; + this.syncTempAlertState(); this.$nextTick(() => { - const el = window.HSSelect?.getInstance('#machine-alert-select'); - if (el) el.setValue(this.machineForm.temp_alert_enabled); + const modeSel = window.HSSelect?.getInstance('#machine-notify-mode-select'); + if (modeSel) modeSel.setValue(this.machineForm.notify_types_mode); + const copyEl = document.getElementById('copy-machine-select'); + if (copyEl) { + copyEl.value = ' '; + const copyInstance = window.HSSelect?.getInstance('#copy-machine-select'); + if (copyInstance) copyInstance.setValue(' '); + } }); this.copiedMachineName = source.name;