Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
Original file line number Diff line number Diff line change
Expand Up @@ -33,7 +33,7 @@ execRouter.post('/node', userMiddleware, async (req: AuthRequest, res: Response)

const context = {
userId: req.user.sub,
config: config,
config: [config],
credentialId: nodeData.CredentialsID || config?.credentialId || "",
authType: nodeData.AvailableNode.authType,
nodeId: nodeData.id,
Expand Down
6 changes: 5 additions & 1 deletion apps/http-backend/src/routes/userRoutes/userRoutes.ts
Original file line number Diff line number Diff line change
Expand Up @@ -152,7 +152,11 @@ router.get("/getAvailableTriggers",
.status(statusCodes.UNAUTHORIZED)
.json({ message: "User isnot logged in /not authorized" });

const Data = await prismaClient.availableTrigger.findMany();
const Data = await prismaClient.availableTrigger.findMany({
where: {
type: 'webhook'
}
});
return res.status(statusCodes.OK).json({
message: "Availabe Triggers Fetched Succesfuuly",
Data: Data,
Expand Down
3 changes: 2 additions & 1 deletion apps/web/app/components/Actions/ActionNode.tsx
Original file line number Diff line number Diff line change
@@ -1,4 +1,5 @@
import { Handle, Position } from "@xyflow/react";
import { NodeIcon } from "@/app/components/ui/NodeIcon";

interface ActionNodeProps {
data: {
Expand All @@ -14,7 +15,7 @@ export const ActionNode = ({ data }: ActionNodeProps) => {
<div className="flex flex-col items-center justify-center p-4 bg-gray-800 border-2 border-blue-500 rounded-lg min-w-[150px]">
<Handle type="target" position={Position.Left} />

<span className="text-3xl mb-2">{data.icon || "⚙️"}</span>
<NodeIcon icon={data.icon} name={data.name} size="lg" nodeType="action" className="mb-2" />
<span className="text-white font-semibold">{data.name}</span>
<span className="text-gray-400 text-sm">{data.type}</span>

Expand Down
13 changes: 10 additions & 3 deletions apps/web/app/components/Actions/ActionSidebar.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -12,6 +12,7 @@ import {
SelectValue,
} from "@workspace/ui/components/select";
import { useActions } from "@/app/hooks/useActions";
import { NodeIcon } from "@/app/components/ui/NodeIcon";

interface SideBarProps {
isOpen: boolean;
Expand Down Expand Up @@ -48,7 +49,7 @@ export const ActionSideBar = ({ isOpen, onClose, onSelectAction }: SideBarProps)
id: selected.id,
name: selected.name,
type: selected.type,
icon: 'icon' in selected && selected.icon ? selected.icon : '⚡',
icon: 'icon' in selected && selected.icon ? selected.icon : undefined,
});
onClose();
}
Expand All @@ -60,8 +61,14 @@ export const ActionSideBar = ({ isOpen, onClose, onSelectAction }: SideBarProps)
{availableActions.length ? (
availableActions.map((action: any) => (
<SelectItem key={action.id} value={String(action.id)}>
<div className="flex items-center gap-2">
{'icon' in action && action.icon ? <img src={action.icon} className="w-8 h-4" /> : '⚡'} {action.name}
<div className="flex items-center gap-3 py-1">
<NodeIcon
icon={'icon' in action && action.icon ? action.icon : undefined}
name={action.name}
size="sm"
nodeType="action"
/>
<span className="font-medium text-sm text-gray-200">{action.name}</span>
</div>
</SelectItem>
))
Expand Down
58 changes: 57 additions & 1 deletion apps/web/app/components/ExecutionHistoryFooter.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -206,6 +206,34 @@ export default function ExecutionHistoryFooter({
// Detect sidebar state via CSS custom property / DOM attribute
const [sidebarWidth, setSidebarWidth] = useState(256); // 16rem default

// Drawer height state for vertical resizing
const [drawerHeight, setDrawerHeight] = useState<number>(450);
const isDraggingRef = useRef(false);

const handleResizeMouseDown = (e: React.MouseEvent) => {
e.preventDefault();
isDraggingRef.current = true;
const startY = e.clientY;
const startH = drawerHeight;

const onMouseMove = (moveEvent: MouseEvent) => {
if (!isDraggingRef.current) return;
const deltaY = startY - moveEvent.clientY; // moving cursor up increases drawer height
const maxHeight = typeof window !== 'undefined' ? window.innerHeight - 80 : 800;
const newH = Math.min(Math.max(startH + deltaY, 200), maxHeight);
setDrawerHeight(newH);
};

const onMouseUp = () => {
isDraggingRef.current = false;
window.removeEventListener('mousemove', onMouseMove);
window.removeEventListener('mouseup', onMouseUp);
};

window.addEventListener('mousemove', onMouseMove);
window.addEventListener('mouseup', onMouseUp);
};

useEffect(() => {
const observeSidebar = () => {
const wrapper = document.querySelector('[data-slot="sidebar-wrapper"]');
Expand Down Expand Up @@ -399,7 +427,7 @@ export default function ExecutionHistoryFooter({
bottom: '48px',
left: sidebarWidth,
right: 0,
height: '55vh',
height: `${drawerHeight}px`,
background: 'linear-gradient(180deg, #0a0d0a 0%, #0c100c 100%)',
borderTop: '1px solid #2a3525',
zIndex: 8,
Expand All @@ -409,6 +437,34 @@ export default function ExecutionHistoryFooter({
backdropFilter: 'blur(16px)',
}}
>
{/* Top Resizable Drag Handle */}
<div
onMouseDown={handleResizeMouseDown}
style={{
height: '8px',
cursor: 'row-resize',
display: 'flex',
alignItems: 'center',
justifyContent: 'center',
userSelect: 'none',
width: '100%',
background: 'transparent',
position: 'relative',
zIndex: 10,
}}
className="group hover:bg-emerald-500/15 transition-colors"
>
<div
style={{
width: '48px',
height: '3px',
borderRadius: '9999px',
backgroundColor: '#3b5330',
transition: 'all 0.2s ease',
}}
className="group-hover:bg-emerald-400 group-hover:w-20"
/>
</div>
{/* Panel Header */}
<div style={{
padding: '12px 20px',
Expand Down
125 changes: 102 additions & 23 deletions apps/web/app/components/nodes/BaseNode.tsx
Original file line number Diff line number Diff line change
@@ -1,4 +1,6 @@
import { Handle, Position } from "@xyflow/react";
import { NodeIcon } from "@/app/components/ui/NodeIcon";
import { getNodeConfig } from "@/app/lib/nodeConfigs";
interface BaseNodeProps {
id: string;
type: string;
Expand All @@ -13,7 +15,7 @@ interface BaseNodeProps {
status?: "idle" | "running" | "success" | "error";
onConfigure?: () => void;
onTest?: () => void;
onAddChild?: () => void;
onAddChild?: (sourceHandleId?: string) => void;
};
}

Expand Down Expand Up @@ -93,12 +95,13 @@ export default function BaseNode({ id, type, data }: BaseNodeProps) {
<div className="px-4 py-3">
{/* Icon + Label */}
<div className="flex flex-col items-center gap-2 mb-1">
<span className="text-xl p-2 rounded-full object-center">
{icon ?
<img src={icon ? icon : "⚡"} className="w-16 h-16 object-obtain"
/> : ("⚡")}

</span>
<NodeIcon
icon={icon}
name={label}
size="xl"
nodeType={nodeType}
className="w-14 h-14 bg-white border-gray-200 shadow-sm"
/>
<span className="font-semibold text-sm">{label}</span>
</div>
<div className="flex justify-center w-full">
Expand Down Expand Up @@ -134,28 +137,104 @@ export default function BaseNode({ id, type, data }: BaseNodeProps) {
</div>
</div>

{/* Add child button */}
{onAddChild && (
<div className="absolute -bottom-8 left-1/2 transform -translate-x-1/2">
<button
onClick={onAddChild}
className="bg-white border-2 border-gray-300 rounded-full w-6 h-6 flex items-center justify-center text-xs hover:border-blue-400"
>
+
</button>
</div>
)}

{/* Handles */}
{nodeType === "action" ? (
<>
{/* Action nodes get both handles */}
<Handle type="target" position={Position.Left} id="a-in" />
<Handle type="source" position={Position.Right} id="a-out" />
{/* Action nodes get input handle on left */}
<Handle
type="target"
position={Position.Left}
id="a-in"
className="!w-3 !h-3 !bg-indigo-500 !border-2 !border-white hover:!scale-125 transition-transform cursor-crosshair shadow-sm"
/>

{(() => {
const nodeConfigDef = getNodeConfig(label);
let resolvedOutputs = config?.outputs;

if (!resolvedOutputs && nodeConfigDef) {
const operationField = nodeConfigDef.fields?.find((f: any) => f.name === "operation");
if (operationField && operationField.options) {
const selectedOpId = config?.operation || operationField.defaultValue;
const selectedOp = operationField.options.find((o: any) => o.id === selectedOpId);
if (selectedOp && selectedOp.outputs) {
resolvedOutputs = selectedOp.outputs;
}
}
}

const outputs = resolvedOutputs || [{ id: "out-0", label: "Output" }];

return outputs.map((output: any, index: number) => {
const topPosition = `${((index + 1) * 100) / (outputs.length + 1)}%`;
const bgClass = output.id === "out-1" ? "!bg-rose-500" : "!bg-indigo-500";
const textClass = output.id === "out-1" ? "text-rose-600 bg-rose-50 border-rose-200" : "text-indigo-600 bg-indigo-50 border-indigo-200";

return (
<div key={output.id} className="group">
<Handle
type="source"
position={Position.Right}
id={output.id}
style={{ top: topPosition }}
className={`!w-4 !h-4 flex items-center justify-center ${bgClass} !border-2 !border-white hover:!scale-125 transition-all cursor-crosshair shadow-sm z-20`}
title={`${output.label} (${output.id})`}
>
{/* The + Button inside the Handle! Always visible */}
{onAddChild && (
<button
onClick={(e) => {
e.stopPropagation();
onAddChild(output.id);
}}
className="w-full h-full flex items-center justify-center text-white font-bold z-30 leading-none pb-[1px]"
style={{ fontSize: '11px' }}
title={`Add node from ${output.label}`}
>
+
</button>
)}
</Handle>

{/* Floating Label (Shows on hover or is very subtle next to it) */}
<div
className="absolute pointer-events-none flex items-center gap-1"
style={{ top: topPosition, right: '-20px', transform: 'translate(100%, -150%)' }}
>
<span className={`text-[9px] font-semibold px-1.5 py-0.5 rounded border shadow-xs ${textClass} whitespace-nowrap opacity-60 group-hover:opacity-100 transition-opacity`}>
{output.label}
</span>
</div>
</div>
);
});
})()}
</>
) : (
// Trigger node gets only source handle (output)
<Handle type="source" position={Position.Right} id="t-out" />
<div className="group">
<Handle
type="source"
position={Position.Right}
id="t-out"
className="!w-4 !h-4 flex items-center justify-center !bg-amber-500 !border-2 !border-white hover:!scale-125 transition-all cursor-crosshair shadow-sm z-20"
>
{/* The + Button inside the Handle! Always visible */}
{onAddChild && (
<button
onClick={(e) => {
e.stopPropagation();
onAddChild("t-out");
}}
className="w-full h-full flex items-center justify-center text-white font-bold z-30 leading-none pb-[1px]"
style={{ fontSize: '11px' }}
title={`Add action`}
>
+
</button>
)}
</Handle>
</div>
)}
</div>
);
Expand Down
3 changes: 2 additions & 1 deletion apps/web/app/components/nodes/TriggerNode.tsx
Original file line number Diff line number Diff line change
@@ -1,4 +1,5 @@
import { Handle, Position } from "@xyflow/react";
import { NodeIcon } from "@/app/components/ui/NodeIcon";

interface TriggerNodeProps {
data: {
Expand All @@ -12,7 +13,7 @@ interface TriggerNodeProps {
export const TriggerNode = ({ data }: TriggerNodeProps) => {
return (
<div className="flex flex-col items-center p-4 bg-gray-800 rounded-lg border border-gray-600">
<div className="text-3xl">{data.icon}</div>
<NodeIcon icon={data.icon} name={data.name} size="lg" nodeType="trigger" />
<div className="text-white font-bold mt-2">{data.name}</div>
<div className="text-gray-400 text-sm">{data.type}</div>
<Handle type="source" position={Position.Right} />
Expand Down
16 changes: 11 additions & 5 deletions apps/web/app/components/nodes/TriggerSidebar.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -12,6 +12,7 @@ import {
SelectValue,
} from "@workspace/ui/components/select";
import { useTriggers } from "@/app/hooks/useTriggers";
import { NodeIcon } from "@/app/components/ui/NodeIcon";

interface SideBarProps {
isOpen: boolean;
Expand Down Expand Up @@ -42,7 +43,7 @@ export const TriggerSideBar = ({ isOpen, onClose, onSelectTrigger }: SideBarPro
id: selected.id,
name: selected.name,
type: selected.type,
icon: (selected as any).icon ?? "⚡", // fallback or undefined
icon: (selected as any).icon ?? undefined,
});
onClose();
}
Expand All @@ -53,10 +54,15 @@ export const TriggerSideBar = ({ isOpen, onClose, onSelectTrigger }: SideBarPro
<SelectContent>
{triggers.map((trigger) => (
<SelectItem key={trigger.id} value={trigger.type}>
{/* Display a placeholder icon if 'icon' is missing */}
<div className="flex items-center gap-2">
{'icon' in trigger && trigger.icon ? <img src={trigger.icon as string} className="w-8 h-4" /> : '⚡'} {trigger.name}
</div>
<div className="flex items-center gap-3 py-1">
<NodeIcon
icon={'icon' in trigger && trigger.icon ? (trigger.icon as string) : undefined}
name={trigger.name}
size="sm"
nodeType="trigger"
/>
<span className="font-medium text-sm text-gray-200">{trigger.name}</span>
</div>
</SelectItem>
))}
</SelectContent>
Expand Down
Loading