" for r in M._reviews(pack))
stale = f" It was {e(m['stale_from'])} before the pack changed." if m["stale_from"] else ""
return (f"
Maturity {maturity_pill(pack)}
"
f"
Earned from run history at revision {e(m['digest'])}; independent of any one environment.{stale}
"
f"
Reviewed: {'yes' if ev['reviewed'] else 'no'} · clean runs: {ev['clean_runs']} "
f"(in rehearsal: {ev['rehearsal_runs']}) · environments: {e(', '.join(ev['environments']) or '-')} · "
f"operators: {e(', '.join(ev['operators']) or '-')}
"
"
proven = reviewed + rehearsed + clean in two environments by two operators. Runs without a project "
"context, and runs with deviations, do not count.
"
f"
Idempotency: {e(idempotency_text(pack))}
"
f"{'
' + revs + '
' if revs else ''}"
f"
")
# ---- index sections ---------------------------------------------------------------------------------------------
def index_sections(app, home: bool = False) -> str:
reg = app.registry()
prj = []
for pid, d in reg.projects.items():
if not d.ok:
prj.append(f"
{e(pid)}: invalid ({e(d.errors[0])})
")
continue
errs = J.validate(reg, d)
st = pill("invalid") if errs else pill(J.state(reg, d)["state"])
origin = (f" from template {e(d.data['template'])}"
if d.data.get("template") else "")
desc = f" {e(d.data['description'])}" if d.data.get("description") else ""
prj.append(f"
"
for tid, d in reg.templates.items()]
from .ui import heading
link = "/projects" if home else None
out = f"{heading('h1', 'Projects', link)}
{''.join(prj) or '
No projects yet. Start one from a template.
'}
"
out += f"{heading('h2', 'Project templates', link)}
{''.join(tpl) or '
No templates found.
'}
"
return out
# ---- attribute tables ----------------------------------------------------------------------------------------------
def attribute_table(rows: list[dict]) -> str:
body = ""
for r in rows:
value = "" if r["value"] is None else str(r["value"])
if r["value"] is None and "default" in r:
value = f"(runbook default: {r['default']})"
cls = " class=bad" if r["state"] == "err" or (r["state"] == "nil" and r["requirement"] == "required" and "default" not in r) else ""
source = e(r["source"]) if r["source"] else "-"
body += (f"
{e(r['label'])} ({e(r['id'])})
{e(value)}
"
f"
{pill(r['state'])}{' ' + pill('modified') if r['modified'] else ''}
{e(r['requirement'])}
"
f"
{source}
{e(r.get('error', ''))}
")
return ("
Attribute
Value
State
Requirement
Source
Problem
"
f"{body}
")
# ---- project page ----------------------------------------------------------------------------------------------------
def project_page(app, pid: str, msg: str = "") -> bytes | None:
from .ui import page
reg = app.registry()
doc = reg.projects.get(pid)
if doc is None:
return None
d = doc.data
out = [f"
{e(d.get('title', pid))}
"]
if msg:
out.append(f"
{e(msg)}
")
errs = J.validate(reg, doc)
if errs:
out.append("
This project has problems:
" + "".join(f"
{e(x)}
" for x in errs) + "
")
return page(d.get("title", pid), "".join(out))
st = J.state(reg, doc)
out.append(f"
{pill(d['environment'])} {pill(st['state'])}"
f"{' from template ' + e(d['template']) + '' if d.get('template') else ''}
")
if d.get("description"):
out.append(f"
{e(d['description'])}
")
if d.get("goal"):
out.append(f"
Goal: {e(d['goal'])}
")
out.append(f"Edit name, description and goal"
f"")
# what is still needed
needs: dict[str, list[str]] = {}
for n in st["needs"]:
needs.setdefault(n["attribute"], []).append(n["pack"])
if needs:
fields = "".join(f"
needed by {e(', '.join(packs))}
"
for a, packs in needs.items())
out.append(f"
Still needed
Required attributes without a value. Never enter secrets.
"
f"
")
# members
rows = ""
by_pack = {m["pack"]: m for m in st["members"]}
for spec in d["runbooks"]:
pack = reg.packs[spec["pack"]]
ms = next(m for m in st["members"] if m["pack"] == spec["pack"] and m["target"] == spec.get("target"))
i = app.pack_index(spec["pack"])
wait = [a for a in spec.get("after", []) if by_pack[a]["status"] != "applied"]
needed = sorted({n["attribute"] for n in st["needs"] if n["pack"] == spec["pack"]})
if not _runnable_here(pack):
action = "cannot be run in this UI yet"
elif needed:
action = f"needs: {e(', '.join(needed))}"
elif wait:
action = f"waiting for {e(', '.join(wait))}"
else:
mode = (" "
if pack.spec == "runbook-pack/v0.2" else "")
action = (f"")
link = f" latest run" if ms.get("run") and i is not None else ""
dev = f" · {ms['deviations']} deviation(s)" if ms.get("deviations") else ""
rows += (f"
Next: {e(', '.join(st['next']) or 'nothing to start')}
"
"
Runbook
Status
Maturity
" + rows + "
")
# attributes per member
out.append("
Attributes
What each runbook receives from this project, and where each value comes from.
")
for spec in d["runbooks"]:
res = J.member_attributes(reg, d, spec)
unused = f"
Unused attributes: {e(', '.join(res['unused']))}
" if res["unused"] else ""
out.append(f"{e(reg.packs[spec['pack']].data.get('title', spec['pack']))}"
f"{' @' + e(spec['target']) if spec.get('target') else ''}{attribute_table(res['rows'])}{unused}")
if d.get("targets"):
out.append("
Targets
" + "".join(f"
{e(t)} {e(v.get('title', ''))}
" for t, v in d["targets"].items()) + "
")
return page(d["title"], "".join(out))
# ---- template page ----------------------------------------------------------------------------------------------------
def template_page(app, tid: str, msg: str = "") -> bytes | None:
from .ui import page
reg = app.registry()
doc = reg.templates.get(tid)
if doc is None:
return None
d = doc.data
out = [f"
{e(d.get('title', tid))}
{pill('template')}
"]
if msg:
out.append(f"
{e(msg)}
")
if not doc.ok:
out.append("
This template is invalid:
" + "".join(f"
{e(x)}
" for x in doc.errors) + "
")
return page(d.get("title", tid), "".join(out))
if d.get("goal"):
out.append(f"
{e(d['goal'])}
")
layers = []
try:
for s in d.get("attribute_sets", []):
layers += set_layers(reg, s)
except ValueError as ex:
out.append(f"
{e(ex)}
")
from .attrs import normalize
layers.append(("template", normalize(d.get("attributes"))))
folded = fold(layers)
members = "".join(f"
{e(m['pack'])}{' @' + e(m['target']) if m.get('target') else ''}"
f"{' (after ' + e(', '.join(m['after'])) + ')' if m.get('after') else ''}
" for m in d["runbooks"])
out.append(f"
Runbooks
{members}
")
fields = ""
for name, a in folded.items():
req = a["requirement"] or "optional"
val = "" if a["value"] is None else a["value"]
note = f" - {e(a['note'])}" if a.get("note") else ""
fields += (f"
"
f"{note}{' from ' + e(a['source']) if a['source'] else ''}
")
envs = "".join(f"" for x in R.ENVIRONMENTS)
out.append("
Start a project from this template
"
f"")
return page(d["title"], "".join(out))
# ---- actions ------------------------------------------------------------------------------------------------------------------
def create_from_template(app, tid: str, form: dict) -> str:
"""Instantiate a template; returns the new project id."""
reg = app.registry()
doc = reg.templates.get(tid)
if doc is None or not doc.ok:
raise ValueError(f"no valid template {tid}")
layers = []
for s in doc.data.get("attribute_sets", []):
layers += set_layers(reg, s)
from .attrs import normalize
layers.append(("template", normalize(doc.data.get("attributes"))))
base = fold(layers)
values = {}
for k, v in form.items():
if not k.startswith("a_"):
continue
name, v = k[2:], v.strip()
if v and str(base.get(name, {}).get("value")) != v:
values[name] = v
from .pack import SECRET_RE
for name, v in values.items():
if SECRET_RE.search(v):
raise ValueError(f"{name}: value looks like a secret; attributes are never secret")
pid = form.get("project_id", "").strip()
if len(form.get("title", "").strip()) < 3:
raise ValueError("give the project a name of at least 3 characters")
J.instantiate(reg, tid, pid, form.get("environment", ""), values, title=form.get("title", ""),
root=doc.path.parent.parent, description=form.get("description", ""), goal=form.get("goal", ""))
reg = app.registry()
created = reg.projects[pid]
errs = J.validate(reg, created)
if errs:
created.path.unlink()
created.path.parent.rmdir()
raise ValueError(f"project would be invalid: {errs[0]}")
return pid