From fb1bcf49e1355656a4f18b287dfc940e5bd2b70a Mon Sep 17 00:00:00 2001 From: sky121113 Date: Fri, 29 May 2026 10:55:23 +0800 Subject: [PATCH] =?UTF-8?q?[FEAT]=20=E6=96=B0=E5=A2=9E=20OTA=20=E9=A0=90?= =?UTF-8?q?=E7=B4=84=E6=8E=92=E7=A8=8B=E8=88=87=E9=83=A8=E7=BD=B2=E7=8B=80?= =?UTF-8?q?=E6=85=8B=E5=B1=95=E7=A4=BA=EF=BC=8C=E4=B8=A6=E5=84=AA=E5=8C=96?= =?UTF-8?q?=E7=89=88=E6=9C=AC=E6=8E=92=E5=BA=8F=E8=88=87=20UI?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit 1. 建立 `ota_schedules` 資料表與 `OtaSchedule` Model,提供 OTA 預約排程基礎儲存結構。 2. 建立 `ota:process-schedules` 背景 Artisan Command 並註冊至 Console Kernel,每分鐘自動執行到期的預約並透過 MQTT 發送。 3. 擴充 `ApkVersionController` index 方法:加入加載列表時自動觸發排程的防呆機制,並在記憶體中快速統計 pending 與 completed 的機台。 4. 擴充 `ApkVersionController` store 與 push 方法,移除已用不到的 `is_forced` (更新模式) 限制,並支援 `scheduled_at` 預約時間的寫入。 5. 調整 `create.blade.php` 頁面,移除「強制更新」的 Switch 欄位,僅保留注意事項防錯提示。 6. 調整 `index.blade.php` 頁面,將「更新模式」改為「部署狀態」一欄,整合系統內建的 `.tooltip` 與原生 `title` 屬性,在加載與刷新時 100% 穩定展示 Hover 機台名單。 7. 優化 `index.blade.php` 彈窗,將原生時間輸入框改為 Flatpickr 樣式,加入日曆圖標並支援動態 locale 語言切換(中/英/日),將標籤統一命名為「選擇更新時間」。 8. 同步更新並對齊中、英、日三語系 `lang/*.json` 語系檔,並完成字母排序與反逸出斜線處理。 --- .../Commands/Machine/ProcessOtaSchedules.php | 119 ++++++++++++++++++ app/Console/Kernel.php | 1 + .../BasicSettings/ApkVersionController.php | 53 +++++++- app/Models/Machine/OtaSchedule.php | 35 ++++++ ...5_29_092500_create_ota_schedules_table.php | 26 ++++ lang/en.json | 11 ++ lang/ja.json | 11 ++ lang/zh_TW.json | 11 ++ .../apk-versions/create.blade.php | 51 ++------ .../apk-versions/index.blade.php | 91 ++++++++++++-- 10 files changed, 352 insertions(+), 57 deletions(-) create mode 100644 app/Console/Commands/Machine/ProcessOtaSchedules.php create mode 100644 app/Models/Machine/OtaSchedule.php create mode 100644 database/migrations/2026_05_29_092500_create_ota_schedules_table.php diff --git a/app/Console/Commands/Machine/ProcessOtaSchedules.php b/app/Console/Commands/Machine/ProcessOtaSchedules.php new file mode 100644 index 0000000..54e904c --- /dev/null +++ b/app/Console/Commands/Machine/ProcessOtaSchedules.php @@ -0,0 +1,119 @@ +info('[' . now()->toDateTimeString() . '] Starting to process scheduled OTA updates...'); + + // 撈取狀態為待發送且時間已到的排程 + $schedules = OtaSchedule::where('status', 'pending') + ->where('scheduled_at', '<=', now()) + ->with('apkVersion') + ->get(); + + if ($schedules->isEmpty()) { + $this->info('No pending OTA schedules to process.'); + return 0; + } + + $this->info("Found {$schedules->count()} pending OTA schedule(s) to process."); + + foreach ($schedules as $schedule) { + $this->info("Processing OTA Schedule ID: {$schedule->id} (APK: {$schedule->apkVersion->version_name})"); + + // 標記為處理中,防止重複執行 + $schedule->update(['status' => 'processing']); + $apkVersion = $schedule->apkVersion; + + try { + // 根據 target_type 撈取目標機台 + if ($schedule->target_type === 'all') { + $machines = Machine::all(); + } else { + $machineIds = $schedule->target_value ?? []; + $machines = Machine::whereIn('id', $machineIds)->get(); + } + + if ($machines->isEmpty()) { + $this->warn("No target machines found for OTA Schedule ID: {$schedule->id}. Skipping."); + $schedule->update(['status' => 'completed']); + continue; + } + + $successCount = 0; + + foreach ($machines as $machine) { + // 1. 先建立 RemoteCommand 紀錄 + $command = RemoteCommand::create([ + 'machine_id' => $machine->id, + 'user_id' => $schedule->user_id, + 'command_type' => 'update_app', + 'status' => 'pending', + 'payload' => [ + 'url' => $apkVersion->url, + 'version' => $apkVersion->version_name, + 'version_code' => $apkVersion->version_code, + 'is_forced' => $apkVersion->is_forced, + ], + ]); + + // 2. 推播 MQTT 更新指令 + $pushed = $mqttService->pushCommand( + $machine->serial_no, + 'update_app', + $command->payload, + (string) $command->id + ); + + if ($pushed) { + $successCount++; + } else { + $command->update(['status' => 'failed']); + $this->error("Failed to push MQTT OTA command to machine: {$machine->serial_no}"); + } + } + + $schedule->update(['status' => 'completed']); + $this->info("Successfully processed OTA Schedule ID: {$schedule->id}. MQTT deployed to {$successCount}/{$machines->count()} machines."); + + } catch (Exception $e) { + Log::error("Error processing OTA Schedule ID {$schedule->id}: " . $e->getMessage(), [ + 'exception' => $e + ]); + $schedule->update(['status' => 'failed']); + $this->error("Error processing OTA Schedule ID {$schedule->id}: " . $e->getMessage()); + } + } + + $this->info('OTA schedules processing completed.'); + return 0; + } +} diff --git a/app/Console/Kernel.php b/app/Console/Kernel.php index e6b9960..3c78da9 100644 --- a/app/Console/Kernel.php +++ b/app/Console/Kernel.php @@ -13,6 +13,7 @@ class Kernel extends ConsoleKernel protected function schedule(Schedule $schedule): void { // $schedule->command('inspire')->hourly(); + $schedule->command('ota:process-schedules')->everyMinute(); } /** diff --git a/app/Http/Controllers/Admin/BasicSettings/ApkVersionController.php b/app/Http/Controllers/Admin/BasicSettings/ApkVersionController.php index 660c204..09a1f47 100644 --- a/app/Http/Controllers/Admin/BasicSettings/ApkVersionController.php +++ b/app/Http/Controllers/Admin/BasicSettings/ApkVersionController.php @@ -15,7 +15,39 @@ class ApkVersionController extends AdminController { public function index(): View { - $versions = ApkVersion::orderBy('version_code', 'desc')->paginate(10); + // 在進入 APK 版本清單時,同步觸發已到期排程的執行(確保無 cron 的開發/測試環境也能 100% 執行) + try { + \Illuminate\Support\Facades\Artisan::call('ota:process-schedules'); + } catch (\Exception $e) { + \Illuminate\Support\Facades\Log::error('Auto-trigger scheduled OTA failed: ' . $e->getMessage()); + } + + $versions = ApkVersion::latest()->paginate(10); + + // 統計並附屬機台部署狀態 + $commands = \App\Models\Machine\RemoteCommand::where('command_type', 'update_app') + ->with('machine') + ->get(); + + foreach ($versions as $version) { + $pending = []; + $completed = []; + + foreach ($commands as $cmd) { + $cmdVersionCode = $cmd->payload['version_code'] ?? null; + if ($cmdVersionCode == $version->version_code) { + if ($cmd->status === 'completed' || $cmd->status === 'success') { + $completed[] = $cmd->machine; + } elseif ($cmd->status === 'pending' || $cmd->status === 'processing') { + $pending[] = $cmd->machine; + } + } + } + + $version->pending_machines = collect($pending)->filter()->unique('id'); + $version->completed_machines = collect($completed)->filter()->unique('id'); + } + // 用於 OTA 下發時讓使用者選擇的機台列表 $machines = Machine::all(); return view('admin.basic-settings.apk-versions.index', compact('versions', 'machines')); @@ -35,7 +67,6 @@ class ApkVersionController extends AdminController 'apk_file' => 'required_without:download_url|nullable|file|max:102400', // 上傳限制 100MB 'download_url' => 'required_without:apk_file|nullable|url|max:500', 'release_notes' => 'nullable|string', - 'is_forced' => 'nullable|boolean', ]); $filePath = null; @@ -50,7 +81,7 @@ class ApkVersionController extends AdminController 'file_path' => $filePath, 'download_url' => $request->download_url, 'release_notes' => $request->release_notes, - 'is_forced' => $request->boolean('is_forced'), + 'is_forced' => false, ]); return redirect()->route('admin.basic-settings.apk-versions.index') @@ -76,8 +107,24 @@ class ApkVersionController extends AdminController $request->validate([ 'machine_ids' => 'required|array', 'machine_ids.*' => 'exists:machines,id', + 'scheduled_at' => 'nullable|date|after:now', ]); + // 如果設定了預約發送時間 + if ($request->filled('scheduled_at')) { + \App\Models\Machine\OtaSchedule::create([ + 'apk_version_id' => $apkVersion->id, + 'scheduled_at' => $request->scheduled_at, + 'target_type' => 'custom', + 'target_value' => $request->machine_ids, + 'status' => 'pending', + 'user_id' => auth()->id(), + ]); + + return redirect()->route('admin.basic-settings.apk-versions.index') + ->with('success', __('OTA update scheduled successfully.')); + } + $machines = Machine::whereIn('id', $request->machine_ids)->get(); $successCount = 0; diff --git a/app/Models/Machine/OtaSchedule.php b/app/Models/Machine/OtaSchedule.php new file mode 100644 index 0000000..9759c6c --- /dev/null +++ b/app/Models/Machine/OtaSchedule.php @@ -0,0 +1,35 @@ + 'datetime', + 'target_value' => 'array', + ]; + + public function apkVersion() + { + return $this->belongsTo(ApkVersion::class); + } + + public function user() + { + return $this->belongsTo(\App\Models\System\User::class); + } +} diff --git a/database/migrations/2026_05_29_092500_create_ota_schedules_table.php b/database/migrations/2026_05_29_092500_create_ota_schedules_table.php new file mode 100644 index 0000000..26f07f1 --- /dev/null +++ b/database/migrations/2026_05_29_092500_create_ota_schedules_table.php @@ -0,0 +1,26 @@ +id(); + $table->foreignId('apk_version_id')->constrained('apk_versions')->onDelete('cascade'); + $table->dateTime('scheduled_at'); + $table->string('target_type')->default('custom'); // 'all', 'custom' + $table->json('target_value')->nullable(); // 當 target_type = 'custom' 時,儲存 [1, 2, 3] 機台 ID 的 JSON 陣列 + $table->string('status')->default('pending'); // 'pending', 'processing', 'completed', 'failed' + $table->foreignId('user_id')->nullable()->constrained('users')->onDelete('set null'); // 建立排程的使用者 + $table->timestamps(); + }); + } + + public function down(): void + { + Schema::dropIfExists('ota_schedules'); + } +}; diff --git a/lang/en.json b/lang/en.json index ff8b5d9..c520f45 100644 --- a/lang/en.json +++ b/lang/en.json @@ -476,6 +476,7 @@ "Delta": "Delta", "Deploy Update": "Deploy Update", "Deploy version": "Deploy version", + "Deployment Status": "Deployment Status", "Deposit Bonus": "Deposit Bonus", "Describe the changes in this version...": "Describe the changes in this version...", "Describe the repair or maintenance status...": "Describe the repair or maintenance status...", @@ -527,6 +528,7 @@ "Dispensing in progress": "Dispensing in progress", "Display Material Code": "Display Material Code", "Displaying": "Displaying", + "Done": "Done", "Door Closed": "Door Closed", "Door Opened": "Door Opened", "Download Image": "Download Image", @@ -887,6 +889,7 @@ "Last Week": "Last Week", "Latitude": "Latitude", "Lease": "Lease", + "Leave blank to deploy immediately, or set a future time to schedule a background release.": "Leave blank to deploy immediately, or set a future time to schedule a background release.", "Leave empty for permanent code": "Leave empty for permanent code", "Leave empty or 0 for permanent code": "Leave empty or 0 for permanent code", "Leave empty to inherit company settings": "Leave empty to inherit company settings", @@ -1174,6 +1177,7 @@ "No machines available": "No machines available", "No machines available in this company.": "No machines available in this company.", "No machines found": "No machines found", + "No machines in this status": "No machines in this status", "No machines match your criteria": "No machines match your criteria", "No maintenance records found": "No maintenance records found", "No matching logs found": "No matching logs found", @@ -1231,6 +1235,7 @@ "OTA Update Deployment": "OTA Update Deployment", "OTA command sent to %d devices successfully.": "OTA command sent to %d devices successfully.", "OTA firmware update and version control": "OTA firmware update and version control", + "OTA update scheduled successfully.": "OTA update scheduled successfully.", "Off": "Off", "Offline": "Offline", "Offline + 1": "Offline + 1", @@ -1503,6 +1508,8 @@ "Quick replenishment from this view": "Quick replenishment from this view", "Quick search...": "Quick search...", "Rank": "Rank", + "Ready": "Ready", + "Ready to Execute": "Ready to Execute", "Ready to print": "Ready to print", "Real-time OEE analysis awaits": "Real-time OEE analysis awaits", "Real-time Operation Logs (Last 50)": "Real-time Operation Logs (Last 50)", @@ -1630,6 +1637,7 @@ "Scan to authorize testing or maintenance": "Scan to authorize testing or maintenance", "Scan to pick up your product": "Scan to pick up your product", "Schedule": "Schedule", + "Schedule Release Time (Optional)": "Schedule Release Time (Optional)", "Search": "Search", "Search Company Title...": "Search Company Title...", "Search Company...": "Search Company...", @@ -1695,6 +1703,7 @@ "Select Slot...": "Select Slot...", "Select Target Company": "Select Target Company", "Select Target Slot": "Select Target Slot", + "Select Update Time (Optional)": "Select Update Time (Optional)", "Select Warehouse": "Select Warehouse", "Select a machine to copy settings from...": "Select a machine to copy settings from...", "Select a machine to deep dive": "Select a machine to deep dive", @@ -1704,6 +1713,7 @@ "Select an asset from the left to start analysis": "Select an asset from the left to start analysis", "Select date to sync data": "Select date to sync data", "Select flavor...": "Select flavor...", + "Select future time...": "Select update time...", "Select...": "Select...", "Selected": "Selected", "Selected Date": "Selected Date", @@ -2015,6 +2025,7 @@ "Unpublish": "Unpublish", "Update": "Update", "Update Authorization": "Update Authorization", + "Update Completed": "Update Completed", "Update Customer": "Update Customer", "Update Mode": "Update Mode", "Update Pass Code": "Update Pass Code", diff --git a/lang/ja.json b/lang/ja.json index 55d4652..11cf9e4 100644 --- a/lang/ja.json +++ b/lang/ja.json @@ -476,6 +476,7 @@ "Delta": "変動量", "Deploy Update": "Deploy Update", "Deploy version": "Deploy version", + "Deployment Status": "配信ステータス", "Deposit Bonus": "チャージボーナス", "Describe the changes in this version...": "Describe the changes in this version...", "Describe the repair or maintenance status...": "修理やメンテナンスの状況を説明してください...", @@ -527,6 +528,7 @@ "Dispensing in progress": "搬送中", "Display Material Code": "資材コードを表示", "Displaying": "表示中", + "Done": "完了", "Door Closed": "扉が閉まりました", "Door Opened": "扉が開きました", "Download Image": "画像をダウンロード", @@ -887,6 +889,7 @@ "Last Week": "先週", "Latitude": "緯度", "Lease": "リース", + "Leave blank to deploy immediately, or set a future time to schedule a background release.": "空欄のままにすると即時配信されます。未来の日時を指定すると背景で自動予約配信されます。", "Leave empty for permanent code": "空欄で無期限コードになります", "Leave empty or 0 for permanent code": "空欄または0で無期限コードになります", "Leave empty to inherit company settings": "空白のままにすると会社のグローバル設定を継承します", @@ -1174,6 +1177,7 @@ "No machines available": "利用可能な機器がありません", "No machines available in this company.": "この会社で利用可能な機器がありません。", "No machines found": "機器が見つかりません", + "No machines in this status": "このステータスのデバイスはありません", "No machines match your criteria": "No machines match your criteria", "No maintenance records found": "メンテナンス記録が見つかりません", "No matching logs found": "一致するログが見つかりません", @@ -1231,6 +1235,7 @@ "OTA Update Deployment": "OTA Update Deployment", "OTA command sent to %d devices successfully.": "OTA command sent to %d devices successfully.", "OTA firmware update and version control": "OTA firmware update and version control", + "OTA update scheduled successfully.": "OTA予約更新スケジュールが正常に作成されました。", "Off": "オフ", "Offline": "オフライン", "Offline + 1": "オフライン + 1", @@ -1503,6 +1508,8 @@ "Quick replenishment from this view": "この画面からクイック補充", "Quick search...": "クイック検索...", "Rank": "順位", + "Ready": "準備中", + "Ready to Execute": "コマンド実行待ちデバイス", "Ready to print": "印刷準備完了", "Real-time OEE analysis awaits": "リアルタイムOEE分析待機中", "Real-time Operation Logs (Last 50)": "リアルタイム操作ログ (直近50件)", @@ -1630,6 +1637,7 @@ "Scan to authorize testing or maintenance": "スキャンしてテストまたはメンテナンスを承認します", "Scan to pick up your product": "スキャンして商品をお受け取りください", "Schedule": "スケジュール", + "Schedule Release Time (Optional)": "予約配信日時(任意)", "Search": "検索", "Search Company Title...": "会社名を検索...", "Search Company...": "会社を検索...", @@ -1695,6 +1703,7 @@ "Select Slot...": "スロットを選択...", "Select Target Company": "対象の会社を選択", "Select Target Slot": "対象のスロットを選択", + "Select Update Time (Optional)": "更新時間を選択してください(任意)", "Select Warehouse": "倉庫を選択", "Select a machine to copy settings from...": "設定のコピー元機器を選択...", "Select a machine to deep dive": "詳細分析する機器を選択", @@ -1704,6 +1713,7 @@ "Select an asset from the left to start analysis": "左側から機器を選択して分析を開始", "Select date to sync data": "データを同期する日付を選択", "Select flavor...": "Select flavor...", + "Select future time...": "配信日時を選択してください...", "Select...": "選択してください...", "Selected": "選択中", "Selected Date": "選択日", @@ -2015,6 +2025,7 @@ "Unpublish": "非公開", "Update": "更新", "Update Authorization": "権限更新", + "Update Completed": "アップデート完了デバイス", "Update Customer": "顧客更新", "Update Mode": "Update Mode", "Update Pass Code": "パスコード更新", diff --git a/lang/zh_TW.json b/lang/zh_TW.json index b889d74..f7674a0 100644 --- a/lang/zh_TW.json +++ b/lang/zh_TW.json @@ -476,6 +476,7 @@ "Delta": "變化量", "Deploy Update": "部署更新", "Deploy version": "部署版本", + "Deployment Status": "部署狀態", "Deposit Bonus": "儲值回饋", "Describe the changes in this version...": "描述此版本的變更...", "Describe the repair or maintenance status...": "請描述維修或保養狀況...", @@ -527,6 +528,7 @@ "Dispensing in progress": "正在出貨中", "Display Material Code": "顯示物料代碼", "Displaying": "目前顯示", + "Done": "已完成", "Door Closed": "機門已關閉", "Door Opened": "機門已開啟", "Download Image": "下載圖片", @@ -887,6 +889,7 @@ "Last Week": "上週", "Latitude": "緯度", "Lease": "租賃", + "Leave blank to deploy immediately, or set a future time to schedule a background release.": "留空將立即部署更新,或設定未來時間由背景自動排程發布。", "Leave empty for permanent code": "留空表示永久有效", "Leave empty or 0 for permanent code": "留空或輸入 0 則為永久代碼", "Leave empty to inherit company settings": "留空則繼承公司全域設定", @@ -1174,6 +1177,7 @@ "No machines available": "目前沒有可供分配的機台", "No machines available in this company.": "此客戶目前沒有可供分配的機台。", "No machines found": "未找到機台", + "No machines in this status": "此狀態下無設備", "No machines match your criteria": "沒有符合條件的機台", "No maintenance records found": "找不到維修紀錄", "No matching logs found": "找不到符合條件的日誌", @@ -1231,6 +1235,7 @@ "OTA Update Deployment": "OTA 更新部署", "OTA command sent to %d devices successfully.": "OTA 指令已成功發送至 %d 台設備。", "OTA firmware update and version control": "OTA 韌體更新與版本控制", + "OTA update scheduled successfully.": "OTA 預約更新排程已建立成功。", "Off": "關閉", "Offline": "離線", "Offline + 1": "離線 + 1", @@ -1503,6 +1508,8 @@ "Quick replenishment from this view": "從此畫面快速補貨", "Quick search...": "快速搜尋...", "Rank": "排行", + "Ready": "準備中", + "Ready to Execute": "準備要執行指令的設備", "Ready to print": "準備列印", "Real-time OEE analysis awaits": "即時 OEE 分析預備中", "Real-time Operation Logs (Last 50)": "即時操作日誌 (最後 50 筆)", @@ -1630,6 +1637,7 @@ "Scan to authorize testing or maintenance": "掃描以進行測試或維護授權", "Scan to pick up your product": "掃描以領取您的商品", "Schedule": "排程區間", + "Schedule Release Time (Optional)": "預約發布時間(選填)", "Search": "搜尋", "Search Company Title...": "搜尋公司名稱...", "Search Company...": "搜尋公司...", @@ -1695,6 +1703,7 @@ "Select Slot...": "選擇貨道...", "Select Target Company": "選擇目標公司", "Select Target Slot": "選擇目標貨道", + "Select Update Time (Optional)": "選擇更新時間(選填)", "Select Warehouse": "選擇倉庫", "Select a machine to copy settings from...": "選擇要複製設定的來源機台...", "Select a machine to deep dive": "請選擇機台以開始深度分析", @@ -1704,6 +1713,7 @@ "Select an asset from the left to start analysis": "選擇左側機台以開始分析數據", "Select date to sync data": "選擇日期以同步數據", "Select flavor...": "選擇風味...", + "Select future time...": "選擇更新時間", "Select...": "請選擇...", "Selected": "已選擇", "Selected Date": "查詢日期", @@ -2015,6 +2025,7 @@ "Unpublish": "下架", "Update": "編輯", "Update Authorization": "更新授權", + "Update Completed": "已經完成更新指令的設備", "Update Customer": "更新客戶", "Update Mode": "更新模式", "Update Pass Code": "更新通行碼", diff --git a/resources/views/admin/basic-settings/apk-versions/create.blade.php b/resources/views/admin/basic-settings/apk-versions/create.blade.php index 51009c7..afd8e52 100644 --- a/resources/views/admin/basic-settings/apk-versions/create.blade.php +++ b/resources/views/admin/basic-settings/apk-versions/create.blade.php @@ -173,49 +173,15 @@ - {{-- 更新模式卡片 --}} -
-
-
- + {{-- 注意事項卡片 --}} +
+
+
+
-
-

{{ __('Update Mode') }}

-

{{ __('Control deployment behavior') }}

-
-
- - {{-- 強制更新開關 --}} -
-
-
- -
-
-

{{ __('Forced Update') }}

-

{{ __('During OTA deployment, the device will install directly in the background without user confirmation.') }}

-
-
- -
- - {{-- 注意事項 --}} -
-
- -
-

{{ __('Important Notice') }}

-

{{ __('Version Code must be strictly greater than the previous release, otherwise the OTA silent installation on the device will fail with INSTALL_FAILED_ALREADY_EXISTS.') }}

-
+
+

{{ __('Important Notice') }}

+

{{ __('Version Code must be strictly greater than the previous release, otherwise the OTA silent installation on the device will fail with INSTALL_FAILED_ALREADY_EXISTS.') }}

@@ -230,7 +196,6 @@ uploadMode: 'file', fileName: '', isDragging: false, - isForced: false, versionName: '{{ old('version_name', '') }}', versionCode: '{{ old('version_code', '') }}', diff --git a/resources/views/admin/basic-settings/apk-versions/index.blade.php b/resources/views/admin/basic-settings/apk-versions/index.blade.php index 543a404..a3e4950 100644 --- a/resources/views/admin/basic-settings/apk-versions/index.blade.php +++ b/resources/views/admin/basic-settings/apk-versions/index.blade.php @@ -43,6 +43,9 @@ this.searchMachineQuery = ''; this.onlyOnlineMachines = false; this.isPushModalOpen = true; + // 清空預約時間 + const scheduleInput = document.getElementById('scheduled_at'); + if (scheduleInput) scheduleInput.value = ''; }, get filteredMachines() { @@ -143,7 +146,7 @@ {{ __('Version Info') }} {{ __('Machine Flavor') }} - {{ __('Update Mode') }} + {{ __('Deployment Status') }} {{ __('Release Notes') }} {{ __('Action') }} @@ -171,16 +174,46 @@ {{ $version->flavor }} - - @if($version->is_forced) - - {{ __('Forced Update') }} - - @else - - {{ __('Optional Update') }} - - @endif + +
+ + @php + $pendingTooltip = __('Ready to Execute') . ":\n"; + if ($version->pending_machines->isEmpty()) { + $pendingTooltip .= "• " . __('No machines in this status'); + } else { + foreach($version->pending_machines as $m) { + $pendingTooltip .= "• {$m->name} ({$m->serial_no})\n"; + } + } + $pendingTooltip = trim($pendingTooltip); + @endphp +
+ + + {{ __('Ready') }}: {{ $version->pending_machines->count() }} + +
+ + + @php + $completedTooltip = __('Update Completed') . ":\n"; + if ($version->completed_machines->isEmpty()) { + $completedTooltip .= "• " . __('No machines in this status'); + } else { + foreach($version->completed_machines as $m) { + $completedTooltip .= "• {$m->name} ({$m->serial_no})\n"; + } + } + $completedTooltip = trim($completedTooltip); + @endphp +
+ + + {{ __('Done') }}: {{ $version->completed_machines->count() }} + +
+
@@ -307,6 +340,42 @@
@csrf + + +
+
+
+ +

+ {{ __('Leave blank to deploy immediately, or set a future time to schedule a background release.') }} +

+
+
+
+ + + + + + +
+
+
+
+