mirror of
https://github.com/okieselbach/AutopilotMonitor.git
synced 2026-10-24 04:32:32 +00:00
Add Data Access Layer with repository interfaces and Table Storage implementations
Introduces a clean abstraction layer to decouple domain services from Azure Table Storage, enabling future migration to Cosmos DB and Event Streaming without touching business logic. Architecture: - 10 repository interfaces in Shared/DataAccess/ (ISessionRepository, IRuleRepository, etc.) - IDataEventPublisher interface with NullDataEventPublisher for future event streaming - IStorageInitializer for provider-agnostic initialization - 5 Table Storage implementations that delegate to existing TableStorageService - DI extension method AddTableStorageDataAccess() with AddEventStreaming<T>() hook - Migration plan in tasks/todo.md for incremental adoption https://claude.ai/code/session_01NHWJfPaa8TQHuPzMD7gG63
This commit is contained in:
@@ -0,0 +1,58 @@
|
||||
using AutopilotMonitor.Shared.DataAccess;
|
||||
using AutopilotMonitor.Functions.DataAccess.TableStorage;
|
||||
using Microsoft.Extensions.DependencyInjection;
|
||||
|
||||
namespace AutopilotMonitor.Functions.DataAccess
|
||||
{
|
||||
/// <summary>
|
||||
/// Extension methods for registering Data Access Layer services.
|
||||
/// Currently registers Azure Table Storage implementations.
|
||||
/// To switch to Cosmos DB, replace the implementation registrations here.
|
||||
/// </summary>
|
||||
public static class DataAccessServiceExtensions
|
||||
{
|
||||
/// <summary>
|
||||
/// Registers all Data Access Layer services with Table Storage implementations.
|
||||
/// </summary>
|
||||
public static IServiceCollection AddTableStorageDataAccess(this IServiceCollection services)
|
||||
{
|
||||
// Event publisher (no-op by default; replace with Event Hub/Service Bus implementation)
|
||||
services.AddSingleton<IDataEventPublisher, NullDataEventPublisher>();
|
||||
|
||||
// Storage initializer
|
||||
services.AddSingleton<IStorageInitializer, TableStorageInitializer>();
|
||||
|
||||
// Repository implementations (delegate to existing TableStorageService)
|
||||
services.AddSingleton<ISessionRepository, TableSessionRepository>();
|
||||
services.AddSingleton<IRuleRepository, TableRuleRepository>();
|
||||
services.AddSingleton<IMetricsRepository, TableMetricsRepository>();
|
||||
services.AddSingleton<IMaintenanceRepository, TableMaintenanceRepository>();
|
||||
services.AddSingleton<IVulnerabilityRepository, TableVulnerabilityRepository>();
|
||||
|
||||
// Note: IConfigRepository, IAdminRepository, IDeviceSecurityRepository,
|
||||
// IBootstrapRepository, and INotificationRepository implementations will be
|
||||
// added as existing services are migrated to use the DAL interfaces.
|
||||
// Currently those services (TenantConfigurationService, GlobalAdminService, etc.)
|
||||
// manage their own Table Storage connections directly.
|
||||
|
||||
return services;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Replaces the default NullDataEventPublisher with a custom implementation.
|
||||
/// Call this AFTER AddTableStorageDataAccess() to override.
|
||||
/// Example: services.AddEventStreaming<EventHubPublisher>();
|
||||
/// </summary>
|
||||
public static IServiceCollection AddEventStreaming<TPublisher>(this IServiceCollection services)
|
||||
where TPublisher : class, IDataEventPublisher
|
||||
{
|
||||
// Remove existing registration and replace
|
||||
var descriptor = services.FirstOrDefault(d => d.ServiceType == typeof(IDataEventPublisher));
|
||||
if (descriptor != null)
|
||||
services.Remove(descriptor);
|
||||
|
||||
services.AddSingleton<IDataEventPublisher, TPublisher>();
|
||||
return services;
|
||||
}
|
||||
}
|
||||
}
|
||||
+63
@@ -0,0 +1,63 @@
|
||||
using AutopilotMonitor.Shared.DataAccess;
|
||||
using AutopilotMonitor.Shared.Models;
|
||||
using AutopilotMonitor.Functions.Services;
|
||||
|
||||
namespace AutopilotMonitor.Functions.DataAccess.TableStorage
|
||||
{
|
||||
/// <summary>
|
||||
/// Table Storage implementation of IMaintenanceRepository.
|
||||
/// Delegates to existing TableStorageService for backwards compatibility.
|
||||
/// </summary>
|
||||
public class TableMaintenanceRepository : IMaintenanceRepository
|
||||
{
|
||||
private readonly TableStorageService _storage;
|
||||
|
||||
public TableMaintenanceRepository(TableStorageService storage)
|
||||
{
|
||||
_storage = storage;
|
||||
}
|
||||
|
||||
public Task<bool> LogAuditEntryAsync(string tenantId, string action, string entityType,
|
||||
string entityId, string performedBy, Dictionary<string, string>? details = null)
|
||||
=> _storage.LogAuditEntryAsync(tenantId, action, entityType, entityId, performedBy, details);
|
||||
|
||||
public Task<List<AuditLogEntry>> GetAuditLogsAsync(string tenantId, int maxResults = 100)
|
||||
=> _storage.GetAuditLogsAsync(tenantId, maxResults);
|
||||
|
||||
public Task<List<AuditLogEntry>> GetAllAuditLogsAsync(int maxResults = 100)
|
||||
=> _storage.GetAllAuditLogsAsync(maxResults);
|
||||
|
||||
public Task<List<SessionSummary>> GetSessionsOlderThanAsync(string tenantId, DateTime cutoffDate)
|
||||
=> _storage.GetSessionsOlderThanAsync(tenantId, cutoffDate);
|
||||
|
||||
public Task<List<SessionSummary>> GetSessionsByDateRangeAsync(DateTime startDate, DateTime endDate, string? tenantId = null)
|
||||
=> _storage.GetSessionsByDateRangeAsync(startDate, endDate, tenantId);
|
||||
|
||||
public Task<List<SessionSummary>> GetStalledSessionsAsync(string tenantId, DateTime cutoffTime)
|
||||
=> _storage.GetStalledSessionsAsync(tenantId, cutoffTime);
|
||||
|
||||
public Task<List<SessionSummary>> GetExcessiveDataSendersAsync(string tenantId, DateTime windowCutoff, int maxSessionWindowHours)
|
||||
=> _storage.GetExcessiveDataSendersAsync(tenantId, windowCutoff, maxSessionWindowHours);
|
||||
|
||||
public Task<List<string>> GetAllTenantIdsAsync()
|
||||
=> _storage.GetAllTenantIdsAsync();
|
||||
|
||||
public Task<int> DeleteSessionEventsAsync(string tenantId, string sessionId)
|
||||
=> _storage.DeleteSessionEventsAsync(tenantId, sessionId);
|
||||
|
||||
public Task<int> DeleteSessionRuleResultsAsync(string tenantId, string sessionId)
|
||||
=> _storage.DeleteSessionRuleResultsAsync(tenantId, sessionId);
|
||||
|
||||
public Task<int> DeleteSessionAppInstallSummariesAsync(string tenantId, string sessionId)
|
||||
=> _storage.DeleteSessionAppInstallSummariesAsync(tenantId, sessionId);
|
||||
|
||||
public Task<int> BackfillSessionIndexAsync()
|
||||
=> _storage.BackfillSessionIndexAsync();
|
||||
|
||||
public Task<int> CleanupGhostSessionIndexEntriesAsync()
|
||||
=> _storage.CleanupGhostSessionIndexEntriesAsync();
|
||||
|
||||
public Task<bool> IsSessionIndexEmptyAsync()
|
||||
=> _storage.IsSessionIndexEmptyAsync();
|
||||
}
|
||||
}
|
||||
+65
@@ -0,0 +1,65 @@
|
||||
using AutopilotMonitor.Shared.DataAccess;
|
||||
using AutopilotMonitor.Shared.Models;
|
||||
using AutopilotMonitor.Functions.Services;
|
||||
|
||||
namespace AutopilotMonitor.Functions.DataAccess.TableStorage
|
||||
{
|
||||
/// <summary>
|
||||
/// Table Storage implementation of IMetricsRepository.
|
||||
/// Delegates to existing TableStorageService for backwards compatibility.
|
||||
/// </summary>
|
||||
public class TableMetricsRepository : IMetricsRepository
|
||||
{
|
||||
private readonly TableStorageService _storage;
|
||||
private readonly IDataEventPublisher _publisher;
|
||||
|
||||
public TableMetricsRepository(TableStorageService storage, IDataEventPublisher publisher)
|
||||
{
|
||||
_storage = storage;
|
||||
_publisher = publisher;
|
||||
}
|
||||
|
||||
public Task<bool> SaveUsageMetricsSnapshotAsync(UsageMetricsSnapshot metrics)
|
||||
=> _storage.SaveUsageMetricsSnapshotAsync(metrics);
|
||||
|
||||
public Task<List<UsageMetricsSnapshot>> GetUsageMetricsSnapshotAsync(
|
||||
string? tenantId = null, string? startDate = null, string? endDate = null, int maxResults = 100)
|
||||
=> _storage.GetUsageMetricsSnapshotAsync(tenantId, startDate, endDate, maxResults);
|
||||
|
||||
public Task<bool> HasUsageMetricsSnapshotAsync(string date)
|
||||
=> _storage.HasUsageMetricsSnapshotAsync(date);
|
||||
|
||||
public Task<bool> StoreAppInstallSummaryAsync(AppInstallSummary summary)
|
||||
=> _storage.StoreAppInstallSummaryAsync(summary);
|
||||
|
||||
public Task<List<AppInstallSummary>> GetAppInstallSummariesByTenantAsync(string tenantId)
|
||||
=> _storage.GetAppInstallSummariesByTenantAsync(tenantId);
|
||||
|
||||
public Task<List<AppInstallSummary>> GetAllAppInstallSummariesAsync()
|
||||
=> _storage.GetAllAppInstallSummariesAsync();
|
||||
|
||||
public Task<PlatformStats?> GetPlatformStatsAsync()
|
||||
=> _storage.GetPlatformStatsAsync();
|
||||
|
||||
public Task<bool> SavePlatformStatsAsync(PlatformStats stats)
|
||||
=> _storage.SavePlatformStatsAsync(stats);
|
||||
|
||||
public Task IncrementPlatformStatAsync(string field, long amount = 1)
|
||||
=> _storage.IncrementPlatformStatAsync(field, amount);
|
||||
|
||||
public Task RecordUserLoginAsync(string tenantId, string upn, string? displayName, string? objectId)
|
||||
=> _storage.RecordUserLoginAsync(tenantId, upn, displayName, objectId);
|
||||
|
||||
public Task<UserActivityMetrics> GetUserActivityMetricsAsync(string tenantId)
|
||||
=> _storage.GetUserActivityMetricsAsync(tenantId);
|
||||
|
||||
public Task<UserActivityMetrics> GetAllUserActivityMetricsAsync()
|
||||
=> _storage.GetAllUserActivityMetricsAsync();
|
||||
|
||||
public Task<(int uniqueUsers, int loginCount)> GetUserActivityForDateAsync(string? tenantId, DateTime date)
|
||||
=> _storage.GetUserActivityForDateAsync(tenantId, date);
|
||||
|
||||
public Task<List<object>> GetMetricsSummaryAsync(string? tenantId)
|
||||
=> _storage.GetMetricsSummaryAsync(tenantId);
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,69 @@
|
||||
using AutopilotMonitor.Shared.DataAccess;
|
||||
using AutopilotMonitor.Shared.Models;
|
||||
using AutopilotMonitor.Functions.Services;
|
||||
|
||||
namespace AutopilotMonitor.Functions.DataAccess.TableStorage
|
||||
{
|
||||
/// <summary>
|
||||
/// Table Storage implementation of IRuleRepository.
|
||||
/// Delegates to existing TableStorageService for backwards compatibility.
|
||||
/// </summary>
|
||||
public class TableRuleRepository : IRuleRepository
|
||||
{
|
||||
private readonly TableStorageService _storage;
|
||||
private readonly IDataEventPublisher _publisher;
|
||||
|
||||
public TableRuleRepository(TableStorageService storage, IDataEventPublisher publisher)
|
||||
{
|
||||
_storage = storage;
|
||||
_publisher = publisher;
|
||||
}
|
||||
|
||||
public async Task<bool> StoreRuleResultAsync(RuleResult result)
|
||||
{
|
||||
var success = await _storage.StoreRuleResultAsync(result);
|
||||
if (success)
|
||||
await _publisher.PublishAsync("rule.evaluated", result, result.TenantId);
|
||||
return success;
|
||||
}
|
||||
|
||||
public Task<List<RuleResult>> GetRuleResultsAsync(string tenantId, string sessionId)
|
||||
=> _storage.GetRuleResultsAsync(tenantId, sessionId);
|
||||
|
||||
public Task<bool> StoreGatherRuleAsync(GatherRule rule, string tenantId = "global")
|
||||
=> _storage.StoreGatherRuleAsync(rule, tenantId);
|
||||
|
||||
public Task<List<GatherRule>> GetGatherRulesAsync(string partitionKey)
|
||||
=> _storage.GetGatherRulesAsync(partitionKey);
|
||||
|
||||
public Task<bool> DeleteGatherRuleAsync(string tenantId, string ruleId)
|
||||
=> _storage.DeleteGatherRuleAsync(tenantId, ruleId);
|
||||
|
||||
public Task<bool> StoreRuleStateAsync(string tenantId, string ruleId, bool enabled)
|
||||
=> _storage.StoreRuleStateAsync(tenantId, ruleId, enabled);
|
||||
|
||||
public Task<Dictionary<string, bool>> GetRuleStatesAsync(string tenantId)
|
||||
=> _storage.GetRuleStatesAsync(tenantId);
|
||||
|
||||
public Task<bool> DeleteRuleStateAsync(string tenantId, string ruleId)
|
||||
=> _storage.DeleteRuleStateAsync(tenantId, ruleId);
|
||||
|
||||
public Task<bool> StoreAnalyzeRuleAsync(AnalyzeRule rule, string tenantId = "global")
|
||||
=> _storage.StoreAnalyzeRuleAsync(rule, tenantId);
|
||||
|
||||
public Task<List<AnalyzeRule>> GetAnalyzeRulesAsync(string partitionKey)
|
||||
=> _storage.GetAnalyzeRulesAsync(partitionKey);
|
||||
|
||||
public Task<bool> DeleteAnalyzeRuleAsync(string tenantId, string ruleId)
|
||||
=> _storage.DeleteAnalyzeRuleAsync(tenantId, ruleId);
|
||||
|
||||
public Task<bool> StoreImeLogPatternAsync(ImeLogPattern pattern, string tenantId = "global")
|
||||
=> _storage.StoreImeLogPatternAsync(pattern, tenantId);
|
||||
|
||||
public Task<List<ImeLogPattern>> GetImeLogPatternsAsync(string partitionKey)
|
||||
=> _storage.GetImeLogPatternsAsync(partitionKey);
|
||||
|
||||
public Task<bool> DeleteImeLogPatternAsync(string tenantId, string patternId)
|
||||
=> _storage.DeleteImeLogPatternAsync(tenantId, patternId);
|
||||
}
|
||||
}
|
||||
+102
@@ -0,0 +1,102 @@
|
||||
using AutopilotMonitor.Shared.DataAccess;
|
||||
using AutopilotMonitor.Shared.Models;
|
||||
using AutopilotMonitor.Functions.Services;
|
||||
|
||||
namespace AutopilotMonitor.Functions.DataAccess.TableStorage
|
||||
{
|
||||
/// <summary>
|
||||
/// Table Storage implementation of ISessionRepository.
|
||||
/// Delegates to existing TableStorageService for backwards compatibility.
|
||||
/// </summary>
|
||||
public class TableSessionRepository : ISessionRepository
|
||||
{
|
||||
private readonly TableStorageService _storage;
|
||||
private readonly IDataEventPublisher _publisher;
|
||||
|
||||
public TableSessionRepository(TableStorageService storage, IDataEventPublisher publisher)
|
||||
{
|
||||
_storage = storage;
|
||||
_publisher = publisher;
|
||||
}
|
||||
|
||||
public async Task<bool> StoreSessionAsync(SessionRegistration registration)
|
||||
{
|
||||
var result = await _storage.StoreSessionAsync(registration);
|
||||
if (result)
|
||||
await _publisher.PublishAsync("session.created", registration, registration.TenantId);
|
||||
return result;
|
||||
}
|
||||
|
||||
public Task<SessionSummary?> GetSessionAsync(string tenantId, string sessionId)
|
||||
=> _storage.GetSessionAsync(tenantId, sessionId);
|
||||
|
||||
public Task<SessionPage> GetSessionsAsync(string tenantId, int maxResults = 100, string? cursor = null)
|
||||
=> _storage.GetSessionsAsync(tenantId, maxResults, cursor);
|
||||
|
||||
public Task<SessionPage> GetAllSessionsAsync(int maxResults = 100, string? cursor = null)
|
||||
=> _storage.GetAllSessionsAsync(maxResults, cursor);
|
||||
|
||||
public Task<bool> DeleteSessionAsync(string tenantId, string sessionId)
|
||||
=> _storage.DeleteSessionAsync(tenantId, sessionId);
|
||||
|
||||
public Task<bool> UpdateSessionStatusAsync(
|
||||
string tenantId, string sessionId, SessionStatus status,
|
||||
EnrollmentPhase? currentPhase = null, string? failureReason = null,
|
||||
DateTime? completedAt = null, DateTime? earliestEventTimestamp = null,
|
||||
DateTime? latestEventTimestamp = null, bool? isPreProvisioned = null,
|
||||
bool? isUserDriven = null, DateTime? resumedAt = null)
|
||||
=> _storage.UpdateSessionStatusAsync(tenantId, sessionId, status,
|
||||
currentPhase, failureReason, completedAt, earliestEventTimestamp,
|
||||
latestEventTimestamp, isPreProvisioned, isUserDriven, resumedAt);
|
||||
|
||||
public Task IncrementSessionEventCountAsync(
|
||||
string tenantId, string sessionId, int increment,
|
||||
DateTime? earliestEventTimestamp = null, DateTime? latestEventTimestamp = null,
|
||||
EnrollmentPhase? currentPhase = null)
|
||||
=> _storage.IncrementSessionEventCountAsync(tenantId, sessionId, increment,
|
||||
earliestEventTimestamp, latestEventTimestamp, currentPhase);
|
||||
|
||||
public Task UpdateSessionDiagnosticsBlobAsync(string tenantId, string sessionId, string blobName)
|
||||
=> _storage.UpdateSessionDiagnosticsBlobAsync(tenantId, sessionId, blobName);
|
||||
|
||||
public Task SetSessionPreProvisionedAsync(string tenantId, string sessionId, bool isPreProvisioned,
|
||||
SessionStatus? status = null, bool? isUserDriven = null)
|
||||
=> _storage.SetSessionPreProvisionedAsync(tenantId, sessionId, isPreProvisioned, status, isUserDriven);
|
||||
|
||||
public Task UpdateSessionGeoAsync(string tenantId, string sessionId,
|
||||
string? country, string? region, string? city, string? loc)
|
||||
=> _storage.UpdateSessionGeoAsync(tenantId, sessionId, country, region, city, loc);
|
||||
|
||||
public Task UpdateSessionImeAgentVersionAsync(string tenantId, string sessionId, string version)
|
||||
=> _storage.UpdateSessionImeAgentVersionAsync(tenantId, sessionId, version);
|
||||
|
||||
public async Task<bool> StoreEventAsync(EnrollmentEvent evt)
|
||||
{
|
||||
var result = await _storage.StoreEventAsync(evt);
|
||||
if (result)
|
||||
await _publisher.PublishAsync("event.ingested", evt, evt.TenantId);
|
||||
return result;
|
||||
}
|
||||
|
||||
public Task<List<SessionSummary>> SearchSessionsAsync(string? tenantId, SessionSearchFilter filter)
|
||||
=> _storage.SearchSessionsAsync(tenantId, filter);
|
||||
|
||||
public Task<List<SessionSummary>> SearchSessionsByEventAsync(
|
||||
string? tenantId, string eventType, string? source, string? severity,
|
||||
string? phase, int limit = 50)
|
||||
=> _storage.SearchSessionsByEventAsync(tenantId, eventType, source, severity, phase, limit);
|
||||
|
||||
public Task<List<SessionSummary>> SearchSessionsByCveAsync(
|
||||
string? tenantId, string cveId, double? minCvssScore, string? overallRisk, int limit = 50)
|
||||
=> _storage.SearchSessionsByCveAsync(tenantId, cveId, minCvssScore, overallRisk, limit);
|
||||
|
||||
public Task UpsertEventTypeIndexBatchAsync(string tenantId, string sessionId, IEnumerable<string> eventTypes)
|
||||
=> _storage.UpsertEventTypeIndexBatchAsync(tenantId, sessionId, eventTypes);
|
||||
|
||||
public Task UpsertDeviceSnapshotAsync(string tenantId, string sessionId, IEnumerable<EnrollmentEvent> events)
|
||||
=> _storage.UpsertDeviceSnapshotAsync(tenantId, sessionId, events);
|
||||
|
||||
public Task UpsertCveIndexEntriesAsync(string tenantId, string sessionId, List<Dictionary<string, object>> findings)
|
||||
=> _storage.UpsertCveIndexEntriesAsync(tenantId, sessionId, findings);
|
||||
}
|
||||
}
|
||||
+22
@@ -0,0 +1,22 @@
|
||||
using AutopilotMonitor.Shared.DataAccess;
|
||||
using AutopilotMonitor.Functions.Services;
|
||||
|
||||
namespace AutopilotMonitor.Functions.DataAccess.TableStorage
|
||||
{
|
||||
/// <summary>
|
||||
/// Table Storage implementation of IStorageInitializer.
|
||||
/// Delegates to existing TableStorageService for backwards compatibility.
|
||||
/// </summary>
|
||||
public class TableStorageInitializer : IStorageInitializer
|
||||
{
|
||||
private readonly TableStorageService _storage;
|
||||
|
||||
public TableStorageInitializer(TableStorageService storage)
|
||||
{
|
||||
_storage = storage;
|
||||
}
|
||||
|
||||
public Task InitializeAsync()
|
||||
=> _storage.InitializeTablesAsync();
|
||||
}
|
||||
}
|
||||
+43
@@ -0,0 +1,43 @@
|
||||
using AutopilotMonitor.Shared.DataAccess;
|
||||
using AutopilotMonitor.Functions.Services;
|
||||
|
||||
namespace AutopilotMonitor.Functions.DataAccess.TableStorage
|
||||
{
|
||||
/// <summary>
|
||||
/// Table Storage implementation of IVulnerabilityRepository.
|
||||
/// Delegates to existing TableStorageService for backwards compatibility.
|
||||
/// </summary>
|
||||
public class TableVulnerabilityRepository : IVulnerabilityRepository
|
||||
{
|
||||
private readonly TableStorageService _storage;
|
||||
private readonly IDataEventPublisher _publisher;
|
||||
|
||||
public TableVulnerabilityRepository(TableStorageService storage, IDataEventPublisher publisher)
|
||||
{
|
||||
_storage = storage;
|
||||
_publisher = publisher;
|
||||
}
|
||||
|
||||
public async Task StoreVulnerabilityReportAsync(string tenantId, string sessionId, Dictionary<string, object> reportData)
|
||||
{
|
||||
await _storage.StoreVulnerabilityReportAsync(tenantId, sessionId, reportData);
|
||||
await _publisher.PublishAsync("vulnerability.reported", new { tenantId, sessionId, reportData }, tenantId);
|
||||
}
|
||||
|
||||
public Task<Dictionary<string, object>?> GetVulnerabilityReportAsync(string tenantId, string sessionId)
|
||||
=> _storage.GetVulnerabilityReportAsync(tenantId, sessionId);
|
||||
|
||||
public Task UpsertSoftwareInventoryAsync(string tenantId, List<Dictionary<string, object>> inventoryItems,
|
||||
string sessionId, Dictionary<string, string?>? cpeMappings = null)
|
||||
=> _storage.UpsertSoftwareInventoryAsync(tenantId, inventoryItems, sessionId, cpeMappings);
|
||||
|
||||
public Task<List<Dictionary<string, object>>> GetSoftwareInventoryAsync(string tenantId)
|
||||
=> _storage.GetSoftwareInventoryAsync(tenantId);
|
||||
|
||||
public Task<int> ImportCpeMappingSeedFromJsonAsync(string json)
|
||||
=> _storage.ImportCpeMappingSeedFromJsonAsync(json);
|
||||
|
||||
public Task<int> ImportCpeCommunityMappingsFromJsonAsync(string json)
|
||||
=> _storage.ImportCpeCommunityMappingsFromJsonAsync(json);
|
||||
}
|
||||
}
|
||||
@@ -1,5 +1,6 @@
|
||||
using System.Text.Json;
|
||||
using System.Text.Json.Serialization;
|
||||
using AutopilotMonitor.Functions.DataAccess;
|
||||
using AutopilotMonitor.Functions.Functions.Config;
|
||||
using AutopilotMonitor.Functions.Functions.Ingest;
|
||||
using AutopilotMonitor.Functions.Functions.Sessions;
|
||||
@@ -100,6 +101,11 @@ builder.Services.AddMemoryCache();
|
||||
builder.Services.AddHttpClient();
|
||||
builder.Services.AddSingleton<TableStorageService>();
|
||||
builder.Services.AddHostedService<TableInitializerService>(); // Initialize all tables at startup
|
||||
|
||||
// Data Access Layer — repository interfaces backed by Table Storage.
|
||||
// To switch to Cosmos DB: replace AddTableStorageDataAccess() with AddCosmosDataAccess().
|
||||
// To add event streaming: chain .AddEventStreaming<EventHubPublisher>() after this call.
|
||||
builder.Services.AddTableStorageDataAccess();
|
||||
builder.Services.AddSingleton<TenantConfigurationService>();
|
||||
builder.Services.AddSingleton<AdminConfigurationService>();
|
||||
builder.Services.AddSingleton<RateLimitService>();
|
||||
|
||||
@@ -0,0 +1,64 @@
|
||||
namespace AutopilotMonitor.Shared.DataAccess
|
||||
{
|
||||
/// <summary>
|
||||
/// Repository for admin and tenant member management.
|
||||
/// Covers: GlobalAdmins, TenantAdmins, ApiKeys tables.
|
||||
/// </summary>
|
||||
public interface IAdminRepository
|
||||
{
|
||||
// --- Global Admins ---
|
||||
Task<bool> IsGlobalAdminAsync(string upn);
|
||||
Task<List<GlobalAdminEntry>> GetAllGlobalAdminsAsync();
|
||||
Task<bool> AddGlobalAdminAsync(string upn, string addedBy);
|
||||
Task<bool> RemoveGlobalAdminAsync(string upn);
|
||||
Task<bool> DisableGlobalAdminAsync(string upn);
|
||||
|
||||
// --- Tenant Members ---
|
||||
Task<List<TenantMember>> GetTenantMembersAsync(string tenantId);
|
||||
Task<bool> AddTenantMemberAsync(string tenantId, string upn, string addedBy, string role, bool canManageBootstrapTokens = false);
|
||||
Task<bool> RemoveTenantMemberAsync(string tenantId, string upn);
|
||||
Task<bool> UpdateMemberPermissionsAsync(string tenantId, string upn, string role, bool canManageBootstrapTokens);
|
||||
Task<TenantMember?> GetTenantMemberAsync(string tenantId, string upn);
|
||||
Task<bool> IsTenantAdminAsync(string tenantId, string upn);
|
||||
Task<bool> IsTenantMemberAsync(string tenantId, string upn);
|
||||
|
||||
// --- API Keys ---
|
||||
Task<ApiKeyEntry?> ValidateApiKeyAsync(string apiKeyHash);
|
||||
Task<bool> StoreApiKeyAsync(string tenantId, ApiKeyEntry entry);
|
||||
Task<List<ApiKeyEntry>> GetApiKeysAsync(string tenantId);
|
||||
Task<bool> RevokeApiKeyAsync(string tenantId, string keyId);
|
||||
}
|
||||
|
||||
public class GlobalAdminEntry
|
||||
{
|
||||
public string Upn { get; set; } = string.Empty;
|
||||
public bool IsEnabled { get; set; } = true;
|
||||
public DateTime AddedAt { get; set; }
|
||||
public string AddedBy { get; set; } = string.Empty;
|
||||
}
|
||||
|
||||
public class TenantMember
|
||||
{
|
||||
public string Upn { get; set; } = string.Empty;
|
||||
public string TenantId { get; set; } = string.Empty;
|
||||
public string Role { get; set; } = "Admin";
|
||||
public bool IsEnabled { get; set; } = true;
|
||||
public bool CanManageBootstrapTokens { get; set; }
|
||||
public DateTime AddedAt { get; set; }
|
||||
public string AddedBy { get; set; } = string.Empty;
|
||||
}
|
||||
|
||||
public class ApiKeyEntry
|
||||
{
|
||||
public string KeyId { get; set; } = string.Empty;
|
||||
public string TenantId { get; set; } = string.Empty;
|
||||
public string KeyHash { get; set; } = string.Empty;
|
||||
public string Name { get; set; } = string.Empty;
|
||||
public string Scope { get; set; } = "tenant";
|
||||
public string CreatedBy { get; set; } = string.Empty;
|
||||
public DateTime CreatedAt { get; set; }
|
||||
public DateTime? ExpiresAt { get; set; }
|
||||
public bool IsActive { get; set; } = true;
|
||||
public long RequestCount { get; set; }
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,18 @@
|
||||
using AutopilotMonitor.Shared.Models;
|
||||
|
||||
namespace AutopilotMonitor.Shared.DataAccess
|
||||
{
|
||||
/// <summary>
|
||||
/// Repository for bootstrap session management.
|
||||
/// Covers: BootstrapSessions table.
|
||||
/// </summary>
|
||||
public interface IBootstrapRepository
|
||||
{
|
||||
Task<bool> CreateBootstrapSessionAsync(BootstrapSession session);
|
||||
Task<BootstrapSession?> GetBootstrapSessionByCodeAsync(string shortCode);
|
||||
Task<BootstrapSession?> ValidateBootstrapTokenAsync(string token);
|
||||
Task<List<BootstrapSession>> GetBootstrapSessionsAsync(string tenantId);
|
||||
Task<bool> RevokeBootstrapSessionAsync(string shortCode);
|
||||
Task<bool> IncrementBootstrapUsageAsync(string shortCode);
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,30 @@
|
||||
using AutopilotMonitor.Shared.Models;
|
||||
|
||||
namespace AutopilotMonitor.Shared.DataAccess
|
||||
{
|
||||
/// <summary>
|
||||
/// Repository for tenant and admin configuration.
|
||||
/// Covers: TenantConfiguration, AdminConfiguration, PreviewWhitelist, PreviewConfig tables.
|
||||
/// </summary>
|
||||
public interface IConfigRepository
|
||||
{
|
||||
// --- Tenant Configuration ---
|
||||
Task<TenantConfiguration?> GetTenantConfigurationAsync(string tenantId);
|
||||
Task<bool> SaveTenantConfigurationAsync(TenantConfiguration config);
|
||||
Task<List<TenantConfiguration>> GetAllTenantConfigurationsAsync();
|
||||
|
||||
// --- Admin Configuration ---
|
||||
Task<AdminConfiguration?> GetAdminConfigurationAsync();
|
||||
Task<bool> SaveAdminConfigurationAsync(AdminConfiguration config);
|
||||
|
||||
// --- Preview Whitelist ---
|
||||
Task<bool> IsInPreviewWhitelistAsync(string tenantId);
|
||||
Task<bool> AddToPreviewWhitelistAsync(string tenantId, string addedBy);
|
||||
Task<bool> RemoveFromPreviewWhitelistAsync(string tenantId);
|
||||
Task<List<string>> GetPreviewWhitelistAsync();
|
||||
|
||||
// --- Preview Config ---
|
||||
Task<Dictionary<string, string>> GetPreviewConfigAsync();
|
||||
Task<bool> SavePreviewConfigAsync(string key, string value);
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,27 @@
|
||||
namespace AutopilotMonitor.Shared.DataAccess
|
||||
{
|
||||
/// <summary>
|
||||
/// Publishes data change events for downstream consumers (Event Hub, Service Bus, etc.).
|
||||
/// Default implementation is NullDataEventPublisher (no-op).
|
||||
/// </summary>
|
||||
public interface IDataEventPublisher
|
||||
{
|
||||
/// <summary>
|
||||
/// Publishes a data change event.
|
||||
/// </summary>
|
||||
/// <param name="eventType">Domain event type (e.g. "session.created", "event.ingested", "rule.evaluated")</param>
|
||||
/// <param name="payload">The event payload (typically the domain object)</param>
|
||||
/// <param name="tenantId">Optional tenant context for routing</param>
|
||||
Task PublishAsync(string eventType, object payload, string? tenantId = null);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// No-op implementation of IDataEventPublisher.
|
||||
/// Used when event streaming is not configured.
|
||||
/// </summary>
|
||||
public class NullDataEventPublisher : IDataEventPublisher
|
||||
{
|
||||
public Task PublishAsync(string eventType, object payload, string? tenantId = null)
|
||||
=> Task.CompletedTask;
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,44 @@
|
||||
namespace AutopilotMonitor.Shared.DataAccess
|
||||
{
|
||||
/// <summary>
|
||||
/// Repository for device and version blocking.
|
||||
/// Covers: BlockedDevices, BlockedVersions tables.
|
||||
/// </summary>
|
||||
public interface IDeviceSecurityRepository
|
||||
{
|
||||
// --- Blocked Devices ---
|
||||
Task<(bool isBlocked, DateTime? unblockAt, string action)> IsDeviceBlockedAsync(string tenantId, string serialNumber);
|
||||
Task<List<BlockedDeviceEntry>> GetBlockedDevicesAsync(string tenantId);
|
||||
Task<List<BlockedDeviceEntry>> GetAllBlockedDevicesAsync();
|
||||
Task BlockDeviceAsync(string tenantId, string serialNumber, int durationHours,
|
||||
string blockedByEmail, string? reason = null, string action = "Block");
|
||||
Task UnblockDeviceAsync(string tenantId, string serialNumber);
|
||||
|
||||
// --- Blocked Versions ---
|
||||
Task<(bool isBlocked, string action, string? matchedPattern)> IsVersionBlockedAsync(string agentVersion);
|
||||
Task<List<BlockedVersionEntry>> GetBlockedVersionsAsync();
|
||||
Task BlockVersionAsync(string versionPattern, string action, string createdByEmail, string? reason = null);
|
||||
Task UnblockVersionAsync(string versionPattern);
|
||||
}
|
||||
|
||||
public class BlockedDeviceEntry
|
||||
{
|
||||
public string TenantId { get; set; } = string.Empty;
|
||||
public string SerialNumber { get; set; } = string.Empty;
|
||||
public DateTime BlockedAt { get; set; }
|
||||
public DateTime? UnblockAt { get; set; }
|
||||
public string? BlockedByEmail { get; set; }
|
||||
public int DurationHours { get; set; }
|
||||
public string? Reason { get; set; }
|
||||
public string Action { get; set; } = "Block";
|
||||
}
|
||||
|
||||
public class BlockedVersionEntry
|
||||
{
|
||||
public string VersionPattern { get; set; } = string.Empty;
|
||||
public string Action { get; set; } = "Block";
|
||||
public string CreatedByEmail { get; set; } = string.Empty;
|
||||
public DateTime CreatedAt { get; set; }
|
||||
public string? Reason { get; set; }
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,36 @@
|
||||
using AutopilotMonitor.Shared.Models;
|
||||
|
||||
namespace AutopilotMonitor.Shared.DataAccess
|
||||
{
|
||||
/// <summary>
|
||||
/// Repository for audit logs and data retention/maintenance operations.
|
||||
/// Covers: AuditLogs table + maintenance queries across Sessions/Events/RuleResults.
|
||||
/// </summary>
|
||||
public interface IMaintenanceRepository
|
||||
{
|
||||
// --- Audit Logs ---
|
||||
Task<bool> LogAuditEntryAsync(string tenantId, string action, string entityType,
|
||||
string entityId, string performedBy, Dictionary<string, string>? details = null);
|
||||
Task<List<AuditLogEntry>> GetAuditLogsAsync(string tenantId, int maxResults = 100);
|
||||
Task<List<AuditLogEntry>> GetAllAuditLogsAsync(int maxResults = 100);
|
||||
|
||||
// --- Data Retention Queries ---
|
||||
Task<List<SessionSummary>> GetSessionsOlderThanAsync(string tenantId, DateTime cutoffDate);
|
||||
Task<List<SessionSummary>> GetSessionsByDateRangeAsync(DateTime startDate, DateTime endDate, string? tenantId = null);
|
||||
Task<List<SessionSummary>> GetStalledSessionsAsync(string tenantId, DateTime cutoffTime);
|
||||
Task<List<SessionSummary>> GetExcessiveDataSendersAsync(string tenantId, DateTime windowCutoff, int maxSessionWindowHours);
|
||||
|
||||
// --- Tenant Discovery ---
|
||||
Task<List<string>> GetAllTenantIdsAsync();
|
||||
|
||||
// --- Cleanup ---
|
||||
Task<int> DeleteSessionEventsAsync(string tenantId, string sessionId);
|
||||
Task<int> DeleteSessionRuleResultsAsync(string tenantId, string sessionId);
|
||||
Task<int> DeleteSessionAppInstallSummariesAsync(string tenantId, string sessionId);
|
||||
|
||||
// --- Index Maintenance ---
|
||||
Task<int> BackfillSessionIndexAsync();
|
||||
Task<int> CleanupGhostSessionIndexEntriesAsync();
|
||||
Task<bool> IsSessionIndexEmptyAsync();
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,36 @@
|
||||
using AutopilotMonitor.Shared.Models;
|
||||
|
||||
namespace AutopilotMonitor.Shared.DataAccess
|
||||
{
|
||||
/// <summary>
|
||||
/// Repository for usage metrics, platform stats, and user activity tracking.
|
||||
/// Covers: UsageMetrics, PlatformStats, UserActivity, AppInstallSummaries tables.
|
||||
/// </summary>
|
||||
public interface IMetricsRepository
|
||||
{
|
||||
// --- Usage Metrics Snapshots ---
|
||||
Task<bool> SaveUsageMetricsSnapshotAsync(UsageMetricsSnapshot metrics);
|
||||
Task<List<UsageMetricsSnapshot>> GetUsageMetricsSnapshotAsync(
|
||||
string? tenantId = null, string? startDate = null, string? endDate = null, int maxResults = 100);
|
||||
Task<bool> HasUsageMetricsSnapshotAsync(string date);
|
||||
|
||||
// --- App Install Summaries ---
|
||||
Task<bool> StoreAppInstallSummaryAsync(AppInstallSummary summary);
|
||||
Task<List<AppInstallSummary>> GetAppInstallSummariesByTenantAsync(string tenantId);
|
||||
Task<List<AppInstallSummary>> GetAllAppInstallSummariesAsync();
|
||||
|
||||
// --- Platform Stats ---
|
||||
Task<PlatformStats?> GetPlatformStatsAsync();
|
||||
Task<bool> SavePlatformStatsAsync(PlatformStats stats);
|
||||
Task IncrementPlatformStatAsync(string field, long amount = 1);
|
||||
|
||||
// --- User Activity ---
|
||||
Task RecordUserLoginAsync(string tenantId, string upn, string? displayName, string? objectId);
|
||||
Task<UserActivityMetrics> GetUserActivityMetricsAsync(string tenantId);
|
||||
Task<UserActivityMetrics> GetAllUserActivityMetricsAsync();
|
||||
Task<(int uniqueUsers, int loginCount)> GetUserActivityForDateAsync(string? tenantId, DateTime date);
|
||||
|
||||
// --- Metrics Summary (Agent API) ---
|
||||
Task<List<object>> GetMetricsSummaryAsync(string? tenantId);
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,34 @@
|
||||
using AutopilotMonitor.Shared.Models;
|
||||
|
||||
namespace AutopilotMonitor.Shared.DataAccess
|
||||
{
|
||||
/// <summary>
|
||||
/// Repository for global notifications and session reports.
|
||||
/// Covers: GlobalNotifications, SessionReports tables.
|
||||
/// </summary>
|
||||
public interface INotificationRepository
|
||||
{
|
||||
// --- Global Notifications ---
|
||||
Task<bool> AddNotificationAsync(GlobalNotification notification);
|
||||
Task<List<GlobalNotification>> GetNotificationsAsync(int maxResults = 50);
|
||||
Task<bool> DismissNotificationAsync(string notificationId, string dismissedBy);
|
||||
|
||||
// --- Session Reports ---
|
||||
Task<bool> StoreSessionReportMetadataAsync(SessionReportMetadata metadata);
|
||||
Task<List<SessionReportMetadata>> GetSessionReportsAsync(string? tenantId = null, int maxResults = 50);
|
||||
Task<SessionReportMetadata?> GetSessionReportAsync(string reportId);
|
||||
}
|
||||
|
||||
public class GlobalNotification
|
||||
{
|
||||
public string NotificationId { get; set; } = string.Empty;
|
||||
public string Title { get; set; } = string.Empty;
|
||||
public string Message { get; set; } = string.Empty;
|
||||
public string Severity { get; set; } = "info";
|
||||
public DateTime CreatedAt { get; set; }
|
||||
public string CreatedBy { get; set; } = string.Empty;
|
||||
public bool IsDismissed { get; set; }
|
||||
public string? DismissedBy { get; set; }
|
||||
public DateTime? DismissedAt { get; set; }
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,35 @@
|
||||
using AutopilotMonitor.Shared.Models;
|
||||
|
||||
namespace AutopilotMonitor.Shared.DataAccess
|
||||
{
|
||||
/// <summary>
|
||||
/// Repository for rule definitions, results, and patterns.
|
||||
/// Covers: RuleResults, GatherRules, AnalyzeRules, ImeLogPatterns, RuleStates tables.
|
||||
/// </summary>
|
||||
public interface IRuleRepository
|
||||
{
|
||||
// --- Rule Results ---
|
||||
Task<bool> StoreRuleResultAsync(RuleResult result);
|
||||
Task<List<RuleResult>> GetRuleResultsAsync(string tenantId, string sessionId);
|
||||
|
||||
// --- Gather Rules ---
|
||||
Task<bool> StoreGatherRuleAsync(GatherRule rule, string tenantId = "global");
|
||||
Task<List<GatherRule>> GetGatherRulesAsync(string partitionKey);
|
||||
Task<bool> DeleteGatherRuleAsync(string tenantId, string ruleId);
|
||||
|
||||
// --- Rule States ---
|
||||
Task<bool> StoreRuleStateAsync(string tenantId, string ruleId, bool enabled);
|
||||
Task<Dictionary<string, bool>> GetRuleStatesAsync(string tenantId);
|
||||
Task<bool> DeleteRuleStateAsync(string tenantId, string ruleId);
|
||||
|
||||
// --- Analyze Rules ---
|
||||
Task<bool> StoreAnalyzeRuleAsync(AnalyzeRule rule, string tenantId = "global");
|
||||
Task<List<AnalyzeRule>> GetAnalyzeRulesAsync(string partitionKey);
|
||||
Task<bool> DeleteAnalyzeRuleAsync(string tenantId, string ruleId);
|
||||
|
||||
// --- IME Log Patterns ---
|
||||
Task<bool> StoreImeLogPatternAsync(ImeLogPattern pattern, string tenantId = "global");
|
||||
Task<List<ImeLogPattern>> GetImeLogPatternsAsync(string partitionKey);
|
||||
Task<bool> DeleteImeLogPatternAsync(string tenantId, string patternId);
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,52 @@
|
||||
using AutopilotMonitor.Shared.Models;
|
||||
|
||||
namespace AutopilotMonitor.Shared.DataAccess
|
||||
{
|
||||
/// <summary>
|
||||
/// Repository for session and event data.
|
||||
/// Covers: Sessions, SessionsIndex, Events tables.
|
||||
/// </summary>
|
||||
public interface ISessionRepository
|
||||
{
|
||||
// --- Session CRUD ---
|
||||
Task<bool> StoreSessionAsync(SessionRegistration registration);
|
||||
Task<SessionSummary?> GetSessionAsync(string tenantId, string sessionId);
|
||||
Task<SessionPage> GetSessionsAsync(string tenantId, int maxResults = 100, string? cursor = null);
|
||||
Task<SessionPage> GetAllSessionsAsync(int maxResults = 100, string? cursor = null);
|
||||
Task<bool> DeleteSessionAsync(string tenantId, string sessionId);
|
||||
|
||||
// --- Session Updates ---
|
||||
Task<bool> UpdateSessionStatusAsync(
|
||||
string tenantId, string sessionId, SessionStatus status,
|
||||
EnrollmentPhase? currentPhase = null, string? failureReason = null,
|
||||
DateTime? completedAt = null, DateTime? earliestEventTimestamp = null,
|
||||
DateTime? latestEventTimestamp = null, bool? isPreProvisioned = null,
|
||||
bool? isUserDriven = null, DateTime? resumedAt = null);
|
||||
Task IncrementSessionEventCountAsync(
|
||||
string tenantId, string sessionId, int increment,
|
||||
DateTime? earliestEventTimestamp = null, DateTime? latestEventTimestamp = null,
|
||||
EnrollmentPhase? currentPhase = null);
|
||||
Task UpdateSessionDiagnosticsBlobAsync(string tenantId, string sessionId, string blobName);
|
||||
Task SetSessionPreProvisionedAsync(string tenantId, string sessionId, bool isPreProvisioned,
|
||||
SessionStatus? status = null, bool? isUserDriven = null);
|
||||
Task UpdateSessionGeoAsync(string tenantId, string sessionId,
|
||||
string? country, string? region, string? city, string? loc);
|
||||
Task UpdateSessionImeAgentVersionAsync(string tenantId, string sessionId, string version);
|
||||
|
||||
// --- Events ---
|
||||
Task<bool> StoreEventAsync(EnrollmentEvent evt);
|
||||
|
||||
// --- Search ---
|
||||
Task<List<SessionSummary>> SearchSessionsAsync(string? tenantId, SessionSearchFilter filter);
|
||||
Task<List<SessionSummary>> SearchSessionsByEventAsync(
|
||||
string? tenantId, string eventType, string? source, string? severity,
|
||||
string? phase, int limit = 50);
|
||||
Task<List<SessionSummary>> SearchSessionsByCveAsync(
|
||||
string? tenantId, string cveId, double? minCvssScore, string? overallRisk, int limit = 50);
|
||||
|
||||
// --- Agent Indexes ---
|
||||
Task UpsertEventTypeIndexBatchAsync(string tenantId, string sessionId, IEnumerable<string> eventTypes);
|
||||
Task UpsertDeviceSnapshotAsync(string tenantId, string sessionId, IEnumerable<EnrollmentEvent> events);
|
||||
Task UpsertCveIndexEntriesAsync(string tenantId, string sessionId, List<Dictionary<string, object>> findings);
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,11 @@
|
||||
namespace AutopilotMonitor.Shared.DataAccess
|
||||
{
|
||||
/// <summary>
|
||||
/// Initializes the storage backend (creates tables, containers, collections, etc.).
|
||||
/// Each storage provider implements this for its specific setup needs.
|
||||
/// </summary>
|
||||
public interface IStorageInitializer
|
||||
{
|
||||
Task InitializeAsync();
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,17 @@
|
||||
namespace AutopilotMonitor.Shared.DataAccess
|
||||
{
|
||||
/// <summary>
|
||||
/// Repository for vulnerability data (CPE mappings, CVE reports, software inventory).
|
||||
/// Covers: VulnerabilityCache, VulnerabilityReports, SoftwareInventory, CveIndex tables.
|
||||
/// </summary>
|
||||
public interface IVulnerabilityRepository
|
||||
{
|
||||
Task StoreVulnerabilityReportAsync(string tenantId, string sessionId, Dictionary<string, object> reportData);
|
||||
Task<Dictionary<string, object>?> GetVulnerabilityReportAsync(string tenantId, string sessionId);
|
||||
Task UpsertSoftwareInventoryAsync(string tenantId, List<Dictionary<string, object>> inventoryItems,
|
||||
string sessionId, Dictionary<string, string?>? cpeMappings = null);
|
||||
Task<List<Dictionary<string, object>>> GetSoftwareInventoryAsync(string tenantId);
|
||||
Task<int> ImportCpeMappingSeedFromJsonAsync(string json);
|
||||
Task<int> ImportCpeCommunityMappingsFromJsonAsync(string json);
|
||||
}
|
||||
}
|
||||
+72
-14
@@ -1,16 +1,74 @@
|
||||
# Tenant Config Report (Global Admin)
|
||||
# Data Access Layer — Migration Plan
|
||||
|
||||
## Goal
|
||||
New global-admin-only page that shows a read-only report of a tenant's full configuration,
|
||||
including admin-set parameters and computed runtime (agent) parameters.
|
||||
## Phase 1: Foundation ✅
|
||||
- [x] Define 10 Repository Interfaces in `Shared/DataAccess/`
|
||||
- [x] Define `IDataEventPublisher` + `NullDataEventPublisher`
|
||||
- [x] Define `IStorageInitializer`
|
||||
- [x] Create 5 core Table Storage implementations (delegate to `TableStorageService`)
|
||||
- [x] Create `DataAccessServiceExtensions` with `AddTableStorageDataAccess()`
|
||||
- [x] Register DAL in `Program.cs`
|
||||
|
||||
## Plan
|
||||
- [x] Write plan
|
||||
- [ ] Create `/app/tenant-config-report/page.tsx` — global admin only, read-only report
|
||||
- Tenant selector (reuse pattern from gather-rules page)
|
||||
- Fetch config via `GET /api/config/{tenantId}` (existing endpoint, GA cross-tenant)
|
||||
- Display all TenantConfiguration fields grouped by section
|
||||
- Show computed runtime parameters (AgentConfigResponse equivalents with defaults)
|
||||
- Highlight non-default values for quick scanning
|
||||
- [ ] Add nav item to global admin section in `globalNavConfig.tsx`
|
||||
- [ ] Commit and push to feature branch
|
||||
## Phase 2: Migrate Core Functions to Use Interfaces
|
||||
These functions currently inject `TableStorageService` directly. Migrate to inject repository interfaces instead.
|
||||
|
||||
### Session Functions
|
||||
- [ ] `GetSessionsFunction` → `ISessionRepository`
|
||||
- [ ] `GetSessionFunction` → `ISessionRepository`
|
||||
- [ ] `DeleteSessionFunction` → `ISessionRepository`
|
||||
- [ ] `RegisterSessionFunction` → `ISessionRepository`
|
||||
- [ ] `IngestEventsFunction` → `ISessionRepository`
|
||||
- [ ] `SearchSessionsFunction` → `ISessionRepository`
|
||||
- [ ] `SearchSessionsByEventFunction` → `ISessionRepository`
|
||||
- [ ] `SearchSessionsByCveFunction` → `ISessionRepository`
|
||||
|
||||
### Rule Functions
|
||||
- [ ] `GetRuleResultsFunction` → `IRuleRepository`
|
||||
- [ ] `RunRulesFunction` → `IRuleRepository`
|
||||
- [ ] Rule management functions → `IRuleRepository`
|
||||
|
||||
### Metrics Functions
|
||||
- [ ] `GetMetricsFunction` → `IMetricsRepository`
|
||||
- [ ] `GetHistoricalMetricsFunction` → `IMetricsRepository`
|
||||
- [ ] `GetPlatformStatsFunction` → `IMetricsRepository`
|
||||
|
||||
### Maintenance Functions
|
||||
- [ ] `GetAuditLogsFunction` → `IMaintenanceRepository`
|
||||
- [ ] `MaintenanceFunction` → `IMaintenanceRepository`
|
||||
|
||||
### Vulnerability Functions
|
||||
- [ ] All vulnerability functions → `IVulnerabilityRepository`
|
||||
|
||||
## Phase 3: Migrate "Bypass" Services
|
||||
These services create their own `TableServiceClient` instances. Create proper repository implementations and migrate.
|
||||
|
||||
- [ ] `TenantConfigurationService` → `IConfigRepository`
|
||||
- [ ] `AdminConfigurationService` → `IConfigRepository`
|
||||
- [ ] `PreviewWhitelistService` → `IConfigRepository`
|
||||
- [ ] `GlobalAdminService` → `IAdminRepository`
|
||||
- [ ] `TenantAdminsService` → `IAdminRepository`
|
||||
- [ ] `BootstrapSessionService` → `IBootstrapRepository`
|
||||
- [ ] `GlobalNotificationService` → `INotificationRepository`
|
||||
- [ ] `SessionReportService` → `INotificationRepository`
|
||||
- [ ] `BlockedDeviceService` → `IDeviceSecurityRepository`
|
||||
- [ ] `BlockedVersionService` → `IDeviceSecurityRepository`
|
||||
- [ ] `ApiKeyMiddleware` → `IAdminRepository`
|
||||
|
||||
## Phase 4: Remove Legacy Code
|
||||
- [ ] Remove `GetTableClient()` and `GetTableServiceClient()` from `TableStorageService`
|
||||
- [ ] Move `TableStorageService` logic into repository implementations
|
||||
- [ ] Delete `TableStorageService` partial files once fully migrated
|
||||
- [ ] Remove direct `TableServiceClient` creation from all services
|
||||
|
||||
## Phase 5: Event Streaming (When Ready)
|
||||
- [ ] Implement `EventHubPublisher` (or `ServiceBusPublisher`)
|
||||
- [ ] Register via `services.AddEventStreaming<EventHubPublisher>()`
|
||||
- [ ] Define event schemas for key domain events
|
||||
- [ ] Add event consumers for downstream processing
|
||||
|
||||
## Phase 6: Cosmos DB Migration (When Ready)
|
||||
- [ ] Create `CosmosSessionRepository`, `CosmosRuleRepository`, etc.
|
||||
- [ ] Create `AddCosmosDataAccess()` extension method
|
||||
- [ ] Replace inverted-tick indexing with native ORDER BY DESC
|
||||
- [ ] Replace denormalized indexes with Cosmos queries
|
||||
- [ ] Handle 2MB document limit vs. 64KB Table Storage limit
|
||||
- [ ] Swap `AddTableStorageDataAccess()` → `AddCosmosDataAccess()` in Program.cs
|
||||
|
||||
Reference in New Issue
Block a user