fix: ajustes diversos
All checks were successful
Deploy QR Rapido / test (push) Successful in 3m51s
Deploy QR Rapido / build-and-push (push) Successful in 14m7s
Deploy QR Rapido / deploy-staging (push) Has been skipped
Deploy QR Rapido / deploy-production (push) Successful in 1m41s

This commit is contained in:
Ricardo Carneiro 2025-09-20 22:46:08 -03:00
parent 49da9f874a
commit 5ba0d62595
7 changed files with 110 additions and 35 deletions

View File

@ -104,7 +104,6 @@ jobs:
--restart unless-stopped \ --restart unless-stopped \
-p 5000:8080 \ -p 5000:8080 \
--add-host=host.docker.internal:host-gateway \ --add-host=host.docker.internal:host-gateway \
-e Serilog__SeqUrl="http://host.docker.internal:5343" \
-e ASPNETCORE_ENVIRONMENT=Staging \ -e ASPNETCORE_ENVIRONMENT=Staging \
${{ env.REGISTRY }}/${{ env.IMAGE_NAME }}:develop ${{ env.REGISTRY }}/${{ env.IMAGE_NAME }}:develop
EOF EOF
@ -127,7 +126,6 @@ jobs:
--restart unless-stopped \ --restart unless-stopped \
-p 5000:8080 \ -p 5000:8080 \
--add-host=host.docker.internal:host-gateway \ --add-host=host.docker.internal:host-gateway \
-e Serilog__SeqUrl="http://host.docker.internal:5342" \
-e ASPNETCORE_ENVIRONMENT=Staging \ -e ASPNETCORE_ENVIRONMENT=Staging \
${{ env.REGISTRY }}/${{ env.IMAGE_NAME }}:develop ${{ env.REGISTRY }}/${{ env.IMAGE_NAME }}:develop
EOF EOF
@ -172,14 +170,19 @@ jobs:
# Puxa nova imagem # Puxa nova imagem
docker pull ${{ env.REGISTRY }}/${{ env.IMAGE_NAME }}:latest docker pull ${{ env.REGISTRY }}/${{ env.IMAGE_NAME }}:latest
# Cria o diretório de chaves no host e define as permissões corretas
sudo mkdir -p /app/keys
sudo chown -R 1000:1000 /app/keys
# Executa novo container # Executa novo container
docker run -d \ docker run -d \
--name qrrapido-prod \ --name qrrapido-prod \
--restart unless-stopped \ --restart unless-stopped \
-p 5001:8080 \ --network host \
-v /app/keys:/app/keys \ -v /app/keys:/app/keys \
-e ASPNETCORE_ENVIRONMENT=Production \ -e ASPNETCORE_ENVIRONMENT=Production \
-e ASPNETCORE_URLS=http://+:8080 \ -e ASPNETCORE_URLS=http://+:5001 \
-e Serilog__OpenSearchUrl="http://localhost:9201" \
${{ env.REGISTRY }}/${{ env.IMAGE_NAME }}:latest ${{ env.REGISTRY }}/${{ env.IMAGE_NAME }}:latest
# Recarrega NGINX para garantir que está apontando para o novo container # Recarrega NGINX para garantir que está apontando para o novo container
@ -198,14 +201,19 @@ jobs:
# Puxa nova imagem # Puxa nova imagem
docker pull ${{ env.REGISTRY }}/${{ env.IMAGE_NAME }}:latest docker pull ${{ env.REGISTRY }}/${{ env.IMAGE_NAME }}:latest
# Cria o diretório de chaves no host e define as permissões corretas
sudo mkdir -p /app/keys
sudo chown -R 1000:1000 /app/keys
# Executa novo container # Executa novo container
docker run -d \ docker run -d \
--name qrrapido-prod \ --name qrrapido-prod \
--restart unless-stopped \ --restart unless-stopped \
-p 5001:8080 \ --network host \
-v /app/keys:/app/keys \ -v /app/keys:/app/keys \
-e ASPNETCORE_ENVIRONMENT=Production \ -e ASPNETCORE_ENVIRONMENT=Production \
-e ASPNETCORE_URLS=http://+:8080 \ -e ASPNETCORE_URLS=http://+:5001 \
-e Serilog__OpenSearchUrl="http://localhost:9202" \
${{ env.REGISTRY }}/${{ env.IMAGE_NAME }}:latest ${{ env.REGISTRY }}/${{ env.IMAGE_NAME }}:latest
EOF EOF

1
.gitignore vendored
View File

@ -352,6 +352,7 @@ MigrationBackup/
FodyWeavers.xsd FodyWeavers.xsd
# Custom # Custom
keys/
appsettings.Local.json appsettings.Local.json
appsettings.Production.json appsettings.Production.json
*.Development.json *.Development.json

View File

@ -2,6 +2,7 @@ using Microsoft.AspNetCore.Mvc;
using Microsoft.Extensions.Diagnostics.HealthChecks; using Microsoft.Extensions.Diagnostics.HealthChecks;
using System.Diagnostics; using System.Diagnostics;
using System.Text.Json; using System.Text.Json;
using System.Threading;
namespace QRRapidoApp.Controllers namespace QRRapidoApp.Controllers
{ {
@ -14,7 +15,7 @@ namespace QRRapidoApp.Controllers
private readonly ILogger<HealthController> _logger; private readonly ILogger<HealthController> _logger;
private readonly string _applicationName; private readonly string _applicationName;
private readonly string _version; private readonly string _version;
private static readonly DateTime _startTime = DateTime.UtcNow; private static readonly DateTime _startTime = System.Diagnostics.Process.GetCurrentProcess().StartTime.ToUniversalTime();
public HealthController( public HealthController(
HealthCheckService healthCheckService, HealthCheckService healthCheckService,
@ -228,13 +229,17 @@ namespace QRRapidoApp.Controllers
// For Uptime Kuma, we want to return 200 OK for healthy/degraded and 503 for unhealthy // For Uptime Kuma, we want to return 200 OK for healthy/degraded and 503 for unhealthy
var statusCode = overallStatus == "unhealthy" ? 503 : 200; var statusCode = overallStatus == "unhealthy" ? 503 : 200;
var uptimeMinutes = (DateTime.UtcNow - _startTime).TotalMinutes;
var memoryBytes = GC.GetTotalMemory(false);
var memoryMB = memoryBytes / 1024.0 / 1024.0;
var response = new var response = new
{ {
status = overallStatus, status = overallStatus,
application = _applicationName, application = _applicationName,
version = _version, version = _version,
timestamp = DateTime.UtcNow.ToString("yyyy-MM-ddTHH:mm:ssZ"), timestamp = DateTime.UtcNow.ToString("yyyy-MM-ddTHH:mm:ssZ"),
uptime = $"{(DateTime.UtcNow - _startTime).TotalHours:F1}h", uptime = System.FormattableString.Invariant($"{(DateTime.UtcNow - _startTime).TotalHours:F1}h"),
// Include critical metrics for monitoring // Include critical metrics for monitoring
metrics = new metrics = new
{ {
@ -245,6 +250,14 @@ namespace QRRapidoApp.Controllers
} }
}; };
_logger.LogInformation("[HEALTH] Memory: {memoryBytes} MemoryMB: {memoryMB} ThreadPool: {threadPool} ProcessId: {processId} ActiveConnections: {activeConnections} UptimeMinutes: {uptimeMinutes}",
memoryBytes,
memoryMB,
ThreadPool.ThreadCount,
Process.GetCurrentProcess().Id,
HttpContext.Connection?.Id ?? "null",
Math.Round(uptimeMinutes, 1));
return StatusCode(statusCode, response); return StatusCode(statusCode, response);
} }
catch (Exception ex) catch (Exception ex)

View File

@ -17,6 +17,7 @@ using Stripe;
using System.Globalization; using System.Globalization;
using Serilog; using Serilog;
using Serilog.Events; using Serilog.Events;
using Serilog.Sinks.OpenSearch;
using Serilog.Sinks.SystemConsole.Themes; using Serilog.Sinks.SystemConsole.Themes;
using Microsoft.AspNetCore.Mvc.Razor; using Microsoft.AspNetCore.Mvc.Razor;
using Microsoft.AspNetCore.HttpOverrides; using Microsoft.AspNetCore.HttpOverrides;
@ -25,23 +26,71 @@ using Microsoft.AspNetCore.RateLimiting;
using System.Threading.RateLimiting; using System.Threading.RateLimiting;
using Microsoft.AspNetCore.Server.Kestrel.Core; using Microsoft.AspNetCore.Server.Kestrel.Core;
var builder = WebApplication.CreateBuilder(args); // Fix for WSL path issues - disable StaticWebAssets completely
var options = new WebApplicationOptions
{
Args = args,
ContentRootPath = Directory.GetCurrentDirectory(),
WebRootPath = Path.Combine(Directory.GetCurrentDirectory(), "wwwroot")
};
// Disable StaticWebAssets for WSL compatibility
Environment.SetEnvironmentVariable("ASPNETCORE_HOSTINGSTARTUP__STATICWEBASSETS__ENABLED", "false");
var builder = WebApplication.CreateBuilder(options);
// Configure Serilog // Configure Serilog
Log.Logger = new LoggerConfiguration() var loggerConfig = new LoggerConfiguration()
.ReadFrom.Configuration(builder.Configuration) .ReadFrom.Configuration(builder.Configuration)
.Enrich.FromLogContext() .Enrich.FromLogContext()
.Enrich.WithEnvironmentName() .Enrich.WithEnvironmentName()
.Enrich.WithProcessId() .Enrich.WithProcessId()
.Enrich.WithThreadId() .Enrich.WithThreadId()
.Enrich.WithProperty("ApplicationName", builder.Configuration["ApplicationName"] ?? "QRRapido") .Enrich.WithProperty("ApplicationName", builder.Configuration["ApplicationName"] ?? "QRRapido")
.Enrich.WithProperty("Environment", "Dev") .Enrich.WithProperty("Environment", builder.Environment.EnvironmentName)
.WriteTo.Async(a => a.Console( .WriteTo.Async(a => a.Console(
outputTemplate: "[{Timestamp:HH:mm:ss} {Level:u3}] {Message:lj} {Properties:j}{NewLine}{Exception}", outputTemplate: "[{Timestamp:HH:mm:ss} {Level:u3}] {Message:lj} {Properties:j}{NewLine}{Exception}",
theme: AnsiConsoleTheme.Code)) theme: AnsiConsoleTheme.Code));
.WriteTo.Async(a => a.Seq(builder.Configuration["Serilog:SeqUrl"],
apiKey: builder.Configuration["Serilog:ApiKey"]=="" ? null : builder.Configuration["Serilog:ApiKey"])) var openSearchUrl = builder.Configuration["Serilog:OpenSearchUrl"];
.CreateLogger(); if (!string.IsNullOrEmpty(openSearchUrl))
{
var environment = builder.Environment.EnvironmentName.ToLower();
var envMapping = environment switch
{
"Production" => "prod",
"Staging" => "staging",
"Development" => "dev",
_ => environment
};
var indexFormat = $"qrrapido-logs-{envMapping}-{{0:yyyy-MM-dd}}";
try
{
loggerConfig.WriteTo.Async(a => a.OpenSearch(new OpenSearchSinkOptions(new Uri(openSearchUrl))
{
IndexFormat = indexFormat,
AutoRegisterTemplate = true,
BufferBaseFilename = "./logs/buffer",
ModifyConnectionSettings = conn => conn
.RequestTimeout(TimeSpan.FromSeconds(30))
.PingTimeout(TimeSpan.FromSeconds(10)),
MinimumLogEventLevel = LogEventLevel.Information,
EmitEventFailure = EmitEventFailureHandling.WriteToSelfLog,
RegisterTemplateFailure = RegisterTemplateRecovery.IndexAnyway,
BatchPostingLimit = 50,
Period = TimeSpan.FromSeconds(5),
}), bufferSize: 10000, blockWhenFull: false);
}
catch (Exception ex)
{
// Fails silently, logs will continue on console.
loggerConfig.WriteTo.Console(outputTemplate: $"Error setting up OpenSearch sink: {ex.Message}");
}
}
Log.Logger = loggerConfig.CreateLogger();
builder.Host.UseSerilog(); builder.Host.UseSerilog();
@ -89,8 +138,10 @@ builder.Services.AddMemoryCache();
builder.Services.AddSingleton<IDistributedCache, MemoryDistributedCacheWrapper>(); builder.Services.AddSingleton<IDistributedCache, MemoryDistributedCacheWrapper>();
// ✅ DataProtection compartilhado via FileSystem (ADICIONAR APÓS O CACHE) // ✅ DataProtection compartilhado via FileSystem (ADICIONAR APÓS O CACHE)
var keysDirectory = Path.Combine(Directory.GetCurrentDirectory(), "keys");
Directory.CreateDirectory(keysDirectory);
builder.Services.AddDataProtection() builder.Services.AddDataProtection()
.PersistKeysToFileSystem(new DirectoryInfo("/app/keys")) .PersistKeysToFileSystem(new DirectoryInfo(keysDirectory))
.SetApplicationName("QRRapido"); .SetApplicationName("QRRapido");

View File

@ -20,7 +20,7 @@
<PackageReference Include="Serilog.Enrichers.Thread" Version="4.0.0" /> <PackageReference Include="Serilog.Enrichers.Thread" Version="4.0.0" />
<PackageReference Include="Serilog.Sinks.Async" Version="2.1.0" /> <PackageReference Include="Serilog.Sinks.Async" Version="2.1.0" />
<PackageReference Include="Serilog.Sinks.Console" Version="6.0.1-dev-00953" /> <PackageReference Include="Serilog.Sinks.Console" Version="6.0.1-dev-00953" />
<PackageReference Include="Serilog.Sinks.Seq" Version="9.0.0" /> <PackageReference Include="Serilog.Sinks.OpenSearch" Version="1.3.0" />
<PackageReference Include="SixLabors.ImageSharp" Version="3.1.6" /> <PackageReference Include="SixLabors.ImageSharp" Version="3.1.6" />
<PackageReference Include="SixLabors.ImageSharp.Drawing" Version="2.1.4" /> <PackageReference Include="SixLabors.ImageSharp.Drawing" Version="2.1.4" />
<PackageReference Include="Stripe.net" Version="48.4.0" /> <PackageReference Include="Stripe.net" Version="48.4.0" />

View File

@ -137,22 +137,6 @@
</div> </div>
</div> </div>
<div class="row">
<div class="col-md-6 mb-3">
<div class="d-grid opacity-controlled disabled-state" id="next-button-group">
<button type="button" class="btn btn-success" id="next-btn">
<i class="fas fa-arrow-right"></i> Personalizar
</button>
</div>
</div>
<div class="col-md-6 mb-3">
<div class="d-grid opacity-controlled disabled-state" id="button-gerar-quick-div">
<button type="submit" class="btn btn-primary" id="generate-quick-btn">
<i class="fas fa-qrcode"></i> Gerar Rápido
</button>
</div>
</div>
</div>
<!-- Dynamic QR Section (Premium) --> <!-- Dynamic QR Section (Premium) -->
<div id="dynamic-qr-section" style="display: none;" class="opacity-controlled disabled-state"> <div id="dynamic-qr-section" style="display: none;" class="opacity-controlled disabled-state">
@ -520,6 +504,24 @@
</div> </div>
</div> </div>
<!-- Navigation Buttons (Global - appears for all QR types) -->
<div class="row mb-4 opacity-controlled disabled-state" id="navigation-buttons">
<div class="col-md-6 mb-3">
<div class="d-grid opacity-controlled disabled-state" id="next-button-group">
<button type="button" class="btn btn-success" id="next-btn">
<i class="fas fa-arrow-right"></i> Personalizar
</button>
</div>
</div>
<div class="col-md-6 mb-3">
<div class="d-grid opacity-controlled disabled-state" id="button-gerar-quick-div">
<button type="submit" class="btn btn-primary" id="generate-quick-btn">
<i class="fas fa-qrcode"></i> Gerar Rápido
</button>
</div>
</div>
</div>
<!-- Advanced customization (collapsible) --> <!-- Advanced customization (collapsible) -->
<div class="accordion mb-3 opacity-controlled disabled-state" id="customization-accordion"> <div class="accordion mb-3 opacity-controlled disabled-state" id="customization-accordion">
<div class="accordion-item"> <div class="accordion-item">
@ -1305,6 +1307,7 @@ document.addEventListener('DOMContentLoaded', function() {
new SimpleOpacityController('#qr-type', '#wifi-interface'); new SimpleOpacityController('#qr-type', '#wifi-interface');
new SimpleOpacityController('#qr-type', '#sms-interface'); new SimpleOpacityController('#qr-type', '#sms-interface');
new SimpleOpacityController('#qr-type', '#email-interface'); new SimpleOpacityController('#qr-type', '#email-interface');
new SimpleOpacityController('#qr-type', '#navigation-buttons');
new SimpleOpacityController('#qr-type', '#customization-accordion'); new SimpleOpacityController('#qr-type', '#customization-accordion');
new SimpleOpacityController('#qr-type', '#button-gerar-div'); new SimpleOpacityController('#qr-type', '#button-gerar-div');

View File

@ -59,8 +59,7 @@
"ApplicationName": "QRRapido", "ApplicationName": "QRRapido",
"Environment": "Personal", "Environment": "Personal",
"Serilog": { "Serilog": {
"SeqUrl": "http://172.17.0.1:5341", "OpenSearchUrl": "http://localhost:9200",
"ApiKey": "",
"MinimumLevel": { "MinimumLevel": {
"Default": "Information", "Default": "Information",
"Override": { "Override": {