183 lines
3.9 KiB
TypeScript
183 lines
3.9 KiB
TypeScript
|
|
interface GateState {
|
||
|
|
canAccept: boolean;
|
||
|
|
reasons: string[];
|
||
|
|
warning: string | null;
|
||
|
|
summary: {
|
||
|
|
total: number;
|
||
|
|
passed: number;
|
||
|
|
failed: number;
|
||
|
|
};
|
||
|
|
missingCategories: string[];
|
||
|
|
}
|
||
|
|
|
||
|
|
interface GatePanelProps {
|
||
|
|
gateState: GateState | null;
|
||
|
|
gateStatusLabel: string;
|
||
|
|
gateStatusColor: string;
|
||
|
|
isGateLoading: boolean;
|
||
|
|
gateError: string | null;
|
||
|
|
lastGateRefresh: Date | null;
|
||
|
|
onRefresh: () => void;
|
||
|
|
}
|
||
|
|
|
||
|
|
const formatTimestamp = (value: Date | null): string => {
|
||
|
|
if (!value) return "—";
|
||
|
|
return value.toLocaleTimeString([], {
|
||
|
|
hour: "2-digit",
|
||
|
|
minute: "2-digit",
|
||
|
|
second: "2-digit",
|
||
|
|
});
|
||
|
|
};
|
||
|
|
|
||
|
|
export function GatePanel({
|
||
|
|
gateState,
|
||
|
|
gateStatusLabel,
|
||
|
|
gateStatusColor,
|
||
|
|
isGateLoading,
|
||
|
|
gateError,
|
||
|
|
lastGateRefresh,
|
||
|
|
onRefresh,
|
||
|
|
}: GatePanelProps) {
|
||
|
|
return (
|
||
|
|
<div
|
||
|
|
style={{
|
||
|
|
border: "1px solid #333",
|
||
|
|
borderRadius: "10px",
|
||
|
|
padding: "12px 16px",
|
||
|
|
background: "#1f1f1f",
|
||
|
|
display: "flex",
|
||
|
|
flexDirection: "column",
|
||
|
|
gap: "8px",
|
||
|
|
}}
|
||
|
|
>
|
||
|
|
<div
|
||
|
|
style={{
|
||
|
|
display: "flex",
|
||
|
|
alignItems: "center",
|
||
|
|
justifyContent: "space-between",
|
||
|
|
gap: "12px",
|
||
|
|
}}
|
||
|
|
>
|
||
|
|
<div
|
||
|
|
style={{
|
||
|
|
display: "flex",
|
||
|
|
alignItems: "center",
|
||
|
|
gap: "8px",
|
||
|
|
}}
|
||
|
|
>
|
||
|
|
<div style={{ fontWeight: 600 }}>Workflow Gates</div>
|
||
|
|
<button
|
||
|
|
type="button"
|
||
|
|
onClick={onRefresh}
|
||
|
|
disabled={isGateLoading}
|
||
|
|
style={{
|
||
|
|
padding: "4px 10px",
|
||
|
|
borderRadius: "999px",
|
||
|
|
border: "1px solid #333",
|
||
|
|
background: isGateLoading ? "#2a2a2a" : "#2f2f2f",
|
||
|
|
color: isGateLoading ? "#777" : "#aaa",
|
||
|
|
cursor: isGateLoading ? "not-allowed" : "pointer",
|
||
|
|
fontSize: "0.75em",
|
||
|
|
fontWeight: 600,
|
||
|
|
}}
|
||
|
|
>
|
||
|
|
Refresh
|
||
|
|
</button>
|
||
|
|
</div>
|
||
|
|
<div
|
||
|
|
style={{
|
||
|
|
display: "flex",
|
||
|
|
flexDirection: "column",
|
||
|
|
alignItems: "flex-end",
|
||
|
|
gap: "2px",
|
||
|
|
fontSize: "0.85em",
|
||
|
|
color: gateStatusColor,
|
||
|
|
}}
|
||
|
|
>
|
||
|
|
<div>{gateStatusLabel}</div>
|
||
|
|
<div style={{ fontSize: "0.8em", color: "#777" }}>
|
||
|
|
Updated {formatTimestamp(lastGateRefresh)}
|
||
|
|
</div>
|
||
|
|
</div>
|
||
|
|
</div>
|
||
|
|
|
||
|
|
{isGateLoading ? (
|
||
|
|
<div style={{ fontSize: "0.85em", color: "#aaa" }}>
|
||
|
|
Loading workflow gates...
|
||
|
|
</div>
|
||
|
|
) : gateError ? (
|
||
|
|
<div
|
||
|
|
style={{
|
||
|
|
fontSize: "0.85em",
|
||
|
|
color: "#ff7b72",
|
||
|
|
display: "flex",
|
||
|
|
alignItems: "center",
|
||
|
|
gap: "8px",
|
||
|
|
flexWrap: "wrap",
|
||
|
|
}}
|
||
|
|
>
|
||
|
|
<span>{gateError}</span>
|
||
|
|
<button
|
||
|
|
type="button"
|
||
|
|
onClick={onRefresh}
|
||
|
|
disabled={isGateLoading}
|
||
|
|
style={{
|
||
|
|
padding: "4px 10px",
|
||
|
|
borderRadius: "999px",
|
||
|
|
border: "1px solid #333",
|
||
|
|
background: isGateLoading ? "#2a2a2a" : "#2f2f2f",
|
||
|
|
color: isGateLoading ? "#777" : "#aaa",
|
||
|
|
cursor: isGateLoading ? "not-allowed" : "pointer",
|
||
|
|
fontSize: "0.75em",
|
||
|
|
fontWeight: 600,
|
||
|
|
}}
|
||
|
|
>
|
||
|
|
Retry
|
||
|
|
</button>
|
||
|
|
</div>
|
||
|
|
) : gateState ? (
|
||
|
|
<div
|
||
|
|
style={{
|
||
|
|
display: "flex",
|
||
|
|
flexDirection: "column",
|
||
|
|
gap: "6px",
|
||
|
|
}}
|
||
|
|
>
|
||
|
|
<div style={{ fontSize: "0.85em", color: "#aaa" }}>
|
||
|
|
Summary: {gateState.summary.passed}/{gateState.summary.total}{" "}
|
||
|
|
passing, {gateState.summary.failed} failing
|
||
|
|
</div>
|
||
|
|
{gateState.missingCategories.length > 0 && (
|
||
|
|
<div style={{ fontSize: "0.85em", color: "#ffb86c" }}>
|
||
|
|
Missing: {gateState.missingCategories.join(", ")}
|
||
|
|
</div>
|
||
|
|
)}
|
||
|
|
{gateState.warning && (
|
||
|
|
<div style={{ fontSize: "0.85em", color: "#ffb86c" }}>
|
||
|
|
{gateState.warning}
|
||
|
|
</div>
|
||
|
|
)}
|
||
|
|
{gateState.reasons.length > 0 && (
|
||
|
|
<ul
|
||
|
|
style={{
|
||
|
|
margin: "0 0 0 16px",
|
||
|
|
padding: 0,
|
||
|
|
fontSize: "0.85em",
|
||
|
|
color: "#ccc",
|
||
|
|
}}
|
||
|
|
>
|
||
|
|
{gateState.reasons.map((reason) => (
|
||
|
|
<li key={`gate-reason-${reason}`}>{reason}</li>
|
||
|
|
))}
|
||
|
|
</ul>
|
||
|
|
)}
|
||
|
|
</div>
|
||
|
|
) : (
|
||
|
|
<div style={{ fontSize: "0.85em", color: "#aaa" }}>
|
||
|
|
No workflow data yet.
|
||
|
|
</div>
|
||
|
|
)}
|
||
|
|
</div>
|
||
|
|
);
|
||
|
|
}
|