@stigmer/react 0.0.92 → 0.0.94
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/README.md +57 -12
- package/agent/AgentDetailView.js +7 -7
- package/agent/AgentDetailView.js.map +1 -1
- package/agent/AgentPicker.js +3 -3
- package/agent/AgentPicker.js.map +1 -1
- package/api-key/ApiKeyCreatedAlert.js +1 -1
- package/api-key/ApiKeyCreatedAlert.js.map +1 -1
- package/api-key/ApiKeyListPanel.js +4 -4
- package/api-key/ApiKeyListPanel.js.map +1 -1
- package/api-key/CreateApiKeyForm.js +1 -1
- package/api-key/CreateApiKeyForm.js.map +1 -1
- package/attachment/AttachmentChipList.js +2 -2
- package/attachment/AttachmentChipList.js.map +1 -1
- package/color-mode.d.ts +48 -0
- package/color-mode.d.ts.map +1 -0
- package/color-mode.js +53 -0
- package/color-mode.js.map +1 -0
- package/composer/ComposerToolbar.d.ts +6 -2
- package/composer/ComposerToolbar.d.ts.map +1 -1
- package/composer/ComposerToolbar.js +5 -3
- package/composer/ComposerToolbar.js.map +1 -1
- package/composer/ConfigureMenu.js +3 -3
- package/composer/ConfigureMenu.js.map +1 -1
- package/composer/ContextChip.js +1 -1
- package/composer/ContextChip.js.map +1 -1
- package/composer/ContextPopover.js +1 -1
- package/composer/ContextPopover.js.map +1 -1
- package/composer/SessionComposer.d.ts +24 -3
- package/composer/SessionComposer.d.ts.map +1 -1
- package/composer/SessionComposer.js +5 -4
- package/composer/SessionComposer.js.map +1 -1
- package/environment/CreateEnvironmentForm.js +1 -1
- package/environment/CreateEnvironmentForm.js.map +1 -1
- package/environment/EnvVarForm.js +2 -2
- package/environment/EnvVarForm.js.map +1 -1
- package/environment/EnvironmentListPanel.js +2 -2
- package/environment/EnvironmentListPanel.js.map +1 -1
- package/environment/EnvironmentVariableEditor.js +6 -6
- package/environment/EnvironmentVariableEditor.js.map +1 -1
- package/error/ErrorMessage.js +1 -1
- package/error/ErrorMessage.js.map +1 -1
- package/execution/ApprovalCard.js +4 -4
- package/execution/ApprovalCard.js.map +1 -1
- package/execution/ArtifactCard.js +1 -1
- package/execution/ArtifactCard.js.map +1 -1
- package/execution/ArtifactPreviewModal.js +4 -4
- package/execution/ArtifactPreviewModal.js.map +1 -1
- package/execution/FollowUpInput.js +1 -1
- package/execution/FollowUpInput.js.map +1 -1
- package/execution/McpToolDetail.js +4 -4
- package/execution/McpToolDetail.js.map +1 -1
- package/execution/MessageEntry.js +1 -1
- package/execution/MessageEntry.js.map +1 -1
- package/execution/MessageThread.js +1 -1
- package/execution/MessageThread.js.map +1 -1
- package/execution/SessionVariablesInput.js +4 -4
- package/execution/SessionVariablesInput.js.map +1 -1
- package/execution/SubAgentSection.js +2 -2
- package/execution/SubAgentSection.js.map +1 -1
- package/execution/ToolCallDetail.js +2 -2
- package/execution/ToolCallDetail.js.map +1 -1
- package/execution/ToolCallGroup.js +1 -1
- package/execution/ToolCallGroup.js.map +1 -1
- package/execution/ToolCallItem.js +2 -2
- package/execution/ToolCallItem.js.map +1 -1
- package/execution/WriteBackCard.js +3 -3
- package/execution/WriteBackCard.js.map +1 -1
- package/execution/tool-rendering-primitives.js +3 -3
- package/execution/tool-rendering-primitives.js.map +1 -1
- package/github/GitHubRepoPicker.js +5 -5
- package/github/GitHubRepoPicker.js.map +1 -1
- package/iam-policy/GrantAccessForm.js +1 -1
- package/iam-policy/GrantAccessForm.js.map +1 -1
- package/iam-policy/OrgMembersPanel.js +5 -5
- package/iam-policy/OrgMembersPanel.js.map +1 -1
- package/identity-provider/CreateIdentityProviderForm.js +3 -3
- package/identity-provider/CreateIdentityProviderForm.js.map +1 -1
- package/identity-provider/IdentityProviderDetailPanel.js +5 -5
- package/identity-provider/IdentityProviderDetailPanel.js.map +1 -1
- package/identity-provider/IdentityProviderListPanel.js +3 -3
- package/identity-provider/IdentityProviderListPanel.js.map +1 -1
- package/identity-provider/IdentityProviderWizard.js +7 -7
- package/identity-provider/IdentityProviderWizard.js.map +1 -1
- package/identity-provider/ProviderPicker.js +2 -2
- package/identity-provider/ProviderPicker.js.map +1 -1
- package/index.d.ts +8 -4
- package/index.d.ts.map +1 -1
- package/index.js +7 -3
- package/index.js.map +1 -1
- package/internal/CloudFeatureNotice.js +1 -1
- package/internal/CloudFeatureNotice.js.map +1 -1
- package/internal/Tabs.js +1 -1
- package/internal/Tabs.js.map +1 -1
- package/internal/markdown-components.js +2 -2
- package/internal/markdown-components.js.map +1 -1
- package/invitation/InvitationCreatedAlert.js +1 -1
- package/invitation/InvitationCreatedAlert.js.map +1 -1
- package/invitation/InvitationManager.js +5 -5
- package/invitation/InvitationManager.js.map +1 -1
- package/invitation/InvitationRedemption.js +4 -4
- package/invitation/InvitationRedemption.js.map +1 -1
- package/library/ResourceCountCard.js +1 -1
- package/library/ResourceCountCard.js.map +1 -1
- package/library/ResourceListView.js +5 -5
- package/library/ResourceListView.js.map +1 -1
- package/mcp-server/McpServerConfigPanel.js +5 -5
- package/mcp-server/McpServerConfigPanel.js.map +1 -1
- package/mcp-server/McpServerConnectDialog.js +4 -4
- package/mcp-server/McpServerConnectDialog.js.map +1 -1
- package/mcp-server/McpServerDetailView.js +14 -14
- package/mcp-server/McpServerDetailView.js.map +1 -1
- package/mcp-server/McpServerPicker.js +4 -4
- package/mcp-server/McpServerPicker.js.map +1 -1
- package/mcp-server/McpToolSelector.js +3 -3
- package/mcp-server/McpToolSelector.js.map +1 -1
- package/mcp-server/OAuthAppForm.js +1 -1
- package/mcp-server/OAuthAppForm.js.map +1 -1
- package/models/ModelSelector.js +1 -1
- package/models/ModelSelector.js.map +1 -1
- package/oauth-app/CreateOAuthAppForm.js +1 -1
- package/oauth-app/CreateOAuthAppForm.js.map +1 -1
- package/oauth-app/OAuthAppDetailPanel.js +3 -3
- package/oauth-app/OAuthAppDetailPanel.js.map +1 -1
- package/oauth-app/OAuthAppListPanel.js +2 -2
- package/oauth-app/OAuthAppListPanel.js.map +1 -1
- package/organization/CreateOrganizationForm.js +1 -1
- package/organization/CreateOrganizationForm.js.map +1 -1
- package/organization/OrgProfilePanel.js +3 -3
- package/organization/OrgProfilePanel.js.map +1 -1
- package/package.json +4 -4
- package/platform-client/CreatePlatformClientForm.js +2 -2
- package/platform-client/CreatePlatformClientForm.js.map +1 -1
- package/platform-client/PlatformClientDetailPanel.js +4 -4
- package/platform-client/PlatformClientDetailPanel.js.map +1 -1
- package/platform-client/PlatformClientListPanel.js +3 -3
- package/platform-client/PlatformClientListPanel.js.map +1 -1
- package/platform-client/PlatformClientSecretAlert.js +2 -2
- package/platform-client/PlatformClientSecretAlert.js.map +1 -1
- package/provider.d.ts +39 -2
- package/provider.d.ts.map +1 -1
- package/provider.js +11 -3
- package/provider.js.map +1 -1
- package/runner/RunnerListPanel.d.ts +65 -0
- package/runner/RunnerListPanel.d.ts.map +1 -0
- package/runner/RunnerListPanel.js +237 -0
- package/runner/RunnerListPanel.js.map +1 -0
- package/runner/RunnerPicker.d.ts +54 -0
- package/runner/RunnerPicker.d.ts.map +1 -0
- package/runner/RunnerPicker.js +133 -0
- package/runner/RunnerPicker.js.map +1 -0
- package/runner/__tests__/useDeleteRunner.test.d.ts +2 -0
- package/runner/__tests__/useDeleteRunner.test.d.ts.map +1 -0
- package/runner/__tests__/useDeleteRunner.test.js +108 -0
- package/runner/__tests__/useDeleteRunner.test.js.map +1 -0
- package/runner/__tests__/useLaunchLocalRunner.test.d.ts +2 -0
- package/runner/__tests__/useLaunchLocalRunner.test.d.ts.map +1 -0
- package/runner/__tests__/useLaunchLocalRunner.test.js +143 -0
- package/runner/__tests__/useLaunchLocalRunner.test.js.map +1 -0
- package/runner/__tests__/useStopRunner.test.d.ts +2 -0
- package/runner/__tests__/useStopRunner.test.d.ts.map +1 -0
- package/runner/__tests__/useStopRunner.test.js +114 -0
- package/runner/__tests__/useStopRunner.test.js.map +1 -0
- package/runner/index.d.ts +14 -0
- package/runner/index.d.ts.map +1 -0
- package/runner/index.js +8 -0
- package/runner/index.js.map +1 -0
- package/runner/phase.d.ts +30 -0
- package/runner/phase.d.ts.map +1 -0
- package/runner/phase.js +58 -0
- package/runner/phase.js.map +1 -0
- package/runner/useDeleteRunner.d.ts +36 -0
- package/runner/useDeleteRunner.d.ts.map +1 -0
- package/runner/useDeleteRunner.js +42 -0
- package/runner/useDeleteRunner.js.map +1 -0
- package/runner/useLaunchLocalRunner.d.ts +84 -0
- package/runner/useLaunchLocalRunner.d.ts.map +1 -0
- package/runner/useLaunchLocalRunner.js +75 -0
- package/runner/useLaunchLocalRunner.js.map +1 -0
- package/runner/useRunnerList.d.ts +49 -0
- package/runner/useRunnerList.d.ts.map +1 -0
- package/runner/useRunnerList.js +70 -0
- package/runner/useRunnerList.js.map +1 -0
- package/runner/useStopRunner.d.ts +53 -0
- package/runner/useStopRunner.d.ts.map +1 -0
- package/runner/useStopRunner.js +50 -0
- package/runner/useStopRunner.js.map +1 -0
- package/session/draft.d.ts +53 -0
- package/session/draft.d.ts.map +1 -0
- package/session/draft.js +45 -0
- package/session/draft.js.map +1 -0
- package/session/index.d.ts +10 -0
- package/session/index.d.ts.map +1 -1
- package/session/index.js +5 -0
- package/session/index.js.map +1 -1
- package/session/useCreateSession.d.ts +8 -0
- package/session/useCreateSession.d.ts.map +1 -1
- package/session/useCreateSession.js +1 -0
- package/session/useCreateSession.js.map +1 -1
- package/session/useEditSessionPrep.d.ts +26 -0
- package/session/useEditSessionPrep.d.ts.map +1 -0
- package/session/useEditSessionPrep.js +83 -0
- package/session/useEditSessionPrep.js.map +1 -0
- package/session/useNewSessionFlow.d.ts +110 -0
- package/session/useNewSessionFlow.d.ts.map +1 -0
- package/session/useNewSessionFlow.js +184 -0
- package/session/useNewSessionFlow.js.map +1 -0
- package/session/usePersistedModel.d.ts +18 -0
- package/session/usePersistedModel.d.ts.map +1 -0
- package/session/usePersistedModel.js +31 -0
- package/session/usePersistedModel.js.map +1 -0
- package/session/useSessionPageFlow.d.ts +104 -0
- package/session/useSessionPageFlow.d.ts.map +1 -0
- package/session/useSessionPageFlow.js +172 -0
- package/session/useSessionPageFlow.js.map +1 -0
- package/skill/SkillDetailView.js +3 -3
- package/skill/SkillDetailView.js.map +1 -1
- package/skill/SkillPicker.js +3 -3
- package/skill/SkillPicker.js.map +1 -1
- package/src/agent/AgentDetailView.tsx +8 -8
- package/src/agent/AgentPicker.tsx +3 -3
- package/src/api-key/ApiKeyCreatedAlert.tsx +2 -2
- package/src/api-key/ApiKeyListPanel.tsx +6 -6
- package/src/api-key/CreateApiKeyForm.tsx +2 -2
- package/src/attachment/AttachmentChipList.tsx +3 -3
- package/src/color-mode.ts +75 -0
- package/src/composer/ComposerToolbar.tsx +29 -7
- package/src/composer/ConfigureMenu.tsx +6 -6
- package/src/composer/ContextChip.tsx +1 -1
- package/src/composer/ContextPopover.tsx +2 -2
- package/src/composer/SessionComposer.tsx +34 -5
- package/src/environment/CreateEnvironmentForm.tsx +3 -3
- package/src/environment/EnvVarForm.tsx +6 -6
- package/src/environment/EnvironmentListPanel.tsx +3 -3
- package/src/environment/EnvironmentVariableEditor.tsx +7 -7
- package/src/error/ErrorMessage.tsx +5 -5
- package/src/execution/ApprovalCard.tsx +5 -5
- package/src/execution/ArtifactCard.tsx +2 -2
- package/src/execution/ArtifactPreviewModal.tsx +4 -4
- package/src/execution/FollowUpInput.tsx +2 -2
- package/src/execution/McpToolDetail.tsx +4 -4
- package/src/execution/MessageEntry.tsx +1 -1
- package/src/execution/MessageThread.tsx +1 -1
- package/src/execution/SessionVariablesInput.tsx +7 -7
- package/src/execution/SubAgentSection.tsx +5 -5
- package/src/execution/ToolCallDetail.tsx +2 -2
- package/src/execution/ToolCallGroup.tsx +3 -3
- package/src/execution/ToolCallItem.tsx +4 -4
- package/src/execution/WriteBackCard.tsx +5 -5
- package/src/execution/tool-rendering-primitives.tsx +5 -5
- package/src/github/GitHubRepoPicker.tsx +5 -5
- package/src/iam-policy/GrantAccessForm.tsx +2 -2
- package/src/iam-policy/OrgMembersPanel.tsx +11 -11
- package/src/identity-provider/CreateIdentityProviderForm.tsx +4 -4
- package/src/identity-provider/IdentityProviderDetailPanel.tsx +7 -7
- package/src/identity-provider/IdentityProviderListPanel.tsx +7 -7
- package/src/identity-provider/IdentityProviderWizard.tsx +8 -8
- package/src/identity-provider/ProviderPicker.tsx +2 -2
- package/src/index.ts +46 -7
- package/src/internal/CloudFeatureNotice.tsx +1 -1
- package/src/internal/Tabs.tsx +1 -1
- package/src/internal/markdown-components.tsx +2 -2
- package/src/invitation/InvitationCreatedAlert.tsx +2 -2
- package/src/invitation/InvitationManager.tsx +9 -9
- package/src/invitation/InvitationRedemption.tsx +11 -11
- package/src/library/ResourceCountCard.tsx +1 -1
- package/src/library/ResourceListView.tsx +7 -7
- package/src/mcp-server/McpServerConfigPanel.tsx +7 -7
- package/src/mcp-server/McpServerConnectDialog.tsx +5 -5
- package/src/mcp-server/McpServerDetailView.tsx +19 -19
- package/src/mcp-server/McpServerPicker.tsx +6 -6
- package/src/mcp-server/McpToolSelector.tsx +4 -4
- package/src/mcp-server/OAuthAppForm.tsx +3 -3
- package/src/models/ModelSelector.tsx +1 -1
- package/src/oauth-app/CreateOAuthAppForm.tsx +3 -3
- package/src/oauth-app/OAuthAppDetailPanel.tsx +7 -7
- package/src/oauth-app/OAuthAppListPanel.tsx +3 -3
- package/src/organization/CreateOrganizationForm.tsx +3 -3
- package/src/organization/OrgProfilePanel.tsx +4 -5
- package/src/platform-client/CreatePlatformClientForm.tsx +6 -6
- package/src/platform-client/PlatformClientDetailPanel.tsx +19 -19
- package/src/platform-client/PlatformClientListPanel.tsx +7 -7
- package/src/platform-client/PlatformClientSecretAlert.tsx +2 -2
- package/src/provider.tsx +52 -2
- package/src/runner/RunnerListPanel.tsx +725 -0
- package/src/runner/RunnerPicker.tsx +319 -0
- package/src/runner/__tests__/useDeleteRunner.test.tsx +150 -0
- package/src/runner/__tests__/useLaunchLocalRunner.test.tsx +223 -0
- package/src/runner/__tests__/useStopRunner.test.tsx +154 -0
- package/src/runner/index.ts +34 -0
- package/src/runner/phase.ts +62 -0
- package/src/runner/useDeleteRunner.ts +67 -0
- package/src/runner/useLaunchLocalRunner.ts +139 -0
- package/src/runner/useRunnerList.ts +114 -0
- package/src/runner/useStopRunner.ts +92 -0
- package/src/session/draft.ts +82 -0
- package/src/session/index.ts +28 -0
- package/src/session/useCreateSession.ts +9 -0
- package/src/session/useEditSessionPrep.ts +111 -0
- package/src/session/useNewSessionFlow.ts +283 -0
- package/src/session/usePersistedModel.ts +41 -0
- package/src/session/useSessionPageFlow.ts +280 -0
- package/src/skill/SkillDetailView.tsx +5 -5
- package/src/skill/SkillPicker.tsx +3 -3
- package/src/styles.css +25 -1
- package/src/usage/OrgUsagePanel.tsx +4 -4
- package/src/workspace/WorkspaceEditor.tsx +78 -66
- package/src/workspace/index.ts +0 -8
- package/styles.css +1 -1
- package/usage/OrgUsagePanel.js +2 -2
- package/usage/OrgUsagePanel.js.map +1 -1
- package/workspace/WorkspaceEditor.d.ts +28 -3
- package/workspace/WorkspaceEditor.d.ts.map +1 -1
- package/workspace/WorkspaceEditor.js +24 -25
- package/workspace/WorkspaceEditor.js.map +1 -1
- package/workspace/index.d.ts +0 -4
- package/workspace/index.d.ts.map +1 -1
- package/workspace/index.js +0 -2
- package/workspace/index.js.map +1 -1
- package/src/workspace/FolderBrowser.tsx +0 -579
- package/src/workspace/useFolderListing.ts +0 -164
- package/workspace/FolderBrowser.d.ts +0 -37
- package/workspace/FolderBrowser.d.ts.map +0 -1
- package/workspace/FolderBrowser.js +0 -188
- package/workspace/FolderBrowser.js.map +0 -1
- package/workspace/useFolderListing.d.ts +0 -73
- package/workspace/useFolderListing.d.ts.map +0 -1
- package/workspace/useFolderListing.js +0 -110
- package/workspace/useFolderListing.js.map +0 -1
|
@@ -0,0 +1,725 @@
|
|
|
1
|
+
"use client";
|
|
2
|
+
|
|
3
|
+
import {
|
|
4
|
+
useCallback,
|
|
5
|
+
useEffect,
|
|
6
|
+
useMemo,
|
|
7
|
+
useRef,
|
|
8
|
+
useState,
|
|
9
|
+
} from "react";
|
|
10
|
+
import { cn } from "@stigmer/theme";
|
|
11
|
+
import { getUserMessage } from "@stigmer/sdk";
|
|
12
|
+
import { timestampDate } from "@bufbuild/protobuf/wkt";
|
|
13
|
+
import { RunnerPhase } from "@stigmer/protos/ai/stigmer/agentic/runner/v1/enum_pb";
|
|
14
|
+
import type { Runner } from "@stigmer/protos/ai/stigmer/agentic/runner/v1/api_pb";
|
|
15
|
+
import { useRunnerList } from "./useRunnerList";
|
|
16
|
+
import { useStopRunner } from "./useStopRunner";
|
|
17
|
+
import { useDeleteRunner } from "./useDeleteRunner";
|
|
18
|
+
import {
|
|
19
|
+
isActivePhase,
|
|
20
|
+
phaseLabel,
|
|
21
|
+
phaseDotColor,
|
|
22
|
+
PHASE_SORT_ORDER,
|
|
23
|
+
} from "./phase";
|
|
24
|
+
|
|
25
|
+
const SYSTEM_MANAGED_LABEL = "stigmer.ai/system-managed";
|
|
26
|
+
|
|
27
|
+
type ConfirmingState = {
|
|
28
|
+
readonly runnerId: string;
|
|
29
|
+
readonly action: "stop" | "delete";
|
|
30
|
+
} | null;
|
|
31
|
+
|
|
32
|
+
// ---------------------------------------------------------------------------
|
|
33
|
+
// Public API
|
|
34
|
+
// ---------------------------------------------------------------------------
|
|
35
|
+
|
|
36
|
+
/** Props for {@link RunnerListPanel}. */
|
|
37
|
+
export interface RunnerListPanelProps {
|
|
38
|
+
/** Organization slug to scope the runner list. */
|
|
39
|
+
readonly org: string;
|
|
40
|
+
/**
|
|
41
|
+
* Include system-managed (ephemeral cloud) runners in the list.
|
|
42
|
+
*
|
|
43
|
+
* System-managed runners are auto-provisioned for cloud executions
|
|
44
|
+
* and labeled `stigmer.ai/system-managed: "true"`. Including them
|
|
45
|
+
* gives admins full visibility into all compute resources.
|
|
46
|
+
*
|
|
47
|
+
* @default true
|
|
48
|
+
*/
|
|
49
|
+
readonly includeSystemManaged?: boolean;
|
|
50
|
+
/** Expose refetch so parent components can trigger a list refresh. */
|
|
51
|
+
readonly onRefetchRef?: (refetch: () => void) => void;
|
|
52
|
+
/**
|
|
53
|
+
* Notification callback fired after a runner is successfully stopped.
|
|
54
|
+
* Receives the updated runner resource with its new phase.
|
|
55
|
+
*/
|
|
56
|
+
readonly onStopped?: (runner: Runner) => void;
|
|
57
|
+
/**
|
|
58
|
+
* Notification callback fired after a runner is successfully deleted.
|
|
59
|
+
* Receives the deleted runner resource for confirmation display.
|
|
60
|
+
*/
|
|
61
|
+
readonly onDeleted?: (runner: Runner) => void;
|
|
62
|
+
/** Additional CSS class names for the root container. */
|
|
63
|
+
readonly className?: string;
|
|
64
|
+
}
|
|
65
|
+
|
|
66
|
+
/**
|
|
67
|
+
* Admin panel that displays all runners in an organization with
|
|
68
|
+
* lifecycle management actions.
|
|
69
|
+
*
|
|
70
|
+
* Each runner is rendered as a card row showing name, phase indicator,
|
|
71
|
+
* machine information, and operational metadata. Non-system-managed
|
|
72
|
+
* runners include an action menu for stop and delete operations with
|
|
73
|
+
* inline confirmation — no modals or portals.
|
|
74
|
+
*
|
|
75
|
+
* Rows are sorted by phase (active runners first) then alphabetically
|
|
76
|
+
* by name. System-managed runners display a "System" badge and have
|
|
77
|
+
* no action affordances.
|
|
78
|
+
*
|
|
79
|
+
* Designed for the Settings > Runners page but embeddable in any
|
|
80
|
+
* context that needs runner fleet management. Fetches data via
|
|
81
|
+
* {@link useRunnerList} and performs mutations via {@link useStopRunner}
|
|
82
|
+
* and {@link useDeleteRunner} — no Console-specific dependencies.
|
|
83
|
+
*
|
|
84
|
+
* All visual properties flow through `--stgm-*` design tokens.
|
|
85
|
+
*
|
|
86
|
+
* @example
|
|
87
|
+
* ```tsx
|
|
88
|
+
* <RunnerListPanel org="acme" />
|
|
89
|
+
*
|
|
90
|
+
* <RunnerListPanel
|
|
91
|
+
* org="acme"
|
|
92
|
+
* includeSystemManaged={false}
|
|
93
|
+
* onStopped={(runner) => toast(`${runner.metadata?.name} stopped`)}
|
|
94
|
+
* onDeleted={(runner) => toast(`${runner.metadata?.name} deleted`)}
|
|
95
|
+
* onRefetchRef={(refetch) => { refetchRef.current = refetch; }}
|
|
96
|
+
* />
|
|
97
|
+
* ```
|
|
98
|
+
*/
|
|
99
|
+
export function RunnerListPanel({
|
|
100
|
+
org,
|
|
101
|
+
includeSystemManaged = true,
|
|
102
|
+
onRefetchRef,
|
|
103
|
+
onStopped,
|
|
104
|
+
onDeleted,
|
|
105
|
+
className,
|
|
106
|
+
}: RunnerListPanelProps) {
|
|
107
|
+
const { runners, isLoading, error, refetch } = useRunnerList(org, {
|
|
108
|
+
includeSystemManaged,
|
|
109
|
+
});
|
|
110
|
+
const [confirming, setConfirming] = useState<ConfirmingState>(null);
|
|
111
|
+
|
|
112
|
+
if (onRefetchRef) {
|
|
113
|
+
onRefetchRef(refetch);
|
|
114
|
+
}
|
|
115
|
+
|
|
116
|
+
const sorted = useMemo(
|
|
117
|
+
() => [...runners].sort(phaseThenName),
|
|
118
|
+
[runners],
|
|
119
|
+
);
|
|
120
|
+
|
|
121
|
+
const handleRequestConfirm = useCallback(
|
|
122
|
+
(runnerId: string, action: "stop" | "delete") => {
|
|
123
|
+
setConfirming({ runnerId, action });
|
|
124
|
+
},
|
|
125
|
+
[],
|
|
126
|
+
);
|
|
127
|
+
|
|
128
|
+
const handleCancelConfirm = useCallback(() => {
|
|
129
|
+
setConfirming(null);
|
|
130
|
+
}, []);
|
|
131
|
+
|
|
132
|
+
const handleActionComplete = useCallback(
|
|
133
|
+
(runner: Runner, action: "stop" | "delete") => {
|
|
134
|
+
setConfirming(null);
|
|
135
|
+
refetch();
|
|
136
|
+
if (action === "stop") onStopped?.(runner);
|
|
137
|
+
else onDeleted?.(runner);
|
|
138
|
+
},
|
|
139
|
+
[refetch, onStopped, onDeleted],
|
|
140
|
+
);
|
|
141
|
+
|
|
142
|
+
if (isLoading) {
|
|
143
|
+
return (
|
|
144
|
+
<div
|
|
145
|
+
className={cn("space-y-2", className)}
|
|
146
|
+
aria-busy="true"
|
|
147
|
+
aria-label="Loading runners"
|
|
148
|
+
>
|
|
149
|
+
{Array.from({ length: 3 }, (_, i) => (
|
|
150
|
+
<div
|
|
151
|
+
key={i}
|
|
152
|
+
className="bg-muted-subtle h-14 animate-pulse rounded-lg"
|
|
153
|
+
/>
|
|
154
|
+
))}
|
|
155
|
+
</div>
|
|
156
|
+
);
|
|
157
|
+
}
|
|
158
|
+
|
|
159
|
+
if (error) {
|
|
160
|
+
return (
|
|
161
|
+
<p className={cn("text-destructive text-xs", className)} role="alert">
|
|
162
|
+
{getUserMessage(error)}
|
|
163
|
+
</p>
|
|
164
|
+
);
|
|
165
|
+
}
|
|
166
|
+
|
|
167
|
+
if (sorted.length === 0) {
|
|
168
|
+
return (
|
|
169
|
+
<div
|
|
170
|
+
className={cn(
|
|
171
|
+
"flex flex-col items-center gap-2 py-8 text-center",
|
|
172
|
+
className,
|
|
173
|
+
)}
|
|
174
|
+
>
|
|
175
|
+
<RunnerIcon size={24} />
|
|
176
|
+
<p className="text-muted-foreground text-xs">
|
|
177
|
+
No runners registered.
|
|
178
|
+
</p>
|
|
179
|
+
<p className="text-muted-foreground-subtle max-w-xs text-[0.65rem]">
|
|
180
|
+
Start a runner with{" "}
|
|
181
|
+
<code className="bg-muted rounded px-1 py-0.5 font-mono text-[0.6rem]">
|
|
182
|
+
stigmer up
|
|
183
|
+
</code>{" "}
|
|
184
|
+
or launch one from your browser with the button above.
|
|
185
|
+
</p>
|
|
186
|
+
</div>
|
|
187
|
+
);
|
|
188
|
+
}
|
|
189
|
+
|
|
190
|
+
return (
|
|
191
|
+
<div
|
|
192
|
+
className={cn("space-y-2", className)}
|
|
193
|
+
role="list"
|
|
194
|
+
aria-label="Runners"
|
|
195
|
+
>
|
|
196
|
+
{sorted.map((runner) => {
|
|
197
|
+
const id = runner.metadata!.id;
|
|
198
|
+
return (
|
|
199
|
+
<RunnerRow
|
|
200
|
+
key={id}
|
|
201
|
+
runner={runner}
|
|
202
|
+
confirmingAction={
|
|
203
|
+
confirming?.runnerId === id ? confirming.action : null
|
|
204
|
+
}
|
|
205
|
+
onRequestConfirm={(action) => handleRequestConfirm(id, action)}
|
|
206
|
+
onCancelConfirm={handleCancelConfirm}
|
|
207
|
+
onActionComplete={handleActionComplete}
|
|
208
|
+
/>
|
|
209
|
+
);
|
|
210
|
+
})}
|
|
211
|
+
</div>
|
|
212
|
+
);
|
|
213
|
+
}
|
|
214
|
+
|
|
215
|
+
// ---------------------------------------------------------------------------
|
|
216
|
+
// RunnerRow (internal)
|
|
217
|
+
// ---------------------------------------------------------------------------
|
|
218
|
+
|
|
219
|
+
function RunnerRow({
|
|
220
|
+
runner,
|
|
221
|
+
confirmingAction,
|
|
222
|
+
onRequestConfirm,
|
|
223
|
+
onCancelConfirm,
|
|
224
|
+
onActionComplete,
|
|
225
|
+
}: {
|
|
226
|
+
runner: Runner;
|
|
227
|
+
confirmingAction: "stop" | "delete" | null;
|
|
228
|
+
onRequestConfirm: (action: "stop" | "delete") => void;
|
|
229
|
+
onCancelConfirm: () => void;
|
|
230
|
+
onActionComplete: (runner: Runner, action: "stop" | "delete") => void;
|
|
231
|
+
}) {
|
|
232
|
+
const {
|
|
233
|
+
stop,
|
|
234
|
+
isStopping,
|
|
235
|
+
error: stopError,
|
|
236
|
+
clearError: clearStopError,
|
|
237
|
+
} = useStopRunner();
|
|
238
|
+
const {
|
|
239
|
+
deleteRunner,
|
|
240
|
+
isDeleting,
|
|
241
|
+
error: deleteError,
|
|
242
|
+
clearError: clearDeleteError,
|
|
243
|
+
} = useDeleteRunner();
|
|
244
|
+
|
|
245
|
+
const name = runner.metadata?.name ?? "Unnamed";
|
|
246
|
+
const id = runner.metadata?.id ?? "";
|
|
247
|
+
const phase = runner.status?.phase ?? RunnerPhase.UNSPECIFIED;
|
|
248
|
+
const active = isActivePhase(phase);
|
|
249
|
+
const systemManaged =
|
|
250
|
+
runner.metadata?.labels[SYSTEM_MANAGED_LABEL] === "true";
|
|
251
|
+
const hasActions = !systemManaged;
|
|
252
|
+
const canStop = active;
|
|
253
|
+
|
|
254
|
+
const info = runner.status?.connectionInfo;
|
|
255
|
+
const hostname = info?.hostname;
|
|
256
|
+
const osArch =
|
|
257
|
+
info?.os && info?.arch ? `${info.os}/${info.arch}` : undefined;
|
|
258
|
+
const version = info?.runnerVersion;
|
|
259
|
+
const executions = runner.status?.currentExecutions ?? 0;
|
|
260
|
+
const lastHeartbeat = runner.status?.lastHeartbeatAt;
|
|
261
|
+
|
|
262
|
+
useEffect(() => {
|
|
263
|
+
if (confirmingAction) {
|
|
264
|
+
clearStopError();
|
|
265
|
+
clearDeleteError();
|
|
266
|
+
}
|
|
267
|
+
}, [confirmingAction, clearStopError, clearDeleteError]);
|
|
268
|
+
|
|
269
|
+
const handleStop = useCallback(async () => {
|
|
270
|
+
try {
|
|
271
|
+
const updated = await stop({
|
|
272
|
+
runnerId: id,
|
|
273
|
+
reason: "stopped via web console",
|
|
274
|
+
});
|
|
275
|
+
onActionComplete(updated, "stop");
|
|
276
|
+
} catch {
|
|
277
|
+
// error state surfaced via useStopRunner hook
|
|
278
|
+
}
|
|
279
|
+
}, [id, stop, onActionComplete]);
|
|
280
|
+
|
|
281
|
+
const handleDelete = useCallback(async () => {
|
|
282
|
+
try {
|
|
283
|
+
const deleted = await deleteRunner(id);
|
|
284
|
+
onActionComplete(deleted, "delete");
|
|
285
|
+
} catch {
|
|
286
|
+
// error state surfaced via useDeleteRunner hook
|
|
287
|
+
}
|
|
288
|
+
}, [id, deleteRunner, onActionComplete]);
|
|
289
|
+
|
|
290
|
+
if (confirmingAction === "stop") {
|
|
291
|
+
return (
|
|
292
|
+
<ConfirmationRow
|
|
293
|
+
message={
|
|
294
|
+
<>
|
|
295
|
+
Stop <span className="font-medium">{name}</span>?
|
|
296
|
+
</>
|
|
297
|
+
}
|
|
298
|
+
description="Active executions on this runner will be interrupted."
|
|
299
|
+
confirmLabel="Stop runner"
|
|
300
|
+
isMutating={isStopping}
|
|
301
|
+
error={stopError}
|
|
302
|
+
onConfirm={handleStop}
|
|
303
|
+
onCancel={onCancelConfirm}
|
|
304
|
+
/>
|
|
305
|
+
);
|
|
306
|
+
}
|
|
307
|
+
|
|
308
|
+
if (confirmingAction === "delete") {
|
|
309
|
+
return (
|
|
310
|
+
<ConfirmationRow
|
|
311
|
+
message={
|
|
312
|
+
<>
|
|
313
|
+
Delete <span className="font-medium">{name}</span>?
|
|
314
|
+
</>
|
|
315
|
+
}
|
|
316
|
+
description="This action is permanent. Sessions bound to this runner will fall back to auto-provisioning."
|
|
317
|
+
confirmLabel="Delete permanently"
|
|
318
|
+
isMutating={isDeleting}
|
|
319
|
+
error={deleteError}
|
|
320
|
+
onConfirm={handleDelete}
|
|
321
|
+
onCancel={onCancelConfirm}
|
|
322
|
+
/>
|
|
323
|
+
);
|
|
324
|
+
}
|
|
325
|
+
|
|
326
|
+
return (
|
|
327
|
+
<div
|
|
328
|
+
role="listitem"
|
|
329
|
+
className={cn(
|
|
330
|
+
"flex items-center gap-3 rounded-lg border border-border-muted px-3 py-2.5",
|
|
331
|
+
"hover:border-border transition-colors",
|
|
332
|
+
!active && "opacity-60",
|
|
333
|
+
)}
|
|
334
|
+
>
|
|
335
|
+
<RunnerIcon size={14} />
|
|
336
|
+
|
|
337
|
+
{/* Name + phase badge */}
|
|
338
|
+
<div className="flex min-w-0 flex-1 items-center gap-2">
|
|
339
|
+
<span className="truncate text-sm font-medium text-foreground">
|
|
340
|
+
{name}
|
|
341
|
+
</span>
|
|
342
|
+
{systemManaged && (
|
|
343
|
+
<span className="shrink-0 rounded bg-muted px-1.5 py-0.5 text-[0.6rem] font-medium text-muted-foreground">
|
|
344
|
+
System
|
|
345
|
+
</span>
|
|
346
|
+
)}
|
|
347
|
+
<PhaseBadge phase={phase} />
|
|
348
|
+
</div>
|
|
349
|
+
|
|
350
|
+
{/* Metadata columns — responsive */}
|
|
351
|
+
<div className="hidden items-center gap-4 text-xs text-muted-foreground sm:flex">
|
|
352
|
+
{hostname && (
|
|
353
|
+
<span className="max-w-[10rem] truncate" title={hostname}>
|
|
354
|
+
{hostname}
|
|
355
|
+
</span>
|
|
356
|
+
)}
|
|
357
|
+
{osArch && (
|
|
358
|
+
<span className="font-mono text-[0.65rem]">{osArch}</span>
|
|
359
|
+
)}
|
|
360
|
+
{version && (
|
|
361
|
+
<span className="font-mono text-[0.65rem]">v{version}</span>
|
|
362
|
+
)}
|
|
363
|
+
{active && (
|
|
364
|
+
<span title="Current executions">
|
|
365
|
+
{executions} exec{executions !== 1 ? "s" : ""}
|
|
366
|
+
</span>
|
|
367
|
+
)}
|
|
368
|
+
{lastHeartbeat && (
|
|
369
|
+
<span
|
|
370
|
+
title={`Last heartbeat: ${timestampDate(lastHeartbeat).toISOString()}`}
|
|
371
|
+
>
|
|
372
|
+
{formatRelativeTime(timestampDate(lastHeartbeat))}
|
|
373
|
+
</span>
|
|
374
|
+
)}
|
|
375
|
+
</div>
|
|
376
|
+
|
|
377
|
+
{hasActions && (
|
|
378
|
+
<ActionMenu
|
|
379
|
+
canStop={canStop}
|
|
380
|
+
onStop={() => onRequestConfirm("stop")}
|
|
381
|
+
onDelete={() => onRequestConfirm("delete")}
|
|
382
|
+
runnerName={name}
|
|
383
|
+
/>
|
|
384
|
+
)}
|
|
385
|
+
</div>
|
|
386
|
+
);
|
|
387
|
+
}
|
|
388
|
+
|
|
389
|
+
// ---------------------------------------------------------------------------
|
|
390
|
+
// ConfirmationRow (internal)
|
|
391
|
+
// ---------------------------------------------------------------------------
|
|
392
|
+
|
|
393
|
+
function ConfirmationRow({
|
|
394
|
+
message,
|
|
395
|
+
description,
|
|
396
|
+
confirmLabel,
|
|
397
|
+
isMutating,
|
|
398
|
+
error,
|
|
399
|
+
onConfirm,
|
|
400
|
+
onCancel,
|
|
401
|
+
}: {
|
|
402
|
+
message: React.ReactNode;
|
|
403
|
+
description: string;
|
|
404
|
+
confirmLabel: string;
|
|
405
|
+
isMutating: boolean;
|
|
406
|
+
error: Error | null;
|
|
407
|
+
onConfirm: () => void;
|
|
408
|
+
onCancel: () => void;
|
|
409
|
+
}) {
|
|
410
|
+
return (
|
|
411
|
+
<div
|
|
412
|
+
role="listitem"
|
|
413
|
+
className="rounded-lg border border-destructive/30 bg-destructive-subtle px-3 py-2.5"
|
|
414
|
+
>
|
|
415
|
+
<p className="text-xs text-foreground">{message}</p>
|
|
416
|
+
<p className="mt-0.5 text-[0.65rem] text-muted-foreground">
|
|
417
|
+
{description}
|
|
418
|
+
</p>
|
|
419
|
+
{error && (
|
|
420
|
+
<p className="mt-1 text-[0.65rem] text-destructive" role="alert">
|
|
421
|
+
{getUserMessage(error)}
|
|
422
|
+
</p>
|
|
423
|
+
)}
|
|
424
|
+
<div className="mt-2 flex items-center gap-1.5">
|
|
425
|
+
<button
|
|
426
|
+
type="button"
|
|
427
|
+
onClick={onConfirm}
|
|
428
|
+
disabled={isMutating}
|
|
429
|
+
className={cn(
|
|
430
|
+
"inline-flex items-center gap-1 rounded-md px-2.5 py-1 text-xs font-medium",
|
|
431
|
+
"bg-destructive text-destructive-foreground hover:bg-destructive-hover",
|
|
432
|
+
"disabled:pointer-events-none disabled:opacity-50",
|
|
433
|
+
)}
|
|
434
|
+
>
|
|
435
|
+
{isMutating && <SpinnerIcon />}
|
|
436
|
+
{confirmLabel}
|
|
437
|
+
</button>
|
|
438
|
+
<button
|
|
439
|
+
type="button"
|
|
440
|
+
onClick={onCancel}
|
|
441
|
+
disabled={isMutating}
|
|
442
|
+
className={cn(
|
|
443
|
+
"rounded-md px-2.5 py-1 text-xs",
|
|
444
|
+
"text-muted-foreground hover:text-foreground hover:bg-accent-hover",
|
|
445
|
+
"disabled:pointer-events-none disabled:opacity-50",
|
|
446
|
+
)}
|
|
447
|
+
>
|
|
448
|
+
Cancel
|
|
449
|
+
</button>
|
|
450
|
+
</div>
|
|
451
|
+
</div>
|
|
452
|
+
);
|
|
453
|
+
}
|
|
454
|
+
|
|
455
|
+
// ---------------------------------------------------------------------------
|
|
456
|
+
// ActionMenu (internal)
|
|
457
|
+
// ---------------------------------------------------------------------------
|
|
458
|
+
|
|
459
|
+
function ActionMenu({
|
|
460
|
+
canStop,
|
|
461
|
+
onStop,
|
|
462
|
+
onDelete,
|
|
463
|
+
runnerName,
|
|
464
|
+
}: {
|
|
465
|
+
canStop: boolean;
|
|
466
|
+
onStop: () => void;
|
|
467
|
+
onDelete: () => void;
|
|
468
|
+
runnerName: string;
|
|
469
|
+
}) {
|
|
470
|
+
const [open, setOpen] = useState(false);
|
|
471
|
+
const containerRef = useRef<HTMLDivElement>(null);
|
|
472
|
+
const triggerRef = useRef<HTMLButtonElement>(null);
|
|
473
|
+
|
|
474
|
+
useEffect(() => {
|
|
475
|
+
if (!open) return;
|
|
476
|
+
|
|
477
|
+
function handlePointerDown(e: MouseEvent) {
|
|
478
|
+
if (
|
|
479
|
+
containerRef.current &&
|
|
480
|
+
!containerRef.current.contains(e.target as Node)
|
|
481
|
+
) {
|
|
482
|
+
setOpen(false);
|
|
483
|
+
}
|
|
484
|
+
}
|
|
485
|
+
|
|
486
|
+
function handleKeyDown(e: KeyboardEvent) {
|
|
487
|
+
if (e.key === "Escape") {
|
|
488
|
+
setOpen(false);
|
|
489
|
+
triggerRef.current?.focus();
|
|
490
|
+
}
|
|
491
|
+
}
|
|
492
|
+
|
|
493
|
+
document.addEventListener("mousedown", handlePointerDown);
|
|
494
|
+
document.addEventListener("keydown", handleKeyDown);
|
|
495
|
+
return () => {
|
|
496
|
+
document.removeEventListener("mousedown", handlePointerDown);
|
|
497
|
+
document.removeEventListener("keydown", handleKeyDown);
|
|
498
|
+
};
|
|
499
|
+
}, [open]);
|
|
500
|
+
|
|
501
|
+
return (
|
|
502
|
+
<div ref={containerRef} className="relative">
|
|
503
|
+
<button
|
|
504
|
+
ref={triggerRef}
|
|
505
|
+
type="button"
|
|
506
|
+
onClick={() => setOpen((prev) => !prev)}
|
|
507
|
+
aria-label={`Actions for ${runnerName}`}
|
|
508
|
+
aria-haspopup="menu"
|
|
509
|
+
aria-expanded={open}
|
|
510
|
+
className={cn(
|
|
511
|
+
"shrink-0 rounded p-1",
|
|
512
|
+
"text-muted-foreground hover:text-foreground hover:bg-accent-hover",
|
|
513
|
+
"transition-colors",
|
|
514
|
+
)}
|
|
515
|
+
>
|
|
516
|
+
<MoreVerticalIcon />
|
|
517
|
+
</button>
|
|
518
|
+
|
|
519
|
+
{open && (
|
|
520
|
+
<div
|
|
521
|
+
role="menu"
|
|
522
|
+
aria-label={`Actions for ${runnerName}`}
|
|
523
|
+
className={cn(
|
|
524
|
+
"absolute right-0 top-full z-10 mt-1",
|
|
525
|
+
"min-w-[10rem] rounded-md border border-border bg-popover py-1 shadow-md",
|
|
526
|
+
)}
|
|
527
|
+
>
|
|
528
|
+
{canStop && (
|
|
529
|
+
<button
|
|
530
|
+
type="button"
|
|
531
|
+
role="menuitem"
|
|
532
|
+
onClick={() => {
|
|
533
|
+
setOpen(false);
|
|
534
|
+
onStop();
|
|
535
|
+
}}
|
|
536
|
+
className={cn(
|
|
537
|
+
"flex w-full items-center gap-2 px-3 py-1.5 text-left text-xs",
|
|
538
|
+
"text-foreground hover:bg-accent-hover transition-colors",
|
|
539
|
+
)}
|
|
540
|
+
>
|
|
541
|
+
<StopIcon />
|
|
542
|
+
Stop runner
|
|
543
|
+
</button>
|
|
544
|
+
)}
|
|
545
|
+
<button
|
|
546
|
+
type="button"
|
|
547
|
+
role="menuitem"
|
|
548
|
+
onClick={() => {
|
|
549
|
+
setOpen(false);
|
|
550
|
+
onDelete();
|
|
551
|
+
}}
|
|
552
|
+
className={cn(
|
|
553
|
+
"flex w-full items-center gap-2 px-3 py-1.5 text-left text-xs",
|
|
554
|
+
"text-destructive-muted hover:text-destructive hover:bg-destructive-subtle",
|
|
555
|
+
"transition-colors",
|
|
556
|
+
)}
|
|
557
|
+
>
|
|
558
|
+
<TrashIcon />
|
|
559
|
+
Delete runner
|
|
560
|
+
</button>
|
|
561
|
+
</div>
|
|
562
|
+
)}
|
|
563
|
+
</div>
|
|
564
|
+
);
|
|
565
|
+
}
|
|
566
|
+
|
|
567
|
+
// ---------------------------------------------------------------------------
|
|
568
|
+
// PhaseBadge (internal)
|
|
569
|
+
// ---------------------------------------------------------------------------
|
|
570
|
+
|
|
571
|
+
function PhaseBadge({ phase }: { phase: RunnerPhase }) {
|
|
572
|
+
return (
|
|
573
|
+
<span className="inline-flex shrink-0 items-center gap-1">
|
|
574
|
+
<span
|
|
575
|
+
className={`inline-block h-1.5 w-1.5 rounded-full ${phaseDotColor(phase)}`}
|
|
576
|
+
aria-hidden="true"
|
|
577
|
+
/>
|
|
578
|
+
<span className="text-[0.65rem] text-muted-foreground">
|
|
579
|
+
{phaseLabel(phase)}
|
|
580
|
+
</span>
|
|
581
|
+
</span>
|
|
582
|
+
);
|
|
583
|
+
}
|
|
584
|
+
|
|
585
|
+
// ---------------------------------------------------------------------------
|
|
586
|
+
// Utilities
|
|
587
|
+
// ---------------------------------------------------------------------------
|
|
588
|
+
|
|
589
|
+
function phaseThenName(a: Runner, b: Runner): number {
|
|
590
|
+
const pa = a.status?.phase ?? RunnerPhase.UNSPECIFIED;
|
|
591
|
+
const pb = b.status?.phase ?? RunnerPhase.UNSPECIFIED;
|
|
592
|
+
const order = PHASE_SORT_ORDER[pa] - PHASE_SORT_ORDER[pb];
|
|
593
|
+
if (order !== 0) return order;
|
|
594
|
+
return (a.metadata?.name ?? "").localeCompare(b.metadata?.name ?? "");
|
|
595
|
+
}
|
|
596
|
+
|
|
597
|
+
function formatRelativeTime(date: Date): string {
|
|
598
|
+
const diffMs = Date.now() - date.getTime();
|
|
599
|
+
|
|
600
|
+
if (diffMs < 0) return "just now";
|
|
601
|
+
|
|
602
|
+
const seconds = Math.floor(diffMs / 1000);
|
|
603
|
+
if (seconds < 60) return "just now";
|
|
604
|
+
|
|
605
|
+
const minutes = Math.floor(seconds / 60);
|
|
606
|
+
if (minutes < 60) return `${minutes}m ago`;
|
|
607
|
+
|
|
608
|
+
const hours = Math.floor(minutes / 60);
|
|
609
|
+
if (hours < 24) return `${hours}h ago`;
|
|
610
|
+
|
|
611
|
+
const days = Math.floor(hours / 24);
|
|
612
|
+
if (days < 30) return `${days}d ago`;
|
|
613
|
+
|
|
614
|
+
return date.toLocaleDateString(undefined, {
|
|
615
|
+
month: "short",
|
|
616
|
+
day: "numeric",
|
|
617
|
+
year: "numeric",
|
|
618
|
+
});
|
|
619
|
+
}
|
|
620
|
+
|
|
621
|
+
// ---------------------------------------------------------------------------
|
|
622
|
+
// Icons
|
|
623
|
+
// ---------------------------------------------------------------------------
|
|
624
|
+
|
|
625
|
+
function RunnerIcon({ size = 14 }: { size?: number }) {
|
|
626
|
+
return (
|
|
627
|
+
<svg
|
|
628
|
+
width={size}
|
|
629
|
+
height={size}
|
|
630
|
+
viewBox="0 0 24 24"
|
|
631
|
+
fill="none"
|
|
632
|
+
stroke="currentColor"
|
|
633
|
+
strokeWidth="2"
|
|
634
|
+
strokeLinecap="round"
|
|
635
|
+
strokeLinejoin="round"
|
|
636
|
+
aria-hidden="true"
|
|
637
|
+
className="shrink-0 text-muted-foreground"
|
|
638
|
+
>
|
|
639
|
+
<rect x="4" y="4" width="16" height="16" rx="2" />
|
|
640
|
+
<rect x="9" y="9" width="6" height="6" />
|
|
641
|
+
<path d="M15 2v2" />
|
|
642
|
+
<path d="M15 20v2" />
|
|
643
|
+
<path d="M2 15h2" />
|
|
644
|
+
<path d="M2 9h2" />
|
|
645
|
+
<path d="M20 15h2" />
|
|
646
|
+
<path d="M20 9h2" />
|
|
647
|
+
<path d="M9 2v2" />
|
|
648
|
+
<path d="M9 20v2" />
|
|
649
|
+
</svg>
|
|
650
|
+
);
|
|
651
|
+
}
|
|
652
|
+
|
|
653
|
+
function MoreVerticalIcon() {
|
|
654
|
+
return (
|
|
655
|
+
<svg
|
|
656
|
+
width="14"
|
|
657
|
+
height="14"
|
|
658
|
+
viewBox="0 0 16 16"
|
|
659
|
+
fill="currentColor"
|
|
660
|
+
aria-hidden="true"
|
|
661
|
+
>
|
|
662
|
+
<circle cx="8" cy="3" r="1.5" />
|
|
663
|
+
<circle cx="8" cy="8" r="1.5" />
|
|
664
|
+
<circle cx="8" cy="13" r="1.5" />
|
|
665
|
+
</svg>
|
|
666
|
+
);
|
|
667
|
+
}
|
|
668
|
+
|
|
669
|
+
function StopIcon() {
|
|
670
|
+
return (
|
|
671
|
+
<svg
|
|
672
|
+
width="14"
|
|
673
|
+
height="14"
|
|
674
|
+
viewBox="0 0 16 16"
|
|
675
|
+
fill="none"
|
|
676
|
+
stroke="currentColor"
|
|
677
|
+
strokeWidth="1.5"
|
|
678
|
+
strokeLinecap="round"
|
|
679
|
+
strokeLinejoin="round"
|
|
680
|
+
aria-hidden="true"
|
|
681
|
+
>
|
|
682
|
+
<circle cx="8" cy="8" r="6" />
|
|
683
|
+
<rect x="5.5" y="5.5" width="5" height="5" rx="0.5" />
|
|
684
|
+
</svg>
|
|
685
|
+
);
|
|
686
|
+
}
|
|
687
|
+
|
|
688
|
+
function TrashIcon() {
|
|
689
|
+
return (
|
|
690
|
+
<svg
|
|
691
|
+
width="14"
|
|
692
|
+
height="14"
|
|
693
|
+
viewBox="0 0 16 16"
|
|
694
|
+
fill="none"
|
|
695
|
+
stroke="currentColor"
|
|
696
|
+
strokeWidth="1.5"
|
|
697
|
+
strokeLinecap="round"
|
|
698
|
+
strokeLinejoin="round"
|
|
699
|
+
aria-hidden="true"
|
|
700
|
+
>
|
|
701
|
+
<path d="M2.5 4h11M5.5 4V2.5a1 1 0 0 1 1-1h3a1 1 0 0 1 1 1V4" />
|
|
702
|
+
<path d="M12.5 4v9a1 1 0 0 1-1 1h-7a1 1 0 0 1-1-1V4" />
|
|
703
|
+
<line x1="6.5" y1="7" x2="6.5" y2="11" />
|
|
704
|
+
<line x1="9.5" y1="7" x2="9.5" y2="11" />
|
|
705
|
+
</svg>
|
|
706
|
+
);
|
|
707
|
+
}
|
|
708
|
+
|
|
709
|
+
function SpinnerIcon() {
|
|
710
|
+
return (
|
|
711
|
+
<svg
|
|
712
|
+
width="12"
|
|
713
|
+
height="12"
|
|
714
|
+
viewBox="0 0 16 16"
|
|
715
|
+
fill="none"
|
|
716
|
+
stroke="currentColor"
|
|
717
|
+
strokeWidth="2"
|
|
718
|
+
strokeLinecap="round"
|
|
719
|
+
className="animate-spin"
|
|
720
|
+
aria-hidden="true"
|
|
721
|
+
>
|
|
722
|
+
<path d="M8 2a6 6 0 1 0 6 6" />
|
|
723
|
+
</svg>
|
|
724
|
+
);
|
|
725
|
+
}
|