From 9a4f763cf002b46fd59f5c12c3b3b6dc9817e371 Mon Sep 17 00:00:00 2001 From: Dave Glover Date: Mon, 3 Jun 2024 12:14:58 +1000 Subject: [PATCH 01/15] Strongly typed admin portal with metrics --- .../AoaiProxyContext.cs | 103 +++++++- .../EventManagement/EventEditor.razor | 3 +- .../Components/Pages/EventList.razor | 60 +++-- .../Components/Pages/EventList.razor.cs | 23 ++ .../Components/Pages/EventMetrics.razor | 100 ++++---- .../Components/Pages/EventMetrics.razor.cs | 131 +++++++--- .../Components/Pages/EventReport.razor | 37 +-- .../Components/Pages/EventReport.razor.cs | 31 ++- .../Components/Pages/ModelEdit.razor.cs | 5 +- .../Components/Pages/ModelList.razor | 30 ++- .../Components/Pages/ModelList.razor.cs | 2 +- .../Database/ActiveAttendeeGrowthView.cs | 13 + .../Database/Event.cs | 40 +-- .../Database/EventAttendee.cs | 15 +- .../Database/EventAttendeeRequest.cs | 17 ++ .../Database/EventRegistrations.cs | 11 + .../Database/Metric.cs | 21 ++ .../Database/MetricView.cs | 21 ++ .../Database/Owner.cs | 13 +- .../Database/OwnerCatalog.cs | 12 +- .../Database/OwnerEventMap.cs | 13 +- src/AzureOpenAIProxy.Management/Dockerfile | 35 ++- .../Models/ChartData.cs | 7 - .../Models/EventMetric.cs | 11 - .../Models/EventWithRegistration.cs | 3 - .../Models/ModelCounts.cs | 10 - .../Models/ModelData.cs | 7 - src/AzureOpenAIProxy.Management/Program.cs | 54 +--- .../Services/AuthService.cs | 9 +- .../Services/EventService.cs | 42 ++- .../Services/IAuthService.cs | 3 +- .../Services/IEventService.cs | 3 +- .../Services/IMetricService.cs | 10 +- .../Services/IModelService.cs | 1 + .../Services/MetricService.cs | 242 +++++++----------- .../Services/ModelService.cs | 57 +++-- src/AzureOpenAIProxy.Management/Usings.cs | 5 - 37 files changed, 705 insertions(+), 495 deletions(-) create mode 100644 src/AzureOpenAIProxy.Management/Database/ActiveAttendeeGrowthView.cs create mode 100644 src/AzureOpenAIProxy.Management/Database/EventAttendeeRequest.cs create mode 100644 src/AzureOpenAIProxy.Management/Database/EventRegistrations.cs create mode 100644 src/AzureOpenAIProxy.Management/Database/Metric.cs create mode 100644 src/AzureOpenAIProxy.Management/Database/MetricView.cs delete mode 100644 src/AzureOpenAIProxy.Management/Models/ChartData.cs delete mode 100644 src/AzureOpenAIProxy.Management/Models/EventMetric.cs delete mode 100644 src/AzureOpenAIProxy.Management/Models/EventWithRegistration.cs delete mode 100644 src/AzureOpenAIProxy.Management/Models/ModelCounts.cs delete mode 100644 src/AzureOpenAIProxy.Management/Models/ModelData.cs delete mode 100644 src/AzureOpenAIProxy.Management/Usings.cs diff --git a/src/AzureOpenAIProxy.Management/AoaiProxyContext.cs b/src/AzureOpenAIProxy.Management/AoaiProxyContext.cs index b19b5e98..f162392b 100644 --- a/src/AzureOpenAIProxy.Management/AoaiProxyContext.cs +++ b/src/AzureOpenAIProxy.Management/AoaiProxyContext.cs @@ -1,5 +1,7 @@ -using AzureOpenAIProxy.Management.Database; +using System; +using System.Collections.Generic; using Microsoft.EntityFrameworkCore; +using AzureOpenAIProxy.Management.Database; namespace AzureOpenAIProxy.Management; @@ -14,10 +16,18 @@ public AoaiProxyContext(DbContextOptions options) { } + public virtual DbSet ActiveAttendeeGrowthViews { get; set; } + public virtual DbSet Events { get; set; } public virtual DbSet EventAttendees { get; set; } + public virtual DbSet EventAttendeeRequests { get; set; } + + public virtual DbSet Metrics { get; set; } + + public virtual DbSet MetricViews { get; set; } + public virtual DbSet Owners { get; set; } public virtual DbSet OwnerCatalogs { get; set; } @@ -30,6 +40,19 @@ protected override void OnModelCreating(ModelBuilder modelBuilder) .HasPostgresEnum("aoai", "model_type", new[] { "openai-chat", "openai-embedding", "openai-dalle3", "openai-whisper", "openai-completion", "openai-instruct", "azure-ai-search" }) .HasPostgresExtension("aoai", "pgcrypto"); + modelBuilder.Entity(entity => + { + entity + .HasNoKey() + .ToView("active_attendee_growth_view", "aoai"); + + entity.Property(e => e.Attendees).HasColumnName("attendees"); + entity.Property(e => e.DateStamp).HasColumnName("date_stamp"); + entity.Property(e => e.EventId) + .HasMaxLength(50) + .HasColumnName("event_id"); + }); + modelBuilder.Entity(entity => { entity.HasKey(e => e.EventId).HasName("event_pkey"); @@ -48,10 +71,6 @@ protected override void OnModelCreating(ModelBuilder modelBuilder) entity.Property(e => e.EventCode) .HasMaxLength(64) .HasColumnName("event_code"); - entity.Property(e => e.EventSharedCode) - .HasMaxLength(64) - .IsRequired(false) - .HasColumnName("event_shared_code"); entity.Property(e => e.EventImageUrl) .HasMaxLength(256) .IsRequired(false) @@ -59,6 +78,10 @@ protected override void OnModelCreating(ModelBuilder modelBuilder) entity.Property(e => e.EventMarkdown) .HasMaxLength(8192) .HasColumnName("event_markdown"); + entity.Property(e => e.EventSharedCode) + .HasMaxLength(64) + .IsRequired(false) + .HasColumnName("event_shared_code"); entity.Property(e => e.MaxTokenCap).HasColumnName("max_token_cap"); entity.Property(e => e.OrganizerEmail) .HasMaxLength(128) @@ -121,6 +144,76 @@ protected override void OnModelCreating(ModelBuilder modelBuilder) .HasConstraintName("fk_eventattendee_event"); }); + modelBuilder.Entity(entity => + { + entity.HasKey(e => new { e.ApiKey, e.DateStamp }).HasName("eventattendeerequest_pkey"); + + entity.ToTable("event_attendee_request", "aoai"); + + entity.Property(e => e.ApiKey) + .HasColumnType("character varying") + .HasColumnName("api_key"); + entity.Property(e => e.DateStamp).HasColumnName("date_stamp"); + entity.Property(e => e.RequestCount).HasColumnName("request_count"); + entity.Property(e => e.TokenCount).HasColumnName("token_count"); + + entity.HasOne(d => d.ApiKeyNavigation).WithMany(p => p.EventAttendeeRequests) + .HasPrincipalKey(p => p.ApiKey) + .HasForeignKey(d => d.ApiKey) + .HasConstraintName("fk_eventattendeerequest_eventattendee"); + }); + + modelBuilder.Entity(entity => + { + entity + .HasNoKey() + .ToTable("metric", "aoai"); + + entity.HasIndex(e => e.EventId, "event_id_index"); + + entity.Property(e => e.ApiKey) + .HasColumnType("character varying") + .HasColumnName("api_key"); + entity.Property(e => e.DateStamp) + .HasDefaultValueSql("CURRENT_DATE") + .HasColumnName("date_stamp"); + entity.Property(e => e.EventId) + .HasMaxLength(50) + .HasColumnName("event_id"); + entity.Property(e => e.Resource) + .HasMaxLength(64) + .HasColumnName("resource"); + entity.Property(e => e.TimeStamp) + .HasDefaultValueSql("CURRENT_TIME") + .HasColumnName("time_stamp"); + entity.Property(e => e.Usage) + .HasColumnType("jsonb") + .HasColumnName("usage"); + + entity.HasOne(d => d.Event).WithMany() + .HasForeignKey(d => d.EventId) + .HasConstraintName("fk_metric"); + }); + + modelBuilder.Entity(entity => + { + entity + .HasNoKey() + .ToView("metric_view", "aoai"); + + entity.Property(e => e.CompletionTokens).HasColumnName("completion_tokens"); + entity.Property(e => e.DateStamp).HasColumnName("date_stamp"); + entity.Property(e => e.EventId) + .HasMaxLength(50) + .HasColumnName("event_id"); + entity.Property(e => e.PromptTokens).HasColumnName("prompt_tokens"); + entity.Property(e => e.Resource) + .HasMaxLength(64) + .HasColumnName("resource"); + entity.Property(e => e.TimeStamp).HasColumnName("time_stamp"); + entity.Property(e => e.TotalTokens).HasColumnName("total_tokens"); + }); + modelBuilder.Entity(entity => { entity.HasKey(e => e.OwnerId).HasName("owner_pkey"); diff --git a/src/AzureOpenAIProxy.Management/Components/EventManagement/EventEditor.razor b/src/AzureOpenAIProxy.Management/Components/EventManagement/EventEditor.razor index 27b7ba77..0f2639a9 100644 --- a/src/AzureOpenAIProxy.Management/Components/EventManagement/EventEditor.razor +++ b/src/AzureOpenAIProxy.Management/Components/EventManagement/EventEditor.razor @@ -49,7 +49,8 @@ - - New Event + + New Event + - Event Organiser - Duration - Active - - + When + Regd + Active - @context.EventCode - @context.OrganizerName
@context.OrganizerEmail
- @context.StartTimestamp.ToString("dd/MM/yyyy HH:mm") - - @context.EndTimestamp.ToString("dd/MM/yyyy HH:mm")
- @{ - var timeZoneInfo = TimeZoneInfo.FindSystemTimeZoneById(@context.TimeZoneLabel); - var displayName = timeZoneInfo.DisplayName; - } - @displayName + + + @context.EventCode + + @string.Join(", ", context.Catalogs.Select(catalog => catalog.FriendlyName).OrderBy(catalog => catalog)) + + - @(context.Active ? "Yes" : "No") - Edit - + + @context.OrganizerName + @context.OrganizerEmail + - Attendee + + + @context.StartTimestamp.ToString("yyyy-MMM-dd HH:mm") + + + @context.EndTimestamp.ToString("yyyy-MMM-dd HH:mm") + + @context.TimeZoneLabel + - Metrics + @context.EventAttendees.Count + + @(context.Active ? "Yes" : "No") + + + + + + +
- diff --git a/src/AzureOpenAIProxy.Management/Components/Pages/EventList.razor.cs b/src/AzureOpenAIProxy.Management/Components/Pages/EventList.razor.cs index 499a691f..d7b43bd7 100644 --- a/src/AzureOpenAIProxy.Management/Components/Pages/EventList.razor.cs +++ b/src/AzureOpenAIProxy.Management/Components/Pages/EventList.razor.cs @@ -1,6 +1,7 @@ using AzureOpenAIProxy.Management.Database; using AzureOpenAIProxy.Management.Services; using Microsoft.AspNetCore.Components; +using MudBlazor; namespace AzureOpenAIProxy.Management.Components.Pages; @@ -12,7 +13,29 @@ public partial class EventList : ComponentBase [Inject] public required IConfiguration Configuration { get; set; } + [Inject] + public required IDialogService DialogService { get; set; } + public IEnumerable? Events { get; set; } protected override async Task OnInitializedAsync() => Events = await EventService.GetOwnerEventsAsync(); + + private async Task OpenDialog(Event @event) + { + DialogParameters parameters = new() + { + { x => x.ContentText, $"Do you really want to delete the event '{@event.EventCode}'?" }, + { x => x.ButtonText, "Delete" }, + { x => x.Color, Color.Error } + }; + var options = new DialogOptions { CloseOnEscapeKey = true }; + var dialog = await DialogService.ShowAsync("Delete Record", parameters, options); + var result = await dialog.Result; + + if (!result.Canceled) + { + await EventService.DeleteEventAsync(@event.EventId); + Events = await EventService.GetOwnerEventsAsync(); + } + } } diff --git a/src/AzureOpenAIProxy.Management/Components/Pages/EventMetrics.razor b/src/AzureOpenAIProxy.Management/Components/Pages/EventMetrics.razor index c2ad8d80..1607341f 100644 --- a/src/AzureOpenAIProxy.Management/Components/Pages/EventMetrics.razor +++ b/src/AzureOpenAIProxy.Management/Components/Pages/EventMetrics.razor @@ -5,63 +5,65 @@ @namespace AzureOpenAIProxy.Management.Components.Pages -@if (Event is not null && EventMetric is not null && EventMetric?.ModelData?.ModelCounts is not null) +@if (Event is not null && ModelCounts is not null) { AI Proxy Metrics | @Event.EventCode + + @Event.EventCode + - - @Event.EventCode - + + Organizer: @Event.OrganizerName (@Event.OrganizerEmail) + When: @Event.StartTimestamp.ToString("yyyy-MMM-dd HH:mm") - @Event.EndTimestamp.ToString("yyyy-MMM-dd HH:mm") (@Event.TimeZoneLabel) + Max Token Cap: @Event.MaxTokenCap | Daily Request Cap: @Event.DailyRequestCap + Registrations: @AttendeeCount | Active Registrations: @ActiveRegistrations + Total Requests: @RequestCount + - - Registrations: @EventMetric.AttendeeCount - Active Registrations: @ActiveRegistrations - Total Request count: @EventMetric.RequestCount - - - + - + - + - - - - - Resource Name (Deployment or Index Name) - - - - Prompt tokens - - - - Completion tokens - - - - Total tokens - - - - Requests - - + + + + + Resource Name (Deployment or Index Name) + + + + Prompt tokens + + + + Completion tokens + + + + Total tokens + + + + Requests + + - - @context.Resource - @context.PromptTokens - @context.CompletionTokens - @context.TotalTokens - @context.Count - - - + + @context.Resource + @context.PromptTokens + @context.CompletionTokens + @context.TotalTokens + @context.Count + + + } diff --git a/src/AzureOpenAIProxy.Management/Components/Pages/EventMetrics.razor.cs b/src/AzureOpenAIProxy.Management/Components/Pages/EventMetrics.razor.cs index ef13c649..588da9a2 100644 --- a/src/AzureOpenAIProxy.Management/Components/Pages/EventMetrics.razor.cs +++ b/src/AzureOpenAIProxy.Management/Components/Pages/EventMetrics.razor.cs @@ -5,6 +5,15 @@ namespace AzureOpenAIProxy.Management.Components.Pages; +public class ModelCounts +{ + public string? Resource { get; set; } + public int Count { get; set; } + public long PromptTokens { get; set; } + public long CompletionTokens { get; set; } + public long TotalTokens { get; set; } +} + public partial class EventMetrics { [Inject] @@ -21,12 +30,17 @@ public partial class EventMetrics private List RequestChartSeries { get; set; } = []; private string[] RequestChartLabels { get; set; } = []; - private EventMetric? EventMetric { get; set; } + // private EventMetric? EventMetric { get; set; } private Event? Event { get; set; } - private List? ActiveUsers { get; set; } + private List? ActiveUsers { get; set; } private List ActiveUsersChartSeries { get; set; } = []; private string[] ActiveUsersChartLabels { get; set; } = []; private long ActiveRegistrations { get; set; } + private List ModelCounts { get; set; } = []; + + private int RequestCount { get; set; } + + private int AttendeeCount { get; set; } private bool IsLoading { get; set; } = false; @@ -39,76 +53,115 @@ private async Task GetData() IsLoading = true; - EventMetric = await MetricService.GetEventMetricsAsync(EventId); + + (AttendeeCount, RequestCount) = MetricService.GetAttendeeMetricsAsync(EventId); + List MetricsData = await MetricService.GetEventMetricsAsync(EventId); Event = await EventService.GetEventAsync(EventId); - if (EventMetric?.ModelData?.ChartData.Count > 0) + if (MetricsData is null) + { + return; + } + + // Generate Model Counts for summary table + ModelCounts = [.. MetricsData + .GroupBy(r => new { r.EventId, r.Resource }) + .Select(g => new ModelCounts + { + Resource = g.Key.Resource, + Count = (int)g.Sum(x => (long)x.Requests), + PromptTokens = g.Sum(x => (long)x.PromptTokens), + CompletionTokens = g.Sum(x => (long)x.CompletionTokens), + TotalTokens = g.Sum(x => (long)x.TotalTokens) + }) + .OrderByDescending(x => x.Count)]; + + // Generate Resouce Requests chart data + List requestsChartData = [.. MetricsData + .GroupBy(r => r.DateStamp) + .Select(g => new EventChartData + { + DateStamp = g.Key, + Count = g.Sum(x => x.Requests) + }) + .OrderBy(x => x.DateStamp)]; + + long runningTotal = 0; + requestsChartData.ForEach(x => runningTotal = x.Count += runningTotal); + + if (requestsChartData.Count > 0) { - (RequestChartSeries, RequestChartLabels) = BuildRequestsChart(EventMetric.ModelData.ChartData); + // Create Resource Requests line chart + (RequestChartSeries, RequestChartLabels) = BuildRequestsChart(requestsChartData); } + // Get Active Registrations ActiveUsers = await MetricService.GetActiveRegistrationsAsync(EventId); - // get the last value for active registrations if (ActiveUsers?.Count > 0) { ActiveRegistrations = ActiveUsers.Last().Count; } + // Create Active Registrations line chart (ActiveUsersChartSeries, ActiveUsersChartLabels) = BuildActiveUsersChart(ActiveUsers); IsLoading = false; } - protected override Task OnInitializedAsync() => GetData(); + protected override async Task OnInitializedAsync() + { + await GetData(); + } - private Task RefreshData() => GetData(); + private async void RefreshData() + { + await GetData(); + } - private (List ActiveUsersChartSeries, string[] ActiveUsersChartLabels) BuildActiveUsersChart(List? activeUsers) + private (List ActiveUsersChartSeries, string[] ActiveUsersChartLabels) BuildActiveUsersChart(List? activeUsers) { - if (activeUsers is null) + if (activeUsers != null) { - return ([], []); - } - - List cd = FillMissingDays(activeUsers); + List cd = FillMissingDays(activeUsers); - ActiveUsersChartSeries = - [ - new ChartSeries + ActiveUsersChartSeries = + [ + new ChartSeries { Name = "New Active Registrations", Data = activeUsers.Select(au => (double)au.Count).ToArray() } - ]; + ]; - ActiveUsersChartLabels = activeUsers.Select(au => au.DateStamp.ToString("dd MMM")).ToArray(); - ActiveUsersChartLabels = ScaleLabels(ActiveUsersChartLabels); + ActiveUsersChartLabels = activeUsers.Select(au => au.DateStamp.ToString("dd MMM")).ToArray(); + ActiveUsersChartLabels = ScaleLabels(ActiveUsersChartLabels); - return (ActiveUsersChartSeries, ActiveUsersChartLabels); + return (ActiveUsersChartSeries, ActiveUsersChartLabels); + } + return ([], []); } - private (List ChartSeries, string[] ChartLabels) BuildRequestsChart(List? chartData) + private (List ChartSeries, string[] ChartLabels) BuildRequestsChart(List? chartData) { - if (chartData is null) + if (chartData != null) { - return ([], []); - } - - List cd = FillMissingDays(chartData); + List cd = FillMissingDays(chartData); - RequestChartSeries = - [ - new ChartSeries + RequestChartSeries = + [ + new ChartSeries { Name = "Requests", Data = cd.Select(cd => (double)cd.Count).ToArray() } - ]; + ]; - RequestChartLabels = cd.Select(cd => cd.DateStamp.ToString("dd MMM")).ToArray(); - RequestChartLabels = ScaleLabels(RequestChartLabels); + RequestChartLabels = cd.Select(cd => cd.DateStamp.ToString("dd MMM")).ToArray(); + RequestChartLabels = ScaleLabels(RequestChartLabels); - return (RequestChartSeries, RequestChartLabels); + return (RequestChartSeries, RequestChartLabels); + } + return ([], []); } private static string[] ScaleLabels(string[] ChartLabels) @@ -119,13 +172,13 @@ private static string[] ScaleLabels(string[] ChartLabels) return ChartLabels; } - private static List FillMissingDays(List? chartData) + private static List FillMissingDays(List? chartData) { DateTime? previousDay = null; long previousRequests = 0; - List cd = []; + List cd = []; - if (chartData is null) + if (chartData == null) { return cd; } @@ -133,11 +186,11 @@ private static List FillMissingDays(List? chartData) // rebuild chart data to fill in missing days foreach (var row in chartData.OrderBy(r => r.DateStamp)) { - if (previousDay is not null && previousDay.Value.AddDays(1) < row.DateStamp) + if (previousDay != null && previousDay.Value.AddDays(1) < row.DateStamp) { while (previousDay.Value.AddDays(1) < row.DateStamp) { - cd.Add(new ChartData { DateStamp = previousDay.Value.AddDays(1), Count = previousRequests }); + cd.Add(new EventChartData { DateStamp = previousDay.Value.AddDays(1), Count = previousRequests }); previousDay = previousDay.Value.AddDays(1); } } diff --git a/src/AzureOpenAIProxy.Management/Components/Pages/EventReport.razor b/src/AzureOpenAIProxy.Management/Components/Pages/EventReport.razor index 299a44cc..e6f8bd0e 100644 --- a/src/AzureOpenAIProxy.Management/Components/Pages/EventReport.razor +++ b/src/AzureOpenAIProxy.Management/Components/Pages/EventReport.razor @@ -1,62 +1,69 @@ @page "/reports" +@using AzureOpenAIProxy.Management.Database +@using AzureOpenAIProxy.Management.Services; @namespace AzureOpenAIProxy.Management.Components.Pages AI Proxy Reports -@if (AllEvents?.Count() > 0) +@if (EventRegistrations?.Count > 0) { Events Report - + + Total events: @EventCount Total registrations: @TotalRegistations Select the event to view the metrics. - + SortBy="new Func(x => x.EventName)"> Event metrics - + Organizer name - + Start date - + End date - - Registrations + + Regd - Attendee link + Event - @context.Name + @context.EventName @context.OrganizerName - @context.StartDate.ToString("yyyy MMM dd") - @context.EndDate.ToString("yyyy MMM dd") - @context.RegistrationCount + + @context.StartDate.ToString("yyyy-MMM-dd") + + + @context.EndDate.ToString("yyyy-MMM-dd") + + @context.Registered diff --git a/src/AzureOpenAIProxy.Management/Components/Pages/EventReport.razor.cs b/src/AzureOpenAIProxy.Management/Components/Pages/EventReport.razor.cs index 590d7e83..f6ddd31d 100644 --- a/src/AzureOpenAIProxy.Management/Components/Pages/EventReport.razor.cs +++ b/src/AzureOpenAIProxy.Management/Components/Pages/EventReport.razor.cs @@ -1,33 +1,40 @@ -namespace AzureOpenAIProxy.Management.Components.Pages; +using System.Diagnostics.Tracing; +using AzureOpenAIProxy.Management.Database; +using AzureOpenAIProxy.Management.Services; +using Microsoft.AspNetCore.Components; + +namespace AzureOpenAIProxy.Management.Components.Pages; public partial class EventReport { [Inject] private IMetricService MetricService { get; set; } = null!; - [Inject] - public required IEventService EventService { get; set; } - [Inject] public required IConfiguration Configuration { get; set; } - private IEnumerable? AllEvents { get; set; } - + private List? EventRegistrations { get; set; } private int TotalRegistations { get; set; } - private string searchString = ""; + + private int EventCount {get; set;} + private string searchString1 = ""; protected override async Task OnInitializedAsync() { - AllEvents = await EventService.GetEventsWithRegistrationsAsync(); - // calculate total attendees - TotalRegistations = AllEvents.Sum(e => e.RegistrationCount); + EventRegistrations = await MetricService.GetAllEventsAsync(); + // calculate total registrations + TotalRegistations = EventRegistrations.Sum(e => e.Registered); + + EventCount = EventRegistrations.Count; } - private bool FilterFunc(EventWithRegistration element) + private bool EventFilter(EventRegistrations element) => FilterByEventOrOrganizer(element, searchString1); + + private bool FilterByEventOrOrganizer(EventRegistrations element, string searchString) { if (string.IsNullOrWhiteSpace(searchString)) return true; - if (element.Name.Contains(searchString, StringComparison.OrdinalIgnoreCase)) + if (element.EventName.Contains(searchString, StringComparison.OrdinalIgnoreCase)) return true; if (element.OrganizerName.Contains(searchString, StringComparison.OrdinalIgnoreCase)) return true; diff --git a/src/AzureOpenAIProxy.Management/Components/Pages/ModelEdit.razor.cs b/src/AzureOpenAIProxy.Management/Components/Pages/ModelEdit.razor.cs index ee44384d..bf8b4fdf 100644 --- a/src/AzureOpenAIProxy.Management/Components/Pages/ModelEdit.razor.cs +++ b/src/AzureOpenAIProxy.Management/Components/Pages/ModelEdit.razor.cs @@ -3,6 +3,7 @@ using AzureOpenAIProxy.Management.Database; using AzureOpenAIProxy.Management.Services; using Microsoft.AspNetCore.Components; +using Microsoft.EntityFrameworkCore; namespace AzureOpenAIProxy.Management.Components.Pages; @@ -15,7 +16,7 @@ public partial class ModelEdit : ComponentBase public IModelService ModelService { get; set; } = null!; [Inject] - public AoaiProxyContext DbContext { get; set; } = null!; + public AoaiProxyContext db { get; set; } = null!; [Inject] public NavigationManager NavigationManager { get; set; } = null!; @@ -52,7 +53,7 @@ protected override async Task OnInitializedAsync() private async Task OnValidSubmit(ModelEditorModel model) { - OwnerCatalog? m = await DbContext.OwnerCatalogs.FindAsync(Guid.Parse(Id)); + OwnerCatalog? m = await db.OwnerCatalogs.FindAsync(Guid.Parse(Id)); if (m is null) { diff --git a/src/AzureOpenAIProxy.Management/Components/Pages/ModelList.razor b/src/AzureOpenAIProxy.Management/Components/Pages/ModelList.razor index 7ddc7ddb..056325d2 100644 --- a/src/AzureOpenAIProxy.Management/Components/Pages/ModelList.razor +++ b/src/AzureOpenAIProxy.Management/Components/Pages/ModelList.razor @@ -7,32 +7,40 @@ - New Resource + + + New Resource + - + + Friendly Name Resource Name Resource Type Location + Refd Active - - @context.FriendlyName + + @context.FriendlyName + @context.DeploymentName @(context.ModelType!.Value.ToString().Replace("_", " ")) @(context.Location) + @(context.Events.Count) @(context.Active ? "Yes" : "No") - Edit - - - - Delete - + + + + diff --git a/src/AzureOpenAIProxy.Management/Components/Pages/ModelList.razor.cs b/src/AzureOpenAIProxy.Management/Components/Pages/ModelList.razor.cs index a69cfce6..b96cfa26 100644 --- a/src/AzureOpenAIProxy.Management/Components/Pages/ModelList.razor.cs +++ b/src/AzureOpenAIProxy.Management/Components/Pages/ModelList.razor.cs @@ -21,7 +21,7 @@ private async Task OpenDialog(OwnerCatalog resource) { DialogParameters parameters = new() { - { x => x.ContentText, $"Do you really want to delete the resource '{resource.FriendlyName}'? You can only delete resources that aren't currently in use." }, + { x => x.ContentText, $"Do you really want to delete the resource '{resource.FriendlyName}'?" }, { x => x.ButtonText, "Delete" }, { x => x.Color, Color.Error } }; diff --git a/src/AzureOpenAIProxy.Management/Database/ActiveAttendeeGrowthView.cs b/src/AzureOpenAIProxy.Management/Database/ActiveAttendeeGrowthView.cs new file mode 100644 index 00000000..6b182c98 --- /dev/null +++ b/src/AzureOpenAIProxy.Management/Database/ActiveAttendeeGrowthView.cs @@ -0,0 +1,13 @@ +using System; +using System.Collections.Generic; + +namespace AzureOpenAIProxy.Management.Database; + +public partial class ActiveAttendeeGrowthView +{ + public string EventId { get; set; } = null!; + + public DateTime DateStamp { get; set; } + + public decimal Attendees { get; set; } +} diff --git a/src/AzureOpenAIProxy.Management/Database/Event.cs b/src/AzureOpenAIProxy.Management/Database/Event.cs index 18b56d09..49009421 100644 --- a/src/AzureOpenAIProxy.Management/Database/Event.cs +++ b/src/AzureOpenAIProxy.Management/Database/Event.cs @@ -1,41 +1,43 @@ -namespace AzureOpenAIProxy.Management.Database; +using System; +using System.Collections.Generic; + +namespace AzureOpenAIProxy.Management.Database; public partial class Event { public string EventId { get; set; } = null!; - + public string OwnerId { get; set; } = null!; - + public string EventCode { get; set; } = null!; - + public string EventMarkdown { get; set; } = null!; - + public DateTime StartTimestamp { get; set; } - + public DateTime EndTimestamp { get; set; } - + public string OrganizerName { get; set; } = null!; - + public string OrganizerEmail { get; set; } = null!; - + public int MaxTokenCap { get; set; } - + public int DailyRequestCap { get; set; } - + public bool Active { get; set; } - + public string? EventImageUrl { get; set; } - + public int TimeZoneOffset { get; set; } - + public string TimeZoneLabel { get; set; } = null!; - + public string? EventSharedCode { get; set; } - - + public virtual ICollection EventAttendees { get; set; } = new List(); - + public virtual ICollection OwnerEventMaps { get; set; } = new List(); - + public virtual ICollection Catalogs { get; set; } = new List(); } diff --git a/src/AzureOpenAIProxy.Management/Database/EventAttendee.cs b/src/AzureOpenAIProxy.Management/Database/EventAttendee.cs index a506aad6..a35d8027 100644 --- a/src/AzureOpenAIProxy.Management/Database/EventAttendee.cs +++ b/src/AzureOpenAIProxy.Management/Database/EventAttendee.cs @@ -1,14 +1,19 @@ -namespace AzureOpenAIProxy.Management.Database; +using System; +using System.Collections.Generic; + +namespace AzureOpenAIProxy.Management.Database; public partial class EventAttendee { public string UserId { get; set; } = null!; - + public string EventId { get; set; } = null!; - + public bool Active { get; set; } - + public string ApiKey { get; set; } = null!; - + public virtual Event Event { get; set; } = null!; + + public virtual ICollection EventAttendeeRequests { get; set; } = new List(); } diff --git a/src/AzureOpenAIProxy.Management/Database/EventAttendeeRequest.cs b/src/AzureOpenAIProxy.Management/Database/EventAttendeeRequest.cs new file mode 100644 index 00000000..f7776b4f --- /dev/null +++ b/src/AzureOpenAIProxy.Management/Database/EventAttendeeRequest.cs @@ -0,0 +1,17 @@ +using System; +using System.Collections.Generic; + +namespace AzureOpenAIProxy.Management.Database; + +public partial class EventAttendeeRequest +{ + public string ApiKey { get; set; } = null!; + + public DateOnly DateStamp { get; set; } + + public int RequestCount { get; set; } + + public int TokenCount { get; set; } + + public virtual EventAttendee ApiKeyNavigation { get; set; } = null!; +} diff --git a/src/AzureOpenAIProxy.Management/Database/EventRegistrations.cs b/src/AzureOpenAIProxy.Management/Database/EventRegistrations.cs new file mode 100644 index 00000000..48068d50 --- /dev/null +++ b/src/AzureOpenAIProxy.Management/Database/EventRegistrations.cs @@ -0,0 +1,11 @@ +namespace AzureOpenAIProxy.Management.Database; + +public class EventRegistrations +{ + public string OrganizerName { get; set; } = null!; + public string EventName { get; set; } = null!; + public DateTime StartDate { get; set; } + public DateTime EndDate { get; set; } + public int Registered { get; set; } + public string EventId { get; set; } = null!; +} diff --git a/src/AzureOpenAIProxy.Management/Database/Metric.cs b/src/AzureOpenAIProxy.Management/Database/Metric.cs new file mode 100644 index 00000000..c94441bb --- /dev/null +++ b/src/AzureOpenAIProxy.Management/Database/Metric.cs @@ -0,0 +1,21 @@ +using System; +using System.Collections.Generic; + +namespace AzureOpenAIProxy.Management.Database; + +public partial class Metric +{ + public string EventId { get; set; } = null!; + + public string ApiKey { get; set; } = null!; + + public DateOnly DateStamp { get; set; } + + public TimeOnly TimeStamp { get; set; } + + public string Resource { get; set; } = null!; + + public string Usage { get; set; } = null!; + + public virtual Event Event { get; set; } = null!; +} diff --git a/src/AzureOpenAIProxy.Management/Database/MetricView.cs b/src/AzureOpenAIProxy.Management/Database/MetricView.cs new file mode 100644 index 00000000..1dafc941 --- /dev/null +++ b/src/AzureOpenAIProxy.Management/Database/MetricView.cs @@ -0,0 +1,21 @@ +using System; +using System.Collections.Generic; + +namespace AzureOpenAIProxy.Management.Database; + +public partial class MetricView +{ + public string EventId { get; set; } = null!; + + public string Resource { get; set; } = null!; + + public DateTime DateStamp { get; set; } + + public TimeOnly TimeStamp { get; set; } + + public int PromptTokens { get; set; } + + public int CompletionTokens { get; set; } + + public int TotalTokens { get; set; } +} diff --git a/src/AzureOpenAIProxy.Management/Database/Owner.cs b/src/AzureOpenAIProxy.Management/Database/Owner.cs index fe8c250d..a5bb0114 100644 --- a/src/AzureOpenAIProxy.Management/Database/Owner.cs +++ b/src/AzureOpenAIProxy.Management/Database/Owner.cs @@ -1,14 +1,17 @@ -namespace AzureOpenAIProxy.Management.Database; +using System; +using System.Collections.Generic; + +namespace AzureOpenAIProxy.Management.Database; public partial class Owner { public string OwnerId { get; set; } = null!; - + public string Name { get; set; } = null!; - + public string Email { get; set; } = null!; - + public virtual ICollection OwnerCatalogs { get; set; } = new List(); - + public virtual ICollection OwnerEventMaps { get; set; } = new List(); } diff --git a/src/AzureOpenAIProxy.Management/Database/OwnerCatalog.cs b/src/AzureOpenAIProxy.Management/Database/OwnerCatalog.cs index 5e0504b0..e8932e74 100644 --- a/src/AzureOpenAIProxy.Management/Database/OwnerCatalog.cs +++ b/src/AzureOpenAIProxy.Management/Database/OwnerCatalog.cs @@ -1,4 +1,6 @@ -using System.ComponentModel.DataAnnotations.Schema; +using System; +using System.Collections.Generic; +using System.ComponentModel.DataAnnotations.Schema; namespace AzureOpenAIProxy.Management.Database; @@ -16,10 +18,6 @@ public partial class OwnerCatalog [NotMapped] public string EndpointKey { get; set; } = null!; - public byte[] EndpointUrlEncrypted { get; set; } = null!; - - public byte[] EndpointKeyEncrypted { get; set; } = null!; - public bool Active { get; set; } public ModelType? ModelType { get; set; } @@ -28,6 +26,10 @@ public partial class OwnerCatalog public string FriendlyName { get; set; } = null!; + public byte[]? EndpointUrlEncrypted { get; set; } + + public byte[]? EndpointKeyEncrypted { get; set; } + public virtual Owner Owner { get; set; } = null!; public virtual ICollection Events { get; set; } = new List(); diff --git a/src/AzureOpenAIProxy.Management/Database/OwnerEventMap.cs b/src/AzureOpenAIProxy.Management/Database/OwnerEventMap.cs index 47fe95b8..ea473156 100644 --- a/src/AzureOpenAIProxy.Management/Database/OwnerEventMap.cs +++ b/src/AzureOpenAIProxy.Management/Database/OwnerEventMap.cs @@ -1,14 +1,17 @@ -namespace AzureOpenAIProxy.Management.Database; +using System; +using System.Collections.Generic; + +namespace AzureOpenAIProxy.Management.Database; public partial class OwnerEventMap { public string OwnerId { get; set; } = null!; - + public string EventId { get; set; } = null!; - + public bool Creator { get; set; } - + public virtual Event Event { get; set; } = null!; - + public virtual Owner Owner { get; set; } = null!; } diff --git a/src/AzureOpenAIProxy.Management/Dockerfile b/src/AzureOpenAIProxy.Management/Dockerfile index 7237ecb6..cb2ed64e 100644 --- a/src/AzureOpenAIProxy.Management/Dockerfile +++ b/src/AzureOpenAIProxy.Management/Dockerfile @@ -1,23 +1,22 @@ -FROM mcr.microsoft.com/dotnet/aspnet:8.0 AS base -USER app -WORKDIR /app -EXPOSE 8080 -EXPOSE 8081 +# https://devblogs.microsoft.com/dotnet/improving-multiplatform-container-support +FROM --platform=$BUILDPLATFORM mcr.microsoft.com/dotnet/sdk:8.0 AS build +ARG TARGETARCH -FROM mcr.microsoft.com/dotnet/sdk:8.0 AS build -ARG BUILD_CONFIGURATION=Release WORKDIR /src -COPY ["AzureOpenAIProxy.Management.csproj", "AzureOpenAIProxy.Management/"] -RUN dotnet restore "AzureOpenAIProxy.Management/AzureOpenAIProxy.Management.csproj" -WORKDIR "/src/AzureOpenAIProxy.Management" -COPY . . -RUN dotnet build -c $BUILD_CONFIGURATION -o /app/build -FROM build AS publish -ARG BUILD_CONFIGURATION=Release -RUN dotnet publish -c $BUILD_CONFIGURATION -o /app/publish /p:UseAppHost=false +# copy csproj and restore as distinct layers +COPY AzureOpenAIProxy.Management.csproj . +RUN dotnet restore -a $TARGETARCH -FROM base AS final +# copy and publish app and libraries +COPY . . +RUN dotnet publish -a $TARGETARCH --no-restore -o /app + +# final stage/image +FROM mcr.microsoft.com/dotnet/aspnet:8.0 +EXPOSE 8080 +EXPOSE 8081 WORKDIR /app -COPY --from=publish /app/publish . -ENTRYPOINT ["dotnet", "AzureOpenAIProxy.Management.dll"] +COPY --from=build /app . +USER $APP_UID +ENTRYPOINT ["./AzureOpenAIProxy.Management"] diff --git a/src/AzureOpenAIProxy.Management/Models/ChartData.cs b/src/AzureOpenAIProxy.Management/Models/ChartData.cs deleted file mode 100644 index 0c0f3559..00000000 --- a/src/AzureOpenAIProxy.Management/Models/ChartData.cs +++ /dev/null @@ -1,7 +0,0 @@ -namespace AzureOpenAIProxy.Management.Models; - -public class ChartData -{ - public DateTime DateStamp { get; set; } - public long Count { get; set; } -} diff --git a/src/AzureOpenAIProxy.Management/Models/EventMetric.cs b/src/AzureOpenAIProxy.Management/Models/EventMetric.cs deleted file mode 100644 index 45ea8dbf..00000000 --- a/src/AzureOpenAIProxy.Management/Models/EventMetric.cs +++ /dev/null @@ -1,11 +0,0 @@ -namespace AzureOpenAIProxy.Management.Models; -public class EventMetric -{ - public string EventId { get; set; } = null!; - - public int AttendeeCount { get; set; } - - public int RequestCount { get; set; } - - public ModelData? ModelData { get; set; } -} diff --git a/src/AzureOpenAIProxy.Management/Models/EventWithRegistration.cs b/src/AzureOpenAIProxy.Management/Models/EventWithRegistration.cs deleted file mode 100644 index 334d7d90..00000000 --- a/src/AzureOpenAIProxy.Management/Models/EventWithRegistration.cs +++ /dev/null @@ -1,3 +0,0 @@ -namespace AzureOpenAIProxy.Management.Models; - -public record EventWithRegistration(string Name, string OrganizerName, DateTime StartDate, DateTime EndDate, int RegistrationCount, string EventId); diff --git a/src/AzureOpenAIProxy.Management/Models/ModelCounts.cs b/src/AzureOpenAIProxy.Management/Models/ModelCounts.cs deleted file mode 100644 index b8e7dee8..00000000 --- a/src/AzureOpenAIProxy.Management/Models/ModelCounts.cs +++ /dev/null @@ -1,10 +0,0 @@ -namespace AzureOpenAIProxy.Management.Models; - -public class ModelCounts -{ - public string? Resource { get; set; } - public int Count { get; set; } - public long PromptTokens { get; set; } - public long CompletionTokens { get; set; } - public long TotalTokens { get; set; } -} diff --git a/src/AzureOpenAIProxy.Management/Models/ModelData.cs b/src/AzureOpenAIProxy.Management/Models/ModelData.cs deleted file mode 100644 index 3a78235d..00000000 --- a/src/AzureOpenAIProxy.Management/Models/ModelData.cs +++ /dev/null @@ -1,7 +0,0 @@ -namespace AzureOpenAIProxy.Management.Models; - -public class ModelData -{ - public List ModelCounts { get; set; } = []; - public List ChartData { get; set; } = []; -} diff --git a/src/AzureOpenAIProxy.Management/Program.cs b/src/AzureOpenAIProxy.Management/Program.cs index d01ea74e..18f5669d 100644 --- a/src/AzureOpenAIProxy.Management/Program.cs +++ b/src/AzureOpenAIProxy.Management/Program.cs @@ -1,3 +1,5 @@ +using Azure.Core; +using Azure.Identity; using AzureOpenAIProxy.Management; using AzureOpenAIProxy.Management.Components; using AzureOpenAIProxy.Management.Database; @@ -6,52 +8,22 @@ using Microsoft.Identity.Web; using MudBlazor.Services; using Npgsql; -using Azure.Identity; -using System.Drawing.Text; -async Task GetConnectionString(WebApplicationBuilder builder) +WebApplicationBuilder builder = WebApplication.CreateBuilder(args); + +NpgsqlDataSourceBuilder dataSourceBuilder = new(builder.Configuration.GetConnectionString("AoaiProxyContext")); + +if (!builder.Environment.IsDevelopment()) { - string? connection_string = builder.Configuration.GetConnectionString("AoaiProxyContext"); - if (string.IsNullOrEmpty(connection_string)) + dataSourceBuilder.UsePeriodicPasswordProvider(async (_, ct) => { - int db_port = 5432; - string? db_host = builder.Configuration["POSTGRES_SERVER"]; - string? db_name = builder.Configuration["POSTGRES_DATABASE"]; - if (string.IsNullOrEmpty(db_name)) - { - db_name = "aoai-proxy"; - } - string? db_user = builder.Configuration["POSTGRES_USER"]; - string? db_password = builder.Configuration["POSTGRES_PASSWORD"]; - - if (string.IsNullOrEmpty(db_host) || string.IsNullOrEmpty(db_user)) - { - throw new Exception("Database connection string not found and POSTGRES_SERVER, POSTGRES_USER not set"); - } - else - { - if (string.IsNullOrEmpty(db_password)) - { - var sqlServerTokenProvider = new DefaultAzureCredential(); - string accessToken = (await sqlServerTokenProvider.GetTokenAsync( - new Azure.Core.TokenRequestContext(scopes: new string[] { "https://ossrdbms-aad.database.windows.net/.default" }) { })).Token; - connection_string = $"Host={db_host};Port={db_port};Database={db_name};Username={db_user};Password={accessToken}"; - Console.WriteLine("Using Postgres Entra Authorization"); - } - else - { - connection_string = $"Host={db_host};Port={db_port};Database={db_name};Username={db_user};Password={db_password}"; - } - } - } - return connection_string; + var credential = new DefaultAzureCredential(); + var ctx = new TokenRequestContext(["https://ossrdbms-aad.database.windows.net/.default"]); + var tokenResponse = await credential.GetTokenAsync(ctx, ct); + return tokenResponse.Token; + }, TimeSpan.FromHours(4), TimeSpan.FromMinutes(1)); } -WebApplicationBuilder builder = WebApplication.CreateBuilder(args); - -string connection_string = await GetConnectionString(builder); - -NpgsqlDataSourceBuilder dataSourceBuilder = new(connection_string); dataSourceBuilder.MapEnum(); NpgsqlDataSource dataSource = dataSourceBuilder.Build(); diff --git a/src/AzureOpenAIProxy.Management/Services/AuthService.cs b/src/AzureOpenAIProxy.Management/Services/AuthService.cs index 8ae5a5c3..a2cee069 100644 --- a/src/AzureOpenAIProxy.Management/Services/AuthService.cs +++ b/src/AzureOpenAIProxy.Management/Services/AuthService.cs @@ -1,16 +1,9 @@ using Microsoft.AspNetCore.Components.Authorization; -using Microsoft.EntityFrameworkCore; namespace AzureOpenAIProxy.Management.Services; -public class AuthService(AuthenticationStateProvider authenticationStateProvider, AoaiProxyContext db) : IAuthService +public class AuthService(AuthenticationStateProvider authenticationStateProvider) : IAuthService { - public async Task GetCurrentOwnerAsync() - { - string entraId = await GetCurrentUserEntraIdAsync(); - return await db.Owners.FirstOrDefaultAsync(o => o.OwnerId == entraId) ?? throw new InvalidOperationException("EntraID is not a registered owner."); - } - public async Task GetCurrentUserEntraIdAsync() { AuthenticationState authState = await authenticationStateProvider.GetAuthenticationStateAsync(); diff --git a/src/AzureOpenAIProxy.Management/Services/EventService.cs b/src/AzureOpenAIProxy.Management/Services/EventService.cs index a00d55e4..50939b27 100644 --- a/src/AzureOpenAIProxy.Management/Services/EventService.cs +++ b/src/AzureOpenAIProxy.Management/Services/EventService.cs @@ -1,12 +1,14 @@ using System.Data; using System.Data.Common; using AzureOpenAIProxy.Management.Components.EventManagement; +using AzureOpenAIProxy.Management.Database; using Microsoft.EntityFrameworkCore; using Npgsql; using NpgsqlTypes; namespace AzureOpenAIProxy.Management.Services; + public class EventService(IAuthService authService, AoaiProxyContext db) : IEventService, IDisposable { private readonly DbConnection conn = db.Database.GetDbConnection(); @@ -91,7 +93,9 @@ public async Task> GetOwnerEventsAsync() return await db.Events .Where(e => e.OwnerEventMaps.Any(o => o.Owner.OwnerId == entraId)) .OrderByDescending(e => e.Active) - .ThenBy(e => e.StartTimestamp) + .ThenByDescending(e => e.EndTimestamp) + .Include(e => e.Catalogs) // Include the Catalogs collection + .Include(ea => ea.EventAttendees) .ToListAsync(); } @@ -169,18 +173,30 @@ protected virtual void Dispose(bool disposing) } } - public async Task> GetEventsWithRegistrationsAsync() + public async Task DeleteEventAsync(string id) { - var events = await db.Events - .Select(evt => new EventWithRegistration( - evt.EventCode, - evt.OrganizerName, - evt.StartTimestamp, - evt.EndTimestamp, - evt.EventAttendees.Count, - evt.EventId)) - .ToListAsync(); - - return events; + Event? evt = await db.Events.FindAsync(id); + + if (evt is null) + { + return; + } + + // double check there are no event attendees for the event + var usageInfo = await db.Events.Where(oc => oc.EventId == id) + .Select(oc => new + { + UsedInEvent = oc.EventAttendees.Count != 0 + }) + .FirstAsync(); + + // block deletion when it's in use to avoid cascading deletes + if (usageInfo.UsedInEvent) + { + return; + } + + db.Events.Remove(evt); + await db.SaveChangesAsync(); } } diff --git a/src/AzureOpenAIProxy.Management/Services/IAuthService.cs b/src/AzureOpenAIProxy.Management/Services/IAuthService.cs index b3b055b2..0789334e 100644 --- a/src/AzureOpenAIProxy.Management/Services/IAuthService.cs +++ b/src/AzureOpenAIProxy.Management/Services/IAuthService.cs @@ -1,7 +1,8 @@ +using AzureOpenAIProxy.Management.Database; + namespace AzureOpenAIProxy.Management.Services; public interface IAuthService { Task GetCurrentUserEntraIdAsync(); - Task GetCurrentOwnerAsync(); } diff --git a/src/AzureOpenAIProxy.Management/Services/IEventService.cs b/src/AzureOpenAIProxy.Management/Services/IEventService.cs index 3d1a2fbf..62bfc78b 100644 --- a/src/AzureOpenAIProxy.Management/Services/IEventService.cs +++ b/src/AzureOpenAIProxy.Management/Services/IEventService.cs @@ -1,4 +1,5 @@ using AzureOpenAIProxy.Management.Components.EventManagement; +using AzureOpenAIProxy.Management.Database; namespace AzureOpenAIProxy.Management.Services; @@ -14,5 +15,5 @@ public interface IEventService Task UpdateModelsForEventAsync(string id, IEnumerable modelIds); - Task> GetEventsWithRegistrationsAsync(); + Task DeleteEventAsync(string id); } diff --git a/src/AzureOpenAIProxy.Management/Services/IMetricService.cs b/src/AzureOpenAIProxy.Management/Services/IMetricService.cs index a493e2dc..4a505d4b 100644 --- a/src/AzureOpenAIProxy.Management/Services/IMetricService.cs +++ b/src/AzureOpenAIProxy.Management/Services/IMetricService.cs @@ -1,8 +1,14 @@ +using AzureOpenAIProxy.Management.Database; + namespace AzureOpenAIProxy.Management.Services; public interface IMetricService { - Task> GetActiveRegistrationsAsync(string eventId); + Task> GetAllEventsAsync(); + + Task> GetActiveRegistrationsAsync(string eventId); + + (int attendeeCount, int requestCount) GetAttendeeMetricsAsync(string eventId); - Task GetEventMetricsAsync(string eventId); + Task> GetEventMetricsAsync(string eventId); } diff --git a/src/AzureOpenAIProxy.Management/Services/IModelService.cs b/src/AzureOpenAIProxy.Management/Services/IModelService.cs index 085ea0eb..dd4f6a90 100644 --- a/src/AzureOpenAIProxy.Management/Services/IModelService.cs +++ b/src/AzureOpenAIProxy.Management/Services/IModelService.cs @@ -1,4 +1,5 @@ using AzureOpenAIProxy.Management.Components.ModelManagement; +using AzureOpenAIProxy.Management.Database; namespace AzureOpenAIProxy.Management.Services; diff --git a/src/AzureOpenAIProxy.Management/Services/MetricService.cs b/src/AzureOpenAIProxy.Management/Services/MetricService.cs index c1b77552..ae3ecb69 100644 --- a/src/AzureOpenAIProxy.Management/Services/MetricService.cs +++ b/src/AzureOpenAIProxy.Management/Services/MetricService.cs @@ -1,184 +1,114 @@ using System.Data; -using System.Data.Common; +using AzureOpenAIProxy.Management.Database; using Microsoft.EntityFrameworkCore; -using Npgsql; namespace AzureOpenAIProxy.Management.Services; -public class MetricService(AoaiProxyContext db) : IMetricService, IDisposable +public class EventMetricsData { - private readonly DbConnection conn = db.Database.GetDbConnection(); + public string EventId { get; set; } = null!; + public DateTime DateStamp { get; set; } + public string Resource { get; set; } = null!; + public long PromptTokens { get; set; } + public long CompletionTokens { get; set; } + public long TotalTokens { get; set; } + public long Requests { get; set; } +} + +public class EventChartData +{ + public DateTime DateStamp { get; set; } + public long Count { get; set; } +} - public void Dispose() - { - Dispose(true); - GC.SuppressFinalize(this); - } - protected virtual void Dispose(bool disposing) +public class MetricService(AoaiProxyContext db) : IMetricService +{ + public async Task> GetEventMetricsAsync(string eventId) { - if (disposing) + var query = from mv in db.MetricViews + where mv.EventId == eventId + group mv by new { mv.EventId, mv.DateStamp, mv.Resource } into g + orderby g.Count() descending + select new + { + g.Key.EventId, + g.Key.DateStamp, + g.Key.Resource, + PromptTokens = g.Sum(x => x.PromptTokens), + CompletionTokens = g.Sum(x => x.CompletionTokens), + TotalTokens = g.Sum(x => x.TotalTokens), + Requests = g.Count() + }; + + List metricsData = await query.Select(x => new EventMetricsData { - conn.Dispose(); - } + EventId = x.EventId, + DateStamp = x.DateStamp, + Resource = x.Resource, + PromptTokens = x.PromptTokens, + CompletionTokens = x.CompletionTokens, + TotalTokens = x.TotalTokens, + Requests = x.Requests + }).ToListAsync(); + + return metricsData; } - public async Task GetEventMetricsAsync(string eventId) + public (int attendeeCount, int requestCount) GetAttendeeMetricsAsync(string eventId) { - (int attendeeCount, int requestCount) = await GetAttendeeMetricsAsync(eventId); - ModelData modeldata = await GetModelCountAsync(eventId); + var userCount = db.EventAttendees + .Where(ea => ea.EventId == eventId) + .Count(); - return new() - { - EventId = eventId, - AttendeeCount = attendeeCount, - RequestCount = requestCount, - ModelData = modeldata - }; - } + var requestCount = db.Metrics + .Where(m => m.EventId == eventId) + .Count(); - private async Task GetModelCountAsync(string eventId) - { - if (conn.State != ConnectionState.Open) - await conn.OpenAsync(); - - using var modelCountCommand = conn.CreateCommand(); - modelCountCommand.CommandText = """ - SELECT event_id, date_stamp, resource, SUM(prompt_tokens) AS prompt_tokens, SUM(completion_tokens) AS completion_tokens, SUM(total_tokens) AS total_tokens, COUNT(*) AS requests - FROM aoai.metric_view where event_id = @EventId - GROUP BY date_stamp, event_id, resource - ORDER BY requests DESC - """; - - modelCountCommand.Parameters.Add(new NpgsqlParameter("EventId", eventId)); - using var reader = await modelCountCommand.ExecuteReaderAsync(); - - List metricsData = []; - while (reader.Read()) - { - var item = new MetricsData - { - EventId = eventId, - DateStamp = reader.GetDateTime(1), - Resource = reader.IsDBNull(2) ? "Unknown" : reader.GetString(2), - PromptTokens = reader.IsDBNull(3) ? 0 : reader.GetInt64(3), - CompletionTokens = reader.IsDBNull(4) ? 0 : reader.GetInt64(4), - TotalTokens = reader.IsDBNull(5) ? 0 : reader.GetInt64(5), - Requests = reader.IsDBNull(6) ? 0 : reader.GetInt64(6) - }; - - metricsData.Add(item); - }; - - var summary = metricsData - .GroupBy(r => new { r.EventId, r.Resource }) - .Select(g => new - { - g.Key.Resource, - PromptTokens = g.Sum(x => (long)x.PromptTokens), - CompletionTokens = g.Sum(x => (long)x.CompletionTokens), - TotalTokens = g.Sum(x => (long)x.TotalTokens), - Requests = g.Sum(x => (long)x.Requests) - }) - .OrderByDescending(x => x.Requests); - - List modelCounts = summary.Select(item => new ModelCounts - { - Resource = item.Resource, - Count = (int)item.Requests, - PromptTokens = item.PromptTokens, - CompletionTokens = item.CompletionTokens, - TotalTokens = item.TotalTokens - }).ToList(); - - // Create Line Chart Data X axis is DateStamp and Y axis is Requests - List chartData = [ - .. metricsData - .GroupBy(r => r.DateStamp) - .Select(g => new ChartData - { - DateStamp = g.Key, - Count = g.Sum(x => x.Requests) - }) - .OrderBy(x => x.DateStamp) - ]; - - long runningTotal = chartData.Aggregate(0L, (acc, x) => acc += x.Count); - - return new() - { - ModelCounts = modelCounts, - ChartData = chartData - }; + return (userCount, requestCount); } - private async Task<(int attendeeCount, int requestCount)> GetAttendeeMetricsAsync(string eventId) + public async Task> GetAllEventsAsync() { - if (conn.State != ConnectionState.Open) - await conn.OpenAsync(); - - using var eventAttendeeCommand = conn.CreateCommand(); - eventAttendeeCommand.CommandText = """ - SELECT - COUNT(user_id) as user_count, - (SELECT count(api_key) FROM aoai.metric WHERE event_id = @EventId) as request_count - FROM aoai.event_attendee - WHERE event_id = @EventId - """; - - eventAttendeeCommand.Parameters.Add(new NpgsqlParameter("EventId", eventId)); - - using var reader = await eventAttendeeCommand.ExecuteReaderAsync(); - if (reader.HasRows) + var query = from e in db.Events + join a in db.EventAttendees on e.EventId equals a.EventId into ea + from a in ea.DefaultIfEmpty() + group a by new { e.EventId, e.EventCode, e.OrganizerName, e.StartTimestamp, e.EndTimestamp } into g + select new + { + g.Key.EventId, + g.Key.EventCode, + g.Key.OrganizerName, + g.Key.StartTimestamp, + g.Key.EndTimestamp, + RegistrationCount = g.Count(a => a.ApiKey != null) + }; + + var allEvents = await query.Select(x => new EventRegistrations { - while (await reader.ReadAsync()) - { - return (reader.GetInt32(0), reader.GetInt32(1)); - } - } - return (0, 0); + EventId = x.EventId, + EventName = x.EventCode, + OrganizerName = x.OrganizerName, + StartDate = x.StartTimestamp, + EndDate = x.EndTimestamp, + Registered = x.RegistrationCount + }).ToListAsync(); + + return allEvents; } - public async Task> GetActiveRegistrationsAsync(string eventId) + public async Task> GetActiveRegistrationsAsync(string eventId) { - // call the Postgres view active_attendee_growth_view, read all the rows and return them as a list of tuples - - if (conn.State != ConnectionState.Open) - conn.Open(); - - using var activeRegistrationsCountCommand = conn.CreateCommand(); - - activeRegistrationsCountCommand.CommandText = """ - SELECT - date_stamp, attendees - FROM - aoai.active_attendee_growth_view - WHERE - event_id = @EventId - """; - - activeRegistrationsCountCommand.Parameters.Add(new NpgsqlParameter("EventId", eventId)); + var query = from a in db.ActiveAttendeeGrowthViews + where a.EventId == eventId + select new { a.DateStamp, a.Attendees }; - using var reader = await activeRegistrationsCountCommand.ExecuteReaderAsync(); - - List activeRegistrations = []; - - while (reader.Read()) + List activeRegistrations = await query.Select(x => new EventChartData { - activeRegistrations.Add(new ChartData { DateStamp = reader.GetDateTime(0), Count = reader.GetInt32(1) }); - }; + DateStamp = x.DateStamp, + Count = (int)x.Attendees + }).ToListAsync(); return activeRegistrations; } - - internal class MetricsData - { - public string EventId { get; set; } = null!; - public DateTime DateStamp { get; set; } - public string Resource { get; set; } = null!; - public long PromptTokens { get; set; } - public long CompletionTokens { get; set; } - public long TotalTokens { get; set; } - public long Requests { get; set; } - } } diff --git a/src/AzureOpenAIProxy.Management/Services/ModelService.cs b/src/AzureOpenAIProxy.Management/Services/ModelService.cs index defcbf23..fc93c605 100644 --- a/src/AzureOpenAIProxy.Management/Services/ModelService.cs +++ b/src/AzureOpenAIProxy.Management/Services/ModelService.cs @@ -1,6 +1,7 @@ using System.Data; using System.Data.Common; using AzureOpenAIProxy.Management.Components.ModelManagement; +using AzureOpenAIProxy.Management.Database; using Microsoft.EntityFrameworkCore; using Npgsql; using NpgsqlTypes; @@ -9,18 +10,31 @@ namespace AzureOpenAIProxy.Management.Services; public class ModelService(IAuthService authService, AoaiProxyContext db, IConfiguration configuration) : IModelService { - private const string PostgresEncryptionKey = "PostgresEncryptionKey"; - private readonly DbConnection connection = db.Database.GetDbConnection(); + private readonly DbConnection conn = db.Database.GetDbConnection(); + + public void Dispose() + { + Dispose(true); + GC.SuppressFinalize(this); + } + + protected virtual void Dispose(bool disposing) + { + if (disposing) + { + conn.Dispose(); + } + } private async Task PostgresEncryptValue(string value) { - if (connection.State != ConnectionState.Open) - await connection.OpenAsync(); + if (conn.State != ConnectionState.Open) + await conn.OpenAsync(); string? postgresEncryptionKey = configuration[PostgresEncryptionKey]; - using DbCommand command = connection.CreateCommand(); + using DbCommand command = conn.CreateCommand(); command.CommandText = $"SELECT aoai.pgp_sym_encrypt(@value, @postgresEncryptionKey);"; command.Parameters.Add(new NpgsqlParameter("value", NpgsqlDbType.Text) { Value = value }); command.Parameters.Add(new NpgsqlParameter("postgresEncryptionKey", NpgsqlDbType.Text) { Value = postgresEncryptionKey }); @@ -32,24 +46,33 @@ public class ModelService(IAuthService authService, AoaiProxyContext db, IConfig private async Task PostgresDecryptValue(byte[] value) { - if (connection.State != ConnectionState.Open) - await connection.OpenAsync(); + if (conn.State != ConnectionState.Open) + await conn.OpenAsync(); string? postgresEncryptionKey = configuration[PostgresEncryptionKey]; - using DbCommand command = connection.CreateCommand(); + using DbCommand command = conn.CreateCommand(); command.CommandText = $"SELECT aoai.pgp_sym_decrypt(@value, @postgresEncryptionKey)"; command.Parameters.Add(new NpgsqlParameter("value", NpgsqlDbType.Bytea) { Value = value }); command.Parameters.Add(new NpgsqlParameter("postgresEncryptionKey", NpgsqlDbType.Text) { Value = postgresEncryptionKey }); - using var reader = await command.ExecuteReaderAsync(); - await reader.ReadAsync(); - return reader[0] as string; + try + { + using var reader = await command.ExecuteReaderAsync(); + await reader.ReadAsync(); + return reader[0] as string; + } + catch (Exception) + { + return string.Empty; + } } public async Task AddOwnerCatalogAsync(ModelEditorModel model) { - Owner owner = await authService.GetCurrentOwnerAsync(); + string entraId = await authService.GetCurrentUserEntraIdAsync(); + + Owner owner = await db.Owners.FirstOrDefaultAsync(o => o.OwnerId == entraId) ?? throw new InvalidOperationException("EntraID is not a registered owner."); byte[]? endpointKey = await PostgresEncryptValue(model.EndpointKey!); byte[]? endpointUrl = await PostgresEncryptValue(model.EndpointUrl!); @@ -103,8 +126,8 @@ public async Task GetOwnerCatalogAsync(Guid catalogId) { var result = await db.OwnerCatalogs.FindAsync(catalogId); - string? endpointKey = await PostgresDecryptValue(result!.EndpointKeyEncrypted); - string? endpointUrl = await PostgresDecryptValue(result!.EndpointUrlEncrypted); + string? endpointKey = await PostgresDecryptValue(result!.EndpointKeyEncrypted!); + string? endpointUrl = await PostgresDecryptValue(result!.EndpointUrlEncrypted!); result.EndpointKey = endpointKey!; result.EndpointUrl = endpointUrl!; @@ -115,7 +138,11 @@ public async Task GetOwnerCatalogAsync(Guid catalogId) public async Task> GetOwnerCatalogsAsync() { string entraId = await authService.GetCurrentUserEntraIdAsync(); - var catalogItems = await db.OwnerCatalogs.Where(oc => oc.Owner.OwnerId == entraId).OrderBy(oc => oc.FriendlyName).ToListAsync(); + var catalogItems = await db.OwnerCatalogs + .Where(oc => oc.Owner.OwnerId == entraId) + .Include(oc => oc.Events) + .OrderBy(oc => oc.FriendlyName) + .ToListAsync(); return catalogItems; } diff --git a/src/AzureOpenAIProxy.Management/Usings.cs b/src/AzureOpenAIProxy.Management/Usings.cs deleted file mode 100644 index 27bfc5a7..00000000 --- a/src/AzureOpenAIProxy.Management/Usings.cs +++ /dev/null @@ -1,5 +0,0 @@ -global using AzureOpenAIProxy.Management.Database; -global using AzureOpenAIProxy.Management.Models; -global using AzureOpenAIProxy.Management.Services; -global using Microsoft.AspNetCore.Components; -global using MudBlazor; From e07092160159198b3bafa58726b66b3143e81837 Mon Sep 17 00:00:00 2001 From: Aaron Powell Date: Mon, 3 Jun 2024 02:38:55 +0000 Subject: [PATCH 02/15] whitespace --- .../Components/EventManagement/EventEditor.razor | 3 +-- 1 file changed, 1 insertion(+), 2 deletions(-) diff --git a/src/AzureOpenAIProxy.Management/Components/EventManagement/EventEditor.razor b/src/AzureOpenAIProxy.Management/Components/EventManagement/EventEditor.razor index 0f2639a9..27b7ba77 100644 --- a/src/AzureOpenAIProxy.Management/Components/EventManagement/EventEditor.razor +++ b/src/AzureOpenAIProxy.Management/Components/EventManagement/EventEditor.razor @@ -49,8 +49,7 @@ - Date: Mon, 3 Jun 2024 02:42:11 +0000 Subject: [PATCH 03/15] Fixing build issues --- src/AzureOpenAIProxy.Management/Program.cs | 3 +-- src/AzureOpenAIProxy.Management/Usings.cs | 4 ++++ 2 files changed, 5 insertions(+), 2 deletions(-) create mode 100644 src/AzureOpenAIProxy.Management/Usings.cs diff --git a/src/AzureOpenAIProxy.Management/Program.cs b/src/AzureOpenAIProxy.Management/Program.cs index 9af0ab1b..c109ea02 100644 --- a/src/AzureOpenAIProxy.Management/Program.cs +++ b/src/AzureOpenAIProxy.Management/Program.cs @@ -1,7 +1,6 @@ using Azure.Core; using Azure.Identity; using AzureOpenAIProxy.Management; -using AzureOpenAIProxy.Management.Components; using Microsoft.EntityFrameworkCore; using Microsoft.Identity.Web; using MudBlazor.Services; @@ -74,7 +73,7 @@ app.MapControllers(); -app.MapRazorComponents() +app.MapRazorComponents() .AddInteractiveServerRenderMode(); app.Run(); diff --git a/src/AzureOpenAIProxy.Management/Usings.cs b/src/AzureOpenAIProxy.Management/Usings.cs new file mode 100644 index 00000000..aec602bb --- /dev/null +++ b/src/AzureOpenAIProxy.Management/Usings.cs @@ -0,0 +1,4 @@ +global using AzureOpenAIProxy.Management.Database; +global using AzureOpenAIProxy.Management.Services; +global using Microsoft.AspNetCore.Components; +global using MudBlazor; From 00c5d889a43fe58ded480c7f42d2c79e8bd87b9a Mon Sep 17 00:00:00 2001 From: Aaron Powell Date: Mon, 3 Jun 2024 03:41:29 +0000 Subject: [PATCH 04/15] Whoops, wrong type --- src/AzureOpenAIProxy.Management/Program.cs | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/src/AzureOpenAIProxy.Management/Program.cs b/src/AzureOpenAIProxy.Management/Program.cs index c109ea02..9af0ab1b 100644 --- a/src/AzureOpenAIProxy.Management/Program.cs +++ b/src/AzureOpenAIProxy.Management/Program.cs @@ -1,6 +1,7 @@ using Azure.Core; using Azure.Identity; using AzureOpenAIProxy.Management; +using AzureOpenAIProxy.Management.Components; using Microsoft.EntityFrameworkCore; using Microsoft.Identity.Web; using MudBlazor.Services; @@ -73,7 +74,7 @@ app.MapControllers(); -app.MapRazorComponents() +app.MapRazorComponents() .AddInteractiveServerRenderMode(); app.Run(); From f8f252e77496f57a16c94f7602ac3141ae61691a Mon Sep 17 00:00:00 2001 From: Aaron Powell Date: Mon, 3 Jun 2024 03:43:26 +0000 Subject: [PATCH 05/15] Using icons more correctly --- .../Components/Pages/EventList.razor | 5 ++--- 1 file changed, 2 insertions(+), 3 deletions(-) diff --git a/src/AzureOpenAIProxy.Management/Components/Pages/EventList.razor b/src/AzureOpenAIProxy.Management/Components/Pages/EventList.razor index 7d00126c..cb37d27e 100644 --- a/src/AzureOpenAIProxy.Management/Components/Pages/EventList.razor +++ b/src/AzureOpenAIProxy.Management/Components/Pages/EventList.razor @@ -6,9 +6,8 @@ - - New Event - + New Event From 083d0030ef4f0db701337e366c21343ada644e93 Mon Sep 17 00:00:00 2001 From: Aaron Powell Date: Mon, 3 Jun 2024 03:45:46 +0000 Subject: [PATCH 06/15] using cleanup --- .../Components/Pages/EventList.razor.cs | 7 +------ 1 file changed, 1 insertion(+), 6 deletions(-) diff --git a/src/AzureOpenAIProxy.Management/Components/Pages/EventList.razor.cs b/src/AzureOpenAIProxy.Management/Components/Pages/EventList.razor.cs index d7b43bd7..ed07656c 100644 --- a/src/AzureOpenAIProxy.Management/Components/Pages/EventList.razor.cs +++ b/src/AzureOpenAIProxy.Management/Components/Pages/EventList.razor.cs @@ -1,9 +1,4 @@ -using AzureOpenAIProxy.Management.Database; -using AzureOpenAIProxy.Management.Services; -using Microsoft.AspNetCore.Components; -using MudBlazor; - -namespace AzureOpenAIProxy.Management.Components.Pages; +namespace AzureOpenAIProxy.Management.Components.Pages; public partial class EventList : ComponentBase { From 5360f9652259c0b7e013b62577ab152999cdb00a Mon Sep 17 00:00:00 2001 From: Aaron Powell Date: Mon, 3 Jun 2024 03:47:44 +0000 Subject: [PATCH 07/15] Reverted some formatting changes --- .../Components/Pages/EventMetrics.razor.cs | 64 ++++++++----------- 1 file changed, 26 insertions(+), 38 deletions(-) diff --git a/src/AzureOpenAIProxy.Management/Components/Pages/EventMetrics.razor.cs b/src/AzureOpenAIProxy.Management/Components/Pages/EventMetrics.razor.cs index 588da9a2..f05352a2 100644 --- a/src/AzureOpenAIProxy.Management/Components/Pages/EventMetrics.razor.cs +++ b/src/AzureOpenAIProxy.Management/Components/Pages/EventMetrics.razor.cs @@ -1,8 +1,3 @@ -using AzureOpenAIProxy.Management.Database; -using AzureOpenAIProxy.Management.Services; -using Microsoft.AspNetCore.Components; -using MudBlazor; - namespace AzureOpenAIProxy.Management.Components.Pages; public class ModelCounts @@ -30,7 +25,6 @@ public partial class EventMetrics private List RequestChartSeries { get; set; } = []; private string[] RequestChartLabels { get; set; } = []; - // private EventMetric? EventMetric { get; set; } private Event? Event { get; set; } private List? ActiveUsers { get; set; } private List ActiveUsersChartSeries { get; set; } = []; @@ -108,60 +102,54 @@ private async Task GetData() IsLoading = false; } - protected override async Task OnInitializedAsync() - { - await GetData(); - } + protected override Task OnInitializedAsync() => GetData(); - private async void RefreshData() - { - await GetData(); - } + private Task RefreshData() => GetData(); private (List ActiveUsersChartSeries, string[] ActiveUsersChartLabels) BuildActiveUsersChart(List? activeUsers) { - if (activeUsers != null) + if (activeUsers is null) { - List cd = FillMissingDays(activeUsers); + return ([], []); + } + List cd = FillMissingDays(activeUsers); - ActiveUsersChartSeries = - [ - new ChartSeries + ActiveUsersChartSeries = + [ + new ChartSeries { Name = "New Active Registrations", Data = activeUsers.Select(au => (double)au.Count).ToArray() } - ]; + ]; - ActiveUsersChartLabels = activeUsers.Select(au => au.DateStamp.ToString("dd MMM")).ToArray(); - ActiveUsersChartLabels = ScaleLabels(ActiveUsersChartLabels); + ActiveUsersChartLabels = activeUsers.Select(au => au.DateStamp.ToString("dd MMM")).ToArray(); + ActiveUsersChartLabels = ScaleLabels(ActiveUsersChartLabels); - return (ActiveUsersChartSeries, ActiveUsersChartLabels); - } - return ([], []); + return (ActiveUsersChartSeries, ActiveUsersChartLabels); } private (List ChartSeries, string[] ChartLabels) BuildRequestsChart(List? chartData) { - if (chartData != null) + if (chartData is null) { - List cd = FillMissingDays(chartData); + return ([], []); + } + List cd = FillMissingDays(chartData); - RequestChartSeries = - [ - new ChartSeries + RequestChartSeries = + [ + new ChartSeries { Name = "Requests", Data = cd.Select(cd => (double)cd.Count).ToArray() } - ]; + ]; - RequestChartLabels = cd.Select(cd => cd.DateStamp.ToString("dd MMM")).ToArray(); - RequestChartLabels = ScaleLabels(RequestChartLabels); + RequestChartLabels = cd.Select(cd => cd.DateStamp.ToString("dd MMM")).ToArray(); + RequestChartLabels = ScaleLabels(RequestChartLabels); - return (RequestChartSeries, RequestChartLabels); - } - return ([], []); + return (RequestChartSeries, RequestChartLabels); } private static string[] ScaleLabels(string[] ChartLabels) @@ -178,7 +166,7 @@ private static List FillMissingDays(List? chartD long previousRequests = 0; List cd = []; - if (chartData == null) + if (chartData is null) { return cd; } @@ -186,7 +174,7 @@ private static List FillMissingDays(List? chartD // rebuild chart data to fill in missing days foreach (var row in chartData.OrderBy(r => r.DateStamp)) { - if (previousDay != null && previousDay.Value.AddDays(1) < row.DateStamp) + if (previousDay is not null && previousDay.Value.AddDays(1) < row.DateStamp) { while (previousDay.Value.AddDays(1) < row.DateStamp) { From af2672f6c592b2ae62048d2afe2c0ad597199b4b Mon Sep 17 00:00:00 2001 From: Aaron Powell Date: Mon, 3 Jun 2024 03:49:58 +0000 Subject: [PATCH 08/15] Code cleanup --- .../Components/Pages/EventReport.razor | 6 ++---- .../Components/Pages/EventReport.razor.cs | 6 ++---- 2 files changed, 4 insertions(+), 8 deletions(-) diff --git a/src/AzureOpenAIProxy.Management/Components/Pages/EventReport.razor b/src/AzureOpenAIProxy.Management/Components/Pages/EventReport.razor index e6f8bd0e..4a2a91ff 100644 --- a/src/AzureOpenAIProxy.Management/Components/Pages/EventReport.razor +++ b/src/AzureOpenAIProxy.Management/Components/Pages/EventReport.razor @@ -1,6 +1,4 @@ @page "/reports" -@using AzureOpenAIProxy.Management.Database -@using AzureOpenAIProxy.Management.Services; @namespace AzureOpenAIProxy.Management.Components.Pages @@ -21,7 +19,7 @@ Select the event to view the metrics. - @@ -45,7 +43,7 @@ - Regd + Registrations Event diff --git a/src/AzureOpenAIProxy.Management/Components/Pages/EventReport.razor.cs b/src/AzureOpenAIProxy.Management/Components/Pages/EventReport.razor.cs index f6ddd31d..6aa5059e 100644 --- a/src/AzureOpenAIProxy.Management/Components/Pages/EventReport.razor.cs +++ b/src/AzureOpenAIProxy.Management/Components/Pages/EventReport.razor.cs @@ -17,7 +17,7 @@ public partial class EventReport private int TotalRegistations { get; set; } private int EventCount {get; set;} - private string searchString1 = ""; + private string searchString = ""; protected override async Task OnInitializedAsync() { @@ -28,9 +28,7 @@ protected override async Task OnInitializedAsync() EventCount = EventRegistrations.Count; } - private bool EventFilter(EventRegistrations element) => FilterByEventOrOrganizer(element, searchString1); - - private bool FilterByEventOrOrganizer(EventRegistrations element, string searchString) + private bool EventFilter(EventRegistrations element) { if (string.IsNullOrWhiteSpace(searchString)) return true; From db385ac41ff4f205088067a6cdac0d87e8afb019 Mon Sep 17 00:00:00 2001 From: Aaron Powell Date: Mon, 3 Jun 2024 03:50:36 +0000 Subject: [PATCH 09/15] Revert change --- .../Components/Pages/ModelEdit.razor.cs | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/src/AzureOpenAIProxy.Management/Components/Pages/ModelEdit.razor.cs b/src/AzureOpenAIProxy.Management/Components/Pages/ModelEdit.razor.cs index bf8b4fdf..4285e44b 100644 --- a/src/AzureOpenAIProxy.Management/Components/Pages/ModelEdit.razor.cs +++ b/src/AzureOpenAIProxy.Management/Components/Pages/ModelEdit.razor.cs @@ -16,7 +16,7 @@ public partial class ModelEdit : ComponentBase public IModelService ModelService { get; set; } = null!; [Inject] - public AoaiProxyContext db { get; set; } = null!; + public AoaiProxyContext DbContext { get; set; } = null!; [Inject] public NavigationManager NavigationManager { get; set; } = null!; @@ -53,7 +53,7 @@ protected override async Task OnInitializedAsync() private async Task OnValidSubmit(ModelEditorModel model) { - OwnerCatalog? m = await db.OwnerCatalogs.FindAsync(Guid.Parse(Id)); + OwnerCatalog? m = await DbContext.OwnerCatalogs.FindAsync(Guid.Parse(Id)); if (m is null) { From e0a25b1f78d7f1cc01136e1debda2d3cec30552e Mon Sep 17 00:00:00 2001 From: Aaron Powell Date: Mon, 3 Jun 2024 03:51:32 +0000 Subject: [PATCH 10/15] Adjusted to use StartIcon rather than component --- .../Components/Pages/ModelList.razor | 53 +++++++++---------- 1 file changed, 26 insertions(+), 27 deletions(-) diff --git a/src/AzureOpenAIProxy.Management/Components/Pages/ModelList.razor b/src/AzureOpenAIProxy.Management/Components/Pages/ModelList.razor index 056325d2..57201a38 100644 --- a/src/AzureOpenAIProxy.Management/Components/Pages/ModelList.razor +++ b/src/AzureOpenAIProxy.Management/Components/Pages/ModelList.razor @@ -7,35 +7,34 @@ - - - New Resource - + New Resource + - - - Friendly Name - Resource Name - Resource Type - Location - Refd - Active - - - - - @context.FriendlyName - - @context.DeploymentName - @(context.ModelType!.Value.ToString().Replace("_", " ")) - @(context.Location) - @(context.Events.Count) - @(context.Active ? "Yes" : "No") - - - + + Friendly Name + Resource Name + Resource Type + Location + Refd + Active + + +