diff --git a/src/routes/v2/pages/Editor/EditorV2.tsx b/src/routes/v2/pages/Editor/EditorV2.tsx
index 8495b249b..f93916b0a 100644
--- a/src/routes/v2/pages/Editor/EditorV2.tsx
+++ b/src/routes/v2/pages/Editor/EditorV2.tsx
@@ -41,6 +41,7 @@ import { usePipelineDetailsWindow } from "./hooks/usePipelineDetailsWindow";
import { usePipelineTreeWindow } from "./hooks/usePipelineTreeWindow";
import { usePropertiesWindowPositioning } from "./hooks/usePropertiesWindowPositioning";
import { useRecentRunsWindow } from "./hooks/useRecentRunsWindow";
+import { useRunsAndSubmissionWindow } from "./hooks/useRunsAndSubmissionWindow";
import { useSelectionWindowSync } from "./hooks/useSelectionWindowSync";
import { useSpecLifecycle } from "./hooks/useSpecLifecycle";
import { useUndoRedoKeyboard } from "./hooks/useUndoRedoKeyboard";
@@ -80,6 +81,7 @@ const PipelineEditor = withSuspenseWrapper(
usePipelineTreeWindow();
useHistoryWindow();
useRecentRunsWindow();
+ useRunsAndSubmissionWindow();
useUndoRedoKeyboard();
useFocusMode();
useShortcutListener();
diff --git a/src/routes/v2/pages/Editor/components/RunsAndSubmissionContent.tsx b/src/routes/v2/pages/Editor/components/RunsAndSubmissionContent.tsx
new file mode 100644
index 000000000..a6db0aee0
--- /dev/null
+++ b/src/routes/v2/pages/Editor/components/RunsAndSubmissionContent.tsx
@@ -0,0 +1,77 @@
+import { observer } from "mobx-react-lite";
+
+import { useAwaitAuthorization } from "@/components/shared/Authentication/useAwaitAuthorization";
+import { HuggingFaceAuthButton } from "@/components/shared/HuggingFaceAuth/HuggingFaceAuthButton";
+import GoogleCloudSubmissionDialog from "@/components/shared/Submitters/GoogleCloud/GoogleCloudSubmissionDialog";
+import TangleSubmitter from "@/components/shared/Submitters/Tangle/TangleSubmitter";
+import { Icon } from "@/components/ui/icon";
+import { BlockStack } from "@/components/ui/layout";
+import { Text } from "@/components/ui/typography";
+import { serializeComponentSpec } from "@/models/componentSpec";
+import { useSharedStores } from "@/routes/v2/shared/store/SharedStoreContext";
+import { ENABLE_GOOGLE_CLOUD_SUBMITTER } from "@/utils/constants";
+import { deepClone } from "@/utils/deepClone";
+
+export const RunsAndSubmissionContent = observer(() => {
+ const { isAuthorized } = useAwaitAuthorization();
+ const { navigation } = useSharedStores();
+ const rootSpec = navigation.rootSpec;
+ const allIssues = rootSpec?.allValidationIssues ?? [];
+ const errorCount = allIssues.filter((i) => i.severity === "error").length;
+ const hasErrors = errorCount > 0;
+
+ const serializedRootPipelineSpec = rootSpec
+ ? deepClone(serializeComponentSpec(rootSpec))
+ : undefined;
+
+ if (!serializedRootPipelineSpec) {
+ return ;
+ }
+
+ return (
+
+
+ {isAuthorized ? (
+ 0}
+ />
+ ) : (
+
+ )}
+
+ {ENABLE_GOOGLE_CLOUD_SUBMITTER && (
+
+ )}
+
+
+ );
+});
+
+function EmptyState() {
+ return (
+
+
+
+ No pipeline loaded
+
+
+ );
+}
diff --git a/src/routes/v2/pages/Editor/hooks/useRunsAndSubmissionWindow.tsx b/src/routes/v2/pages/Editor/hooks/useRunsAndSubmissionWindow.tsx
new file mode 100644
index 000000000..f42c861be
--- /dev/null
+++ b/src/routes/v2/pages/Editor/hooks/useRunsAndSubmissionWindow.tsx
@@ -0,0 +1,27 @@
+import { useEffect } from "react";
+
+import { RunsAndSubmissionContent } from "@/routes/v2/pages/Editor/components/RunsAndSubmissionContent";
+import { useSharedStores } from "@/routes/v2/shared/store/SharedStoreContext";
+
+const RUNS_AND_SUBMISSION_WINDOW_ID = "runs-and-submission";
+
+export function useRunsAndSubmissionWindow() {
+ const { windows } = useSharedStores();
+ useEffect(() => {
+ if (!windows.getWindowById(RUNS_AND_SUBMISSION_WINDOW_ID)) {
+ windows.openWindow(, {
+ id: RUNS_AND_SUBMISSION_WINDOW_ID,
+ title: "Runs & Submissions",
+ position: { x: 100, y: 460 },
+ size: { width: 280, height: 50 },
+ minSize: {
+ width: 280,
+ height: 50,
+ },
+ disabledActions: ["close"],
+ persisted: true,
+ defaultDockState: "left",
+ });
+ }
+ }, [windows]);
+}
diff --git a/src/routes/v2/shared/windows/viewPresets.ts b/src/routes/v2/shared/windows/viewPresets.ts
index 6858bc208..b2bae6eec 100644
--- a/src/routes/v2/shared/windows/viewPresets.ts
+++ b/src/routes/v2/shared/windows/viewPresets.ts
@@ -7,6 +7,7 @@ export interface ViewPreset {
}
const DEFAULT_DOCK_POSITIONS: Record = {
+ "runs-and-submission": "left",
"component-library": "left",
"pipeline-tree": "left",
history: "left",
@@ -18,8 +19,13 @@ const DEFAULT_DOCK_POSITIONS: Record = {
export const DEFAULT_VIEW_PRESET: ViewPreset = {
label: "Default",
- description: "Components, Recent Runs, Pipeline Details",
- visible: new Set(["component-library", "recent-runs", "pipeline-details"]),
+ description: "Components, Runs & Submissions, Recent Runs, Pipeline Details",
+ visible: new Set([
+ "runs-and-submission",
+ "recent-runs",
+ "component-library",
+ "pipeline-details",
+ ]),
dockPositions: DEFAULT_DOCK_POSITIONS,
};
@@ -27,8 +33,10 @@ export const VIEW_PRESETS: ViewPreset[] = [
DEFAULT_VIEW_PRESET,
{
label: "All",
- description: "All windows visible",
+ description:
+ "All windows visible, including Runs & Submissions and Recent Runs",
visible: new Set([
+ "runs-and-submission",
"component-library",
"history",
"pipeline-tree",
diff --git a/src/utils/constants.ts b/src/utils/constants.ts
index 5f3890919..3be064c03 100644
--- a/src/utils/constants.ts
+++ b/src/utils/constants.ts
@@ -22,6 +22,9 @@ export const GIT_REPO_URL =
export const GIT_COMMIT = import.meta.env.VITE_GIT_COMMIT || "master";
+export const ENABLE_GOOGLE_CLOUD_SUBMITTER =
+ import.meta.env.VITE_ENABLE_GOOGLE_CLOUD_SUBMITTER === "true";
+
export const USER_PIPELINES_LIST_NAME = "user_pipelines";
export const defaultPipelineYamlWithName = (name: string) => `