A dashboard frame is a summary that prints once and says how something is doing: the title line with the scope's facts and its state, a row of meters, a "Needs attention" list with the worst problems first, summary sections packed beside it, detail sections below, a legend and a footer. You describe the parts; the frame decides where they go for the width it gets, so every summary built on it reads the same way. New-ConsoleDashboardFrame takes the parts as records, DashboardFrame takes them as objects in C#, and Ui.DashboardFrame() chains them in C# fluent.
Try it
The estate and its numbers are fictional and nothing is queried. The three programs print the same cells.
Install-Module ConsoleMonster -Scope CurrentUser# Summarize a server estate in one dashboard frame: facts, meters, problems first, then the details.
Import-Module -Name ConsoleMonster
# All data is synthetic and inline; no services are contacted or changed.
# Data: fictional inputs kept in memory.
$sites = @(
[pscustomobject] @{ Site = 'London'; Servers = '22'; Healthy = '21' }
[pscustomobject] @{ Site = 'Amsterdam'; Servers = '16'; Healthy = '15' }
[pscustomobject] @{ Site = 'New York'; Servers = '10'; Healthy = '8' }
)
$backups = @(
[pscustomobject] @{ Job = 'Nightly SQL'; 'Last run' = '02:10'; Size = '84 GB' }
[pscustomobject] @{ Job = 'Weekly files'; 'Last run' = 'Sun 01:00'; Size = '312 GB' }
)
# Layout: the frame places each part for the terminal's width.
$frame = @{
Title = 'Server estate'
Fact = 'contoso.example', '48 servers', '3 sites'
State = @(
[pscustomobject] @{ State = 'Failing'; Count = 1; Word = 'failing' }
[pscustomobject] @{ State = 'Warning'; Count = 3; Word = 'warning' }
)
Meter = @(
[pscustomobject] @{ Label = 'Servers healthy'; Healthy = 44; Warning = 3; Failing = 1 }
[pscustomobject] @{ Label = 'Backups current'; Healthy = 46; Whole = 48 }
[pscustomobject] @{ Label = 'Oldest patch'; Value = '41 days'; Fact = 'FS-02'
State = 'Warning' }
[pscustomobject] @{ Label = 'Open alerts'; Value = 5 }
)
Attention = @(
[pscustomobject] @{ State = 'Failing'
Identity = 'SQL-03'
Reason = 'Disk D: 97% full'
Order = 1
Values = '97%' }
[pscustomobject] @{ State = 'Warning'
Identity = 'FS-02'
Reason = 'Patches 41 days old'
Order = 2
Values = '41 d' }
[pscustomobject] @{ State = 'Warning'
Identity = 'WEB-07'
Reason = 'Certificate expires in 9 days'
Order = 3
Values = '9 d' }
)
Noun = 'server'
OutOf = 48
Section = @(
[pscustomobject] @{ Title = 'Sites'; Facts = 'by location'
Content = { $sites | New-ConsoleTable } }
[pscustomobject] @{ Title = 'Backups'; Placement = 'Detail'
Content = { $backups | New-ConsoleTable } }
)
Legend = [ordered] @{ Healthy = 'healthy'; Warning = 'warning'; Failing = 'failing' }
FooterCount = '48 servers'
}
# Behaviour: print the frame once and return.
New-ConsoleDashboardFrame @frame | Show-ConsoleFull program
# Summarize a server estate in one dashboard frame: facts, meters, problems first, then the details.
Import-Module -Name ConsoleMonster
# All data is synthetic and inline; no services are contacted or changed.
# Data: fictional inputs kept in memory.
$sites = @(
[pscustomobject] @{ Site = 'London'; Servers = '22'; Healthy = '21' }
[pscustomobject] @{ Site = 'Amsterdam'; Servers = '16'; Healthy = '15' }
[pscustomobject] @{ Site = 'New York'; Servers = '10'; Healthy = '8' }
)
$backups = @(
[pscustomobject] @{ Job = 'Nightly SQL'; 'Last run' = '02:10'; Size = '84 GB' }
[pscustomobject] @{ Job = 'Weekly files'; 'Last run' = 'Sun 01:00'; Size = '312 GB' }
)
# Layout: the frame places each part for the terminal's width.
$frame = @{
Title = 'Server estate'
Fact = 'contoso.example', '48 servers', '3 sites'
State = @(
[pscustomobject] @{ State = 'Failing'; Count = 1; Word = 'failing' }
[pscustomobject] @{ State = 'Warning'; Count = 3; Word = 'warning' }
)
Meter = @(
[pscustomobject] @{ Label = 'Servers healthy'; Healthy = 44; Warning = 3; Failing = 1 }
[pscustomobject] @{ Label = 'Backups current'; Healthy = 46; Whole = 48 }
[pscustomobject] @{ Label = 'Oldest patch'; Value = '41 days'; Fact = 'FS-02'
State = 'Warning' }
[pscustomobject] @{ Label = 'Open alerts'; Value = 5 }
)
Attention = @(
[pscustomobject] @{ State = 'Failing'
Identity = 'SQL-03'
Reason = 'Disk D: 97% full'
Order = 1
Values = '97%' }
[pscustomobject] @{ State = 'Warning'
Identity = 'FS-02'
Reason = 'Patches 41 days old'
Order = 2
Values = '41 d' }
[pscustomobject] @{ State = 'Warning'
Identity = 'WEB-07'
Reason = 'Certificate expires in 9 days'
Order = 3
Values = '9 d' }
)
Noun = 'server'
OutOf = 48
Section = @(
[pscustomobject] @{ Title = 'Sites'; Facts = 'by location'
Content = { $sites | New-ConsoleTable } }
[pscustomobject] @{ Title = 'Backups'; Placement = 'Detail'
Content = { $backups | New-ConsoleTable } }
)
Legend = [ordered] @{ Healthy = 'healthy'; Warning = 'warning'; Failing = 'failing' }
FooterCount = '48 servers'
}
# Behaviour: print the frame once and return.
New-ConsoleDashboardFrame @frame | Show-ConsoleRun: ./server-estate.ps1
dotnet new console -n ServerEstate
cd ServerEstate
dotnet add package ConsoleMonster// Summarize a server estate in one dashboard frame: facts, meters, problems first, then the details.
// All data is synthetic and inline; no services are contacted or changed.
using ConsoleMonster;
using ConsoleMonster.Frames;
using ConsoleMonster.Widgets;
// All values are synthetic. This example never connects to a host.
// Data: fictional inputs kept in memory.
string[][] sites =
{
new[] { "London", "22", "21" },
new[] { "Amsterdam", "16", "15" },
new[] { "New York", "10", "8" }
};
string[][] backups =
{
new[] { "Nightly SQL", "02:10", "84 GB" },
new[] { "Weekly files", "Sun 01:00", "312 GB" }
};
// Layout: the frame places each part for the terminal's width.
DashboardFrame frame = new DashboardFrame("Server estate");
frame.Header.Facts.Add("contoso.example");
frame.Header.Facts.Add("48 servers");
frame.Header.Facts.Add("3 sites");
frame.Header.States.Add(FrameState.Of(HealthState.Failing, 1, "failing"));
frame.Header.States.Add(FrameState.Of(HealthState.Warning, 3, "warning"));
frame.Meters.Add(Meter.Composition("Servers healthy", 44, 3, 1));
frame.Meters.Add(Meter.Ratio("Backups current", 46, 48));
frame.Meters.Add(Meter.Fact("Oldest patch", "41 days", "FS-02", HealthState.Warning));
frame.Meters.Add(Meter.Count("Open alerts", 5));
frame.Attention.Noun = "server";
frame.Attention.OutOf = 48;
(HealthState State, string Identity, string Reason, string Value)[] problems =
{
(HealthState.Failing, "SQL-03", "Disk D: 97% full", "97%"),
(HealthState.Warning, "FS-02", "Patches 41 days old", "41 d"),
(HealthState.Warning, "WEB-07", "Certificate expires in 9 days", "9 d")
};
for (int i = 0; i < problems.Length; i++)
{
AttentionItem item = new AttentionItem(problems[i].State, problems[i].Identity,
problems[i].Reason, i + 1.0);
item.Values.Add(problems[i].Value);
frame.Attention.Items.Add(item);
}
Table siteTable = new Table().AddColumn("Site").AddColumn("Servers").AddColumn("Healthy");
foreach (string[] site in sites) { siteTable.AddValues(site); }
FrameSection siteSection = new FrameSection("Sites", context => siteTable);
siteSection.Facts.Add("by location");
frame.Sections.Add(siteSection);
Table backupTable = new Table().AddColumn("Job").AddColumn("Last run").AddColumn("Size");
foreach (string[] backup in backups) { backupTable.AddValues(backup); }
frame.Sections.Add(new FrameSection(
"Backups",
context => backupTable) { Placement = FramePlacement.Detail });
frame.Legend.Add(HealthState.Healthy, "healthy");
frame.Legend.Add(HealthState.Warning, "warning");
frame.Legend.Add(HealthState.Failing, "failing");
frame.Footer.Counts.Add("48 servers");
frame.Footer.Hint = FrameHint.None;
// Behaviour: print the frame once and return.
Terminal.Write(frame);Full program
// Summarize a server estate in one dashboard frame: facts, meters, problems first, then the details.
// All data is synthetic and inline; no services are contacted or changed.
using ConsoleMonster;
using ConsoleMonster.Frames;
using ConsoleMonster.Widgets;
// All values are synthetic. This example never connects to a host.
// Data: fictional inputs kept in memory.
string[][] sites =
{
new[] { "London", "22", "21" },
new[] { "Amsterdam", "16", "15" },
new[] { "New York", "10", "8" }
};
string[][] backups =
{
new[] { "Nightly SQL", "02:10", "84 GB" },
new[] { "Weekly files", "Sun 01:00", "312 GB" }
};
// Layout: the frame places each part for the terminal's width.
DashboardFrame frame = new DashboardFrame("Server estate");
frame.Header.Facts.Add("contoso.example");
frame.Header.Facts.Add("48 servers");
frame.Header.Facts.Add("3 sites");
frame.Header.States.Add(FrameState.Of(HealthState.Failing, 1, "failing"));
frame.Header.States.Add(FrameState.Of(HealthState.Warning, 3, "warning"));
frame.Meters.Add(Meter.Composition("Servers healthy", 44, 3, 1));
frame.Meters.Add(Meter.Ratio("Backups current", 46, 48));
frame.Meters.Add(Meter.Fact("Oldest patch", "41 days", "FS-02", HealthState.Warning));
frame.Meters.Add(Meter.Count("Open alerts", 5));
frame.Attention.Noun = "server";
frame.Attention.OutOf = 48;
(HealthState State, string Identity, string Reason, string Value)[] problems =
{
(HealthState.Failing, "SQL-03", "Disk D: 97% full", "97%"),
(HealthState.Warning, "FS-02", "Patches 41 days old", "41 d"),
(HealthState.Warning, "WEB-07", "Certificate expires in 9 days", "9 d")
};
for (int i = 0; i < problems.Length; i++)
{
AttentionItem item = new AttentionItem(problems[i].State, problems[i].Identity,
problems[i].Reason, i + 1.0);
item.Values.Add(problems[i].Value);
frame.Attention.Items.Add(item);
}
Table siteTable = new Table().AddColumn("Site").AddColumn("Servers").AddColumn("Healthy");
foreach (string[] site in sites) { siteTable.AddValues(site); }
FrameSection siteSection = new FrameSection("Sites", context => siteTable);
siteSection.Facts.Add("by location");
frame.Sections.Add(siteSection);
Table backupTable = new Table().AddColumn("Job").AddColumn("Last run").AddColumn("Size");
foreach (string[] backup in backups) { backupTable.AddValues(backup); }
frame.Sections.Add(new FrameSection(
"Backups",
context => backupTable) { Placement = FramePlacement.Detail });
frame.Legend.Add(HealthState.Healthy, "healthy");
frame.Legend.Add(HealthState.Warning, "warning");
frame.Legend.Add(HealthState.Failing, "failing");
frame.Footer.Counts.Add("48 servers");
frame.Footer.Hint = FrameHint.None;
// Behaviour: print the frame once and return.
Terminal.Write(frame);Run: dotnet run
dotnet new console -n ServerEstate
cd ServerEstate
dotnet add package ConsoleMonster// Summarize a server estate in one dashboard frame: facts, meters, problems first, then the details.
// All data is synthetic and inline; no services are contacted or changed.
using ConsoleMonster;
using ConsoleMonster.Frames;
using ConsoleMonster.Widgets;
// All values are synthetic. This example never connects to a host.
// Data: fictional inputs kept in memory.
string[][] sites =
{
new[] { "London", "22", "21" },
new[] { "Amsterdam", "16", "15" },
new[] { "New York", "10", "8" }
};
string[][] backups =
{
new[] { "Nightly SQL", "02:10", "84 GB" },
new[] { "Weekly files", "Sun 01:00", "312 GB" }
};
// Layout: the frame places each part for the terminal's width.
DashboardFrame frame = Ui.DashboardFrame("Server estate");
frame.Header.Facts.Add("contoso.example");
frame.Header.Facts.Add("48 servers");
frame.Header.Facts.Add("3 sites");
frame.Header.States.Add(FrameState.Of(HealthState.Failing, 1, "failing"));
frame.Header.States.Add(FrameState.Of(HealthState.Warning, 3, "warning"));
frame.Meters.Add(Meter.Composition("Servers healthy", 44, 3, 1));
frame.Meters.Add(Meter.Ratio("Backups current", 46, 48));
frame.Meters.Add(Meter.Fact("Oldest patch", "41 days", "FS-02", HealthState.Warning));
frame.Meters.Add(Meter.Count("Open alerts", 5));
frame.Attention.Noun("server");
frame.Attention.OutOf(48);
(HealthState State, string Identity, string Reason, string Value)[] problems =
{
(HealthState.Failing, "SQL-03", "Disk D: 97% full", "97%"),
(HealthState.Warning, "FS-02", "Patches 41 days old", "41 d"),
(HealthState.Warning, "WEB-07", "Certificate expires in 9 days", "9 d")
};
for (int i = 0; i < problems.Length; i++)
{
AttentionItem item = new AttentionItem(problems[i].State, problems[i].Identity,
problems[i].Reason, i + 1.0);
item.Values.Add(problems[i].Value);
frame.Attention.Items.Add(item);
}
Table siteTable = Ui.Table().AddColumn("Site").AddColumn("Servers").AddColumn("Healthy");
foreach (string[] site in sites) { siteTable.AddValues(site); }
FrameSection siteSection = new FrameSection("Sites", context => siteTable);
siteSection.Facts.Add("by location");
frame.Sections.Add(siteSection);
Table backupTable = Ui.Table().AddColumn("Job").AddColumn("Last run").AddColumn("Size");
foreach (string[] backup in backups) { backupTable.AddValues(backup); }
frame.Sections.Add(new FrameSection("Backups", context => backupTable)
.Placement(FramePlacement.Detail));
frame.Legend.Add(HealthState.Healthy, "healthy");
frame.Legend.Add(HealthState.Warning, "warning");
frame.Legend.Add(HealthState.Failing, "failing");
frame.Footer.Counts.Add("48 servers");
frame.Footer.Hint(FrameHint.None);
// Behaviour: print the frame once and return.
frame.Show();
Full program
// Summarize a server estate in one dashboard frame: facts, meters, problems first, then the details.
// All data is synthetic and inline; no services are contacted or changed.
using ConsoleMonster;
using ConsoleMonster.Frames;
using ConsoleMonster.Widgets;
// All values are synthetic. This example never connects to a host.
// Data: fictional inputs kept in memory.
string[][] sites =
{
new[] { "London", "22", "21" },
new[] { "Amsterdam", "16", "15" },
new[] { "New York", "10", "8" }
};
string[][] backups =
{
new[] { "Nightly SQL", "02:10", "84 GB" },
new[] { "Weekly files", "Sun 01:00", "312 GB" }
};
// Layout: the frame places each part for the terminal's width.
DashboardFrame frame = Ui.DashboardFrame("Server estate");
frame.Header.Facts.Add("contoso.example");
frame.Header.Facts.Add("48 servers");
frame.Header.Facts.Add("3 sites");
frame.Header.States.Add(FrameState.Of(HealthState.Failing, 1, "failing"));
frame.Header.States.Add(FrameState.Of(HealthState.Warning, 3, "warning"));
frame.Meters.Add(Meter.Composition("Servers healthy", 44, 3, 1));
frame.Meters.Add(Meter.Ratio("Backups current", 46, 48));
frame.Meters.Add(Meter.Fact("Oldest patch", "41 days", "FS-02", HealthState.Warning));
frame.Meters.Add(Meter.Count("Open alerts", 5));
frame.Attention.Noun("server");
frame.Attention.OutOf(48);
(HealthState State, string Identity, string Reason, string Value)[] problems =
{
(HealthState.Failing, "SQL-03", "Disk D: 97% full", "97%"),
(HealthState.Warning, "FS-02", "Patches 41 days old", "41 d"),
(HealthState.Warning, "WEB-07", "Certificate expires in 9 days", "9 d")
};
for (int i = 0; i < problems.Length; i++)
{
AttentionItem item = new AttentionItem(problems[i].State, problems[i].Identity,
problems[i].Reason, i + 1.0);
item.Values.Add(problems[i].Value);
frame.Attention.Items.Add(item);
}
Table siteTable = Ui.Table().AddColumn("Site").AddColumn("Servers").AddColumn("Healthy");
foreach (string[] site in sites) { siteTable.AddValues(site); }
FrameSection siteSection = new FrameSection("Sites", context => siteTable);
siteSection.Facts.Add("by location");
frame.Sections.Add(siteSection);
Table backupTable = Ui.Table().AddColumn("Job").AddColumn("Last run").AddColumn("Size");
foreach (string[] backup in backups) { backupTable.AddValues(backup); }
frame.Sections.Add(new FrameSection("Backups", context => backupTable)
.Placement(FramePlacement.Detail));
frame.Legend.Add(HealthState.Healthy, "healthy");
frame.Legend.Add(HealthState.Warning, "warning");
frame.Legend.Add(HealthState.Failing, "failing");
frame.Footer.Counts.Add("48 servers");
frame.Footer.Hint(FrameHint.None);
// Behaviour: print the frame once and return.
frame.Show();
Run: dotnet run
Server estate · contoso.example · 48 servers · 3 sites ─────────────────────────── × 1 failing · ▲ 3 warning Servers healthy Backups current Oldest patch 44/48 ████████████████████ 46/48 ████████████████████ 41 days FS-02 Needs attention 3 of 48 servers · worst first ────────────── × 1 · ▲ 2 Sites by location ────────────── × SQL-03 97% Disk D: 97% full ╭───────────┬─────────┬─────────╮ ▲ FS-02 41 d Patches 41 days old │ Site │ Servers │ Healthy │ ▲ WEB-07 9 d Certificate expires in 9 days ├───────────┼─────────┼─────────┤ ✓ healthy · ▲ warning · × failing · 48 servers │ London │ 22 │ 21 │ │ Amsterdam │ 16 │ 15 │ │ New York │ 10 │ 8 │ ╰───────────┴─────────┴─────────╯ Backups ──────────────────────────────────────────────────────────────────────────────────────────────────── ╭──────────────┬───────────┬────────╮ │ Job │ Last run │ Size │ ├──────────────┼───────────┼────────┤ │ Nightly SQL │ 02:10 │ 84 GB │ │ Weekly files │ Sun 01:00 │ 312 GB │ ╰──────────────┴───────────┴────────╯
At 110 columns the attention list and the sites share a row, and the backups, a detail section, follow below.
How it works
Each part is a record in PowerShell and an object in C#. Meters pick their kind from the fields they carry: Healthy with Warning and Failing is a composition bar, Healthy with Whole is a ratio, a number Value is a count and a text Value is a fact.
$frame = @{
Title = 'Server estate'
Fact = 'contoso.example', '48 servers'
Meter = @(
[pscustomobject]@{ Label = 'Servers healthy'; Healthy = 44; Warning = 3; Failing = 1 }
[pscustomobject]@{ Label = 'Backups current'; Healthy = 46; Whole = 48 }
)
Attention = [pscustomobject]@{
State = 'Failing'; Identity = 'SQL-03'; Reason = 'Disk D: 97% full'; Values = '97%'
}
Noun = 'server'
OutOf = 48
Section = [pscustomobject]@{ Title = 'Sites'; Content = { $sites | New-ConsoleTable } }
}
New-ConsoleDashboardFrame @frame | Show-Console
var frame = new DashboardFrame("Server estate");
frame.Header.Facts.Add("contoso.example");
frame.Header.Facts.Add("48 servers");
frame.Meters.Add(Meter.Composition("Servers healthy", 44, 3, 1));
frame.Meters.Add(Meter.Ratio("Backups current", 46, 48));
frame.Attention.Noun = "server";
frame.Attention.OutOf = 48;
frame.Attention.Items.Add(new AttentionItem(HealthState.Failing, "SQL-03", "Disk D: 97% full") {
Values = { "97%" }
});
frame.Sections.Add(new FrameSection("Sites", context => siteTable));
Terminal.Write(frame);
Ui.DashboardFrame("Server estate")
.Fact("contoso.example", "48 servers")
.Meter(Meter.Composition("Servers healthy", 44, 3, 1))
.Meter(Meter.Ratio("Backups current", 46, 48))
.AttentionOf("server", 48)
.Attention(HealthState.Failing, "SQL-03", "Disk D: 97% full", "97%")
.Section("Sites", c => c.Add(siteTable))
.Show();
- Title line. Facts follow the title and states (
State,Count,Word) sit at the right edge. When every state counts zero and nothing needs attention, the line says "all healthy".-ObservedAtadds how long ago the data was taken. - Meters print in the order given; a narrow row keeps the first ones.
- Needs attention prints first in the summary rows whenever it has items, worst state first, then by
Order, with "3 of 48 servers" from-Nounand-OutOf. - Sections build their content once. Summary sections pack side by side while their minimum widths fit, and a later one drops into the shortest column;
Placement = 'Detail'prints a section below at full width.MinWidth,MaxWidth,GrowandFullWidthshape the row. - Legend and footer. The legend lists the states you name; the footer prints the counts. A frame the script builds has no footer hint; module views use
EveryRow("pipe to Format-Table for every row").
Variations
The same program at 60 columns: the facts move under the title, meters stack two to a row and each section takes the whole width.
Server estate ────────────────── × 1 failing · ▲ 3 warning contoso.example · 48 servers · 3 sites Servers healthy Backups current 44/48 ████████████████████ 46/48 ████████████████████ Oldest patch Open alerts 41 days FS-02 5 Needs attention 3 of 48 servers · worst fi… ─── × 1 · ▲ 2 × SQL-03 97% Disk D: 97% full ▲ FS-02 41 d Patches 41 days old ▲ WEB-07 9 d Certificate expires in 9 days ✓ healthy · ▲ warning · × failing · 48 servers Sites by location ────────────── ╭───────────┬─────────┬─────────╮ │ Site │ Servers │ Healthy │ ├───────────┼─────────┼─────────┤ │ London │ 22 │ 21 │ │ Amsterdam │ 16 │ 15 │ │ New York │ 10 │ 8 │ ╰───────────┴─────────┴─────────╯ Backups ────────────────────────────────────────────────── ╭──────────────┬───────────┬────────╮ │ Job │ Last run │ Size │ ├──────────────┼───────────┼────────┤ │ Nightly SQL │ 02:10 │ 84 GB │ │ Weekly files │ Sun 01:00 │ 312 GB │ ╰──────────────┴───────────┴────────╯
Server estate ────────────────── × 1 failing · ▲ 3 warning contoso.example · 48 servers · 3 sites Servers healthy Backups current 44/48 ████████████████████ 46/48 ████████████████████ Oldest patch Open alerts 41 days FS-02 5 Needs attention 3 of 48 servers · worst fi… ─── × 1 · ▲ 2 × SQL-03 97% Disk D: 97% full ▲ FS-02 41 d Patches 41 days old ▲ WEB-07 9 d Certificate expires in 9 days ✓ healthy · ▲ warning · × failing · 48 servers Sites by location ────────────── ╭───────────┬─────────┬─────────╮ │ Site │ Servers │ Healthy │ ├───────────┼─────────┼─────────┤ │ London │ 22 │ 21 │ │ Amsterdam │ 16 │ 15 │ │ New York │ 10 │ 8 │ ╰───────────┴─────────┴─────────╯ Backups ────────────────────────────────────────────────── ╭──────────────┬───────────┬────────╮ │ Job │ Last run │ Size │ ├──────────────┼───────────┼────────┤ │ Nightly SQL │ 02:10 │ 84 GB │ │ Weekly files │ Sun 01:00 │ 312 GB │ ╰──────────────┴───────────┴────────╯
-Length Summaryleaves out the detail sections andFulllifts the row caps; without it the frame follows the session's preference.- On the ASCII tier every cell inside the frame, including your own widgets, is folded to printable ASCII without changing the layout.
- A module that owns summary objects declares its frame in C#, next to its data model, and registers it as their default display;
DashboardFrame.Compactthen gives it a narrow view of its own below 80 columns.
The server estate demo is the program above with its download and run lines, and the readiness report builds a similar summary from ordinary widgets.
Next
- Hosting dashboard: a live dashboard for a .NET generic host.
- Charts from the pipeline: numeric properties as charts you can put in a section.
- Host overview: a finished read-only summary for a login.