using System; using System.ComponentModel; using System.Diagnostics; using System.Threading; using System.Threading.Tasks; using System.Windows; using System.Windows.Media; using Terius.Automate.Agent.Models; using Terius.Automate.Agent.Services; using Drawing = System.Drawing; using WinForms = System.Windows.Forms; namespace Terius.Automate.Agent; public partial class MainWindow : Window { private readonly AgentConfigStore _configStore = new(); private readonly StartupManager _startupManager = new(); private readonly WinForms.NotifyIcon _trayIcon; private readonly WinForms.ToolStripMenuItem _trayStatusItem; private CancellationTokenSource? _loopCancellation; private AgentLoop? _agentLoop; private AgentConfig _config; private bool _allowExit; private bool _hasHiddenAfterConnect; public MainWindow() { InitializeComponent(); _config = _configStore.Load(); _trayStatusItem = new WinForms.ToolStripMenuItem("Desconectado") { Enabled = false }; _trayIcon = CreateTrayIcon(); MachineText.Text = Environment.MachineName; AgentNameTextBox.Text = string.IsNullOrWhiteSpace(_config.Name) ? $"Robot {Environment.MachineName}" : _config.Name; ServerUrlTextBox.Text = string.IsNullOrWhiteSpace(_config.ServerUrl) ? "https://automate.terius.cl" : _config.ServerUrl; StartWithWindowsCheckBox.IsChecked = _config.StartWithWindows; MinimizeToTrayCheckBox.IsChecked = _config.MinimizeToTray; SettingsServerText.Text = $"Servidor: {_config.ServerUrl}"; SettingsMachineText.Text = $"Equipo: {Environment.MachineName} · Agente v0.1.0 Build 7"; _startupManager.Apply(_config.StartWithWindows); AddActivity("Agente iniciado."); var backgroundStart = Array.Exists(Environment.GetCommandLineArgs(), argument => argument.Equals("--background", StringComparison.OrdinalIgnoreCase)); if (backgroundStart) { Loaded += (_, _) => HideToTray(false); _hasHiddenAfterConnect = true; } if (_config.IsRegistered) { Loaded += async (_, _) => await StartLoopAsync(); } } private WinForms.NotifyIcon CreateTrayIcon() { var resource = Application.GetResourceStream(new Uri("Assets/terius.ico", UriKind.Relative)); if (resource is null) { throw new InvalidOperationException("No se encontró el icono TERIUS."); } using var sourceIcon = new Drawing.Icon(resource.Stream); var menu = new WinForms.ContextMenuStrip(); var openItem = new WinForms.ToolStripMenuItem("Abrir TERIUS Robot Agent"); openItem.Click += (_, _) => Dispatcher.Invoke(ShowWindow); var exitItem = new WinForms.ToolStripMenuItem("Salir"); exitItem.Click += (_, _) => Dispatcher.Invoke(ExitApplication); menu.Items.Add(openItem); menu.Items.Add(_trayStatusItem); menu.Items.Add(new WinForms.ToolStripSeparator()); menu.Items.Add(exitItem); var tray = new WinForms.NotifyIcon { Icon = (Drawing.Icon)sourceIcon.Clone(), Text = "TERIUS Robot Agent - Desconectado", ContextMenuStrip = menu, Visible = true, }; tray.DoubleClick += (_, _) => Dispatcher.Invoke(ShowWindow); return tray; } private async void ConnectButton_Click(object sender, RoutedEventArgs e) { try { SetBusy(true); var url = ServerUrlTextBox.Text.Trim().TrimEnd('/'); var name = AgentNameTextBox.Text.Trim(); if (!Uri.TryCreate(url, UriKind.Absolute, out var serverUri) || serverUri.Scheme != Uri.UriSchemeHttps) { throw new InvalidOperationException("Ingrese una URL HTTPS válida."); } if (string.IsNullOrWhiteSpace(name)) { throw new InvalidOperationException("Ingrese el nombre del robot."); } _config.ServerUrl = url; _config.Name = name; _config.AgentId = string.IsNullOrWhiteSpace(_config.AgentId) ? Guid.NewGuid().ToString() : _config.AgentId; var api = new ApiClient(_config); if (!_config.IsRegistered) { var token = RegistrationTokenBox.Password.Trim(); if (string.IsNullOrWhiteSpace(token)) { throw new InvalidOperationException("Ingrese el código de registro generado en el panel web."); } var registration = await api.RegisterAsync(token, CancellationToken.None); _config.AgentSecret = registration.AgentSecret; RegistrationTokenBox.Clear(); AddActivity("Equipo registrado correctamente."); } _configStore.Save(_config); SettingsServerText.Text = $"Servidor: {_config.ServerUrl}"; await StartLoopAsync(); } catch (Exception exception) { AddActivity($"ERROR: {exception.Message}"); SetStatus(false, "Error de conexión"); MessageBox.Show(exception.Message, "TERIUS Robot Agent", MessageBoxButton.OK, MessageBoxImage.Warning); } finally { SetBusy(false); } } private async Task StartLoopAsync() { if (_loopCancellation is not null || !_config.IsRegistered) { return; } _loopCancellation = new CancellationTokenSource(); _agentLoop = new AgentLoop(new ApiClient(_config), new JobExecutor()); _agentLoop.Activity += message => Dispatcher.Invoke(() => AddActivity(message)); _agentLoop.ConnectionChanged += online => Dispatcher.Invoke(() => SetStatus(online, online ? "Conectado" : "Reconectando")); _agentLoop.JobCompleted += job => Dispatcher.Invoke(() => AddExecution(job)); StopButton.IsEnabled = true; ServerUrlTextBox.IsEnabled = false; AgentNameTextBox.IsEnabled = false; RegistrationTokenBox.IsEnabled = false; ConnectButton.IsEnabled = false; AddActivity("Conectando con el servidor…"); try { await _agentLoop.RunAsync(_loopCancellation.Token); } catch (OperationCanceledException) { AddActivity("Conexión detenida."); } finally { _loopCancellation?.Dispose(); _loopCancellation = null; SetStatus(false, "Desconectado"); StopButton.IsEnabled = false; ServerUrlTextBox.IsEnabled = true; AgentNameTextBox.IsEnabled = true; RegistrationTokenBox.IsEnabled = true; ConnectButton.IsEnabled = true; } } private void HomeNavButton_Click(object sender, RoutedEventArgs e) => ShowView("home"); private void ExecutionsNavButton_Click(object sender, RoutedEventArgs e) => ShowView("executions"); private void SettingsNavButton_Click(object sender, RoutedEventArgs e) => ShowView("settings"); private void ShowView(string view) { HomeView.Visibility = view == "home" ? Visibility.Visible : Visibility.Collapsed; ExecutionsView.Visibility = view == "executions" ? Visibility.Visible : Visibility.Collapsed; SettingsView.Visibility = view == "settings" ? Visibility.Visible : Visibility.Collapsed; HomeNavButton.Style = (Style)FindResource(view == "home" ? "ActiveNavButton" : "NavButton"); ExecutionsNavButton.Style = (Style)FindResource(view == "executions" ? "ActiveNavButton" : "NavButton"); SettingsNavButton.Style = (Style)FindResource(view == "settings" ? "ActiveNavButton" : "NavButton"); } private void SaveSettingsButton_Click(object sender, RoutedEventArgs e) { _config.StartWithWindows = StartWithWindowsCheckBox.IsChecked == true; _config.MinimizeToTray = MinimizeToTrayCheckBox.IsChecked == true; _configStore.Save(_config); _startupManager.Apply(_config.StartWithWindows); AddActivity("Configuración guardada."); MessageBox.Show("Configuración guardada correctamente.", "TERIUS Robot Agent", MessageBoxButton.OK, MessageBoxImage.Information); } private void OpenLogsButton_Click(object sender, RoutedEventArgs e) { System.IO.Directory.CreateDirectory(LocalLog.DirectoryPath); Process.Start(new ProcessStartInfo(LocalLog.DirectoryPath) { UseShellExecute = true }); } private void StopButton_Click(object sender, RoutedEventArgs e) => _loopCancellation?.Cancel(); private void Window_StateChanged(object? sender, EventArgs e) { if (WindowState == WindowState.Minimized && _config.MinimizeToTray) { HideToTray(false); } } private void Window_Closing(object? sender, CancelEventArgs e) { if (!_allowExit && _config.MinimizeToTray) { e.Cancel = true; HideToTray(true); } } private void HideToTray(bool notify) { Hide(); if (notify) { _trayIcon.ShowBalloonTip(2500, "TERIUS Robot Agent", "El robot continúa activo junto al reloj.", WinForms.ToolTipIcon.Info); } } private void ShowWindow() { Show(); WindowState = WindowState.Normal; Activate(); } private void ExitApplication() { _allowExit = true; _loopCancellation?.Cancel(); _trayIcon.Visible = false; _trayIcon.Dispose(); Close(); Application.Current.Shutdown(); } private void SetBusy(bool busy) => ConnectButton.IsEnabled = !busy && _loopCancellation is null; private async void SetStatus(bool online, string text) { StatusText.Text = text; StatusDot.Fill = new SolidColorBrush((Color)ColorConverter.ConvertFromString(online ? "#55D6BE" : "#93A1BD")); _trayStatusItem.Text = text; _trayIcon.Text = $"TERIUS Robot Agent - {text}"; if (online && _config.MinimizeToTray && !_hasHiddenAfterConnect) { _hasHiddenAfterConnect = true; await Task.Delay(1200); if (IsVisible) { HideToTray(true); } } } private void AddActivity(string message) { ActivityList.Items.Insert(0, $"{DateTime.Now:HH:mm:ss} {message}"); while (ActivityList.Items.Count > 200) { ActivityList.Items.RemoveAt(ActivityList.Items.Count - 1); } } private void AddExecution(JobExecutionSummary job) { var status = job.Success ? "Completada" : "Fallida"; ExecutionList.Items.Insert(0, $"#{job.Id,-6} {job.AutomationName,-34} {status,-14} {job.FinishedAt:HH:mm:ss}"); } }