From 5e9ec6a4abb9daf2e90d245065e322017e6fc928 Mon Sep 17 00:00:00 2001 From: prosfus Date: Thu, 10 Oct 2024 21:05:05 +0200 Subject: [PATCH 1/2] feat: minio bucket list --- .eslintrc.cjs | 15 +- package.json | 1 + .../components/MinioBucketList/index.tsx | 51 +---- src/components/ui/alert.tsx | 59 ++++++ src/components/ui/input.tsx | 2 +- src/components/ui/popover.tsx | 29 +++ src/contexts/Minio/MinioContext.tsx | 91 ++++++++ .../components/AddBucketButton/index.tsx | 76 +++++++ .../components/AddFolderButton/index.tsx | 77 +++++++ .../minio/components/BucketContent/index.tsx | 197 ++++++++++++++++++ .../ui/minio/components/BucketList/index.tsx | 122 ++++------- src/pages/ui/minio/components/Topbar.tsx | 93 +++++---- src/pages/ui/minio/hooks/useSelectedBucket.ts | 7 + src/pages/ui/minio/router.tsx | 7 +- .../components/ServicesList/index.tsx | 4 +- 15 files changed, 642 insertions(+), 189 deletions(-) create mode 100644 src/components/ui/alert.tsx create mode 100644 src/components/ui/popover.tsx create mode 100644 src/pages/ui/minio/components/AddBucketButton/index.tsx create mode 100644 src/pages/ui/minio/components/AddFolderButton/index.tsx create mode 100644 src/pages/ui/minio/components/BucketContent/index.tsx create mode 100644 src/pages/ui/minio/hooks/useSelectedBucket.ts diff --git a/.eslintrc.cjs b/.eslintrc.cjs index e1d9b7d..725889f 100644 --- a/.eslintrc.cjs +++ b/.eslintrc.cjs @@ -1,13 +1,8 @@ module.exports = { root: true, env: { browser: true, es2020: true }, - extends: [ - 'eslint:recommended', - 'plugin:@typescript-eslint/recommended', - 'plugin:react-hooks/recommended', - ], - ignorePatterns: ['dist', '.eslintrc.cjs'], - parser: '@typescript-eslint/parser', - plugins: ['react-refresh'], - -} + extends: ["eslint:recommended", "plugin:@typescript-eslint/recommended"], + ignorePatterns: ["dist", ".eslintrc.cjs"], + parser: "@typescript-eslint/parser", + plugins: ["react-refresh"], +}; diff --git a/package.json b/package.json index 80963aa..d9d115d 100644 --- a/package.json +++ b/package.json @@ -18,6 +18,7 @@ "@radix-ui/react-dialog": "^1.1.1", "@radix-ui/react-dropdown-menu": "^2.1.1", "@radix-ui/react-label": "^2.1.0", + "@radix-ui/react-popover": "^1.1.2", "@radix-ui/react-select": "^2.1.1", "@radix-ui/react-separator": "^1.1.0", "@radix-ui/react-slot": "^1.1.0", diff --git a/src/components/Sidebar/components/MinioBucketList/index.tsx b/src/components/Sidebar/components/MinioBucketList/index.tsx index b621a7d..6697a65 100644 --- a/src/components/Sidebar/components/MinioBucketList/index.tsx +++ b/src/components/Sidebar/components/MinioBucketList/index.tsx @@ -2,26 +2,14 @@ import OscarColors from "@/styles"; import { Link, useLocation, useParams } from "react-router-dom"; import "./styles.css"; import { useMinio } from "@/contexts/Minio/MinioContext"; -import { Button } from "@/components/ui/button"; -import { Save } from "lucide-react"; -import { useState } from "react"; -import { Input } from "@/components/ui/input"; export function MinioBucketList() { - const { buckets, createBucket } = useMinio(); + const { buckets } = useMinio(); const location = useLocation(); const isActive = location.pathname.includes("/minio"); const params = useParams(); const activeItem = Object.values(params)?.pop(); - const [isCreatingBucket, setIsCreatingBucket] = useState(false); - const [newBucketName, setNewBucketName] = useState(""); - - function handleCreateBucket() { - createBucket(newBucketName); - setIsCreatingBucket(false); - setNewBucketName(""); - } if (!isActive) return null; @@ -60,43 +48,6 @@ export function MinioBucketList() { {bucket.Name} ))} - {!isCreatingBucket && ( - - )} - {isCreatingBucket && ( -
- { - if (e.key === "Enter") { - handleCreateBucket(); - } - }} - onBlur={() => setIsCreatingBucket(false)} - onChange={(e) => setNewBucketName(e.target.value)} - /> - -
- )} ); } diff --git a/src/components/ui/alert.tsx b/src/components/ui/alert.tsx new file mode 100644 index 0000000..74edbf9 --- /dev/null +++ b/src/components/ui/alert.tsx @@ -0,0 +1,59 @@ +import * as React from "react" +import { cva, type VariantProps } from "class-variance-authority" + +import { cn } from "@/lib/utils" + +const alertVariants = cva( + "relative w-full rounded-lg border border-slate-200 p-4 [&>svg~*]:pl-7 [&>svg+div]:translate-y-[-3px] [&>svg]:absolute [&>svg]:left-4 [&>svg]:top-4 [&>svg]:text-slate-950 dark:border-slate-800 dark:[&>svg]:text-slate-50", + { + variants: { + variant: { + default: "bg-white text-slate-950 dark:bg-slate-950 dark:text-slate-50", + destructive: + "border-red-500/50 text-red-500 dark:border-red-500 [&>svg]:text-red-500 dark:border-red-900/50 dark:text-red-900 dark:dark:border-red-900 dark:[&>svg]:text-red-900", + }, + }, + defaultVariants: { + variant: "default", + }, + } +) + +const Alert = React.forwardRef< + HTMLDivElement, + React.HTMLAttributes & VariantProps +>(({ className, variant, ...props }, ref) => ( +
+)) +Alert.displayName = "Alert" + +const AlertTitle = React.forwardRef< + HTMLParagraphElement, + React.HTMLAttributes +>(({ className, ...props }, ref) => ( +
+)) +AlertTitle.displayName = "AlertTitle" + +const AlertDescription = React.forwardRef< + HTMLParagraphElement, + React.HTMLAttributes +>(({ className, ...props }, ref) => ( +
+)) +AlertDescription.displayName = "AlertDescription" + +export { Alert, AlertTitle, AlertDescription } diff --git a/src/components/ui/input.tsx b/src/components/ui/input.tsx index 4627247..77fcdb9 100644 --- a/src/components/ui/input.tsx +++ b/src/components/ui/input.tsx @@ -48,7 +48,7 @@ const Input = React.forwardRef(
)} {endIcon && ( -
+
{endIcon}
)} diff --git a/src/components/ui/popover.tsx b/src/components/ui/popover.tsx new file mode 100644 index 0000000..d628782 --- /dev/null +++ b/src/components/ui/popover.tsx @@ -0,0 +1,29 @@ +import * as React from "react" +import * as PopoverPrimitive from "@radix-ui/react-popover" + +import { cn } from "@/lib/utils" + +const Popover = PopoverPrimitive.Root + +const PopoverTrigger = PopoverPrimitive.Trigger + +const PopoverContent = React.forwardRef< + React.ElementRef, + React.ComponentPropsWithoutRef +>(({ className, align = "center", sideOffset = 4, ...props }, ref) => ( + + + +)) +PopoverContent.displayName = PopoverPrimitive.Content.displayName + +export { Popover, PopoverTrigger, PopoverContent } diff --git a/src/contexts/Minio/MinioContext.tsx b/src/contexts/Minio/MinioContext.tsx index 514ca21..bf5bc5b 100644 --- a/src/contexts/Minio/MinioContext.tsx +++ b/src/contexts/Minio/MinioContext.tsx @@ -15,6 +15,9 @@ import { ListObjectsV2CommandInput, CommonPrefix, _Object, + DeleteBucketCommand, + PutObjectCommand, + DeleteObjectCommand, } from "@aws-sdk/client-s3"; import getSystemConfigApi from "@/api/config/getSystemConfig"; import { alert } from "@/lib/alert"; @@ -33,6 +36,10 @@ export type MinioProviderData = { folders: CommonPrefix[]; items: _Object[]; }>; + deleteBucket: (bucketName: string) => Promise; + createFolder: (bucketName: string, folderName: string) => Promise; + uploadFile: (bucketName: string, path: string, file: File) => Promise; + deleteFile: (bucketName: string, path: string) => Promise; }; export const MinioContext = createContext({} as MinioProviderData); @@ -145,6 +152,86 @@ export const MinioProvider = ({ children }: { children: React.ReactNode }) => { updateBuckets(); } + async function deleteBucket(bucketName: string) { + if (!client) return; + + try { + const command = new DeleteBucketCommand({ Bucket: bucketName }); + await client.send(command); + alert.success("Bucket deleted successfully"); + } catch (error) { + console.error(error); + alert.error("Error deleting bucket"); + } + + updateBuckets(); + } + + async function createFolder(bucketName: string, folderName: string) { + if (!client) return; + + const folderKey = folderName.endsWith("/") ? folderName : `${folderName}/`; + + try { + await client.send( + new PutObjectCommand({ + Bucket: bucketName, + Key: folderKey, + }) + ); + alert.success("Folder created successfully"); + } catch (error) { + console.error(error); + alert.error("Error creating folder"); + } + + updateBuckets(); + } + + async function uploadFile( + bucketName: string, + path: string, + file: File + ): Promise { + if (!client) return; + + const key = path ? `${path}${file.name}` : file.name; + console.log(key); + + try { + const command = new PutObjectCommand({ + Bucket: bucketName, + Key: key, + Body: file, + }); + await client.send(command); + alert.success("File uploaded successfully"); + } catch (error) { + console.error(error); + alert.error("Error uploading file"); + } + + updateBuckets(); + } + + async function deleteFile(bucketName: string, path: string) { + if (!client) return; + + try { + const command = new DeleteObjectCommand({ + Bucket: bucketName, + Key: path, + }); + await client.send(command); + alert.success("File deleted successfully"); + } catch (error) { + console.error(error); + alert.error("Error deleting file"); + } + + updateBuckets(); + } + return ( { buckets, setBuckets, createBucket, + createFolder, updateBuckets, getBucketItems, + deleteBucket, + uploadFile, + deleteFile, }} > {children} diff --git a/src/pages/ui/minio/components/AddBucketButton/index.tsx b/src/pages/ui/minio/components/AddBucketButton/index.tsx new file mode 100644 index 0000000..fa3c6ea --- /dev/null +++ b/src/pages/ui/minio/components/AddBucketButton/index.tsx @@ -0,0 +1,76 @@ +import { useEffect, useState } from "react"; +import { Button } from "@/components/ui/button"; +import { Input } from "@/components/ui/input"; +import { Label } from "@/components/ui/label"; +import { + Popover, + PopoverContent, + PopoverTrigger, +} from "@/components/ui/popover"; +import { useMinio } from "@/contexts/Minio/MinioContext"; +import { Plus } from "lucide-react"; + +export default function AddBucketButton() { + const [bucketName, setBucketName] = useState(""); + const [isOpen, setIsOpen] = useState(false); + const { createBucket } = useMinio(); + + const handleCreateBucket = async () => { + console.log("Creating bucket", bucketName); + await createBucket(bucketName); + setBucketName(""); + setIsOpen(false); + }; + + useEffect(() => { + function handleKeyDown(e: KeyboardEvent) { + if (e.key === "Escape") { + setIsOpen(false); + } + + if (e.key === "Enter") { + handleCreateBucket(); + } + } + window.addEventListener("keydown", handleKeyDown); + + return () => { + window.removeEventListener("keydown", handleKeyDown); + }; + }, [handleCreateBucket]); + + return ( + + + + + +
+
+

Create MinIO Bucket

+
+
+ + setBucketName(e.target.value)} + placeholder="Enter bucket name" + /> +
+
+ + +
+
+
+
+ ); +} diff --git a/src/pages/ui/minio/components/AddFolderButton/index.tsx b/src/pages/ui/minio/components/AddFolderButton/index.tsx new file mode 100644 index 0000000..f840881 --- /dev/null +++ b/src/pages/ui/minio/components/AddFolderButton/index.tsx @@ -0,0 +1,77 @@ +import { useEffect, useState } from "react"; +import { Button } from "@/components/ui/button"; +import { Input } from "@/components/ui/input"; +import { Label } from "@/components/ui/label"; +import { + Popover, + PopoverContent, + PopoverTrigger, +} from "@/components/ui/popover"; +import { useMinio } from "@/contexts/Minio/MinioContext"; +import { Plus } from "lucide-react"; +import useSelectedBucket from "../../hooks/useSelectedBucket"; + +export default function AddFolderButton() { + const { name: bucketName, path } = useSelectedBucket(); + const [folderName, setFolderName] = useState(""); + const [isOpen, setIsOpen] = useState(false); + const { createFolder } = useMinio(); + + const handleCreateFolder = async () => { + await createFolder(bucketName as string, path + folderName); + setFolderName(""); + setIsOpen(false); + }; + + useEffect(() => { + function handleKeyDown(e: KeyboardEvent) { + if (e.key === "Escape") { + setIsOpen(false); + } + + if (e.key === "Enter") { + handleCreateFolder(); + } + } + window.addEventListener("keydown", handleKeyDown); + + return () => { + window.removeEventListener("keydown", handleKeyDown); + }; + }, [handleCreateFolder]); + + return ( + + + + + +
+
+

Crear Carpeta

+
+
+ + setFolderName(e.target.value)} + placeholder="Ingrese el nombre de la carpeta" + /> +
+
+ + +
+
+
+
+ ); +} diff --git a/src/pages/ui/minio/components/BucketContent/index.tsx b/src/pages/ui/minio/components/BucketContent/index.tsx new file mode 100644 index 0000000..1e1d7e8 --- /dev/null +++ b/src/pages/ui/minio/components/BucketContent/index.tsx @@ -0,0 +1,197 @@ +import { useMinio } from "@/contexts/Minio/MinioContext"; +import { _Object, CommonPrefix } from "@aws-sdk/client-s3"; +import { useEffect, useState } from "react"; +import { Link } from "react-router-dom"; +import GenericTable from "@/components/Table"; // Importar GenericTable +import { AlertCircle, Folder, Trash } from "lucide-react"; +import { Alert, AlertTitle, AlertDescription } from "@/components/ui/alert"; +import OscarColors from "@/styles"; +import { motion, AnimatePresence } from "framer-motion"; +import useSelectedBucket from "../../hooks/useSelectedBucket"; +import { Button } from "@/components/ui/button"; +import DeleteDialog from "@/components/DeleteDialog"; + +type BucketItem = + | { + Name: string; + Type: "folder"; + Key: CommonPrefix; + } + | { + Name: string; + Type: "file"; + Key: _Object; + }; + +export default function BucketContent() { + const { name: bucketName, path } = useSelectedBucket(); + + const { getBucketItems, buckets, uploadFile, deleteFile } = useMinio(); + + const [items, setItems] = useState([]); + + const [isDroppingFile, setIsDroppingFile] = useState(false); + + useEffect(() => { + if (bucketName) { + getBucketItems(bucketName, path).then(({ items, folders }) => { + const combinedItems = [ + ...(folders?.map((folder) => { + const path = folder.Prefix?.split("/"); + const name = path?.[path.length - 2] || ""; + const res: BucketItem = { + Name: name, + Type: "folder", + Key: folder, + }; + return res; + }) || []), + ...(items?.map((item) => { + const res: BucketItem = { + Name: item.Key?.split("/").pop() || "", + Type: "file", + Key: item, + }; + return res; + }) || []), + ]; + setItems(combinedItems); + }); + } + }, [bucketName, getBucketItems, buckets, path]); + + const handleDrop = (event: React.DragEvent) => { + event.preventDefault(); + setIsDroppingFile(false); // Restablecer el estado al soltar + const files = event.dataTransfer.files; + if (files.length > 0) { + uploadFile(bucketName!, path, files[0]); + } + }; + + const handleDragOver = (event: React.DragEvent) => { + event.preventDefault(); + setIsDroppingFile(true); // Cambiar el estado a true cuando se arrastra un archivo + }; + + const handleDragLeave = () => { + setIsDroppingFile(false); // Restablecer el estado cuando el archivo ya no está sobre el div + }; + + const [itemsToDelete, setItemsToDelete] = useState([]); + + return ( + + + {isDroppingFile && ( + + + + Drop files to upload + + Release the mouse button to upload the files to the selected + bucket. + + + + )} + + 0} + onClose={() => setItemsToDelete([])} + onDelete={() => { + itemsToDelete.forEach((item) => { + if (item.Type === "file") { + deleteFile(bucketName!, item.Key.Key!); + } + if (item.Type === "folder") { + deleteFile(bucketName!, item.Key!.Prefix!); + } + }); + }} + itemNames={itemsToDelete.map((item) => item.Name)} + /> + { + if (item.Type === "folder") { + return ( + + {item.Name} + + ); + } + return item.Name; + }, + }, + ]} + idKey="Name" + actions={[ + { + button: (item) => { + return ( + + ); + }, + }, + ]} + /> + + ); +} diff --git a/src/pages/ui/minio/components/BucketList/index.tsx b/src/pages/ui/minio/components/BucketList/index.tsx index 94c2b46..e555b1b 100644 --- a/src/pages/ui/minio/components/BucketList/index.tsx +++ b/src/pages/ui/minio/components/BucketList/index.tsx @@ -1,89 +1,55 @@ +import DeleteDialog from "@/components/DeleteDialog"; +import GenericTable from "@/components/Table"; +import { Button } from "@/components/ui/button"; import { useMinio } from "@/contexts/Minio/MinioContext"; -import { _Object, CommonPrefix } from "@aws-sdk/client-s3"; -import { useEffect, useState } from "react"; -import { Link, useParams } from "react-router-dom"; -import GenericTable from "@/components/Table"; // Importar GenericTable -import { Folder } from "lucide-react"; - -type BucketItem = - | { - Name: string; - Type: "folder"; - Key: CommonPrefix; - } - | { - Name: string; - Type: "file"; - Key: _Object; - }; +import OscarColors from "@/styles"; +import { Bucket } from "@aws-sdk/client-s3"; +import { Trash } from "lucide-react"; +import { useState } from "react"; +import { Link } from "react-router-dom"; export default function BucketList() { - const { name: bucketName, ...rest } = useParams(); - const folderPath = Object.values(rest)?.pop?.() ?? ""; - - const { getBucketItems, buckets } = useMinio(); - - const [items, setItems] = useState([]); - - useEffect(() => { - if (bucketName) { - getBucketItems(bucketName, folderPath).then(({ items, folders }) => { - const combinedItems = [ - ...(folders?.map((folder) => { - const path = folder.Prefix?.split("/"); - const name = path?.[path.length - 2] || ""; - const res: BucketItem = { - Name: name, - Type: "folder", - Key: folder, - }; - return res; - }) || []), - ...(items?.map((item) => { - const res: BucketItem = { - Name: item.Key?.split("/").pop() || "", - Type: "file", - Key: item, - }; - return res; - }) || []), - ]; - setItems(combinedItems); - }); - } - }, [bucketName, getBucketItems, buckets, folderPath]); - + const { buckets, deleteBucket } = useMinio(); + const [itemsToDelete, setItemsToDelete] = useState([]); return ( - { - if (item.Type === "folder") { + <> + 0} + onClose={() => setItemsToDelete([])} + onDelete={() => { + itemsToDelete.forEach((bucket) => deleteBucket(bucket.Name!)); + }} + itemNames={itemsToDelete.map((bucket) => bucket.Name!)} + /> + + data={buckets} + columns={[ + { + header: "Name", + accessor: (row) => ( + {row.Name} + ), + }, + ]} + actions={[ + { + button: (bucket) => { return ( - { + setItemsToDelete([...itemsToDelete, bucket]); }} > - {item.Name} - + + ); - } - return item.Name; + }, }, - }, - ]} - idKey="Name" - // Añade acciones si es necesario - /> + ]} + idKey="Name" + /> + ); } diff --git a/src/pages/ui/minio/components/Topbar.tsx b/src/pages/ui/minio/components/Topbar.tsx index ad33643..481f88b 100644 --- a/src/pages/ui/minio/components/Topbar.tsx +++ b/src/pages/ui/minio/components/Topbar.tsx @@ -1,34 +1,30 @@ import OscarColors, { OscarStyles } from "@/styles"; -import { useParams, Link } from "react-router-dom"; +import { Link } from "react-router-dom"; import UserInfo from "@/components/UserInfo"; +import AddBucketButton from "./AddBucketButton"; +import AddFolderButton from "./AddFolderButton"; +import useSelectedBucket from "../hooks/useSelectedBucket"; +import { ChevronRight } from "lucide-react"; function MinioTopbar() { - const { name, folderPath = "" } = useParams(); - const pathSegments = folderPath ? folderPath.split("/") : []; + const { name, path } = useSelectedBucket(); + const pathSegments = path ? path.split("/").filter((segment) => segment) : []; + + const isOnRoot = name === undefined; + const breadcrumbs = pathSegments.map((segment, index) => { const path = pathSegments.slice(0, index + 1).join("/"); + return ( - - {segment} - + <> + + + {segment} + + ); }); - /* const pathnames = location.pathname.split("/").filter((x) => x && x !== "ui"); - const [_, serviceId] = pathnames; */ - - /* const mode = useMemo(() => { - if (!serviceId) { - return ServiceViewMode.List; - } - - if (serviceId === "create") { - return ServiceViewMode.Create; - } - - return ServiceViewMode.Update; - }, [pathnames]); */ - return (
-

- Bucket:{" "} - {name} -

- - {/* - - {mode === ServiceViewMode.List ? ( - <> -
- - -
- - - ) : ( - - )} */} +
+

+ {isOnRoot ? ( + Buckets + ) : ( + <> + Bucket: + + {name} + + + )} +

+ +
+ {isOnRoot && } + {!isOnRoot && }
diff --git a/src/pages/ui/minio/hooks/useSelectedBucket.ts b/src/pages/ui/minio/hooks/useSelectedBucket.ts new file mode 100644 index 0000000..534accf --- /dev/null +++ b/src/pages/ui/minio/hooks/useSelectedBucket.ts @@ -0,0 +1,7 @@ +import { useParams } from "react-router-dom"; + +export default function useSelectedBucket() { + const { name, ...params } = useParams(); + const path = params["*"] ?? ""; + return { name, path }; +} diff --git a/src/pages/ui/minio/router.tsx b/src/pages/ui/minio/router.tsx index 865a815..e53cdc3 100644 --- a/src/pages/ui/minio/router.tsx +++ b/src/pages/ui/minio/router.tsx @@ -1,7 +1,7 @@ import { Outlet, Route, Routes } from "react-router-dom"; import MinioTopbar from "./components/Topbar"; +import BucketContent from "./components/BucketContent"; import BucketList from "./components/BucketList"; - function MinioRouter() { return ( @@ -17,12 +17,13 @@ function MinioRouter() { }} > +
} > - Minio} /> - } /> + } /> + } /> ); diff --git a/src/pages/ui/services/components/ServicesList/index.tsx b/src/pages/ui/services/components/ServicesList/index.tsx index a075c08..3a0fe44 100644 --- a/src/pages/ui/services/components/ServicesList/index.tsx +++ b/src/pages/ui/services/components/ServicesList/index.tsx @@ -89,14 +89,14 @@ function ServicesList() { ]} actions={[ { - button: (_) => ( + button: () => ( ), }, { - button: (_) => ( + button: () => ( From 868609e452e596c2d5d8c51631970eeb6e30cb26 Mon Sep 17 00:00:00 2001 From: prosfus Date: Thu, 10 Oct 2024 21:50:40 +0200 Subject: [PATCH 2/2] feat: add log_level, alpine, users and interlink to service form --- src/components/ui/select.tsx | 44 +++---- .../components/GeneralTab/index.tsx | 121 ++++++++++++++---- src/pages/ui/services/models/service.ts | 13 +- 3 files changed, 130 insertions(+), 48 deletions(-) diff --git a/src/components/ui/select.tsx b/src/components/ui/select.tsx index ddf7cf0..c94dcde 100644 --- a/src/components/ui/select.tsx +++ b/src/components/ui/select.tsx @@ -1,14 +1,14 @@ -import * as React from "react" -import * as SelectPrimitive from "@radix-ui/react-select" -import { Check, ChevronDown, ChevronUp } from "lucide-react" +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" +import { cn } from "@/lib/utils"; -const Select = SelectPrimitive.Root +const Select = SelectPrimitive.Root; -const SelectGroup = SelectPrimitive.Group +const SelectGroup = SelectPrimitive.Group; -const SelectValue = SelectPrimitive.Value +const SelectValue = SelectPrimitive.Value; const SelectTrigger = React.forwardRef< React.ElementRef, @@ -27,8 +27,8 @@ const SelectTrigger = React.forwardRef< -)) -SelectTrigger.displayName = SelectPrimitive.Trigger.displayName +)); +SelectTrigger.displayName = SelectPrimitive.Trigger.displayName; const SelectScrollUpButton = React.forwardRef< React.ElementRef, @@ -44,8 +44,8 @@ const SelectScrollUpButton = React.forwardRef< > -)) -SelectScrollUpButton.displayName = SelectPrimitive.ScrollUpButton.displayName +)); +SelectScrollUpButton.displayName = SelectPrimitive.ScrollUpButton.displayName; const SelectScrollDownButton = React.forwardRef< React.ElementRef, @@ -61,9 +61,9 @@ const SelectScrollDownButton = React.forwardRef< > -)) +)); SelectScrollDownButton.displayName = - SelectPrimitive.ScrollDownButton.displayName + SelectPrimitive.ScrollDownButton.displayName; const SelectContent = React.forwardRef< React.ElementRef, @@ -94,8 +94,8 @@ const SelectContent = React.forwardRef< -)) -SelectContent.displayName = SelectPrimitive.Content.displayName +)); +SelectContent.displayName = SelectPrimitive.Content.displayName; const SelectLabel = React.forwardRef< React.ElementRef, @@ -106,8 +106,8 @@ const SelectLabel = React.forwardRef< className={cn("py-1.5 pl-8 pr-2 text-sm font-semibold", className)} {...props} /> -)) -SelectLabel.displayName = SelectPrimitive.Label.displayName +)); +SelectLabel.displayName = SelectPrimitive.Label.displayName; const SelectItem = React.forwardRef< React.ElementRef, @@ -129,8 +129,8 @@ const SelectItem = React.forwardRef< {children} -)) -SelectItem.displayName = SelectPrimitive.Item.displayName +)); +SelectItem.displayName = SelectPrimitive.Item.displayName; const SelectSeparator = React.forwardRef< React.ElementRef, @@ -141,8 +141,8 @@ const SelectSeparator = React.forwardRef< className={cn("-mx-1 my-1 h-px bg-slate-100 dark:bg-slate-800", className)} {...props} /> -)) -SelectSeparator.displayName = SelectPrimitive.Separator.displayName +)); +SelectSeparator.displayName = SelectPrimitive.Separator.displayName; export { Select, @@ -155,4 +155,4 @@ export { SelectSeparator, SelectScrollUpButton, SelectScrollDownButton, -} +}; diff --git a/src/pages/ui/services/components/ServiceForm/components/GeneralTab/index.tsx b/src/pages/ui/services/components/ServiceForm/components/GeneralTab/index.tsx index 26e61f8..b50da79 100644 --- a/src/pages/ui/services/components/ServiceForm/components/GeneralTab/index.tsx +++ b/src/pages/ui/services/components/ServiceForm/components/GeneralTab/index.tsx @@ -1,6 +1,6 @@ import { Input } from "@/components/ui/input"; import useServicesContext from "@/pages/ui/services/context/ServicesContext"; -import { Service } from "@/pages/ui/services/models/service"; +import { LOG_LEVEL, Service } from "@/pages/ui/services/models/service"; import { useState } from "react"; import { Select, @@ -12,10 +12,11 @@ import { import EnviromentVariables from "./components/EnviromentVariables"; import ServiceFormCell from "../FormCell"; import ScriptButton from "./components/ScriptButton"; -import { CopyIcon } from "lucide-react"; +import { CheckIcon, CopyIcon, XIcon } from "lucide-react"; import { Button } from "@/components/ui/button"; import { alert } from "@/lib/alert"; import Divider from "@/components/ui/divider"; +import { Label } from "@/components/ui/label"; function ServiceGeneralTab() { const { formService, setFormService } = useServicesContext(); @@ -79,37 +80,107 @@ function ServiceGeneralTab() { }} /> - {formService.token && ( +
+ {formService.token && ( +
+ + +
+ )}
- -
+
+
+
+ Alpine: + {formService.alpine ? ( + + ) : ( + + )}
- )} + +
+ Interlink: + {formService.interlink_node_name ? ( + formService.interlink_node_name + ) : ( + + )} +
+ +
+ Allowed users: + {formService.allowed_users?.length ? ( + formService.allowed_users.join(", ") + ) : ( + + )} +
+
diff --git a/src/pages/ui/services/models/service.ts b/src/pages/ui/services/models/service.ts index 6a15192..8eb7c6f 100644 --- a/src/pages/ui/services/models/service.ts +++ b/src/pages/ui/services/models/service.ts @@ -98,7 +98,17 @@ interface Synchronous { max_scale: number; } +export enum LOG_LEVEL { + CRITICAL = "CRITICAL", + ERROR = "ERROR", + WARNING = "WARNING", + INFO = "INFO", + DEBUG = "DEBUG", + NOTSET = "NOTSET", +} + export interface Service { + allowed_users: string[]; name: string; cluster_id: string; memory: string; @@ -110,8 +120,9 @@ export interface Service { replicas: Replica[]; rescheduler_threshold: string; token: string; - log_level: string; + log_level: LOG_LEVEL; image: string; + interlink_node_name: string; image_rules: []; alpine: boolean; script: string;