+
Scheduled Jobs
+ {scheduledJobsForAlias.length === 0 && (
+
No scheduled jobs
+ )}
+ {scheduledJobsForAlias.map((job, i) => (
+
+
{`${job.cron} (${
+ job.timezone ?? "UTC"
+ })`}
+
+ {job.variables ? (
+
+
+ Variables
+
+
+
+
+ Key |
+ Value |
+
+
+
+ {Object.entries(job.variables).map(([key, value]) => (
+
+ {key} |
+ {String(value)} |
+
+ ))}
+
+
+
+ ) : (
+
+ No variables
+
+ )}
+
+ {i < scheduledJobsForAlias.length - 1 && (
+
+ )}
+
+ ))}
+
+ );
+};
diff --git a/core/morph/frontend/template/src/admin/datapipeline/ResourceNode.tsx b/core/morph/frontend/template/src/admin/datapipeline/ResourceNode.tsx
new file mode 100644
index 0000000..048d6d3
--- /dev/null
+++ b/core/morph/frontend/template/src/admin/datapipeline/ResourceNode.tsx
@@ -0,0 +1,79 @@
+import { tv } from "tailwind-variants";
+import { Resource } from "../common/useResourcesQuery";
+import {
+ Handle,
+ Node,
+ NodeProps,
+ Position,
+ useOnSelectionChange,
+} from "@xyflow/react";
+import React from "react";
+import databaseIcon from "../../assets/icons/database.svg";
+import pythonIcon from "../../assets/icons/python.svg";
+
+export type ResourceNode = Node<
+ {
+ resource: Resource;
+ },
+ "resource"
+>;
+
+const useSelected = (id: string) => {
+ const [selected, setSelected] = React.useState(false);
+
+ useOnSelectionChange({
+ onChange: React.useCallback(({ nodes }) => {
+ setSelected(nodes.some((node) => node.id === id));
+ }, []),
+ });
+
+ return selected;
+};
+
+const ResourceNode = ({ id, data }: NodeProps