diff --git a/src/SQLInsight.Hub/Portal/Pages/Overview.razor b/src/SQLInsight.Hub/Portal/Pages/Overview.razor
index 7dd6c13..2e9b1be 100644
--- a/src/SQLInsight.Hub/Portal/Pages/Overview.razor
+++ b/src/SQLInsight.Hub/Portal/Pages/Overview.razor
@@ -28,11 +28,11 @@
@(c.Plan ?? "–") |
@(c.LicenseExpires?.ToString("yyyy-MM-dd", CultureInfo.InvariantCulture) ?? "–") |
@c.Instances |
-
+ |
@if (c.Critical > 0) { }
@if (c.High > 0) { }
@if (c.Critical == 0 && c.High == 0) { ingen kritiske/høje }
- |
+
@(c.LastSubmission?.ToString("yyyy-MM-dd HH:mm", CultureInfo.InvariantCulture) ?? "aldrig") UTC |
}
diff --git a/src/SQLInsight.Web/Components/Pages/Home.razor b/src/SQLInsight.Web/Components/Pages/Home.razor
index 3a1d18a..5839f4a 100644
--- a/src/SQLInsight.Web/Components/Pages/Home.razor
+++ b/src/SQLInsight.Web/Components/Pages/Home.razor
@@ -45,12 +45,12 @@ else
@i.Name @i.ServerName |
@(i.ProductVersion is null ? "–" : Display.Release(i.ProductVersion, i.EngineEdition)) @i.ProductVersion @i.ProductLevel @i.ProductUpdateLevel |
-
+ |
@if (i.Critical > 0) { }
@if (i.High > 0) { }
@if (i.Medium > 0) { }
@if (i.Low > 0) { }
- |
+
@i.NotMeasurable |
@Display.Ago(i.LastSeenAt) |
diff --git a/tests/SQLInsight.Community.Tests/TableMarkupTests.cs b/tests/SQLInsight.Community.Tests/TableMarkupTests.cs
new file mode 100644
index 0000000..e9713b5
--- /dev/null
+++ b/tests/SQLInsight.Community.Tests/TableMarkupTests.cs
@@ -0,0 +1,57 @@
+using System.Text.RegularExpressions;
+
+namespace SQLInsight.Community.Tests;
+
+///
+/// A table cell must stay a table cell. A class that sets display: flex or grid turns a <td>/<th> into a flex box,
+/// so it gets its own height and border and the row's divider jumps (the severity badges on the overviews did that).
+/// Such classes belong on an element inside the cell. Checked for every site's stylesheet against its own pages.
+///
+public sealed partial class TableMarkupTests
+{
+ [Theory]
+ [InlineData("SQLInsight.Web")]
+ [InlineData("SQLInsight.Hub")]
+ [InlineData("SQLInsight.Community")]
+ public void No_table_cell_has_a_flex_or_grid_class(string project)
+ {
+ var folder = Path.Combine(RepositoryRoot(), "src", project);
+ var layoutClasses = Directory.EnumerateFiles(Path.Combine(folder, "wwwroot"), "*.css", SearchOption.AllDirectories)
+ .SelectMany(css => FlexOrGridRule().Matches(File.ReadAllText(css)))
+ .SelectMany(rule => ClassName().Matches(rule.Groups["selectors"].Value).Select(c => c.Groups[1].Value))
+ .ToHashSet(StringComparer.Ordinal);
+
+ var offenders = Directory.EnumerateFiles(folder, "*.razor", SearchOption.AllDirectories)
+ .Where(file => !file.Contains($"{Path.DirectorySeparatorChar}bin{Path.DirectorySeparatorChar}", StringComparison.Ordinal)
+ && !file.Contains($"{Path.DirectorySeparatorChar}obj{Path.DirectorySeparatorChar}", StringComparison.Ordinal))
+ .SelectMany(file => CellClass().Matches(File.ReadAllText(file))
+ .SelectMany(cell => cell.Groups["classes"].Value.Split(' ', StringSplitOptions.RemoveEmptyEntries))
+ .Where(layoutClasses.Contains)
+ .Select(c => $"{Path.GetFileName(file)}: "))
+ .ToList();
+
+ Assert.True(offenders.Count == 0, "Flyt klassen ind på et element i cellen: " + string.Join(", ", offenders));
+ }
+
+ private static string RepositoryRoot()
+ {
+ var root = AppContext.BaseDirectory;
+ while (!File.Exists(Path.Combine(root, "SQLInsight.slnx")))
+ {
+ root = Path.GetDirectoryName(root) ?? throw new InvalidOperationException("SQLInsight.slnx ikke fundet.");
+ }
+
+ return root;
+ }
+
+ // A rule whose declarations set display to (inline-)flex or (inline-)grid; media queries are fine, their inner rules match.
+ [GeneratedRegex(@"(?[^{}]+)\{[^{}]*\bdisplay\s*:\s*(inline-)?(flex|grid)\b")]
+ private static partial Regex FlexOrGridRule();
+
+ [GeneratedRegex(@"\.([A-Za-z][\w-]*)")]
+ private static partial Regex ClassName();
+
+ // Literal class names only; Razor expressions (@…) in the attribute are skipped by splitting on spaces.
+ [GeneratedRegex(@"]*\bclass=""(?[^""]*)""")]
+ private static partial Regex CellClass();
+}
|