fix
This commit is contained in:
179
src/screens/RunLog.jsx
Normal file
179
src/screens/RunLog.jsx
Normal file
@@ -0,0 +1,179 @@
|
||||
import React, { useEffect, useReducer } from "react";
|
||||
import { Box, Text } from "ink";
|
||||
import Spinner from "ink-spinner";
|
||||
import SelectInput from "ink-select-input";
|
||||
|
||||
import { startRun } from "../lib/runner.js";
|
||||
|
||||
const MAX_LOG_LINES = 12;
|
||||
|
||||
function reducer(state, action) {
|
||||
switch (action.type) {
|
||||
case "step-start":
|
||||
return {
|
||||
...state,
|
||||
currentIndex: action.index,
|
||||
statuses: { ...state.statuses, [action.index]: "running" },
|
||||
};
|
||||
case "step-done":
|
||||
return {
|
||||
...state,
|
||||
statuses: { ...state.statuses, [action.index]: action.status },
|
||||
errors:
|
||||
action.error != null
|
||||
? { ...state.errors, [action.index]: action.error }
|
||||
: state.errors,
|
||||
};
|
||||
case "log": {
|
||||
const next = [...state.log, action];
|
||||
if (next.length > MAX_LOG_LINES * 4) {
|
||||
next.splice(0, next.length - MAX_LOG_LINES * 4);
|
||||
}
|
||||
return { ...state, log: next };
|
||||
}
|
||||
case "done":
|
||||
return { ...state, finished: true, success: action.success, cancelled: !!action.cancelled };
|
||||
default:
|
||||
return state;
|
||||
}
|
||||
}
|
||||
|
||||
export default function RunLog({ nav, plan, origin }) {
|
||||
const [state, dispatch] = useReducer(reducer, {
|
||||
statuses: {},
|
||||
errors: {},
|
||||
currentIndex: -1,
|
||||
log: [],
|
||||
finished: false,
|
||||
success: false,
|
||||
cancelled: false,
|
||||
});
|
||||
|
||||
useEffect(() => {
|
||||
const run = startRun(plan.steps);
|
||||
run.on("event", (event) => {
|
||||
dispatch(event);
|
||||
});
|
||||
return () => run.cancel();
|
||||
}, [plan]);
|
||||
|
||||
const recentLog = state.log.slice(-MAX_LOG_LINES);
|
||||
|
||||
return (
|
||||
<Box flexDirection="column">
|
||||
<Text bold>{plan.title}</Text>
|
||||
<Box flexDirection="column" marginTop={1}>
|
||||
{plan.steps.map((step, index) => (
|
||||
<StepRow
|
||||
key={index}
|
||||
index={index}
|
||||
step={step}
|
||||
status={state.statuses[index]}
|
||||
error={state.errors[index]}
|
||||
/>
|
||||
))}
|
||||
</Box>
|
||||
{recentLog.length > 0 ? (
|
||||
<Box flexDirection="column" marginTop={1} borderStyle="single" borderColor="gray" paddingX={1}>
|
||||
{recentLog.map((entry, i) => (
|
||||
<Text key={i} color={entry.stream === "stderr" ? "yellow" : undefined} dimColor>
|
||||
{entry.text}
|
||||
</Text>
|
||||
))}
|
||||
</Box>
|
||||
) : null}
|
||||
{state.finished ? <FinishedFooter state={state} nav={nav} origin={origin} /> : (
|
||||
<Box marginTop={1}>
|
||||
<Text color="cyan">
|
||||
<Spinner type="dots" />
|
||||
</Text>
|
||||
<Text> running…</Text>
|
||||
</Box>
|
||||
)}
|
||||
</Box>
|
||||
);
|
||||
}
|
||||
|
||||
function StepRow({ index, step, status, error }) {
|
||||
const symbol = symbolFor(status);
|
||||
const color = colorFor(status);
|
||||
return (
|
||||
<Box>
|
||||
<Box width={3}>
|
||||
<Text color={color}>{symbol}</Text>
|
||||
</Box>
|
||||
<Box width={3}>
|
||||
<Text dimColor>{`${index + 1}.`}</Text>
|
||||
</Box>
|
||||
<Box flexDirection="column">
|
||||
<Text>{step.label}</Text>
|
||||
{error ? <Text color="red"> {error}</Text> : null}
|
||||
</Box>
|
||||
</Box>
|
||||
);
|
||||
}
|
||||
|
||||
function symbolFor(status) {
|
||||
switch (status) {
|
||||
case "running":
|
||||
return "◐";
|
||||
case "ok":
|
||||
return "✓";
|
||||
case "failed":
|
||||
return "✗";
|
||||
case "cancelled":
|
||||
return "⊘";
|
||||
default:
|
||||
return "·";
|
||||
}
|
||||
}
|
||||
|
||||
function colorFor(status) {
|
||||
switch (status) {
|
||||
case "running":
|
||||
return "cyan";
|
||||
case "ok":
|
||||
return "green";
|
||||
case "failed":
|
||||
return "red";
|
||||
case "cancelled":
|
||||
return "yellow";
|
||||
default:
|
||||
return "gray";
|
||||
}
|
||||
}
|
||||
|
||||
function FinishedFooter({ state, nav, origin }) {
|
||||
const items = [
|
||||
{ label: "Back to main menu", value: "home" },
|
||||
...(origin === "frp" ? [{ label: "Back to FRP menu", value: "frp" }] : []),
|
||||
{ label: "Quit", value: "quit" },
|
||||
];
|
||||
|
||||
const handleSelect = (item) => {
|
||||
if (item.value === "home") {
|
||||
nav.home();
|
||||
} else if (item.value === "frp") {
|
||||
nav.replace({ name: "frp" });
|
||||
// collapse stack down to just main + frp
|
||||
// (replace just swaps top; we want clean stack)
|
||||
} else if (item.value === "quit") {
|
||||
nav.exit();
|
||||
}
|
||||
};
|
||||
|
||||
return (
|
||||
<Box flexDirection="column" marginTop={1}>
|
||||
{state.cancelled ? (
|
||||
<Text color="yellow">Cancelled.</Text>
|
||||
) : state.success ? (
|
||||
<Text color="green">All steps completed successfully.</Text>
|
||||
) : (
|
||||
<Text color="red">Run failed.</Text>
|
||||
)}
|
||||
<Box marginTop={1}>
|
||||
<SelectInput items={items} onSelect={handleSelect} />
|
||||
</Box>
|
||||
</Box>
|
||||
);
|
||||
}
|
||||
Reference in New Issue
Block a user