From 5b4c567b71edd9f4d8ddaef68c4b7ab2e2c30f93 Mon Sep 17 00:00:00 2001 From: Glauber x86 Date: Thu, 11 Dec 2025 14:29:39 -0300 Subject: [PATCH 1/9] =?UTF-8?q?[Wip](Tray):=20Console=20e=20conex=C3=A3o?= =?UTF-8?q?=20Named=20Pipes.=20Erro=20de=20I/O?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- AssetManager.service/README.md | 1 - AssetManager.service/src/Worker.cs | 8 +- AssetManager.tray/AssetManager.tray.csproj | 4 + AssetManager.tray/AssetManager.tray.sln | 24 ++++ AssetManager.tray/TrayForm.cs | 67 ---------- .../src/DebugConsole.Designer.cs | 51 +++++++ AssetManager.tray/src/DebugConsole.cs | 24 ++++ AssetManager.tray/src/DebugConsole.resx | 120 +++++++++++++++++ AssetManager.tray/{ => src}/Program.cs | 0 AssetManager.tray/src/TrayForm.cs | 126 ++++++++++++++++++ AssetManager.tray/src/TrayForm.resx | 120 +++++++++++++++++ 11 files changed, 473 insertions(+), 72 deletions(-) delete mode 100644 AssetManager.service/README.md create mode 100644 AssetManager.tray/AssetManager.tray.sln delete mode 100644 AssetManager.tray/TrayForm.cs create mode 100644 AssetManager.tray/src/DebugConsole.Designer.cs create mode 100644 AssetManager.tray/src/DebugConsole.cs create mode 100644 AssetManager.tray/src/DebugConsole.resx rename AssetManager.tray/{ => src}/Program.cs (100%) create mode 100644 AssetManager.tray/src/TrayForm.cs create mode 100644 AssetManager.tray/src/TrayForm.resx diff --git a/AssetManager.service/README.md b/AssetManager.service/README.md deleted file mode 100644 index 12fa51b..0000000 --- a/AssetManager.service/README.md +++ /dev/null @@ -1 +0,0 @@ -# AssetManager \ No newline at end of file diff --git a/AssetManager.service/src/Worker.cs b/AssetManager.service/src/Worker.cs index aeb043f..ebd2b51 100644 --- a/AssetManager.service/src/Worker.cs +++ b/AssetManager.service/src/Worker.cs @@ -4,6 +4,7 @@ // O tray faz (vai fazer) POST para o backend. // TODO: Melhorar coleta, incluir CPU model, RAM total, tráfego de rede. // TODO: Implementar reconexão do pipe e manejo de falhas de envio. +// TODO: Mudar a lógica de cálculo e outras estatiticas para o tray (ex: disk usage / cpu model). using Microsoft.Extensions.Hosting; using Microsoft.Extensions.Logging; @@ -83,7 +84,7 @@ private async Task SendMetricsAsync(CancellationToken stoppingToken) ram_info = new { - usage_percent = _mem.GetMemoryUsage() // TODO: mostrar total em GB + usage_percent = _mem.GetMemoryUsage() // TODO: mostrar total em GB (talvez pegar o total no tray diretamente) }, storage_info = new @@ -116,10 +117,9 @@ private async Task SendMetricsAsync(CancellationToken stoppingToken) return; } - using var writer = new StreamWriter(pipeClient, Encoding.UTF8, leaveOpen: true) { AutoFlush = true }; + using var writer = new StreamWriter(pipeClient, Encoding.UTF8) { AutoFlush = true }; await writer.WriteLineAsync(json); - _logger.LogInformation("Metricas enviadas via named pipe."); } catch (OperationCanceledException) { @@ -127,7 +127,7 @@ private async Task SendMetricsAsync(CancellationToken stoppingToken) } catch (IOException ex) { - _logger.LogError(ex, "Erro de E/S ao enviar metricas via named pipe."); + _logger.LogError(ex, "Erro de I/O ao enviar metricas via named pipe."); } catch (Exception ex) { diff --git a/AssetManager.tray/AssetManager.tray.csproj b/AssetManager.tray/AssetManager.tray.csproj index e7df544..b43835a 100644 --- a/AssetManager.tray/AssetManager.tray.csproj +++ b/AssetManager.tray/AssetManager.tray.csproj @@ -10,4 +10,8 @@ true + + + + \ No newline at end of file diff --git a/AssetManager.tray/AssetManager.tray.sln b/AssetManager.tray/AssetManager.tray.sln new file mode 100644 index 0000000..e8d41ec --- /dev/null +++ b/AssetManager.tray/AssetManager.tray.sln @@ -0,0 +1,24 @@ +Microsoft Visual Studio Solution File, Format Version 12.00 +# Visual Studio Version 17 +VisualStudioVersion = 17.5.2.0 +MinimumVisualStudioVersion = 10.0.40219.1 +Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "AssetManager.tray", "AssetManager.tray.csproj", "{6CB97AFA-98C8-08E3-26D7-7B13F7EF296A}" +EndProject +Global + GlobalSection(SolutionConfigurationPlatforms) = preSolution + Debug|Any CPU = Debug|Any CPU + Release|Any CPU = Release|Any CPU + EndGlobalSection + GlobalSection(ProjectConfigurationPlatforms) = postSolution + {6CB97AFA-98C8-08E3-26D7-7B13F7EF296A}.Debug|Any CPU.ActiveCfg = Debug|Any CPU + {6CB97AFA-98C8-08E3-26D7-7B13F7EF296A}.Debug|Any CPU.Build.0 = Debug|Any CPU + {6CB97AFA-98C8-08E3-26D7-7B13F7EF296A}.Release|Any CPU.ActiveCfg = Release|Any CPU + {6CB97AFA-98C8-08E3-26D7-7B13F7EF296A}.Release|Any CPU.Build.0 = Release|Any CPU + EndGlobalSection + GlobalSection(SolutionProperties) = preSolution + HideSolutionNode = FALSE + EndGlobalSection + GlobalSection(ExtensibilityGlobals) = postSolution + SolutionGuid = {D9664C6F-572A-4672-9D20-14731F5C2FB9} + EndGlobalSection +EndGlobal diff --git a/AssetManager.tray/TrayForm.cs b/AssetManager.tray/TrayForm.cs deleted file mode 100644 index 790fd44..0000000 --- a/AssetManager.tray/TrayForm.cs +++ /dev/null @@ -1,67 +0,0 @@ -using System; -using System.Windows.Forms; -using System.ServiceProcess; - -namespace AssetManager.Tray -{ - public class TrayForm : Form - { - private readonly NotifyIcon trayIcon; - private readonly ContextMenuStrip trayMenu; - - private void EnsureServiceRunning() - { - const string serviceName = "AssetManager"; - - try - { - using var sc = new ServiceController(serviceName); - - if (sc.Status == ServiceControllerStatus.Stopped || - sc.Status == ServiceControllerStatus.StopPending) - { - sc.Start(); - sc.WaitForStatus(ServiceControllerStatus.Running, TimeSpan.FromSeconds(30)); - } - } - catch (Exception ex) - { - MessageBox.Show($"Erro ao iniciar serviço: {ex.Message}"); - } - } - - public TrayForm() - { - // Ocultar a janela - this.WindowState = FormWindowState.Minimized; - this.ShowInTaskbar = false; - this.Visible = false; - - // Menu - trayMenu = new ContextMenuStrip(); - trayMenu.Items.Add("Enviar agora", null, OnSendNow); - trayMenu.Items.Add("Sair", null, OnExit); - - // Icon - trayIcon = new NotifyIcon - { - Icon = SystemIcons.Information, // TODO: trocar icone - Visible = true, - Text = "Asset Manager Agent", - ContextMenuStrip = trayMenu - }; - EnsureServiceRunning(); - } - - private void OnSendNow(object? sender, EventArgs e) - { - MessageBox.Show("Envio manual ainda não implementado."); - } - - private void OnExit(object? sender, EventArgs e) - { - trayIcon.Visible = false; - Application.Exit(); - } - } -} diff --git a/AssetManager.tray/src/DebugConsole.Designer.cs b/AssetManager.tray/src/DebugConsole.Designer.cs new file mode 100644 index 0000000..9860c06 --- /dev/null +++ b/AssetManager.tray/src/DebugConsole.Designer.cs @@ -0,0 +1,51 @@ +namespace AssetManager.Tray +{ + public partial class DebugConsole : Form + { + /// + /// Required designer variable. + /// + private System.ComponentModel.IContainer components = null; + + private TextBox txtLog; + + /// + /// Clean up any resources being used. + /// + /// true if managed resources should be disposed; otherwise, false. + protected override void Dispose(bool disposing) + { + if (disposing && (components != null)) + { + components.Dispose(); + } + base.Dispose(disposing); + } + + #region Windows Form Designer generated code + + /// + /// Required method for Designer support - do not modify + /// the contents of this method with the code editor. + /// + private void InitializeComponent() + { + txtLog = new TextBox + { + Multiline = true, + Dock = DockStyle.Fill, + ScrollBars = ScrollBars.Vertical, + ReadOnly = true, + Font = new Font("Consolas", 10) + }; + + Controls.Add(txtLog); + + Text = "AssetManager Debug Console"; + Width = 800; + Height = 600; + } + + #endregion + } +} \ No newline at end of file diff --git a/AssetManager.tray/src/DebugConsole.cs b/AssetManager.tray/src/DebugConsole.cs new file mode 100644 index 0000000..8437735 --- /dev/null +++ b/AssetManager.tray/src/DebugConsole.cs @@ -0,0 +1,24 @@ +using System; +using System.Collections.Generic; +using System.ComponentModel; +using System.Data; +using System.Drawing; +using System.Text; +using System.Windows.Forms; + +namespace AssetManager.Tray +{ + public partial class DebugConsole : Form + { + public DebugConsole() + { + InitializeComponent(); + txtLog.ReadOnly = true; + } + + public void WriteLine(string text) + { + txtLog.AppendText($"{text}\r\n"); + } + } +} diff --git a/AssetManager.tray/src/DebugConsole.resx b/AssetManager.tray/src/DebugConsole.resx new file mode 100644 index 0000000..1af7de1 --- /dev/null +++ b/AssetManager.tray/src/DebugConsole.resx @@ -0,0 +1,120 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + text/microsoft-resx + + + 2.0 + + + System.Resources.ResXResourceReader, System.Windows.Forms, Version=4.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089 + + + System.Resources.ResXResourceWriter, System.Windows.Forms, Version=4.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089 + + \ No newline at end of file diff --git a/AssetManager.tray/Program.cs b/AssetManager.tray/src/Program.cs similarity index 100% rename from AssetManager.tray/Program.cs rename to AssetManager.tray/src/Program.cs diff --git a/AssetManager.tray/src/TrayForm.cs b/AssetManager.tray/src/TrayForm.cs new file mode 100644 index 0000000..0e7845e --- /dev/null +++ b/AssetManager.tray/src/TrayForm.cs @@ -0,0 +1,126 @@ +using System; +using System.Windows.Forms; +using System.ServiceProcess; +using System.IO.Pipes; + +namespace AssetManager.Tray +{ + public class TrayForm : Form + { + private readonly NotifyIcon trayIcon; + private readonly ContextMenuStrip trayMenu; + private const string PIPE_NAME = "asset-monitor-pipe"; + private DebugConsole? debugConsole; + private readonly List logBuffer = new(); + + private void Log(string msg) + { + string line = $"[{DateTime.Now:HH:mm:ss}] {msg}"; + logBuffer.Add(line); + + debugConsole?.WriteLine(line); + } + + private void OpenConsole() + { + if (debugConsole == null || debugConsole.IsDisposed) + debugConsole = new DebugConsole(); + + debugConsole.Show(); + debugConsole.BringToFront(); + + foreach (var line in logBuffer) + debugConsole.WriteLine(line); + } + + private async Task StartPipeServer() + { + while (true) + { + try + { + using var pipeServer = new NamedPipeServerStream( + PIPE_NAME, + PipeDirection.In, + 1, + PipeTransmissionMode.Byte, + PipeOptions.Asynchronous); + + await pipeServer.WaitForConnectionAsync(); + + Log("Conexão estabelecida com o serviço."); + + using var reader = new StreamReader(pipeServer); + string? json = await reader.ReadLineAsync(); + + if (!string.IsNullOrWhiteSpace(json)) + { + Log($"[{DateTime.Now:HH:mm:ss}] Dados recebidos:"); + Log(json); + Log(new string('-', 40)); + } + } + catch (Exception ex) + { + debugConsole?.WriteLine($"Erro no pipe: {ex.Message}"); + } + } + } + private void EnsureServiceRunning() + { + const string serviceName = "AssetManager"; + + try + { + using var sc = new ServiceController(serviceName); + + if (sc.Status == ServiceControllerStatus.Stopped || + sc.Status == ServiceControllerStatus.StopPending) + { + sc.Start(); + sc.WaitForStatus(ServiceControllerStatus.Running, TimeSpan.FromSeconds(30)); + } + } + catch (Exception ex) + { + MessageBox.Show($"Erro ao iniciar serviço: {ex.Message}"); + } + } + + public TrayForm() + { + // Ocultar a janela + this.WindowState = FormWindowState.Minimized; + this.ShowInTaskbar = false; + this.Visible = false; + + // Menu + trayMenu = new ContextMenuStrip(); + trayMenu.Items.Add("Abrir Console", null, (_, __) => OpenConsole()); + trayMenu.Items.Add("Enviar agora", null, OnSendNow); + trayMenu.Items.Add("Sair", null, OnExit); + + // Icon + trayIcon = new NotifyIcon + { + Icon = SystemIcons.Information, // TODO: trocar icone + Visible = true, + Text = "Asset Manager Agent", + ContextMenuStrip = trayMenu + }; + EnsureServiceRunning(); + _ = Task.Run(StartPipeServer); + } + + private void OnSendNow(object? sender, EventArgs e) + { + MessageBox.Show("Envio manual ainda não implementado."); + } + + private void OnExit(object? sender, EventArgs e) + { + trayIcon.Visible = false; + Application.Exit(); + } + } +} diff --git a/AssetManager.tray/src/TrayForm.resx b/AssetManager.tray/src/TrayForm.resx new file mode 100644 index 0000000..1af7de1 --- /dev/null +++ b/AssetManager.tray/src/TrayForm.resx @@ -0,0 +1,120 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + text/microsoft-resx + + + 2.0 + + + System.Resources.ResXResourceReader, System.Windows.Forms, Version=4.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089 + + + System.Resources.ResXResourceWriter, System.Windows.Forms, Version=4.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089 + + \ No newline at end of file From d1cf6dcd720b0dd2644503af3e25488f9db94c8b Mon Sep 17 00:00:00 2001 From: Glauber x86 Date: Fri, 12 Dec 2025 10:38:38 -0300 Subject: [PATCH 2/9] =?UTF-8?q?[Feat](Worker):=20Agora=20age=20como=20Serv?= =?UTF-8?q?er=20inv=C3=A9s=20de=20Client?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- AssetManager.service/debug.ps1 | 29 +++ AssetManager.service/src/Program.cs | 2 +- AssetManager.service/src/Worker.cs | 176 ++++++++++-------- .../src/DebugConsole.Designer.cs | 51 ----- AssetManager.tray/src/DebugConsole.cs | 24 --- AssetManager.tray/src/TrayForm.cs | 80 -------- 6 files changed, 126 insertions(+), 236 deletions(-) create mode 100644 AssetManager.service/debug.ps1 delete mode 100644 AssetManager.tray/src/DebugConsole.Designer.cs delete mode 100644 AssetManager.tray/src/DebugConsole.cs diff --git a/AssetManager.service/debug.ps1 b/AssetManager.service/debug.ps1 new file mode 100644 index 0000000..68a63d5 --- /dev/null +++ b/AssetManager.service/debug.ps1 @@ -0,0 +1,29 @@ +param( + [string]$PipeName = "asset-monitor-pipe", + [int]$IntervalSeconds = 2 +) + +while ($true) { + Clear-Host + Write-Host "AssetManager Debug ($(Get-Date))" + Write-Host "-------------------------------" + + try { + $pipe = New-Object System.IO.Pipes.NamedPipeClientStream(".", $PipeName, [System.IO.Pipes.PipeDirection]::In) + $pipe.Connect(3000) + + $reader = New-Object System.IO.StreamReader($pipe) + $json = $reader.ReadLine() + + $reader.Close() + $pipe.Close() + + $obj = $json | ConvertFrom-Json + $obj | Format-List + } + catch { + Write-Host "Erro ao ler pipe: $_" -ForegroundColor Red + } + + Start-Sleep -Seconds $IntervalSeconds +} diff --git a/AssetManager.service/src/Program.cs b/AssetManager.service/src/Program.cs index 1c0d141..6306cf3 100644 --- a/AssetManager.service/src/Program.cs +++ b/AssetManager.service/src/Program.cs @@ -7,7 +7,7 @@ builder.Services.AddWindowsService(options => { - options.ServiceName = "AssetManager"; +options.ServiceName = "AssetManager"; }); builder.Services.AddSingleton(); diff --git a/AssetManager.service/src/Worker.cs b/AssetManager.service/src/Worker.cs index ebd2b51..69d3cb5 100644 --- a/AssetManager.service/src/Worker.cs +++ b/AssetManager.service/src/Worker.cs @@ -6,12 +6,15 @@ // TODO: Implementar reconexão do pipe e manejo de falhas de envio. // TODO: Mudar a lógica de cálculo e outras estatiticas para o tray (ex: disk usage / cpu model). +using System; +using System.IO; +using System.IO.Pipes; +using System.Text; +using System.Text.Json; +using System.Threading; +using System.Threading.Tasks; using Microsoft.Extensions.Hosting; using Microsoft.Extensions.Logging; -using System.Text.Json; -using System.Text; -using System.Runtime.InteropServices; -using System.IO.Pipes; public class Worker : BackgroundService { @@ -22,17 +25,18 @@ public class Worker : BackgroundService private readonly INetworkMonitor _net; private const string PIPE_NAME = "asset-monitor-pipe"; - private const int PIPE_CONNECT_TIMEOUT_MS = 5_000; + private const int COLLECT_INTERVAL_SECONDS = 5; - private const int TIMER = 2_000; - private readonly int timerSeconds = TIMER / 1000; + // Snapshot atual (thread-safe) + private readonly object _lock = new(); + private string _latestJson = "{}"; public Worker( - ILogger logger, - ICpuMonitor cpu, - IMemoryMonitor mem, - IDiskMonitor disk, - INetworkMonitor net) + ILogger logger, + ICpuMonitor cpu, + IMemoryMonitor mem, + IDiskMonitor disk, + INetworkMonitor net) { _logger = logger; _cpu = cpu; @@ -40,98 +44,110 @@ public Worker( _disk = disk; _net = net; } - protected override async Task ExecuteAsync(CancellationToken stoppingToken) { - _logger.LogInformation($"Monitor iniciado. Intervalo: {timerSeconds} segundos"); + _logger.LogInformation("AssetManager Worker iniciado."); - while (!stoppingToken.IsCancellationRequested) - { - await SendMetricsAsync(stoppingToken); // TODO: Melhorar - await Task.Delay(TIMER, stoppingToken); - } - } + // Loop de coleta + _ = Task.Run(() => CollectLoop(stoppingToken), stoppingToken); - private async Task SendMetricsAsync(CancellationToken stoppingToken) + // Loop do servidor de pipe + await PipeServerLoop(stoppingToken); + } + /// Coleta e mantém o último snapshot + private async Task CollectLoop(CancellationToken token) { var (diskUsedGb, diskTotalGb) = _disk.GetDiskUsage(); + var diskUsagePercent = diskTotalGb > 0 + ? Math.Round((diskUsedGb / diskTotalGb) * 100, 2) + : 0; var (ip, mac) = _net.GetNetworkInfo(); // TODO: mudar para ID real var assetId = Environment.MachineName; var hostname = Environment.MachineName; - var diskUsagePercent = diskTotalGb > 0 - ? Math.Round((diskUsedGb / diskTotalGb) * 100, 2) - : 0; - - var payload = new + while (!token.IsCancellationRequested) { - asset_id = assetId, // TODO: mudar para ID real - hostname = hostname, - cpu_usage = _cpu.GetCpuUsage(), - memory_usage = _mem.GetMemoryUsage(), - disk_usage = diskUsagePercent, - network_in = 0, // TODO: implementar - network_out = 0, - uptime = (long)Environment.TickCount64 / 1000, - - cpu_info = new + try { - model = "unknown", // TODO: mostrar modelo - cores = Environment.ProcessorCount - }, + var payload = new + { + asset_id = assetId, // TODO: mudar para ID real + hostname = Environment.MachineName, + // TODO: mostrar model CPU + cpu_usage = _cpu.GetCpuUsage(), + // TODO: mostrar RAM total + memory_usage = _mem.GetMemoryUsage(), + disk = diskUsagePercent, + ip_addr = ip, + mac_addr = mac, + os = Environment.OSVersion.ToString(), + timestamp = DateTime.UtcNow, + // TODO: adicionar versionamento do payload + version = "v1" + }; + + string json = JsonSerializer.Serialize(payload, new JsonSerializerOptions + { + WriteIndented = false + }); - ram_info = new + lock (_lock) + { + _latestJson = json; + } + } + catch (Exception ex) { - usage_percent = _mem.GetMemoryUsage() // TODO: mostrar total em GB (talvez pegar o total no tray diretamente) - }, + _logger.LogError(ex, "Erro ao coletar métricas."); + } - storage_info = new + await Task.Delay(TimeSpan.FromSeconds(COLLECT_INTERVAL_SECONDS), token); + } + } + private async Task PipeServerLoop(CancellationToken token) + { + while (!token.IsCancellationRequested) + { + try { - disks = new[] - { - new { name = "C:", used_gb = diskUsedGb, total_gb = diskTotalGb } - } - }, + using var pipeServer = new NamedPipeServerStream( + PIPE_NAME, + PipeDirection.Out, + 1, + PipeTransmissionMode.Byte, + PipeOptions.Asynchronous); - ip_address = ip, - mac_address = mac, - os_name = RuntimeInformation.OSDescription, - os_version = Environment.OSVersion.Version.ToString() - }; + _logger.LogDebug("Aguardando conexão no named pipe..."); - var json = JsonSerializer.Serialize(payload); + await pipeServer.WaitForConnectionAsync(token); - try - { - using var pipeClient = new NamedPipeClientStream(".", PIPE_NAME, PipeDirection.Out); - using var cts = CancellationTokenSource.CreateLinkedTokenSource(stoppingToken); - cts.CancelAfter(PIPE_CONNECT_TIMEOUT_MS); + string snapshot; + lock (_lock) + { + snapshot = _latestJson; + } + + using var writer = new StreamWriter(pipeServer, Encoding.UTF8) + { + AutoFlush = true + }; - await pipeClient.ConnectAsync(cts.Token); + // Envia uma única linha (snapshot atual) + await writer.WriteLineAsync(snapshot); - if (!pipeClient.IsConnected) + _logger.LogDebug("Snapshot enviado para cliente do pipe."); + } + catch (OperationCanceledException) { - _logger.LogWarning("Nao foi possivel conectar ao named pipe {PipeName}.", PIPE_NAME); - return; + // shutdown normal + } + catch (Exception ex) + { + _logger.LogError(ex, "Erro no servidor do named pipe."); + await Task.Delay(1000, token); // backoff leve } - - using var writer = new StreamWriter(pipeClient, Encoding.UTF8) { AutoFlush = true }; - await writer.WriteLineAsync(json); - - } - catch (OperationCanceledException) - { - _logger.LogWarning("Timeout ao conectar ao named pipe {PipeName}.", PIPE_NAME); - } - catch (IOException ex) - { - _logger.LogError(ex, "Erro de I/O ao enviar metricas via named pipe."); - } - catch (Exception ex) - { - _logger.LogError(ex, "Erro ao enviar metricas via named pipe."); } } -} +} \ No newline at end of file diff --git a/AssetManager.tray/src/DebugConsole.Designer.cs b/AssetManager.tray/src/DebugConsole.Designer.cs deleted file mode 100644 index 9860c06..0000000 --- a/AssetManager.tray/src/DebugConsole.Designer.cs +++ /dev/null @@ -1,51 +0,0 @@ -namespace AssetManager.Tray -{ - public partial class DebugConsole : Form - { - /// - /// Required designer variable. - /// - private System.ComponentModel.IContainer components = null; - - private TextBox txtLog; - - /// - /// Clean up any resources being used. - /// - /// true if managed resources should be disposed; otherwise, false. - protected override void Dispose(bool disposing) - { - if (disposing && (components != null)) - { - components.Dispose(); - } - base.Dispose(disposing); - } - - #region Windows Form Designer generated code - - /// - /// Required method for Designer support - do not modify - /// the contents of this method with the code editor. - /// - private void InitializeComponent() - { - txtLog = new TextBox - { - Multiline = true, - Dock = DockStyle.Fill, - ScrollBars = ScrollBars.Vertical, - ReadOnly = true, - Font = new Font("Consolas", 10) - }; - - Controls.Add(txtLog); - - Text = "AssetManager Debug Console"; - Width = 800; - Height = 600; - } - - #endregion - } -} \ No newline at end of file diff --git a/AssetManager.tray/src/DebugConsole.cs b/AssetManager.tray/src/DebugConsole.cs deleted file mode 100644 index 8437735..0000000 --- a/AssetManager.tray/src/DebugConsole.cs +++ /dev/null @@ -1,24 +0,0 @@ -using System; -using System.Collections.Generic; -using System.ComponentModel; -using System.Data; -using System.Drawing; -using System.Text; -using System.Windows.Forms; - -namespace AssetManager.Tray -{ - public partial class DebugConsole : Form - { - public DebugConsole() - { - InitializeComponent(); - txtLog.ReadOnly = true; - } - - public void WriteLine(string text) - { - txtLog.AppendText($"{text}\r\n"); - } - } -} diff --git a/AssetManager.tray/src/TrayForm.cs b/AssetManager.tray/src/TrayForm.cs index 0e7845e..bea8be3 100644 --- a/AssetManager.tray/src/TrayForm.cs +++ b/AssetManager.tray/src/TrayForm.cs @@ -9,83 +9,6 @@ public class TrayForm : Form { private readonly NotifyIcon trayIcon; private readonly ContextMenuStrip trayMenu; - private const string PIPE_NAME = "asset-monitor-pipe"; - private DebugConsole? debugConsole; - private readonly List logBuffer = new(); - - private void Log(string msg) - { - string line = $"[{DateTime.Now:HH:mm:ss}] {msg}"; - logBuffer.Add(line); - - debugConsole?.WriteLine(line); - } - - private void OpenConsole() - { - if (debugConsole == null || debugConsole.IsDisposed) - debugConsole = new DebugConsole(); - - debugConsole.Show(); - debugConsole.BringToFront(); - - foreach (var line in logBuffer) - debugConsole.WriteLine(line); - } - - private async Task StartPipeServer() - { - while (true) - { - try - { - using var pipeServer = new NamedPipeServerStream( - PIPE_NAME, - PipeDirection.In, - 1, - PipeTransmissionMode.Byte, - PipeOptions.Asynchronous); - - await pipeServer.WaitForConnectionAsync(); - - Log("Conexão estabelecida com o serviço."); - - using var reader = new StreamReader(pipeServer); - string? json = await reader.ReadLineAsync(); - - if (!string.IsNullOrWhiteSpace(json)) - { - Log($"[{DateTime.Now:HH:mm:ss}] Dados recebidos:"); - Log(json); - Log(new string('-', 40)); - } - } - catch (Exception ex) - { - debugConsole?.WriteLine($"Erro no pipe: {ex.Message}"); - } - } - } - private void EnsureServiceRunning() - { - const string serviceName = "AssetManager"; - - try - { - using var sc = new ServiceController(serviceName); - - if (sc.Status == ServiceControllerStatus.Stopped || - sc.Status == ServiceControllerStatus.StopPending) - { - sc.Start(); - sc.WaitForStatus(ServiceControllerStatus.Running, TimeSpan.FromSeconds(30)); - } - } - catch (Exception ex) - { - MessageBox.Show($"Erro ao iniciar serviço: {ex.Message}"); - } - } public TrayForm() { @@ -96,7 +19,6 @@ public TrayForm() // Menu trayMenu = new ContextMenuStrip(); - trayMenu.Items.Add("Abrir Console", null, (_, __) => OpenConsole()); trayMenu.Items.Add("Enviar agora", null, OnSendNow); trayMenu.Items.Add("Sair", null, OnExit); @@ -108,8 +30,6 @@ public TrayForm() Text = "Asset Manager Agent", ContextMenuStrip = trayMenu }; - EnsureServiceRunning(); - _ = Task.Run(StartPipeServer); } private void OnSendNow(object? sender, EventArgs e) From db6bdc76855f3b863a30073a873ae9976b409035 Mon Sep 17 00:00:00 2001 From: Glauber x86 Date: Fri, 12 Dec 2025 11:57:41 -0300 Subject: [PATCH 3/9] =?UTF-8?q?[Feat](Tray):=20Conex=C3=A3o=20com=20Pipe?= =?UTF-8?q?=20e=20debug?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- AssetManager.tray/AssetManager.tray.csproj | 1 + AssetManager.tray/src/HttpDebugService.cs | 32 ++++++++++++++++++++ AssetManager.tray/src/TrayForm.cs | 35 ++++++++++++++++++++++ 3 files changed, 68 insertions(+) create mode 100644 AssetManager.tray/src/HttpDebugService.cs diff --git a/AssetManager.tray/AssetManager.tray.csproj b/AssetManager.tray/AssetManager.tray.csproj index b43835a..71b287f 100644 --- a/AssetManager.tray/AssetManager.tray.csproj +++ b/AssetManager.tray/AssetManager.tray.csproj @@ -11,6 +11,7 @@ + diff --git a/AssetManager.tray/src/HttpDebugService.cs b/AssetManager.tray/src/HttpDebugService.cs new file mode 100644 index 0000000..a336974 --- /dev/null +++ b/AssetManager.tray/src/HttpDebugService.cs @@ -0,0 +1,32 @@ +using Microsoft.AspNetCore.Builder; +using Microsoft.AspNetCore.Hosting; +using Microsoft.AspNetCore.Http; +using Microsoft.Extensions.Hosting; + +namespace AssetManager.Tray +{ + public class HttpDebugService + { + // Snapshot atual (por enquanto fixo / mock) + // Depois isso vai ser atualizado pelo pipe + private static string _latestJson = "{ \"status\": \"aguardando dados\" }"; + public static void UpdateSnapshot(string json) + { + _latestJson = json; + } + public async Task StartAsync() + { + var builder = WebApplication.CreateBuilder(); + builder.WebHost.UseUrls("http://localhost:8765"); + + var app = builder.Build(); + + app.MapGet("/metrics", () => + { + return Results.Text(_latestJson, "application/json"); + }); + + await app.RunAsync(); + } + } +} \ No newline at end of file diff --git a/AssetManager.tray/src/TrayForm.cs b/AssetManager.tray/src/TrayForm.cs index bea8be3..88a944c 100644 --- a/AssetManager.tray/src/TrayForm.cs +++ b/AssetManager.tray/src/TrayForm.cs @@ -2,6 +2,7 @@ using System.Windows.Forms; using System.ServiceProcess; using System.IO.Pipes; +using System.Net.WebSockets; namespace AssetManager.Tray { @@ -9,6 +10,7 @@ public class TrayForm : Form { private readonly NotifyIcon trayIcon; private readonly ContextMenuStrip trayMenu; + private readonly HttpDebugService _httpDebugService = new(); public TrayForm() { @@ -30,6 +32,8 @@ public TrayForm() Text = "Asset Manager Agent", ContextMenuStrip = trayMenu }; + _ = Task.Run(_httpDebugService.StartAsync); + _ = Task.Run(PipeReadLoop); } private void OnSendNow(object? sender, EventArgs e) @@ -42,5 +46,36 @@ private void OnExit(object? sender, EventArgs e) trayIcon.Visible = false; Application.Exit(); } + private async Task PipeReadLoop() + { + while (true) + { + try + { + using var pipe = new NamedPipeClientStream( + ".", + "asset-monitor-pipe", + PipeDirection.In + ); + + await pipe.ConnectAsync(3000); + + using var reader = new StreamReader(pipe); + var json = await reader.ReadLineAsync(); + + if (!string.IsNullOrWhiteSpace(json)) + { + HttpDebugService.UpdateSnapshot(json); + } + } + catch + { + // Serviço pode estar indisponível ainda + // ignorar + } + + await Task.Delay(2000); + } + } } } From 1655c93acb3a525c36130f1c24ca61a108b22801 Mon Sep 17 00:00:00 2001 From: Glauber x86 Date: Mon, 15 Dec 2025 20:25:22 -0300 Subject: [PATCH 4/9] =?UTF-8?q?[Feat]:=20Integra=C3=A7=C3=A3o=20tray=20+?= =?UTF-8?q?=20worker?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../AssetManager.service.csproj | 4 +- AssetManager.service/package-lock.json | 6 -- AssetManager.slnx | 11 +++ AssetManager.tray/AssetManager.tray.csproj | 6 +- AssetManager.tray/app.manifest | 92 +++++++++++++++++++ AssetManager.tray/src/TrayForm.cs | 47 ++++++++++ 6 files changed, 155 insertions(+), 11 deletions(-) delete mode 100644 AssetManager.service/package-lock.json create mode 100644 AssetManager.tray/app.manifest diff --git a/AssetManager.service/AssetManager.service.csproj b/AssetManager.service/AssetManager.service.csproj index f9d0f08..f5e6a77 100644 --- a/AssetManager.service/AssetManager.service.csproj +++ b/AssetManager.service/AssetManager.service.csproj @@ -1,4 +1,4 @@ - + net10.0 @@ -12,4 +12,4 @@ - + \ No newline at end of file diff --git a/AssetManager.service/package-lock.json b/AssetManager.service/package-lock.json deleted file mode 100644 index 80bcb7f..0000000 --- a/AssetManager.service/package-lock.json +++ /dev/null @@ -1,6 +0,0 @@ -{ - "name": "AssetManager", - "lockfileVersion": 3, - "requires": true, - "packages": {} -} diff --git a/AssetManager.slnx b/AssetManager.slnx index 5e08da1..e6652e9 100644 --- a/AssetManager.slnx +++ b/AssetManager.slnx @@ -1,4 +1,15 @@ + + + + + + + + + + + diff --git a/AssetManager.tray/AssetManager.tray.csproj b/AssetManager.tray/AssetManager.tray.csproj index 71b287f..55a54b4 100644 --- a/AssetManager.tray/AssetManager.tray.csproj +++ b/AssetManager.tray/AssetManager.tray.csproj @@ -6,13 +6,13 @@ enable true enable - SystemAware - true + app.manifest + SystemAware + true - \ No newline at end of file diff --git a/AssetManager.tray/app.manifest b/AssetManager.tray/app.manifest new file mode 100644 index 0000000..467e905 --- /dev/null +++ b/AssetManager.tray/app.manifest @@ -0,0 +1,92 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + \ No newline at end of file diff --git a/AssetManager.tray/src/TrayForm.cs b/AssetManager.tray/src/TrayForm.cs index 88a944c..5de1a98 100644 --- a/AssetManager.tray/src/TrayForm.cs +++ b/AssetManager.tray/src/TrayForm.cs @@ -11,6 +11,7 @@ public class TrayForm : Form private readonly NotifyIcon trayIcon; private readonly ContextMenuStrip trayMenu; private readonly HttpDebugService _httpDebugService = new(); + private const string ServiceName = "AssetManager"; public TrayForm() { @@ -34,8 +35,53 @@ public TrayForm() }; _ = Task.Run(_httpDebugService.StartAsync); _ = Task.Run(PipeReadLoop); + EnsureServiceRunning(); } + private void EnsureServiceRunning() + { + try + { + using var sc = new ServiceController(ServiceName); + if (sc.Status == ServiceControllerStatus.Stopped || + sc.Status == ServiceControllerStatus.StopPending) + { + sc.Start(); + sc.WaitForStatus(ServiceControllerStatus.Running, TimeSpan.FromSeconds(15)); + } + } + catch (Exception ex) + { + MessageBox.Show( + $"Erro ao iniciar o serviço {ServiceName}.\n\n{ex.Message}", + "AssetManager", + MessageBoxButtons.OK, + MessageBoxIcon.Error + ); + } + } + private void StopService() + { + try + { + using var sc = new ServiceController(ServiceName); + + if (sc.Status == ServiceControllerStatus.Running) + { + sc.Stop(); + sc.WaitForStatus(ServiceControllerStatus.Stopped, TimeSpan.FromSeconds(15)); + } + } + catch (Exception ex) + { + MessageBox.Show( + $"Erro ao parar o serviço {ServiceName}.\n\n{ex.Message}", + "AssetManager", + MessageBoxButtons.OK, + MessageBoxIcon.Error + ); + } + } private void OnSendNow(object? sender, EventArgs e) { MessageBox.Show("Envio manual ainda não implementado."); @@ -44,6 +90,7 @@ private void OnSendNow(object? sender, EventArgs e) private void OnExit(object? sender, EventArgs e) { trayIcon.Visible = false; + StopService(); Application.Exit(); } private async Task PipeReadLoop() From c865df4297e165599038943a1d9007e4ff81b37e Mon Sep 17 00:00:00 2001 From: Glauber x86 Date: Tue, 16 Dec 2025 13:19:09 -0300 Subject: [PATCH 5/9] =?UTF-8?q?[Chore]:=20Configura=C3=A7=C3=A3o=20Inno=20?= =?UTF-8?q?Setup?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .gitignore | 7 +- AssetManager.installer/setup.iss | 79 +++++++++++++++++++ .../AssetManager.service.csproj | 7 +- AssetManager.service/src/Worker.cs | 10 +-- AssetManager.tray/AssetManager.tray.csproj | 4 + 5 files changed, 100 insertions(+), 7 deletions(-) create mode 100644 AssetManager.installer/setup.iss diff --git a/.gitignore b/.gitignore index 9491a2f..bd48d4f 100644 --- a/.gitignore +++ b/.gitignore @@ -360,4 +360,9 @@ MigrationBackup/ .ionide/ # Fody - auto-generated XML schema -FodyWeavers.xsd \ No newline at end of file +FodyWeavers.xsd + +# Installer projects output +*.msi +*.exe +AssetManager.installer\Output\* \ No newline at end of file diff --git a/AssetManager.installer/setup.iss b/AssetManager.installer/setup.iss new file mode 100644 index 0000000..aaff1ab --- /dev/null +++ b/AssetManager.installer/setup.iss @@ -0,0 +1,79 @@ +; Script generated by the Inno Setup Script Wizard. +; SEE THE DOCUMENTATION FOR DETAILS ON CREATING INNO SETUP SCRIPT FILES! +; Non-commercial use only + +#define MyAppName "AssetManager" +#define MyAppVersion "1.0" +#define MyAppPublisher "Borgno" +#define MyAppExeName "AssetManager.tray.exe" + +[Setup] +; NOTE: The value of AppId uniquely identifies this application. Do not use the same AppId value in installers for other applications. +; (To generate a new GUID, click Tools | Generate GUID inside the IDE.) +AppId={{8E2374B6-2EE6-4C3A-B17A-AF32C9565CB1} +AppName={#MyAppName} +AppVersion={#MyAppVersion} +;AppVerName={#MyAppName} {#MyAppVersion} +AppPublisher={#MyAppPublisher} +DefaultDirName={autopf}\{#MyAppPublisher}\{#MyAppName} +DisableDirPage=yes +UninstallDisplayIcon={app}\{#MyAppExeName} +; "ArchitecturesAllowed=x64compatible" specifies that Setup cannot run +; on anything but x64 and Windows 11 on Arm. +ArchitecturesAllowed=x64compatible +; "ArchitecturesInstallIn64BitMode=x64compatible" requests that the +; install be done in "64-bit mode" on x64 or Windows 11 on Arm, +; meaning it should use the native 64-bit Program Files directory and +; the 64-bit view of the registry. +ArchitecturesInstallIn64BitMode=x64compatible +DefaultGroupName={#MyAppName} +DisableProgramGroupPage=yes +; Uncomment the following line to run in non administrative install mode (install for current user only). +;PrivilegesRequired=lowest +OutputDir=C:\Users\GYNTI-N03\source\repos\AssetManager\AssetManager.installer\Output +OutputBaseFilename=assetmanager_installer +; Compression=lzma +SolidCompression=yes +WizardStyle=modern dynamic windows11 + +[Languages] +Name: "brazilianportuguese"; MessagesFile: "compiler:Languages\BrazilianPortuguese.isl" + +[Files] +Source: "C:\Users\GYNTI-N03\source\repos\AssetManager\AssetManager.tray\bin\Release\net10.0-windows\win-x64\*"; \ +DestDir: "{app}\tray"; Flags: ignoreversion +Source: "C:\Users\GYNTI-N03\source\repos\AssetManager\AssetManager.service\bin\Release\net10.0-windows\win-x64\*"; \ +DestDir: "{app}\service"; Flags: ignoreversion +; NOTE: Don't use "Flags: ignoreversion" on any shared system files + +; [Icons] +; Name: "{group}\{#MyAppName}"; Filename: "{app}\{#MyAppExeName}" +; Name: "{group}\{cm:UninstallProgram,{#MyAppName}}"; Filename: "{uninstallexe}" + +[Run] +; Criar o serviço +Filename: "sc.exe"; \ +Parameters: "create AssetManager binPath= ""{app}\service\AssetManager.service.exe"" start= auto"; \ +Flags: runhidden + +; Iniciar o serviço +Filename: "sc.exe"; \ +Parameters: "start AssetManager"; \ +Flags: runhidden + +; Criar tarefa agendada para o tray (SYSTEM, sem UAC) +Filename: "schtasks.exe"; \ +Parameters: "/create /f /sc onlogon /rl highest /ru SYSTEM /tn ""AssetManagerTray"" /tr ""\""{app}\tray\AssetManager.tray.exe\"" """; \ +Flags: runhidden + +Filename: "{app}\tray\{#MyAppExeName}"; \ +Description: "{cm:LaunchProgram,{#StringChange(MyAppName, '&', '&&')}}"; \ +Flags: nowait postinstall skipifsilent + +[UninstallRun] +; Parar e remover serviço +Filename: "sc.exe"; Parameters: "stop AssetManager"; Flags: runhidden +Filename: "sc.exe"; Parameters: "delete AssetManager"; Flags: runhidden + +; Remover tarefa agendada +Filename: "schtasks.exe"; Parameters: "/delete /f /tn ""AssetManagerTray"""; Flags: runhidden \ No newline at end of file diff --git a/AssetManager.service/AssetManager.service.csproj b/AssetManager.service/AssetManager.service.csproj index f5e6a77..b941555 100644 --- a/AssetManager.service/AssetManager.service.csproj +++ b/AssetManager.service/AssetManager.service.csproj @@ -1,10 +1,15 @@  - net10.0 + WinExe + win-x64 + net10.0-windows enable enable + pt-BR dotnet-AssetManager-7dd1a0f1-3fce-4c35-bc7b-8f9a71a59f68 + true + false diff --git a/AssetManager.service/src/Worker.cs b/AssetManager.service/src/Worker.cs index 69d3cb5..f5ed02a 100644 --- a/AssetManager.service/src/Worker.cs +++ b/AssetManager.service/src/Worker.cs @@ -32,11 +32,11 @@ public class Worker : BackgroundService private string _latestJson = "{}"; public Worker( - ILogger logger, - ICpuMonitor cpu, - IMemoryMonitor mem, - IDiskMonitor disk, - INetworkMonitor net) + ILogger logger, + ICpuMonitor cpu, + IMemoryMonitor mem, + IDiskMonitor disk, + INetworkMonitor net) { _logger = logger; _cpu = cpu; diff --git a/AssetManager.tray/AssetManager.tray.csproj b/AssetManager.tray/AssetManager.tray.csproj index 55a54b4..3f4d219 100644 --- a/AssetManager.tray/AssetManager.tray.csproj +++ b/AssetManager.tray/AssetManager.tray.csproj @@ -2,6 +2,7 @@ WinExe + win-x64 net10.0-windows enable true @@ -9,6 +10,9 @@ app.manifest SystemAware true + pt-BR + true + false From f2983c82fc04d929e1b10b5eccb448e7a5194b92 Mon Sep 17 00:00:00 2001 From: Glauber x86 Date: Tue, 16 Dec 2025 15:34:15 -0300 Subject: [PATCH 6/9] =?UTF-8?q?[Feat](WebSocket):=20Conex=C3=A3o=20via=20W?= =?UTF-8?q?ebSocket=20b=C3=A1sica?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .gitignore | 6 +- AssetManager.tray/AssetManager.tray.csproj | 6 ++ AssetManager.tray/appsettings_example.json | 7 +++ .../src/MetricsWebSocketClient.cs | 57 +++++++++++++++++++ AssetManager.tray/src/Program.cs | 11 +++- AssetManager.tray/src/TrayForm.cs | 11 +++- 6 files changed, 93 insertions(+), 5 deletions(-) create mode 100644 AssetManager.tray/appsettings_example.json create mode 100644 AssetManager.tray/src/MetricsWebSocketClient.cs diff --git a/.gitignore b/.gitignore index bd48d4f..2c23d83 100644 --- a/.gitignore +++ b/.gitignore @@ -365,4 +365,8 @@ FodyWeavers.xsd # Installer projects output *.msi *.exe -AssetManager.installer\Output\* \ No newline at end of file +AssetManager.installer/Output + +# Environment files +.env +AssetManager.tray/appsettings.json \ No newline at end of file diff --git a/AssetManager.tray/AssetManager.tray.csproj b/AssetManager.tray/AssetManager.tray.csproj index 3f4d219..b8dc3c4 100644 --- a/AssetManager.tray/AssetManager.tray.csproj +++ b/AssetManager.tray/AssetManager.tray.csproj @@ -19,4 +19,10 @@ + + + + PreserveNewest + + \ No newline at end of file diff --git a/AssetManager.tray/appsettings_example.json b/AssetManager.tray/appsettings_example.json new file mode 100644 index 0000000..7c97ab3 --- /dev/null +++ b/AssetManager.tray/appsettings_example.json @@ -0,0 +1,7 @@ +{ + "WebSocket": { + "Url": "", + "Token": "", + "ReconnectSeconds": 5 + } +} diff --git a/AssetManager.tray/src/MetricsWebSocketClient.cs b/AssetManager.tray/src/MetricsWebSocketClient.cs new file mode 100644 index 0000000..e15be31 --- /dev/null +++ b/AssetManager.tray/src/MetricsWebSocketClient.cs @@ -0,0 +1,57 @@ +using System; +using System.Net.WebSockets; +using System.Text; +using System.Threading; +using System.Threading.Tasks; + +namespace AssetManager.Tray +{ + public class MetricsWebSocketClient + { + private ClientWebSocket? _ws; + private readonly string _url; + private readonly string _token; + + public MetricsWebSocketClient(string url, string token) + { + _url = url; + _token = token; + } + + public async Task EnsureConnectedAsync() + { + if (_ws != null && _ws.State == WebSocketState.Open) + return; + + _ws?.Dispose(); + _ws = new ClientWebSocket(); + + _ws.Options.SetRequestHeader( + "Authorization", + $"Bearer {_token}" + ); + + await _ws.ConnectAsync( + new Uri(_url), + CancellationToken.None + ); + } + + public async Task SendAsync(string json) + { + await EnsureConnectedAsync(); + + if (_ws?.State != WebSocketState.Open) + return; + + var buffer = Encoding.UTF8.GetBytes(json); + + await _ws.SendAsync( + buffer, + WebSocketMessageType.Text, + true, + CancellationToken.None + ); + } + } +} diff --git a/AssetManager.tray/src/Program.cs b/AssetManager.tray/src/Program.cs index 3ad457b..1d5f362 100644 --- a/AssetManager.tray/src/Program.cs +++ b/AssetManager.tray/src/Program.cs @@ -1,3 +1,4 @@ +using Microsoft.Extensions.Configuration; using System; using System.Windows.Forms; @@ -5,12 +6,18 @@ namespace AssetManager.Tray { internal static class Program { + public static IConfiguration Configuration { get; private set; } = null!; + [STAThread] static void Main() { + Configuration = new ConfigurationBuilder() + .SetBasePath(AppContext.BaseDirectory) + .AddJsonFile("appsettings.json", optional: false) + .Build(); + ApplicationConfiguration.Initialize(); Application.Run(new TrayForm()); } } - -} \ No newline at end of file +} diff --git a/AssetManager.tray/src/TrayForm.cs b/AssetManager.tray/src/TrayForm.cs index 5de1a98..bee0e40 100644 --- a/AssetManager.tray/src/TrayForm.cs +++ b/AssetManager.tray/src/TrayForm.cs @@ -12,6 +12,7 @@ public class TrayForm : Form private readonly ContextMenuStrip trayMenu; private readonly HttpDebugService _httpDebugService = new(); private const string ServiceName = "AssetManager"; + private readonly MetricsWebSocketClient _wsClient; public TrayForm() { @@ -33,6 +34,11 @@ public TrayForm() Text = "Asset Manager Agent", ContextMenuStrip = trayMenu }; + var wsUrl = Program.Configuration["WebSocket:Url"]; + var wsToken = Program.Configuration["WebSocket:Token"]; + + _wsClient = new MetricsWebSocketClient(wsUrl!, wsToken!); + _ = Task.Run(_httpDebugService.StartAsync); _ = Task.Run(PipeReadLoop); EnsureServiceRunning(); @@ -113,12 +119,13 @@ private async Task PipeReadLoop() if (!string.IsNullOrWhiteSpace(json)) { HttpDebugService.UpdateSnapshot(json); + await _wsClient.SendAsync(json); } } catch { - // Serviço pode estar indisponível ainda - // ignorar + // serviço pode não estar pronto + // TODO: adicionar logs } await Task.Delay(2000); From 2b8e07f9ecaca753dd223635b24ddf9643e812c2 Mon Sep 17 00:00:00 2001 From: Glauber x86 Date: Tue, 16 Dec 2025 16:17:47 -0300 Subject: [PATCH 7/9] =?UTF-8?q?[Feat](API):=20Removido=20WebSocket=20e=20f?= =?UTF-8?q?eito=20HTTP=20POST=20b=C3=A1sico?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .gitignore | 4 +- .../AssetManager.service.csproj | 2 - AssetManager.tray/appsettings_example.json | 7 --- .../src/MetricsWebSocketClient.cs | 57 ------------------- AssetManager.tray/src/TrayForm.cs | 52 +++++++++++++---- 5 files changed, 44 insertions(+), 78 deletions(-) delete mode 100644 AssetManager.tray/appsettings_example.json delete mode 100644 AssetManager.tray/src/MetricsWebSocketClient.cs diff --git a/.gitignore b/.gitignore index 2c23d83..69797de 100644 --- a/.gitignore +++ b/.gitignore @@ -367,6 +367,6 @@ FodyWeavers.xsd *.exe AssetManager.installer/Output -# Environment files -.env +# Env +.Env AssetManager.tray/appsettings.json \ No newline at end of file diff --git a/AssetManager.service/AssetManager.service.csproj b/AssetManager.service/AssetManager.service.csproj index b941555..e54cff1 100644 --- a/AssetManager.service/AssetManager.service.csproj +++ b/AssetManager.service/AssetManager.service.csproj @@ -1,8 +1,6 @@  - WinExe - win-x64 net10.0-windows enable enable diff --git a/AssetManager.tray/appsettings_example.json b/AssetManager.tray/appsettings_example.json deleted file mode 100644 index 7c97ab3..0000000 --- a/AssetManager.tray/appsettings_example.json +++ /dev/null @@ -1,7 +0,0 @@ -{ - "WebSocket": { - "Url": "", - "Token": "", - "ReconnectSeconds": 5 - } -} diff --git a/AssetManager.tray/src/MetricsWebSocketClient.cs b/AssetManager.tray/src/MetricsWebSocketClient.cs deleted file mode 100644 index e15be31..0000000 --- a/AssetManager.tray/src/MetricsWebSocketClient.cs +++ /dev/null @@ -1,57 +0,0 @@ -using System; -using System.Net.WebSockets; -using System.Text; -using System.Threading; -using System.Threading.Tasks; - -namespace AssetManager.Tray -{ - public class MetricsWebSocketClient - { - private ClientWebSocket? _ws; - private readonly string _url; - private readonly string _token; - - public MetricsWebSocketClient(string url, string token) - { - _url = url; - _token = token; - } - - public async Task EnsureConnectedAsync() - { - if (_ws != null && _ws.State == WebSocketState.Open) - return; - - _ws?.Dispose(); - _ws = new ClientWebSocket(); - - _ws.Options.SetRequestHeader( - "Authorization", - $"Bearer {_token}" - ); - - await _ws.ConnectAsync( - new Uri(_url), - CancellationToken.None - ); - } - - public async Task SendAsync(string json) - { - await EnsureConnectedAsync(); - - if (_ws?.State != WebSocketState.Open) - return; - - var buffer = Encoding.UTF8.GetBytes(json); - - await _ws.SendAsync( - buffer, - WebSocketMessageType.Text, - true, - CancellationToken.None - ); - } - } -} diff --git a/AssetManager.tray/src/TrayForm.cs b/AssetManager.tray/src/TrayForm.cs index bee0e40..89a87d5 100644 --- a/AssetManager.tray/src/TrayForm.cs +++ b/AssetManager.tray/src/TrayForm.cs @@ -2,7 +2,9 @@ using System.Windows.Forms; using System.ServiceProcess; using System.IO.Pipes; -using System.Net.WebSockets; +using System.Net.Http; +using System.Text; +using System.Text.Json; namespace AssetManager.Tray { @@ -12,8 +14,10 @@ public class TrayForm : Form private readonly ContextMenuStrip trayMenu; private readonly HttpDebugService _httpDebugService = new(); private const string ServiceName = "AssetManager"; - private readonly MetricsWebSocketClient _wsClient; - + private static readonly HttpClient _http = new(); + private readonly string _apiUrl; + private readonly string _apiToken; + private readonly bool _apiEnabled; public TrayForm() { // Ocultar a janela @@ -34,10 +38,18 @@ public TrayForm() Text = "Asset Manager Agent", ContextMenuStrip = trayMenu }; - var wsUrl = Program.Configuration["WebSocket:Url"]; - var wsToken = Program.Configuration["WebSocket:Token"]; + var config = Program.Configuration; + + _apiUrl = config["Api:Url"]; + _apiToken = config["Api:Token"]; - _wsClient = new MetricsWebSocketClient(wsUrl!, wsToken!); + _apiEnabled = !string.IsNullOrWhiteSpace(_apiUrl); + + if (_apiEnabled && !string.IsNullOrWhiteSpace(_apiToken)) + { + _http.DefaultRequestHeaders.Authorization = + new System.Net.Http.Headers.AuthenticationHeaderValue("Bearer", _apiToken); + } _ = Task.Run(_httpDebugService.StartAsync); _ = Task.Run(PipeReadLoop); @@ -119,17 +131,37 @@ private async Task PipeReadLoop() if (!string.IsNullOrWhiteSpace(json)) { HttpDebugService.UpdateSnapshot(json); - await _wsClient.SendAsync(json); + _ = SendToApiAsync(json); } } catch { - // serviço pode não estar pronto - // TODO: adicionar logs + // Serviço pode estar indisponível ainda + // ignorar } - await Task.Delay(2000); } } + private async Task SendToApiAsync(string json) + { + if (!_apiEnabled) + return; + + try + { + using var content = new StringContent( + json, + Encoding.UTF8, + "application/json" + ); + + await _http.PostAsync(_apiUrl!, content); + } + catch + { + // falha silenciosa + // tray nunca deve quebrar por rede + } + } } } From b8019db26c7e408ee4a600a65cc5e01ea9c01885 Mon Sep 17 00:00:00 2001 From: Glauber x86 Date: Wed, 17 Dec 2025 09:36:02 -0300 Subject: [PATCH 8/9] README --- README.md | 22 +++++++++++++++++++++- 1 file changed, 21 insertions(+), 1 deletion(-) diff --git a/README.md b/README.md index 2c5646a..2a07298 100644 --- a/README.md +++ b/README.md @@ -1,2 +1,22 @@ # AssetManager -Sistema para controle de ativos + +Sistema para Controle de Ativos + +O **AssetManager** é uma aplicação desenvolvida para gerenciar e monitorar ativos de uma organização. Ele permite o cadastro, acompanhamento e manutenção de ativos, garantindo maior controle e eficiência na gestão. + +## Funcionalidades + +- Cadastro de ativos com informações detalhadas. +- Monitoramento do status e localização dos ativos. +- Agendamento e controle de manutenções preventivas e corretivas. +- Geração de relatórios para análise de desempenho e custos. +- Integração com outros sistemas de gestão. + +## Tecnologias Utilizadas + +- **Linguagem:** C# +- **Framework:** .NET + +## Licença + +Este projeto está licenciado sob a [MIT License](LICENSE). From e7f5f71494ba1f68c76abea7f65069d09bfa6e1f Mon Sep 17 00:00:00 2001 From: Glauber x86 Date: Wed, 24 Dec 2025 08:09:21 -0300 Subject: [PATCH 9/9] [Feat](Instalador): Adicionado etapa de API no instalador (fixes: #3) --- AssetManager.installer/setup.iss | 119 ++++++++++++++++++--- AssetManager.tray/AssetManager.tray.csproj | 2 +- AssetManager.tray/appsettings_example.json | 7 ++ 3 files changed, 114 insertions(+), 14 deletions(-) create mode 100644 AssetManager.tray/appsettings_example.json diff --git a/AssetManager.installer/setup.iss b/AssetManager.installer/setup.iss index aaff1ab..63cce3f 100644 --- a/AssetManager.installer/setup.iss +++ b/AssetManager.installer/setup.iss @@ -3,7 +3,7 @@ ; Non-commercial use only #define MyAppName "AssetManager" -#define MyAppVersion "1.0" +#define MyAppVersion "1.0.2" #define MyAppPublisher "Borgno" #define MyAppExeName "AssetManager.tray.exe" @@ -30,9 +30,9 @@ DefaultGroupName={#MyAppName} DisableProgramGroupPage=yes ; Uncomment the following line to run in non administrative install mode (install for current user only). ;PrivilegesRequired=lowest -OutputDir=C:\Users\GYNTI-N03\source\repos\AssetManager\AssetManager.installer\Output +OutputDir=Output OutputBaseFilename=assetmanager_installer -; Compression=lzma +Compression=lzma SolidCompression=yes WizardStyle=modern dynamic windows11 @@ -40,10 +40,16 @@ WizardStyle=modern dynamic windows11 Name: "brazilianportuguese"; MessagesFile: "compiler:Languages\BrazilianPortuguese.isl" [Files] -Source: "C:\Users\GYNTI-N03\source\repos\AssetManager\AssetManager.tray\bin\Release\net10.0-windows\win-x64\*"; \ -DestDir: "{app}\tray"; Flags: ignoreversion -Source: "C:\Users\GYNTI-N03\source\repos\AssetManager\AssetManager.service\bin\Release\net10.0-windows\win-x64\*"; \ +Source: "C:\Users\GYNTI-N03.GYNTI-N03\source\repos\AssetManager\AssetManager.tray\bin\Release\net10.0-windows\win-x64\*"; \ +DestDir: "{app}\tray"; Flags: ignoreversion; \ +Excludes: "appsettings.json, appsettings_example.json" + +Source: "C:\Users\GYNTI-N03.GYNTI-N03\source\repos\AssetManager\AssetManager.service\bin\Release\net10.0-windows\win-x64\*"; \ DestDir: "{app}\service"; Flags: ignoreversion + +Source: "C:\Users\GYNTI-N03.GYNTI-N03\source\repos\AssetManager\AssetManager.tray\bin\Release\net10.0-windows\win-x64\appsettings_example.json"; \ +DestDir: "{app}\tray"; \ +DestName: "appsettings.json"; Flags: ignoreversion ; NOTE: Don't use "Flags: ignoreversion" on any shared system files ; [Icons] @@ -51,24 +57,26 @@ DestDir: "{app}\service"; Flags: ignoreversion ; Name: "{group}\{cm:UninstallProgram,{#MyAppName}}"; Filename: "{uninstallexe}" [Run] -; Criar o serviço Filename: "sc.exe"; \ Parameters: "create AssetManager binPath= ""{app}\service\AssetManager.service.exe"" start= auto"; \ Flags: runhidden -; Iniciar o serviço Filename: "sc.exe"; \ Parameters: "start AssetManager"; \ Flags: runhidden -; Criar tarefa agendada para o tray (SYSTEM, sem UAC) Filename: "schtasks.exe"; \ Parameters: "/create /f /sc onlogon /rl highest /ru SYSTEM /tn ""AssetManagerTray"" /tr ""\""{app}\tray\AssetManager.tray.exe\"" """; \ Flags: runhidden -Filename: "{app}\tray\{#MyAppExeName}"; \ -Description: "{cm:LaunchProgram,{#StringChange(MyAppName, '&', '&&')}}"; \ -Flags: nowait postinstall skipifsilent +Filename: "powershell.exe"; \ +Parameters: "-ExecutionPolicy Bypass -Command ""$t = Get-ScheduledTask -TaskName 'AssetManagerTray'; $s = $t.Settings; $s.ExecutionTimeLimit = [TimeSpan]::Zero; $s.AllowStartIfOnBatteries = $true; $s.DontStopIfGoingOnBatteries = $true; Set-ScheduledTask -TaskName 'AssetManagerTray' -Settings $s"""; \ +Flags: runhidden + +; Iniciar o tray após a instalação +;Filename: "{app}\tray\{#MyAppExeName}"; \ +;Description: "{cm:LaunchProgram,{#StringChange(MyAppName, '&', '&&')}}"; \ +;Flags: postinstall skipifsilent [UninstallRun] ; Parar e remover serviço @@ -76,4 +84,89 @@ Filename: "sc.exe"; Parameters: "stop AssetManager"; Flags: runhidden Filename: "sc.exe"; Parameters: "delete AssetManager"; Flags: runhidden ; Remover tarefa agendada -Filename: "schtasks.exe"; Parameters: "/delete /f /tn ""AssetManagerTray"""; Flags: runhidden \ No newline at end of file +Filename: "schtasks.exe"; Parameters: "/end /tn ""AssetManagerTray"""; Flags: runhidden +Filename: "schtasks.exe"; Parameters: "/delete /f /tn ""AssetManagerTray""" ; Flags: runhidden + +[Code] +var + TokenPage: TInputQueryWizardPage; + +procedure InitializeWizard; +begin + TokenPage := CreateInputQueryPage(wpWelcome, + 'Configuração da API', 'Autenticação do Agente', + 'Insira a URL e o Token de acesso da API para que o AssetManager possa enviar os dados.'); + TokenPage.Add('URL da API:', False); + TokenPage.Add('API Token:', False); +end; + +function ShouldSkipPage(PageID: Integer): Boolean; +begin + if (PageID = TokenPage.ID) and FileExists(ExpandConstant('{src}\appsettings.json')) then + Result := True + else + Result := False; +end; + +procedure CurStepChanged(CurStep: TSetupStep); +var + JsonDestino: String; + JsonOrigem: String; + FileContentAnsi: AnsiString; + FileContentString: String; + UserUrl: String; + UserToken: String; + HouveAlteracao: Boolean; +begin + if CurStep = ssPostInstall then + begin + JsonDestino := ExpandConstant('{app}\tray\appsettings.json'); + JsonOrigem := ExpandConstant('{src}\appsettings.json'); + HouveAlteracao := False; + + if FileExists(JsonOrigem) then + begin + Log('Arquivo appsettings.json encontrado na origem. Copiando...'); + if FileCopy(JsonOrigem, JsonDestino, False) then + Log('Configuração importada automaticamente.') + else + Log('Falha ao copiar configuração automática.'); + end + + else + begin + if (TokenPage <> nil) then + begin + UserUrl := TokenPage.Values[0]; + UserToken := TokenPage.Values[1]; + end + else + begin + UserUrl := ''; + UserToken := ''; + end; + if FileExists(JsonDestino) then + begin + if LoadStringFromFile(JsonDestino, FileContentAnsi) then + begin + FileContentString := String(FileContentAnsi); + if (UserUrl <> '') and (StringChange(FileContentString, '{{URL}}', UserUrl) > 0) then + begin + HouveAlteracao := True; + Log('URL atualizada.'); + end; + if (UserToken <> '') and (StringChange(FileContentString, '{{TOKEN}}', UserToken) > 0) then + begin + HouveAlteracao := True; + Log('Token atualizado.'); + end; + if HouveAlteracao then + begin + SaveStringToFile(JsonDestino, AnsiString(FileContentString), False); + Log('Arquivo JSON salvo com sucesso.'); + end; + end; + end; + end; + end; +end; \ No newline at end of file diff --git a/AssetManager.tray/AssetManager.tray.csproj b/AssetManager.tray/AssetManager.tray.csproj index b8dc3c4..51f391e 100644 --- a/AssetManager.tray/AssetManager.tray.csproj +++ b/AssetManager.tray/AssetManager.tray.csproj @@ -21,7 +21,7 @@ - + PreserveNewest diff --git a/AssetManager.tray/appsettings_example.json b/AssetManager.tray/appsettings_example.json new file mode 100644 index 0000000..448b57d --- /dev/null +++ b/AssetManager.tray/appsettings_example.json @@ -0,0 +1,7 @@ +{ + "API": { + "Url": "{{URL}}", + "Token": "{{TOKEN}}", + "ReconnectSeconds": 5 + } +} \ No newline at end of file