Distinguish services from scripts with type system and runtime tracking
This commit is contained in:
@@ -355,12 +355,43 @@ function WorkspaceHoverCardContent({
|
||||
<View style={styles.sectionList} testID="hover-card-script-list">
|
||||
{workspace.scripts.map((script) => {
|
||||
const isRunning = script.lifecycle === "running";
|
||||
const isLinkable = isRunning && !!script.url;
|
||||
const isService = (script.type ?? "service") === "service";
|
||||
const isLinkable = isService && isRunning && !!script.url;
|
||||
const exitCode = script.exitCode ?? null;
|
||||
|
||||
let dotColor: string;
|
||||
if (isService) {
|
||||
dotColor = isRunning
|
||||
? getScriptHealthColor({ health: script.health, theme })
|
||||
: theme.colors.foregroundMuted;
|
||||
} else if (isRunning) {
|
||||
dotColor = theme.colors.palette.green[500];
|
||||
} else if (exitCode === 0) {
|
||||
dotColor = theme.colors.palette.green[500];
|
||||
} else if (exitCode !== null) {
|
||||
dotColor = theme.colors.palette.red[500];
|
||||
} else {
|
||||
dotColor = theme.colors.foregroundMuted;
|
||||
}
|
||||
|
||||
let accessibilityStatus: string;
|
||||
if (isService) {
|
||||
accessibilityStatus = isRunning ? getScriptHealthLabel(script.health) : "Stopped";
|
||||
} else if (isRunning) {
|
||||
accessibilityStatus = "Running";
|
||||
} else if (exitCode === 0) {
|
||||
accessibilityStatus = "Completed";
|
||||
} else if (exitCode !== null) {
|
||||
accessibilityStatus = `Failed (exit ${exitCode})`;
|
||||
} else {
|
||||
accessibilityStatus = "Stopped";
|
||||
}
|
||||
|
||||
return (
|
||||
<Pressable
|
||||
key={script.hostname}
|
||||
accessibilityRole={isLinkable ? "link" : undefined}
|
||||
accessibilityLabel={`${script.scriptName} script — ${isRunning ? getScriptHealthLabel(script.health) : "Stopped"}`}
|
||||
accessibilityLabel={`${script.scriptName} script — ${accessibilityStatus}`}
|
||||
testID={`hover-card-script-${script.scriptName}`}
|
||||
style={({ hovered }) => [
|
||||
styles.listRow,
|
||||
@@ -375,11 +406,7 @@ function WorkspaceHoverCardContent({
|
||||
testID={`hover-card-script-health-${script.scriptName}`}
|
||||
style={[
|
||||
styles.statusDot,
|
||||
{
|
||||
backgroundColor: isRunning
|
||||
? getScriptHealthColor({ health: script.health, theme })
|
||||
: theme.colors.foregroundMuted,
|
||||
},
|
||||
{ backgroundColor: dotColor },
|
||||
]}
|
||||
/>
|
||||
<Text
|
||||
@@ -395,15 +422,19 @@ function WorkspaceHoverCardContent({
|
||||
>
|
||||
{script.scriptName}
|
||||
</Text>
|
||||
{isRunning && script.url ? (
|
||||
{isService && isRunning && script.url ? (
|
||||
<Text style={styles.listRowSecondary} numberOfLines={1}>
|
||||
{script.url.replace(/^https?:\/\//, "")}
|
||||
</Text>
|
||||
) : !isService && !isRunning && exitCode !== null && exitCode !== 0 ? (
|
||||
<Text style={styles.listRowSecondary} numberOfLines={1}>
|
||||
exit {exitCode}
|
||||
</Text>
|
||||
) : (
|
||||
<View style={styles.listRowSpacer} />
|
||||
)}
|
||||
{isRunning ? (
|
||||
script.url && hovered ? (
|
||||
isLinkable && hovered ? (
|
||||
<View
|
||||
style={[
|
||||
styles.externalLinkOverlay,
|
||||
|
||||
@@ -25,11 +25,13 @@ function workspace(input: {
|
||||
|
||||
const runningScript: WorkspaceScriptPayload = {
|
||||
scriptName: "web",
|
||||
type: "service",
|
||||
hostname: "main.web.localhost",
|
||||
port: 3000,
|
||||
url: "http://main.web.localhost:6767",
|
||||
lifecycle: "running",
|
||||
health: "healthy",
|
||||
exitCode: null,
|
||||
};
|
||||
|
||||
describe("patchWorkspaceScripts", () => {
|
||||
|
||||
@@ -47,20 +47,24 @@ function workspace(
|
||||
|
||||
const runningScript: WorkspaceScriptPayload = {
|
||||
scriptName: "web",
|
||||
type: "service",
|
||||
hostname: "main.web.localhost",
|
||||
port: 3000,
|
||||
url: "http://main.web.localhost:6767",
|
||||
lifecycle: "running",
|
||||
health: "healthy",
|
||||
exitCode: null,
|
||||
};
|
||||
|
||||
const stoppedScript: WorkspaceScriptPayload = {
|
||||
scriptName: "api",
|
||||
type: "service",
|
||||
hostname: "main.api.localhost",
|
||||
port: 3001,
|
||||
url: "http://main.api.localhost:6767",
|
||||
lifecycle: "stopped",
|
||||
health: null,
|
||||
exitCode: null,
|
||||
};
|
||||
|
||||
describe("applyStoredOrdering", () => {
|
||||
|
||||
@@ -104,7 +104,24 @@ export function WorkspaceScriptsButton({
|
||||
<View style={styles.scriptList}>
|
||||
{scripts.map((script) => {
|
||||
const isRunning = script.lifecycle === "running";
|
||||
const isLinkable = isRunning && !!script.url;
|
||||
const isService = (script.type ?? "service") === "service";
|
||||
const isLinkable = isService && isRunning && !!script.url;
|
||||
const exitCode = script.exitCode ?? null;
|
||||
|
||||
let dotColor: string;
|
||||
if (isService) {
|
||||
dotColor = isRunning
|
||||
? getScriptHealthColor(script.health, theme)
|
||||
: theme.colors.foregroundMuted;
|
||||
} else if (isRunning) {
|
||||
dotColor = theme.colors.palette.green[500];
|
||||
} else if (exitCode === 0) {
|
||||
dotColor = theme.colors.palette.green[500];
|
||||
} else if (exitCode !== null) {
|
||||
dotColor = theme.colors.palette.red[500];
|
||||
} else {
|
||||
dotColor = theme.colors.foregroundMuted;
|
||||
}
|
||||
|
||||
return (
|
||||
<Pressable
|
||||
@@ -124,11 +141,7 @@ export function WorkspaceScriptsButton({
|
||||
<View
|
||||
style={[
|
||||
styles.statusDot,
|
||||
{
|
||||
backgroundColor: isRunning
|
||||
? getScriptHealthColor(script.health, theme)
|
||||
: theme.colors.foregroundMuted,
|
||||
},
|
||||
{ backgroundColor: dotColor },
|
||||
]}
|
||||
/>
|
||||
<Text
|
||||
@@ -144,15 +157,19 @@ export function WorkspaceScriptsButton({
|
||||
>
|
||||
{script.scriptName}
|
||||
</Text>
|
||||
{isRunning && script.url ? (
|
||||
{isService && isRunning && script.url ? (
|
||||
<Text style={styles.scriptUrl} numberOfLines={1}>
|
||||
{script.url.replace(/^https?:\/\//, "")}
|
||||
</Text>
|
||||
) : !isService && !isRunning && exitCode !== null && exitCode !== 0 ? (
|
||||
<Text style={styles.scriptUrl} numberOfLines={1}>
|
||||
exit {exitCode}
|
||||
</Text>
|
||||
) : (
|
||||
<View style={styles.spacer} />
|
||||
)}
|
||||
{isRunning ? (
|
||||
script.url && hovered ? (
|
||||
isLinkable && hovered ? (
|
||||
<View
|
||||
style={[
|
||||
styles.externalLinkOverlay,
|
||||
|
||||
@@ -38,11 +38,13 @@ describe("normalizeWorkspaceDescriptor", () => {
|
||||
const scripts = [
|
||||
{
|
||||
scriptName: "web",
|
||||
type: "service" as const,
|
||||
hostname: "main.web.localhost",
|
||||
port: 3000,
|
||||
url: "http://main.web.localhost:6767",
|
||||
lifecycle: "running" as const,
|
||||
health: "healthy" as const,
|
||||
exitCode: null,
|
||||
},
|
||||
];
|
||||
const workspace = normalizeWorkspaceDescriptor({
|
||||
@@ -64,11 +66,13 @@ describe("normalizeWorkspaceDescriptor", () => {
|
||||
expect(workspace.scripts).toEqual([
|
||||
{
|
||||
scriptName: "web",
|
||||
type: "service",
|
||||
hostname: "main.web.localhost",
|
||||
port: 3000,
|
||||
url: "http://main.web.localhost:6767",
|
||||
lifecycle: "running",
|
||||
health: "healthy",
|
||||
exitCode: null,
|
||||
},
|
||||
]);
|
||||
expect(workspace.scripts).not.toBe(scripts);
|
||||
@@ -111,11 +115,13 @@ describe("mergeWorkspaces", () => {
|
||||
scripts: [
|
||||
{
|
||||
scriptName: "web",
|
||||
type: "service",
|
||||
hostname: "main.web.localhost",
|
||||
port: 3000,
|
||||
url: "http://main.web.localhost:6767",
|
||||
lifecycle: "running",
|
||||
health: "healthy",
|
||||
exitCode: null,
|
||||
},
|
||||
],
|
||||
}),
|
||||
@@ -124,11 +130,13 @@ describe("mergeWorkspaces", () => {
|
||||
expect(store.getSession("test-server")?.workspaces.get("/repo/main")?.scripts).toEqual([
|
||||
{
|
||||
scriptName: "web",
|
||||
type: "service",
|
||||
hostname: "main.web.localhost",
|
||||
port: 3000,
|
||||
url: "http://main.web.localhost:6767",
|
||||
lifecycle: "running",
|
||||
health: "healthy",
|
||||
exitCode: null,
|
||||
},
|
||||
]);
|
||||
});
|
||||
|
||||
Reference in New Issue
Block a user