288 lines
11 KiB
C#
288 lines
11 KiB
C#
using System;
|
|
using System.Collections.Concurrent;
|
|
using System.Collections.Generic;
|
|
using System.Linq;
|
|
using System.Text.Json.Serialization;
|
|
using System.Threading.Tasks;
|
|
using FinlyticBackend.Util;
|
|
using FinlyticCore.Dtos;
|
|
using FinlyticCore.Util;
|
|
using Microsoft.AspNetCore.Authorization;
|
|
using Microsoft.AspNetCore.Cors;
|
|
using Microsoft.AspNetCore.Mvc;
|
|
using Microsoft.Extensions.Logging;
|
|
|
|
namespace FinlyticBackend.Controllers;
|
|
|
|
/// <summary>
|
|
/// Represents a single configuration item for a microservice.
|
|
/// </summary>
|
|
public record ServiceConfigItem(
|
|
string Key,
|
|
string Value,
|
|
string DataType,
|
|
string Description,
|
|
DateTime UpdatedAt
|
|
);
|
|
|
|
/// <summary>
|
|
/// DTO for returning service configuration items (AOT-compliant).
|
|
/// </summary>
|
|
public record ServiceConfigItemResponseDto(
|
|
[property: JsonPropertyName("key")] string Key,
|
|
[property: JsonPropertyName("value")] string Value,
|
|
[property: JsonPropertyName("dataType")] string DataType,
|
|
[property: JsonPropertyName("description")] string Description,
|
|
[property: JsonPropertyName("updatedAt")] DateTime UpdatedAt
|
|
);
|
|
|
|
/// <summary>
|
|
/// DTO representing the operational health status of a microservice (AOT-compliant).
|
|
/// </summary>
|
|
public record ServiceHealthStatusDto(
|
|
[property: JsonPropertyName("name")] string Name,
|
|
[property: JsonPropertyName("type")] string Type,
|
|
[property: JsonPropertyName("status")] string Status,
|
|
[property: JsonPropertyName("port")] string Port,
|
|
[property: JsonPropertyName("communication")] string Communication,
|
|
[property: JsonPropertyName("db")] string Db,
|
|
[property: JsonPropertyName("lastPing")] DateTime LastPing
|
|
);
|
|
|
|
[ApiController]
|
|
[Route("api/v1/admin/settings")]
|
|
[Authorize(Roles = "Admin")]
|
|
[EnableCors("AllowAll")]
|
|
public class AdminSettingsController : ControllerBase
|
|
{
|
|
private readonly WebMqttClient _mqttClient;
|
|
private readonly ILogger<AdminSettingsController> _logger;
|
|
|
|
// In-memory static store for default UI configuration templates
|
|
private static readonly ConcurrentDictionary<string, List<ServiceConfigItem>> _inMemorySettings = new(StringComparer.OrdinalIgnoreCase);
|
|
|
|
static AdminSettingsController()
|
|
{
|
|
// Default-Templates für Microservice-Konfigurationen initialisieren
|
|
_inMemorySettings["FinlyticAnalyzer"] = new List<ServiceConfigItem>
|
|
{
|
|
new("MinSignalScore", "75.0", "double", "Mindest-Score für KI-Trade-Proposals (0-100)", DateTime.UtcNow),
|
|
new("VixPanicThreshold", "30.0", "double", "VIX-Wert ab dem Panik-Modus aktiviert wird", DateTime.UtcNow),
|
|
new("ProposalTtlMinutes", "180", "int", "Gültigkeitsdauer von Trade-Proposals in Minuten", DateTime.UtcNow)
|
|
};
|
|
|
|
_inMemorySettings["FinlyticNews"] = new List<ServiceConfigItem>
|
|
{
|
|
new("ScrapeIntervalMinutes", "15", "int", "Intervall für das Scraping neuer Nachrichten", DateTime.UtcNow),
|
|
new("FinBertBatchSize", "8", "int", "Batch-Größe für die Sentiment-Analyse", DateTime.UtcNow)
|
|
};
|
|
|
|
_inMemorySettings["FinlyticTechnicalAnalysis"] = new List<ServiceConfigItem>
|
|
{
|
|
new("EmaShortPeriod", "20", "int", "Kurze Periode für EMA-Berechnungen", DateTime.UtcNow),
|
|
new("EmaLongPeriod", "50", "int", "Lange Periode für EMA-Berechnungen", DateTime.UtcNow),
|
|
new("RsiPeriod", "14", "int", "Standard-Periode für RSI-Berechnung", DateTime.UtcNow)
|
|
};
|
|
|
|
_inMemorySettings["FinlyticTrades"] = new List<ServiceConfigItem>
|
|
{
|
|
new("ExportFeedbackIntervalHours", "6", "int", "Intervall für den Parquet/JSON Feedback-Export", DateTime.UtcNow),
|
|
new("DefaultLeverageLimit", "10", "decimal", "Standardmäßiger Maximalhebel für Derivate", DateTime.UtcNow)
|
|
};
|
|
}
|
|
|
|
public AdminSettingsController(
|
|
WebMqttClient mqttClient,
|
|
ILogger<AdminSettingsController> logger)
|
|
{
|
|
_mqttClient = mqttClient;
|
|
_logger = logger;
|
|
}
|
|
|
|
/// <summary>
|
|
/// Retrieves all service configurations grouped by service name.
|
|
/// </summary>
|
|
[HttpGet]
|
|
public IActionResult GetAllSettings()
|
|
{
|
|
var grouped = _inMemorySettings.ToDictionary(
|
|
g => g.Key,
|
|
g => g.Value.Select(item => new ServiceConfigItemResponseDto(
|
|
Key: item.Key,
|
|
Value: item.Value,
|
|
DataType: item.DataType,
|
|
Description: item.Description,
|
|
UpdatedAt: item.UpdatedAt
|
|
)).ToList()
|
|
);
|
|
|
|
return Ok(grouped);
|
|
}
|
|
|
|
/// <summary>
|
|
/// Performs live MQTT RPC health pings across all microservices and returns their real operational status.
|
|
/// </summary>
|
|
[HttpGet("health")]
|
|
public async Task<IActionResult> GetServicesHealth()
|
|
{
|
|
var servicesToCheck = new (string Name, string Channel, string Type, string Db)[]
|
|
{
|
|
("FinlyticAssets", "health_Ping/FinlyticAssets", "Asset Catalog & Scraper", "PostgreSQL assets"),
|
|
("FinlyticNews", "health_Ping/FinlyticNews", "News RSS Scraper & AI", "PostgreSQL news"),
|
|
("FinlyticTechnicalAnalysis", "health_Ping/FinlyticTechnicalAnalysis", "Technical Indicators (EMA/RSI)", "PostgreSQL ta"),
|
|
("FinlyticSentiment", "health_Ping/FinlyticSentiment", "NLP Sentiment Engine", "PostgreSQL sentiment"),
|
|
("FinlyticAnalyzer", "health_Ping/FinlyticAnalyzer", "Multi-Layer Signal Engine", "PostgreSQL analyzer"),
|
|
("FinlyticTrades", "health_Ping/FinlyticTrades", "Trade Lifecycle Manager", "PostgreSQL trades"),
|
|
("FinlyticFundamentals", "health_Ping/FinlyticFundamentals", "Financial Statements & Estimates", "PostgreSQL fundamentals"),
|
|
};
|
|
|
|
var results = new List<ServiceHealthStatusDto>
|
|
{
|
|
new(
|
|
Name: "FinlyticBackend",
|
|
Type: "REST API & SignalR Gateway",
|
|
Status: "Online",
|
|
Port: "5000",
|
|
Communication: "Kestrel HTTP / WebSocket",
|
|
Db: "PostgreSQL backend",
|
|
LastPing: DateTime.UtcNow
|
|
)
|
|
};
|
|
|
|
var tasks = servicesToCheck.Select(async s =>
|
|
{
|
|
try
|
|
{
|
|
if (_mqttClient.IsConnected)
|
|
{
|
|
var resp = await _mqttClient.SendRpcRequestAsync<ServiceHealthResponse, EmptyRequest>(
|
|
s.Channel,
|
|
new EmptyRequest(),
|
|
TimeSpan.FromMilliseconds(1200)
|
|
);
|
|
|
|
if (resp != null)
|
|
{
|
|
return new ServiceHealthStatusDto(
|
|
Name: s.Name,
|
|
Type: s.Type,
|
|
Status: "Online",
|
|
Port: "MQTT Only (No HTTP Port)",
|
|
Communication: "MQTT RPC & Pub/Sub",
|
|
Db: s.Db,
|
|
LastPing: resp.Timestamp
|
|
);
|
|
}
|
|
}
|
|
}
|
|
catch (Exception ex)
|
|
{
|
|
_logger.LogWarning(ex, "[AdminSettings] Health ping timed out or failed for service {ServiceName}", s.Name);
|
|
}
|
|
|
|
return new ServiceHealthStatusDto(
|
|
Name: s.Name,
|
|
Type: s.Type,
|
|
Status: "Offline",
|
|
Port: "MQTT Only (No HTTP Port)",
|
|
Communication: "MQTT (No Response / Timeout)",
|
|
Db: s.Db,
|
|
LastPing: DateTime.UtcNow
|
|
);
|
|
});
|
|
|
|
var pingResults = await Task.WhenAll(tasks);
|
|
results.AddRange(pingResults);
|
|
|
|
return Ok(results);
|
|
}
|
|
|
|
/// <summary>
|
|
/// Retrieves settings for a specific service.
|
|
/// </summary>
|
|
[HttpGet("{serviceName}")]
|
|
public IActionResult GetServiceSettings(string serviceName)
|
|
{
|
|
if (_inMemorySettings.TryGetValue(serviceName, out var list))
|
|
{
|
|
var dtos = list.Select(item => new ServiceConfigItemResponseDto(
|
|
Key: item.Key,
|
|
Value: item.Value,
|
|
DataType: item.DataType,
|
|
Description: item.Description,
|
|
UpdatedAt: item.UpdatedAt
|
|
)).ToList();
|
|
|
|
return Ok(dtos);
|
|
}
|
|
|
|
return Ok(new List<ServiceConfigItemResponseDto>());
|
|
}
|
|
|
|
/// <summary>
|
|
/// Broadcasts configuration settings to the targeted microservice via MQTT.
|
|
/// Does NOT write to Backend database. The microservice persists updated settings directly into its own database.
|
|
/// </summary>
|
|
[HttpPut("{serviceName}")]
|
|
public async Task<IActionResult> UpdateServiceSettings(string serviceName, [FromBody] Dictionary<string, string> updatedValues)
|
|
{
|
|
if (updatedValues == null || !updatedValues.Any())
|
|
{
|
|
return BadRequest(new { message = "No settings provided for update." });
|
|
}
|
|
|
|
_logger.LogInformation("[AdminSettings] Transmitting {Count} config settings to microservice '{ServiceName}' via MQTT", updatedValues.Count, serviceName);
|
|
|
|
// In-Memory Template-Store aktualisieren
|
|
if (_inMemorySettings.TryGetValue(serviceName, out var existingList))
|
|
{
|
|
foreach (var (key, value) in updatedValues)
|
|
{
|
|
var idx = existingList.FindIndex(item => item.Key.Equals(key, StringComparison.OrdinalIgnoreCase));
|
|
if (idx >= 0)
|
|
{
|
|
var old = existingList[idx];
|
|
existingList[idx] = old with { Value = value, UpdatedAt = DateTime.UtcNow };
|
|
}
|
|
else
|
|
{
|
|
existingList.Add(new ServiceConfigItem(key, value, "string", $"Setting for {serviceName}", DateTime.UtcNow));
|
|
}
|
|
}
|
|
}
|
|
else
|
|
{
|
|
var newList = updatedValues.Select(kv => new ServiceConfigItem(kv.Key, kv.Value, "string", $"Setting for {serviceName}", DateTime.UtcNow)).ToList();
|
|
_inMemorySettings[serviceName] = newList;
|
|
}
|
|
|
|
// MQTT Config-Update Event senden
|
|
bool mqttPublished = false;
|
|
try
|
|
{
|
|
if (_mqttClient.IsConnected)
|
|
{
|
|
string topic = $"services/config/updated/{serviceName}";
|
|
var payload = new ServiceConfigUpdatePayload(
|
|
ServiceName: serviceName,
|
|
Timestamp: DateTime.UtcNow,
|
|
Settings: updatedValues
|
|
);
|
|
|
|
await _mqttClient.PublishAsync(topic, payload);
|
|
mqttPublished = true;
|
|
_logger.LogInformation("[AdminSettings] Broadcasted config update event to MQTT topic '{Topic}' for microservice persistence.", topic);
|
|
}
|
|
}
|
|
catch (Exception ex)
|
|
{
|
|
_logger.LogError(ex, "[AdminSettings] Failed to publish MQTT config update event for service '{ServiceName}'", serviceName);
|
|
}
|
|
|
|
return Ok(new
|
|
{
|
|
message = $"Settings successfully transmitted to microservice {serviceName}.",
|
|
mqttDispatched = mqttPublished
|
|
});
|
|
}
|
|
} |