mirror of
https://github.com/MindWorkAI/AI-Studio.git
synced 2026-05-13 12:14:22 +00:00
security card component that shows all information of the current saved audit like activation state, policy and meta data
This commit is contained in:
parent
75407a8e10
commit
5f94428204
@ -0,0 +1,191 @@
|
||||
@using AIStudio.Agents.AssistantAudit
|
||||
@inherits MSGComponentBase
|
||||
|
||||
@if (this.Plugin is not null)
|
||||
{
|
||||
var state = this.SecurityState;
|
||||
|
||||
<div class="d-flex">
|
||||
<MudTooltip Text="@state.ActionLabel" Placement="Placement.Top">
|
||||
<MudIconButton Icon="@state.BadgeIcon"
|
||||
Color="@state.AuditColor"
|
||||
Size="@(this.Compact ? Size.Small : Size.Medium)"
|
||||
OnClick="@this.ToggleSecurityCard" />
|
||||
</MudTooltip>
|
||||
|
||||
<MudPopover Open="@this.showSecurityCard"
|
||||
AnchorOrigin="Origin.BottomRight"
|
||||
TransformOrigin="Origin.BottomLeft"
|
||||
OverflowBehavior="OverflowBehavior.FlipAlways"
|
||||
DropShadow="@true"
|
||||
Class="border-solid border-4 rounded-lg"
|
||||
Style="@this.GetPopoverStyle()">
|
||||
<MudCard Elevation="2" Outlined Style="max-width: min(42rem, 90vw);">
|
||||
<MudCardHeader>
|
||||
<CardHeaderAvatar>
|
||||
<MudAvatar Color="@state.AuditColor" Variant="Variant.Filled" Size="Size.Large">
|
||||
<MudIcon Icon="@state.AuditIcon" Size="Size.Medium" />
|
||||
</MudAvatar>
|
||||
</CardHeaderAvatar>
|
||||
<CardHeaderContent>
|
||||
<div class="d-flex align-center gap-2">
|
||||
<MudText Typo="Typo.h6">@T("Assistant Security")</MudText>
|
||||
<MudChip T="string" Size="Size.Small" Variant="Variant.Filled" Color="@state.AuditColor">
|
||||
@state.AuditLabel
|
||||
</MudChip>
|
||||
@if (!string.IsNullOrWhiteSpace(state.AvailabilityLabel))
|
||||
{
|
||||
<MudChip T="string" Size="Size.Small" Variant="Variant.Outlined" Color="@state.AvailabilityColor" Icon="@state.AvailabilityIcon">
|
||||
@state.AvailabilityLabel
|
||||
</MudChip>
|
||||
}
|
||||
</div>
|
||||
<MudText Typo="Typo.body2" Class="mud-text-secondary">
|
||||
@state.Headline
|
||||
</MudText>
|
||||
</CardHeaderContent>
|
||||
<CardHeaderActions>
|
||||
<MudTooltip Text="@T("Show or hide the detailed security information.")">
|
||||
<MudIconButton Icon="@Icons.Material.Filled.ExpandMore" OnClick="@this.ToggleDetails" />
|
||||
</MudTooltip>
|
||||
</CardHeaderActions>
|
||||
</MudCardHeader>
|
||||
|
||||
<MudCardContent Class="pt-0 pb-2">
|
||||
<MudStack Row="true" AlignItems="AlignItems.Center" Spacing="4" Class="flex-wrap">
|
||||
<MudStack Row="true" AlignItems="AlignItems.Center" Spacing="1">
|
||||
<MudIcon Icon="@Icons.Material.Filled.Speed" Size="Size.Small" />
|
||||
<MudText Typo="Typo.body2">@T("Confidence"):</MudText>
|
||||
<MudProgressLinear Color="@state.AuditColor"
|
||||
Value="@this.GetConfidencePercentage()"
|
||||
Rounded="@true"
|
||||
Size="Size.Medium"
|
||||
Style="width: 80px; min-width: 80px;" />
|
||||
<MudText Typo="Typo.caption" Class="mud-text-secondary">
|
||||
@this.GetConfidenceLabel()
|
||||
</MudText>
|
||||
</MudStack>
|
||||
<MudDivider Vertical="@true" FlexItem="@true" />
|
||||
<MudStack Row="true" AlignItems="AlignItems.Center" Spacing="1">
|
||||
<MudIcon Icon="@Icons.Material.Filled.BugReport" Size="Size.Small" Color="@state.AuditColor" />
|
||||
<MudText Typo="Typo.body2">@this.GetFindingSummary()</MudText>
|
||||
</MudStack>
|
||||
<MudDivider Vertical="@true" FlexItem="@true" />
|
||||
<MudStack Row="true" AlignItems="AlignItems.Center" Spacing="1">
|
||||
<MudIcon Icon="@Icons.Material.Filled.Schedule" Size="Size.Small" />
|
||||
<MudText Typo="Typo.body2" Class="mud-text-secondary">
|
||||
@this.GetAuditTimestampLabel()
|
||||
</MudText>
|
||||
</MudStack>
|
||||
</MudStack>
|
||||
</MudCardContent>
|
||||
|
||||
<MudCollapse Expanded="@this.showDetails">
|
||||
<MudDivider />
|
||||
<MudCardContent>
|
||||
<MudStack Spacing="3">
|
||||
<MudAlert Severity="@this.GetStatusSeverity()" Variant="Variant.Outlined" Dense="@true">
|
||||
@state.Description
|
||||
</MudAlert>
|
||||
|
||||
<MudSimpleTable Dense="@true" Hover="@true" Bordered="@true" Striped="@true" Style="overflow-x: auto;">
|
||||
<tbody>
|
||||
<tr>
|
||||
<td style="width: 180px;">
|
||||
<MudText Typo="Typo.body2"><b>@T("Plugin ID")</b></MudText>
|
||||
</td>
|
||||
<td><code style="font-size: 0.8rem;">@this.Plugin.Id</code></td>
|
||||
</tr>
|
||||
<tr>
|
||||
<td>
|
||||
<MudText Typo="Typo.body2"><b>@T("Current hash")</b></MudText>
|
||||
</td>
|
||||
<td><code style="font-size: 0.8rem;">@GetShortHash(state.CurrentHash)</code></td>
|
||||
</tr>
|
||||
@if (state.Audit is not null)
|
||||
{
|
||||
<tr>
|
||||
<td>
|
||||
<MudText Typo="Typo.body2"><b>@T("Audit hash")</b></MudText>
|
||||
</td>
|
||||
<td><code style="font-size: 0.8rem;">@GetShortHash(state.Audit.PluginHash)</code></td>
|
||||
</tr>
|
||||
<tr>
|
||||
<td>
|
||||
<MudText Typo="Typo.body2"><b>@T("Audit provider")</b></MudText>
|
||||
</td>
|
||||
<td><MudText Typo="Typo.body2">@this.GetAuditProviderLabel()</MudText></td>
|
||||
</tr>
|
||||
<tr>
|
||||
<td>
|
||||
<MudText Typo="Typo.body2"><b>@T("Audited at")</b></MudText>
|
||||
</td>
|
||||
<td><MudText Typo="Typo.body2">@this.FormatFileTimestamp(state.Audit.AuditedAtUtc.ToLocalTime().DateTime)</MudText></td>
|
||||
</tr>
|
||||
<tr>
|
||||
<td>
|
||||
<MudText Typo="Typo.body2"><b>@T("Audit level")</b></MudText>
|
||||
</td>
|
||||
<td><MudText Typo="Typo.body2">@state.AuditLabel</MudText></td>
|
||||
</tr>
|
||||
<tr>
|
||||
<td>
|
||||
<MudText Typo="Typo.body2"><b>@T("Availability")</b></MudText>
|
||||
</td>
|
||||
<td><MudText Typo="Typo.body2">@state.AvailabilityLabel</MudText></td>
|
||||
</tr>
|
||||
}
|
||||
<tr>
|
||||
<td>
|
||||
<MudText Typo="Typo.body2"><b>@T("Required minimum")</b></MudText>
|
||||
</td>
|
||||
<td><MudText Typo="Typo.body2">@state.Settings.MinimumLevel.GetName()</MudText></td>
|
||||
</tr>
|
||||
</tbody>
|
||||
</MudSimpleTable>
|
||||
|
||||
@if (state.Audit is null)
|
||||
{
|
||||
<MudAlert Severity="Severity.Info" Variant="Variant.Text" Dense="@true">
|
||||
@T("No stored audit details are available yet.")
|
||||
</MudAlert>
|
||||
}
|
||||
else if (state.Audit.Findings.Count == 0)
|
||||
{
|
||||
<MudAlert Severity="Severity.Success" Variant="Variant.Text" Dense="@true">
|
||||
@T("No security findings were stored for this assistant plugin.")
|
||||
</MudAlert>
|
||||
}
|
||||
else
|
||||
{
|
||||
<MudStack Spacing="2">
|
||||
@foreach (var finding in state.Audit.Findings)
|
||||
{
|
||||
<MudAlert Severity="@finding.Severity.GetSeverity()" Variant="Variant.Text" Dense="@true">
|
||||
<strong>@finding.Category</strong><span>: @finding.Description</span>
|
||||
@if (!string.IsNullOrWhiteSpace(finding.Location))
|
||||
{
|
||||
<div>
|
||||
<MudText Typo="Typo.caption">@finding.Location</MudText>
|
||||
</div>
|
||||
}
|
||||
</MudAlert>
|
||||
}
|
||||
</MudStack>
|
||||
}
|
||||
</MudStack>
|
||||
</MudCardContent>
|
||||
</MudCollapse>
|
||||
|
||||
<MudCardActions>
|
||||
<MudButton Variant="Variant.Outlined" Color="Color.Primary" OnClick="@this.OpenAuditDialogAsync">
|
||||
@state.ActionLabel
|
||||
</MudButton>
|
||||
<MudButton Variant="Variant.Text" OnClick="@this.HideSecurityCard">
|
||||
@T("Close")
|
||||
</MudButton>
|
||||
</MudCardActions>
|
||||
</MudCard>
|
||||
</MudPopover>
|
||||
</div>
|
||||
}
|
||||
@ -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<AssistantPluginAuditDialog>
|
||||
{
|
||||
{ x => x.PluginId, this.Plugin.Id },
|
||||
};
|
||||
var dialog = await this.DialogService.ShowAsync<AssistantPluginAuditDialog>(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<T>(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<PluginAssistantAudit> 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)",
|
||||
};
|
||||
}
|
||||
Loading…
Reference in New Issue
Block a user