rvlib-shipkit 1.1.1 → 1.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 +130 -0
- package/README.md +1 -1
- package/package.json +3 -1
- package/src/assets/run-assets.ts +3 -8
- package/src/check.ts +40 -2
- package/src/checks/SK001-package-json-shape.check.ts +14 -3
- package/src/checks/SK002-contracts-fulfillness.check.ts +3 -3
- package/src/checks/SK004-gitignore-policy.check.ts +11 -3
- package/src/checks/SK005-rvlib-versions.check.ts +13 -1
- package/src/checks/SK006-npm-publish-safety.check.ts +5 -0
- package/src/checks/SK007-github-visibility.check.ts +9 -9
- package/src/checks/SK008-reviewer-agent.check.ts +14 -8
- package/src/checks/SK009-commit-ai-tells.check.ts +29 -5
- package/src/checks/SK013-one-way-in-policy.check.ts +10 -2
- package/src/checks/SK015-nested-repo.check.ts +5 -1
- package/src/checks/SK017-vscode-hub-task.check.ts +7 -0
- package/src/checks/SK022-machine-runs-the-pin.check.ts +47 -0
- package/src/checks/SK024-identity-colors.check.ts +54 -0
- package/src/checks/SK025-release-drift.check.ts +58 -0
- package/src/checks/SK026-asks-autoclose.check.ts +44 -0
- package/src/checks/SK027-agent-wiki-links.check.ts +28 -0
- package/src/checks/SK028-readme.check.ts +79 -0
- package/src/cli/cmd_Assets.ts +1 -1
- package/src/cli/cmd_Check.ts +26 -1
- package/src/cli/cmd_Ci.ts +23 -5
- package/src/cli/cmd_CrossRepoActivity.ts +116 -0
- package/src/cli/cmd_GlobalProcesses.ts +155 -0
- package/src/cli/cmd_Home.ts +170 -0
- package/src/cli/cmd_Keys.ts +17 -3
- package/src/cli/cmd_Overview.ts +98 -0
- package/src/cli/cmd_Planned.ts +117 -0
- package/src/cli.ts +30 -2
- package/src/collections/checks.ts +3 -2
- package/src/collections/providers.ts +1 -1
- package/src/collections/test-time.ts +10 -1
- package/src/comfy/comfy-panel.ts +1 -16
- package/src/core/agent-spawn.ts +46 -0
- package/src/core/archived.ts +82 -0
- package/src/core/attach.ts +14 -9
- package/src/core/canonical-scripts.ts +12 -3
- package/src/core/check-file-names.ts +8 -0
- package/src/core/check-runner.ts +80 -30
- package/src/core/discover-checks.ts +38 -18
- package/src/core/file-stamp.ts +20 -0
- package/src/{checks/SK021-github-login.check.ts → core/gh-login.ts} +45 -54
- package/src/core/identity-color.ts +203 -0
- package/src/core/identity.ts +91 -0
- package/src/core/job-follow.ts +102 -0
- package/src/core/job-run.ts +27 -0
- package/src/core/jobs.ts +87 -27
- package/src/core/legacy-shapes.ts +1 -14
- package/src/core/machine-db.ts +83 -0
- package/src/core/one-lib.ts +85 -0
- package/src/core/port-holders.ts +43 -0
- package/src/core/repo-facts.ts +14 -0
- package/src/core/retention.ts +222 -0
- package/src/core/retired-checks.ts +7 -0
- package/src/core/run-check.ts +12 -4
- package/src/core/run-ci.ts +46 -12
- package/src/core/run-fix.ts +7 -2
- package/src/core/run-fixers.ts +10 -6
- package/src/core/slots.ts +5 -0
- package/src/core/statement-end.ts +17 -0
- package/src/core/wire.ts +11 -0
- package/src/fleet/scan-fleet.ts +2 -1
- package/src/home/activity-shape.ts +135 -0
- package/src/home/activity.ts +249 -0
- package/src/home/attach.ts +95 -0
- package/src/home/autostart.ts +108 -0
- package/src/home/global-units.ts +87 -0
- package/src/home/gui-app/ShipkitGui.swift +159 -0
- package/src/home/gui-app.ts +99 -0
- package/src/home/handoff.ts +51 -0
- package/src/home/home-launchd.ts +104 -0
- package/src/home/home-paths.ts +88 -0
- package/src/home/home-routes.ts +235 -0
- package/src/home/home-start.ts +105 -0
- package/src/home/home-state.ts +43 -0
- package/src/home/home-words.ts +12 -0
- package/src/home/hubs.ts +286 -0
- package/src/home/lib-watch.ts +139 -0
- package/src/home/machine-stores.ts +115 -0
- package/src/home/open-app.ts +16 -0
- package/src/home/pm-config.ts +37 -0
- package/src/home/pm.ts +174 -0
- package/src/home/process-rows.ts +205 -0
- package/src/home/resources.ts +365 -0
- package/src/home/run-daemon.ts +137 -0
- package/src/home/run-pm.ts +150 -0
- package/src/home/run-unit-action.ts +25 -0
- package/src/home/run-web.ts +37 -0
- package/src/home/serve-daemon.ts +31 -0
- package/src/home/serve-web.ts +175 -0
- package/src/home/supervise.ts +361 -0
- package/src/home/via-hub.ts +142 -0
- package/src/home/widget-text.ts +119 -0
- package/src/home/widgets.ts +102 -0
- package/src/hooks/run-hooks.ts +20 -3
- package/src/ledger/check-stats.ts +19 -45
- package/src/ledger/db.ts +69 -17
- package/src/ledger/gate-checks.ts +22 -3
- package/src/ledger/git-scan.ts +31 -7
- package/src/ledger/house-runs.ts +43 -0
- package/src/ledger/ledger.ts +40 -14
- package/src/ledger/live-timeline.ts +4 -19
- package/src/ledger/run-log.ts +92 -28
- package/src/ledger/store.ts +245 -103
- package/src/ledger/world-cache.ts +64 -15
- package/src/plan/cadence.ts +253 -0
- package/src/plan/catch-up.ts +37 -0
- package/src/plan/plan-timeline.ts +99 -0
- package/src/plan/planner.ts +428 -0
- package/src/plan/repo-source.ts +212 -0
- package/src/problems/problem-line.ts +1 -1
- package/src/release/run-release.ts +9 -2
- package/src/release/unit-resolve.ts +8 -12
- package/src/repo-config.ts +61 -4
- package/src/serve/house-runs.ts +53 -0
- package/src/serve/hub-app/activity-st.ts +168 -0
- package/src/serve/hub-app/activity-view.tsx +218 -0
- package/src/serve/hub-app/activity.css +78 -0
- package/src/serve/hub-app/admin-svc.ts +41 -0
- package/src/serve/hub-app/admin-view.tsx +42 -0
- package/src/serve/hub-app/chart-view.ts +12 -5
- package/src/serve/hub-app/check-view.tsx +9 -9
- package/src/serve/hub-app/check-words.ts +3 -1
- package/src/serve/hub-app/checks-svc.ts +12 -8
- package/src/serve/hub-app/col-card.tsx +4 -4
- package/src/serve/hub-app/cols-svc.ts +92 -13
- package/src/serve/hub-app/comfy-svc.ts +12 -6
- package/src/serve/hub-app/comfy-view.tsx +3 -3
- package/src/serve/hub-app/controls.css +32 -0
- package/src/serve/hub-app/cycle-button.tsx +36 -0
- package/src/serve/hub-app/dock-anim.ts +25 -0
- package/src/serve/hub-app/dock-st.ts +329 -0
- package/src/serve/hub-app/dock-view.tsx +68 -0
- package/src/serve/hub-app/dock.css +35 -0
- package/src/serve/hub-app/floating.tsx +49 -0
- package/src/serve/hub-app/geometry-view.tsx +4 -4
- package/src/serve/hub-app/hash-route.ts +8 -2
- package/src/serve/hub-app/header-bar.tsx +102 -95
- package/src/serve/hub-app/health-svc.ts +3 -3
- package/src/serve/hub-app/help.tsx +10 -7
- package/src/serve/hub-app/home-st.ts +278 -0
- package/src/serve/hub-app/home-strip.css +113 -0
- package/src/serve/hub-app/home-strip.tsx +376 -0
- package/src/serve/hub-app/home.html +21 -0
- package/src/serve/hub-app/hub-app.tsx +221 -177
- package/src/serve/hub-app/hub-types.ts +0 -3
- package/src/serve/hub-app/hub.css +112 -52
- package/src/serve/hub-app/icons.tsx +34 -0
- package/src/serve/hub-app/index.html +7 -1
- package/src/serve/hub-app/keys.ts +60 -0
- package/src/serve/hub-app/layout-st.ts +55 -0
- package/src/serve/hub-app/live-svc.ts +35 -72
- package/src/serve/hub-app/machine-st.ts +333 -0
- package/src/serve/hub-app/md.ts +38 -5
- package/src/serve/hub-app/media-svc.ts +63 -44
- package/src/serve/hub-app/media-view.tsx +20 -20
- package/src/serve/hub-app/next-run.ts +24 -30
- package/src/serve/hub-app/palette-st.ts +92 -0
- package/src/serve/hub-app/palette-view.tsx +47 -0
- package/src/serve/hub-app/pill-row.tsx +65 -0
- package/src/serve/hub-app/planned-st.ts +107 -0
- package/src/serve/hub-app/planned-view.tsx +114 -0
- package/src/serve/hub-app/planned.css +42 -0
- package/src/serve/hub-app/processes-svc.ts +63 -0
- package/src/serve/hub-app/project-list.css +119 -0
- package/src/serve/hub-app/project-list.tsx +266 -0
- package/src/serve/hub-app/projects-svc.ts +253 -23
- package/src/serve/hub-app/prose-hits-st.ts +3 -3
- package/src/serve/hub-app/prose-hits-view.tsx +6 -6
- package/src/serve/hub-app/readme-svc.ts +38 -0
- package/src/serve/hub-app/readme-view.tsx +19 -0
- package/src/serve/hub-app/readme.css +11 -0
- package/src/serve/hub-app/repo-sections.tsx +61 -0
- package/src/serve/hub-app/repo-st.ts +340 -0
- package/src/serve/hub-app/runs-tabs.ts +1 -1
- package/src/serve/hub-app/screen-cache.ts +74 -0
- package/src/serve/hub-app/sidebar.tsx +84 -0
- package/src/serve/hub-app/test-view.tsx +4 -4
- package/src/serve/hub-app/tests-svc.ts +3 -3
- package/src/serve/hub-app/tests-view.tsx +41 -14
- package/src/serve/hub-app/timeline-chart.tsx +55 -39
- package/src/serve/hub-app/timeline-geometry.ts +40 -5
- package/src/serve/hub-app/timeline-svc.ts +7 -6
- package/src/serve/hub-app/timeline-view.tsx +33 -16
- package/src/serve/hub-app/tree-row.tsx +2 -2
- package/src/serve/hub-app/tree-svc.ts +2 -2
- package/src/serve/hub-app/wikis-view.tsx +10 -6
- package/src/serve/hub-runtime.ts +550 -0
- package/src/serve/other-repos.ts +8 -45
- package/src/serve/project-gutter-route.ts +121 -20
- package/src/serve/project-gutter.ts +33 -11
- package/src/serve/readme-route.ts +57 -0
- package/src/serve/repo-processes.ts +89 -0
- package/src/serve/run-serve.ts +110 -402
- package/src/serve/serve-hub.ts +155 -52
- package/src/serve/stale-restart.ts +5 -0
- package/src/serve/{gate-trigger.ts → tracked-watch.ts} +37 -65
- package/src/serve/warm-cell.ts +95 -0
- package/src/shipkit-files.ts +130 -0
- package/src/shipkit.ts +2 -1
- package/src/suite/kept-timings.ts +1 -1
- package/src/wiki/graph-positions.ts +1 -1
- package/src/checks/contracts.check.ts +0 -20
- package/src/serve/health-schedule.ts +0 -102
- package/src/serve/hub-app/hub-st.ts +0 -250
- package/src/serve/hub-app/project-gutter.css +0 -65
- package/src/serve/hub-app/project-gutter.tsx +0 -128
|
@@ -0,0 +1,113 @@
|
|
|
1
|
+
/* the home's strip over a hub page, and the overview. hub.css owns every
|
|
2
|
+
token and the base controls: no colour is written here. */
|
|
3
|
+
.hstrip{display:flex;align-items:center;flex-wrap:nowrap;gap:var(--s2);height:32px;flex:none;padding:0 var(--s3);border-bottom:1px solid var(--line);background:var(--bg-top);overflow:hidden;white-space:nowrap}
|
|
4
|
+
.hstrip .sp{flex:1}
|
|
5
|
+
.hplace{font-size:12px;font-weight:650;letter-spacing:.08em;text-transform:uppercase;color:var(--dim)}
|
|
6
|
+
.hrepo{display:inline-flex;align-items:center;gap:var(--s2);min-width:0;flex:0 1 auto}
|
|
7
|
+
.hrepo img,.hrico{flex:none;width:18px;height:18px;border-radius:4px;object-fit:contain}
|
|
8
|
+
.hrname{font-size:13px;font-weight:650;letter-spacing:.04em;min-width:0;overflow:hidden;text-overflow:ellipsis}
|
|
9
|
+
.hswatch{display:inline-flex;gap:2px}
|
|
10
|
+
.hswatch input{width:16px;height:16px;padding:0;border:1px solid var(--line);border-radius:3px;background:none;cursor:pointer}
|
|
11
|
+
.hswatch input::-webkit-color-swatch-wrapper{padding:0}
|
|
12
|
+
.hswatch input::-webkit-color-swatch{border:none;border-radius:2px}
|
|
13
|
+
.halert{display:inline-flex;align-items:center;font-size:12px;color:var(--errink)}
|
|
14
|
+
.herr{font-size:12px;color:var(--errink);max-width:40ch;overflow:hidden;text-overflow:ellipsis;white-space:nowrap}
|
|
15
|
+
/* the bottom line: its height is reserved whether or not it says anything,
|
|
16
|
+
so the daemon or pm going down never moves the page. The strip renders it,
|
|
17
|
+
its order puts it last in the page column (hub.css .appcol) */
|
|
18
|
+
.hbar{order:1;display:flex;align-items:center;gap:var(--s3);height:22px;flex:none;padding:0 var(--s3);border-top:1px solid var(--line);overflow:hidden;white-space:nowrap;font-size:12px}
|
|
19
|
+
.hdown{min-width:0;overflow:hidden;text-overflow:ellipsis;color:var(--errink)}
|
|
20
|
+
.hdown b{color:var(--red);margin-right:var(--s2)}
|
|
21
|
+
.hdot{display:inline-block;width:8px;height:8px;border-radius:4px;margin-right:var(--s1);background:var(--line)}
|
|
22
|
+
.hdot.up{background:var(--ok)}.hdot.starting,.hdot.stopping{background:var(--warn)}.hdot.crashed{background:var(--red)}
|
|
23
|
+
.hres{display:inline-flex;align-items:center;padding:var(--s0) var(--s2);font-size:12px}
|
|
24
|
+
.hres.down{color:var(--dim)}
|
|
25
|
+
.hproc{display:inline-flex}
|
|
26
|
+
/* a process log on hover, placed by floating-ui (floating.tsx); the pointer
|
|
27
|
+
can move into it to scroll */
|
|
28
|
+
.hpop{z-index:20;margin:0;padding:var(--s2);width:min(80ch,80vw);max-height:40vh;overflow:auto;
|
|
29
|
+
background:var(--panel);border:1px solid var(--line);border-radius:3px;box-shadow:0 6px 20px var(--shadow);font:11px/1.45 ui-monospace,Menlo,monospace;color:var(--dim);white-space:pre-wrap}
|
|
30
|
+
/* each widget card exactly as wide as what it holds; a widget's leading
|
|
31
|
+
<header> joins the title line, dim */
|
|
32
|
+
.hmon{display:flex;flex-wrap:wrap;align-items:flex-start;gap:var(--s2)}
|
|
33
|
+
.hwidget{flex:0 1 auto;max-width:100%;padding:var(--s1) var(--s2);border:1px solid var(--line);border-radius:3px;font-size:12px;line-height:1.5;overflow:auto}
|
|
34
|
+
.hwidget h3{margin:0;font-size:12px;font-weight:650;white-space:nowrap}
|
|
35
|
+
.hwidget h3 .hdim{font-weight:400}
|
|
36
|
+
/* meters in two columns: <table class="wmeters">, label · bar · value, twice a row */
|
|
37
|
+
.wmeters td{padding-right:var(--s1)}
|
|
38
|
+
.wmeters td:nth-child(3n){padding-right:var(--s3)}
|
|
39
|
+
.wmeters td:last-child{padding-right:0}
|
|
40
|
+
.wmeters .wbar{margin-right:0}
|
|
41
|
+
.hwidget table{border-collapse:collapse}
|
|
42
|
+
.hwidget td{padding:0 var(--s3) 0 0;white-space:nowrap;vertical-align:middle}
|
|
43
|
+
.hbad{color:var(--errink)}
|
|
44
|
+
.hwait{flex:1;min-height:0;padding:var(--s4);color:var(--dim);font-size:13px;overflow:auto}
|
|
45
|
+
.hwait p{margin:0 0 var(--s2)}
|
|
46
|
+
.hlog{margin:var(--s2) 0 0;padding:var(--s2);background:var(--panel);border:1px solid var(--line);border-radius:3px;
|
|
47
|
+
font:11px/1.45 ui-monospace,Menlo,monospace;color:var(--dim);white-space:pre-wrap;max-height:60vh;overflow:auto}
|
|
48
|
+
.hall{flex:1;min-height:0;overflow:auto;padding:var(--s4)}
|
|
49
|
+
/* the sections one under the other: the monitor, then the processes */
|
|
50
|
+
.hstack{display:flex;flex-direction:column;gap:var(--s4)}
|
|
51
|
+
.hpico{flex:none;display:inline-flex;align-items:center;justify-content:center;width:16px;height:16px;border-radius:3px;font-size:13px;line-height:1;font-style:normal;object-fit:contain}
|
|
52
|
+
/* the processes: the activity page's table (activity.css actable, acgrp,
|
|
53
|
+
acname, acrepo), a line per process tinted by its state, the group's name
|
|
54
|
+
at the top of its group, the label cut to its narrow column, then one
|
|
55
|
+
column per kind of button so they align across lines. The container query
|
|
56
|
+
drops the url, the version and the pid first, so a phone never scrolls
|
|
57
|
+
sideways */
|
|
58
|
+
.gpwrap{container-type:inline-size;font-size:13px}
|
|
59
|
+
.c-gpname{width:180px}.c-gpver{width:56px}.c-gppid{width:64px}.c-gpstate{width:110px}
|
|
60
|
+
.c-gpb.take{width:78px}.c-gpb.restart{width:64px}.c-gpb.stop{width:58px}.c-gpb.open{width:58px}
|
|
61
|
+
.gptable td{vertical-align:middle}
|
|
62
|
+
.gptable td.acname{vertical-align:top}
|
|
63
|
+
.gpcore{color:var(--ink)}
|
|
64
|
+
.gptag{flex:none;padding:0 var(--s1);border:1px solid var(--line);border-radius:3px;font-size:9px;font-weight:600;line-height:13px;letter-spacing:.06em;text-transform:uppercase;color:var(--dim)}
|
|
65
|
+
.gpver{color:var(--dim);font-size:12px;font-variant-numeric:tabular-nums}
|
|
66
|
+
.gppid{color:var(--dim);font-size:12px;font-variant-numeric:tabular-nums}
|
|
67
|
+
.gpcopy{padding:0;border:0;background:none;color:inherit;font:inherit;cursor:pointer}
|
|
68
|
+
.gpcopy:hover{color:var(--ink);text-decoration:underline dotted}
|
|
69
|
+
.gpcopy.on{color:var(--ok)}
|
|
70
|
+
.gpline.up td:not(.acname){background:var(--okbg)}
|
|
71
|
+
.gpline.crashed td:not(.acname){background:var(--errbg)}
|
|
72
|
+
.gplabel{display:flex;align-items:center;gap:var(--s1);min-width:0;cursor:default}
|
|
73
|
+
.gplabel .hdot{flex:none;margin-right:0}
|
|
74
|
+
.gplabel span{overflow:hidden;text-overflow:ellipsis}
|
|
75
|
+
.hdot.unknown{background:var(--line)}
|
|
76
|
+
.gphost a{color:var(--accent);text-decoration:underline;text-underline-offset:2px;font-size:12px}
|
|
77
|
+
.gphost span{color:var(--dim);font-size:12px}
|
|
78
|
+
.gpstate{color:var(--dim);font-size:12px;font-variant-numeric:tabular-nums}
|
|
79
|
+
.gpstate.up{color:var(--ok)}.gpstate.crashed{color:var(--red)}.gpstate.starting{color:var(--warn)}
|
|
80
|
+
.gptable td.gpb{padding-left:0;padding-right:var(--s1);text-align:right;overflow:visible}
|
|
81
|
+
.gpb button{padding:0 var(--s2);font-size:12px}
|
|
82
|
+
.gpb button.hsm{padding:0 var(--s1);font-size:11px}
|
|
83
|
+
.gpb.act{white-space:nowrap}
|
|
84
|
+
.gpb.act button + button{margin-left:var(--s1)}
|
|
85
|
+
.gphead div{display:flex;justify-content:flex-end;align-items:center;gap:var(--s1)}
|
|
86
|
+
.gphead button.hsm{padding:0 var(--s1);font-size:11px}
|
|
87
|
+
.gpnote{white-space:nowrap;overflow:hidden;text-overflow:ellipsis}
|
|
88
|
+
@container (max-width:620px){.c-gphost,.gphost,.c-gpver,.gpver,.c-gppid,.gppid{display:none}.gptable .c-name{width:96px}.c-gpname{width:110px}.c-gpstate{width:88px}}
|
|
89
|
+
/* a phone: the state words go too, the dot and the tint still say it, and
|
|
90
|
+
only start (stop) and open stay, the label taking what is left */
|
|
91
|
+
@container (max-width:440px){.c-gpstate,.gpstate,.c-gpb.act,.gpb.act,.c-gpb.take,.gpb.take,.c-gpb.restart,.gpb.restart,.gphead button.hsm{display:none}.gptable .c-name{width:64px}.c-gpname{width:auto}.c-gpb.stop,.c-gpb.open{width:50px}}
|
|
92
|
+
.hopen{margin-left:var(--s2)}
|
|
93
|
+
.hopen button{display:inline-flex;align-items:center;padding:var(--s0) var(--s1)}
|
|
94
|
+
.hopen .hico{color:inherit}
|
|
95
|
+
.hsec{min-width:0}
|
|
96
|
+
.hsec h2{display:flex;align-items:center;gap:var(--s2);height:28px;margin:0 0 var(--s2);padding-bottom:var(--s1);border-bottom:1px solid var(--line);
|
|
97
|
+
font-size:12px;letter-spacing:.14em;text-transform:uppercase;color:var(--dim);font-weight:600}
|
|
98
|
+
.hsec h2 .sp{flex:1}
|
|
99
|
+
.hsec h2 button{letter-spacing:0;text-transform:none}
|
|
100
|
+
.hico{flex:none;color:var(--accent)}
|
|
101
|
+
.hrow .sp{flex:1}
|
|
102
|
+
.hrow{display:flex;flex-wrap:wrap;align-items:center;gap:var(--s2);padding:var(--s1) 0;border-bottom:1px solid var(--line)}
|
|
103
|
+
.hrow .hlog{flex-basis:100%}
|
|
104
|
+
.hlink{display:inline-flex;align-items:center;color:inherit;text-decoration:none;font-size:13px}
|
|
105
|
+
.hlink:hover{text-decoration:underline}
|
|
106
|
+
.hdim{font-size:12px;color:var(--dim)}
|
|
107
|
+
.hnote{font-size:12px;color:var(--warn)}
|
|
108
|
+
/* a filled bar any widget can draw: <span class="wbar" style="--p:51%"><i></i></span>,
|
|
109
|
+
green below 70%, `mid` from 70%, `hi` from 90% (the widget picks the class) */
|
|
110
|
+
.wbar{display:inline-block;vertical-align:middle;width:56px;height:7px;margin-right:var(--s2);border-radius:4px;background:var(--line);overflow:hidden}
|
|
111
|
+
.wbar > i{display:block;height:100%;width:var(--p,0%);background:var(--ok)}
|
|
112
|
+
.wbar.mid > i{background:var(--warn)}
|
|
113
|
+
.wbar.hi > i{background:var(--red)}
|
|
@@ -0,0 +1,376 @@
|
|
|
1
|
+
// the home's strip over every page it serves: one line that never wraps. On a
|
|
2
|
+
// repo page the shown repo (its icon, its name in its own colour, the two
|
|
3
|
+
// swatches that set those colours), what it runs now and its two runs, then
|
|
4
|
+
// on the right its processes, its hub's trouble and its editor icons
|
|
5
|
+
// (header-bar.tsx); on the overview and the plan, the page's name. At the
|
|
6
|
+
// far right, only machine wide trouble, and only while it lasts (pm or the
|
|
7
|
+
// daemon down, a child or a global process crashed). The places themselves
|
|
8
|
+
// are the place buttons at the top of the project list (⌘J, ⌘L, ⌘', the
|
|
9
|
+
// picker's ⌘K between the first two) and the first three entries of the
|
|
10
|
+
// picker. What administers one repo (reload its hub, keep it active, archive
|
|
11
|
+
// it) is its admin page (admin-view.tsx). NotUp stands in for the hub page
|
|
12
|
+
// while its hub cannot answer, with the fix. Every link of the home is a
|
|
13
|
+
// page of the same app: a plain click moves it in place. The daemon or pm
|
|
14
|
+
// down is said in MachineBar, a reserved line the strip brings with it,
|
|
15
|
+
// drawn last in the page column (its flex order).
|
|
16
|
+
import { observer } from 'mobx-react-lite'
|
|
17
|
+
import type { ReactNode } from 'react'
|
|
18
|
+
import type { ProcessIcon, ResourceView } from '../../home/resources.ts'
|
|
19
|
+
import type { WorkerView } from '../../home/hubs.ts'
|
|
20
|
+
import { NOT_INSTALLED } from '../../home/home-words.ts'
|
|
21
|
+
import { splitWidgetHead } from '../../home/widget-text.ts'
|
|
22
|
+
import type { MachineSt, OpenHow } from './machine-st.ts'
|
|
23
|
+
import type { Role } from '../../home/pm.ts'
|
|
24
|
+
import { processIcon, stateWords, type ProcessRow } from '../../home/process-rows.ts'
|
|
25
|
+
import { plainClick, repoPage, type HomeSt } from './home-st.ts'
|
|
26
|
+
import { stampWords } from './time-ago.tsx'
|
|
27
|
+
import { Icon, ICON } from './icons.tsx'
|
|
28
|
+
import { Tip } from './floating.tsx'
|
|
29
|
+
import { notUp, RepoOpeners, RepoRun, RepoTools } from './header-bar.tsx'
|
|
30
|
+
|
|
31
|
+
const STATUS_WORD: Record<WorkerView['status'], string> = { up: 'up', starting: 'starting', crashed: 'crashed', down: 'down', stopping: 'stopping' }
|
|
32
|
+
|
|
33
|
+
export const Dot = ({ s }: { s: WorkerView['status'] }) => <i className={`hdot ${s}`} />
|
|
34
|
+
|
|
35
|
+
/** a link to a page of the home: a plain click shows it in place, any other
|
|
36
|
+
* click keeps the browser's own behaviour */
|
|
37
|
+
export const HomeLink = ({ app, to, className, title, children }: { app: HomeSt; to: string; className?: string; title?: string; children: ReactNode }) => (
|
|
38
|
+
<a className={className} href={to} title={title} onClick={(e) => {
|
|
39
|
+
if (!plainClick(e)) return
|
|
40
|
+
e.preventDefault()
|
|
41
|
+
app.navigate(to)
|
|
42
|
+
}}>{children}</a>
|
|
43
|
+
)
|
|
44
|
+
|
|
45
|
+
const Log = ({ lines }: { lines: string[] }) => (lines.length === 0 ? null : <pre className="hlog">{lines.join('\n')}</pre>)
|
|
46
|
+
|
|
47
|
+
/** one switch per global process; its log floats under it on hover */
|
|
48
|
+
export const ResourceChip = observer(({ m, r }: { m: MachineSt; r: ResourceView }) => {
|
|
49
|
+
const on = r.status === 'up' || r.status === 'starting'
|
|
50
|
+
const title = [r.description, r.where != null ? `on ${r.where}` : null, r.note].filter((x) => x != null).join(' · ')
|
|
51
|
+
const log = r.tail.length > 0 ? `${title !== '' ? `${title}\n\n` : ''}${r.tail.join('\n')}` : null
|
|
52
|
+
return (
|
|
53
|
+
<Tip className="hproc" tipClass="hpop" placement="bottom-start" interactive content={log} data-log={log == null ? undefined : 'yes'}>
|
|
54
|
+
<button className={`hres ${r.status}`} title={log != null ? undefined : title}
|
|
55
|
+
disabled={r.status === 'stopping' || (on && !r.canStop)}
|
|
56
|
+
onClick={() => void m.processAct(r.id, on ? 'stop' : 'start')}>
|
|
57
|
+
<Dot s={r.status} />{r.label}
|
|
58
|
+
</button>
|
|
59
|
+
</Tip>
|
|
60
|
+
)
|
|
61
|
+
})
|
|
62
|
+
|
|
63
|
+
/** what a card or a group header wears: an emoji, an image, or an empty box of the same size */
|
|
64
|
+
const Mark = ({ i }: { i: ProcessIcon | null }) =>
|
|
65
|
+
i == null ? <i className="hpico" /> : 'text' in i ? <span className="hpico">{i.text}</span> : <img className="hpico" src={i.src} alt="" width={16} height={16} />
|
|
66
|
+
|
|
67
|
+
/** a global process's state in words, on the one clock: `up 2h`, `crashed 3m ago` */
|
|
68
|
+
const StateWords = observer(({ m, r }: { m: MachineSt; r: ProcessRow }) => (
|
|
69
|
+
<span title={r.since == null ? undefined : `since ${stampWords(r.since)}`}>{stateWords(r, m.clock.now)}</span>
|
|
70
|
+
))
|
|
71
|
+
|
|
72
|
+
/** the button columns a table holds: a kind no line needs takes none */
|
|
73
|
+
type BtnCols = { act: string | null; take: boolean }
|
|
74
|
+
|
|
75
|
+
/** one process as one line, like a commit on the activity page: its dot,
|
|
76
|
+
* icon and label (cut to its column, whole with its log on hover), its url
|
|
77
|
+
* as a link, its version, its pid (a click copies it), its state, then one
|
|
78
|
+
* cell per kind of button, so every start sits under every start: its
|
|
79
|
+
* declared actions, take over, restart, start (stop while it runs), open.
|
|
80
|
+
* One of shipkit's own wears a core tag and has its stop disabled: it is
|
|
81
|
+
* always on, restarted through pm (pm itself by launchd). A running line is
|
|
82
|
+
* tinted green, a crashed one red */
|
|
83
|
+
const ProcessLine = observer(({ m, r, repoIcon, head, cols, span }: { m: MachineSt; r: ProcessRow; repoIcon: string | null; head: ReactNode; cols: BtnCols; span: number }) => {
|
|
84
|
+
const on = r.status === 'up' || r.status === 'starting'
|
|
85
|
+
const about = [r.label, ...(r.core ? [r.description] : [`${r.repo}/${r.file}`, r.description, r.where != null ? `on ${r.where}` : null])].filter((x) => x != null).join(' · ')
|
|
86
|
+
const log = r.tail.length > 0 ? `${about}\n\n${r.tail.join('\n')}` : null
|
|
87
|
+
const via = m.openHow === 'app' ? 'as a shipkit app window' : 'in the default browser'
|
|
88
|
+
const note = r.takeOver?.ok === false ? `cannot take over: ${r.takeOver.why}` : r.note
|
|
89
|
+
const stopTitle = r.core ? `always on: ${r.id === 'pm' ? 'pm is the LaunchAgent com.shipkit.pm, launchd starts it again when it dies' : `restart ${r.label} instead`}`
|
|
90
|
+
: on && !r.canStop ? `${r.label} was started outside pm, so pm cannot stop it` : on ? `stop ${r.label}` : `start ${r.label}`
|
|
91
|
+
const copied = r.pid != null && m.copiedPid === r.pid
|
|
92
|
+
return (
|
|
93
|
+
<>
|
|
94
|
+
<tr className={`gpline ${r.dot}`}>
|
|
95
|
+
{head}
|
|
96
|
+
<td className="gpname">
|
|
97
|
+
<Tip className="gplabel" tipClass="hpop" placement="bottom-start" interactive content={log} data-log={log == null ? undefined : 'yes'}>
|
|
98
|
+
<i className={`hdot ${r.dot}`} /><Mark i={processIcon(r, repoIcon)} /><span title={log != null ? undefined : about}>{r.label}</span>
|
|
99
|
+
{r.core ? <b className="gptag" title="built into shipkit: always on">core</b> : null}
|
|
100
|
+
</Tip>
|
|
101
|
+
</td>
|
|
102
|
+
<td className="gphost">
|
|
103
|
+
{r.url == null ? null : r.core ? <span>{r.host}</span> : (
|
|
104
|
+
<a href={r.url} title={`open ${r.url} ${via}`} onClick={(e) => {
|
|
105
|
+
if (!plainClick(e)) return
|
|
106
|
+
e.preventDefault()
|
|
107
|
+
void m.openProcess(r.id)
|
|
108
|
+
}}>{r.host}</a>
|
|
109
|
+
)}
|
|
110
|
+
</td>
|
|
111
|
+
<td className="gpver">{r.version ?? ''}</td>
|
|
112
|
+
<td className="gppid">{r.pid == null ? null : <button className={copied ? 'gpcopy on' : 'gpcopy'} title={`copy pid ${r.pid}`} onClick={() => void m.copyPid(r.pid!)}>{copied ? 'copied' : r.pid}</button>}</td>
|
|
113
|
+
<td className={`gpstate ${r.dot}`}><StateWords m={m} r={r} /></td>
|
|
114
|
+
{cols.act == null ? null : <td className="gpb act">{r.actions.map((a) => (
|
|
115
|
+
<button key={a.id} className="ghost hsm" disabled={r.running != null} onClick={() => void m.processAct(r.id, 'action', a.id)}>{r.running === a.id ? <span className="spin" /> : null}{a.label}</button>
|
|
116
|
+
))}</td>}
|
|
117
|
+
{!cols.take ? null : <td className="gpb take">{r.takeOver?.ok === true ? <button className="ghost hsm" onClick={() => void m.processAct(r.id, 'takeover')} title={`stop the copy of ${r.label} started elsewhere (the process group listening on its port) and start one pm supervises, in one go`}>take over</button> : null}</td>}
|
|
118
|
+
<td className="gpb restart">{r.canRestart ? <button className="ghost hsm" onClick={() => void (r.core ? m.restartChild(r.id as Role) : m.processAct(r.id, 'restart'))} title={r.core ? `stop the ${r.label} and start it again, through pm` : `stop ${r.label} and start it again`}>restart</button> : null}</td>
|
|
119
|
+
<td className="gpb stop"><button className={on || r.core ? 'ghost' : 'primary'} disabled={r.core || r.status === 'stopping' || r.status === 'unknown' || (on && !r.canStop)}
|
|
120
|
+
title={stopTitle} onClick={() => void m.processAct(r.id, on ? 'stop' : 'start')}>{on || r.core ? 'stop' : 'start'}</button></td>
|
|
121
|
+
<td className="gpb open">{r.url != null && !r.core ? <button className="ghost" onClick={() => void m.openProcess(r.id)} title={r.status === 'up' ? `open ${r.url} ${via}` : `start ${r.label}, then open ${r.url} ${via}`}>open</button> : null}</td>
|
|
122
|
+
</tr>
|
|
123
|
+
{note == null ? null : <tr className={`gpline ${r.dot}`}><td colSpan={span} className="gpnote hnote" title={note}>{note}</td></tr>}
|
|
124
|
+
</>
|
|
125
|
+
)
|
|
126
|
+
})
|
|
127
|
+
|
|
128
|
+
const OPENERS: { how: OpenHow; d: string; title: string }[] = [
|
|
129
|
+
{ how: 'browser', d: ICON.browser, title: 'open opens the default browser' },
|
|
130
|
+
{ how: 'app', d: ICON.window, title: 'open opens a shipkit app window' },
|
|
131
|
+
]
|
|
132
|
+
|
|
133
|
+
/** how every open button and url of the table opens, kept per browser */
|
|
134
|
+
const OpenHowPick = observer(({ m }: { m: MachineSt }) => (
|
|
135
|
+
<span className="seg hopen" role="group" aria-label="how open opens">
|
|
136
|
+
{OPENERS.map((o) => (
|
|
137
|
+
<button key={o.how} className={m.openHow === o.how ? 'on' : undefined} aria-pressed={m.openHow === o.how} title={o.title} onClick={() => m.setOpenHow(o.how)}><Icon d={o.d} /></button>
|
|
138
|
+
))}
|
|
139
|
+
</span>
|
|
140
|
+
))
|
|
141
|
+
|
|
142
|
+
/** the repo's icon and plain name, as on the activity page; a plain click shows it in place */
|
|
143
|
+
const ProcessRepo = observer(({ app, name, icon }: { app: HomeSt; name: string; icon: string | null }) => (
|
|
144
|
+
<a className="acrepo" href={repoPage(name)} onClick={(e) => {
|
|
145
|
+
if (!plainClick(e)) return
|
|
146
|
+
e.preventDefault()
|
|
147
|
+
app.select(name)
|
|
148
|
+
}}>
|
|
149
|
+
{icon == null ? null : <img src={icon} alt="" width={14} height={14} draggable={false} />}
|
|
150
|
+
<span>{name}</span>
|
|
151
|
+
</a>
|
|
152
|
+
))
|
|
153
|
+
|
|
154
|
+
/** the processes as one table in the activity page's style: shipkit's own
|
|
155
|
+
* first, then a row group per declaring repo in the project list's order,
|
|
156
|
+
* its name in the first column, at the top of its group, then one line per
|
|
157
|
+
* process. The header row ends with the new window button and the open
|
|
158
|
+
* choice */
|
|
159
|
+
const ProcessTable = observer(({ app, m }: { app: HomeSt; m: MachineSt }) => {
|
|
160
|
+
const groups = m.processGroups
|
|
161
|
+
const rows = groups.flatMap((g) => g.rows)
|
|
162
|
+
// wide enough for the longest set of declared actions, in the button font
|
|
163
|
+
const act = Math.max(0, ...rows.map((r) => r.actions.reduce((n, a) => n + a.label.length + 3, 0)))
|
|
164
|
+
const cols: BtnCols = { act: act === 0 ? null : `${act}ch`, take: rows.some((r) => r.takeOver?.ok === true) }
|
|
165
|
+
const btns = 3 + (cols.act == null ? 0 : 1) + (cols.take ? 1 : 0)
|
|
166
|
+
return (
|
|
167
|
+
<div className="gpwrap">
|
|
168
|
+
<table className="actable gptable">
|
|
169
|
+
<colgroup>
|
|
170
|
+
<col className="c-name" /><col className="c-gpname" /><col className="c-gphost" /><col className="c-gpver" /><col className="c-gppid" /><col className="c-gpstate" />
|
|
171
|
+
{cols.act == null ? null : <col className="c-gpb act" style={{ width: cols.act }} />}
|
|
172
|
+
{cols.take ? <col className="c-gpb take" /> : null}
|
|
173
|
+
<col className="c-gpb restart" /><col className="c-gpb stop" /><col className="c-gpb open" />
|
|
174
|
+
</colgroup>
|
|
175
|
+
<thead><tr>
|
|
176
|
+
<th>repo</th><th>process</th><th className="gphost">url</th><th className="gpver">version</th><th className="gppid">pid</th><th className="gpstate">state</th>
|
|
177
|
+
<th colSpan={btns} className="gphead"><div><button className="ghost hsm" title="open one more window on this page" onClick={() => m.newWindow()}>new window</button><OpenHowPick m={m} /></div></th>
|
|
178
|
+
</tr></thead>
|
|
179
|
+
{groups.map((g) => {
|
|
180
|
+
const icon = g.core ? null : app.projects.list.find((p) => p.name === g.repo)?.icon ?? null
|
|
181
|
+
const lines = g.rows.length + g.rows.filter((r) => r.note != null || r.takeOver?.ok === false).length
|
|
182
|
+
const head = <td rowSpan={lines} className="acname">{g.core ? <span className="acrepo gpcore"><Icon d={ICON.shipkit} /><span>{g.repo}</span></span> : <ProcessRepo app={app} name={g.repo} icon={icon} />}</td>
|
|
183
|
+
return (
|
|
184
|
+
<tbody key={g.core ? ':core' : g.repo} className="acgrp">
|
|
185
|
+
{g.rows.map((r, i) => <ProcessLine key={r.id} m={m} r={r} repoIcon={icon} head={i === 0 ? head : null} cols={cols} span={5 + btns} />)}
|
|
186
|
+
</tbody>
|
|
187
|
+
)
|
|
188
|
+
})}
|
|
189
|
+
</table>
|
|
190
|
+
</div>
|
|
191
|
+
)
|
|
192
|
+
})
|
|
193
|
+
|
|
194
|
+
/** every house repo's widgets: the html the daemon last rendered, each under its title */
|
|
195
|
+
const Monitor = observer(({ m }: { m: MachineSt }) => {
|
|
196
|
+
const w = m.widgets
|
|
197
|
+
if (w == null || (w.widgets.length === 0 && w.errors.length === 0)) return null
|
|
198
|
+
return (
|
|
199
|
+
<section className="hsec">
|
|
200
|
+
<div className="hmon">
|
|
201
|
+
{w.widgets.map((x) => {
|
|
202
|
+
const { head, body } = splitWidgetHead(x.html ?? '')
|
|
203
|
+
return (
|
|
204
|
+
<section key={`${x.repo}/${x.id}`} className="hwidget" title={`${x.repo}/${x.file}`}>
|
|
205
|
+
<h3>
|
|
206
|
+
{x.title}
|
|
207
|
+
{head == null ? null : <span className="hdim"> · <span dangerouslySetInnerHTML={{ __html: head }} /></span>}
|
|
208
|
+
</h3>
|
|
209
|
+
{x.error != null ? <p className="hnote">{x.error}</p> : null}
|
|
210
|
+
{x.html == null ? <p className="twait"><span className="spin" /></p> : <div dangerouslySetInnerHTML={{ __html: body }} />}
|
|
211
|
+
</section>
|
|
212
|
+
)
|
|
213
|
+
})}
|
|
214
|
+
</div>
|
|
215
|
+
{w.errors.map((e) => <p key={e} className="hnote">{e}</p>)}
|
|
216
|
+
</section>
|
|
217
|
+
)
|
|
218
|
+
})
|
|
219
|
+
|
|
220
|
+
/** the two colours of a repo, a picker each: the page draws the dark one */
|
|
221
|
+
export const IdentitySwatches = observer(({ app, name }: { app: HomeSt; name: string }) => {
|
|
222
|
+
const id = app.projects.identityOf(name)
|
|
223
|
+
return (
|
|
224
|
+
<span className="hswatch">
|
|
225
|
+
<input type="color" aria-label="colour on a dark background" title={`${name} on a dark background (this page): ${id.colorDark}, written to repo.config.ts identity.colorDark`}
|
|
226
|
+
value={id.colorDark} onChange={(e) => app.projects.setIdentity(name, { colorDark: e.currentTarget.value })} />
|
|
227
|
+
<input type="color" aria-label="colour on a light background" title={`${name} on a light background: ${id.colorLight}, written to repo.config.ts identity.colorLight`}
|
|
228
|
+
value={id.colorLight} onChange={(e) => app.projects.setIdentity(name, { colorLight: e.currentTarget.value })} />
|
|
229
|
+
</span>
|
|
230
|
+
)
|
|
231
|
+
})
|
|
232
|
+
|
|
233
|
+
/** the shown repo in the strip: its icon, its name in its colour */
|
|
234
|
+
const ShownRepo = observer(({ app, name }: { app: HomeSt; name: string }) => {
|
|
235
|
+
const p = app.projects.list.find((x) => x.name === name)
|
|
236
|
+
const id = app.projects.identityOf(name)
|
|
237
|
+
return (
|
|
238
|
+
<span className="hrepo">
|
|
239
|
+
{/* the icon's box is there before the project list is, so the name never moves */}
|
|
240
|
+
{p?.icon != null ? <img src={p.icon} alt="" width={18} height={18} /> : <i className="hrico" />}
|
|
241
|
+
<b className="hrname" style={{ color: id.colorDark }}>{name}</b>
|
|
242
|
+
<IdentitySwatches app={app} name={name} />
|
|
243
|
+
</span>
|
|
244
|
+
)
|
|
245
|
+
})
|
|
246
|
+
|
|
247
|
+
/** what is wrong with the machine itself, one chip each. Nothing when all is
|
|
248
|
+
* well: the strip states trouble, never health */
|
|
249
|
+
const MachineAlerts = observer(({ m }: { m: MachineSt }) => (
|
|
250
|
+
<>
|
|
251
|
+
{(m.pm?.children ?? []).filter((c) => c.status === 'crashed' || c.status === 'down').map((c) => (
|
|
252
|
+
<span key={c.id} className="halert" title={c.note ?? c.tail.at(-1) ?? ''}><Dot s={c.status} />{c.id} {STATUS_WORD[c.status]}</span>
|
|
253
|
+
))}
|
|
254
|
+
{(m.pm?.resources ?? []).filter((r) => r.status === 'crashed').map((r) => <ResourceChip key={r.id} m={m} r={r} />)}
|
|
255
|
+
{m.error !== '' ? <span className="herr" title={m.error}>{m.error}</span> : null}
|
|
256
|
+
</>
|
|
257
|
+
))
|
|
258
|
+
|
|
259
|
+
export const HomeStrip = observer(({ app }: { app: HomeSt }) => {
|
|
260
|
+
const m = app.machine
|
|
261
|
+
if (m == null) return null
|
|
262
|
+
const name = m.repo
|
|
263
|
+
// the repo's own pieces only while its hub answers: NotUp says why not
|
|
264
|
+
const shown = name == null ? null : app.shown
|
|
265
|
+
const live = shown != null && !notUp(shown) ? shown : null
|
|
266
|
+
return (
|
|
267
|
+
<>
|
|
268
|
+
<div className="hstrip">
|
|
269
|
+
{name == null ? <span className="hplace">{app.page === 'overview' ? 'overview' : app.page}</span> : <ShownRepo app={app} name={name} />}
|
|
270
|
+
{live == null ? null : <RepoRun app={live} />}
|
|
271
|
+
<span className="sp" />
|
|
272
|
+
{live == null ? null : <RepoTools app={live} />}
|
|
273
|
+
<MachineAlerts m={m} />
|
|
274
|
+
{live == null ? null : <RepoOpeners app={live} />}
|
|
275
|
+
</div>
|
|
276
|
+
<MachineBar app={app} />
|
|
277
|
+
</>
|
|
278
|
+
)
|
|
279
|
+
})
|
|
280
|
+
|
|
281
|
+
/** the daemon or pm not answering, on one line at the bottom of the window.
|
|
282
|
+
* The line is always there, empty while all is well, so an alert never
|
|
283
|
+
* moves the page. Only a failed probe sets down or pmDown: before the first
|
|
284
|
+
* answer the line stays empty */
|
|
285
|
+
export const MachineBar = observer(({ app }: { app: HomeSt }) => {
|
|
286
|
+
const m = app.machine
|
|
287
|
+
if (m == null) return null
|
|
288
|
+
return (
|
|
289
|
+
<div className="hbar">
|
|
290
|
+
{m.down !== '' ? <span className="hdown" role="alert" title={m.down}><b>daemon down</b> {m.down}</span> : null}
|
|
291
|
+
{m.pmDown !== '' ? <span className="hdown" role="alert" title={m.pmDown}><b>pm down</b> {m.pmDown}</span> : null}
|
|
292
|
+
</div>
|
|
293
|
+
)
|
|
294
|
+
})
|
|
295
|
+
|
|
296
|
+
/** archive the repo or bring it back: `archived` in its repo.config.ts, and
|
|
297
|
+
* only that key. Archived, it stays listed and nothing runs there */
|
|
298
|
+
export const ArchiveButton = observer(({ app, name }: { app: HomeSt; name: string }) => {
|
|
299
|
+
const on = app.projects.isArchived(name)
|
|
300
|
+
return (
|
|
301
|
+
<button className="ghost" onClick={() => void app.projects.setArchived(name, !on)}
|
|
302
|
+
title={on ? 'bring it back: it returns to the project list, and its hub, watch, plan rows, processes and widgets run again' : 'archive it: it leaves the project list for the archived page, and nothing runs there (no hub, no watch, no plan rows, no processes, no widgets). Written to repo.config.ts archived'}>
|
|
303
|
+
{on ? 'unarchive' : 'archive'}
|
|
304
|
+
</button>
|
|
305
|
+
)
|
|
306
|
+
})
|
|
307
|
+
|
|
308
|
+
/** why the shown repo's hub does not answer, and what to do about it */
|
|
309
|
+
export const NotUp = observer(({ app }: { app: HomeSt }) => {
|
|
310
|
+
const m = app.machine
|
|
311
|
+
const name = m?.repo
|
|
312
|
+
if (m == null || name == null) return null
|
|
313
|
+
const w = m.shown
|
|
314
|
+
const mod = m.data?.modernize[name]
|
|
315
|
+
if (app.projects.isArchived(name))
|
|
316
|
+
return (
|
|
317
|
+
<div className="hwait">
|
|
318
|
+
<p>{name} is archived: nothing runs there, and only the <HomeLink app={app} to="/archived">archived page</HomeLink> lists it.</p>
|
|
319
|
+
<ArchiveButton app={app} name={name} />
|
|
320
|
+
</div>
|
|
321
|
+
)
|
|
322
|
+
if (w == null) return <div className="hwait"><p className="twait"><span className="spin" />{m.down !== '' ? `waiting for the daemon to load ${name}…` : `looking for ${name}…`}</p></div>
|
|
323
|
+
return (
|
|
324
|
+
<div className="hwait">
|
|
325
|
+
{w.blocked != null ? <p>{w.blocked}</p> : <p><Dot s={w.status} />{name}'s hub is {STATUS_WORD[w.status]}{w.note != null ? `: ${w.note}` : ''}</p>}
|
|
326
|
+
{w.blocked == null && (w.status === 'down' || w.status === 'crashed') ? <button className="primary" onClick={() => void m.hub(name, 'restart')}>start it</button> : null}
|
|
327
|
+
{w.blocked != null && mod?.status !== 'running' ? <button className="primary" onClick={() => void m.modernize(name)}>{w.blocked === NOT_INSTALLED ? 'bun install' : 'modernize'}</button> : null}
|
|
328
|
+
{mod == null ? null : <><p>modernize: {mod.status}{mod.code != null ? `, exit ${mod.code}` : ''}</p><Log lines={mod.tail} /></>}
|
|
329
|
+
<Log lines={w.tail} />
|
|
330
|
+
</div>
|
|
331
|
+
)
|
|
332
|
+
})
|
|
333
|
+
|
|
334
|
+
/** the archived page at `/archived`: the one place an archived repo is
|
|
335
|
+
* listed, each with its way back */
|
|
336
|
+
export const ArchivedView = observer(({ app }: { app: HomeSt }) => {
|
|
337
|
+
const list = app.projects.archived
|
|
338
|
+
return (
|
|
339
|
+
<div className="hall">
|
|
340
|
+
<section className="hsec">
|
|
341
|
+
<h2><Icon d={ICON.archived} />archived</h2>
|
|
342
|
+
{list.length === 0 ? <p className="hdim">no archived repo: a repo is archived from its admin page, or `archived: true` in its repo.config.ts</p> : null}
|
|
343
|
+
{list.map((p) => (
|
|
344
|
+
<div key={p.name} className="hrow">
|
|
345
|
+
<span className="hlink">{p.icon != null ? <img className="hpico" src={p.icon} alt="" width={16} height={16} /> : <i className="hpico" />}{p.name}</span>
|
|
346
|
+
<span className="sp" />
|
|
347
|
+
<button className="ghost" onClick={() => void app.projects.setArchived(p.name, false)} title={`bring ${p.name} back: it returns to the project list, and its hub loads again`}>unarchive</button>
|
|
348
|
+
</div>
|
|
349
|
+
))}
|
|
350
|
+
</section>
|
|
351
|
+
</div>
|
|
352
|
+
)
|
|
353
|
+
})
|
|
354
|
+
|
|
355
|
+
/** the overview at `/`: the monitor widgets, then one processes table,
|
|
356
|
+
* shipkit's own (pm, the daemon, the web) its first group. The repos are
|
|
357
|
+
* the project list's, not a section here */
|
|
358
|
+
export const Overview = observer(({ app }: { app: HomeSt }) => {
|
|
359
|
+
const m = app.machine
|
|
360
|
+
if (m == null) return null
|
|
361
|
+
if (m.data == null) return <div className="hwait"><p className="twait"><span className="spin" />{m.down !== '' ? 'the daemon does not answer yet' : 'loading…'}</p></div>
|
|
362
|
+
return (
|
|
363
|
+
<div className="hall">
|
|
364
|
+
<div className="hstack">
|
|
365
|
+
<Monitor m={m} />
|
|
366
|
+
<section className="hsec">
|
|
367
|
+
<ProcessTable app={app} m={m} />
|
|
368
|
+
{m.pm != null && m.pm.resources.length === 0 ? <p className="hdim">no global process declared: a repo declares one in a <name>.shipkit.ts file, as export default globalProcess({...})</p> : null}
|
|
369
|
+
{(m.pm?.unitErrors ?? []).map((e) => <p key={e} className="hnote">{e}</p>)}
|
|
370
|
+
</section>
|
|
371
|
+
{app.projects.archived.length === 0 ? null : <p className="hdim"><HomeLink app={app} to="/archived">{app.projects.archived.length} archived repo{app.projects.archived.length === 1 ? '' : 's'}</HomeLink>, listed nowhere else</p>}
|
|
372
|
+
<p className="hdim">in the console: shipkit overview</p>
|
|
373
|
+
</div>
|
|
374
|
+
</div>
|
|
375
|
+
)
|
|
376
|
+
})
|
|
@@ -0,0 +1,21 @@
|
|
|
1
|
+
<!DOCTYPE html>
|
|
2
|
+
<html lang="en">
|
|
3
|
+
<head>
|
|
4
|
+
<meta charset="UTF-8" />
|
|
5
|
+
<meta name="viewport" content="width=device-width, initial-scale=1.0" />
|
|
6
|
+
<title>home</title>
|
|
7
|
+
<link rel="stylesheet" href="./hub.css" />
|
|
8
|
+
<link rel="stylesheet" href="./project-list.css" />
|
|
9
|
+
<link rel="stylesheet" href="./prose-hits.css" />
|
|
10
|
+
<link rel="stylesheet" href="./home-strip.css" />
|
|
11
|
+
<link rel="stylesheet" href="./planned.css" />
|
|
12
|
+
<link rel="stylesheet" href="./activity.css" />
|
|
13
|
+
<link rel="stylesheet" href="./readme.css" />
|
|
14
|
+
<link rel="stylesheet" href="./dock.css" />
|
|
15
|
+
<link rel="stylesheet" href="./controls.css" />
|
|
16
|
+
</head>
|
|
17
|
+
<body data-home>
|
|
18
|
+
<div id="root"></div>
|
|
19
|
+
<script type="module" src="./hub-app.tsx"></script>
|
|
20
|
+
</body>
|
|
21
|
+
</html>
|