Canvas: Improve layout
Change-Id: Ife4f14d23eefc0ef0cb6b189446590fc42b8d797
diff --git a/apps/canvas/front/package-lock.json b/apps/canvas/front/package-lock.json
index a691f23..856057f 100644
--- a/apps/canvas/front/package-lock.json
+++ b/apps/canvas/front/package-lock.json
@@ -9,6 +9,7 @@
"version": "0.0.0",
"dependencies": {
"@hookform/resolvers": "^3.9.1",
+ "@microlink/react-json-view": "^1.26.1",
"@radix-ui/react-accordion": "^1.2.1",
"@radix-ui/react-checkbox": "^1.3.1",
"@radix-ui/react-collapsible": "^1.1.1",
@@ -291,6 +292,14 @@
"@babel/core": "^7.0.0-0"
}
},
+ "node_modules/@babel/runtime": {
+ "version": "7.27.1",
+ "resolved": "https://registry.npmjs.org/@babel/runtime/-/runtime-7.27.1.tgz",
+ "integrity": "sha512-1x3D2xEk2fRo3PAhwQwu5UubzgiVWSXTBfWpVd2Mx2AzRqJuDJCsgaDVZ7HB5iGzDW1Hl1sWN2mFyKjmR9uAog==",
+ "engines": {
+ "node": ">=6.9.0"
+ }
+ },
"node_modules/@babel/template": {
"version": "7.25.9",
"resolved": "https://registry.npmjs.org/@babel/template/-/template-7.25.9.tgz",
@@ -989,6 +998,23 @@
"@jridgewell/sourcemap-codec": "^1.4.14"
}
},
+ "node_modules/@microlink/react-json-view": {
+ "version": "1.26.1",
+ "resolved": "https://registry.npmjs.org/@microlink/react-json-view/-/react-json-view-1.26.1.tgz",
+ "integrity": "sha512-2H5QCYdZlJi+oN4YBiUYPPFTNh/KLCN9i9yz8NwmSkRqXSRXYtEVIRffc9L34jdopKGK/tK21SeuzXVJHQLkfQ==",
+ "dependencies": {
+ "react-base16-styling": "~0.9.0",
+ "react-lifecycles-compat": "~3.0.4",
+ "react-textarea-autosize": "~8.5.7"
+ },
+ "engines": {
+ "node": ">=17"
+ },
+ "peerDependencies": {
+ "react": ">= 15",
+ "react-dom": ">= 15"
+ }
+ },
"node_modules/@nodelib/fs.scandir": {
"version": "2.1.5",
"resolved": "https://registry.npmjs.org/@nodelib/fs.scandir/-/fs.scandir-2.1.5.tgz",
@@ -2631,6 +2657,11 @@
"@babel/types": "^7.20.7"
}
},
+ "node_modules/@types/base16": {
+ "version": "1.0.5",
+ "resolved": "https://registry.npmjs.org/@types/base16/-/base16-1.0.5.tgz",
+ "integrity": "sha512-OzOWrTluG9cwqidEzC/Q6FAmIPcnZfm8BFRlIx0+UIUqnuAmi5OS88O0RpT3Yz6qdmqObvUhasrbNsCofE4W9A=="
+ },
"node_modules/@types/d3-color": {
"version": "3.1.3",
"resolved": "https://registry.npmjs.org/@types/d3-color/-/d3-color-3.1.3.tgz",
@@ -2686,6 +2717,11 @@
"integrity": "sha512-5+fP8P8MFNC+AyZCDxrB2pkZFPGzqQWUzpSeuuVLvm8VMcorNYavBqoFcxK8bQz4Qsbn4oUEEem4wDLfcysGHA==",
"dev": true
},
+ "node_modules/@types/lodash": {
+ "version": "4.17.16",
+ "resolved": "https://registry.npmjs.org/@types/lodash/-/lodash-4.17.16.tgz",
+ "integrity": "sha512-HX7Em5NYQAXKW+1T+FiuG27NGwzJfCX3s1GjOa7ujxZa52kjJLOr4FUxT+giF6Tgxv1e+/czV/iTtBw27WTU9g=="
+ },
"node_modules/@types/node": {
"version": "22.8.4",
"resolved": "https://registry.npmjs.org/@types/node/-/node-22.8.4.tgz",
@@ -3167,6 +3203,11 @@
"resolved": "https://registry.npmjs.org/balanced-match/-/balanced-match-1.0.2.tgz",
"integrity": "sha512-3oSeUO0TMV67hN1AmbXsK4yaqU7tjiHlbxRDZOpH0KW9+CeX4bRAaX0Anxt0tx2MrpRpWwQaPwIlISEJhYU5Pw=="
},
+ "node_modules/base16": {
+ "version": "1.0.0",
+ "resolved": "https://registry.npmjs.org/base16/-/base16-1.0.0.tgz",
+ "integrity": "sha512-pNdYkNPiJUnEhnfXV56+sQy8+AaPcG3POZAUnwr4EeqCUZFz4u2PePbo3e5Gj4ziYPCWGUZT9RHisvJKnwFuBQ=="
+ },
"node_modules/binary-extensions": {
"version": "2.3.0",
"resolved": "https://registry.npmjs.org/binary-extensions/-/binary-extensions-2.3.0.tgz",
@@ -3350,6 +3391,15 @@
"node": ">=6"
}
},
+ "node_modules/color": {
+ "version": "3.2.1",
+ "resolved": "https://registry.npmjs.org/color/-/color-3.2.1.tgz",
+ "integrity": "sha512-aBl7dZI9ENN6fUGC7mWpMTPNHmWUSNan9tuWN6ahh5ZLNk9baLJOnSMlrQkHcrfFgz2/RigjUVAjdx36VcemKA==",
+ "dependencies": {
+ "color-convert": "^1.9.3",
+ "color-string": "^1.6.0"
+ }
+ },
"node_modules/color-convert": {
"version": "2.0.1",
"resolved": "https://registry.npmjs.org/color-convert/-/color-convert-2.0.1.tgz",
@@ -3366,6 +3416,28 @@
"resolved": "https://registry.npmjs.org/color-name/-/color-name-1.1.4.tgz",
"integrity": "sha512-dOy+3AuW3a2wNbZHIuMZpTcgjGuLU/uBL/ubcZF9OXbDo8ff4O8yVp5Bf0efS8uEoYo5q4Fx7dY9OgQGXgAsQA=="
},
+ "node_modules/color-string": {
+ "version": "1.9.1",
+ "resolved": "https://registry.npmjs.org/color-string/-/color-string-1.9.1.tgz",
+ "integrity": "sha512-shrVawQFojnZv6xM40anx4CkoDP+fZsw/ZerEMsW/pyzsRbElpsL/DBVW7q3ExxwusdNXI3lXpuhEZkzs8p5Eg==",
+ "dependencies": {
+ "color-name": "^1.0.0",
+ "simple-swizzle": "^0.2.2"
+ }
+ },
+ "node_modules/color/node_modules/color-convert": {
+ "version": "1.9.3",
+ "resolved": "https://registry.npmjs.org/color-convert/-/color-convert-1.9.3.tgz",
+ "integrity": "sha512-QfAUtd+vFdAtFQcC8CCyYt1fYWxSqAiK2cSD6zDB8N3cpsEBAvRxp9zOGg6G/SHHJYAT88/az/IuDGALsNVbGg==",
+ "dependencies": {
+ "color-name": "1.1.3"
+ }
+ },
+ "node_modules/color/node_modules/color-name": {
+ "version": "1.1.3",
+ "resolved": "https://registry.npmjs.org/color-name/-/color-name-1.1.3.tgz",
+ "integrity": "sha512-72fSenhMw2HZMTVHeCA9KCmpEIbzWiQsjN+BHcBbS9vr1mtt+vJjPdksIBNUmKAW8TFUDPJK5SUU3QhE9NEXDw=="
+ },
"node_modules/commander": {
"version": "4.1.1",
"resolved": "https://registry.npmjs.org/commander/-/commander-4.1.1.tgz",
@@ -3413,8 +3485,7 @@
"node_modules/csstype": {
"version": "3.1.3",
"resolved": "https://registry.npmjs.org/csstype/-/csstype-3.1.3.tgz",
- "integrity": "sha512-M1uQkMl8rQK/szD0LNhtqxIPLpimGm8sOBwU7lLnCpSbTyY3yeU1Vc7l4KT5zT4s/yOxHH5O7tIuuLOCnLADRw==",
- "devOptional": true
+ "integrity": "sha512-M1uQkMl8rQK/szD0LNhtqxIPLpimGm8sOBwU7lLnCpSbTyY3yeU1Vc7l4KT5zT4s/yOxHH5O7tIuuLOCnLADRw=="
},
"node_modules/d3-color": {
"version": "3.1.0",
@@ -4110,6 +4181,11 @@
"loose-envify": "^1.0.0"
}
},
+ "node_modules/is-arrayish": {
+ "version": "0.3.2",
+ "resolved": "https://registry.npmjs.org/is-arrayish/-/is-arrayish-0.3.2.tgz",
+ "integrity": "sha512-eVRqCvVlZbuw3GrM63ovNSNAeA1K16kaR/LRY/92w0zxQ5/1YzwblUX652i4Xs9RwAGjW9d9y6X88t8OaAJfWQ=="
+ },
"node_modules/is-binary-path": {
"version": "2.1.0",
"resolved": "https://registry.npmjs.org/is-binary-path/-/is-binary-path-2.1.0.tgz",
@@ -4306,6 +4382,11 @@
"url": "https://github.com/sponsors/sindresorhus"
}
},
+ "node_modules/lodash.curry": {
+ "version": "4.1.1",
+ "resolved": "https://registry.npmjs.org/lodash.curry/-/lodash.curry-4.1.1.tgz",
+ "integrity": "sha512-/u14pXGviLaweY5JI0IUzgzF2J6Ne8INyzAZjImcryjgkZ+ebruBxy2/JaOOkTqScddcYtakjhSaeemV8lR0tA=="
+ },
"node_modules/lodash.merge": {
"version": "4.6.2",
"resolved": "https://registry.npmjs.org/lodash.merge/-/lodash.merge-4.6.2.tgz",
@@ -4821,6 +4902,20 @@
"node": ">=0.10.0"
}
},
+ "node_modules/react-base16-styling": {
+ "version": "0.9.1",
+ "resolved": "https://registry.npmjs.org/react-base16-styling/-/react-base16-styling-0.9.1.tgz",
+ "integrity": "sha512-1s0CY1zRBOQ5M3T61wetEpvQmsYSNtWEcdYzyZNxKa8t7oDvaOn9d21xrGezGAHFWLM7SHcktPuPTrvoqxSfKw==",
+ "dependencies": {
+ "@babel/runtime": "^7.16.7",
+ "@types/base16": "^1.0.2",
+ "@types/lodash": "^4.14.178",
+ "base16": "^1.0.0",
+ "color": "^3.2.1",
+ "csstype": "^3.0.10",
+ "lodash.curry": "^4.1.1"
+ }
+ },
"node_modules/react-dom": {
"version": "18.3.1",
"resolved": "https://registry.npmjs.org/react-dom/-/react-dom-18.3.1.tgz",
@@ -4856,6 +4951,11 @@
"react": "*"
}
},
+ "node_modules/react-lifecycles-compat": {
+ "version": "3.0.4",
+ "resolved": "https://registry.npmjs.org/react-lifecycles-compat/-/react-lifecycles-compat-3.0.4.tgz",
+ "integrity": "sha512-fBASbA6LnOU9dOU2eW7aQ8xmYBSXUIWr+UmF9b1efZBazGNO+rcXT/icdKnYm2pTwcRylVUYwW7H1PHfLekVzA=="
+ },
"node_modules/react-refresh": {
"version": "0.14.2",
"resolved": "https://registry.npmjs.org/react-refresh/-/react-refresh-0.14.2.tgz",
@@ -4941,6 +5041,22 @@
}
}
},
+ "node_modules/react-textarea-autosize": {
+ "version": "8.5.9",
+ "resolved": "https://registry.npmjs.org/react-textarea-autosize/-/react-textarea-autosize-8.5.9.tgz",
+ "integrity": "sha512-U1DGlIQN5AwgjTyOEnI1oCcMuEr1pv1qOtklB2l4nyMGbHzWrI0eFsYK0zos2YWqAolJyG0IWJaqWmWj5ETh0A==",
+ "dependencies": {
+ "@babel/runtime": "^7.20.13",
+ "use-composed-ref": "^1.3.0",
+ "use-latest": "^1.2.1"
+ },
+ "engines": {
+ "node": ">=10"
+ },
+ "peerDependencies": {
+ "react": "^16.8.0 || ^17.0.0 || ^18.0.0 || ^19.0.0"
+ }
+ },
"node_modules/read-cache": {
"version": "1.0.0",
"resolved": "https://registry.npmjs.org/read-cache/-/read-cache-1.0.0.tgz",
@@ -5100,6 +5216,14 @@
"url": "https://github.com/sponsors/isaacs"
}
},
+ "node_modules/simple-swizzle": {
+ "version": "0.2.2",
+ "resolved": "https://registry.npmjs.org/simple-swizzle/-/simple-swizzle-0.2.2.tgz",
+ "integrity": "sha512-JA//kQgZtbuY83m+xT+tXJkmJncGMTFT+C+g2h2R9uxkYIrE2yy9sgmcLhCnw57/WSD+Eh3J97FPEDFnbXnDUg==",
+ "dependencies": {
+ "is-arrayish": "^0.3.1"
+ }
+ },
"node_modules/source-map-js": {
"version": "1.2.1",
"resolved": "https://registry.npmjs.org/source-map-js/-/source-map-js-1.2.1.tgz",
@@ -5476,6 +5600,48 @@
}
}
},
+ "node_modules/use-composed-ref": {
+ "version": "1.4.0",
+ "resolved": "https://registry.npmjs.org/use-composed-ref/-/use-composed-ref-1.4.0.tgz",
+ "integrity": "sha512-djviaxuOOh7wkj0paeO1Q/4wMZ8Zrnag5H6yBvzN7AKKe8beOaED9SF5/ByLqsku8NP4zQqsvM2u3ew/tJK8/w==",
+ "peerDependencies": {
+ "react": "^16.8.0 || ^17.0.0 || ^18.0.0 || ^19.0.0"
+ },
+ "peerDependenciesMeta": {
+ "@types/react": {
+ "optional": true
+ }
+ }
+ },
+ "node_modules/use-isomorphic-layout-effect": {
+ "version": "1.2.0",
+ "resolved": "https://registry.npmjs.org/use-isomorphic-layout-effect/-/use-isomorphic-layout-effect-1.2.0.tgz",
+ "integrity": "sha512-q6ayo8DWoPZT0VdG4u3D3uxcgONP3Mevx2i2b0434cwWBoL+aelL1DzkXI6w3PhTZzUeR2kaVlZn70iCiseP6w==",
+ "peerDependencies": {
+ "react": "^16.8.0 || ^17.0.0 || ^18.0.0 || ^19.0.0"
+ },
+ "peerDependenciesMeta": {
+ "@types/react": {
+ "optional": true
+ }
+ }
+ },
+ "node_modules/use-latest": {
+ "version": "1.3.0",
+ "resolved": "https://registry.npmjs.org/use-latest/-/use-latest-1.3.0.tgz",
+ "integrity": "sha512-mhg3xdm9NaM8q+gLT8KryJPnRFOz1/5XPBhmDEVZK1webPzDjrPk7f/mbpeLqTgB9msytYWANxgALOCJKnLvcQ==",
+ "dependencies": {
+ "use-isomorphic-layout-effect": "^1.1.1"
+ },
+ "peerDependencies": {
+ "react": "^16.8.0 || ^17.0.0 || ^18.0.0 || ^19.0.0"
+ },
+ "peerDependenciesMeta": {
+ "@types/react": {
+ "optional": true
+ }
+ }
+ },
"node_modules/use-sidecar": {
"version": "1.1.2",
"resolved": "https://registry.npmjs.org/use-sidecar/-/use-sidecar-1.1.2.tgz",
diff --git a/apps/canvas/front/package.json b/apps/canvas/front/package.json
index 646ef6b..c99ec28 100644
--- a/apps/canvas/front/package.json
+++ b/apps/canvas/front/package.json
@@ -17,6 +17,7 @@
},
"dependencies": {
"@hookform/resolvers": "^3.9.1",
+ "@microlink/react-json-view": "^1.26.1",
"@radix-ui/react-accordion": "^1.2.1",
"@radix-ui/react-checkbox": "^1.3.1",
"@radix-ui/react-collapsible": "^1.1.1",
@@ -75,4 +76,4 @@
"bracketSpacing": true,
"arrowParens": "always"
}
-}
\ No newline at end of file
+}
diff --git a/apps/canvas/front/src/App.tsx b/apps/canvas/front/src/App.tsx
index afacdb1..f37370a 100644
--- a/apps/canvas/front/src/App.tsx
+++ b/apps/canvas/front/src/App.tsx
@@ -5,38 +5,42 @@
import { Config } from "./Config";
import { Integrations } from "./Integrations";
import { Toaster } from "./components/ui/toaster";
-import { Header } from "./Header";
+import { ProjectSelect } from "./ProjectSelect";
import { Logs } from "./components/logs";
export default function App() {
return (
<ReactFlowProvider>
- <Header />
- <AppImpl />
- <Toaster />
+ <div className="h-screen flex flex-col">
+ <AppImpl />
+ <Toaster />
+ </div>
</ReactFlowProvider>
);
}
function AppImpl() {
return (
- <Tabs defaultValue="canvas">
- <TabsList>
- <TabsTrigger value="canvas">Canvas</TabsTrigger>
- <TabsTrigger value="config">Config</TabsTrigger>
- <TabsTrigger value="integrations">Integrations</TabsTrigger>
- <TabsTrigger value="logs">Logs</TabsTrigger>
- </TabsList>
- <TabsContent value="canvas">
+ <Tabs defaultValue="canvas" className="flex-1 flex flex-col min-h-0">
+ <div className="flex items-center justify-between px-4 border-b">
+ <TabsList>
+ <TabsTrigger value="canvas">Canvas</TabsTrigger>
+ <TabsTrigger value="logs">Logs</TabsTrigger>
+ <TabsTrigger value="config">Config</TabsTrigger>
+ <TabsTrigger value="integrations">Integrations</TabsTrigger>
+ </TabsList>
+ <ProjectSelect />
+ </div>
+ <TabsContent value="canvas" className="flex-1 min-h-0">
<CanvasBuilder />
</TabsContent>
- <TabsContent value="config">
+ <TabsContent value="config" className="flex-1 min-h-0">
<Config />
</TabsContent>
- <TabsContent value="integrations">
+ <TabsContent value="integrations" className="flex-1 min-h-0">
<Integrations />
</TabsContent>
- <TabsContent value="logs">
+ <TabsContent value="logs" className="flex-1 min-h-0">
<Logs />
</TabsContent>
</Tabs>
diff --git a/apps/canvas/front/src/Config.tsx b/apps/canvas/front/src/Config.tsx
index df03746..7a93634 100644
--- a/apps/canvas/front/src/Config.tsx
+++ b/apps/canvas/front/src/Config.tsx
@@ -2,6 +2,8 @@
import { AppNode, useEnv, useProjectId } from "./lib/state";
import { generateDodoConfig } from "./lib/config";
import { useEffect, useMemo, useState } from "react";
+import { Card, CardContent } from "./components/ui/card";
+import JSONView from "@microlink/react-json-view";
export function Config() {
const env = useEnv();
@@ -14,11 +16,21 @@
setNodes(n);
}
}, [n, setNodes]);
- const config = useMemo(() => generateDodoConfig(projectId, nodes, env), [projectId, nodes, env]);
- const configS = useMemo(() => JSON.stringify(config, undefined, 4), [config]);
+ const config = useMemo(() => generateDodoConfig(projectId, nodes, env) || {}, [projectId, nodes, env]);
return (
- <div className="px-5">
- <pre>{configS}</pre>
- </div>
+ <Card className="h-full flex flex-col">
+ <CardContent className="flex-1 min-h-0 p-4">
+ <div className="h-full p-4 bg-muted rounded-lg overflow-auto">
+ <JSONView
+ src={config as object}
+ theme="rjv-default"
+ name={false}
+ displayDataTypes={false}
+ enableClipboard={true}
+ style={{ fontFamily: "JetBrains Mono" }}
+ />
+ </div>
+ </CardContent>
+ </Card>
);
}
diff --git a/apps/canvas/front/src/Header.tsx b/apps/canvas/front/src/ProjectSelect.tsx
similarity index 78%
rename from apps/canvas/front/src/Header.tsx
rename to apps/canvas/front/src/ProjectSelect.tsx
index 979d85c..5178b5e 100644
--- a/apps/canvas/front/src/Header.tsx
+++ b/apps/canvas/front/src/ProjectSelect.tsx
@@ -7,7 +7,7 @@
import { Dialog, DialogContent, DialogTrigger } from "./components/ui/dialog";
import { useToast } from "@/hooks/use-toast";
-export function Header() {
+export function ProjectSelect() {
const { toast } = useToast();
const store = useStateStore();
const [projects, setProjects] = useState<Project[]>([]);
@@ -96,26 +96,26 @@
});
}, [name, setCreateNewOpen, toast, store, refreshProjects]);
return (
- <div className="flex flex-row h-9">
- <Select onValueChange={onSelect} value={project}>
- <SelectTrigger>
- <SelectValue placeholder="Choose Project" defaultValue={project} />
- </SelectTrigger>
- <SelectContent>
- {projects.map((p) => (
- <SelectItem value={p.id}>{p.name}</SelectItem>
- ))}
- <SelectItem value={"create-new"}>
- <Dialog open={createNewOpen} onOpenChange={setCreateNewOpen}>
- <DialogTrigger>Create New</DialogTrigger>
- <DialogContent>
- <Input type="text" placeholder="Name" onChange={updateName} />
- <Button onClick={createNew}>Create New</Button>
- </DialogContent>
- </Dialog>
+ <Select onValueChange={onSelect} value={project}>
+ <SelectTrigger className="w-[200px]">
+ <SelectValue placeholder="Choose Project" defaultValue={project} />
+ </SelectTrigger>
+ <SelectContent>
+ <SelectItem value={"create-new"}>
+ <Dialog open={createNewOpen} onOpenChange={setCreateNewOpen}>
+ <DialogTrigger>Create New</DialogTrigger>
+ <DialogContent>
+ <Input type="text" placeholder="Name" onChange={updateName} />
+ <Button onClick={createNew}>Create New</Button>
+ </DialogContent>
+ </Dialog>
+ </SelectItem>
+ {projects.map((p) => (
+ <SelectItem key={p.id} value={p.id}>
+ {p.name}
</SelectItem>
- </SelectContent>
- </Select>
- </div>
+ ))}
+ </SelectContent>
+ </Select>
);
}
diff --git a/apps/canvas/front/src/components/details.tsx b/apps/canvas/front/src/components/details.tsx
index 252952d..cb7d7fe 100644
--- a/apps/canvas/front/src/components/details.tsx
+++ b/apps/canvas/front/src/components/details.tsx
@@ -5,6 +5,7 @@
import { AccordionItem } from "@radix-ui/react-accordion";
import { useMemo, useState } from "react";
import { Icon } from "./icon";
+import { Separator } from "./ui/separator";
function unique<T>(v: T, i: number, a: T[]) {
return a.indexOf(v) === i;
@@ -42,18 +43,21 @@
const all = useMemo(() => open.concat(selected).filter(unique), [open, selected]);
return (
<Accordion type="multiple" value={all} onValueChange={(v) => setOpen(v)}>
- {sorted.map((n) => (
- <AccordionItem key={n.id} value={n.id} className="px-3">
- <AccordionTrigger>
- <div className="flex flex-row space-x-2">
- {Icon(n.type)}
- <span>{nodeLabel(n)}</span>
- </div>
- </AccordionTrigger>
- <AccordionContent>
- <NodeDetails {...n} />
- </AccordionContent>
- </AccordionItem>
+ {sorted.map((n, index) => (
+ <>
+ {index > 0 && <Separator className="my-2" />}
+ <AccordionItem key={n.id} value={n.id} className="px-3">
+ <AccordionTrigger>
+ <div className="flex flex-row space-x-2">
+ {Icon(n.type)}
+ <span>{nodeLabel(n)}</span>
+ </div>
+ </AccordionTrigger>
+ <AccordionContent>
+ <NodeDetails {...n} />
+ </AccordionContent>
+ </AccordionItem>
+ </>
))}
</Accordion>
);
diff --git a/apps/canvas/front/src/components/logs.tsx b/apps/canvas/front/src/components/logs.tsx
index 5918cf7..a2bf1ba 100644
--- a/apps/canvas/front/src/components/logs.tsx
+++ b/apps/canvas/front/src/components/logs.tsx
@@ -5,6 +5,7 @@
import { useToast } from "@/hooks/use-toast";
// ANSI escape sequence regex
+// eslint-disable-next-line no-control-regex
const ANSI_ESCAPE_REGEX = /\x1B(?:[@-Z\\-_]|\[[0-?]*[ -/]*[@-~])/g;
function cleanAnsiEscapeSequences(text: string): string {
@@ -98,7 +99,7 @@
}, [sortedServices, selectedService]);
return (
- <Card>
+ <Card className="h-full flex flex-col">
<CardHeader>
<Select value={selectedService} onValueChange={setSelectedService}>
<SelectTrigger>
@@ -113,11 +114,11 @@
</SelectContent>
</Select>
</CardHeader>
- <CardContent className="h-full">
+ <CardContent className="flex-1 min-h-0">
{selectedService && (
<pre
ref={preRef}
- className="p-4 bg-muted rounded-lg overflow-auto max-h-[500px] font-['JetBrains_Mono'] whitespace-pre-wrap break-all"
+ className="h-full p-4 bg-muted rounded-lg overflow-auto font-['JetBrains_Mono'] whitespace-pre-wrap break-all"
>
{cleanAnsiEscapeSequences(logs) || "No logs available"}
</pre>