Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
72 changes: 37 additions & 35 deletions .serena/project.yml
Original file line number Diff line number Diff line change
@@ -1,41 +1,6 @@
# the name by which the project can be referenced within Serena/when chatting with the LLM.
project_name: "DockerUpdateGuard"

# list of languages for which language servers are started (LSP backend only); choose from:
# ada al angular ansible bash
# bsl clojure cpp cpp_ccls crystal
# csharp csharp_omnisharp cue dart elixir
# elm erlang fortran fsharp gdscript
# go groovy haskell haxe hlsl
# html java json julia kotlin
# latex lean4 lua luau markdown
# matlab msl nix ocaml pascal
# perl php php_phpactor php_phpantom powershell
# python python_jedi python_pyrefly python_ty r
# rego ruby ruby_solargraph rust scala
# scss solidity svelte swift systemverilog
# terraform toml typescript typescript_vts vue
# yaml zig
# (This list may be outdated; generated with scripts/print_language_list.py;
# For the current list, see values of Language enum here:
# https://github.com/oraios/serena/blob/main/src/solidlsp/ls_config.py)
# For some languages, there are alternative language servers, e.g. csharp_omnisharp, ruby_solargraph.)
# Note:
# - For C, use cpp
# - For JavaScript, use typescript
# - For Angular projects, use angular (subsumes typescript+html; requires `npm install` in the project root)
# - For Svelte projects, use svelte (subsumes typescript/javascript for .svelte projects; requires npm)
# - For SCSS / Sass / plain CSS, use scss (some-sass-language-server handles all three)
# - For Free Pascal/Lazarus, use pascal
# Special requirements:
# Some languages require additional setup/installations.
# See here for details: https://oraios.github.io/serena/01-about/020_programming-languages.html#language-servers
# When using multiple languages, the first language server that supports a given file will be used for that file.
# The first language is the default language and the respective language server will be used as a fallback.
# Note that when using the JetBrains backend, language servers are not used and this list is correspondingly ignored.
languages:
- csharp

# the encoding used by text files in the project
# For a list of possible encodings, see https://docs.python.org/3.11/library/codecs.html#standard-encodings
encoding: "utf-8"
Expand Down Expand Up @@ -173,3 +138,40 @@ activation_command:
# maximum time in seconds to wait for activation_command to complete before killing it (default 180s).
# must be a positive number.
activation_command_timeout: 180.0

# list of language servers to start when using the LSP backend; choose from:
# ada al angular ansible bash
# bsl clojure cpp cpp_ccls crystal
# csharp csharp_omnisharp cue dart deno
# elixir elm erlang fortran fsharp
# gdscript gleam go groovy haskell
# haxe hlsl html java json
# julia kotlin latex lean4 lua
# luau markdown matlab msl nextflow
# nix ocaml pascal perl php
# php_phpactor php_phpantom powershell python python_basedpyright
# python_jedi python_pyrefly python_ty qml r
# rego ruby ruby_solargraph rust scala
# scss solidity svelte swift systemverilog
# terraform toml typescript typescript_vts vue
# wolfram yaml zig
# (This list may be outdated; generated with scripts/print_language_list.py;
# For the current list, see values of the LanguageServerId enum here:
# https://github.com/oraios/serena/blob/main/src/solidlsp/ls_config.py)
# For some languages, there are several alternative language servers, e.g. csharp_omnisharp, ruby_solargraph.)
# Note:
# - For C, use cpp
# - For JavaScript, use typescript
# - For Angular projects, use angular (subsumes typescript+html; requires `npm install` in the project root)
# - For Svelte projects, use svelte (subsumes typescript/javascript for .svelte projects; requires npm)
# - For Deno projects, use deno (serves the same .ts/.js files as typescript; requires the deno CLI on PATH)
# - For SCSS / Sass / plain CSS, use scss (some-sass-language-server handles all three)
# - For Free Pascal/Lazarus, use pascal
# Special requirements:
# Some language servers require additional setup/installations.
# See here for details: https://oraios.github.io/serena/01-about/020_programming-languages.html#language-servers
# When using multiple language servers, the first language server that supports a given file will be used for that file.
# The first language server is the default language and the respective language server will be used as a fallback.
# Note that when using the JetBrains backend, language servers are not used and this list is correspondingly ignored.
language_servers:
- csharp
2 changes: 1 addition & 1 deletion LICENSE.md
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
MIT License

Copyright (c) 2026 e-networld
Copyright (c) 2026 e-networld.com

Permission is hereby granted, free of charge, to any person obtaining a copy
of this software and associated documentation files (the "Software"), to deal
Expand Down
2 changes: 1 addition & 1 deletion src/DockerUpdateGuard/Components/Pages/Dashboard.razor
Original file line number Diff line number Diff line change
Expand Up @@ -150,7 +150,7 @@ else
<span class="summary-value">@_dashboard.ActiveUpdateFindingCount</span>
</MudPaper>
<MudPaper Class="summary-card" Elevation="0">
<span class="summary-label">Active vulnerability findings</span>
<span class="summary-label">Active vulnerability findings (running containers)</span>
<span class="summary-value">@_dashboard.ActiveVulnerabilityFindingCount</span>
<VulnerabilitySeverityChips Summary="_dashboard.VulnerabilitySeveritySummary" />
</MudPaper>
Expand Down
1 change: 1 addition & 0 deletions src/DockerUpdateGuard/Components/Pages/MyImages.razor
Original file line number Diff line number Diff line change
Expand Up @@ -75,6 +75,7 @@
<MudChip T="string" Size="Size.Small" Color="Color.Warning">Base images affected</MudChip>
<span class="table-secondary">@context.BaseImageVulnerabilitySummary</span>
}
<VulnerabilityRescanControl ImageVersionId="context.CurrentImageVersionId" CheckedAtUtc="context.VulnerabilityCheckedAtUtc" OnRescanned="HandleRescannedAsync" />
</MudTd>
</RowTemplate>
</MudTable>
Expand Down
40 changes: 33 additions & 7 deletions src/DockerUpdateGuard/Components/Pages/MyImages.razor.cs
Original file line number Diff line number Diff line change
Expand Up @@ -55,6 +55,12 @@ public sealed partial class MyImages : IDisposable
[Inject]
public IOptions<DockerUpdateGuardOptions> AppOptions { get; set; } = null!;

/// <summary>
/// Dashboard refresh state
/// </summary>
[Inject]
public DashboardRefreshState DashboardRefreshState { get; set; } = null!;

/// <summary>
/// Persistent component state used to reuse the prerendered data on the interactive render
/// </summary>
Expand Down Expand Up @@ -136,6 +142,32 @@ private Task PersistImages()
return Task.CompletedTask;
}

/// <summary>
/// Load the discovery-owned image list
/// </summary>
/// <returns>Task</returns>
private async Task LoadAsync()
{
var images = await ViewService.GetDiscoveryObservedImagesAsync()
.ConfigureAwait(false);

await InvokeAsync(() =>
{
_images = images;
}).ConfigureAwait(false);
}

/// <summary>
/// Refresh the image list and dashboard-derived widgets after a manual vulnerability rescan
/// </summary>
/// <returns>Task</returns>
private async Task HandleRescannedAsync()
{
DashboardRefreshState.NotifyChanged();

await LoadAsync().ConfigureAwait(false);
}

#endregion // Methods

#region ComponentBase
Expand All @@ -154,13 +186,7 @@ protected override async Task OnInitializedAsync()
return;
}

var images = await ViewService.GetDiscoveryObservedImagesAsync()
.ConfigureAwait(false);

await InvokeAsync(() =>
{
_images = images;
}).ConfigureAwait(false);
await LoadAsync().ConfigureAwait(false);
}

#endregion // ComponentBase
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -126,6 +126,7 @@
<MudChip T="string" Size="Size.Small" Color="Color.Warning">Base images affected</MudChip>
<span class="table-secondary">@context.BaseImageVulnerabilitySummary</span>
}
<VulnerabilityRescanControl ImageVersionId="context.CurrentImageVersionId" CheckedAtUtc="context.VulnerabilityCheckedAtUtc" OnRescanned="HandleRescannedAsync" />
</MudTd>
</RowTemplate>
</MudTable>
Expand Down
11 changes: 11 additions & 0 deletions src/DockerUpdateGuard/Components/Pages/ObservedImages.razor.cs
Original file line number Diff line number Diff line change
Expand Up @@ -207,6 +207,17 @@ await InvokeAsync(() =>
}
}

/// <summary>
/// Refresh the image list and dashboard-derived widgets after a manual vulnerability rescan
/// </summary>
/// <returns>Task</returns>
private async Task HandleRescannedAsync()
{
DashboardRefreshState.NotifyChanged();

await LoadAsync().ConfigureAwait(false);
}

#endregion // Methods

#region ComponentBase
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -129,6 +129,7 @@
<VulnerabilitySeverityChips Summary="context.VulnerabilitySeveritySummary" Compact="true" />
<span class="table-secondary">@($"{context.ActiveVulnerabilityFindingCount} active Β· {context.VulnerabilitySummary}")</span>
}
<VulnerabilityRescanControl ImageVersionId="context.ImageVersionId" CheckedAtUtc="context.VulnerabilityCheckedAtUtc" OnRescanned="HandleRescannedAsync" />
@if (context.ActiveBaseImageVulnerabilityFindingCount > 0)
{
<MudChip T="string" Size="Size.Small" Color="Color.Warning">Base images affected</MudChip>
Expand Down
11 changes: 11 additions & 0 deletions src/DockerUpdateGuard/Components/Pages/RuntimeContainers.razor.cs
Original file line number Diff line number Diff line change
Expand Up @@ -398,6 +398,17 @@ await InvokeAsync(() =>
}
}

/// <summary>
/// Refresh the container list and dashboard-derived widgets after a manual vulnerability rescan
/// </summary>
/// <returns>Task</returns>
private async Task HandleRescannedAsync()
{
DashboardRefreshState.NotifyChanged();

await LoadAsync().ConfigureAwait(false);
}

#endregion // Methods

#region ComponentBase
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,13 @@
<div class="vulnerability-rescan-control">
<span class="table-secondary">@(CheckedAtUtc is DateTimeOffset checkedAtUtc ? $"Checked {checkedAtUtc.ToLocalTime():g}" : "Never scanned")</span>
<MudTooltip Text="Rescan for vulnerabilities now">
<MudIconButton Icon="@Icons.Material.Filled.Refresh"
Size="Size.Small"
Disabled="@_isBusy"
OnClick="RescanAsync" />
</MudTooltip>
</div>
@if (string.IsNullOrWhiteSpace(_errorMessage) == false)
{
<span class="table-secondary">@_errorMessage</span>
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,104 @@
using DockerUpdateGuard.Data.Entities;
using DockerUpdateGuard.Images.Interfaces;

using Microsoft.AspNetCore.Components;
using Microsoft.Extensions.DependencyInjection;

namespace DockerUpdateGuard.Components.Shared;

/// <summary>
/// Shows the last vulnerability scan timestamp of an image version and lets the user trigger a manual rescan
/// </summary>
public partial class VulnerabilityRescanControl
{
#region Fields

/// <summary>
/// Busy-state flag for the manual rescan
/// </summary>
private bool _isBusy;

/// <summary>
/// Current error message
/// </summary>
private string? _errorMessage;

#endregion // Fields

#region Properties

/// <summary>
/// Image version to rescan
/// </summary>
[Parameter]
public Guid ImageVersionId { get; set; }

/// <summary>
/// Timestamp of the last vulnerability scan of the image version
/// </summary>
[Parameter]
public DateTimeOffset? CheckedAtUtc { get; set; }

/// <summary>
/// Raised after a manual rescan completed
/// </summary>
[Parameter]
public EventCallback OnRescanned { get; set; }

/// <summary>
/// Service-scope factory
/// </summary>
[Inject]
public IServiceScopeFactory ServiceScopeFactory { get; set; } = null!;

#endregion // Properties

#region Methods

/// <summary>
/// Trigger a manual vulnerability rescan of the image version
/// </summary>
/// <returns>Task</returns>
private async Task RescanAsync()
{
await InvokeAsync(() =>
{
_isBusy = true;
_errorMessage = null;
}).ConfigureAwait(false);

try
{
var scope = ServiceScopeFactory.CreateAsyncScope();

await using (scope.ConfigureAwait(false))
{
var enrichmentService = scope.ServiceProvider.GetRequiredService<IVulnerabilityEnrichmentService>();

await enrichmentService.RefreshImageVersionAsync(ImageVersionId, ScanTriggerSource.Manual)
.ConfigureAwait(false);
}

await OnRescanned.InvokeAsync()
.ConfigureAwait(false);
}
catch (Exception exception)
{
await InvokeAsync(() =>
{
_errorMessage = exception.Message;
}).ConfigureAwait(false);
}
finally
{
await InvokeAsync(() =>
{
_isBusy = false;

StateHasChanged();
}).ConfigureAwait(false);
}
}

#endregion // Methods
}
10 changes: 10 additions & 0 deletions src/DockerUpdateGuard/Images/ImageHostLoggingExtensions.cs
Original file line number Diff line number Diff line change
Expand Up @@ -583,6 +583,16 @@ public static partial void VulnerabilityRefreshImageIncomplete(this ILogger logg
ExternalOperationStatus operationStatus,
string? message);

/// <summary>
/// Log that a manual single-image vulnerability rescan targeted an image version that no longer exists
/// </summary>
/// <param name="logger">Logger</param>
/// <param name="imageVersionId">Image version identifier</param>
[LoggerMessage(EventId = 2083,
Level = LogLevel.Warning,
Message = "Vulnerability refresh could not find image version {ImageVersionId}")]
public static partial void VulnerabilityRefreshImageVersionNotFound(this ILogger logger, Guid imageVersionId);

/// <summary>
/// Log that Docker instance synchronization has started
/// </summary>
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -17,5 +17,14 @@ public interface IVulnerabilityEnrichmentService
/// <returns>Task</returns>
Task RefreshAsync(ScanTriggerSource triggerSource, CancellationToken cancellationToken = default);

/// <summary>
/// Refresh vulnerability findings for a single image version, without deactivating findings of other image versions
/// </summary>
/// <param name="imageVersionId">Image version identifier</param>
/// <param name="triggerSource">Trigger source</param>
/// <param name="cancellationToken">Cancellation token</param>
/// <returns>Task</returns>
Task RefreshImageVersionAsync(Guid imageVersionId, ScanTriggerSource triggerSource, CancellationToken cancellationToken = default);

#endregion // Methods
}
Loading
Loading