Canvas: Disable all inputs during non-edit mode

Change-Id: Ifca28e7cb78cb38175d5463311ff3d5006d412f8
diff --git a/apps/canvas/front/src/components/details.tsx b/apps/canvas/front/src/components/details.tsx
index cb5aba5..288390a 100644
--- a/apps/canvas/front/src/components/details.tsx
+++ b/apps/canvas/front/src/components/details.tsx
@@ -1,5 +1,5 @@
 import { useNodes } from "@xyflow/react";
-import { AppNode, nodeLabel, NodeType } from "@/lib/state";
+import { AppNode, nodeLabel, NodeType, useMode } from "@/lib/state";
 import { NodeDetails } from "@/components/node-details";
 import { Accordion, AccordionContent, AccordionTrigger } from "./ui/accordion";
 import { AccordionItem } from "@radix-ui/react-accordion";
@@ -41,6 +41,9 @@
 	const [open, setOpen] = useState<string[]>([]);
 	const selected = useMemo(() => nodes.filter((n) => n.selected).map((n) => n.id), [nodes]);
 	const all = useMemo(() => open.concat(selected).filter(unique), [open, selected]);
+	const mode = useMode();
+	const isDeployMode = mode === "deploy";
+
 	return (
 		<Accordion
 			type="multiple"
@@ -59,7 +62,7 @@
 							</div>
 						</AccordionTrigger>
 						<AccordionContent className="pt-1">
-							<NodeDetails {...n} />
+							<NodeDetails {...n} disabled={isDeployMode} />
 						</AccordionContent>
 					</AccordionItem>
 				</>
diff --git a/apps/canvas/front/src/components/node-app.tsx b/apps/canvas/front/src/components/node-app.tsx
index f01bd98..7516d19 100644
--- a/apps/canvas/front/src/components/node-app.tsx
+++ b/apps/canvas/front/src/components/node-app.tsx
@@ -91,7 +91,7 @@
 	subdomain: z.string().min(1, "required"),
 });
 
-export function NodeAppDetails({ id, data }: ServiceNode) {
+export function NodeAppDetails({ id, data, disabled }: ServiceNode & { disabled?: boolean }) {
 	const store = useStateStore();
 	const nodes = useNodes<AppNode>();
 	const env = useEnv();
@@ -640,6 +640,7 @@
 										className="lowercase"
 										{...field}
 										ref={focus(field, "name")}
+										disabled={disabled}
 									/>
 								</FormControl>
 								<FormMessage />
@@ -651,7 +652,12 @@
 						name="type"
 						render={({ field }) => (
 							<FormItem>
-								<Select onValueChange={field.onChange} defaultValue={field.value} {...typeProps}>
+								<Select
+									onValueChange={field.onChange}
+									defaultValue={field.value}
+									{...typeProps}
+									disabled={disabled}
+								>
 									<FormControl>
 										<SelectTrigger>
 											<SelectValue placeholder="Runtime" />
@@ -679,7 +685,7 @@
 						name="id"
 						render={({ field }) => (
 							<FormItem>
-								<Select onValueChange={field.onChange} defaultValue={field.value}>
+								<Select onValueChange={field.onChange} defaultValue={field.value} disabled={disabled}>
 									<FormControl>
 										<SelectTrigger>
 											<SelectValue placeholder="Repository" />
@@ -708,7 +714,7 @@
 						render={({ field }) => (
 							<FormItem>
 								<FormControl>
-									<Input placeholder="master" className="lowercase" {...field} />
+									<Input placeholder="master" className="lowercase" {...field} disabled={disabled} />
 								</FormControl>
 								<FormMessage />
 							</FormItem>
@@ -720,7 +726,7 @@
 						render={({ field }) => (
 							<FormItem>
 								<FormControl>
-									<Input placeholder="/" {...field} />
+									<Input placeholder="/" {...field} disabled={disabled} />
 								</FormControl>
 								<FormMessage />
 							</FormItem>
@@ -739,6 +745,7 @@
 								variant={"ghost"}
 								onClick={() => removePort(p.id)}
 								className="w-4 h-4"
+								disabled={disabled}
 							>
 								<XIcon />
 							</Button>
@@ -756,7 +763,7 @@
 						render={({ field }) => (
 							<FormItem>
 								<FormControl>
-									<Input placeholder="name" className="lowercase" {...field} />
+									<Input placeholder="name" className="lowercase" {...field} disabled={disabled} />
 								</FormControl>
 								<FormMessage />
 							</FormItem>
@@ -768,13 +775,15 @@
 						render={({ field }) => (
 							<FormItem>
 								<FormControl>
-									<Input placeholder="value" {...field} />
+									<Input placeholder="value" {...field} disabled={disabled} />
 								</FormControl>
 								<FormMessage />
 							</FormItem>
 						)}
 					/>
-					<Button type="submit">Add Port</Button>
+					<Button type="submit" disabled={disabled}>
+						Add Port
+					</Button>
 				</form>
 			</Form>
 			Env Vars
@@ -794,6 +803,7 @@
 											onKeyUp={saveAliasOnEnter(v)}
 											onBlur={saveAliasOnBlur(v)}
 											autoFocus={true}
+											disabled={disabled}
 										/>
 									</li>
 								);
@@ -823,6 +833,7 @@
 				placeholder="new line separated list of commands to run before running the service"
 				value={data.preBuildCommands}
 				onChange={setPreBuildCommands}
+				disabled={disabled}
 			/>
 			Dev
 			<Form {...devForm}>
@@ -833,7 +844,12 @@
 						render={({ field }) => (
 							<FormItem>
 								<div className="flex flex-row gap-1 items-center">
-									<Checkbox id="devEnabled" onCheckedChange={field.onChange} checked={field.value} />
+									<Checkbox
+										id="devEnabled"
+										onCheckedChange={field.onChange}
+										checked={field.value}
+										disabled={disabled}
+									/>
 									<Label htmlFor="devEnabled">Enabled</Label>
 								</div>
 								<FormMessage />
@@ -850,7 +866,11 @@
 							name="network"
 							render={({ field }) => (
 								<FormItem>
-									<Select onValueChange={field.onChange} defaultValue={field.value}>
+									<Select
+										onValueChange={field.onChange}
+										defaultValue={field.value}
+										disabled={disabled}
+									>
 										<FormControl>
 											<SelectTrigger>
 												<SelectValue placeholder="Network" />
@@ -875,7 +895,7 @@
 							render={({ field }) => (
 								<FormItem>
 									<FormControl>
-										<Input placeholder="subdomain" {...field} />
+										<Input placeholder="subdomain" {...field} disabled={disabled} />
 									</FormControl>
 									<FormMessage />
 								</FormItem>
diff --git a/apps/canvas/front/src/components/node-details.tsx b/apps/canvas/front/src/components/node-details.tsx
index 503d5ee..2d9cd58 100644
--- a/apps/canvas/front/src/components/node-details.tsx
+++ b/apps/canvas/front/src/components/node-details.tsx
@@ -7,7 +7,11 @@
 import { NodeGithubDetails } from "./node-github";
 import { NodeGatewayTCPDetails } from "./node-gateway-tcp";
 
-export function NodeDetails(props: AppNode) {
+type NodeDetailsProps = AppNode & {
+	disabled?: boolean;
+};
+
+export function NodeDetails(props: NodeDetailsProps) {
 	return (
 		<div className="px-1 flex flex-col gap-2">
 			<NodeDetailsImpl {...props} />
@@ -15,7 +19,7 @@
 	);
 }
 
-function NodeDetailsImpl(props: AppNode) {
+function NodeDetailsImpl(props: NodeDetailsProps) {
 	switch (props.type) {
 		case "app":
 			return <NodeAppDetails {...props} />;
diff --git a/apps/canvas/front/src/components/node-gateway-https.tsx b/apps/canvas/front/src/components/node-gateway-https.tsx
index 3785d59..e88902d 100644
--- a/apps/canvas/front/src/components/node-gateway-https.tsx
+++ b/apps/canvas/front/src/components/node-gateway-https.tsx
@@ -71,7 +71,7 @@
 	);
 }
 
-export function NodeGatewayHttpsDetails({ id, data }: GatewayHttpsNode) {
+export function NodeGatewayHttpsDetails({ id, data, disabled }: GatewayHttpsNode & { disabled?: boolean }) {
 	const store = useStateStore();
 	const env = useEnv();
 	const form = useForm<z.infer<typeof schema>>({
@@ -301,7 +301,7 @@
 								<Select
 									onValueChange={field.onChange}
 									defaultValue={field.value}
-									disabled={data.readonly}
+									disabled={data.readonly || disabled}
 								>
 									<FormControl>
 										<SelectTrigger>
@@ -327,7 +327,7 @@
 						render={({ field }) => (
 							<FormItem>
 								<FormControl>
-									<Input placeholder="subdomain" {...field} disabled={data.readonly} />
+									<Input placeholder="subdomain" {...field} disabled={data.readonly || disabled} />
 								</FormControl>
 								<FormMessage />
 							</FormItem>
@@ -345,7 +345,7 @@
 								<Select
 									onValueChange={field.onChange}
 									defaultValue={field.value}
-									disabled={data.readonly}
+									disabled={data.readonly || disabled}
 								>
 									<FormControl>
 										<SelectTrigger>
@@ -372,7 +372,7 @@
 								<Select
 									onValueChange={field.onChange}
 									defaultValue={field.value}
-									disabled={data.readonly}
+									disabled={data.readonly || disabled}
 								>
 									<FormControl>
 										<SelectTrigger>
@@ -403,7 +403,12 @@
 						render={({ field }) => (
 							<FormItem>
 								<div className="flex flex-row gap-1 items-center">
-									<Checkbox id="authEnabled" onCheckedChange={field.onChange} checked={field.value} />
+									<Checkbox
+										id="authEnabled"
+										onCheckedChange={field.onChange}
+										checked={field.value}
+										disabled={disabled}
+									/>
 									<Label htmlFor="authEnabled">Enabled</Label>
 								</div>
 								<FormMessage />
@@ -418,7 +423,12 @@
 					<ul>
 						{(data.auth.groups || []).map((p) => (
 							<li key={p} className="flex flex-row gap-1 items-center">
-								<Button size={"icon"} variant={"ghost"} onClick={() => removeGroup(p)}>
+								<Button
+									size={"icon"}
+									variant={"ghost"}
+									onClick={() => removeGroup(p)}
+									disabled={disabled}
+								>
 									<XIcon />
 								</Button>
 								<div>{p}</div>
@@ -433,20 +443,27 @@
 								render={({ field }) => (
 									<FormItem>
 										<FormControl>
-											<Input placeholder="group" {...field} />
+											<Input placeholder="group" {...field} disabled={disabled} />
 										</FormControl>
 										<FormMessage />
 									</FormItem>
 								)}
 							/>
-							<Button type="submit">Add Group</Button>
+							<Button type="submit" disabled={disabled}>
+								Add Group
+							</Button>
 						</form>
 					</Form>
 					Auth optional path patterns
 					<ul>
 						{(data.auth.noAuthPathPatterns || []).map((p) => (
 							<li key={p} className="flex flex-row gap-1 items-center">
-								<Button size={"icon"} variant={"ghost"} onClick={() => removeNoAuthPathPattern(p)}>
+								<Button
+									size={"icon"}
+									variant={"ghost"}
+									onClick={() => removeNoAuthPathPattern(p)}
+									disabled={disabled}
+								>
 									<XIcon />
 								</Button>
 								<div>{p}</div>
@@ -464,13 +481,15 @@
 								render={({ field }) => (
 									<FormItem>
 										<FormControl>
-											<Input placeholder="group" {...field} />
+											<Input placeholder="group" {...field} disabled={disabled} />
 										</FormControl>
 										<FormMessage />
 									</FormItem>
 								)}
 							/>
-							<Button type="submit">Add</Button>
+							<Button type="submit" disabled={disabled}>
+								Add
+							</Button>
 						</form>
 					</Form>
 				</>
diff --git a/apps/canvas/front/src/components/node-gateway-tcp.tsx b/apps/canvas/front/src/components/node-gateway-tcp.tsx
index 86fa493..bd1e5b6 100644
--- a/apps/canvas/front/src/components/node-gateway-tcp.tsx
+++ b/apps/canvas/front/src/components/node-gateway-tcp.tsx
@@ -48,7 +48,7 @@
 	);
 }
 
-export function NodeGatewayTCPDetails({ id, data }: GatewayTCPNode) {
+export function NodeGatewayTCPDetails({ id, data, disabled }: GatewayTCPNode & { disabled?: boolean }) {
 	const store = useStateStore();
 	const env = useEnv();
 	const form = useForm<z.infer<typeof schema>>({
@@ -235,7 +235,7 @@
 								<Select
 									onValueChange={field.onChange}
 									defaultValue={field.value}
-									disabled={data.readonly}
+									disabled={data.readonly || disabled}
 								>
 									<FormControl>
 										<SelectTrigger>
@@ -261,7 +261,7 @@
 						render={({ field }) => (
 							<FormItem>
 								<FormControl>
-									<Input placeholder="subdomain" {...field} disabled={data.readonly} />
+									<Input placeholder="subdomain" {...field} disabled={data.readonly || disabled} />
 								</FormControl>
 								<FormMessage />
 							</FormItem>
@@ -287,7 +287,7 @@
 								<Select
 									onValueChange={field.onChange}
 									defaultValue={field.value}
-									disabled={data.readonly}
+									disabled={data.readonly || disabled}
 								>
 									<FormControl>
 										<SelectTrigger>
@@ -312,7 +312,7 @@
 								<Select
 									onValueChange={field.onChange}
 									defaultValue={field.value}
-									disabled={data.readonly}
+									disabled={data.readonly || disabled}
 								>
 									<FormControl>
 										<SelectTrigger>
@@ -332,7 +332,9 @@
 							</FormItem>
 						)}
 					/>
-					<Button type="submit">Expose</Button>
+					<Button type="submit" disabled={disabled}>
+						Expose
+					</Button>
 				</form>
 			</Form>
 		</>
diff --git a/apps/canvas/front/src/components/node-github.tsx b/apps/canvas/front/src/components/node-github.tsx
index ae9d2ab..4c065be 100644
--- a/apps/canvas/front/src/components/node-github.tsx
+++ b/apps/canvas/front/src/components/node-github.tsx
@@ -36,8 +36,7 @@
 	repositoryId: z.number().optional(),
 });
 
-export function NodeGithubDetails(node: GithubNode) {
-	const { id, data } = node;
+export function NodeGithubDetails({ id, data, disabled }: GithubNode & { disabled?: boolean }) {
 	const store = useStateStore();
 	const projectId = useProjectId();
 	const [repos, setRepos] = useState<GitHubRepository[]>([]);
@@ -138,7 +137,7 @@
 								<Select
 									onValueChange={(value) => field.onChange(Number(value))}
 									value={field.value?.toString()}
-									disabled={loading || !projectId || !githubService}
+									disabled={loading || !projectId || !githubService || disabled}
 								>
 									<FormControl>
 										<SelectTrigger>
diff --git a/apps/canvas/front/src/components/node-mongodb.tsx b/apps/canvas/front/src/components/node-mongodb.tsx
index bb787cd..69d0d0f 100644
--- a/apps/canvas/front/src/components/node-mongodb.tsx
+++ b/apps/canvas/front/src/components/node-mongodb.tsx
@@ -31,7 +31,7 @@
 	name: z.string().min(1, "required"),
 });
 
-export function NodeMongoDBDetails({ id, data }: MongoDBNode) {
+export function NodeMongoDBDetails({ id, data, disabled }: MongoDBNode & { disabled?: boolean }) {
 	const store = useStateStore();
 	const form = useForm<z.infer<typeof schema>>({
 		resolver: zodResolver(schema),
@@ -66,7 +66,7 @@
 						render={({ field }) => (
 							<FormItem>
 								<FormControl>
-									<Input placeholder="name" {...field} />
+									<Input placeholder="name" {...field} disabled={disabled} />
 								</FormControl>
 								<FormMessage />
 							</FormItem>
diff --git a/apps/canvas/front/src/components/node-postgresql.tsx b/apps/canvas/front/src/components/node-postgresql.tsx
index 292fd1e..d31cfe2 100644
--- a/apps/canvas/front/src/components/node-postgresql.tsx
+++ b/apps/canvas/front/src/components/node-postgresql.tsx
@@ -31,7 +31,7 @@
 	name: z.string().min(1, "required"),
 });
 
-export function NodePostgreSQLDetails({ id, data }: PostgreSQLNode) {
+export function NodePostgreSQLDetails({ id, data, disabled }: PostgreSQLNode & { disabled?: boolean }) {
 	const store = useStateStore();
 	const form = useForm<z.infer<typeof schema>>({
 		resolver: zodResolver(schema),
@@ -66,7 +66,7 @@
 						render={({ field }) => (
 							<FormItem>
 								<FormControl>
-									<Input placeholder="name" {...field} />
+									<Input placeholder="name" {...field} disabled={disabled} />
 								</FormControl>
 								<FormMessage />
 							</FormItem>
diff --git a/apps/canvas/front/src/components/node-volume.tsx b/apps/canvas/front/src/components/node-volume.tsx
index 80d8c99..3118681 100644
--- a/apps/canvas/front/src/components/node-volume.tsx
+++ b/apps/canvas/front/src/components/node-volume.tsx
@@ -39,7 +39,7 @@
 	size: z.string().min(1).default("1Gi"),
 });
 
-export function NodeVolumeDetails({ id, data }: VolumeNode) {
+export function NodeVolumeDetails({ id, data, disabled }: VolumeNode & { disabled?: boolean }) {
 	const store = useStateStore();
 	const form = useForm<z.infer<typeof schema>>({
 		resolver: zodResolver(schema),
@@ -86,7 +86,7 @@
 						render={({ field }) => (
 							<FormItem>
 								<FormControl>
-									<Input placeholder="name" {...field} />
+									<Input placeholder="name" {...field} disabled={disabled} />
 								</FormControl>
 								<FormMessage />
 							</FormItem>
@@ -97,7 +97,7 @@
 						name="type"
 						render={({ field }) => (
 							<FormItem>
-								<Select onValueChange={field.onChange} defaultValue={field.value}>
+								<Select onValueChange={field.onChange} defaultValue={field.value} disabled={disabled}>
 									<FormControl>
 										<SelectTrigger>
 											<SelectValue placeholder="Volume Type" />
@@ -121,7 +121,7 @@
 						render={({ field }) => (
 							<FormItem>
 								<FormControl>
-									<Input placeholder="size" {...field} />
+									<Input placeholder="size" {...field} disabled={disabled} />
 								</FormControl>
 								<FormMessage />
 							</FormItem>
diff --git a/apps/canvas/front/src/lib/state.ts b/apps/canvas/front/src/lib/state.ts
index a3caf51..9d74d5b 100644
--- a/apps/canvas/front/src/lib/state.ts
+++ b/apps/canvas/front/src/lib/state.ts
@@ -474,6 +474,10 @@
 	return useStateStore(githubServiceSelector);
 }
 
+export function useMode(): "edit" | "deploy" {
+	return useStateStore((state) => state.mode);
+}
+
 const v: Validator = CreateValidators();
 
 function getRandomPosition({ width, height, transformX, transformY, transformZoom }: Viewport): XYPosition {