rvlib-shipkit 1.2.0 → 1.4.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 +104 -0
- package/README.md +1 -1
- package/package.json +1 -1
- package/src/asks/asks-queue.ts +4 -2
- package/src/assets/kits/identity-kit.ts +15 -13
- package/src/assets/run-assets.ts +15 -10
- package/src/check.ts +26 -10
- package/src/checks/SK001-package-json-shape.check.ts +34 -5
- 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 +103 -61
- package/src/checks/SK013-one-way-in-policy.check.ts +10 -2
- package/src/checks/SK014-prose-ste.check.ts +16 -6
- package/src/checks/SK015-nested-repo.check.ts +5 -1
- package/src/checks/SK016-legacy-check-shapes.check.ts +11 -5
- package/src/checks/SK017-vscode-hub-task.check.ts +7 -0
- package/src/checks/SK022-machine-runs-the-pin.check.ts +6 -2
- package/src/checks/SK024-identity-colors.check.ts +5 -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 +50 -0
- package/src/checks/SK028-readme.check.ts +83 -0
- package/src/cli/cmd_Assets.ts +1 -1
- package/src/cli/cmd_Ci.ts +5 -0
- package/src/cli/cmd_CrossRepoActivity.ts +119 -0
- package/src/cli/cmd_GlobalProcesses.ts +155 -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 +9 -1
- package/src/collections/checks.ts +3 -2
- package/src/collections/collection-types.ts +15 -0
- package/src/collections/providers.ts +7 -3
- package/src/collections/test-time.ts +10 -1
- package/src/contracts/apply-contracts.ts +9 -8
- package/src/contracts/contract-types.ts +8 -2
- package/src/contracts/contracts-engine.ts +34 -13
- package/src/contracts/manifests/identity.contract.ts +6 -5
- package/src/contracts/manifests/social.contract.ts +4 -2
- package/src/contracts/manifests/steam.contract.ts +7 -3
- package/src/contracts/web-surface.ts +59 -0
- package/src/core/agent-spawn.ts +46 -0
- package/src/core/archived.ts +82 -0
- package/src/core/budget.ts +19 -9
- package/src/core/canonical-scripts.ts +12 -3
- package/src/core/check-runner.ts +66 -17
- package/src/core/discover-checks.ts +10 -5
- 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 +1 -1
- package/src/core/layout.ts +2 -0
- package/src/core/motherhome.ts +88 -66
- package/src/core/repo-facts.ts +14 -0
- package/src/core/retention.ts +14 -5
- package/src/core/retired-checks.ts +7 -0
- package/src/core/run-check.ts +8 -7
- package/src/core/run-ci.ts +46 -15
- package/src/core/run-fix.ts +7 -2
- package/src/core/run-fixers.ts +10 -6
- package/src/core/wire.ts +49 -9
- package/src/fleet/scan-fleet.ts +2 -1
- package/src/home/activity-shape.ts +138 -0
- package/src/home/activity.ts +256 -0
- package/src/home/global-units.ts +3 -2
- package/src/home/gui-app/ShipkitGui.swift +144 -25
- package/src/home/gui-app.ts +93 -30
- package/src/home/home-launchd.ts +10 -2
- package/src/home/home-routes.ts +34 -6
- package/src/home/home-start.ts +33 -5
- package/src/home/machine-stores.ts +4 -2
- package/src/home/open-app.ts +23 -10
- package/src/home/pm-config.ts +15 -11
- package/src/home/pm-inputs.ts +76 -0
- package/src/home/pm.ts +40 -7
- package/src/home/process-rows.ts +222 -0
- package/src/home/resources.ts +99 -20
- package/src/home/run-daemon.ts +12 -7
- package/src/home/run-pm.ts +30 -5
- package/src/home/serve-web.ts +6 -3
- package/src/home/supervise.ts +16 -1
- package/src/home/widget-text.ts +119 -0
- package/src/home/widgets.ts +32 -3
- package/src/hooks/commit-message.ts +2 -3
- package/src/hooks/run-hooks.ts +32 -5
- package/src/ledger/db.ts +1 -1
- package/src/ledger/gate-checks.ts +20 -6
- package/src/ledger/house-runs.ts +43 -0
- package/src/ledger/live-timeline.ts +4 -19
- package/src/ledger/run-log.ts +39 -10
- package/src/ledger/store.ts +30 -2
- package/src/ledger/world-cache.ts +63 -14
- package/src/plan/cadence.ts +21 -5
- package/src/plan/catch-up.ts +37 -0
- package/src/plan/plan-timeline.ts +99 -0
- package/src/plan/planner.ts +138 -13
- package/src/plan/repo-source.ts +120 -8
- package/src/release/run-release.ts +9 -2
- package/src/release/unit-resolve.ts +8 -12
- package/src/repo-config.ts +17 -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-words.ts +2 -0
- package/src/serve/hub-app/checks-svc.ts +2 -1
- package/src/serve/hub-app/col-tables-view.tsx +124 -0
- package/src/serve/hub-app/col-tables.ts +115 -0
- package/src/serve/hub-app/cols-svc.ts +28 -0
- 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/hash-route.ts +7 -1
- package/src/serve/hub-app/header-bar.tsx +79 -104
- package/src/serve/hub-app/home-st.ts +50 -13
- package/src/serve/hub-app/home-strip.css +81 -23
- package/src/serve/hub-app/home-strip.tsx +243 -102
- package/src/serve/hub-app/home.html +4 -0
- package/src/serve/hub-app/hub-app.tsx +103 -161
- package/src/serve/hub-app/hub-types.ts +6 -3
- package/src/serve/hub-app/hub.css +101 -39
- package/src/serve/hub-app/icons.tsx +27 -3
- package/src/serve/hub-app/index.html +4 -0
- package/src/serve/hub-app/keys.ts +44 -9
- package/src/serve/hub-app/live-svc.ts +24 -64
- package/src/serve/hub-app/machine-st.ts +65 -13
- package/src/serve/hub-app/md.ts +38 -5
- package/src/serve/hub-app/next-run.ts +1 -1
- package/src/serve/hub-app/palette-st.ts +13 -9
- 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 +21 -85
- package/src/serve/hub-app/planned-view.tsx +12 -7
- package/src/serve/hub-app/planned.css +5 -2
- package/src/serve/hub-app/project-list.css +43 -21
- package/src/serve/hub-app/project-list.tsx +122 -23
- package/src/serve/hub-app/projects-svc.ts +120 -12
- 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 +26 -7
- package/src/serve/hub-app/runs-tabs.ts +1 -1
- package/src/serve/hub-app/sidebar.tsx +84 -0
- package/src/serve/hub-app/tests-view.tsx +31 -4
- package/src/serve/hub-app/timeline-chart.tsx +31 -27
- package/src/serve/hub-app/timeline-geometry.ts +40 -5
- package/src/serve/hub-app/timeline-svc.ts +2 -2
- package/src/serve/hub-app/timeline-view.tsx +29 -9
- package/src/serve/hub-runtime.ts +17 -9
- package/src/serve/other-repos.ts +8 -45
- package/src/serve/project-gutter-route.ts +100 -20
- package/src/serve/project-gutter.ts +34 -13
- package/src/serve/readme-route.ts +57 -0
- package/src/serve/serve-hub.ts +13 -8
- package/src/serve/tracked-watch.ts +33 -1
- package/src/serve/warm-cell.ts +49 -16
- package/src/shipkit-files.ts +20 -4
- package/src/suite/judge-suites.ts +42 -17
- package/src/suite/suite-cli.ts +5 -8
- package/src/wiki/lint-md.ts +2 -0
- package/src/wiki/scan-md.ts +37 -10
- package/src/checks/SK023-stores-under-cap.check.ts +0 -49
- package/src/checks/contracts.check.ts +0 -20
|
@@ -1,23 +1,32 @@
|
|
|
1
|
-
// the home's strip over every page it serves: one
|
|
2
|
-
// the shown repo (its icon, its name in its own colour, the two
|
|
3
|
-
// set those colours)
|
|
4
|
-
//
|
|
5
|
-
//
|
|
6
|
-
//
|
|
7
|
-
//
|
|
8
|
-
//
|
|
9
|
-
//
|
|
10
|
-
//
|
|
11
|
-
//
|
|
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 (the picker's ⌘J,
|
|
9
|
+
// then ⌘K, ⌘L, ⌘') 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 MachineToasts, floating over the page column's bottom
|
|
15
|
+
// right corner, taking no room.
|
|
12
16
|
import { observer } from 'mobx-react-lite'
|
|
13
17
|
import type { ReactNode } from 'react'
|
|
14
|
-
import type { ResourceView } from '../../home/resources.ts'
|
|
18
|
+
import type { ProcessIcon, ResourceView } from '../../home/resources.ts'
|
|
15
19
|
import type { WorkerView } from '../../home/hubs.ts'
|
|
16
20
|
import { NOT_INSTALLED } from '../../home/home-words.ts'
|
|
21
|
+
import { splitWidgetHead } from '../../home/widget-text.ts'
|
|
17
22
|
import type { MachineSt } from './machine-st.ts'
|
|
18
|
-
import {
|
|
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'
|
|
19
27
|
import { Icon, ICON } from './icons.tsx'
|
|
20
28
|
import { Tip } from './floating.tsx'
|
|
29
|
+
import { notUp, RepoOpeners, RepoRun, RepoTools } from './header-bar.tsx'
|
|
21
30
|
|
|
22
31
|
const STATUS_WORD: Record<WorkerView['status'], string> = { up: 'up', starting: 'starting', crashed: 'crashed', down: 'down', stopping: 'stopping' }
|
|
23
32
|
|
|
@@ -51,60 +60,164 @@ export const ResourceChip = observer(({ m, r }: { m: MachineSt; r: ResourceView
|
|
|
51
60
|
)
|
|
52
61
|
})
|
|
53
62
|
|
|
54
|
-
/** a
|
|
55
|
-
|
|
56
|
-
|
|
57
|
-
|
|
58
|
-
|
|
59
|
-
|
|
60
|
-
|
|
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 process's state in words, on the one clock (`up 2h`, `crashed 3m ago`,
|
|
68
|
+
* `no answer`). What it has to say (its note, a refused take over) is a
|
|
69
|
+
* mark after the words and the hover, never a line of its own, so the
|
|
70
|
+
* table keeps its height whatever a process says */
|
|
71
|
+
const StateCell = observer(({ m, r }: { m: MachineSt; r: ProcessRow }) => {
|
|
72
|
+
const say = [r.note, r.takeOver?.ok === false ? `cannot take over: ${r.takeOver.why}` : null].filter((x) => x != null)
|
|
73
|
+
const title = [r.since == null ? null : `since ${stampWords(r.since)}`, ...say].filter((x) => x != null).join('\n')
|
|
61
74
|
return (
|
|
62
|
-
<
|
|
63
|
-
<
|
|
64
|
-
|
|
65
|
-
|
|
66
|
-
|
|
67
|
-
|
|
68
|
-
|
|
69
|
-
|
|
70
|
-
|
|
71
|
-
|
|
72
|
-
|
|
73
|
-
|
|
74
|
-
|
|
75
|
-
|
|
76
|
-
|
|
77
|
-
|
|
78
|
-
|
|
79
|
-
|
|
80
|
-
|
|
81
|
-
|
|
82
|
-
|
|
83
|
-
|
|
84
|
-
|
|
85
|
-
|
|
86
|
-
|
|
87
|
-
|
|
75
|
+
<td className={`gpstate ${r.dot}`}>
|
|
76
|
+
<span title={title === '' ? undefined : title}>{stateWords(r, m.clock.now)}</span>
|
|
77
|
+
{say.length > 0 ? <i className="gpwarn" /> : null}
|
|
78
|
+
</td>
|
|
79
|
+
)
|
|
80
|
+
})
|
|
81
|
+
|
|
82
|
+
/** the button columns a table holds: a kind no line needs takes none */
|
|
83
|
+
type BtnCols = { act: string | null; take: boolean }
|
|
84
|
+
|
|
85
|
+
/** one process as one line, like a commit on the activity page: its dot,
|
|
86
|
+
* icon and label (cut to its column, whole with its log on hover), then
|
|
87
|
+
* its two switches as small icons, start or stop (play while stopped, stop
|
|
88
|
+
* while it runs) and restart, each in a column of its own so every play
|
|
89
|
+
* sits under every play, then its url: open in Chrome, open as its own
|
|
90
|
+
* app (a spinner while that app builds), then the url itself, a link to
|
|
91
|
+
* the default browser. Each starts the process first when it is down. Then
|
|
92
|
+
* its version, its pid (a click copies it), its state, and its
|
|
93
|
+
* declared actions and take over. One of shipkit's own wears a core tag and
|
|
94
|
+
* has its stop disabled: it is always on, restarted through pm (pm itself
|
|
95
|
+
* by launchd). A running line is tinted green, a crashed one red */
|
|
96
|
+
const ProcessLine = observer(({ m, r, repoIcon, head, cols }: { m: MachineSt; r: ProcessRow; repoIcon: string | null; head: ReactNode; cols: BtnCols }) => {
|
|
97
|
+
const on = r.status === 'up' || r.status === 'starting'
|
|
98
|
+
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(' · ')
|
|
99
|
+
const log = r.tail.length > 0 ? `${about}\n\n${r.tail.join('\n')}` : null
|
|
100
|
+
const first = r.status === 'up' ? '' : `start ${r.label}, then `
|
|
101
|
+
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`}`
|
|
102
|
+
: on && !r.canStop ? `${r.label} was started outside pm, so pm cannot stop it` : on ? `stop ${r.label}` : `start ${r.label}`
|
|
103
|
+
const stops = on || r.core
|
|
104
|
+
const copied = r.pid != null && m.copiedPid === r.pid
|
|
105
|
+
return (
|
|
106
|
+
<tr className={`gpline ${r.dot}`}>
|
|
107
|
+
{head}
|
|
108
|
+
<td className="gpname">
|
|
109
|
+
<Tip className="gplabel" tipClass="hpop" placement="bottom-start" interactive content={log} data-log={log == null ? undefined : 'yes'}>
|
|
110
|
+
<i className={`hdot ${r.dot}`} /><Mark i={processIcon(r, repoIcon)} /><span title={log != null ? undefined : about}>{r.label}</span>
|
|
111
|
+
{r.core ? <b className="gptag" title="built into shipkit: always on">core</b> : null}
|
|
112
|
+
</Tip>
|
|
113
|
+
</td>
|
|
114
|
+
<td className="gpb stop"><button className={`gpi${stops ? '' : ' go'}`} aria-label={stops ? 'stop' : 'start'} disabled={r.core || r.status === 'stopping' || r.status === 'unknown' || (on && !r.canStop)}
|
|
115
|
+
title={stopTitle} onClick={() => void m.processAct(r.id, on ? 'stop' : 'start')}><Icon d={stops ? ICON.stop : ICON.play} /></button></td>
|
|
116
|
+
<td className="gpb restart">{r.canRestart ? <button className="gpi" aria-label="restart" 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`}><Icon d={ICON.restart} /></button> : null}</td>
|
|
117
|
+
<td className="gphost">
|
|
118
|
+
{r.url == null ? null : r.core ? <span>{r.host}</span> : (
|
|
119
|
+
<>
|
|
120
|
+
<button className="gpi" aria-label="open in Chrome" onClick={() => void m.openProcess(r.id, 'chrome')} title={`${first}open ${r.url} in Chrome`}><Icon d={ICON.browser} /></button>
|
|
121
|
+
<button className="gpi" aria-label="open as an app" disabled={r.appBusy} aria-busy={r.appBusy || undefined} onClick={() => void m.openProcess(r.id, 'app')}
|
|
122
|
+
title={r.appBusy ? `building the ${r.label} app, then opening it` : `${first}open ${r.url} as its own app, ${r.label} in the dock`}>{r.appBusy ? <span className="spin" /> : <Icon d={ICON.window} />}</button>
|
|
123
|
+
<a href={r.url} title={`${first}open ${r.url} in the default browser`} onClick={(e) => {
|
|
124
|
+
if (!plainClick(e)) return
|
|
125
|
+
e.preventDefault()
|
|
126
|
+
void m.openProcess(r.id, 'browser')
|
|
127
|
+
}}>{r.host}</a>
|
|
128
|
+
</>
|
|
129
|
+
)}
|
|
130
|
+
</td>
|
|
131
|
+
<td className="gpver">{r.version ?? ''}</td>
|
|
132
|
+
<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>
|
|
133
|
+
<StateCell m={m} r={r} />
|
|
134
|
+
{cols.act == null ? null : <td className="gpb act">{r.actions.map((a) => (
|
|
135
|
+
<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>
|
|
136
|
+
))}</td>}
|
|
137
|
+
{!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>}
|
|
138
|
+
</tr>
|
|
139
|
+
)
|
|
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 url's header carries the new window button */
|
|
158
|
+
const ProcessTable = observer(({ app, m }: { app: HomeSt; m: MachineSt }) => {
|
|
159
|
+
const groups = m.processGroups
|
|
160
|
+
const rows = groups.flatMap((g) => g.rows)
|
|
161
|
+
// wide enough for the longest set of declared actions, in the button font
|
|
162
|
+
const act = Math.max(0, ...rows.map((r) => r.actions.reduce((n, a) => n + a.label.length + 3, 0)))
|
|
163
|
+
const cols: BtnCols = { act: act === 0 ? null : `${act}ch`, take: rows.some((r) => r.takeOver?.ok === true) }
|
|
164
|
+
return (
|
|
165
|
+
<div className="gpwrap">
|
|
166
|
+
<table className="actable gptable">
|
|
167
|
+
<colgroup>
|
|
168
|
+
<col className="c-name" /><col className="c-gpname" /><col className="c-gpb stop" /><col className="c-gpb restart" /><col className="c-gphost" /><col className="c-gpver" /><col className="c-gppid" /><col className="c-gpstate" />
|
|
169
|
+
{cols.act == null ? null : <col className="c-gpb act" style={{ width: cols.act }} />}
|
|
170
|
+
{cols.take ? <col className="c-gpb take" /> : null}
|
|
171
|
+
</colgroup>
|
|
172
|
+
<thead><tr>
|
|
173
|
+
<th>repo</th><th>process</th><th colSpan={2} />
|
|
174
|
+
<th className="gphost gphead"><div><span>url</span><span className="sp" /><button className="ghost hsm" title="open one more window on this page" onClick={() => m.newWindow()}>new window</button></div></th>
|
|
175
|
+
<th className="gpver">version</th><th className="gppid">pid</th><th className="gpstate">state</th>
|
|
176
|
+
{cols.act == null ? null : <th />}{cols.take ? <th /> : null}
|
|
177
|
+
</tr></thead>
|
|
178
|
+
{groups.map((g) => {
|
|
179
|
+
const icon = g.core ? null : app.projects.list.find((p) => p.name === g.repo)?.icon ?? null
|
|
180
|
+
const head = <td rowSpan={g.rows.length} 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>
|
|
181
|
+
return (
|
|
182
|
+
<tbody key={g.core ? ':core' : g.repo} className="acgrp">
|
|
183
|
+
{g.rows.map((r, i) => <ProcessLine key={r.id} m={m} r={r} repoIcon={icon} head={i === 0 ? head : null} cols={cols} />)}
|
|
184
|
+
</tbody>
|
|
185
|
+
)
|
|
186
|
+
})}
|
|
187
|
+
</table>
|
|
188
|
+
</div>
|
|
88
189
|
)
|
|
89
190
|
})
|
|
90
191
|
|
|
91
|
-
/** every house repo's widgets: the html the daemon last rendered, each under
|
|
192
|
+
/** every house repo's widgets: the html the daemon last rendered, each under
|
|
193
|
+
* its title. An error is a mark on the title, its words on hover, so a card
|
|
194
|
+
* keeps its lines; a card with no html yet says it where the html goes. A
|
|
195
|
+
* widget file that failed to load is one mark after the cards */
|
|
92
196
|
const Monitor = observer(({ m }: { m: MachineSt }) => {
|
|
93
197
|
const w = m.widgets
|
|
94
198
|
if (w == null || (w.widgets.length === 0 && w.errors.length === 0)) return null
|
|
95
199
|
return (
|
|
96
200
|
<section className="hsec">
|
|
97
|
-
<h2><Icon d={ICON.monitor} />monitor</h2>
|
|
98
201
|
<div className="hmon">
|
|
99
|
-
{w.widgets.map((x) =>
|
|
100
|
-
|
|
101
|
-
|
|
102
|
-
{x.
|
|
103
|
-
|
|
104
|
-
|
|
105
|
-
|
|
202
|
+
{w.widgets.map((x) => {
|
|
203
|
+
const { head, body } = splitWidgetHead(x.html ?? '')
|
|
204
|
+
return (
|
|
205
|
+
<section key={`${x.repo}/${x.id}`} className="hwidget" title={`${x.repo}/${x.file}`}>
|
|
206
|
+
<h3>
|
|
207
|
+
{x.title}
|
|
208
|
+
{head == null ? null : <span className="hdim"> · <span dangerouslySetInnerHTML={{ __html: head }} /></span>}
|
|
209
|
+
{x.error != null && x.html != null ? <i className="gpwarn" title={x.error} /> : null}
|
|
210
|
+
</h3>
|
|
211
|
+
{x.html != null ? <div dangerouslySetInnerHTML={{ __html: body }} />
|
|
212
|
+
: x.error != null ? <div className="hwerr">{x.error}</div>
|
|
213
|
+
: <p className="twait"><span className="spin" /></p>}
|
|
214
|
+
</section>
|
|
215
|
+
)
|
|
216
|
+
})}
|
|
217
|
+
{w.errors.length === 0 ? null : (
|
|
218
|
+
<span className="hwerr" title={w.errors.join('\n')}><i className="gpwarn" />{w.errors.length} widget file{w.errors.length === 1 ? '' : 's'} failed</span>
|
|
219
|
+
)}
|
|
106
220
|
</div>
|
|
107
|
-
{w.errors.map((e) => <p key={e} className="hnote">{e}</p>)}
|
|
108
221
|
</section>
|
|
109
222
|
)
|
|
110
223
|
})
|
|
@@ -140,10 +253,11 @@ const ShownRepo = observer(({ app, name }: { app: HomeSt; name: string }) => {
|
|
|
140
253
|
* well: the strip states trouble, never health */
|
|
141
254
|
const MachineAlerts = observer(({ m }: { m: MachineSt }) => (
|
|
142
255
|
<>
|
|
143
|
-
{
|
|
256
|
+
{/* pm silent, its last answer is kept for the table and says nothing here */}
|
|
257
|
+
{(m.pmDown !== '' ? [] : m.pm?.children ?? []).filter((c) => c.status === 'crashed' || c.status === 'down').map((c) => (
|
|
144
258
|
<span key={c.id} className="halert" title={c.note ?? c.tail.at(-1) ?? ''}><Dot s={c.status} />{c.id} {STATUS_WORD[c.status]}</span>
|
|
145
259
|
))}
|
|
146
|
-
{(m.pm?.resources ?? []).filter((r) => r.status === 'crashed').map((r) => <ResourceChip key={r.id} m={m} r={r} />)}
|
|
260
|
+
{(m.pmDown !== '' ? [] : m.pm?.resources ?? []).filter((r) => r.status === 'crashed').map((r) => <ResourceChip key={r.id} m={m} r={r} />)}
|
|
147
261
|
{m.error !== '' ? <span className="herr" title={m.error}>{m.error}</span> : null}
|
|
148
262
|
</>
|
|
149
263
|
))
|
|
@@ -152,36 +266,49 @@ export const HomeStrip = observer(({ app }: { app: HomeSt }) => {
|
|
|
152
266
|
const m = app.machine
|
|
153
267
|
if (m == null) return null
|
|
154
268
|
const name = m.repo
|
|
269
|
+
// the repo's own pieces only while its hub answers: NotUp says why not
|
|
270
|
+
const shown = name == null ? null : app.shown
|
|
271
|
+
const live = shown != null && !notUp(shown) ? shown : null
|
|
155
272
|
return (
|
|
156
273
|
<>
|
|
157
|
-
{m.down !== '' ? <div className="hdown" role="alert"><b>daemon down</b> {m.down}</div> : null}
|
|
158
|
-
{m.pmDown !== '' ? <div className="hdown" role="alert"><b>pm down</b> {m.pmDown}</div> : null}
|
|
159
274
|
<div className="hstrip">
|
|
160
|
-
{name == null ? <span className="hplace">{app.page === '
|
|
275
|
+
{name == null ? <span className="hplace">{app.page === 'overview' ? 'overview' : app.page}</span> : <ShownRepo app={app} name={name} />}
|
|
276
|
+
{live == null ? null : <RepoRun app={live} />}
|
|
161
277
|
<span className="sp" />
|
|
278
|
+
{live == null ? null : <RepoTools app={live} />}
|
|
162
279
|
<MachineAlerts m={m} />
|
|
280
|
+
{live == null ? null : <RepoOpeners app={live} />}
|
|
163
281
|
</div>
|
|
282
|
+
<MachineToasts app={app} />
|
|
164
283
|
</>
|
|
165
284
|
)
|
|
166
285
|
})
|
|
167
286
|
|
|
168
|
-
/**
|
|
169
|
-
*
|
|
170
|
-
|
|
287
|
+
/** the daemon or pm not answering, as toasts floating over the bottom right
|
|
288
|
+
* corner of the page column, just above the dock. They take no room, so
|
|
289
|
+
* one coming or going moves nothing, and nothing is drawn while all is
|
|
290
|
+
* well. Only a failed probe sets down or pmDown: before the first answer
|
|
291
|
+
* there is none */
|
|
292
|
+
export const MachineToasts = observer(({ app }: { app: HomeSt }) => {
|
|
171
293
|
const m = app.machine
|
|
172
|
-
if (m == null) return null
|
|
173
|
-
const w = m.worker(name)
|
|
174
|
-
const behind = m.data?.behind[name]
|
|
294
|
+
if (m == null || (m.down === '' && m.pmDown === '')) return null
|
|
175
295
|
return (
|
|
176
|
-
<
|
|
177
|
-
{
|
|
178
|
-
<
|
|
179
|
-
|
|
180
|
-
|
|
181
|
-
|
|
182
|
-
|
|
183
|
-
|
|
184
|
-
|
|
296
|
+
<div className="htoasts">
|
|
297
|
+
{m.down !== '' ? <div className="htoast" role="alert" title={m.down}><b>daemon down</b> {m.down}</div> : null}
|
|
298
|
+
{m.pmDown !== '' ? <div className="htoast" role="alert" title={m.pmDown}><b>pm down</b> {m.pmDown}</div> : null}
|
|
299
|
+
</div>
|
|
300
|
+
)
|
|
301
|
+
})
|
|
302
|
+
|
|
303
|
+
/** archive the repo or bring it back: `archived` in its repo.config.ts, and
|
|
304
|
+
* only that key. Archived, it stays listed and nothing runs there */
|
|
305
|
+
export const ArchiveButton = observer(({ app, name }: { app: HomeSt; name: string }) => {
|
|
306
|
+
const on = app.projects.isArchived(name)
|
|
307
|
+
return (
|
|
308
|
+
<button className="ghost" onClick={() => void app.projects.setArchived(name, !on)}
|
|
309
|
+
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'}>
|
|
310
|
+
{on ? 'unarchive' : 'archive'}
|
|
311
|
+
</button>
|
|
185
312
|
)
|
|
186
313
|
})
|
|
187
314
|
|
|
@@ -192,6 +319,13 @@ export const NotUp = observer(({ app }: { app: HomeSt }) => {
|
|
|
192
319
|
if (m == null || name == null) return null
|
|
193
320
|
const w = m.shown
|
|
194
321
|
const mod = m.data?.modernize[name]
|
|
322
|
+
if (app.projects.isArchived(name))
|
|
323
|
+
return (
|
|
324
|
+
<div className="hwait">
|
|
325
|
+
<p>{name} is archived: nothing runs there, and only the <HomeLink app={app} to="/archived">archived page</HomeLink> lists it.</p>
|
|
326
|
+
<ArchiveButton app={app} name={name} />
|
|
327
|
+
</div>
|
|
328
|
+
)
|
|
195
329
|
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>
|
|
196
330
|
return (
|
|
197
331
|
<div className="hwait">
|
|
@@ -204,41 +338,48 @@ export const NotUp = observer(({ app }: { app: HomeSt }) => {
|
|
|
204
338
|
)
|
|
205
339
|
})
|
|
206
340
|
|
|
207
|
-
/** the
|
|
208
|
-
*
|
|
341
|
+
/** the archived page at `/archived`: the one place an archived repo is
|
|
342
|
+
* listed, each with its way back */
|
|
343
|
+
export const ArchivedView = observer(({ app }: { app: HomeSt }) => {
|
|
344
|
+
const list = app.projects.archived
|
|
345
|
+
return (
|
|
346
|
+
<div className="hall">
|
|
347
|
+
<section className="hsec">
|
|
348
|
+
<h2><Icon d={ICON.archived} />archived</h2>
|
|
349
|
+
{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}
|
|
350
|
+
{list.map((p) => (
|
|
351
|
+
<div key={p.name} className="hrow">
|
|
352
|
+
<span className="hlink">{p.icon != null ? <img className="hpico" src={p.icon} alt="" width={16} height={16} /> : <i className="hpico" />}{p.name}</span>
|
|
353
|
+
<span className="sp" />
|
|
354
|
+
<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>
|
|
355
|
+
</div>
|
|
356
|
+
))}
|
|
357
|
+
</section>
|
|
358
|
+
</div>
|
|
359
|
+
)
|
|
360
|
+
})
|
|
361
|
+
|
|
362
|
+
/** the overview at `/`: the monitor widgets, then one processes table,
|
|
363
|
+
* shipkit's own (pm, the daemon, the web) its first group. The repos are
|
|
364
|
+
* the project list's, not a section here */
|
|
209
365
|
export const Overview = observer(({ app }: { app: HomeSt }) => {
|
|
210
366
|
const m = app.machine
|
|
211
367
|
if (m == null) return null
|
|
212
|
-
|
|
213
|
-
|
|
368
|
+
// the table waits for pm's first word and for the list's order, so its
|
|
369
|
+
// rows never arrive or reorder after it paints
|
|
370
|
+
const p = app.projects
|
|
371
|
+
if (m.data == null || (m.pm == null && m.pmDown === '') || (p.list.length === 0 && !p.fresh && p.error === '')) return <div className="hwait"><p className="twait"><span className="spin" />{m.down !== '' ? 'the daemon does not answer yet' : 'loading…'}</p></div>
|
|
214
372
|
return (
|
|
215
373
|
<div className="hall">
|
|
216
374
|
<div className="hstack">
|
|
375
|
+
<Monitor m={m} />
|
|
217
376
|
<section className="hsec">
|
|
218
|
-
<
|
|
219
|
-
{m.pm
|
|
220
|
-
<div className="hcards"><section className="hcard">
|
|
221
|
-
<div className="hrow"><span className="hlink"><Dot s="up" />pm</span><span className="hdim">pid {m.pm.pid} · shipkit {m.pm.version}</span></div>
|
|
222
|
-
{m.pm.children.map((c) => (
|
|
223
|
-
<div key={c.id} className="hrow">
|
|
224
|
-
<span className="hlink"><Dot s={c.status} />{c.id}</span>
|
|
225
|
-
<span className="hdim">{STATUS_WORD[c.status]}{c.pid != null ? ` · pid ${c.pid}` : ''}{c.restarts > 0 ? ` · ${c.restarts} restart(s)` : ''}</span>
|
|
226
|
-
{c.note != null ? <span className="hnote">{c.note}</span> : null}
|
|
227
|
-
<span className="sp" />
|
|
228
|
-
<button className="ghost" onClick={() => void m.restartChild(c.id as 'daemon' | 'web')} title={`stop the ${c.id} and start it again, through pm`}>restart</button>
|
|
229
|
-
{c.status === 'crashed' ? <Log lines={c.tail} /> : null}
|
|
230
|
-
</div>
|
|
231
|
-
))}
|
|
232
|
-
</section></div>
|
|
233
|
-
)}
|
|
234
|
-
</section>
|
|
235
|
-
<section className="hsec">
|
|
236
|
-
<h2><Icon d={ICON.process} />global processes</h2>
|
|
237
|
-
{procs.length === 0 ? <p className="hdim">{m.pm == null ? 'unknown while pm does not answer' : 'none declared: a repo declares one in a <name>.shipkit.ts file, as export default globalProcess({...})'}</p> : null}
|
|
238
|
-
<div className="hcards">{procs.map((r) => <ProcessCard key={r.id} m={m} r={r} />)}</div>
|
|
377
|
+
<ProcessTable app={app} m={m} />
|
|
378
|
+
{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}
|
|
239
379
|
{(m.pm?.unitErrors ?? []).map((e) => <p key={e} className="hnote">{e}</p>)}
|
|
240
380
|
</section>
|
|
241
|
-
<
|
|
381
|
+
{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>}
|
|
382
|
+
<p className="hdim">in the console: shipkit overview</p>
|
|
242
383
|
</div>
|
|
243
384
|
</div>
|
|
244
385
|
)
|
|
@@ -9,6 +9,10 @@
|
|
|
9
9
|
<link rel="stylesheet" href="./prose-hits.css" />
|
|
10
10
|
<link rel="stylesheet" href="./home-strip.css" />
|
|
11
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" />
|
|
12
16
|
</head>
|
|
13
17
|
<body data-home>
|
|
14
18
|
<div id="root"></div>
|