0.2.78
This commit is contained in:
@@ -0,0 +1,82 @@
|
||||
import { Icon } from './Icon';
|
||||
import { Tag } from './ui';
|
||||
import type { Tone } from '../lib/format';
|
||||
import type { RuntimeHealth, RuntimeLevel, RuntimeTrend } from '../api/types';
|
||||
|
||||
/* The two places the process figures are read — the overview's Process card and the
|
||||
Runtime page — say the same things about them, so the wording lives here rather than
|
||||
twice. None of it decides anything: the verdict, its sentences and the areas to
|
||||
investigate all arrive from the gateway, and this renders what it was handed. */
|
||||
|
||||
export const levelTone: Record<RuntimeLevel, Tone> = {
|
||||
ok: 'ok',
|
||||
watch: 'warn',
|
||||
bad: 'bad',
|
||||
};
|
||||
|
||||
const levelWord: Record<RuntimeLevel, string> = {
|
||||
ok: 'Healthy',
|
||||
watch: 'Worth watching',
|
||||
bad: 'Needs attention',
|
||||
};
|
||||
|
||||
/** RuntimeVerdict is the headline. It replaces a bare goroutine count with the answer that
|
||||
* count was standing in for, and when there is something to look at it names the area
|
||||
* rather than leaving an operator to work out which number matters. */
|
||||
export function RuntimeVerdict({
|
||||
health,
|
||||
compact,
|
||||
}: {
|
||||
health: RuntimeHealth;
|
||||
compact?: boolean;
|
||||
}) {
|
||||
const notes = health.notes ?? [];
|
||||
return (
|
||||
<div className="verdict" data-tone={levelTone[health.level] ?? 'ok'}>
|
||||
<div className="verdict-head">
|
||||
<Icon name={health.level === 'ok' ? 'check' : 'alert'} />
|
||||
<b>{levelWord[health.level] ?? 'Unknown'}</b>
|
||||
<span>{health.summary}</span>
|
||||
</div>
|
||||
{/* On the overview the summary is the whole of it — the card is a signpost, and the
|
||||
notes belong on the page it points at. */}
|
||||
{compact || notes.length === 0 ? null : (
|
||||
<ul className="verdict-notes">
|
||||
{notes.map((note, index) => (
|
||||
<li key={`${note.area}-${index}`}>
|
||||
<Tag tone={levelTone[note.level] ?? 'note'}>{note.area}</Tag>
|
||||
<span>{note.message}</span>
|
||||
</li>
|
||||
))}
|
||||
</ul>
|
||||
)}
|
||||
</div>
|
||||
);
|
||||
}
|
||||
|
||||
/** trendWord turns a trend into the half-sentence that sits under a figure. "Steady" is
|
||||
* worth printing: it is the answer to the question the figure raises, and a tile that
|
||||
* says nothing when nothing is wrong makes the operator check anyway. */
|
||||
export function trendWord(trend: RuntimeTrend, format: (value: number) => string): string {
|
||||
switch (trend.direction) {
|
||||
case 'rising':
|
||||
return `rising ${format(Math.abs(trend.perHour))} an hour`;
|
||||
case 'falling':
|
||||
return `falling ${format(Math.abs(trend.perHour))} an hour`;
|
||||
case 'steady':
|
||||
return 'steady';
|
||||
default:
|
||||
return 'gathering history';
|
||||
}
|
||||
}
|
||||
|
||||
/** uptime words how long the process has been up. Days matter here and seconds do not: the
|
||||
* question this answers is "has it restarted", not "how long exactly". */
|
||||
export function uptime(seconds: number): string {
|
||||
if (!seconds || seconds < 60) return `${Math.max(0, Math.round(seconds))}s`;
|
||||
const minutes = Math.floor(seconds / 60);
|
||||
if (minutes < 60) return `${minutes} min`;
|
||||
const hours = Math.floor(minutes / 60);
|
||||
if (hours < 48) return `${hours}h ${minutes % 60}m`;
|
||||
return `${Math.floor(hours / 24)} days`;
|
||||
}
|
||||
Reference in New Issue
Block a user