Contents

Building a Real-time Terminal Weather Dashboard in Coni

Who says the terminal has to be boring?

Recently, I stumbled across some stunning e-paper displays online. You know the ones—sleek, grid-based layouts showing live stock market numbers and crypto rates. It got me thinking: what if we could replicate that exact aesthetic natively in the terminal using Coni?

As it turns out, it’s not just possible—it’s incredibly easy.

Thanks to Coni’s built-in ui-mount primitive, we don’t have to mess around with raw ANSI escape codes or manual cursor positioning. We just declare our UI state, map it to a layout tree, and let the engine do the heavy lifting!

Here’s the result:

/posts/coni/weather-dashboard-tui.png

Fetching the Data

To make it actually useful, we pull live data from wttr.in. Coni’s native sys-http-get makes HTTP calls trivial, and sys-json-parse automatically converts JSON keys into idiomatic Clojure-style keywords (e.g., :temp_C, :humidity).

We can spawn this inside a background spawn block, firing a loop that quietly sleeps and updates our global state atom every 10 minutes.

(require "libs/http/src/http.coni" :as http)
(require "libs/json/src/json.coni" :as json)

(def *state (atom {
  :city "Locating..." :temp "--" 
  :condition "Fetching..." :humidity "--" 
  :wind "--" :pressure "--" :uv-index "--" 
  :visibility "--" :feels-like "--" :forecast []
}))

(defn update-weather []
  (try
    (let [resp (sys-http-get "https://wttr.in/?format=j1")
          data (sys-json-parse resp)
          curr (first (:current_condition data))
          area (first (:nearest_area data))
          city (:value (first (:areaName area)))
          weather (:weather data)
          forecast (map (fn [w] 
                          {:day (:date w) 
                           :temp (:maxtempC w)}) 
                        weather)]
      (swap! *state merge {
        :city city
        :temp (:temp_C curr)
        :condition (:value (first (:weatherDesc curr)))
        :humidity (str (:humidity curr) "%")
        :wind (str (:windspeedKmph curr) " km/h " 
                   (:winddir16Point curr))
        :pressure (str (:pressure curr) " hPa")
        :uv-index (:uvIndex curr)
        :visibility (str (:visibility curr) " km")
        :feels-like (:FeelsLikeC curr)
        :forecast forecast
      }))
    (catch e
      (swap! *state assoc :condition "Network Error!"))))

;; Auto-Refresh every 10 mins in background
(spawn (fn []
  (loop []
    (update-weather)
    (sleep 600000)
    (recur))))

Declarative Grid Layout

The layout is built entirely with declarative maps. We use :row and :column panes, and define :weight values so the grid perfectly resizes when you stretch your terminal.

(defn render-forecast [state]
  (if (> (count (:forecast state)) 0)
    (map (fn [f]
           {:type :text 
            :text (str "\n [cyan]" (:day f) "[-]\n\n"
                       " [white]" (:temp f) "°C[-]") 
            :align :center
            :border true})
         (:forecast state))
    [{:type :text 
      :text "\n [gray]Loading...[-]" 
      :align :center 
      :border true}]))

(defn render [state]
  {:type :pane
   :direction :column
   :children [
     ;; Header
     {:type :text 
      :text " [blue:white] LIVE WEATHER DASHBOARD [-:-]" 
      :size 1}
     
     ;; Main Split
     {:type :pane
      :direction :row
      :children [
        
        ;; Left Column (40% width)
        {:type :pane
         :direction :column
         :weight 40
         :border true
         :title " CURRENT "
         :children [
           {:type :text 
            :text (str "\n\n  [cyan]" (:city state) "[-]\n"
                       "  [white::b]" (:temp state) "°C[-]\n"
                       "  [gray]" (:condition state) "[-]") 
            :size 10}
           {:type :text 
            :text (str "\n  Humidity:   [blue]" (:humidity state) "[-]\n"
                       "  Wind:       [green]" (:wind state) "[-]\n"
                       "  Pressure:   [yellow]" (:pressure state) "[-]")}
         ]}
         
        ;; Right Column (60% width)
        {:type :pane
         :direction :column
         :weight 60
         :children [
           
           ;; Top Right - Forecast
           {:type :pane
            :direction :row
            :border true
            :title " MULTI-DAY FORECAST "
            :weight 50
            :children (render-forecast state)}
                           
           ;; Bottom Right - Metrics
           {:type :text
            :border true
            :title " METRICS "
            :weight 50
            :text (str "\n [green]UV Index:[-]   " (:uv-index state) "\n"
                       " [green]Visibility:[-] " (:visibility state) "\n"
                       " [blue]Feels Like:[-] " (:feels-like state) "°C\n\n"
                       " [yellow]Alerts:[-]     None\n")
           }
         ]}
      ]}
   ]})

;; Mount to Terminal!
(ui-mount *state render)

Why this is awesome

The real magic here is the ui-mount primitive. Notice how there are zero imperative UI updates? We don’t manually clear the screen or print new lines. Because we mount the *state atom to the render layout tree, any time the background spawn loop calls swap! to mutate the global state, the terminal natively re-calculates the diff and instantly repaints just the updated text boxes.

Give it a try and transform your terminal into a sleek weather station!

What’s Next? (Raspberry Pi & IoT Ideas)

Because Coni’s ui-mount engine is so declarative and automatically handles resizing, it’s absolutely perfect for Raspberry Pi e-ink displays, tiny LCDs, or old iPads repurposed as SSH terminals.

Here are a few other fun, highly practical CLI dashboards you could build in just a few lines of code:

  • 📈 Crypto & Stock Market Ticker: Map out a grid of tiles for Bitcoin, Ethereum, and your favorite tech stocks. Using Coni’s built-in (spawn) loop, it can glow green or red as the prices change in real-time.
  • 🧠 Smart Home & IoT Hub: Hit local Home Assistant or Philips Hue APIs. You could have a 2x2 grid showing Living Room Lights, Front Door Lock status, and what’s currently playing on Spotify.
  • 🖥️ Homelab Server Monitor: A sleek resource monitor for a home server rack. You can use Coni’s (sys-exec "top -l 1") to pull system data and use the ui-mount engine to draw progress bars for CPU load and RAM usage.
  • 📝 Daily Kanban & Focus Timer: Build a “Deep Work” screen that pulls your tasks for today on the left column (using Coni’s todoist library), and has a giant 25-minute Pomodoro countdown timer running in the right column.
  • 📰 AI-Summarized Morning Briefing: Combine sys-http-get (to fetch RSS feeds) with Coni’s native LLM integrations. The background thread could fetch the top 5 articles, pass them to a local LLM to write a 1-sentence summary for each, and display them in a clean, scrollable terminal feed on your desk.

The terminal is your oyster. Happy hacking!


The Grand Vision: Coni Ops (SSH Fleet Manager)

I’d actually position it less as an SSH client and more as a terminal control center for infrastructure.

A terminal-native dashboard for managing fleets of Linux servers from a single interface. Rather than opening dozens of SSH sessions, the Fleet Manager provides a live overview of every machine and lets you inspect, troubleshoot, and operate them from one place.

Dashboard

Fleet Overview

  Production      24 servers
  Staging          8 servers
  Development     15 servers
  Home Lab         6 servers

──────────────────────────────────────────────

✓ api-01              12% CPU   48% RAM
✓ api-02              18% CPU   52% RAM
⚠ worker-03           95% CPU   91% RAM
✗ redis-01            Offline
✓ postgres-01         Healthy

The dashboard refreshes continuously and immediately highlights unhealthy machines.

Server Details

Selecting a server opens a detailed view.

Hostname      api-01
OS            Ubuntu 24.04
Kernel        6.12
Uptime        36 days
Load          0.42

CPU           ████░░░░░░ 38%
Memory        ██████░░░░ 61%
Disk          ██░░░░░░░░ 24%
Network       320 Mbps

Additional panels include:

  • Running services
  • Docker containers
  • Disk usage
  • Mounted filesystems
  • Active users
  • Recent logs
  • Open ports
  • Installed software

Interactive Operations

Every server can be managed directly. Examples:

  • Restart a service
  • Tail live logs
  • Open an interactive shell
  • Browse files
  • Upload or download files
  • Reboot
  • Shutdown
  • Update packages
  • Check system health

Multi-Server Operations

One of the strongest features is executing commands across an entire fleet.

Run command
> sudo systemctl restart nginx

Targets
✓ Production
✓ Staging

Running...
24 / 24 completed

Results are aggregated, making it easy to identify failures without opening individual SSH sessions.

Docker & Kubernetes

When available, the manager automatically detects container environments.

  • Docker: Running containers, Images, Volumes, Logs, Restart containers
  • Kubernetes: Namespaces, Pods, Deployments, Events, Live logs, Resource usage

Monitoring & Alerts

Built-in live monitoring includes CPU history, Memory trends, Disk utilization, Network throughput, Temperature (where available), Process list, and Top resource consumers.

Servers exceeding configurable thresholds are highlighted immediately (e.g., High CPU, Low disk space, Offline host, Service failure, Expiring certificates).

Architecture

The Fleet Manager is intentionally lightweight.

Fleet Manager
        ├── SSH
        ├── SCP
        ├── SFTP
        └── Optional Agent
Linux Servers (AWS EC2, Azure VMs, Google Cloud, Raspberry Pi, Home Lab)

No mandatory agents are required. Standard SSH is sufficient for most functionality, with an optional lightweight agent for enhanced metrics.

Why it’s a great Coni showcase

This application demonstrates many aspects of the language and runtime:

  • Terminal UI layouts
  • Live asynchronous updates
  • Networking
  • SSH integration
  • Concurrent execution across many hosts
  • Streaming logs
  • Interactive widgets
  • Charts and sparklines
  • Keyboard-driven workflows
  • Cross-platform native binaries

It is also a practical tool that developers, DevOps engineers, SREs, and system administrators could use every day, making it both a compelling demo and a valuable real-world application.

I also see a natural evolution where this becomes Coni Ops: not just SSH, but a unified interface for SSH, Docker, Kubernetes, MCP servers, Git repositories, CI/CD pipelines, and local AI assistants—all within a single terminal workspace. That would differentiate it from traditional SSH managers by making it an operational cockpit rather than just a connection manager.