Skip to content
ConsoleMonster

Server estate

One frame for a whole estate: facts and state on the title line, meters, problems worst first, sites beside them and backups below.

Demos Dashboards

Use it when one screen has to say how an estate is doing and what to fix first.

  • Static
  • 110×30
  • PowerShell 7.4+
  • .NET 8.0+
Server estate 110×3024-bitFull

How it's built

The whole program in one file, with its sample data at the top. It drew the recording above; all three styles produce the same frame bytes.

PowerShellserver-estate.ps1View on GitHub
# 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.csView on GitHub
// 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.Fluent.csView on GitHub
// 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 it yourself

Three steps in PowerShell, four in C#. Every command copies with one click.

  1. Install the module

    Once. Windows PowerShell 5.1 or PowerShell 7 on Windows, macOS or Linux.

    Install-Module ConsoleMonster -Scope CurrentUser
  2. Save the program

    Download it, or copy it from above into server-estate.ps1.

  3. Run it

    From the folder you saved it in.

    ./server-estate.ps1
  1. Create a project

    A plain console app; .NET 8 or later.

    dotnet new console -n ServerEstate; cd ServerEstate
  2. Save the project

    The project selects one C# style at a time and restores ConsoleMonster.

  3. Save both programs

    Replace Program.cs and save Program.Fluent.cs beside it.

  4. Run it

    dotnet run
  1. Create a project

    A plain console app; .NET 8 or later.

    dotnet new console -n ServerEstate; cd ServerEstate
  2. Save the project

    The project selects one C# style at a time and restores ConsoleMonster.

  3. Save both programs

    Replace Program.cs and save Program.Fluent.cs beside it.

  4. Run it

    dotnet run --property:DemoStyle=Fluent

Or skip the copying. Run the bundled program or create it from the project template.

Show-ConsoleDemo server-estate
$source = (Get-ConsoleDemo server-estate).Source
[IO.File]::WriteAllText("$PWD/server-estate.ps1", $source, [Text.UTF8Encoding]::new($true))
dotnet new install ConsoleMonster.Templates
dotnet new consolemonster --demo server-estate -n ServerEstate
dotnet new install ConsoleMonster.Templates
dotnet new consolemonster --demo server-estate --DemoStyle Fluent -n ServerEstate

Make it yours

Start with these, then read the rest of the file.

  1. Fill the meters and the attention list from results you already have in memory.

  2. Add a section per area you report on, and mark the long ones Detail so they print below.

  3. Name what the attention list counts with AttentionNoun and AttentionOutOf.

How the frame lays itself out

Meters, the attention list, summary and detail sections, the narrow layout and how a module registers a frame as its default display. Dashboard frames

More dashboards

All demos