Commit inicial: GTI Maquina Comercial
App local (Node + SQLite) para captura, tratamento e exportacao de leads comerciais, com dashboard de gestao, login/usuarios e integracao Z-PRO. Co-Authored-By: Claude <noreply@anthropic.com>
This commit is contained in:
@@ -0,0 +1,10 @@
|
||||
<Project Sdk="Microsoft.NET.Sdk">
|
||||
<PropertyGroup>
|
||||
<OutputType>Exe</OutputType>
|
||||
<TargetFramework>net8.0</TargetFramework>
|
||||
<ImplicitUsings>enable</ImplicitUsings>
|
||||
<Nullable>enable</Nullable>
|
||||
<AssemblyName>GTI-Comercial</AssemblyName>
|
||||
<RootNamespace>GtiComercialLauncher</RootNamespace>
|
||||
</PropertyGroup>
|
||||
</Project>
|
||||
@@ -0,0 +1,198 @@
|
||||
using System;
|
||||
using System.Diagnostics;
|
||||
using System.IO;
|
||||
using System.Net;
|
||||
using System.Threading;
|
||||
|
||||
// Launcher do GTI Maquina Comercial:
|
||||
// 1. localiza a pasta do projeto (procura package.json + scripts/servidor.mjs);
|
||||
// 2. se o servidor ja responde em http://127.0.0.1:3900, so abre o navegador;
|
||||
// 3. senao, inicia "node scripts/servidor.mjs" escondido (log em data/server.log),
|
||||
// espera o /api/health responder (ate 30s) e abre o navegador.
|
||||
//
|
||||
// Compilavel tanto pelo dotnet SDK (net8.0) quanto pelo csc.exe do
|
||||
// .NET Framework 4 (C# 5): sem interpolacao de string, sem APIs novas.
|
||||
|
||||
namespace GtiComercialLauncher
|
||||
{
|
||||
internal static class Program
|
||||
{
|
||||
private static string BaseUrl()
|
||||
{
|
||||
string port = Environment.GetEnvironmentVariable("GTI_PORT");
|
||||
if (string.IsNullOrEmpty(port)) port = "3900";
|
||||
return "http://127.0.0.1:" + port;
|
||||
}
|
||||
|
||||
private static int Main()
|
||||
{
|
||||
Console.Title = "GTI Maquina Comercial";
|
||||
Console.WriteLine("=== GTI Maquina Comercial ===");
|
||||
|
||||
string root = LocateProjectDir();
|
||||
if (root == null)
|
||||
{
|
||||
return Fail("Pasta do projeto nao encontrada (procurei package.json + scripts/servidor.mjs).\n" +
|
||||
"Deixe o GTI-Comercial.exe dentro da pasta do projeto.");
|
||||
}
|
||||
|
||||
string url = BaseUrl();
|
||||
|
||||
if (IsHealthy(url))
|
||||
{
|
||||
Console.WriteLine("Servidor ja esta rodando. Abrindo o navegador...");
|
||||
OpenBrowser(url);
|
||||
return 0;
|
||||
}
|
||||
|
||||
string node = FindNode();
|
||||
if (node == null)
|
||||
{
|
||||
return Fail("Node.js nao encontrado no PATH. Instale em https://nodejs.org (versao 24 ou superior).");
|
||||
}
|
||||
|
||||
Console.WriteLine("Iniciando o servidor (aguarde alguns segundos)...");
|
||||
if (!StartServer(node, root))
|
||||
{
|
||||
return Fail("Nao foi possivel iniciar o servidor. Veja o log em data\\server.log");
|
||||
}
|
||||
|
||||
for (int i = 0; i < 60; i++)
|
||||
{
|
||||
Thread.Sleep(500);
|
||||
if (IsHealthy(url))
|
||||
{
|
||||
Console.WriteLine("Pronto! Abrindo " + url + " no navegador...");
|
||||
OpenBrowser(url);
|
||||
Thread.Sleep(1500);
|
||||
return 0;
|
||||
}
|
||||
}
|
||||
|
||||
return Fail("O servidor nao respondeu em 30 segundos.\n" +
|
||||
"Veja o log em data\\server.log ou rode manualmente: node scripts\\servidor.mjs");
|
||||
}
|
||||
|
||||
private static string LocateProjectDir()
|
||||
{
|
||||
string[] candidates = new string[]
|
||||
{
|
||||
AppDomain.CurrentDomain.BaseDirectory,
|
||||
Directory.GetCurrentDirectory()
|
||||
};
|
||||
|
||||
foreach (string start in candidates)
|
||||
{
|
||||
DirectoryInfo dir = new DirectoryInfo(start);
|
||||
for (int depth = 0; dir != null && depth < 5; depth++)
|
||||
{
|
||||
if (File.Exists(Path.Combine(dir.FullName, "package.json")) &&
|
||||
File.Exists(Path.Combine(Path.Combine(dir.FullName, "scripts"), "servidor.mjs")))
|
||||
{
|
||||
return dir.FullName;
|
||||
}
|
||||
dir = dir.Parent;
|
||||
}
|
||||
}
|
||||
return null;
|
||||
}
|
||||
|
||||
private static string FindNode()
|
||||
{
|
||||
try
|
||||
{
|
||||
ProcessStartInfo psi = new ProcessStartInfo("where.exe", "node");
|
||||
psi.UseShellExecute = false;
|
||||
psi.RedirectStandardOutput = true;
|
||||
psi.CreateNoWindow = true;
|
||||
using (Process p = Process.Start(psi))
|
||||
{
|
||||
string output = p.StandardOutput.ReadToEnd();
|
||||
p.WaitForExit(5000);
|
||||
if (!string.IsNullOrEmpty(output))
|
||||
{
|
||||
string first = output.Split(new char[] { '\r', '\n' }, StringSplitOptions.RemoveEmptyEntries)[0].Trim();
|
||||
if (File.Exists(first)) return first;
|
||||
}
|
||||
}
|
||||
}
|
||||
catch (Exception)
|
||||
{
|
||||
// continua para o retorno nulo
|
||||
}
|
||||
return null;
|
||||
}
|
||||
|
||||
private static bool StartServer(string node, string root)
|
||||
{
|
||||
try
|
||||
{
|
||||
string dataDir = Path.Combine(root, "data");
|
||||
Directory.CreateDirectory(dataDir);
|
||||
string logPath = Path.Combine(dataDir, "server.log");
|
||||
|
||||
File.AppendAllText(logPath,
|
||||
"---- " + DateTime.Now.ToString("yyyy-MM-dd HH:mm:ss") + " launcher iniciou o servidor ----\r\n");
|
||||
|
||||
// O redirecionamento para o log e feito pelo proprio cmd.exe: assim o
|
||||
// launcher pode encerrar sem deixar o servidor escrevendo num pipe
|
||||
// sem leitor (o que derrubaria o node com EPIPE mais tarde).
|
||||
string comando = "/c \"\"" + node + "\" scripts\\servidor.mjs >> \"" + logPath + "\" 2>&1\"";
|
||||
ProcessStartInfo psi = new ProcessStartInfo("cmd.exe", comando);
|
||||
psi.WorkingDirectory = root;
|
||||
psi.UseShellExecute = false;
|
||||
psi.CreateNoWindow = true;
|
||||
|
||||
Process.Start(psi);
|
||||
return true;
|
||||
}
|
||||
catch (Exception ex)
|
||||
{
|
||||
Console.WriteLine("Falha ao iniciar: " + ex.Message);
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
||||
private static bool IsHealthy(string baseUrl)
|
||||
{
|
||||
try
|
||||
{
|
||||
HttpWebRequest request = (HttpWebRequest)WebRequest.Create(baseUrl + "/api/health");
|
||||
request.Timeout = 1500;
|
||||
request.Method = "GET";
|
||||
using (HttpWebResponse response = (HttpWebResponse)request.GetResponse())
|
||||
{
|
||||
return (int)response.StatusCode == 200;
|
||||
}
|
||||
}
|
||||
catch (Exception)
|
||||
{
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
||||
private static void OpenBrowser(string url)
|
||||
{
|
||||
try
|
||||
{
|
||||
ProcessStartInfo psi = new ProcessStartInfo(url);
|
||||
psi.UseShellExecute = true;
|
||||
Process.Start(psi);
|
||||
}
|
||||
catch (Exception)
|
||||
{
|
||||
Console.WriteLine("Abra manualmente no navegador: " + url);
|
||||
}
|
||||
}
|
||||
|
||||
private static int Fail(string message)
|
||||
{
|
||||
Console.WriteLine();
|
||||
Console.WriteLine("ERRO: " + message);
|
||||
Console.WriteLine();
|
||||
Console.WriteLine("Pressione qualquer tecla para fechar...");
|
||||
try { Console.ReadKey(true); } catch (Exception) { Thread.Sleep(8000); }
|
||||
return 1;
|
||||
}
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user