Rollup of operational signals across every env in an application.
Pure — driven entirely by the in-memory env list, so the Apps
table can refresh as part of the same view-rebuild that touches
the Envs table.
Pure render of the :options overlay body. Groups rows by
namespace; within each group, operator-set rows come first
(marked ▸), defaults follow (marked •). Optional
filter_ns restricts to one namespace.
Payload for AppMsg::FormMultiSelectLoaded. Carries the full option
list, parallel display annotations, and the current EB selection so
the form’s MultiSelect field can be populated in one update.
One in-flight or recently-completed action. label is the human-readable
verb (e.g. “Rebuild env”), target the env or instance the
action was dispatched against. completed lands when AppMsg::ActionResult
arrives; until then the entry counts as in-flight and the user can see it
in the :pending overlay + header chip.
One action (or batch of actions) queued for dispatch with a brief
cancel window. After the operator authorises a confirm (Y on a
YesNo modal, typed name on a TypeName modal) or runs a
:batch-* command, ebman doesn’t fire the AWS call immediately —
it holds the dispatch here, shows a countdown in the header, and
fires only when UNDO_WINDOW elapses. U in Normal mode
aborts before the deadline.
Items the Apps-scope action overlay (Overlay::AppsActionMenu)
offers when the operator presses a from the Apps table. Each
dispatches via cmd_batch_* after seeding multi_selected with the
envs captured at menu-open time.
How event timestamps render. Three-state cycle:
Utc (default — matches EB / CloudWatch API output) →
Local (operator’s wall-clock for cross-referencing with
other terminals / Slack threads) → Age (compact 5m /
2h / 3d relative form). Persists in state.toml as
event_time_format = "utc"|"local"|"age".
Which on-screen panel is “focused” — i.e. which one j/k/Enter target. The
main table is the default; the user can Ctrl-] over to the events panel
(when visible) for cursor navigation + line yank.
Help overlay scope. Global shows the full keymap; the per-mode topics
surface only the keys relevant to where the user just pressed ?,
avoiding the “wall of help” problem when the user just needs a reminder
about the screen they’re on. Set when entering Mode::Help.
A single read-only popup that overlays the main UI. Only one can be open
at once: opening another replaces it; Esc / q dismisses it. Replacing
the previous six Option<String> fields with this enum eliminates the
“did I forget one?” footgun every time a new overlay is added (separate
dismiss path, separate draw conditional, separate dismiss-on-context-switch
branch, …).
The actual work tick_pending_dispatch dispatches when the
cancel window elapses. Mirrors the existing dispatch paths:
Single re-uses App::spawn_action; the batch variants
re-use the per-env spawn_batch_* helpers in a loop.
Inferred kind of an Updating env’s in-flight operation. EB’s
status field is generic (“Updating”) regardless of cause, but the
recent events expose what’s actually happening. The Health tab uses
this to render Updating: deploying build-142 (or similar) instead
of just the generic pill.
One drillable row in the :why triage overlay. The renderer pushes
these in lockstep with the lines it emits (events / alarms /
instances / deploys / queues / dlq), and writes the list to
App.why_items so the key handler can act on items[cursor] when
the operator presses Enter.
Once the loading indicator becomes visible, keep it visible for at
least this long even if the load completes earlier. Smooths over the
case where a round-trip is just slow enough to cross the threshold
and then finishes ~100 ms later — without the linger, the indicator
flashes on and off in a single visible frame which reads as flicker.
How long a refresh has to be in flight before the loading… indicator
in the header appears. Faster round-trips complete invisibly so the user
doesn’t see a quick blip on every cycle.
Cancel window after a confirm — long enough that an “oops” reflex
can recover but short enough that operators don’t notice it on a
deliberate action. The UX review flagged the absence of any
abort affordance after dispatch as a real safety gap.
Map a friendly env-metric “kind” to a (metric_name, default_op, default_stat)
triple. The user can override the operator on the CLI but the defaults
reflect “what you’d reasonably alarm on for this metric” — e.g. drop in
health (LE) vs spike in 5xx (GT). Pure so the unit tests don’t need
AWS.
Compute the rollup for one application. Iterates envs once and
counts Red / Updating envs (case-insensitive on the health + status
columns). Worker-DLQ alerts come from dlq_depths which the App
owns globally — passed in so this stays a free fn that test code
can call without a full App.
Pure: compose a probe URL from a CNAME + a health-check path.
EB CNAMEs are bare hostnames (api-prod.eba.amazonaws.com);
the path may or may not start with a slash. We always emit a
http:// URL because EB envs aren’t HTTPS by default and a
missing TLS cert is a separate operator concern (the probe
shouldn’t false-positive on that). Operators with custom TLS
can put their HTTPS CNAME directly into their LB listener
config; the probe is a development-mode best-effort signal.
Names of all built-in :commands. Used to detect collisions when loading
user plugins from commands.toml — plugins that shadow a built-in are
dropped with a warning rather than silently masking it.
Pure: classify an Updating env’s in-flight op by looking at the
most recent event whose message matches a known pattern. Events are
expected newest-first (as the EB API returns them); returns the kind
from the first matching event. Returns Generic when nothing
matches.
Flatten the per-application configuration_templates lists into a single
(application, template) vector, sorted by app then by template name so
the overlay’s cursor order is stable across refreshes. Pure so the unit
tests don’t need an AWS client.
Pure helper: resolve BatchSize + BatchSizeType into a
concrete instance count, clamped to [1, asg_max]. Percentage
rounds UP (a 33% batch on a 4-instance ASG is 2 instances; EB
rounds up internally too, per the docs).
Exponential back-off horizon: 2× base on the first throttle, doubling each
consecutive failure, capped at 5 minutes. The 5 min cap keeps the app
responsive when the throttle clears — the user shouldn’t have to wait
arbitrarily long after rate limits ease.
Pure: given the moment a load started and the display constants, return
the instant the loading indicator should remain visible until (if it
was visible at all). Returns None when the load completed before the
indicator’s display threshold, signalling “no linger needed”.
Pre-flight signal for the confirm modal: looks at the env’s current state
at action-open time and returns a one-line warning when something
noteworthy is in progress (mid-deploy, recently updated, currently in
Updating / Terminating). None for envs that look quiet. Pure function so
the rule set can be pinned down with unit tests.
Pure: how many instances will be simultaneously unavailable
during a deploy with the given EB deployment policy + batch
settings + ASG max-size. Returns the worst-case planning
number — what the operator sees on the EB dashboard during
the rollout. asg_max clamps at 1 to avoid divide-by-zero
nonsense on misconfigured envs.
Extract a “delta toast key” from text shaped like ▲2 Red / ▼1 Yellow.
Returns Some(bucket_name) when the text is a status-delta toast and we
want subsequent updates for the same bucket to replace rather than stack.
Pure function so it’s easy to pin down in tests.
Pure: “deploy has fully succeeded for this env” predicate.
Both conditions matter — UpdateEnvironment momentarily
leaves health=Green while status flips to Updating,
so a watcher that only checks health would false-positive
during that window and disarm a rollback (or report
success) before the deploy has actually settled. Single
source of truth shared by the rollback-watchdog pass, the
wait-for-green pass, and the non-interactive CLI’s
decide_poll.
Derive a version label from a file path + a timestamp. Uses the
filename stem (everything before the last .) so ./build.zip becomes
build_1684512345. Sanitises any chars EB rejects in version labels
(anything outside [A-Za-z0-9_.-]). Pure for testability.
Pure: option-settings that differ between two envs. Compares the
operator-set value per (namespace, name); rows where both
sides agree — including both unset — are dropped. EB’s
Some("") and None both mean “unset”, so they’re normalised
to equal. Result is sorted (namespace, name) for a stable
overlay.
Encode a filter-only saved view — the value :save NAME writes
to saved_views. Omits sort=, grouped=, scope= so loading
the view doesn’t perturb the operator’s current sort / group /
scope state. apply_view ignores missing fields, so a
filter-only view is a safe no-touch-other-state operation.
Pure: expand a typed command line through the operator’s
alias map. If the first whitespace-separated token matches a
key, swap it for the alias’s expansion and keep any remaining
args (appended after the expansion). Single-level only — the
expanded line is NOT re-checked for further aliases, so
alias.x = "x ..." is safe (degenerates to “x …” dispatched
once). Non-alias lines pass through unchanged.
Expand a leading ~/ to $HOME/. Other tilde forms (e.g. ~user) are
left as-is; the operator gets a clear “can’t read” error if they pass
something obscure. Pure for ease of testing.
Pure: extract the four option-settings the unavailability
estimate needs from the flat (namespace, name, value) shape
fetch_env_option_settings returns. Defaults match EB’s own
defaults so the math degrades gracefully on partial reads.
Render env vars as KEY=VALUE lines, aligned on the = for easy scan.
Empty values render as "" so operators can distinguish “explicitly
empty” from “not set”. Pure.
Render the :versions overlay body. Marks the currently-deployed
version with ◀ deployed; trims the redundant
“Application version created from “ prefix that every CI-pipeline
description tends to carry; shows “showing N of M (newest first)”
when the list was truncated. limit caps the visible rows.
Pure: render the :deploy LABEL --preview body. Highlights the
candidate version (label / age / description), the currently-deployed
version’s age for context, and warns if the candidate predates the
current one (rolling back is intentional but worth flagging).
Render a sorted (namespace, option_name, value) list as an aligned
text block grouped by namespace. Empty values render as "" so the
reader can distinguish “explicitly empty” from “not present”.
Pure: render the modal’s unavailability line. Returns the
human-readable text plus a severity flag for colouring (true
= caution, false = green/no impact).
Best-effort hourly USD price for an EC2 instance type, on-demand Linux,
us-east-1 as the baseline. Returned in USD/hour. Returns None for unknown
types — caller should label the estimate as “approximate (us-east-1)”.
Pure: whether an event message looks like a deploy or a
configuration change — the rows the :changes timeline keeps,
filtering out routine health / scaling / launch noise.
Parse the optional trailing args of :metric add LABEL NS NAME ....
Args after NAME are either a stat name (Average, Sum, …) or a
dimension list (InstanceId=i-abc,Foo=bar). Any token containing =
is treated as dims; the other is stat. Returns (stat, dims) with
stat defaulting to Average and dims to empty when absent. Pure.
Pull a --flag VALUE style named argument out of a :commandrest
slice and parse it. Returns None if the flag is absent, the value is
missing, or parsing fails. Used by commands like :logs-stream that
take optional flags alongside their positional args. Pure.
Parse an s3://bucket/key/with/slashes URL into a (bucket, key)
tuple. Returns None if the input isn’t an s3:// URL or the bucket
or key is empty. Pure.
Parse a :tag KEY [value tokens…] argument list. Returns Some((key, value)) when there’s at least a key and one value token. Value tokens
are joined with a single space — there’s no shell-style quoting, since
we trust the operator and want the command bar to stay typeable.
Pick the most useful CloudWatch Logs group for an env’s :logs-tail
default. EB streams to a handful of groups per env (web.stdout.log,
nginx access, eb-engine.log, …); we prefer the app stdout because that’s
where deploy / runtime output lives. Falls back to the first by name.
Pure for testability.
Compact age formatter — “3s”, “12s”, “2m”, “1h”, “4d”. Used for the
pending-actions overlay so ages stay short and uniform.
Pure: the version label deployed beforecurrent, found by
scanning events (newest-first, as DescribeEvents returns)
for the first version_label that differs from current. EB
tags each event with the version current at the time, so walking
back, the first label ≠ current is the one the env ran before
this deploy. None when no prior version appears in the window.
Pure: extract the filter portion of an encoded saved view.
Returns the empty string when the view doesn’t include a
filter= part (which means “no filter” — operator wanted the
view to clear whatever filter was set). Used by the chip-bar
active-check + the cycle keybind.
Wrap text at width columns, prefixing the first line with lead and
subsequent lines with cont so continuation visually flows under the
leader (e.g. "↳ " followed by aligned continuation). Greedy
word-wrap; falls back to hard-break inside a word that won’t fit on its
own line. Pure for testability.