Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
49 changes: 48 additions & 1 deletion CHANGELOG.md
Original file line number Diff line number Diff line change
Expand Up @@ -12,6 +12,8 @@ Per-release notes, in the form they were published on GitHub, are in

## [Unreleased]

## [0.2.0] - 2026-08-23

Everything since 0.1.3 has been about a dashboard that says what it knows, and
what it does not.

Expand Down Expand Up @@ -58,8 +60,52 @@ what it does not.
- **Contrast is a test.** `internal/theme` holds text to 4.5:1, structural
colours to 3:1, and signal pairs to a minimum distance in OKLab.

- **Focus mode, clustering and a minimap.** At 200 workloads across 40
namespaces the whole picture is not a picture. `F` reduces the graph to a
node's neighbourhood and reports what was left out; the remainder becomes one
counted card rather than nothing at all. Past 220 nodes the graph is refused
rather than drawn, with search and the namespace level offered instead. The
minimap keeps unprotected workloads red even when they are off screen, since
focus must never hide the thing you are hunting.
- **A "reachable from outside" filter.** Directed, unlike the focus traversal:
being able to call the internet is not being reachable from it, and private
ranges are somebody's VPC rather than the internet.
- **System namespaces collapse.** `kube-system` and its neighbours become one
counted card each, because on most clusters they hold the only unprotected
workloads there are and the headline number ends up pointing at infrastructure
nobody will write a policy for. Configurable by flag and Helm value, always
reversible, and never hidden.
- **A list at phone width.** Below 700px the graph is replaced rather than
shrunk — a node-link diagram at that size is not a small diagram — grouped by
namespace and ordered worst-first, with the panels becoming sheets.
- **The graph, for a keyboard.** A canvas is a rectangle with no contents to a
screen reader, so every workload and rule in the picture was absent to anyone
not reading it with their eyes and unreachable to anyone without a mouse. It
is now one tab stop with arrow navigation along real edges, `E` to step onto
the edges leaving a node, and a sentence describing each one.
- **A shortcut list**, on `?`. Every key in it already worked; none of them was
written down anywhere a person would look.
- **Policies are searchable** by name, through `GET /api/policies`, with how
many workloads each one actually selects — zero usually means label drift.

### Changed

- The palette is the single way in: search and simulate were adjacent buttons
doing the same thing to different halves of one question, and `⌘↵` on any
workload now opens the simulation already framed by it.
- Three edge kinds get three dash patterns, so hue is never the only thing
carrying the meaning — for readers who cannot separate the green from the
amber, and for print.
- One focus ring everywhere, drawn as an outline with an offset so that focusing
something never nudges it. There had been three different treatments across
the components that had one at all.
- Namespace cards say "1 of 3 unprotected" rather than "3": the count is how big
it is, the posture is whether anything is wrong with it. Aggregated edges
carry a port pill.
- Empty namespaces are reported along the bottom rather than drawn, having no
posture to show and no edges to place them.
- The README's screenshots render the example cluster and are regenerated by
`make screenshots` rather than captured by hand.
- The header leads with the finding: one chip, in danger colour, reading "N
unprotected of M workloads", and a control rather than a statement. The other
counts are faint context.
Expand Down Expand Up @@ -172,7 +218,8 @@ First release.
a Docker-only toolchain so no Go, Node, or Kubernetes tooling is needed
locally.

[Unreleased]: https://github.com/FathiQ/marsad/compare/v0.1.3...HEAD
[Unreleased]: https://github.com/FathiQ/marsad/compare/v0.2.0...HEAD
[0.2.0]: https://github.com/FathiQ/marsad/compare/v0.1.3...v0.2.0
[0.1.3]: https://github.com/FathiQ/marsad/compare/v0.1.2...v0.1.3
[0.1.2]: https://github.com/FathiQ/marsad/compare/v0.1.1...v0.1.2
[0.1.1]: https://github.com/FathiQ/marsad/compare/v0.1.0...v0.1.1
Expand Down
4 changes: 2 additions & 2 deletions charts/marsad/Chart.yaml
Original file line number Diff line number Diff line change
Expand Up @@ -6,8 +6,8 @@ type: application
# The chart version tracks the application version. Marsad is a single
# component with no subcharts, so a chart change that is not an app change is
# rare enough that two independent version lines would cost more than it saves.
version: 0.1.3
appVersion: "0.1.3"
version: 0.2.0
appVersion: "0.2.0"

kubeVersion: ">=1.25.0-0"

Expand Down
73 changes: 73 additions & 0 deletions docs/releases/v0.2.0.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,73 @@
# v0.2.0

A dashboard that says what it knows, and what it does not.

Everything since 0.1.3 has gone into the two questions a read-only tool has to
answer honestly: is what I am showing you current, and is what I am not showing
you important.

## The screens that were missing

**A cluster Marsad cannot read** used to produce no screen at all — preflight
failed, the process exited, Kubernetes restarted it, and the explanation lived
in a pod log somebody had to know to look for. The UI now comes up either way,
shows the API server's own words verbatim, and hands you the ClusterRole that
answers it, generated from the same rule list `deploy/rbac.yaml` is tested
against.

**Freshness is a state.** A graph built four minutes ago looks exactly like one
built a second ago. The header says live, reconnecting or snapshot, with the
time of the last update or a countdown to the next attempt.

**Startup reports real progress**, read from `/api/health` — the one endpoint
that answers while the informer caches are still filling and everything else
is returning 503.

## The questions the panels were leaving open

**Closest misses.** "No policy selects this workload" states a fact and stops.
The inspector now ranks the policies in that namespace that nearly did, and
shows both halves: what the policy wanted, and what the workload actually has.

**The rule beside the edge.** Clicking an edge opens a popover at the click
point holding the matching rule — not the hundred-line document it lives in.

**What the refusing half does accept.** A denial says the connection fails; it
does not say what the policy was written for, and without that the next step is
a guess.

## Scale, and what it costs to hide things

Focus reduces the graph to a neighbourhood and says how much it left out.
System namespaces collapse to one counted card each, because on most clusters
they hold the only unprotected workloads there are. Past 220 nodes the graph is
refused rather than drawn. Every one of those is reversible and none of them is
silent — the minimap keeps unprotected workloads red even off screen, because
focus must never hide the thing you are hunting.

## Reachable without a mouse

The graph was a rectangle with no contents to a screen reader. It is now one tab
stop with arrow navigation along real edges, a sentence describing every node,
and `?` for the shortcut list. Below 700px the graph is replaced by a list
rather than shrunk.

Colour no longer carries meaning alone: three edge kinds, three dash patterns.
Contrast is a test rather than an intention, and `--node-world` is no longer the
same red as `--danger` — "outside the cluster" and "unprotected" were
indistinguishable.

## Upgrading

No action required. `GET /api/rules` and `GET /api/policies` are new; nothing
was removed. `config.systemNamespaces` is a new Helm value and defaults to a
built-in list.

One behaviour change worth knowing: Marsad no longer exits when it cannot read
the cluster. It serves the UI and explains the failure instead, so a permission
problem shows as a screen rather than a CrashLoopBackOff. Readiness still
reports false, and the liveness probe still passes — deliberately, since
restarting would take away the screen that explains the problem.

Still read-only. RBAC is `get`, `list`, `watch`, and there is no write path in
the code.
Loading