mirror of
https://github.com/okieselbach/AutopilotMonitor.git
synced 2026-10-24 12:42:38 +00:00
f358d8ebd9
app install metrics platform stats global gather and analyze rule editng Dark mode (needs improvement) UI enhancments
212 lines
8.1 KiB
C#
212 lines
8.1 KiB
C#
using System;
|
|
using System.Collections.Generic;
|
|
using System.Linq;
|
|
using System.Threading.Tasks;
|
|
using AutopilotMonitor.Shared;
|
|
using AutopilotMonitor.Shared.Models;
|
|
using Azure;
|
|
using Azure.Data.Tables;
|
|
using Microsoft.Extensions.Configuration;
|
|
using Microsoft.Extensions.Logging;
|
|
|
|
namespace AutopilotMonitor.Functions.Services
|
|
{
|
|
/// <summary>
|
|
/// Service for managing global admin configuration in Azure Table Storage
|
|
/// </summary>
|
|
public class AdminConfigurationService
|
|
{
|
|
private readonly TableClient _adminTableClient;
|
|
private readonly TableClient _tenantConfigTableClient;
|
|
private readonly ILogger<AdminConfigurationService> _logger;
|
|
|
|
// In-memory cache for admin configuration (to avoid table lookups on every request)
|
|
private AdminConfiguration? _cachedConfig;
|
|
private DateTime? _cacheTime;
|
|
|
|
public AdminConfigurationService(IConfiguration configuration, ILogger<AdminConfigurationService> logger)
|
|
{
|
|
_logger = logger;
|
|
|
|
var connectionString = configuration["AzureWebJobsStorage"];
|
|
var serviceClient = new TableServiceClient(connectionString);
|
|
_adminTableClient = serviceClient.GetTableClient(Constants.TableNames.AdminConfiguration);
|
|
_tenantConfigTableClient = serviceClient.GetTableClient(Constants.TableNames.TenantConfiguration);
|
|
// Tables are initialized centrally by TableInitializerService at startup
|
|
}
|
|
|
|
/// <summary>
|
|
/// Gets global admin configuration (uses cache with 5-minute TTL)
|
|
/// </summary>
|
|
public async Task<AdminConfiguration> GetConfigurationAsync()
|
|
{
|
|
// Check cache first (5-minute TTL)
|
|
if (_cachedConfig != null && _cacheTime.HasValue)
|
|
{
|
|
if (DateTime.UtcNow.Subtract(_cacheTime.Value).TotalMinutes < 5)
|
|
{
|
|
return _cachedConfig;
|
|
}
|
|
}
|
|
|
|
try
|
|
{
|
|
// Load from Table Storage
|
|
var entity = await _adminTableClient.GetEntityAsync<TableEntity>("GlobalConfig", "config");
|
|
var config = ConvertFromTableEntity(entity.Value);
|
|
|
|
// Update cache
|
|
_cachedConfig = config;
|
|
_cacheTime = DateTime.UtcNow;
|
|
|
|
return config;
|
|
}
|
|
catch (RequestFailedException ex) when (ex.Status == 404)
|
|
{
|
|
// Configuration not found - create and save default immediately
|
|
_logger.LogInformation("Admin configuration not found, creating and saving default configuration");
|
|
var defaultConfig = AdminConfiguration.CreateDefault();
|
|
|
|
try
|
|
{
|
|
// Save the default configuration to Table Storage
|
|
var entity = ConvertToTableEntity(defaultConfig);
|
|
await _adminTableClient.UpsertEntityAsync(entity);
|
|
|
|
// Update cache
|
|
_cachedConfig = defaultConfig;
|
|
_cacheTime = DateTime.UtcNow;
|
|
|
|
_logger.LogInformation("Default admin configuration created and saved");
|
|
}
|
|
catch (Exception saveEx)
|
|
{
|
|
_logger.LogError(saveEx, "Failed to save default admin configuration");
|
|
}
|
|
|
|
return defaultConfig;
|
|
}
|
|
catch (Exception ex)
|
|
{
|
|
_logger.LogError(ex, "Error loading admin configuration");
|
|
// Return default on error (fail-open)
|
|
return AdminConfiguration.CreateDefault();
|
|
}
|
|
}
|
|
|
|
/// <summary>
|
|
/// Saves global admin configuration and syncs rate limit to all tenant configurations
|
|
/// </summary>
|
|
public async Task SaveConfigurationAsync(AdminConfiguration config)
|
|
{
|
|
if (config == null)
|
|
{
|
|
throw new ArgumentException("Configuration is required");
|
|
}
|
|
|
|
try
|
|
{
|
|
config.LastUpdated = DateTime.UtcNow;
|
|
|
|
var entity = ConvertToTableEntity(config);
|
|
await _adminTableClient.UpsertEntityAsync(entity);
|
|
|
|
// Invalidate cache
|
|
_cachedConfig = null;
|
|
_cacheTime = null;
|
|
|
|
_logger.LogInformation($"Admin configuration saved by {config.UpdatedBy}");
|
|
|
|
// Sync global rate limit to all tenant configurations (only if not custom)
|
|
await SyncRateLimitToTenantsAsync(config.GlobalRateLimitRequestsPerMinute);
|
|
}
|
|
catch (Exception ex)
|
|
{
|
|
_logger.LogError(ex, "Error saving admin configuration");
|
|
throw;
|
|
}
|
|
}
|
|
|
|
/// <summary>
|
|
/// Syncs the global rate limit to all tenant configurations that don't have a custom override
|
|
/// </summary>
|
|
private async Task SyncRateLimitToTenantsAsync(int globalRateLimit)
|
|
{
|
|
try
|
|
{
|
|
_logger.LogInformation($"Syncing global rate limit ({globalRateLimit}) to all tenant configurations...");
|
|
|
|
var tenantsUpdated = 0;
|
|
var tenantsSkipped = 0;
|
|
|
|
// Query all tenant configurations
|
|
var query = _tenantConfigTableClient.QueryAsync<TableEntity>(filter: $"RowKey eq 'config'");
|
|
|
|
await foreach (var entity in query)
|
|
{
|
|
// Check if tenant has a custom rate limit override
|
|
var customRateLimit = entity.GetInt32("CustomRateLimitRequestsPerMinute");
|
|
|
|
if (customRateLimit.HasValue)
|
|
{
|
|
// Skip tenants with custom rate limit
|
|
tenantsSkipped++;
|
|
_logger.LogDebug($"Skipping tenant {entity.PartitionKey} - has custom rate limit: {customRateLimit}");
|
|
continue;
|
|
}
|
|
|
|
// Update tenant's rate limit to match global default
|
|
entity["RateLimitRequestsPerMinute"] = globalRateLimit;
|
|
entity["LastUpdated"] = DateTime.UtcNow;
|
|
entity["UpdatedBy"] = "System (Global Rate Limit Sync)";
|
|
|
|
await _tenantConfigTableClient.UpdateEntityAsync(entity, ETag.All, TableUpdateMode.Replace);
|
|
tenantsUpdated++;
|
|
}
|
|
|
|
_logger.LogInformation($"Rate limit sync completed: {tenantsUpdated} tenants updated, {tenantsSkipped} tenants skipped (custom rate limit)");
|
|
}
|
|
catch (Exception ex)
|
|
{
|
|
_logger.LogError(ex, "Error syncing rate limit to tenant configurations");
|
|
// Don't throw - we don't want to fail the admin config save if sync fails
|
|
}
|
|
}
|
|
|
|
/// <summary>
|
|
/// Invalidates cache (forces reload on next request)
|
|
/// </summary>
|
|
public void InvalidateCache()
|
|
{
|
|
_cachedConfig = null;
|
|
_cacheTime = null;
|
|
}
|
|
|
|
private TableEntity ConvertToTableEntity(AdminConfiguration config)
|
|
{
|
|
var entity = new TableEntity("GlobalConfig", "config")
|
|
{
|
|
{ "LastUpdated", config.LastUpdated },
|
|
{ "UpdatedBy", config.UpdatedBy },
|
|
{ "GlobalRateLimitRequestsPerMinute", config.GlobalRateLimitRequestsPerMinute },
|
|
{ "CustomSettings", config.CustomSettings }
|
|
};
|
|
|
|
return entity;
|
|
}
|
|
|
|
private AdminConfiguration ConvertFromTableEntity(TableEntity entity)
|
|
{
|
|
return new AdminConfiguration
|
|
{
|
|
PartitionKey = entity.PartitionKey,
|
|
RowKey = entity.RowKey,
|
|
LastUpdated = entity.GetDateTime("LastUpdated") ?? DateTime.UtcNow,
|
|
UpdatedBy = entity.GetString("UpdatedBy") ?? "Unknown",
|
|
GlobalRateLimitRequestsPerMinute = entity.GetInt32("GlobalRateLimitRequestsPerMinute") ?? 100,
|
|
CustomSettings = entity.GetString("CustomSettings")
|
|
};
|
|
}
|
|
}
|
|
}
|