diff --git a/.gitignore b/.gitignore
index 9491a2f..69797de 100644
--- a/.gitignore
+++ b/.gitignore
@@ -360,4 +360,13 @@ 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
+
+# Env
+.Env
+AssetManager.tray/appsettings.json
\ No newline at end of file
diff --git a/AssetManager.installer/setup.iss b/AssetManager.installer/setup.iss
new file mode 100644
index 0000000..63cce3f
--- /dev/null
+++ b/AssetManager.installer/setup.iss
@@ -0,0 +1,172 @@
+; 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.2"
+#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=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.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]
+; Name: "{group}\{#MyAppName}"; Filename: "{app}\{#MyAppExeName}"
+; Name: "{group}\{cm:UninstallProgram,{#MyAppName}}"; Filename: "{uninstallexe}"
+
+[Run]
+Filename: "sc.exe"; \
+Parameters: "create AssetManager binPath= ""{app}\service\AssetManager.service.exe"" start= auto"; \
+Flags: runhidden
+
+Filename: "sc.exe"; \
+Parameters: "start AssetManager"; \
+Flags: runhidden
+
+Filename: "schtasks.exe"; \
+Parameters: "/create /f /sc onlogon /rl highest /ru SYSTEM /tn ""AssetManagerTray"" /tr ""\""{app}\tray\AssetManager.tray.exe\"" """; \
+Flags: runhidden
+
+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
+Filename: "sc.exe"; Parameters: "stop AssetManager"; Flags: runhidden
+Filename: "sc.exe"; Parameters: "delete AssetManager"; Flags: runhidden
+
+; Remover tarefa agendada
+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.service/AssetManager.service.csproj b/AssetManager.service/AssetManager.service.csproj
index f9d0f08..e54cff1 100644
--- a/AssetManager.service/AssetManager.service.csproj
+++ b/AssetManager.service/AssetManager.service.csproj
@@ -1,10 +1,13 @@
-
+
- net10.0
+ net10.0-windows
enable
enable
+ pt-BR
dotnet-AssetManager-7dd1a0f1-3fce-4c35-bc7b-8f9a71a59f68
+ true
+ false
@@ -12,4 +15,4 @@
-
+
\ No newline at end of file
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/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/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.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 aeb043f..f5ed02a 100644
--- a/AssetManager.service/src/Worker.cs
+++ b/AssetManager.service/src/Worker.cs
@@ -4,13 +4,17 @@
// 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 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
{
@@ -21,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;
@@ -39,99 +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
- },
+ _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, leaveOpen: true) { AutoFlush = true };
- await writer.WriteLineAsync(json);
-
- _logger.LogInformation("Metricas enviadas via named pipe.");
- }
- catch (OperationCanceledException)
- {
- _logger.LogWarning("Timeout ao conectar ao named pipe {PipeName}.", PIPE_NAME);
- }
- catch (IOException ex)
- {
- _logger.LogError(ex, "Erro de E/S 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.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 e7df544..51f391e 100644
--- a/AssetManager.tray/AssetManager.tray.csproj
+++ b/AssetManager.tray/AssetManager.tray.csproj
@@ -2,12 +2,27 @@
WinExe
+ win-x64
net10.0-windows
enable
true
enable
- SystemAware
- true
+ app.manifest
+ SystemAware
+ true
+ pt-BR
+ true
+ false
+
+
+
+
+
+
+
+ PreserveNewest
+
+
\ 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/Program.cs b/AssetManager.tray/Program.cs
deleted file mode 100644
index 3ad457b..0000000
--- a/AssetManager.tray/Program.cs
+++ /dev/null
@@ -1,16 +0,0 @@
-using System;
-using System.Windows.Forms;
-
-namespace AssetManager.Tray
-{
- internal static class Program
- {
- [STAThread]
- static void Main()
- {
- ApplicationConfiguration.Initialize();
- Application.Run(new TrayForm());
- }
- }
-
-}
\ No newline at end of file
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/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/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
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/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/Program.cs b/AssetManager.tray/src/Program.cs
new file mode 100644
index 0000000..1d5f362
--- /dev/null
+++ b/AssetManager.tray/src/Program.cs
@@ -0,0 +1,23 @@
+using Microsoft.Extensions.Configuration;
+using System;
+using System.Windows.Forms;
+
+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());
+ }
+ }
+}
diff --git a/AssetManager.tray/src/TrayForm.cs b/AssetManager.tray/src/TrayForm.cs
new file mode 100644
index 0000000..89a87d5
--- /dev/null
+++ b/AssetManager.tray/src/TrayForm.cs
@@ -0,0 +1,167 @@
+using System;
+using System.Windows.Forms;
+using System.ServiceProcess;
+using System.IO.Pipes;
+using System.Net.Http;
+using System.Text;
+using System.Text.Json;
+
+namespace AssetManager.Tray
+{
+ public class TrayForm : Form
+ {
+ private readonly NotifyIcon trayIcon;
+ private readonly ContextMenuStrip trayMenu;
+ private readonly HttpDebugService _httpDebugService = new();
+ private const string ServiceName = "AssetManager";
+ private static readonly HttpClient _http = new();
+ private readonly string _apiUrl;
+ private readonly string _apiToken;
+ private readonly bool _apiEnabled;
+ 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
+ };
+ var config = Program.Configuration;
+
+ _apiUrl = config["Api:Url"];
+ _apiToken = config["Api:Token"];
+
+ _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);
+ 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.");
+ }
+
+ private void OnExit(object? sender, EventArgs e)
+ {
+ trayIcon.Visible = false;
+ StopService();
+ 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);
+ _ = SendToApiAsync(json);
+ }
+ }
+ catch
+ {
+ // 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
+ }
+ }
+ }
+}
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
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).