diff options
Diffstat (limited to 'frontend/src/components')
| -rw-r--r-- | frontend/src/components/Dashboard.tsx | 186 | ||||
| -rw-r--r-- | frontend/src/components/ImageProcessor.tsx | 67 | ||||
| -rw-r--r-- | frontend/src/components/ImageSideBar.tsx | 24 | ||||
| -rw-r--r-- | frontend/src/components/ui/button.tsx | 56 | ||||
| -rw-r--r-- | frontend/src/components/ui/input.tsx | 25 | ||||
| -rw-r--r-- | frontend/src/components/ui/select.tsx | 158 |
6 files changed, 493 insertions, 23 deletions
diff --git a/frontend/src/components/Dashboard.tsx b/frontend/src/components/Dashboard.tsx new file mode 100644 index 0000000..0834bee --- /dev/null +++ b/frontend/src/components/Dashboard.tsx @@ -0,0 +1,186 @@ +import { useEffect, useState } from 'react'; +import { EKSClient, DescribeClusterCommand } from '@aws-sdk/client-eks'; +import axios from 'axios'; + +// Configure AWS SDK v3 +const region = 'us-east-1'; // Change to your region + + +// Create an Axios instance +const axiosInstance = axios.create(); + +// Add a request interceptor +axiosInstance.interceptors.request.use(config => { + config.headers['Access-Control-Allow-Origin'] = '*'; + return config; +}, error => { + return Promise.reject(error); +}); + + + +const eksClient = new EKSClient({ + region, + credentials: { + accessKeyId: "AKIA3X4DCJJWHYF5M42F", + secretAccessKey: "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", + }, +}); + +const Header = () => { + return ( + <header className="bg-gray-900 text-white py-4 px-6"> + <h1 className="text-2xl font-bold">Kubernetes Dashboard</h1> + </header> + ); +}; + +const Card = ({ title, children, className = "" }) => { + return ( + <div className={`bg-white dark:bg-gray-900 rounded-lg shadow-md p-6 ${className}`}> + <h2 className="text-lg font-bold mb-4 dark:text-white">{title}</h2> + {children} + </div> + ); +}; + +const StatusItem = ({ value, label }) => { + return ( + <div className="bg-gray-200 dark:bg-gray-800 rounded-lg p-4"> + <p className="text-4xl font-bold dark:text-white">{value}</p> + <p className="text-gray-500 dark:text-gray-400">{label}</p> + </div> + ); +}; + +const StatusTable = ({ headers, rows }) => { + return ( + <div className="overflow-x-auto"> + <table className="w-full table-auto"> + <thead> + <tr className="bg-gray-200 dark:bg-gray-800"> + {headers.map((header, index) => ( + <th key={index} className="px-4 py-2 text-left dark:text-white">{header}</th> + ))} + </tr> + </thead> + <tbody> + {rows.map((row, rowIndex) => ( + <tr key={rowIndex} className="border-b border-gray-200 dark:border-gray-800"> + {row.map((cell, cellIndex) => ( + <td key={cellIndex} className={`px-4 py-2 ${cellIndex >= headers.length - 2 ? 'text-right' : 'text-left'} dark:text-white`}> + {cell} + </td> + ))} + </tr> + ))} + </tbody> + </table> + </div> + ); +}; + +const Dashboard = ({ clusterName }) => { + const [clusterOverview, setClusterOverview] = useState({}); + const [nodes, setNodes] = useState([]); + const [pods, setPods] = useState([]); + + useEffect(() => { + const fetchClusterData = async () => { + try { + const clusterData = await eksClient.send(new DescribeClusterCommand({ name: clusterName })); + const endpoint = clusterData.cluster.endpoint; + + console.log(endpoint) + + const nodeCount = await getNodeCount(endpoint); + const podCount = await getPodCount(endpoint); + const serviceCount = await getServiceCount(endpoint); + + setClusterOverview({ + nodes: nodeCount, + pods: podCount, + services: serviceCount, + }); + + const nodesData = await getNodeStatuses(endpoint); + setNodes(nodesData); + + const podsData = await getPodStatuses(endpoint); + setPods(podsData); + + } catch (error) { + console.error('Error fetching data from AWS:', error); + } + }; + + fetchClusterData(); + }, [clusterName]); + + const getNodeCount = async (endpoint) => { + const response = await axiosInstance.get(`${endpoint}/api/v1/nodes`); + return response.data.items.length; + }; + + const getPodCount = async (endpoint) => { + const response = await axiosInstance.get(`${endpoint}/api/v1/pods`); + return response.data.items.length; + }; + + const getServiceCount = async (endpoint) => { + const response = await axiosInstance.get(`${endpoint}/api/v1/services`); + return response.data.items.length; + }; + + const getNodeStatuses = async (endpoint) => { + const response = await axiosInstance.get(`${endpoint}/api/v1/nodes`); + return response.data.items.map(node => ({ + name: node.metadata.name, + status: node.status.conditions.find(condition => condition.type === 'Ready').status === 'True' ? 'Running' : 'NotReady', + cpu: `${Math.round((node.status.allocatable.cpu / node.status.capacity.cpu) * 100)}%`, + memory: `${Math.round((node.status.allocatable.memory / node.status.capacity.memory) * 100)}%`, + })); + }; + + const getPodStatuses = async (endpoint) => { + const response = await axiosInstance.get(`${endpoint}/api/v1/pods`); + return response.data.items.map(pod => ({ + name: pod.metadata.name, + status: pod.status.phase, + image: pod.spec.containers[0].image, + })); + }; + + return ( + <div className="flex flex-col h-screen dark:bg-gray-900"> + <Header /> + <main className="flex-1 bg-gray-100 dark:bg-gray-800 p-6"> + <div className="grid grid-cols-1 gap-6"> + <div className="grid grid-cols-1 md:grid-cols-3 gap-6"> + <Card title="Cluster Overview"> + <div className="grid grid-cols-3 gap-4"> + <StatusItem value={clusterOverview.nodes} label="Nodes" /> + <StatusItem value={clusterOverview.pods} label="Pods" /> + <StatusItem value={clusterOverview.services} label="Services" /> + </div> + </Card> + <Card title="Node Status" className="md:col-span-2"> + <StatusTable + headers={['Name', 'Status', 'CPU', 'Memory']} + rows={nodes.map(node => [node.name, node.status, node.cpu, node.memory])} + /> + </Card> + </div> + <Card title="Pod Status"> + <StatusTable + headers={['Name', 'Status', 'Image']} + rows={pods.map(pod => [pod.name, pod.status, pod.image])} + /> + </Card> + </div> + </main> + </div> + ); +}; + +export default Dashboard; diff --git a/frontend/src/components/ImageProcessor.tsx b/frontend/src/components/ImageProcessor.tsx index bdcae19..315f6f4 100644 --- a/frontend/src/components/ImageProcessor.tsx +++ b/frontend/src/components/ImageProcessor.tsx @@ -1,18 +1,28 @@ import React, { useState } from 'react'; +import { SelectValue, SelectTrigger, SelectItem, SelectContent, Select } from "@/components/ui/select"; +import { Input } from "@/components/ui/input"; +import { Button } from "@/components/ui/button"; const BACK_END_URL = 'http://localhost:5000' +// const BACK_END_URL = '<link of kubernetes loadbalancer>'; -const ImageProcessor = (): JSX.Element => { +type Operation = 'edge_detection' | 'color_inversion' | 'grayscale' | 'blur' | 'sharpen' | 'brightness_increase' | 'contrast_increase' | 'sharpening'; + +export default function Component() { const [file, setFile] = useState<File | null>(null); + const [filePreview, setFilePreview] = useState<string>('https://placehold.jp/1000x1000.png'); const [downloadUrl, setDownloadUrl] = useState<string>(''); + const [operation, setOperation] = useState<Operation>('edge_detection'); const handleFileChange = (event: React.ChangeEvent<HTMLInputElement>): void => { if (event.target.files) { - setFile(event.target.files[0]); + const selectedFile = event.target.files[0]; + setFile(selectedFile); + setFilePreview(URL.createObjectURL(selectedFile)); } }; - const processImage = async (operation: 'edge_detection' | 'color_inversion'): Promise<void> => { + const processImage = async (): Promise<void> => { if (!file) { alert('Please select a file first!'); return; @@ -31,7 +41,7 @@ const ImageProcessor = (): JSX.Element => { const data = await response.json(); if (response.ok) { setDownloadUrl(`${data.processed_file}`); - console.log(data.processed_file) + console.log(data.processed_file); alert('File processed successfully!'); } else { alert(data.error || 'Failed to process the file'); @@ -50,26 +60,37 @@ const ImageProcessor = (): JSX.Element => { }; return ( - <div className="p-8 bg-white rounded-lg shadow-md flex flex-col items-center"> - <h2 className="text-2xl font-bold mb-4">Image Processing</h2> - <div className="mb-4"> - <label className="block mb-2">Upload an image</label> - <input type="file" onChange={handleFileChange} className="mb-4" /> + <div className="flex flex-col items-center justify-center h-screen w-full dark:bg-gray-950"> + <Select className="dark:bg-gray-800 dark:text-gray-50 mb-4" defaultValue="edge_detection" onValueChange={(value) => setOperation(value as Operation)}> + <SelectTrigger className="w-64 dark:bg-gray-800 dark:text-gray-50"> + <SelectValue placeholder="Select Operation" /> + </SelectTrigger> + <SelectContent className="dark:bg-gray-800 dark:text-gray-50"> + <SelectItem className="dark:hover:bg-gray-700 dark:hover:text-gray-50" value="edge_detection">Edge Detection</SelectItem> + <SelectItem className="dark:hover:bg-gray-700 dark:hover:text-gray-50" value="color_inversion">Color Inversion</SelectItem> + <SelectItem className="dark:hover:bg-gray-700 dark:hover:text-gray-50" value="grayscale">Grayscale</SelectItem> + <SelectItem className="dark:hover:bg-gray-700 dark:hover:text-gray-50" value="blur">Blur</SelectItem> + <SelectItem className="dark:hover:bg-gray-700 dark:hover:text-gray-50" value="sharpen">Sharpen</SelectItem> + <SelectItem className="dark:hover:bg-gray-700 dark:hover:text-gray-50" value="brightness_increase">Brightness Increase</SelectItem> + <SelectItem className="dark:hover:bg-gray-700 dark:hover:text-gray-50" value="contrast_increase">Contrast Increase</SelectItem> + <SelectItem className="dark:hover:bg-gray-700 dark:hover:text-gray-50" value="sharpening">Sharpening</SelectItem> + </SelectContent> + </Select> + <div className="flex gap-4 mb-4"> + <Button className="dark:bg-gray-800 dark:text-gray-50 dark:border-gray-700 dark:hover:bg-gray-700 dark:hover:text-gray-50" size="lg" variant="outline" onClick={() => document.getElementById('fileInput')?.click()}> + Upload Image + <Input id="fileInput" className="hidden" type="file" onChange={handleFileChange} /> + </Button> + <Button className="dark:bg-gray-800 dark:text-gray-50 dark:border-gray-700 dark:hover:bg-gray-700 dark:hover:text-gray-50" size="lg" variant="outline" onClick={processImage}> + Process Image + </Button> + <Button className="dark:bg-gray-800 dark:text-gray-50 dark:border-gray-700 dark:hover:bg-gray-700 dark:hover:text-gray-50" size="lg" variant="outline" onClick={downloadImage}> + Download Image + </Button> </div> - <div className="flex flex-row justify-between w-full mb-4"> - <button onClick={() => processImage('edge_detection')} className="bg-black text-white font-bold py-2 px-4 rounded w-full mr-2"> - Edge Detection - </button> - <button onClick={() => processImage('color_inversion')} className="bg-black text-white font-bold py-2 px-4 rounded w-full ml-2"> - Color Inversion - </button> + <div className="w-full max-w-2xl"> + <img alt="Processed Image" className="rounded-md" height={600} src={downloadUrl || filePreview} style={{ aspectRatio: "800/600", objectFit: "cover" }} width={800} /> </div> - <img className='border-8 border-red-50' src={downloadUrl == '' ? "https://placehold.co/600x400" : downloadUrl} /> - <button onClick={downloadImage} className="bg-black text-white font-bold py-2 px-4 rounded w-full"> - Download - </button> </div> ); -}; - -export default ImageProcessor; +} diff --git a/frontend/src/components/ImageSideBar.tsx b/frontend/src/components/ImageSideBar.tsx new file mode 100644 index 0000000..c2ec33f --- /dev/null +++ b/frontend/src/components/ImageSideBar.tsx @@ -0,0 +1,24 @@ +import React from 'react'; + +type ImageSidebarProps = { + name: string; + previews: string[]; + files: File[]; +}; + +const ImageSidebar: React.FC<ImageSidebarProps> = ({ name, previews, files }) => { + return ( + <div className="w-64 flex-shrink-0 overflow-y-auto border-l-2 border-gray-400 bg-blue-100 shadow-xl p-4"> + <h4 className="font-semibold text-lg text-blue-900 mb-4">{name}</h4> + {previews.map((preview, index) => ( + <div key={index} className="mb-4"> + <p className="text-sm font-medium text-gray-800">{files[index].name}</p> + <img src={preview} alt={files[index].name} style={{ width: '100%', height: 'auto' }} /> + </div> + ))} + </div> + ); +}; + +export default ImageSidebar; + diff --git a/frontend/src/components/ui/button.tsx b/frontend/src/components/ui/button.tsx new file mode 100644 index 0000000..0ba4277 --- /dev/null +++ b/frontend/src/components/ui/button.tsx @@ -0,0 +1,56 @@ +import * as React from "react" +import { Slot } from "@radix-ui/react-slot" +import { cva, type VariantProps } from "class-variance-authority" + +import { cn } from "@/lib/utils" + +const buttonVariants = cva( + "inline-flex items-center justify-center whitespace-nowrap rounded-md text-sm font-medium ring-offset-background transition-colors focus-visible:outline-none focus-visible:ring-2 focus-visible:ring-ring focus-visible:ring-offset-2 disabled:pointer-events-none disabled:opacity-50", + { + variants: { + variant: { + default: "bg-primary text-primary-foreground hover:bg-primary/90", + destructive: + "bg-destructive text-destructive-foreground hover:bg-destructive/90", + outline: + "border border-input bg-background hover:bg-accent hover:text-accent-foreground", + secondary: + "bg-secondary text-secondary-foreground hover:bg-secondary/80", + ghost: "hover:bg-accent hover:text-accent-foreground", + link: "text-primary underline-offset-4 hover:underline", + }, + size: { + default: "h-10 px-4 py-2", + sm: "h-9 rounded-md px-3", + lg: "h-11 rounded-md px-8", + icon: "h-10 w-10", + }, + }, + defaultVariants: { + variant: "default", + size: "default", + }, + } +) + +export interface ButtonProps + extends React.ButtonHTMLAttributes<HTMLButtonElement>, + VariantProps<typeof buttonVariants> { + asChild?: boolean +} + +const Button = React.forwardRef<HTMLButtonElement, ButtonProps>( + ({ className, variant, size, asChild = false, ...props }, ref) => { + const Comp = asChild ? Slot : "button" + return ( + <Comp + className={cn(buttonVariants({ variant, size, className }))} + ref={ref} + {...props} + /> + ) + } +) +Button.displayName = "Button" + +export { Button, buttonVariants } diff --git a/frontend/src/components/ui/input.tsx b/frontend/src/components/ui/input.tsx new file mode 100644 index 0000000..677d05f --- /dev/null +++ b/frontend/src/components/ui/input.tsx @@ -0,0 +1,25 @@ +import * as React from "react" + +import { cn } from "@/lib/utils" + +export interface InputProps + extends React.InputHTMLAttributes<HTMLInputElement> {} + +const Input = React.forwardRef<HTMLInputElement, InputProps>( + ({ className, type, ...props }, ref) => { + return ( + <input + type={type} + className={cn( + "flex h-10 w-full rounded-md border border-input bg-background px-3 py-2 text-sm ring-offset-background file:border-0 file:bg-transparent file:text-sm file:font-medium placeholder:text-muted-foreground focus-visible:outline-none focus-visible:ring-2 focus-visible:ring-ring focus-visible:ring-offset-2 disabled:cursor-not-allowed disabled:opacity-50", + className + )} + ref={ref} + {...props} + /> + ) + } +) +Input.displayName = "Input" + +export { Input } diff --git a/frontend/src/components/ui/select.tsx b/frontend/src/components/ui/select.tsx new file mode 100644 index 0000000..fe56d4d --- /dev/null +++ b/frontend/src/components/ui/select.tsx @@ -0,0 +1,158 @@ +import * as React from "react" +import * as SelectPrimitive from "@radix-ui/react-select" +import { Check, ChevronDown, ChevronUp } from "lucide-react" + +import { cn } from "@/lib/utils" + +const Select = SelectPrimitive.Root + +const SelectGroup = SelectPrimitive.Group + +const SelectValue = SelectPrimitive.Value + +const SelectTrigger = React.forwardRef< + React.ElementRef<typeof SelectPrimitive.Trigger>, + React.ComponentPropsWithoutRef<typeof SelectPrimitive.Trigger> +>(({ className, children, ...props }, ref) => ( + <SelectPrimitive.Trigger + ref={ref} + className={cn( + "flex h-10 w-full items-center justify-between rounded-md border border-input bg-background px-3 py-2 text-sm ring-offset-background placeholder:text-muted-foreground focus:outline-none focus:ring-2 focus:ring-ring focus:ring-offset-2 disabled:cursor-not-allowed disabled:opacity-50 [&>span]:line-clamp-1", + className + )} + {...props} + > + {children} + <SelectPrimitive.Icon asChild> + <ChevronDown className="h-4 w-4 opacity-50" /> + </SelectPrimitive.Icon> + </SelectPrimitive.Trigger> +)) +SelectTrigger.displayName = SelectPrimitive.Trigger.displayName + +const SelectScrollUpButton = React.forwardRef< + React.ElementRef<typeof SelectPrimitive.ScrollUpButton>, + React.ComponentPropsWithoutRef<typeof SelectPrimitive.ScrollUpButton> +>(({ className, ...props }, ref) => ( + <SelectPrimitive.ScrollUpButton + ref={ref} + className={cn( + "flex cursor-default items-center justify-center py-1", + className + )} + {...props} + > + <ChevronUp className="h-4 w-4" /> + </SelectPrimitive.ScrollUpButton> +)) +SelectScrollUpButton.displayName = SelectPrimitive.ScrollUpButton.displayName + +const SelectScrollDownButton = React.forwardRef< + React.ElementRef<typeof SelectPrimitive.ScrollDownButton>, + React.ComponentPropsWithoutRef<typeof SelectPrimitive.ScrollDownButton> +>(({ className, ...props }, ref) => ( + <SelectPrimitive.ScrollDownButton + ref={ref} + className={cn( + "flex cursor-default items-center justify-center py-1", + className + )} + {...props} + > + <ChevronDown className="h-4 w-4" /> + </SelectPrimitive.ScrollDownButton> +)) +SelectScrollDownButton.displayName = + SelectPrimitive.ScrollDownButton.displayName + +const SelectContent = React.forwardRef< + React.ElementRef<typeof SelectPrimitive.Content>, + React.ComponentPropsWithoutRef<typeof SelectPrimitive.Content> +>(({ className, children, position = "popper", ...props }, ref) => ( + <SelectPrimitive.Portal> + <SelectPrimitive.Content + ref={ref} + className={cn( + "relative z-50 max-h-96 min-w-[8rem] overflow-hidden rounded-md border bg-popover text-popover-foreground shadow-md data-[state=open]:animate-in data-[state=closed]:animate-out data-[state=closed]:fade-out-0 data-[state=open]:fade-in-0 data-[state=closed]:zoom-out-95 data-[state=open]:zoom-in-95 data-[side=bottom]:slide-in-from-top-2 data-[side=left]:slide-in-from-right-2 data-[side=right]:slide-in-from-left-2 data-[side=top]:slide-in-from-bottom-2", + position === "popper" && + "data-[side=bottom]:translate-y-1 data-[side=left]:-translate-x-1 data-[side=right]:translate-x-1 data-[side=top]:-translate-y-1", + className + )} + position={position} + {...props} + > + <SelectScrollUpButton /> + <SelectPrimitive.Viewport + className={cn( + "p-1", + position === "popper" && + "h-[var(--radix-select-trigger-height)] w-full min-w-[var(--radix-select-trigger-width)]" + )} + > + {children} + </SelectPrimitive.Viewport> + <SelectScrollDownButton /> + </SelectPrimitive.Content> + </SelectPrimitive.Portal> +)) +SelectContent.displayName = SelectPrimitive.Content.displayName + +const SelectLabel = React.forwardRef< + React.ElementRef<typeof SelectPrimitive.Label>, + React.ComponentPropsWithoutRef<typeof SelectPrimitive.Label> +>(({ className, ...props }, ref) => ( + <SelectPrimitive.Label + ref={ref} + className={cn("py-1.5 pl-8 pr-2 text-sm font-semibold", className)} + {...props} + /> +)) +SelectLabel.displayName = SelectPrimitive.Label.displayName + +const SelectItem = React.forwardRef< + React.ElementRef<typeof SelectPrimitive.Item>, + React.ComponentPropsWithoutRef<typeof SelectPrimitive.Item> +>(({ className, children, ...props }, ref) => ( + <SelectPrimitive.Item + ref={ref} + className={cn( + "relative flex w-full cursor-default select-none items-center rounded-sm py-1.5 pl-8 pr-2 text-sm outline-none focus:bg-accent focus:text-accent-foreground data-[disabled]:pointer-events-none data-[disabled]:opacity-50", + className + )} + {...props} + > + <span className="absolute left-2 flex h-3.5 w-3.5 items-center justify-center"> + <SelectPrimitive.ItemIndicator> + <Check className="h-4 w-4" /> + </SelectPrimitive.ItemIndicator> + </span> + + <SelectPrimitive.ItemText>{children}</SelectPrimitive.ItemText> + </SelectPrimitive.Item> +)) +SelectItem.displayName = SelectPrimitive.Item.displayName + +const SelectSeparator = React.forwardRef< + React.ElementRef<typeof SelectPrimitive.Separator>, + React.ComponentPropsWithoutRef<typeof SelectPrimitive.Separator> +>(({ className, ...props }, ref) => ( + <SelectPrimitive.Separator + ref={ref} + className={cn("-mx-1 my-1 h-px bg-muted", className)} + {...props} + /> +)) +SelectSeparator.displayName = SelectPrimitive.Separator.displayName + +export { + Select, + SelectGroup, + SelectValue, + SelectTrigger, + SelectContent, + SelectLabel, + SelectItem, + SelectSeparator, + SelectScrollUpButton, + SelectScrollDownButton, +} |
