diff --git a/app/MindWork AI Studio/Components/AssistantPluginSecurityCard.razor b/app/MindWork AI Studio/Components/AssistantPluginSecurityCard.razor new file mode 100644 index 00000000..15df384b --- /dev/null +++ b/app/MindWork AI Studio/Components/AssistantPluginSecurityCard.razor @@ -0,0 +1,191 @@ +@using AIStudio.Agents.AssistantAudit +@inherits MSGComponentBase + +@if (this.Plugin is not null) +{ + var state = this.SecurityState; + +
+ + + + + + + + + + + + + +
+ @T("Assistant Security") + + @state.AuditLabel + + @if (!string.IsNullOrWhiteSpace(state.AvailabilityLabel)) + { + + @state.AvailabilityLabel + + } +
+ + @state.Headline + +
+ + + + + +
+ + + + + + @T("Confidence"): + + + @this.GetConfidenceLabel() + + + + + + @this.GetFindingSummary() + + + + + + @this.GetAuditTimestampLabel() + + + + + + + + + + + @state.Description + + + + + + + @T("Plugin ID") + + @this.Plugin.Id + + + + @T("Current hash") + + @GetShortHash(state.CurrentHash) + + @if (state.Audit is not null) + { + + + @T("Audit hash") + + @GetShortHash(state.Audit.PluginHash) + + + + @T("Audit provider") + + @this.GetAuditProviderLabel() + + + + @T("Audited at") + + @this.FormatFileTimestamp(state.Audit.AuditedAtUtc.ToLocalTime().DateTime) + + + + @T("Audit level") + + @state.AuditLabel + + + + @T("Availability") + + @state.AvailabilityLabel + + } + + + @T("Required minimum") + + @state.Settings.MinimumLevel.GetName() + + + + + @if (state.Audit is null) + { + + @T("No stored audit details are available yet.") + + } + else if (state.Audit.Findings.Count == 0) + { + + @T("No security findings were stored for this assistant plugin.") + + } + else + { + + @foreach (var finding in state.Audit.Findings) + { + + @finding.Category: @finding.Description + @if (!string.IsNullOrWhiteSpace(finding.Location)) + { +
+ @finding.Location +
+ } +
+ } +
+ } +
+
+
+ + + + @state.ActionLabel + + + @T("Close") + + +
+
+
+} diff --git a/app/MindWork AI Studio/Components/AssistantPluginSecurityCard.razor.cs b/app/MindWork AI Studio/Components/AssistantPluginSecurityCard.razor.cs new file mode 100644 index 00000000..b6f34a82 --- /dev/null +++ b/app/MindWork AI Studio/Components/AssistantPluginSecurityCard.razor.cs @@ -0,0 +1,152 @@ +using System.Globalization; +using AIStudio.Dialogs; +using AIStudio.Tools.PluginSystem.Assistants; +using Microsoft.AspNetCore.Components; +using DialogOptions = AIStudio.Dialogs.DialogOptions; + +namespace AIStudio.Components; + +public partial class AssistantPluginSecurityCard : MSGComponentBase +{ + [Parameter] + public PluginAssistants? Plugin { get; set; } + + [Parameter] + public bool Compact { get; set; } + + [Inject] + private IDialogService DialogService { get; init; } = null!; + + private PluginAssistantSecurityState SecurityState => this.Plugin is null + ? new PluginAssistantSecurityState() + : PluginAssistantSecurityResolver.Resolve(this.SettingsManager, this.Plugin); + + private CultureInfo currentCultureInfo = CultureInfo.InvariantCulture; + private bool showSecurityCard; + private bool showDetails; + + protected override async Task OnInitializedAsync() + { + var activeLanguagePlugin = await this.SettingsManager.GetActiveLanguagePlugin(); + this.currentCultureInfo = CommonTools.DeriveActiveCultureOrInvariant(activeLanguagePlugin.IETFTag); + this.showDetails = !this.Compact; + + this.ApplyFilters([], [ Event.CONFIGURATION_CHANGED, Event.PLUGINS_RELOADED ]); + await base.OnInitializedAsync(); + } + + protected override Task OnAfterRenderAsync(bool firstRender) + { + if (!firstRender) + { + + } + return base.OnAfterRenderAsync(firstRender); + } + + private async Task OpenAuditDialogAsync() + { + if (this.Plugin is null) + return; + + var parameters = new DialogParameters + { + { x => x.PluginId, this.Plugin.Id }, + }; + var dialog = await this.DialogService.ShowAsync(this.T("Assistant Audit"), parameters, DialogOptions.FULLSCREEN); + var result = await dialog.Result; + if (result is null || result.Canceled || result.Data is not AssistantPluginAuditDialogResult auditResult) + return; + + if (auditResult.Audit is not null) + UpsertAudit(this.SettingsManager.ConfigurationData.AssistantPluginAudits, auditResult.Audit); + + if (auditResult.ActivatePlugin && !this.SettingsManager.ConfigurationData.EnabledPlugins.Contains(this.Plugin.Id)) + this.SettingsManager.ConfigurationData.EnabledPlugins.Add(this.Plugin.Id); + + await this.SettingsManager.StoreSettings(); + await this.SendMessage(Event.CONFIGURATION_CHANGED, true); + } + + protected override Task ProcessIncomingMessage(ComponentBase? sendingComponent, Event triggeredEvent, T? data) where T : default + { + if (triggeredEvent is Event.CONFIGURATION_CHANGED or Event.PLUGINS_RELOADED) + return this.InvokeAsync(this.StateHasChanged); + + return Task.CompletedTask; + } + + private void ToggleSecurityCard() => this.showSecurityCard = !this.showSecurityCard; + + private void HideSecurityCard() => this.showSecurityCard = false; + + private void ToggleDetails() => this.showDetails = !this.showDetails; + + private static void UpsertAudit(List audits, PluginAssistantAudit audit) + { + var existingIndex = audits.FindIndex(x => x.PluginId == audit.PluginId); + if (existingIndex >= 0) + audits[existingIndex] = audit; + else + audits.Add(audit); + } + + private string FormatFileTimestamp(DateTime timestamp) => CommonTools.FormatTimestampToGeneral(timestamp, this.currentCultureInfo); + + private string GetPopoverStyle() => $"border-color: {this.GetStatusBorderColor()};"; + + private double GetConfidencePercentage() + { + var confidence = this.SecurityState.Audit?.Confidence ?? 0f; + if (confidence <= 1) + confidence *= 100; + + return Math.Clamp(confidence, 0, 100); + } + + private string GetConfidenceLabel() => $"{this.GetConfidencePercentage():0}%"; + + private string GetFindingSummary() + { + var count = this.SecurityState.Audit?.Findings.Count ?? 0; + return string.Format(this.T("{0} Finding(s)"), count); + } + + private string GetAuditTimestampLabel() + { + var auditedAt = this.SecurityState.Audit?.AuditedAtUtc; + return auditedAt is null + ? this.T("No audit yet") + : this.FormatFileTimestamp(auditedAt.Value.ToLocalTime().DateTime); + } + + private string GetAuditProviderLabel() + { + var providerName = this.SecurityState.Audit?.AuditProviderName; + return string.IsNullOrWhiteSpace(providerName) ? this.T("Unknown") : providerName; + } + + private static string GetShortHash(string hash) + { + if (string.IsNullOrWhiteSpace(hash) || hash.Length <= 16) + return hash; + + return $"{hash[..8]}...{hash[^8..]}"; + } + + private Severity GetStatusSeverity() => this.SecurityState.AuditColor switch + { + Color.Success => Severity.Success, + Color.Warning => Severity.Warning, + Color.Error => Severity.Error, + _ => Severity.Info, + }; + + private string GetStatusBorderColor() => this.SecurityState.AuditColor switch + { + Color.Success => "var(--mud-palette-success)", + Color.Warning => "var(--mud-palette-warning)", + Color.Error => "var(--mud-palette-error)", + _ => "var(--mud-palette-info)", + }; +}