This is an automated email from the ASF dual-hosted git repository.

twice pushed a commit to branch unstable
in repository https://gitbox.apache.org/repos/asf/kvrocks-controller.git


The following commit(s) were added to refs/heads/unstable by this push:
     new 5fc828c  Support namespace CRUD (#149)
5fc828c is described below

commit 5fc828c1da4720dbb40f65f3df424101d7c673af
Author: Venjy <[email protected]>
AuthorDate: Mon Mar 4 21:40:40 2024 +0800

    Support namespace CRUD (#149)
    
    * Add banner and namespace list
    
    * Support namespace creation and deletion
    
    * Add error message when fail to create namespace
    
    ---------
    
    Co-authored-by: datavisorwenjiejiang <[email protected]>
---
 webui/package.json                               |   4 +
 webui/public/logo.svg                            | 240 +++++++++++++++++++++++
 webui/src/app/{ => cluster}/layout.tsx           |  27 +--
 webui/src/app/{layout.tsx => cluster/page.tsx}   |  33 ++--
 webui/src/app/globals.css                        |  27 ---
 webui/src/app/layout.tsx                         |   9 +-
 webui/src/app/{layout.tsx => lib/actions.ts}     |  36 ++--
 webui/src/app/lib/api.ts                         |  82 ++++++++
 webui/src/app/page.tsx                           |  14 +-
 webui/src/app/{page.tsx => ui/banner.tsx}        |  38 ++--
 webui/src/app/ui/namespace/namespaceCreation.tsx |  89 +++++++++
 webui/src/app/ui/namespace/namespaceItem.tsx     |  81 ++++++++
 webui/src/app/{layout.tsx => ui/nav-links.tsx}   |  42 ++--
 webui/src/app/ui/sidebar.tsx                     |  44 +++++
 14 files changed, 638 insertions(+), 128 deletions(-)

diff --git a/webui/package.json b/webui/package.json
index f7f2418..4ae3d9b 100644
--- a/webui/package.json
+++ b/webui/package.json
@@ -11,7 +11,11 @@
     "dependencies": {
         "@emotion/react": "^11.11.3",
         "@emotion/styled": "^11.11.0",
+        "@mui/icons-material": "^5.15.7",
         "@mui/material": "^5.15.5",
+        "@types/js-yaml": "^4.0.9",
+        "axios": "^1.6.7",
+        "js-yaml": "^4.1.0",
         "next": "14.1.0",
         "react": "^18",
         "react-dom": "^18"
diff --git a/webui/public/logo.svg b/webui/public/logo.svg
new file mode 100644
index 0000000..cfba0d1
--- /dev/null
+++ b/webui/public/logo.svg
@@ -0,0 +1,240 @@
+<?xml version="1.0" encoding="UTF-8" standalone="no"?>
+<!-- 
+ * Licensed to the Apache Software Foundation (ASF) under one
+ * or more contributor license agreements.  See the NOTICE file
+ * distributed with this work for additional information
+ * regarding copyright ownership.  The ASF licenses this file
+ * to you under the Apache License, Version 2.0 (the
+ * "License"); you may not use this file except in compliance
+ * with the License.  You may obtain a copy of the License at
+ * 
+ *   http://www.apache.org/licenses/LICENSE-2.0
+ * 
+ * Unless required by applicable law or agreed to in writing,
+ * software distributed under the License is distributed on an
+ * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
+ * KIND, either express or implied.  See the License for the
+ * specific language governing permissions and limitations
+ * under the License. 
+-->
+<!DOCTYPE svg PUBLIC "-//W3C//DTD SVG 1.1//EN" 
"http://www.w3.org/Graphics/SVG/1.1/DTD/svg11.dtd";>
+<svg version="1.1" id="Layer_1" xmlns="http://www.w3.org/2000/svg"; 
xmlns:xlink="http://www.w3.org/1999/xlink"; x="0px" y="0px" width="203px" 
height="202px" viewBox="0 0 203 202" enable-background="new 0 0 203 202" 
xml:space="preserve">  <image id="image0" width="203" height="202" x="0" y="0"
+    
href="data:image/png;base64,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
+LgAAAABJRU5ErkJggg==" />
+</svg>
diff --git a/webui/src/app/layout.tsx b/webui/src/app/cluster/layout.tsx
similarity index 63%
copy from webui/src/app/layout.tsx
copy to webui/src/app/cluster/layout.tsx
index ec5e29d..0c4c376 100644
--- a/webui/src/app/layout.tsx
+++ b/webui/src/app/cluster/layout.tsx
@@ -17,25 +17,10 @@
  * under the License. 
  */
 
-import type { Metadata } from "next";
-import { Inter } from "next/font/google";
-import "./globals.css";
-
-const inter = Inter({ subsets: ["latin"] });
-
-export const metadata: Metadata = {
-    title: "Kvrocks Controller",
-    description: "Kvrocks Controller",
-};
-
-export default function RootLayout({
-    children,
-}: Readonly<{
-  children: React.ReactNode;
-}>) {
+export default function Layout({children}: {children: React.ReactNode}) {
     return (
-        <html lang="en">
-            <body className={inter.className}>{children}</body>
-        </html>
-    );
-}
+        <>
+            {children}
+        </>
+    )
+}
\ No newline at end of file
diff --git a/webui/src/app/layout.tsx b/webui/src/app/cluster/page.tsx
similarity index 63%
copy from webui/src/app/layout.tsx
copy to webui/src/app/cluster/page.tsx
index ec5e29d..62a9847 100644
--- a/webui/src/app/layout.tsx
+++ b/webui/src/app/cluster/page.tsx
@@ -17,25 +17,18 @@
  * under the License. 
  */
 
-import type { Metadata } from "next";
-import { Inter } from "next/font/google";
-import "./globals.css";
+import { Box, Container } from "@mui/material";
+import Sidebar from "../ui/sidebar";
 
-const inter = Inter({ subsets: ["latin"] });
-
-export const metadata: Metadata = {
-    title: "Kvrocks Controller",
-    description: "Kvrocks Controller",
-};
-
-export default function RootLayout({
-    children,
-}: Readonly<{
-  children: React.ReactNode;
-}>) {
+export default function Cluster() {
     return (
-        <html lang="en">
-            <body className={inter.className}>{children}</body>
-        </html>
-    );
-}
+        <div className="flex h-full">
+            <Sidebar />
+            <Container maxWidth={false} disableGutters sx={{height: '100%', 
overflowY: 'auto', marginLeft: '16px'}}>
+                <div>
+                    todo: show all clusters in selected namespace here
+                </div>
+            </Container>
+        </div>
+    )
+}
\ No newline at end of file
diff --git a/webui/src/app/globals.css b/webui/src/app/globals.css
index bd5c251..7c7084d 100644
--- a/webui/src/app/globals.css
+++ b/webui/src/app/globals.css
@@ -20,30 +20,3 @@
 @tailwind base;
 @tailwind components;
 @tailwind utilities;
-
-:root {
-    --foreground-rgb: 0, 0, 0;
-    --background-start-rgb: 214, 219, 220;
-    --background-end-rgb: 255, 255, 255;
-}
-
-@media (prefers-color-scheme: dark) {
-    :root {
-        --foreground-rgb: 255, 255, 255;
-        --background-start-rgb: 0, 0, 0;
-        --background-end-rgb: 0, 0, 0;
-    }
-}
-
-body {
-    color: rgb(var(--foreground-rgb));
-    background: linear-gradient(to bottom,
-            transparent,
-            rgb(var(--background-end-rgb))) rgb(var(--background-start-rgb));
-}
-
-@layer utilities {
-    .text-balance {
-        text-wrap: balance;
-    }
-}
\ No newline at end of file
diff --git a/webui/src/app/layout.tsx b/webui/src/app/layout.tsx
index ec5e29d..a1e1a0a 100644
--- a/webui/src/app/layout.tsx
+++ b/webui/src/app/layout.tsx
@@ -20,6 +20,8 @@
 import type { Metadata } from "next";
 import { Inter } from "next/font/google";
 import "./globals.css";
+import Banner from "./ui/banner";
+import { Container } from "@mui/material";
 
 const inter = Inter({ subsets: ["latin"] });
 
@@ -35,7 +37,12 @@ export default function RootLayout({
 }>) {
     return (
         <html lang="en">
-            <body className={inter.className}>{children}</body>
+            <body className={inter.className}>
+                <Banner />
+                <Container sx={{marginTop: '64px', height: 'calc(100vh - 
64px)'}} maxWidth={false} disableGutters>
+                    {children}
+                </Container>
+            </body>
         </html>
     );
 }
diff --git a/webui/src/app/layout.tsx b/webui/src/app/lib/actions.ts
similarity index 60%
copy from webui/src/app/layout.tsx
copy to webui/src/app/lib/actions.ts
index ec5e29d..55ff8d0 100644
--- a/webui/src/app/layout.tsx
+++ b/webui/src/app/lib/actions.ts
@@ -16,26 +16,22 @@
  * specific language governing permissions and limitations
  * under the License. 
  */
+'use server';
 
-import type { Metadata } from "next";
-import { Inter } from "next/font/google";
-import "./globals.css";
+import { redirect } from "next/navigation";
+import { createNamespace, deleteNamespace } from "./api";
+import { revalidatePath } from "next/cache";
 
-const inter = Inter({ subsets: ["latin"] });
-
-export const metadata: Metadata = {
-    title: "Kvrocks Controller",
-    description: "Kvrocks Controller",
-};
-
-export default function RootLayout({
-    children,
-}: Readonly<{
-  children: React.ReactNode;
-}>) {
-    return (
-        <html lang="en">
-            <body className={inter.className}>{children}</body>
-        </html>
-    );
+export async function createNamespaceAction(name: string): Promise<string> {
+    const errMsg = await createNamespace(name);
+    if(!errMsg) {
+        revalidatePath('/cluster');
+    }
+    return errMsg;
 }
+
+export async function deleteNamespaceAction(name: string): Promise<string> {
+    const result = deleteNamespace(name);
+    revalidatePath('/cluster');
+    return result;
+}
\ No newline at end of file
diff --git a/webui/src/app/lib/api.ts b/webui/src/app/lib/api.ts
new file mode 100644
index 0000000..723d0bf
--- /dev/null
+++ b/webui/src/app/lib/api.ts
@@ -0,0 +1,82 @@
+/* 
+ * Licensed to the Apache Software Foundation (ASF) under one
+ * or more contributor license agreements.  See the NOTICE file
+ * distributed with this work for additional information
+ * regarding copyright ownership.  The ASF licenses this file
+ * to you under the Apache License, Version 2.0 (the
+ * "License"); you may not use this file except in compliance
+ * with the License.  You may obtain a copy of the License at
+ * 
+ *   http://www.apache.org/licenses/LICENSE-2.0
+ * 
+ * Unless required by applicable law or agreed to in writing,
+ * software distributed under the License is distributed on an
+ * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
+ * KIND, either express or implied.  See the License for the
+ * specific language governing permissions and limitations
+ * under the License. 
+ */
+
+import yaml from 'js-yaml';
+import fs from 'fs';
+import path from 'path';
+import axios, { AxiosError } from 'axios';
+
+const configFile = './config/config.yaml';
+const apiPrefix = '/api/v1';
+let host;
+try {
+    const wholeFilePath = path.join(process.cwd(), '..', configFile);
+    const doc = yaml.load(fs.readFileSync(wholeFilePath, 'utf8'));
+    host = (doc as any)['addr'];
+} catch (error) {
+    host = '127.0.0.1:9379';
+}
+const apiHost = `http://${host}${apiPrefix}`;
+
+export async function fetchNamespaces(): Promise<string[]> {
+    try {
+        const { data: responseData } = await 
axios.get(`${apiHost}/namespaces`);
+        return responseData.data.namespaces || [];
+    } catch (error) {
+        handleError(error);
+        return [];
+    }
+}
+export async function createNamespace(name: string): Promise<string> {
+    try {
+        const { data: responseData } = await 
axios.post(`${apiHost}/namespaces`, {namespace: name});
+        if(responseData?.data == 'created') {
+            return '';
+        } else {
+            return handleError(responseData);
+        }
+    } catch (error) {
+        return handleError(error);
+    }
+}
+
+export async function deleteNamespace(name: string): Promise<string> {
+    try {
+        const { data: responseData } = await 
axios.delete(`${apiHost}/namespaces/${name}`);
+        if(responseData?.data == 'ok') {
+            return '';
+        } else {
+            return handleError(responseData);
+        }
+    } catch (error) {
+        return handleError(error);
+    }
+}
+
+function handleError(error: any): string {
+    let message: string = '';
+    if(error instanceof AxiosError) {
+        message = error.response?.data?.error?.message || error.message;
+    } else if (error instanceof Error) {
+        message = error.message;
+    } else if (typeof error === 'object') {
+        message = error?.error?.message || error?.message;
+    }
+    return message || 'Unknown error';
+}
\ No newline at end of file
diff --git a/webui/src/app/page.tsx b/webui/src/app/page.tsx
index 23a1068..78456a6 100644
--- a/webui/src/app/page.tsx
+++ b/webui/src/app/page.tsx
@@ -17,17 +17,19 @@
  * under the License. 
  */
 
-import { Button } from "@mui/material";
-import Image from "next/image";
+import { Button, Container, Typography } from "@mui/material";
 
 export default function Home() {
     return (
-        <main className="flex flex-col items-center justify-center 
min-h-screen space-y-4">
-            <h1 className="text-4xl font-bold">Kvrocks Controler UI</h1>
-            <p className="text-xl">Work in progress...</p>
+        <div
+            style={{minHeight: 'calc(100vh - 64px)', height: 'calc(100vh - 
64px)'}}
+            className={'flex flex-col items-center justify-center space-y-2 
h-full'}
+        >
+            <Typography variant="h3">Kvrocks Controler UI</Typography>
+            <Typography variant="body1">Work in progress...</Typography>
             <Button size="large" variant="outlined" sx={{ textTransform: 
'none' }} href="https://github.com/apache/kvrocks-controller/issues/135";>
                 Click here to submit your suggestions
             </Button>
-        </main>
+        </div>
     );
 }
diff --git a/webui/src/app/page.tsx b/webui/src/app/ui/banner.tsx
similarity index 56%
copy from webui/src/app/page.tsx
copy to webui/src/app/ui/banner.tsx
index 23a1068..8839817 100644
--- a/webui/src/app/page.tsx
+++ b/webui/src/app/ui/banner.tsx
@@ -17,17 +17,31 @@
  * under the License. 
  */
 
-import { Button } from "@mui/material";
+import { AppBar, Container, Toolbar } from "@mui/material";
 import Image from "next/image";
+import NavLinks from "./nav-links";
 
-export default function Home() {
-    return (
-        <main className="flex flex-col items-center justify-center 
min-h-screen space-y-4">
-            <h1 className="text-4xl font-bold">Kvrocks Controler UI</h1>
-            <p className="text-xl">Work in progress...</p>
-            <Button size="large" variant="outlined" sx={{ textTransform: 
'none' }} href="https://github.com/apache/kvrocks-controller/issues/135";>
-                Click here to submit your suggestions
-            </Button>
-        </main>
-    );
-}
+const links = [
+    {
+        url: '/',
+        title: 'Home'
+    },{
+        url: '/cluster',
+        title: 'cluster'
+    },{
+        url: 'https://kvrocks.apache.org',
+        title: 'community',
+        _blank: true
+    },
+]
+
+export default function Banner() {
+    return (<AppBar>
+        <Container maxWidth={false}>
+            <Toolbar className="space-x-4">
+                <Image src="/logo.svg" width={40} height={40} 
alt='logo'></Image>
+                <NavLinks links={links}/>
+            </Toolbar>
+        </Container>
+    </AppBar>)
+}
\ No newline at end of file
diff --git a/webui/src/app/ui/namespace/namespaceCreation.tsx 
b/webui/src/app/ui/namespace/namespaceCreation.tsx
new file mode 100644
index 0000000..a0bcf23
--- /dev/null
+++ b/webui/src/app/ui/namespace/namespaceCreation.tsx
@@ -0,0 +1,89 @@
+/* 
+ * Licensed to the Apache Software Foundation (ASF) under one
+ * or more contributor license agreements.  See the NOTICE file
+ * distributed with this work for additional information
+ * regarding copyright ownership.  The ASF licenses this file
+ * to you under the Apache License, Version 2.0 (the
+ * "License"); you may not use this file except in compliance
+ * with the License.  You may obtain a copy of the License at
+ * 
+ *   http://www.apache.org/licenses/LICENSE-2.0
+ * 
+ * Unless required by applicable law or agreed to in writing,
+ * software distributed under the License is distributed on an
+ * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
+ * KIND, either express or implied.  See the License for the
+ * specific language governing permissions and limitations
+ * under the License. 
+ */
+
+'use client';
+import { createNamespaceAction } from "@/app/lib/actions";
+import { Alert, Button, Dialog, DialogActions, DialogContent, 
DialogContentText, DialogTitle, Snackbar, TextField } from "@mui/material";
+import { useCallback, useState } from "react";
+
+export default function NamespaceCreation() {
+    const [showDialog, setShowDialog] = useState(false);
+    const openDialog = useCallback(() => setShowDialog(true), []);
+    const closeDialog = useCallback(() => setShowDialog(false), []);
+    const [errorMessage, setErrorMessage] = useState('');
+
+    return (
+        <>
+            <Button variant="outlined" onClick={openDialog}>Create 
Namespace</Button>
+            <Dialog
+                open={showDialog}
+                PaperProps={{
+                    component: 'form',
+                    action: async (formData: FormData) => {
+                        const formObj = Object.fromEntries(formData.entries());
+                        if(typeof formObj['name'] === 'string') {
+                            const errMsg = await 
createNamespaceAction(formObj['name']);
+                            if (errMsg) {
+                                setErrorMessage(errMsg);
+                            }
+                            closeDialog();
+                        }
+                    },
+                }}
+                onClose={closeDialog}
+            >
+                <DialogTitle>Create Namespace</DialogTitle>
+                <DialogContent
+                    sx={{
+                        width: '500px'
+                    }}
+                >
+                    <TextField
+                        autoFocus
+                        required
+                        name="name"
+                        label="Input Name"
+                        type="name"
+                        fullWidth
+                        variant="standard"
+                    />
+                </DialogContent>
+                <DialogActions>
+                    <Button onClick={closeDialog}>Cancel</Button>
+                    <Button type="submit">Create</Button>
+                </DialogActions>
+            </Dialog>
+            <Snackbar
+                open={!!errorMessage}
+                autoHideDuration={5000}
+                onClose={() => setErrorMessage('')}
+                anchorOrigin={{vertical: 'bottom', horizontal: 'right'}}
+            >
+                <Alert
+                    onClose={() => setErrorMessage('')}
+                    severity="error"
+                    variant="filled"
+                    sx={{ width: '100%' }}
+                >
+                    {errorMessage}
+                </Alert>
+            </Snackbar>
+        </>
+    )
+}
diff --git a/webui/src/app/ui/namespace/namespaceItem.tsx 
b/webui/src/app/ui/namespace/namespaceItem.tsx
new file mode 100644
index 0000000..21145e0
--- /dev/null
+++ b/webui/src/app/ui/namespace/namespaceItem.tsx
@@ -0,0 +1,81 @@
+/* 
+ * Licensed to the Apache Software Foundation (ASF) under one
+ * or more contributor license agreements.  See the NOTICE file
+ * distributed with this work for additional information
+ * regarding copyright ownership.  The ASF licenses this file
+ * to you under the Apache License, Version 2.0 (the
+ * "License"); you may not use this file except in compliance
+ * with the License.  You may obtain a copy of the License at
+ * 
+ *   http://www.apache.org/licenses/LICENSE-2.0
+ * 
+ * Unless required by applicable law or agreed to in writing,
+ * software distributed under the License is distributed on an
+ * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
+ * KIND, either express or implied.  See the License for the
+ * specific language governing permissions and limitations
+ * under the License. 
+ */
+
+'use client';
+import { Button, Dialog, DialogActions, DialogContent, DialogContentText, 
DialogTitle, IconButton, ListItem, ListItemButton, ListItemText, Menu, 
MenuItem, Tooltip } from "@mui/material";
+import MoreHorizIcon from '@mui/icons-material/MoreHoriz';
+import { useCallback, useRef, useState } from "react";
+import { deleteNamespaceAction } from "@/app/lib/actions";
+
+export default function NamespaceItem({ item }: {item: string}) {
+    const [hover, setHover] = useState<boolean>(false);
+    const [showMenu, setShowMenu] = useState<boolean>(false);
+    const listItemTextRef = useRef(null);
+    const openMenu = useCallback(() => setShowMenu(true), []);
+    const closeMenu = useCallback(() => (setShowMenu(false), setHover(false)), 
[]);
+    const [showDeleteConfirm, setShowDeleteConfirm] = useState<boolean>(false);
+    const openDeleteConfirmDialog = useCallback(() => 
(setShowDeleteConfirm(true), closeMenu()), [closeMenu]);
+    const closeDeleteConfirmDialog = useCallback(() => 
setShowDeleteConfirm(false), []);
+    const confirmDelete = useCallback(async () => {
+        await deleteNamespaceAction(item);
+        closeMenu();
+    },[item, closeMenu])
+    return (<ListItem
+        disablePadding
+        secondaryAction={
+            hover && <IconButton onClick={openMenu} ref={listItemTextRef} >
+                <MoreHorizIcon />
+            </IconButton>
+        }
+        onMouseEnter={() => setHover(true)}
+        onMouseLeave={() => !showMenu && setHover(false)}
+    >
+        <ListItemButton sx={{paddingRight: '10px'}}>
+            <Tooltip title={item} arrow>
+                <ListItemText classes={{primary: 'overflow-hidden 
text-ellipsis text-nowrap'}} primary={`${item}`} />
+            </Tooltip>
+        </ListItemButton>
+        <Menu
+            id={item}
+            open={showMenu}
+            onClose={closeMenu}
+            anchorEl={listItemTextRef.current}
+            anchorOrigin={{
+                vertical: 'center',
+                horizontal: 'center',
+            }}
+        >
+            <MenuItem color="red" 
onClick={openDeleteConfirmDialog}>Delete</MenuItem>
+        </Menu>
+        <Dialog
+            open={showDeleteConfirm}
+        >
+            <DialogTitle>Confirm</DialogTitle>
+            <DialogContent>
+                <DialogContentText>
+                    Please confirm you want to delete namespace {item}
+                </DialogContentText>
+            </DialogContent>
+            <DialogActions>
+                <Button onClick={closeDeleteConfirmDialog}>Cancel</Button>
+                <Button onClick={confirmDelete} color="error">Delete</Button>
+            </DialogActions>
+        </Dialog>
+    </ListItem>)
+}
diff --git a/webui/src/app/layout.tsx b/webui/src/app/ui/nav-links.tsx
similarity index 62%
copy from webui/src/app/layout.tsx
copy to webui/src/app/ui/nav-links.tsx
index ec5e29d..66312d6 100644
--- a/webui/src/app/layout.tsx
+++ b/webui/src/app/ui/nav-links.tsx
@@ -17,25 +17,25 @@
  * under the License. 
  */
 
-import type { Metadata } from "next";
-import { Inter } from "next/font/google";
-import "./globals.css";
+import { Button } from "@mui/material"
+import Link from "next/link"
 
-const inter = Inter({ subsets: ["latin"] });
-
-export const metadata: Metadata = {
-    title: "Kvrocks Controller",
-    description: "Kvrocks Controller",
-};
-
-export default function RootLayout({
-    children,
-}: Readonly<{
-  children: React.ReactNode;
-}>) {
-    return (
-        <html lang="en">
-            <body className={inter.className}>{children}</body>
-        </html>
-    );
-}
+export default function NavLinks({links}: {
+    links: Array<{
+        url: string,
+        title: string,
+        _blank?: boolean,
+    }>
+}) {
+    return <>
+        {links.map(link => <Link
+            key={link.url}
+            href={link.url}
+            {...(link._blank ? {target: '_blank'} : {})}
+        >
+            <Button color="inherit">
+                {link.title}
+            </Button>
+        </Link>)}
+    </>
+}
\ No newline at end of file
diff --git a/webui/src/app/ui/sidebar.tsx b/webui/src/app/ui/sidebar.tsx
new file mode 100644
index 0000000..73c976a
--- /dev/null
+++ b/webui/src/app/ui/sidebar.tsx
@@ -0,0 +1,44 @@
+/* 
+ * Licensed to the Apache Software Foundation (ASF) under one
+ * or more contributor license agreements.  See the NOTICE file
+ * distributed with this work for additional information
+ * regarding copyright ownership.  The ASF licenses this file
+ * to you under the Apache License, Version 2.0 (the
+ * "License"); you may not use this file except in compliance
+ * with the License.  You may obtain a copy of the License at
+ * 
+ *   http://www.apache.org/licenses/LICENSE-2.0
+ * 
+ * Unless required by applicable law or agreed to in writing,
+ * software distributed under the License is distributed on an
+ * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
+ * KIND, either express or implied.  See the License for the
+ * specific language governing permissions and limitations
+ * under the License. 
+ */
+
+import { Divider, List } from "@mui/material";
+import { fetchNamespaces } from "@/app/lib/api";
+import NamespaceItem from "./namespace/namespaceItem";
+import NamespaceCreation from "./namespace/namespaceCreation";
+
+export default async function Sidebar() {
+    const namespaces = await fetchNamespaces();
+    return (
+        <div className="w-60 h-full flex">
+            <List className="w-full overflow-y-auto">
+                <div className="mt-2 mb-4 text-center">
+                    <NamespaceCreation />
+                </div>
+                {namespaces.map((namespace, index) => (<>
+                    {index === 0 && (
+                        <Divider variant="middle"/>
+                    )}
+                    <NamespaceItem key={namespace} item={namespace} />
+                    <Divider variant="middle"/>
+                </>))}
+            </List>
+            <Divider orientation="vertical" flexItem/>
+        </div>
+    )
+}
\ No newline at end of file


Reply via email to