Skip to content
ConsoleMonster

Facts, bars and hierarchy

Give counts, quotas, comparisons and composition consistent meaning in terminal reports.

Reports give the same kind of number the same look everywhere: a count, a quota, a comparison and a share of a whole each have one form, so a reader learns them once and reads every report the same way.

Server estate 110×2024-bitFull
PowerShellserver-estate.ps1
# 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-Console
Full 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-Console
C#Program.cs
// 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);
C# fluentProgram.cs
// 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();
The server estate summary: facts after the title, a composition bar and a ratio as meters, and the needs-attention list.

Facts

Facts are bold counts with a label. A status fact uses the same colour for the count and its word; a neutral fact steps back in Muted. Facts are separated by middle dots, or | in ASCII. The summaries under the module's default views use the same widget.

Import-Module ConsoleMonster

$facts = @(
    [pscustomobject]@{ Value = 3; Label = 'passed'; Role = 'Pass' }
    [pscustomobject]@{ Value = 1; Label = 'warning'; Role = 'Warn' }
    [pscustomobject]@{ Value = 5; Label = 'checks' }
)
$facts | New-ConsoleFacts | Show-Console
using ConsoleMonster;
using ConsoleMonster.Styling;
using ConsoleMonster.Widgets;

Terminal.Write(new ViewFacts {
    Facts = {
        new ViewFact(3, "passed", role: ThemeToken.Pass),
        new ViewFact(1, "warning", role: ThemeToken.Warn),
        new ViewFact(5, "checks")
    }
});
using ConsoleMonster;
using ConsoleMonster.Styling;

Ui.Facts().Add(3, "passed", ThemeToken.Pass).Add(1, "warning", ThemeToken.Warn)
    .Add(5, "checks").Show();

Bars

  • Quota. A Bar chart with -Maximum 100 draws a Line bar on a Subtle track with a percentage on the right. With any other maximum it shows value/whole.
  • Comparison. Without -Maximum, bars compare their values as solid blocks without a track.
  • Composition. A composition bar keeps the healthy share neutral and colours only the problems. Adjacent identical fills are separated by one cell when there is room, and the legend fits its columns to their labels before wrapping.

Progress bars and score gauges use the same quota form.

New-ConsoleChart -Type Sparkline -Values 4,3,8 -Labels '00:00','12:00','23:00' adds an axis and the peak value under the trend. Without -Labels it stays a compact one-line trend. A chart inside a titled panel drops a caption that would repeat the title.

Height and alignment

Printed reports keep their natural height. In a live Dashboard, rows share the remaining screen space, and an explicit Fill row takes precedence over the last section. Table columns that hold only numbers align right, even when the values arrive as strings; an explicit alignment wins.

Next

Edit this page on GitHub