@monster-orange/den 0.2.8 → 0.3.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/CHANGELOG.md +1547 -0
- package/README.md +4 -2
- package/bin/den +2 -1
- package/docs/distributions.md +507 -4
- package/make/den.mk +19 -4
- package/make/journeys.mk +2 -1
- package/make/local_dev.mk +6 -13
- package/make/secrets-1password.mk +18 -0
- package/make/test.mk +1 -1
- package/package.json +9 -2
- package/schemas/den.plugin.v1.json +156 -0
- package/schemas/den.theme.v1.json +269 -0
- package/scripts/auth-configure.ts +16 -8
- package/scripts/heal-config.ts +0 -5
- package/scripts/setup-1password.ts +41 -19
- package/scripts/setup-secrets.ts +67 -101
- package/src/pillars/api/global-setup.ts +1 -1
- package/src/pillars/journeys/sandbox.ts +23 -18
- package/src/pillars/journeys/tools/browser-session.ts +2 -2
- package/src/scripts/setup-default-workspace.ts +2 -2
- package/src/shared/auth/headless-login.ts +1 -1
- package/src/shared/auth/sudo-vault.ts +93 -31
- package/src/shared/auth/token-providers/index.ts +7 -3
- package/src/shared/brand.ts +7 -0
- package/src/shared/config-error.ts +33 -3
- package/src/shared/constants.ts +3 -24
- package/src/shared/den-config.ts +141 -5
- package/src/shared/edge/config.ts +147 -21
- package/src/shared/edge/exec.ts +31 -0
- package/src/shared/edge/included.ts +169 -0
- package/src/shared/edge/render.ts +69 -14
- package/src/shared/edge/runtime.ts +128 -121
- package/src/shared/edge/status.ts +68 -8
- package/src/shared/edge/tls.ts +328 -0
- package/src/shared/edge/types.ts +25 -9
- package/src/shared/environment-manager/default-workspace.ts +16 -1
- package/src/shared/environment-manager/health.ts +73 -38
- package/src/shared/environment-manager/scope.ts +168 -0
- package/src/shared/environment-manager/service-runner.ts +789 -43
- package/src/shared/environment-manager/up-targets.ts +132 -0
- package/src/shared/health/rollup.ts +48 -0
- package/src/shared/open-url.ts +21 -2
- package/src/shared/org-sources.ts +78 -59
- package/src/shared/org.ts +53 -5
- package/src/shared/plugins/manifest.ts +406 -0
- package/src/shared/plugins/store.ts +358 -0
- package/src/shared/process/legacy-adopt.ts +285 -0
- package/src/shared/process/process-group.ts +202 -0
- package/src/shared/process/spawn-streaming.ts +7 -0
- package/src/shared/secrets/doppler.ts +1 -1
- package/src/shared/secrets/index.ts +279 -50
- package/src/shared/secrets/none.ts +56 -0
- package/src/shared/secrets/strategies.ts +202 -0
- package/src/shared/secrets/types.ts +1 -1
- package/src/shared/smoke/git-remote.ts +40 -0
- package/src/shared/stacks/manifest-commands.ts +1 -1
- package/src/shared/stacks/namespace.ts +178 -0
- package/src/shared/stacks/owners.ts +303 -0
- package/src/shared/stacks/render.ts +8 -3
- package/src/shared/stacks/runtime.ts +100 -16
- package/src/shared/stacks/status.ts +63 -12
- package/src/shared/strategies/registry.ts +89 -0
- package/src/shared/theme/contract.ts +538 -0
- package/src/shared/types/index.ts +12 -3
- package/src/ui/cli/create.ts +24 -7
- package/src/ui/cli/doctor-command.ts +131 -0
- package/src/ui/cli/edge-command.ts +56 -10
- package/src/ui/cli/flag-parser.ts +7 -2
- package/src/ui/cli/health-display.ts +25 -0
- package/src/ui/cli/index.ts +548 -60
- package/src/ui/cli/notice.ts +20 -0
- package/src/ui/cli/plugin-command.ts +849 -0
- package/src/ui/cli/stack-command.ts +63 -2
- package/src/ui/cli/test-runner.ts +11 -9
- package/src/ui/cli/theme-command.ts +136 -0
- package/src/ui/cli/web-command.ts +47 -23
- package/src/ui/mcp/api.ts +28 -2
- package/src/ui/mcp/scope.ts +22 -1
- package/src/ui/mcp/tools.ts +50 -7
- package/src/ui/server/appearance-prefs.ts +95 -0
- package/src/ui/server/daemon-reaper.ts +29 -1
- package/src/ui/server/engine-stamp.ts +83 -0
- package/src/ui/server/github-service.ts +23 -44
- package/src/ui/server/graph-page.ts +20 -0
- package/src/ui/server/graph-prefs.ts +44 -1
- package/src/ui/server/http-errors.ts +293 -0
- package/src/ui/server/http-helpers.ts +22 -3
- package/src/ui/server/index.ts +532 -51
- package/src/ui/server/public/appearance.js +531 -0
- package/src/ui/server/public/edge-page.js +248 -0
- package/src/ui/server/public/edge.html +251 -0
- package/src/ui/server/public/environments.html +65 -83
- package/src/ui/server/public/error-page.js +247 -0
- package/src/ui/server/public/error.html +260 -0
- package/src/ui/server/public/graph-model.js +332 -14
- package/src/ui/server/public/graph.css +141 -47
- package/src/ui/server/public/graph.js +223 -44
- package/src/ui/server/public/header.js +396 -600
- package/src/ui/server/public/health-fit.js +248 -0
- package/src/ui/server/public/health-rollup.js +128 -0
- package/src/ui/server/public/i18n/es.js +287 -0
- package/src/ui/server/public/i18n/ja.js +276 -0
- package/src/ui/server/public/index.html +139 -438
- package/src/ui/server/public/local-dev.html +3468 -4044
- package/src/ui/server/public/logs.html +71 -208
- package/src/ui/server/public/panel.js +14 -2
- package/src/ui/server/public/release.html +175 -389
- package/src/ui/server/public/services.html +596 -770
- package/src/ui/server/public/settings-page.js +759 -0
- package/src/ui/server/public/settings-registry.js +521 -0
- package/src/ui/server/public/settings.html +590 -0
- package/src/ui/server/public/shell.js +3869 -0
- package/src/ui/server/public/topbar.js +1413 -0
- package/src/ui/server/public/version-label.js +12 -0
- package/src/ui/server/public/workspace-picker.js +74 -34
- package/src/ui/server/readonly-routes.ts +12 -1
- package/src/ui/server/services-list.ts +94 -17
- package/src/ui/server/settings-distribution.ts +74 -0
- package/src/ui/server/shell-prefs.ts +192 -0
- package/src/ui/server/theme-routes.ts +292 -0
- package/src/ui/server/theme-runtime.ts +407 -0
- package/src/ui/server/ui-assets.ts +16 -1
- package/src/ui/server/ui-language.ts +50 -0
- package/src/ui/server/workspace-routes.ts +45 -3
- package/themes/den/den-theme.json +72 -0
- package/themes/den/favicon.ico +0 -0
- package/themes/den/mark.svg +6 -0
|
@@ -0,0 +1,131 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* `doctor`: every strategy kind in the registry (secrets, tls), the
|
|
3
|
+
* strategy this distribution selects for it, whether nothing chose it
|
|
4
|
+
* (implicit), and its detect() result with the reason.
|
|
5
|
+
*
|
|
6
|
+
* Read-only: detect() is a PATH check, so doctor never spawns a backend
|
|
7
|
+
* CLI, never prompts, and never prints a secret value. Exit 1 when a
|
|
8
|
+
* strategy that will be used is unavailable or its config is invalid.
|
|
9
|
+
*/
|
|
10
|
+
import chalk from "chalk";
|
|
11
|
+
import { readBrand } from "../../shared/brand.js";
|
|
12
|
+
import { hasEdgeBlock, readEdgeConfig } from "../../shared/edge/config.js";
|
|
13
|
+
import { edgeTlsWarnings, tlsStrategyFor } from "../../shared/edge/tls.js";
|
|
14
|
+
import {
|
|
15
|
+
readSecretsConfig,
|
|
16
|
+
secretsStatus,
|
|
17
|
+
} from "../../shared/secrets/index.js";
|
|
18
|
+
import { strategyKinds } from "../../shared/strategies/registry.js";
|
|
19
|
+
|
|
20
|
+
export interface DoctorRow {
|
|
21
|
+
kind: string;
|
|
22
|
+
/** The selected strategy id ("" when the config could not be read). */
|
|
23
|
+
selected: string;
|
|
24
|
+
implicit: boolean;
|
|
25
|
+
available: boolean;
|
|
26
|
+
reason?: string;
|
|
27
|
+
/** True when it will be used and cannot work: doctor exits 1. */
|
|
28
|
+
problem: boolean;
|
|
29
|
+
warnings: string[];
|
|
30
|
+
}
|
|
31
|
+
|
|
32
|
+
type Selector = (env: NodeJS.ProcessEnv) => DoctorRow;
|
|
33
|
+
|
|
34
|
+
const SELECTORS: Record<string, Selector> = {
|
|
35
|
+
secrets(env) {
|
|
36
|
+
const st = secretsStatus(readSecretsConfig(), env);
|
|
37
|
+
return {
|
|
38
|
+
kind: "secrets",
|
|
39
|
+
selected: st.backend,
|
|
40
|
+
implicit: st.implicit,
|
|
41
|
+
available: st.detect.available,
|
|
42
|
+
reason: st.detect.reason,
|
|
43
|
+
// The implicit default is used only when a secret is asked for.
|
|
44
|
+
problem: !st.detect.available && !st.implicit,
|
|
45
|
+
warnings: [],
|
|
46
|
+
};
|
|
47
|
+
},
|
|
48
|
+
tls(env) {
|
|
49
|
+
if (!hasEdgeBlock())
|
|
50
|
+
return {
|
|
51
|
+
kind: "tls",
|
|
52
|
+
selected: "none",
|
|
53
|
+
implicit: false,
|
|
54
|
+
available: true,
|
|
55
|
+
reason: "no edge: block",
|
|
56
|
+
problem: false,
|
|
57
|
+
warnings: [],
|
|
58
|
+
};
|
|
59
|
+
try {
|
|
60
|
+
const cfg = readEdgeConfig();
|
|
61
|
+
const d = tlsStrategyFor(cfg).detect(env);
|
|
62
|
+
return {
|
|
63
|
+
kind: "tls",
|
|
64
|
+
selected: cfg.tls.strategy,
|
|
65
|
+
implicit: cfg.tlsFrom === "implicit",
|
|
66
|
+
available: d.available,
|
|
67
|
+
reason: d.reason,
|
|
68
|
+
problem: !d.available,
|
|
69
|
+
warnings: edgeTlsWarnings(cfg),
|
|
70
|
+
};
|
|
71
|
+
} catch (err) {
|
|
72
|
+
return {
|
|
73
|
+
kind: "tls",
|
|
74
|
+
selected: "",
|
|
75
|
+
implicit: false,
|
|
76
|
+
available: false,
|
|
77
|
+
reason: (err as Error).message,
|
|
78
|
+
problem: true,
|
|
79
|
+
warnings: [],
|
|
80
|
+
};
|
|
81
|
+
}
|
|
82
|
+
},
|
|
83
|
+
};
|
|
84
|
+
|
|
85
|
+
/**
|
|
86
|
+
* One row per registered kind (secrets, then tls, then any other kind in
|
|
87
|
+
* registration order). Never spawns, never throws.
|
|
88
|
+
*/
|
|
89
|
+
export function doctorRows(env: NodeJS.ProcessEnv = process.env): DoctorRow[] {
|
|
90
|
+
const kinds = strategyKinds();
|
|
91
|
+
const ordered = [
|
|
92
|
+
...Object.keys(SELECTORS).filter((k) => kinds.includes(k)),
|
|
93
|
+
...kinds.filter((k) => !(k in SELECTORS)),
|
|
94
|
+
];
|
|
95
|
+
return ordered.map(
|
|
96
|
+
(kind) =>
|
|
97
|
+
SELECTORS[kind]?.(env) ?? {
|
|
98
|
+
kind,
|
|
99
|
+
selected: "",
|
|
100
|
+
implicit: false,
|
|
101
|
+
available: false,
|
|
102
|
+
reason: "no selector for this kind",
|
|
103
|
+
problem: false,
|
|
104
|
+
warnings: [],
|
|
105
|
+
},
|
|
106
|
+
);
|
|
107
|
+
}
|
|
108
|
+
|
|
109
|
+
export function formatDoctor(rows: DoctorRow[], title: string): string[] {
|
|
110
|
+
const lines = [chalk.bold(`${title} doctor: strategies`), ""];
|
|
111
|
+
const width = Math.max(...rows.map((r) => r.kind.length));
|
|
112
|
+
for (const r of rows) {
|
|
113
|
+
const sel = r.selected
|
|
114
|
+
? `${r.selected}${r.implicit ? " (implicit)" : ""}`
|
|
115
|
+
: "(invalid config)";
|
|
116
|
+
const state = r.available
|
|
117
|
+
? chalk.green("✓ available")
|
|
118
|
+
: (r.problem ? chalk.red : chalk.yellow)("✗ unavailable");
|
|
119
|
+
lines.push(
|
|
120
|
+
` ${r.kind.padEnd(width)} ${sel.padEnd(20)} ${state}${r.reason ? `: ${r.reason}` : ""}`,
|
|
121
|
+
);
|
|
122
|
+
for (const w of r.warnings) lines.push(chalk.yellow(` ⚠ ${w}`));
|
|
123
|
+
}
|
|
124
|
+
return lines;
|
|
125
|
+
}
|
|
126
|
+
|
|
127
|
+
export function cmdDoctor(_args: string[]): number {
|
|
128
|
+
const rows = doctorRows();
|
|
129
|
+
for (const l of formatDoctor(rows, readBrand().name)) console.log(l);
|
|
130
|
+
return rows.some((r) => r.problem) ? 1 : 0;
|
|
131
|
+
}
|
|
@@ -1,7 +1,11 @@
|
|
|
1
1
|
/**
|
|
2
|
-
* `edge up | down | status | trust | untrust | routes`
|
|
2
|
+
* `edge up [--take-over] | down | status | trust | untrust | routes`
|
|
3
3
|
*
|
|
4
|
-
* This org's
|
|
4
|
+
* This org's edge: routes at `<name>.<edge.domain>`, TLS as `edge.tls` says
|
|
5
|
+
* (none, mkcert or files). `trust` / `untrust` exist only under mkcert.
|
|
6
|
+
* `routes` lists Den's own routes, then the routers in the included
|
|
7
|
+
* dynamic files, each with its source file, then the stacks' routers
|
|
8
|
+
* (`stack-*.yml`, 0.3.0-dev.32), each marked "stack" with its file.
|
|
5
9
|
*/
|
|
6
10
|
import chalk from "chalk";
|
|
7
11
|
import { readBrand } from "../../shared/brand.js";
|
|
@@ -15,18 +19,37 @@ import {
|
|
|
15
19
|
trustCa,
|
|
16
20
|
untrustCa,
|
|
17
21
|
} from "../../shared/edge/runtime.js";
|
|
18
|
-
import {
|
|
22
|
+
import {
|
|
23
|
+
includedRouters,
|
|
24
|
+
stackRouters,
|
|
25
|
+
routerUrls,
|
|
26
|
+
type IncludedRouter,
|
|
27
|
+
} from "../../shared/edge/included.js";
|
|
28
|
+
import { edgeRoutes, edgeStatus } from "../../shared/edge/status.js";
|
|
29
|
+
import { tlsYaml } from "../../shared/edge/tls.js";
|
|
30
|
+
import { rejoinRunningStacks } from "../../shared/stacks/status.js";
|
|
31
|
+
|
|
32
|
+
/** An included router's URLs (the hosts its rule names), else its name. */
|
|
33
|
+
function includedUrl(r: IncludedRouter): string {
|
|
34
|
+
return r.hosts.length ? routerUrls(r).join(", ") : r.name;
|
|
35
|
+
}
|
|
19
36
|
|
|
20
37
|
export async function cmdEdge(args: string[]): Promise<void> {
|
|
21
38
|
const sub = args[0] ?? "status";
|
|
22
39
|
const cfg = readEdgeConfig();
|
|
23
40
|
const n = edgeNames(cfg.org);
|
|
24
41
|
const published = cfg.ports
|
|
25
|
-
?
|
|
42
|
+
? cfg.tls.strategy === "none"
|
|
43
|
+
? `host :${cfg.ports.http} (http only)`
|
|
44
|
+
: `host :${cfg.ports.http} / :${cfg.ports.https}`
|
|
26
45
|
: `not published (reach it on Docker network ${n.network})`;
|
|
27
46
|
switch (sub) {
|
|
28
47
|
case "up": {
|
|
29
|
-
|
|
48
|
+
// --take-over: the only way to replace an edge another state dir
|
|
49
|
+
// owns (0.3.0-dev.40); never implied.
|
|
50
|
+
const { certs } = await edgeUp(cfg, {
|
|
51
|
+
takeOver: args.includes("--take-over"),
|
|
52
|
+
});
|
|
30
53
|
console.log(
|
|
31
54
|
chalk.green(
|
|
32
55
|
`✓ ${n.container} up — ${edgeDomains(cfg)
|
|
@@ -37,7 +60,7 @@ export async function cmdEdge(args: string[]): Promise<void> {
|
|
|
37
60
|
console.log(` ports ${published}`);
|
|
38
61
|
if (certs.issued)
|
|
39
62
|
console.log(` cert issued for ${certs.names.join(", ")}`);
|
|
40
|
-
if (caTrusted(cfg) === false)
|
|
63
|
+
if (cfg.tls.strategy === "mkcert" && caTrusted(cfg) === false)
|
|
41
64
|
console.log(
|
|
42
65
|
chalk.yellow(
|
|
43
66
|
" CA not trusted yet — run `edge trust` (asks for your password once)",
|
|
@@ -45,6 +68,8 @@ export async function cmdEdge(args: string[]): Promise<void> {
|
|
|
45
68
|
);
|
|
46
69
|
for (const name of ["edge", ...Object.keys(cfg.routes)])
|
|
47
70
|
console.log(` ${routeUrl(name, cfg)}`);
|
|
71
|
+
// Running stacks' routes stay up: the edge rejoins their networks.
|
|
72
|
+
rejoinRunningStacks();
|
|
48
73
|
return;
|
|
49
74
|
}
|
|
50
75
|
case "down":
|
|
@@ -57,10 +82,25 @@ export async function cmdEdge(args: string[]): Promise<void> {
|
|
|
57
82
|
case "untrust":
|
|
58
83
|
process.exitCode = untrustCa(cfg);
|
|
59
84
|
return;
|
|
60
|
-
case "routes":
|
|
61
|
-
for (const r of
|
|
85
|
+
case "routes": {
|
|
86
|
+
for (const r of edgeRoutes(cfg))
|
|
62
87
|
console.log(`${r.url.padEnd(52)} → ${r.upstream}`);
|
|
88
|
+
// Routers in the included dynamic files: Traefik serves them too.
|
|
89
|
+
const inc = includedRouters(cfg);
|
|
90
|
+
for (const r of inc.routers)
|
|
91
|
+
console.log(
|
|
92
|
+
`${includedUrl(r).padEnd(52)} → ${r.upstream || "?"} (${r.file})`,
|
|
93
|
+
);
|
|
94
|
+
// Routers the stacks wrote (stack up): neither Den's nor included.
|
|
95
|
+
const stk = stackRouters(cfg);
|
|
96
|
+
for (const r of stk.routers)
|
|
97
|
+
console.log(
|
|
98
|
+
`${includedUrl(r).padEnd(52)} → ${r.upstream || "?"} (stack: ${r.file})`,
|
|
99
|
+
);
|
|
100
|
+
for (const e of [...inc.errors, ...stk.errors])
|
|
101
|
+
console.log(chalk.yellow(`${e.file}: not read (${e.error})`));
|
|
63
102
|
return;
|
|
103
|
+
}
|
|
64
104
|
case "status": {
|
|
65
105
|
const st = edgeStatus(cfg);
|
|
66
106
|
console.log(chalk.bold(`${readBrand().name} edge — ${st.org}`));
|
|
@@ -68,14 +108,20 @@ export async function cmdEdge(args: string[]): Promise<void> {
|
|
|
68
108
|
console.log(` ports ${published}`);
|
|
69
109
|
console.log(` domains ${st.domains.join(", ")} (+ every subdomain)`);
|
|
70
110
|
console.log(
|
|
71
|
-
`
|
|
111
|
+
` tls ${tlsYaml(cfg.tls).replace(/^tls: /, "")}${cfg.tlsFrom === "implicit" ? " (implicit: edge.tls is not set)" : ""}`,
|
|
72
112
|
);
|
|
113
|
+
if (cfg.tls.strategy !== "none")
|
|
114
|
+
console.log(
|
|
115
|
+
// Fronted (edge.publish: false): the proxy in front owns
|
|
116
|
+
// browser trust (BOOT-1), so this machine's trust is moot.
|
|
117
|
+
` CA ${caDir(cfg)} — ${!st.ports ? "not needed: fronted" : st.caTrusted === null ? "unknown" : st.caTrusted ? "trusted" : "NOT trusted"}`,
|
|
118
|
+
);
|
|
73
119
|
if (st.certIssued) console.log(` cert ${st.certNames.join(", ")}`);
|
|
74
120
|
return;
|
|
75
121
|
}
|
|
76
122
|
default:
|
|
77
123
|
throw new Error(
|
|
78
|
-
`Unknown edge command "${sub}". Use: up | down | status | trust | untrust | routes`,
|
|
124
|
+
`Unknown edge command "${sub}". Use: up [--take-over] | down | status | trust | untrust | routes`,
|
|
79
125
|
);
|
|
80
126
|
}
|
|
81
127
|
}
|
|
@@ -12,6 +12,7 @@ export type CommandName =
|
|
|
12
12
|
| "services"
|
|
13
13
|
| "graph"
|
|
14
14
|
| "register"
|
|
15
|
+
| "relocate"
|
|
15
16
|
| "use-local"
|
|
16
17
|
| "use-managed"
|
|
17
18
|
| "setup"
|
|
@@ -63,9 +64,10 @@ const WORKSPACE_READ_COMMANDS = new Set([
|
|
|
63
64
|
|
|
64
65
|
/**
|
|
65
66
|
* Commands that edit one workspace's entries without changing what runs:
|
|
66
|
-
* `-w` picks the workspace to write,
|
|
67
|
+
* `-w` picks the workspace to write, whether or not it is active, and leaves
|
|
68
|
+
* focus alone.
|
|
67
69
|
*/
|
|
68
|
-
const WORKSPACE_EDIT_COMMANDS = new Set(["register"]);
|
|
70
|
+
const WORKSPACE_EDIT_COMMANDS = new Set(["register", "relocate"]);
|
|
69
71
|
|
|
70
72
|
const WORKSPACE_COMMANDS = new Set([
|
|
71
73
|
...WORKSPACE_MUTATION_COMMANDS,
|
|
@@ -196,6 +198,9 @@ const COMMAND_FLAGS: Record<string, readonly string[]> = {
|
|
|
196
198
|
logs: [],
|
|
197
199
|
setup: ["-f", "--force", "--plan", "-w", "--workspace", "--all-active"],
|
|
198
200
|
sync: [],
|
|
201
|
+
// The only override for an edge another state dir owns; a typo'd one
|
|
202
|
+
// must not be ignored.
|
|
203
|
+
edge: ["--take-over"],
|
|
199
204
|
};
|
|
200
205
|
|
|
201
206
|
/**
|
|
@@ -2,6 +2,7 @@ import chalk from "chalk";
|
|
|
2
2
|
import type { ServiceHealthResult } from "../../shared/environment-manager/health.js";
|
|
3
3
|
|
|
4
4
|
import { product } from "../../shared/product.js";
|
|
5
|
+
import type { RepoRollup } from "../../shared/health/rollup.js";
|
|
5
6
|
type HealthState = ServiceHealthResult["health"];
|
|
6
7
|
type HealthReasonInput = Pick<ServiceHealthResult, "health" | "healthDetail">;
|
|
7
8
|
|
|
@@ -62,3 +63,27 @@ export function formatHealthReason(result: HealthReasonInput): string {
|
|
|
62
63
|
const reason = healthReasonText(result);
|
|
63
64
|
return reason ? chalk.gray(reason.slice(0, 48)) : "";
|
|
64
65
|
}
|
|
66
|
+
|
|
67
|
+
/**
|
|
68
|
+
* A repo row's rolled-up state (rollupRepoHealth), the one the web UI's
|
|
69
|
+
* header and graph show: "● down", "● degraded", "● ok", "● unknown".
|
|
70
|
+
*/
|
|
71
|
+
export function formatRollupLabel(r: RepoRollup): string {
|
|
72
|
+
if (r.state === "down") return chalk.red("● down");
|
|
73
|
+
if (r.state === "degraded") return chalk.yellow("● degraded");
|
|
74
|
+
if (r.state === "ok") return chalk.green("● ok");
|
|
75
|
+
return chalk.gray("● unknown");
|
|
76
|
+
}
|
|
77
|
+
|
|
78
|
+
/**
|
|
79
|
+
* Why, for a repo row: the down services ("mailpit down"), and how many
|
|
80
|
+
* services aren't checked ("25 not checked"). Unchecked never means down.
|
|
81
|
+
*/
|
|
82
|
+
export function rollupReasonText(r: RepoRollup): string {
|
|
83
|
+
return [
|
|
84
|
+
r.down.length ? `${r.down.join(", ")} down` : "",
|
|
85
|
+
r.unchecked ? `${r.unchecked} not checked` : "",
|
|
86
|
+
]
|
|
87
|
+
.filter(Boolean)
|
|
88
|
+
.join("; ");
|
|
89
|
+
}
|