defmodule DaProductAppWeb.ApplicationUpgradeLive.Index do use DaProductAppWeb, :live_view alias DaProductApp.TerminalManagement alias DaProductApp.TerminalManagement.AppUpgradeConfig @impl true def mount(_params, _session, socket) do changeset = AppUpgradeConfig.changeset(%AppUpgradeConfig{}, %{}) form = to_form(changeset) app_packages = TerminalManagement.list_app_packages() parameter_templates = DaProductApp.ParameterManagement.list_parameter_templates(%{is_active: true}) {:ok, assign(socket, filters: %{}, upgrades: [], show_form: false, show_parameter_form: false, show_custom_parameter_form: false, show_download_form: false, form_changeset: changeset, form: form, current_page: "app_upgrade", app_packages: app_packages, parameter_templates: parameter_templates, selected_template: nil, template_preview: nil, device_compatibility: %{}, parameter_form: nil, download_form: nil, parameter_mode: "template", last_operation_result: nil, # Phase 2 assigns show_job_management: false, show_bulk_operations: false, show_device_groups: false, parameter_jobs: [], job_stats: %{total: 0, completed: 0, running: 0, failed: 0}, device_groups: [], selected_group: nil, bulk_operation_result: nil )} end @impl true def handle_params(_params, _url, socket) do upgrades = TerminalManagement.list_app_upgrade_configs() |> DaProductApp.Repo.preload(:package) {:noreply, assign(socket, upgrades: upgrades)} end @impl true def handle_event("close_panel", _params, socket) do changeset = AppUpgradeConfig.changeset(%AppUpgradeConfig{}, %{}) form = to_form(changeset) {:noreply, assign(socket, show_form: false, show_parameter_form: false, show_custom_parameter_form: false, show_download_form: false, show_edit_form: false, edit_package: nil, form_changeset: changeset, form: form )} end @impl true def handle_event("close_slide_over", _params, socket) do changeset = AppUpgradeConfig.changeset(%AppUpgradeConfig{}, %{}) form = to_form(changeset) {:noreply, assign(socket, show_slide_over: false, show_form: false, show_parameter_form: false, show_download_form: false, form_changeset: changeset, form: form )} end @impl true def handle_event("show_form", _params, socket) do changeset = AppUpgradeConfig.changeset(%AppUpgradeConfig{}, %{}) form = to_form(changeset) {:noreply, assign(socket, show_form: true, show_parameter_form: false, show_custom_parameter_form: false, show_download_form: false, form_changeset: changeset, form: form)} end def handle_event("hide_form", _params, socket) do {:noreply, assign(socket, show_form: false)} end def handle_event("show_parameter_form", _params, socket) do {:noreply, assign(socket, show_parameter_form: true, show_form: false, show_download_form: false, show_custom_parameter_form: false)} end def handle_event("hide_parameter_form", _params, socket) do {:noreply, assign(socket, show_parameter_form: false)} end def handle_event("show_custom_parameter_form", _params, socket) do {:noreply, assign(socket, show_custom_parameter_form: true, show_form: false, show_parameter_form: false, show_download_form: false)} end def handle_event("hide_custom_parameter_form", _params, socket) do {:noreply, assign(socket, show_custom_parameter_form: false)} end def handle_event("show_download_form", _params, socket) do {:noreply, assign(socket, show_download_form: true, show_form: false, show_parameter_form: false, show_custom_parameter_form: false)} end def handle_event("hide_download_form", _params, socket) do {:noreply, assign(socket, show_download_form: false)} end def handle_event("set_parameter_mode", %{"mode" => mode}, socket) do {:noreply, assign(socket, parameter_mode: mode, selected_template: nil, template_preview: nil, device_compatibility: %{})} end def handle_event("validate", %{"app_upgrade_config" => params}, socket) do params = if is_binary(params["target_devices"]) do Map.put(params, "target_devices", normalize_target_devices(params["target_devices"])) else params end changeset = AppUpgradeConfig.changeset(%AppUpgradeConfig{}, params) form = to_form(%{changeset | action: :validate}) {:noreply, assign(socket, form_changeset: %{changeset | action: :validate}, form: form)} end def handle_event("save", %{"app_upgrade_config" => params}, socket) do params = if is_binary(params["target_devices"]) do Map.put(params, "target_devices", normalize_target_devices(params["target_devices"])) else params end # Extract device list from target_devices device_ids = case Jason.decode(params["target_devices"]) do {:ok, list} when is_list(list) -> list _ -> [] end # Remove target_devices from params before saving config params = Map.delete(params, "target_devices") case TerminalManagement.create_app_upgrade_config(params) do {:ok, upgrade_config} -> # For each device_id, create AppUpgradeDeviceStatus Enum.each(device_ids, fn device_id -> # Find terminal by serial number if terminal = TerminalManagement.get_terminal_by_serial(to_string(device_id)) do result = TerminalManagement.create_app_upgrade_device_status(%{ config_id: upgrade_config.id, device_id: terminal.id, device_sn: terminal.serial_number, vendor: terminal.vendor || "N/A", model: terminal.model || "N/A", app_version: terminal.app_version || "N/A", data_version: terminal.data_version || "N/A", system_version: terminal.system_version || "N/A", update_result: nil, status: "pending", remark: nil, finish_time: nil, pushed_time: nil }) unless match?({:ok, _}, result), do: IO.inspect(result, label: "Failed to insert device status") else IO.puts("Terminal not found for device_id: #{inspect(device_id)}") end end) upgrades = TerminalManagement.list_app_upgrade_configs() changeset = AppUpgradeConfig.changeset(%AppUpgradeConfig{}, %{}) form = to_form(changeset) {:noreply, assign(socket, upgrades: upgrades, show_form: false, form_changeset: changeset, form: form)} {:error, changeset} -> IO.inspect(changeset.errors, label: "Upgrade config changeset errors") form = to_form(changeset) {:noreply, assign(socket, form_changeset: changeset, form: form)} end end def handle_event("search", _params, socket) do # Apply filters to search for upgrades upgrades = TerminalManagement.list_app_upgrade_configs() |> DaProductApp.Repo.preload(:package) {:noreply, assign(socket, upgrades: upgrades)} end def handle_event("reset", _params, socket) do # Reset filters and reload all upgrades upgrades = TerminalManagement.list_app_upgrade_configs() |> DaProductApp.Repo.preload(:package) {:noreply, assign(socket, filters: %{}, upgrades: upgrades)} end def handle_event("export", _params, socket) do # TODO: Implement export functionality {:noreply, socket} end def handle_event("delete", %{"id" => id}, socket) do id = String.to_integer(id) config = TerminalManagement.get_app_upgrade_config!(id) case TerminalManagement.delete_app_upgrade_config(config) do {:ok, _} -> upgrades = TerminalManagement.list_app_upgrade_configs() |> DaProductApp.Repo.preload(:package) {:noreply, assign(socket, upgrades: upgrades)} {:error, _changeset} -> {:noreply, socket} end end def handle_event("set_status_tab", %{"status" => status}, socket) do filters = Map.put(socket.assigns.filters, "status", status) upgrades = TerminalManagement.list_app_upgrade_configs() |> DaProductApp.Repo.preload(:package) {:noreply, assign(socket, filters: filters, upgrades: upgrades)} end def handle_event("push_parameters", %{"device_serials" => device_serials_str, "parameters" => parameters}, socket) do IO.puts("DEBUG: push_parameters handler called") IO.inspect(device_serials_str, label: "device_serials_str") IO.inspect(parameters, label: "parameters") device_serials = String.split(device_serials_str, [",", ";", " "], trim: true) IO.puts("DEBUG: About to call batch_send_parameters") IO.inspect(device_serials, label: "processed device_serials") case DaProductApp.TerminalManagement.ParameterPushService.batch_send_parameters(device_serials, parameters) do {:ok, result} -> IO.puts("DEBUG: Parameters pushed successfully") IO.inspect(result, label: "result") {:noreply, socket |> put_flash(:info, "Parameter push completed: #{result.successes} successful, #{result.failures} failed") |> assign(show_parameter_form: false)} {:error, reason} -> IO.puts("DEBUG: Parameter push failed") IO.inspect(reason, label: "error reason") {:noreply, put_flash(socket, :error, "Parameter push failed: #{reason}")} end end def handle_event("send_file_download", %{"device_serials" => device_serials_str, "download_params" => download_params}, socket) do IO.puts("DEBUG: send_file_download handler called") IO.inspect(device_serials_str, label: "device_serials_str") IO.inspect(download_params, label: "download_params") device_serials = String.split(device_serials_str, [",", ";", " "], trim: true) # Auto-generate request_id if not provided download_params = if download_params["request_id"] == "" or is_nil(download_params["request_id"]) do Map.put(download_params, "request_id", "download_#{System.unique_integer([:positive])}") else download_params end IO.puts("DEBUG: About to schedule batch file download") IO.inspect(device_serials, label: "processed device_serials") IO.inspect(download_params, label: "processed download_params") case DaProductApp.Workers.FileDownloadWorker.schedule_batch_file_download(device_serials, download_params) do {:ok, job} -> IO.puts("DEBUG: Job scheduled successfully") IO.inspect(job, label: "job") {:noreply, socket |> put_flash(:info, "File download job scheduled successfully for #{length(device_serials)} devices (Job ID: #{job.id})") |> assign(show_download_form: false)} {:error, reason} -> IO.puts("DEBUG: Job scheduling failed") IO.inspect(reason, label: "error reason") {:noreply, put_flash(socket, :error, "Failed to schedule file download: #{inspect(reason)}")} end end def handle_event("select_template", %{"template_id" => template_id}, socket) do template_id = String.to_integer(template_id) template = DaProductApp.ParameterManagement.get_parameter_template!(template_id) template_values = DaProductApp.ParameterManagement.list_template_values_by_template(template_id) {:noreply, assign(socket, selected_template: template, template_preview: template_values )} end def handle_event("check_template_compatibility", %{"template_id" => template_id, "device_serials" => device_serials}, socket) do template_id = String.to_integer(template_id) device_list = String.split(device_serials, [",", ";", " "], trim: true) compatibility_results = Enum.map(device_list, fn device_serial -> case DaProductApp.ParameterManagement.check_template_compatibility(template_id, device_serial) do {:ok, compatibility} -> %{device_serial: device_serial, compatible: true, info: compatibility} {:error, reason} -> %{device_serial: device_serial, compatible: false, reason: reason} end end) compatible_count = Enum.count(compatibility_results, & &1.compatible) compatibility_summary = %{ total: length(device_list), compatible: compatible_count, incompatible: length(device_list) - compatible_count, results: compatibility_results } {:noreply, assign(socket, device_compatibility: compatibility_summary)} end def handle_event("apply_template", %{"template_id" => template_id, "device_serials" => device_serials, "schedule_at" => schedule_at}, socket) do template_id = String.to_integer(template_id) device_list = String.split(device_serials, [",", ";", " "], trim: true) case schedule_at do "" -> # Immediate execution result = apply_template_to_devices(device_list, template_id) {:noreply, assign(socket, show_parameter_form: false, last_operation_result: result )} schedule_time -> # Scheduled execution case DateTime.from_iso8601(schedule_time) do {:ok, datetime, _} -> case DaProductApp.Workers.ParameterPushWorker.schedule_batch_template_application( device_list, template_id, %{}, schedule_at: datetime ) do {:ok, job} -> {:noreply, assign(socket, show_parameter_form: false, last_operation_result: %{ success: true, message: "Template application scheduled successfully", job_id: job.id, scheduled_at: datetime } )} {:error, reason} -> {:noreply, assign(socket, last_operation_result: %{success: false, error: reason} )} end {:error, _} -> {:noreply, assign(socket, last_operation_result: %{success: false, error: "Invalid schedule time format"} )} end end end def handle_event("push_custom_parameters", %{"device_serials" => device_serials, "custom_parameters" => custom_parameters}, socket) do device_list = String.split(device_serials, [",", ";", " "], trim: true) # Filter out empty parameters filtered_params = Enum.reject(custom_parameters, fn {_key, value} -> value == "" || is_nil(value) end) |> Enum.into(%{}) case DaProductApp.TerminalManagement.ParameterPushService.batch_send_parameters(device_list, filtered_params) do {:ok, result} -> {:noreply, assign(socket, show_parameter_form: false, last_operation_result: %{ success: true, message: "Custom parameters pushed: #{result.successes} successful, #{result.failures} failed" } )} {:error, reason} -> {:noreply, assign(socket, last_operation_result: %{success: false, error: reason} )} end end # Private functions defp normalize_target_devices(input) when is_binary(input) do # Try to decode as JSON array case Jason.decode(input) do {:ok, list} when is_list(list) -> Jason.encode!(list) _ -> # Convert CSV/semicolon/space separated to JSON array string input |> String.split([",", ";", " "], trim: true) |> Enum.reject(&(&1 == "")) |> Jason.encode!() end end defp apply_template_to_devices(device_serials, template_id) do results = Enum.map(device_serials, fn device_serial -> case DaProductApp.TerminalManagement.apply_parameter_template(device_serial, template_id, %{}) do {:ok, push_log} -> {device_serial, :ok, push_log.request_id} {:error, reason} -> {device_serial, :error, reason} end end) successes = Enum.count(results, fn {_, status, _} -> status == :ok end) failures = Enum.count(results, fn {_, status, _} -> status == :error end) %{ success: failures == 0, total: length(device_serials), successes: successes, failures: failures, results: results, message: "Template applied to #{successes}/#{length(device_serials)} devices" } end # Phase 2: Job Management Event Handlers def handle_event("show_job_management", _params, socket) do jobs = TerminalManagement.list_parameter_push_jobs() statistics = TerminalManagement.get_job_statistics() {:noreply, assign(socket, show_job_management: true, show_parameter_form: false, show_bulk_operations: false, show_device_groups: false, parameter_jobs: jobs, job_statistics: statistics )} end def handle_event("hide_job_management", _params, socket) do {:noreply, assign(socket, show_job_management: false)} end def handle_event("filter_jobs", params, socket) do jobs = TerminalManagement.list_parameter_push_jobs(params) {:noreply, assign(socket, parameter_jobs: jobs)} end def handle_event("cancel_job", %{"job_id" => job_id}, socket) do job_id = String.to_integer(job_id) case TerminalManagement.cancel_parameter_push_job(job_id) do {:ok, _updated_log} -> # Refresh job list jobs = TerminalManagement.list_parameter_push_jobs() statistics = TerminalManagement.get_job_statistics() {:noreply, assign(socket, parameter_jobs: jobs, job_statistics: statistics, last_operation_result: %{success: true, message: "Job cancelled successfully"} )} {:error, reason} -> {:noreply, assign(socket, last_operation_result: %{success: false, error: reason} )} end end def handle_event("retry_job", %{"job_id" => job_id}, socket) do job_id = String.to_integer(job_id) case TerminalManagement.retry_failed_parameter_push_job(job_id) do {:ok, _updated_log} -> # Refresh job list jobs = TerminalManagement.list_parameter_push_jobs() statistics = TerminalManagement.get_job_statistics() {:noreply, assign(socket, parameter_jobs: jobs, job_statistics: statistics, last_operation_result: %{success: true, message: "Job retried successfully"} )} {:error, reason} -> {:noreply, assign(socket, last_operation_result: %{success: false, error: reason} )} end end # Phase 2: Bulk Operations Event Handlers def handle_event("show_bulk_operations", _params, socket) do device_groups = TerminalManagement.list_device_groups() {:noreply, assign(socket, show_bulk_operations: true, show_parameter_form: false, show_job_management: false, show_device_groups: false, device_groups: device_groups )} end def handle_event("hide_bulk_operations", _params, socket) do {:noreply, assign(socket, show_bulk_operations: false)} end def handle_event("bulk_apply_template", %{"template_id" => template_id, "device_serials" => device_serials_str, "batch_size" => batch_size}, socket) do template_id = String.to_integer(template_id) batch_size = String.to_integer(batch_size) device_serials = String.split(device_serials_str, [",", ";", " "], trim: true) case TerminalManagement.bulk_apply_template_to_devices(device_serials, template_id, batch_size: batch_size) do result -> {:noreply, assign(socket, bulk_operation_result: result, last_operation_result: %{ success: true, message: "Bulk operation completed: #{result.successful}/#{result.total_devices} devices successful" } )} end end def handle_event("apply_template_to_group", %{"group_name" => group_name, "template_id" => template_id}, socket) do template_id = String.to_integer(template_id) case TerminalManagement.apply_template_to_group(group_name, template_id) do result -> {:noreply, assign(socket, bulk_operation_result: result, last_operation_result: %{ success: true, message: "Group operation completed: #{result.successful}/#{result.total_devices} devices successful" } )} end end # Phase 2: Device Groups Event Handlers def handle_event("show_device_groups", _params, socket) do device_groups = TerminalManagement.list_device_groups() {:noreply, assign(socket, show_device_groups: true, show_parameter_form: false, show_job_management: false, show_bulk_operations: false, device_groups: device_groups )} end def handle_event("hide_device_groups", _params, socket) do {:noreply, assign(socket, show_device_groups: false)} end def handle_event("select_group", %{"group_name" => group_name}, socket) do devices = TerminalManagement.get_devices_by_group(group_name) {:noreply, assign(socket, selected_group: %{name: group_name, devices: devices} )} end def handle_event("bulk_update_groups", %{"device_serials" => device_serials_str, "new_group" => new_group}, socket) do device_serials = String.split(device_serials_str, [",", ";", " "], trim: true) case TerminalManagement.bulk_update_device_groups(device_serials, new_group) do result -> # Refresh device groups device_groups = TerminalManagement.list_device_groups() {:noreply, assign(socket, device_groups: device_groups, bulk_operation_result: result, last_operation_result: %{ success: true, message: "Group update completed: #{result.successful}/#{result.total_devices} devices updated" } )} end end # Phase 2: Hide all modals (unified handler) @impl true def handle_event("hide_modal", _, socket) do {:noreply, assign(socket, show_job_management: false, show_bulk_operations: false, show_device_groups: false )} end # Specific modal hide handlers for better event management @impl true def handle_event("hide_job_management", _, socket) do {:noreply, assign(socket, show_job_management: false)} end @impl true def handle_event("hide_bulk_operations", _, socket) do {:noreply, assign(socket, show_bulk_operations: false)} end @impl true def handle_event("hide_device_groups", _, socket) do {:noreply, assign(socket, show_device_groups: false)} end # Catch-all for unhandled events to prevent FunctionClauseError @impl true def handle_event(event, params, socket) do IO.puts("DEBUG: Unhandled event received: #{event}") IO.inspect(params, label: "params") {:noreply, socket} end end