Canvas: Prettier
Change-Id: I620dde109df0f29f0c85c6fe150e347d2c32a03e
diff --git a/apps/canvas/front/src/components/node-app.tsx b/apps/canvas/front/src/components/node-app.tsx
index 3027f27..cfbbbde 100644
--- a/apps/canvas/front/src/components/node-app.tsx
+++ b/apps/canvas/front/src/components/node-app.tsx
@@ -1,13 +1,24 @@
import { v4 as uuidv4 } from "uuid";
-import { NodeRect } from './node-rect';
-import { useStateStore, ServiceNode, ServiceTypes, nodeLabel, BoundEnvVar, AppState, nodeIsConnectable, GatewayTCPNode, GatewayHttpsNode, AppNode } from '@/lib/state';
-import { KeyboardEvent, FocusEvent, useCallback, useEffect, useMemo, useState } from 'react';
+import { NodeRect } from "./node-rect";
+import {
+ useStateStore,
+ ServiceNode,
+ ServiceTypes,
+ nodeLabel,
+ BoundEnvVar,
+ AppState,
+ nodeIsConnectable,
+ GatewayTCPNode,
+ GatewayHttpsNode,
+ AppNode,
+} from "@/lib/state";
+import { KeyboardEvent, FocusEvent, useCallback, useEffect, useMemo, useState } from "react";
import { z } from "zod";
-import { DeepPartial, EventType, useForm, ControllerRenderProps, FieldPath } from 'react-hook-form';
-import { zodResolver } from '@hookform/resolvers/zod';
-import { Form, FormControl, FormField, FormItem, FormMessage } from './ui/form';
-import { Input } from './ui/input';
-import { Button } from './ui/button';
+import { DeepPartial, EventType, useForm, ControllerRenderProps, FieldPath } from "react-hook-form";
+import { zodResolver } from "@hookform/resolvers/zod";
+import { Form, FormControl, FormField, FormItem, FormMessage } from "./ui/form";
+import { Input } from "./ui/input";
+import { Button } from "./ui/button";
import { Handle, Position, useNodes } from "@xyflow/react";
import { Select, SelectContent, SelectItem, SelectTrigger, SelectValue } from "./ui/select";
import { PencilIcon, XIcon } from "lucide-react";
@@ -15,480 +26,577 @@
import { Textarea } from "./ui/textarea";
export function NodeApp(node: ServiceNode) {
- const { id, selected } = node;
- const isConnectablePorts = useMemo(() => nodeIsConnectable(node, "ports"), [node]);
- const isConnectableRepository = useMemo(() => nodeIsConnectable(node, "repository"), [node]);
- return (
- <NodeRect id={id} selected={selected} type={node.type} state={node.data.state}>
- <div style={{ padding: '10px 20px' }}>
- {nodeLabel(node)}
- <Handle
- id="repository"
- type={"target"}
- position={Position.Left}
- isConnectableStart={isConnectableRepository}
- isConnectableEnd={isConnectableRepository}
- isConnectable={isConnectableRepository}
- />
- <Handle
- id="ports"
- type={"source"}
- position={Position.Top}
- isConnectableStart={isConnectablePorts}
- isConnectableEnd={isConnectablePorts}
- isConnectable={isConnectablePorts}
- />
- <Handle
- id="env_var"
- type={"target"}
- position={Position.Bottom}
- isConnectableStart={true}
- isConnectableEnd={true}
- isConnectable={true}
- />
- </div>
- </NodeRect>
- );
+ const { id, selected } = node;
+ const isConnectablePorts = useMemo(() => nodeIsConnectable(node, "ports"), [node]);
+ const isConnectableRepository = useMemo(() => nodeIsConnectable(node, "repository"), [node]);
+ return (
+ <NodeRect id={id} selected={selected} type={node.type} state={node.data.state}>
+ <div style={{ padding: "10px 20px" }}>
+ {nodeLabel(node)}
+ <Handle
+ id="repository"
+ type={"target"}
+ position={Position.Left}
+ isConnectableStart={isConnectableRepository}
+ isConnectableEnd={isConnectableRepository}
+ isConnectable={isConnectableRepository}
+ />
+ <Handle
+ id="ports"
+ type={"source"}
+ position={Position.Top}
+ isConnectableStart={isConnectablePorts}
+ isConnectableEnd={isConnectablePorts}
+ isConnectable={isConnectablePorts}
+ />
+ <Handle
+ id="env_var"
+ type={"target"}
+ position={Position.Bottom}
+ isConnectableStart={true}
+ isConnectableEnd={true}
+ isConnectable={true}
+ />
+ </div>
+ </NodeRect>
+ );
}
const schema = z.object({
- name: z.string().min(1, "requried"),
- type: z.enum(ServiceTypes),
+ name: z.string().min(1, "requried"),
+ type: z.enum(ServiceTypes),
});
const portSchema = z.object({
- name: z.string().min(1, "required"),
- value: z.coerce.number().gt(0, "can not be negative"),
+ name: z.string().min(1, "required"),
+ value: z.coerce.number().gt(0, "can not be negative"),
});
const sourceSchema = z.object({
- id: z.string().min(1, "required"),
- branch: z.string(),
- rootDir: z.string(),
+ id: z.string().min(1, "required"),
+ branch: z.string(),
+ rootDir: z.string(),
});
export function NodeAppDetails({ id, data }: ServiceNode) {
- const store = useStateStore();
- const nodes = useNodes<AppNode>();
- const form = useForm<z.infer<typeof schema>>({
- resolver: zodResolver(schema),
- mode: "onChange",
- defaultValues: {
- name: data.label,
- type: data.type,
- }
- });
- const portForm = useForm<z.infer<typeof portSchema>>({
- resolver: zodResolver(portSchema),
- mode: "onSubmit",
- defaultValues: {
- name: "",
- value: 0,
- }
- });
- const onSubmit = useCallback((values: z.infer<typeof portSchema>) => {
- const portId = uuidv4();
- store.updateNodeData<"app">(id, {
- ports: (data.ports || []).concat({
- id: portId,
- name: values.name,
- value: values.value,
- }),
- envVars: (data.envVars || []).concat({
- id: uuidv4(),
- source: null,
- portId,
- name: `DODO_PORT_${values.name.toUpperCase()}`,
- }),
- });
- portForm.reset();
- }, [id, data, portForm, store]);
- useEffect(() => {
- const sub = form.watch((value: DeepPartial<z.infer<typeof schema>>, { name, type }: { name?: keyof z.infer<typeof schema> | undefined, type?: EventType | undefined }) => {
- console.log({ name, type });
- if (type !== "change") {
- return;
- }
- switch (name) {
- case "name":
- if (!value.name) {
- break;
- }
- store.updateNodeData<"app">(id, {
- label: value.name,
- });
- break;
- case "type":
- if (!value.type) {
- break;
- }
- store.updateNodeData<"app">(id, {
- type: value.type,
- })
- break;
- }
- });
- return () => sub.unsubscribe();
- }, [id, form, store]);
- const focus = useCallback((field: ControllerRenderProps<z.infer<typeof schema>, FieldPath<z.infer<typeof schema>>>, name: string) => {
- return (e: HTMLElement | null) => {
- field.ref(e);
- if (e != null && name === data.activeField) {
- console.log(e);
- e.focus();
- store.updateNodeData(id, {
- activeField: undefined,
- });
- }
- }
- }, [id, data, store]);
- const [typeProps, setTypeProps] = useState({});
- useEffect(() => {
- if (data.activeField === "type") {
- setTypeProps({
- open: true,
- onOpenChange: () => store.updateNodeData(id, { activeField: undefined }),
- });
- } else {
- setTypeProps({});
- }
- }, [id, data, store, setTypeProps]);
- const editAlias = useCallback((e: BoundEnvVar) => {
- return () => {
- store.updateNodeData(id, {
- ...data,
- envVars: data.envVars!.map((o) => {
- if (o.id !== e.id) {
- return o;
- } else return {
- ...o,
- isEditting: true,
- }
- }),
- });
- };
- }, [id, data, store]);
- const saveAlias = useCallback((e: BoundEnvVar, value: string, store: AppState) => {
- store.updateNodeData(id, {
- ...data,
- envVars: data.envVars!.map((o) => {
- if (o.id !== e.id) {
- return o;
- }
- if (value) {
- return {
- ...o,
- isEditting: false,
- alias: value.toUpperCase(),
- }
- }
- console.log(o);
- if ("alias" in o) {
- const { alias: _, ...rest } = o;
- console.log(rest);
- return {
- ...rest,
- isEditting: false,
- };
- }
- return {
- ...o,
- isEditting: false,
- };
- }),
- });
- }, [id, data]);
- const saveAliasOnEnter = useCallback((e: BoundEnvVar) => {
- return (event: KeyboardEvent<HTMLInputElement>) => {
- if (event.key === "Enter") {
- event.preventDefault();
- saveAlias(e, event.currentTarget.value, store);
- }
- }
- }, [store, saveAlias]);
- const saveAliasOnBlur = useCallback((e: BoundEnvVar) => {
- return (event: FocusEvent<HTMLInputElement>) => {
- saveAlias(e, event.currentTarget.value, store);
- }
- }, [store, saveAlias]);
- const removePort = useCallback((portId: string) => {
- // TODO(gio): this is ugly
- const tcpRemoved = new Set<string>();
- console.log(store.edges);
- store.setEdges(store.edges.filter((e) => {
- if (e.source !== id || e.sourceHandle !== "ports") {
- return true;
- }
- const tn = store.nodes.find((n) => n.id == e.target)!;
- if (e.targetHandle === "https") {
- const t = tn as GatewayHttpsNode;
- if (t.data.https?.serviceId === id && t.data.https.portId === portId) {
- return false;
- }
- }
- if (e.targetHandle === "tcp") {
- const t = tn as GatewayTCPNode;
- if (tcpRemoved.has(t.id)) {
- return true;
- }
- if (t.data.exposed.find((e) => e.serviceId === id && e.portId === portId)) {
- tcpRemoved.add(t.id);
- return false;
- }
- }
- if (e.targetHandle === "env_var") {
- if (tn && (tn.data.envVars || []).find((ev) => ev.source === id && "portId" in ev && ev.portId === portId)) {
- return false;
- }
- }
- return true;
- }));
- store.nodes.filter((n) => n.type === "gateway-https" && n.data.https && n.data.https.serviceId === id && n.data.https.portId === portId).forEach((n) => {
- store.updateNodeData<"gateway-https">(n.id, {
- https: undefined,
- });
- });
- store.nodes.filter((n) => n.type === "gateway-tcp").forEach((n) => {
- const filtered = n.data.exposed.filter((e) => {
- if (e.serviceId === id && e.portId === portId) {
- return false;
- } else {
- return true;
- }
- })
- if (filtered.length != n.data.exposed.length) {
- store.updateNodeData<"gateway-tcp">(n.id, {
- exposed: filtered,
- });
- }
- });
- store.nodes.filter((n) => n.type === "app" && n.data.envVars).forEach((n) => {
- store.updateNodeData<"app">(n.id, {
- envVars: n.data.envVars.filter((ev) => {
- if (ev.source === id && "portId" in ev && ev.portId === portId) {
- return false;
- }
- return true;
- })
- });
- });
- store.updateNodeData<"app">(id, {
- ports: (data.ports || []).filter((p) => p.id !== portId),
- envVars: (data.envVars || []).filter((ev) => !(ev.source === null && "portId" in ev && ev.portId === portId)),
- });
- }, [id, data, store]);
- const setPreBuildCommands = useCallback((e: React.ChangeEvent<HTMLTextAreaElement>) => {
- store.updateNodeData<"app">(id, {
- preBuildCommands: e.currentTarget.value,
- });
- }, [id, store]);
+ const store = useStateStore();
+ const nodes = useNodes<AppNode>();
+ const form = useForm<z.infer<typeof schema>>({
+ resolver: zodResolver(schema),
+ mode: "onChange",
+ defaultValues: {
+ name: data.label,
+ type: data.type,
+ },
+ });
+ const portForm = useForm<z.infer<typeof portSchema>>({
+ resolver: zodResolver(portSchema),
+ mode: "onSubmit",
+ defaultValues: {
+ name: "",
+ value: 0,
+ },
+ });
+ const onSubmit = useCallback(
+ (values: z.infer<typeof portSchema>) => {
+ const portId = uuidv4();
+ store.updateNodeData<"app">(id, {
+ ports: (data.ports || []).concat({
+ id: portId,
+ name: values.name,
+ value: values.value,
+ }),
+ envVars: (data.envVars || []).concat({
+ id: uuidv4(),
+ source: null,
+ portId,
+ name: `DODO_PORT_${values.name.toUpperCase()}`,
+ }),
+ });
+ portForm.reset();
+ },
+ [id, data, portForm, store],
+ );
+ useEffect(() => {
+ const sub = form.watch(
+ (
+ value: DeepPartial<z.infer<typeof schema>>,
+ { name, type }: { name?: keyof z.infer<typeof schema> | undefined; type?: EventType | undefined },
+ ) => {
+ console.log({ name, type });
+ if (type !== "change") {
+ return;
+ }
+ switch (name) {
+ case "name":
+ if (!value.name) {
+ break;
+ }
+ store.updateNodeData<"app">(id, {
+ label: value.name,
+ });
+ break;
+ case "type":
+ if (!value.type) {
+ break;
+ }
+ store.updateNodeData<"app">(id, {
+ type: value.type,
+ });
+ break;
+ }
+ },
+ );
+ return () => sub.unsubscribe();
+ }, [id, form, store]);
+ const focus = useCallback(
+ (field: ControllerRenderProps<z.infer<typeof schema>, FieldPath<z.infer<typeof schema>>>, name: string) => {
+ return (e: HTMLElement | null) => {
+ field.ref(e);
+ if (e != null && name === data.activeField) {
+ console.log(e);
+ e.focus();
+ store.updateNodeData(id, {
+ activeField: undefined,
+ });
+ }
+ };
+ },
+ [id, data, store],
+ );
+ const [typeProps, setTypeProps] = useState({});
+ useEffect(() => {
+ if (data.activeField === "type") {
+ setTypeProps({
+ open: true,
+ onOpenChange: () => store.updateNodeData(id, { activeField: undefined }),
+ });
+ } else {
+ setTypeProps({});
+ }
+ }, [id, data, store, setTypeProps]);
+ const editAlias = useCallback(
+ (e: BoundEnvVar) => {
+ return () => {
+ store.updateNodeData(id, {
+ ...data,
+ envVars: data.envVars!.map((o) => {
+ if (o.id !== e.id) {
+ return o;
+ } else
+ return {
+ ...o,
+ isEditting: true,
+ };
+ }),
+ });
+ };
+ },
+ [id, data, store],
+ );
+ const saveAlias = useCallback(
+ (e: BoundEnvVar, value: string, store: AppState) => {
+ store.updateNodeData(id, {
+ ...data,
+ envVars: data.envVars!.map((o) => {
+ if (o.id !== e.id) {
+ return o;
+ }
+ if (value) {
+ return {
+ ...o,
+ isEditting: false,
+ alias: value.toUpperCase(),
+ };
+ }
+ console.log(o);
+ if ("alias" in o) {
+ const { alias: _, ...rest } = o;
+ console.log(rest);
+ return {
+ ...rest,
+ isEditting: false,
+ };
+ }
+ return {
+ ...o,
+ isEditting: false,
+ };
+ }),
+ });
+ },
+ [id, data],
+ );
+ const saveAliasOnEnter = useCallback(
+ (e: BoundEnvVar) => {
+ return (event: KeyboardEvent<HTMLInputElement>) => {
+ if (event.key === "Enter") {
+ event.preventDefault();
+ saveAlias(e, event.currentTarget.value, store);
+ }
+ };
+ },
+ [store, saveAlias],
+ );
+ const saveAliasOnBlur = useCallback(
+ (e: BoundEnvVar) => {
+ return (event: FocusEvent<HTMLInputElement>) => {
+ saveAlias(e, event.currentTarget.value, store);
+ };
+ },
+ [store, saveAlias],
+ );
+ const removePort = useCallback(
+ (portId: string) => {
+ // TODO(gio): this is ugly
+ const tcpRemoved = new Set<string>();
+ console.log(store.edges);
+ store.setEdges(
+ store.edges.filter((e) => {
+ if (e.source !== id || e.sourceHandle !== "ports") {
+ return true;
+ }
+ const tn = store.nodes.find((n) => n.id == e.target)!;
+ if (e.targetHandle === "https") {
+ const t = tn as GatewayHttpsNode;
+ if (t.data.https?.serviceId === id && t.data.https.portId === portId) {
+ return false;
+ }
+ }
+ if (e.targetHandle === "tcp") {
+ const t = tn as GatewayTCPNode;
+ if (tcpRemoved.has(t.id)) {
+ return true;
+ }
+ if (t.data.exposed.find((e) => e.serviceId === id && e.portId === portId)) {
+ tcpRemoved.add(t.id);
+ return false;
+ }
+ }
+ if (e.targetHandle === "env_var") {
+ if (
+ tn &&
+ (tn.data.envVars || []).find(
+ (ev) => ev.source === id && "portId" in ev && ev.portId === portId,
+ )
+ ) {
+ return false;
+ }
+ }
+ return true;
+ }),
+ );
+ store.nodes
+ .filter(
+ (n) =>
+ n.type === "gateway-https" &&
+ n.data.https &&
+ n.data.https.serviceId === id &&
+ n.data.https.portId === portId,
+ )
+ .forEach((n) => {
+ store.updateNodeData<"gateway-https">(n.id, {
+ https: undefined,
+ });
+ });
+ store.nodes
+ .filter((n) => n.type === "gateway-tcp")
+ .forEach((n) => {
+ const filtered = n.data.exposed.filter((e) => {
+ if (e.serviceId === id && e.portId === portId) {
+ return false;
+ } else {
+ return true;
+ }
+ });
+ if (filtered.length != n.data.exposed.length) {
+ store.updateNodeData<"gateway-tcp">(n.id, {
+ exposed: filtered,
+ });
+ }
+ });
+ store.nodes
+ .filter((n) => n.type === "app" && n.data.envVars)
+ .forEach((n) => {
+ store.updateNodeData<"app">(n.id, {
+ envVars: n.data.envVars.filter((ev) => {
+ if (ev.source === id && "portId" in ev && ev.portId === portId) {
+ return false;
+ }
+ return true;
+ }),
+ });
+ });
+ store.updateNodeData<"app">(id, {
+ ports: (data.ports || []).filter((p) => p.id !== portId),
+ envVars: (data.envVars || []).filter(
+ (ev) => !(ev.source === null && "portId" in ev && ev.portId === portId),
+ ),
+ });
+ },
+ [id, data, store],
+ );
+ const setPreBuildCommands = useCallback(
+ (e: React.ChangeEvent<HTMLTextAreaElement>) => {
+ store.updateNodeData<"app">(id, {
+ preBuildCommands: e.currentTarget.value,
+ });
+ },
+ [id, store],
+ );
- const sourceForm = useForm<z.infer<typeof sourceSchema>>({
- resolver: zodResolver(sourceSchema),
- mode: "onChange",
- defaultValues: {
- id: data?.repository?.id,
- branch: data.repository && "branch" in data.repository ? data.repository.branch : undefined,
- rootDir: data.repository && "rootDir" in data.repository ? data.repository.rootDir : undefined,
- },
- });
- useEffect(() => {
- const sub = sourceForm.watch((value: DeepPartial<z.infer<typeof sourceSchema>>, { name }: { name?: keyof z.infer<typeof sourceSchema> | undefined, type?: EventType | undefined }) => {
- console.log(value);
- if (name === "id") {
- let edges = store.edges;
- if (data?.repository?.id !== undefined) {
- edges = edges.filter((e) => {
- if (e.target === id && e.targetHandle === "repository" && e.source === data.repository.id) {
- return false;
- } else {
- return true;
- }
- });
- }
- if (value.id !== undefined) {
- edges = edges.concat({
- id: uuidv4(),
- source: value.id,
- sourceHandle: "repository",
- target: id,
- targetHandle: "repository",
- });
- }
- store.setEdges(edges);
- store.updateNodeData<"app">(id, {
- repository: {
- id: value.id,
- },
- });
- } else if (name === "branch") {
- store.updateNodeData<"app">(id, {
- repository: {
- ...data?.repository,
- branch: value.branch,
- },
- });
- } else if (name === "rootDir") {
- store.updateNodeData<"app">(id, {
- repository: {
- ...data?.repository,
- rootDir: value.rootDir,
- },
- });
- }
- });
- return () => sub.unsubscribe();
- }, [id, data, sourceForm, store]);
+ const sourceForm = useForm<z.infer<typeof sourceSchema>>({
+ resolver: zodResolver(sourceSchema),
+ mode: "onChange",
+ defaultValues: {
+ id: data?.repository?.id,
+ branch: data.repository && "branch" in data.repository ? data.repository.branch : undefined,
+ rootDir: data.repository && "rootDir" in data.repository ? data.repository.rootDir : undefined,
+ },
+ });
+ useEffect(() => {
+ const sub = sourceForm.watch(
+ (
+ value: DeepPartial<z.infer<typeof sourceSchema>>,
+ { name }: { name?: keyof z.infer<typeof sourceSchema> | undefined; type?: EventType | undefined },
+ ) => {
+ console.log(value);
+ if (name === "id") {
+ let edges = store.edges;
+ if (data?.repository?.id !== undefined) {
+ edges = edges.filter((e) => {
+ if (e.target === id && e.targetHandle === "repository" && e.source === data.repository.id) {
+ return false;
+ } else {
+ return true;
+ }
+ });
+ }
+ if (value.id !== undefined) {
+ edges = edges.concat({
+ id: uuidv4(),
+ source: value.id,
+ sourceHandle: "repository",
+ target: id,
+ targetHandle: "repository",
+ });
+ }
+ store.setEdges(edges);
+ store.updateNodeData<"app">(id, {
+ repository: {
+ id: value.id,
+ },
+ });
+ } else if (name === "branch") {
+ store.updateNodeData<"app">(id, {
+ repository: {
+ ...data?.repository,
+ branch: value.branch,
+ },
+ });
+ } else if (name === "rootDir") {
+ store.updateNodeData<"app">(id, {
+ repository: {
+ ...data?.repository,
+ rootDir: value.rootDir,
+ },
+ });
+ }
+ },
+ );
+ return () => sub.unsubscribe();
+ }, [id, data, sourceForm, store]);
- return (
- <>
- <Form {...form}>
- <form>
- <FormField
- control={form.control}
- name="name"
- render={({ field }) => (
- <FormItem>
- <FormControl>
- <Input placeholder="name" className="border border-black" {...field} ref={focus(field, "name")} />
- </FormControl>
- <FormMessage />
- </FormItem>
- )}
- />
- <FormField
- control={form.control}
- name="type"
- render={({ field }) => (
- <FormItem>
- <Select onValueChange={field.onChange} defaultValue={field.value} {...typeProps}>
- <FormControl>
- <SelectTrigger>
- <SelectValue placeholder="Runtime" />
- </SelectTrigger>
- </FormControl>
- <SelectContent>
- {ServiceTypes.map((t) => (
- <SelectItem key={t} value={t}>{t}</SelectItem>
- ))}
- </SelectContent>
- </Select>
- <FormMessage />
- </FormItem>
- )}
- />
- </form>
- </Form>
- Source
- <Form {...sourceForm}>
- <form className="space-y-2">
- <FormField
- control={sourceForm.control}
- name="id"
- render={({ field }) => (
- <FormItem>
- <Select onValueChange={field.onChange} defaultValue={field.value}>
- <FormControl>
- <SelectTrigger>
- <SelectValue placeholder="Repository" />
- </SelectTrigger>
- </FormControl>
- <SelectContent>
- {(nodes.filter((n) => n.type === "github" && n.data.repository?.id !== undefined) as GithubNode[]).map((n) => (
- <SelectItem key={n.id} value={n.id}>{`${n.data.repository?.sshURL}`}</SelectItem>
- ))}
- </SelectContent>
- </Select>
- <FormMessage />
- </FormItem>
- )}
- />
- <FormField
- control={sourceForm.control}
- name="branch"
- render={({ field }) => (
- <FormItem>
- <FormControl>
- <Input placeholder="master" className="border border-black" {...field} />
- </FormControl>
- <FormMessage />
- </FormItem>
- )}
- />
- <FormField
- control={sourceForm.control}
- name="rootDir"
- render={({ field }) => (
- <FormItem>
- <FormControl>
- <Input placeholder="/" className="border border-black" {...field} />
- </FormControl>
- <FormMessage />
- </FormItem>
- )}
- />
- </form>
- </Form>
- Ports
- <ul>
- {data && data.ports && data.ports.map((p) => (<li key={p.id}><Button size={"icon"} variant={"ghost"} onClick={() => removePort(p.id)}><XIcon /></Button> {p.name} - {p.value}</li>))}
- </ul>
- <Form {...portForm}>
- <form className="flex flex-row space-x-1" onSubmit={portForm.handleSubmit(onSubmit)}>
- <FormField
- control={portForm.control}
- name="name"
- render={({ field }) => (
- <FormItem>
- <FormControl>
- <Input placeholder="name" className="border border-black" {...field} />
- </FormControl>
- <FormMessage />
- </FormItem>
- )}
- />
- <FormField
- control={portForm.control}
- name="value"
- render={({ field }) => (
- <FormItem>
- <FormControl>
- <Input placeholder="value" className="border border-black" {...field} />
- </FormControl>
- <FormMessage />
- </FormItem>
- )}
- />
- <Button type="submit">Add Port</Button>
- </form>
- </Form>
- Env Vars
- <ul>
- {data && data.envVars && data.envVars.map((v) => {
- if ("name" in v) {
- const value = "alias" in v ? v.alias : v.name;
- if (v.isEditting) {
- return (<li key={v.id}><Input type="text" className="border border-black" defaultValue={value} onKeyUp={saveAliasOnEnter(v)} onBlur={saveAliasOnBlur(v)} autoFocus={true} /></li>);
- }
- return (
- <li key={v.id} onClick={editAlias(v)}>
- <TooltipProvider>
- <Tooltip>
- <TooltipTrigger>
- <Button size={"icon"} variant={"ghost"}><PencilIcon /></Button>
- {value}
- </TooltipTrigger>
- <TooltipContent>
- {v.name}
- </TooltipContent>
- </Tooltip>
- </TooltipProvider>
- </li>
- );
- }
- })}
- </ul>
- Pre-Build Commands
- <Textarea placeholder="new line separated list of commands to run before running the service" value={data.preBuildCommands} onChange={setPreBuildCommands} />
- </>);
-}
\ No newline at end of file
+ return (
+ <>
+ <Form {...form}>
+ <form>
+ <FormField
+ control={form.control}
+ name="name"
+ render={({ field }) => (
+ <FormItem>
+ <FormControl>
+ <Input
+ placeholder="name"
+ className="border border-black"
+ {...field}
+ ref={focus(field, "name")}
+ />
+ </FormControl>
+ <FormMessage />
+ </FormItem>
+ )}
+ />
+ <FormField
+ control={form.control}
+ name="type"
+ render={({ field }) => (
+ <FormItem>
+ <Select onValueChange={field.onChange} defaultValue={field.value} {...typeProps}>
+ <FormControl>
+ <SelectTrigger>
+ <SelectValue placeholder="Runtime" />
+ </SelectTrigger>
+ </FormControl>
+ <SelectContent>
+ {ServiceTypes.map((t) => (
+ <SelectItem key={t} value={t}>
+ {t}
+ </SelectItem>
+ ))}
+ </SelectContent>
+ </Select>
+ <FormMessage />
+ </FormItem>
+ )}
+ />
+ </form>
+ </Form>
+ Source
+ <Form {...sourceForm}>
+ <form className="space-y-2">
+ <FormField
+ control={sourceForm.control}
+ name="id"
+ render={({ field }) => (
+ <FormItem>
+ <Select onValueChange={field.onChange} defaultValue={field.value}>
+ <FormControl>
+ <SelectTrigger>
+ <SelectValue placeholder="Repository" />
+ </SelectTrigger>
+ </FormControl>
+ <SelectContent>
+ {(
+ nodes.filter(
+ (n) => n.type === "github" && n.data.repository?.id !== undefined,
+ ) as GithubNode[]
+ ).map((n) => (
+ <SelectItem
+ key={n.id}
+ value={n.id}
+ >{`${n.data.repository?.sshURL}`}</SelectItem>
+ ))}
+ </SelectContent>
+ </Select>
+ <FormMessage />
+ </FormItem>
+ )}
+ />
+ <FormField
+ control={sourceForm.control}
+ name="branch"
+ render={({ field }) => (
+ <FormItem>
+ <FormControl>
+ <Input placeholder="master" className="border border-black" {...field} />
+ </FormControl>
+ <FormMessage />
+ </FormItem>
+ )}
+ />
+ <FormField
+ control={sourceForm.control}
+ name="rootDir"
+ render={({ field }) => (
+ <FormItem>
+ <FormControl>
+ <Input placeholder="/" className="border border-black" {...field} />
+ </FormControl>
+ <FormMessage />
+ </FormItem>
+ )}
+ />
+ </form>
+ </Form>
+ Ports
+ <ul>
+ {data &&
+ data.ports &&
+ data.ports.map((p) => (
+ <li key={p.id}>
+ <Button size={"icon"} variant={"ghost"} onClick={() => removePort(p.id)}>
+ <XIcon />
+ </Button>{" "}
+ {p.name} - {p.value}
+ </li>
+ ))}
+ </ul>
+ <Form {...portForm}>
+ <form className="flex flex-row space-x-1" onSubmit={portForm.handleSubmit(onSubmit)}>
+ <FormField
+ control={portForm.control}
+ name="name"
+ render={({ field }) => (
+ <FormItem>
+ <FormControl>
+ <Input placeholder="name" className="border border-black" {...field} />
+ </FormControl>
+ <FormMessage />
+ </FormItem>
+ )}
+ />
+ <FormField
+ control={portForm.control}
+ name="value"
+ render={({ field }) => (
+ <FormItem>
+ <FormControl>
+ <Input placeholder="value" className="border border-black" {...field} />
+ </FormControl>
+ <FormMessage />
+ </FormItem>
+ )}
+ />
+ <Button type="submit">Add Port</Button>
+ </form>
+ </Form>
+ Env Vars
+ <ul>
+ {data &&
+ data.envVars &&
+ data.envVars.map((v) => {
+ if ("name" in v) {
+ const value = "alias" in v ? v.alias : v.name;
+ if (v.isEditting) {
+ return (
+ <li key={v.id}>
+ <Input
+ type="text"
+ className="border border-black"
+ defaultValue={value}
+ onKeyUp={saveAliasOnEnter(v)}
+ onBlur={saveAliasOnBlur(v)}
+ autoFocus={true}
+ />
+ </li>
+ );
+ }
+ return (
+ <li key={v.id} onClick={editAlias(v)}>
+ <TooltipProvider>
+ <Tooltip>
+ <TooltipTrigger>
+ <Button size={"icon"} variant={"ghost"}>
+ <PencilIcon />
+ </Button>
+ {value}
+ </TooltipTrigger>
+ <TooltipContent>{v.name}</TooltipContent>
+ </Tooltip>
+ </TooltipProvider>
+ </li>
+ );
+ }
+ })}
+ </ul>
+ Pre-Build Commands
+ <Textarea
+ placeholder="new line separated list of commands to run before running the service"
+ value={data.preBuildCommands}
+ onChange={setPreBuildCommands}
+ />
+ </>
+ );
+}