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 99cea5c  feat(webui/node): redesign node page (#327)
99cea5c is described below

commit 99cea5c584442619785d1ca42c8cc2e5fff8dc2e
Author: Agnik Misra <[email protected]>
AuthorDate: Thu Jul 24 17:56:42 2025 +0530

    feat(webui/node): redesign node page (#327)
---
 .../clusters/[cluster]/shards/[shard]/page.tsx     | 592 ++++++++++++++++++---
 webui/src/app/ui/formCreation.tsx                  |  13 +-
 webui/src/app/ui/formDialog.tsx                    |   7 +-
 webui/src/app/ui/sidebar.tsx                       | 115 ++--
 4 files changed, 607 insertions(+), 120 deletions(-)

diff --git 
a/webui/src/app/namespaces/[namespace]/clusters/[cluster]/shards/[shard]/page.tsx
 
b/webui/src/app/namespaces/[namespace]/clusters/[cluster]/shards/[shard]/page.tsx
index aa62a56..67f7c10 100644
--- 
a/webui/src/app/namespaces/[namespace]/clusters/[cluster]/shards/[shard]/page.tsx
+++ 
b/webui/src/app/namespaces/[namespace]/clusters/[cluster]/shards/[shard]/page.tsx
@@ -19,12 +19,26 @@
 
 "use client";
 
-import { Box, Typography, Chip, Badge } from "@mui/material";
+import {
+    Box,
+    Typography,
+    Chip,
+    Paper,
+    Grid,
+    Button,
+    IconButton,
+    Tooltip,
+    Popover,
+    RadioGroup,
+    FormControlLabel,
+    Radio,
+    Fade,
+} from "@mui/material";
 import { ShardSidebar } from "@/app/ui/sidebar";
 import { fetchShard } from "@/app/lib/api";
 import { useRouter } from "next/navigation";
 import { useState, useEffect } from "react";
-import { AddNodeCard, ResourceCard } from "@/app/ui/createCard";
+import { AddNodeCard } from "@/app/ui/createCard";
 import Link from "next/link";
 import { LoadingSpinner } from "@/app/ui/loadingSpinner";
 import { truncateText } from "@/app/utils";
@@ -34,6 +48,14 @@ import EmptyState from "@/app/ui/emptyState";
 import AlarmIcon from "@mui/icons-material/Alarm";
 import CheckCircleIcon from "@mui/icons-material/CheckCircle";
 import RemoveCircleIcon from "@mui/icons-material/RemoveCircle";
+import SearchIcon from "@mui/icons-material/Search";
+import FilterListIcon from "@mui/icons-material/FilterList";
+import SortIcon from "@mui/icons-material/Sort";
+import CheckIcon from "@mui/icons-material/Check";
+import ArrowUpwardIcon from "@mui/icons-material/ArrowUpward";
+import ArrowDownwardIcon from "@mui/icons-material/ArrowDownward";
+import { NodeCreation } from "@/app/ui/formCreation";
+import AddIcon from "@mui/icons-material/Add";
 
 export default function Shard({
     params,
@@ -43,6 +65,11 @@ export default function Shard({
     const { namespace, cluster, shard } = params;
     const [nodesData, setNodesData] = useState<any>(null);
     const [loading, setLoading] = useState<boolean>(true);
+    const [searchTerm, setSearchTerm] = useState<string>("");
+    const [filterAnchorEl, setFilterAnchorEl] = useState<null | 
HTMLElement>(null);
+    const [sortAnchorEl, setSortAnchorEl] = useState<null | HTMLElement>(null);
+    const [filterOption, setFilterOption] = useState<string>("all");
+    const [sortOption, setSortOption] = useState<string>("index-asc");
     const router = useRouter();
 
     useEffect(() => {
@@ -61,7 +88,6 @@ export default function Shard({
                 setLoading(false);
             }
         };
-
         fetchData();
     }, [namespace, cluster, shard, router]);
 
@@ -73,7 +99,6 @@ export default function Shard({
     const calculateUptime = (timestamp: number) => {
         const now = Math.floor(Date.now() / 1000);
         const uptimeSeconds = now - timestamp;
-
         if (uptimeSeconds < 60) return `${uptimeSeconds} seconds`;
         if (uptimeSeconds < 3600) return `${Math.floor(uptimeSeconds / 60)} 
minutes`;
         if (uptimeSeconds < 86400) return `${Math.floor(uptimeSeconds / 3600)} 
hours`;
@@ -94,104 +119,515 @@ export default function Shard({
         };
     };
 
+    // Filtering and sorting logic for nodes
+    const filteredAndSortedNodes = (nodesData?.nodes || [])
+        .filter((node: any, idx: number) => {
+            if (!`node ${idx + 
1}`.toLowerCase().includes(searchTerm.toLowerCase())) {
+                return false;
+            }
+            switch (filterOption) {
+                case "master":
+                    return node.role === "master";
+                case "replica":
+                    return node.role !== "master";
+                default:
+                    return true;
+            }
+        })
+        .sort((a: any, b: any) => {
+            switch (sortOption) {
+                case "index-asc":
+                    return a.index - b.index;
+                case "index-desc":
+                    return b.index - a.index;
+                case "uptime-desc":
+                    return b.created_at - a.created_at;
+                case "uptime-asc":
+                    return a.created_at - b.created_at;
+                default:
+                    return 0;
+            }
+        });
+
+    const isFilterOpen = Boolean(filterAnchorEl);
+    const isSortOpen = Boolean(sortAnchorEl);
+    const filterId = isFilterOpen ? "filter-popover" : undefined;
+    const sortId = isSortOpen ? "sort-popover" : undefined;
+
     return (
         <div className="flex h-full">
             <ShardSidebar namespace={namespace} cluster={cluster} />
-            <div className="flex-1 overflow-auto">
-                <Box className="container-inner">
-                    <Box className="mb-6 flex items-center justify-between">
+            <div className="no-scrollbar flex-1 overflow-y-auto bg-white pb-8 
dark:bg-dark">
+                <Box className="px-6 py-4 sm:px-8 sm:py-6">
+                    <div className="mb-4 flex flex-col gap-3 sm:mb-5 
lg:flex-row lg:items-center lg:justify-between">
                         <div>
                             <Typography
-                                variant="h5"
-                                className="flex items-center font-medium 
text-gray-800 dark:text-gray-100"
+                                variant="h4"
+                                className="flex items-center font-medium 
text-gray-900 dark:text-white"
                             >
-                                <DnsIcon className="mr-2 text-primary 
dark:text-primary-light" />
+                                <DnsIcon className="mr-3 text-primary 
dark:text-primary-light" />
                                 Shard {parseInt(shard) + 1}
-                                {nodesData?.nodes && (
-                                    <Chip
-                                        label={`${nodesData.nodes.length} 
nodes`}
-                                        size="small"
-                                        color="secondary"
-                                        className="ml-3"
-                                    />
-                                )}
                             </Typography>
                             <Typography
-                                variant="body2"
-                                className="mt-1 text-gray-500 
dark:text-gray-400"
+                                variant="body1"
+                                className="mt-0.5 text-gray-500 
dark:text-gray-400"
                             >
-                                {cluster} cluster in namespace {namespace}
+                                Manage nodes in this shard
                             </Typography>
                         </div>
-                    </Box>
-
-                    <div className="grid grid-cols-1 gap-4 sm:grid-cols-2 
lg:grid-cols-3 xl:grid-cols-4">
-                        <Box className="col-span-1">
-                            <AddNodeCard namespace={namespace} 
cluster={cluster} shard={shard} />
-                        </Box>
-
-                        {nodesData?.nodes && nodesData.nodes.length > 0 ? (
-                            nodesData.nodes.map((node: any, index: number) => {
-                                const roleInfo = getRoleInfo(node.role);
-                                return (
-                                    <Link
-                                        
href={`/namespaces/${namespace}/clusters/${cluster}/shards/${shard}/nodes/${index}`}
-                                        key={index}
-                                        className="col-span-1"
+                        <div className="flex w-full flex-row items-center 
gap-2 lg:w-auto">
+                            <div className="search-container relative max-w-md 
flex-grow transition-all duration-300 lg:min-w-[280px]">
+                                <div className="search-inner relative w-full 
rounded-lg bg-gray-50 transition-all duration-300 focus-within:bg-white 
focus-within:shadow-md dark:bg-dark-paper/90 dark:focus-within:bg-dark-paper">
+                                    <div className="pointer-events-none 
absolute inset-y-0 left-3 flex items-center">
+                                        <SearchIcon
+                                            className="text-gray-400"
+                                            sx={{ fontSize: 18 }}
+                                        />
+                                    </div>
+                                    <input
+                                        type="text"
+                                        placeholder="Search nodes..."
+                                        className="w-full rounded-lg border-0 
bg-transparent py-2.5 pl-9 pr-4 text-sm text-gray-800 outline-none ring-1 
ring-gray-200 transition-all focus:ring-2 focus:ring-primary dark:text-gray-200 
dark:ring-gray-700 dark:focus:ring-primary-light"
+                                        value={searchTerm}
+                                        onChange={(e) => 
setSearchTerm(e.target.value)}
+                                    />
+                                    {searchTerm && (
+                                        <button
+                                            className="absolute inset-y-0 
right-3 flex items-center text-gray-400 transition-colors hover:text-gray-600 
dark:hover:text-gray-300"
+                                            onClick={() => setSearchTerm("")}
+                                        >
+                                            <span className="text-xs">✕</span>
+                                        </button>
+                                    )}
+                                </div>
+                            </div>
+                            <div className="flex flex-shrink-0 gap-3">
+                                <NodeCreation
+                                    position="card"
+                                    namespace={namespace}
+                                    cluster={cluster}
+                                    shard={shard}
+                                >
+                                    <Button
+                                        variant="outlined"
+                                        color="primary"
+                                        className="whitespace-nowrap 
rounded-lg px-5 py-2.5 font-medium shadow-sm transition-all hover:shadow-md"
+                                        startIcon={<AddIcon />}
+                                        disableElevation
+                                        size="medium"
                                     >
-                                        <ResourceCard
-                                            title={`Node ${index + 1}`}
-                                            tags={[
-                                                { label: node.role, color: 
roleInfo.color as any },
-                                            ]}
+                                        Create Node
+                                    </Button>
+                                </NodeCreation>
+                            </div>
+                        </div>
+                    </div>
+                    <Paper
+                        elevation={0}
+                        className="overflow-hidden rounded-2xl border 
border-gray-100 transition-all hover:shadow-md dark:border-gray-800 
dark:bg-dark-paper"
+                    >
+                        <div className="border-b border-gray-100 px-6 py-3 
dark:border-gray-800 sm:px-8">
+                            <div className="flex items-center justify-between">
+                                <Typography
+                                    variant="h6"
+                                    className="font-medium text-gray-800 
dark:text-gray-100"
+                                >
+                                    All Nodes
+                                </Typography>
+                                <div className="flex items-center gap-2">
+                                    <Tooltip title="Filter">
+                                        <IconButton
+                                            size="small"
+                                            onClick={(e) => 
setFilterAnchorEl(e.currentTarget)}
+                                            aria-describedby={filterId}
+                                            className="rounded-full bg-gray-50 
text-gray-500 hover:bg-gray-100 dark:bg-gray-800 dark:text-gray-400 
dark:hover:bg-gray-700"
                                         >
-                                            <div className="mt-2 space-y-2 
text-sm">
-                                                <div className="flex 
items-center justify-between">
-                                                    <span 
className="text-gray-500 dark:text-gray-400">
-                                                        ID:
+                                            <FilterListIcon fontSize="small" />
+                                        </IconButton>
+                                    </Tooltip>
+                                    <Tooltip title="Sort">
+                                        <IconButton
+                                            size="small"
+                                            onClick={(e) => 
setSortAnchorEl(e.currentTarget)}
+                                            aria-describedby={sortId}
+                                            className="rounded-full bg-gray-50 
text-gray-500 hover:bg-gray-100 dark:bg-gray-800 dark:text-gray-400 
dark:hover:bg-gray-700"
+                                        >
+                                            <SortIcon fontSize="small" />
+                                        </IconButton>
+                                    </Tooltip>
+                                </div>
+                            </div>
+                        </div>
+                        <Popover
+                            id={filterId}
+                            open={isFilterOpen}
+                            anchorEl={filterAnchorEl}
+                            onClose={() => setFilterAnchorEl(null)}
+                            anchorOrigin={{ vertical: "bottom", horizontal: 
"right" }}
+                            transformOrigin={{ vertical: "top", horizontal: 
"right" }}
+                            TransitionComponent={Fade}
+                            PaperProps={{
+                                className:
+                                    "rounded-xl shadow-xl border 
border-gray-100 dark:border-gray-700",
+                                elevation: 3,
+                                sx: { width: 220 },
+                            }}
+                        >
+                            <div className="p-4">
+                                <div className="mb-3 flex items-center 
justify-between border-b border-gray-100 pb-2 dark:border-gray-700">
+                                    <Typography variant="subtitle1" 
className="font-medium">
+                                        Filter Nodes
+                                    </Typography>
+                                </div>
+                                <RadioGroup
+                                    value={filterOption}
+                                    onChange={(e) => 
setFilterOption(e.target.value)}
+                                >
+                                    <div className="space-y-2">
+                                        <div className="rounded-lg bg-gray-50 
p-2 dark:bg-gray-800">
+                                            <FormControlLabel
+                                                value="all"
+                                                control={
+                                                    <Radio
+                                                        size="small"
+                                                        
className="text-primary"
+                                                        checkedIcon={
+                                                            <div 
className="flex h-5 w-5 items-center justify-center rounded-full border-2 
border-primary bg-primary text-white">
+                                                                <CheckIcon
+                                                                    style={{ 
fontSize: 12 }}
+                                                                />
+                                                            </div>
+                                                        }
+                                                    />
+                                                }
+                                                label={
+                                                    <span className="text-sm 
font-medium">
+                                                        All nodes
                                                     </span>
-                                                    <span
-                                                        
className="max-w-[120px] overflow-hidden text-ellipsis rounded bg-gray-100 px-2 
py-0.5 font-mono text-xs dark:bg-dark-border"
-                                                        title={node.id}
-                                                    >
-                                                        {truncateText(node.id, 
10)}
+                                                }
+                                                className="m-0 w-full"
+                                            />
+                                        </div>
+                                        <div className="rounded-lg bg-gray-50 
p-2 dark:bg-gray-800">
+                                            <FormControlLabel
+                                                value="master"
+                                                control={
+                                                    <Radio
+                                                        size="small"
+                                                        
className="text-primary"
+                                                        checkedIcon={
+                                                            <div 
className="flex h-5 w-5 items-center justify-center rounded-full border-2 
border-primary bg-primary text-white">
+                                                                <CheckIcon
+                                                                    style={{ 
fontSize: 12 }}
+                                                                />
+                                                            </div>
+                                                        }
+                                                    />
+                                                }
+                                                label={
+                                                    <span className="text-sm 
font-medium">
+                                                        Master nodes
                                                     </span>
-                                                </div>
-
-                                                <div className="flex 
justify-between">
-                                                    <span 
className="text-gray-500 dark:text-gray-400">
-                                                        Address:
+                                                }
+                                                className="m-0 w-full"
+                                            />
+                                        </div>
+                                        <div className="rounded-lg bg-gray-50 
p-2 dark:bg-gray-800">
+                                            <FormControlLabel
+                                                value="replica"
+                                                control={
+                                                    <Radio
+                                                        size="small"
+                                                        
className="text-primary"
+                                                        checkedIcon={
+                                                            <div 
className="flex h-5 w-5 items-center justify-center rounded-full border-2 
border-primary bg-primary text-white">
+                                                                <CheckIcon
+                                                                    style={{ 
fontSize: 12 }}
+                                                                />
+                                                            </div>
+                                                        }
+                                                    />
+                                                }
+                                                label={
+                                                    <span className="text-sm 
font-medium">
+                                                        Replica nodes
                                                     </span>
-                                                    <span 
className="font-medium">{node.addr}</span>
-                                                </div>
-
-                                                <div className="flex 
items-center justify-between">
-                                                    <span 
className="text-gray-500 dark:text-gray-400">
-                                                        Uptime:
+                                                }
+                                                className="m-0 w-full"
+                                            />
+                                        </div>
+                                    </div>
+                                </RadioGroup>
+                                <div className="mt-4 flex justify-end">
+                                    <Button
+                                        variant="text"
+                                        size="small"
+                                        onClick={() => setFilterAnchorEl(null)}
+                                        className="rounded-lg px-3 py-1 
text-xs"
+                                    >
+                                        Close
+                                    </Button>
+                                </div>
+                            </div>
+                        </Popover>
+                        <Popover
+                            id={sortId}
+                            open={isSortOpen}
+                            anchorEl={sortAnchorEl}
+                            onClose={() => setSortAnchorEl(null)}
+                            anchorOrigin={{ vertical: "bottom", horizontal: 
"right" }}
+                            transformOrigin={{ vertical: "top", horizontal: 
"right" }}
+                            TransitionComponent={Fade}
+                            PaperProps={{
+                                className:
+                                    "rounded-xl shadow-xl border 
border-gray-100 dark:border-gray-700",
+                                elevation: 3,
+                                sx: { width: 220 },
+                            }}
+                        >
+                            <div className="p-4">
+                                <div className="mb-3 flex items-center 
justify-between border-b border-gray-100 pb-2 dark:border-gray-700">
+                                    <Typography variant="subtitle1" 
className="font-medium">
+                                        Sort Nodes
+                                    </Typography>
+                                </div>
+                                <RadioGroup
+                                    value={sortOption}
+                                    onChange={(e) => 
setSortOption(e.target.value)}
+                                >
+                                    <div className="space-y-2">
+                                        <div className="rounded-lg bg-gray-50 
p-2 dark:bg-gray-800">
+                                            <FormControlLabel
+                                                value="index-asc"
+                                                control={
+                                                    <Radio
+                                                        size="small"
+                                                        
className="text-primary"
+                                                        checkedIcon={
+                                                            <div 
className="flex h-5 w-5 items-center justify-center rounded-full border-2 
border-primary bg-primary text-white">
+                                                                <CheckIcon
+                                                                    style={{ 
fontSize: 12 }}
+                                                                />
+                                                            </div>
+                                                        }
+                                                    />
+                                                }
+                                                label={
+                                                    <span className="text-sm 
font-medium">
+                                                        Index 1-N
                                                     </span>
-                                                    <span className="flex 
items-center">
-                                                        <AlarmIcon
-                                                            fontSize="small"
-                                                            className="mr-1 
text-gray-400 dark:text-gray-500"
-                                                        />
-                                                        
{calculateUptime(node.created_at)}
+                                                }
+                                                className="m-0 w-full"
+                                            />
+                                        </div>
+                                        <div className="rounded-lg bg-gray-50 
p-2 dark:bg-gray-800">
+                                            <FormControlLabel
+                                                value="index-desc"
+                                                control={
+                                                    <Radio
+                                                        size="small"
+                                                        
className="text-primary"
+                                                        checkedIcon={
+                                                            <div 
className="flex h-5 w-5 items-center justify-center rounded-full border-2 
border-primary bg-primary text-white">
+                                                                <CheckIcon
+                                                                    style={{ 
fontSize: 12 }}
+                                                                />
+                                                            </div>
+                                                        }
+                                                    />
+                                                }
+                                                label={
+                                                    <span className="text-sm 
font-medium">
+                                                        Index N-1
                                                     </span>
+                                                }
+                                                className="m-0 w-full"
+                                            />
+                                        </div>
+                                        <div className="rounded-lg bg-gray-50 
p-2 dark:bg-gray-800">
+                                            <FormControlLabel
+                                                value="uptime-desc"
+                                                control={
+                                                    <Radio
+                                                        size="small"
+                                                        
className="text-primary"
+                                                        checkedIcon={
+                                                            <div 
className="flex h-5 w-5 items-center justify-center rounded-full border-2 
border-primary bg-primary text-white">
+                                                                <CheckIcon
+                                                                    style={{ 
fontSize: 12 }}
+                                                                />
+                                                            </div>
+                                                        }
+                                                    />
+                                                }
+                                                label={
+                                                    <span className="text-sm 
font-medium">
+                                                        Newest
+                                                    </span>
+                                                }
+                                                className="m-0 w-full"
+                                            />
+                                        </div>
+                                        <div className="rounded-lg bg-gray-50 
p-2 dark:bg-gray-800">
+                                            <FormControlLabel
+                                                value="uptime-asc"
+                                                control={
+                                                    <Radio
+                                                        size="small"
+                                                        
className="text-primary"
+                                                        checkedIcon={
+                                                            <div 
className="flex h-5 w-5 items-center justify-center rounded-full border-2 
border-primary bg-primary text-white">
+                                                                <CheckIcon
+                                                                    style={{ 
fontSize: 12 }}
+                                                                />
+                                                            </div>
+                                                        }
+                                                    />
+                                                }
+                                                label={
+                                                    <span className="text-sm 
font-medium">
+                                                        Oldest
+                                                    </span>
+                                                }
+                                                className="m-0 w-full"
+                                            />
+                                        </div>
+                                    </div>
+                                </RadioGroup>
+                                <div className="mt-4 flex justify-end">
+                                    <Button
+                                        variant="text"
+                                        size="small"
+                                        onClick={() => setSortAnchorEl(null)}
+                                        className="rounded-lg px-3 py-1 
text-xs"
+                                    >
+                                        Close
+                                    </Button>
+                                </div>
+                            </div>
+                        </Popover>
+                        {filteredAndSortedNodes.length > 0 ? (
+                            <div className="divide-y divide-gray-100 
dark:divide-gray-800">
+                                {filteredAndSortedNodes.map((node: any, index: 
number) => {
+                                    const roleInfo = getRoleInfo(node.role);
+                                    return (
+                                        <div
+                                            key={index}
+                                            className="group p-2 
transition-colors hover:bg-gray-50 dark:hover:bg-gray-800/30"
+                                        >
+                                            <Paper
+                                                elevation={0}
+                                                className="overflow-hidden 
rounded-xl border border-transparent bg-white p-4 transition-all 
group-hover:border-primary/10 group-hover:shadow-sm dark:bg-dark-paper 
dark:group-hover:border-primary-dark/20"
+                                            >
+                                                <div className="flex flex-col 
items-start sm:flex-row sm:items-center">
+                                                    <div className="mb-3 flex 
h-14 w-14 flex-shrink-0 items-center justify-center rounded-xl bg-green-50 
text-green-500 dark:bg-green-900/30 dark:text-green-400 sm:mb-0">
+                                                        <DeviceHubIcon sx={{ 
fontSize: 28 }} />
+                                                    </div>
+                                                    <div className="flex 
flex-1 flex-col sm:ml-5 sm:flex-row sm:items-center sm:overflow-hidden">
+                                                        <div className="flex-1 
overflow-hidden">
+                                                            <Link
+                                                                
href={`/namespaces/${namespace}/clusters/${cluster}/shards/${shard}/nodes/${index}`}
+                                                                
className="block"
+                                                            >
+                                                                <div 
className="flex items-center gap-2">
+                                                                    <Typography
+                                                                        
variant="h6"
+                                                                        
className="truncate font-medium text-gray-900 transition-colors 
hover:text-primary dark:text-gray-100 dark:hover:text-primary-light"
+                                                                    >
+                                                                        Node 
{index + 1}
+                                                                    
</Typography>
+                                                                    {node.role 
=== "master" ? (
+                                                                        <div 
className="flex items-center gap-1 rounded-full border border-green-200 
bg-green-50 px-2.5 py-1 dark:border-green-800 dark:bg-green-900/30">
+                                                                            
<div className="h-1.5 w-1.5 rounded-full bg-green-500"></div>
+                                                                            
<span className="text-xs font-medium text-green-700 dark:text-green-300">
+                                                                               
 Master
+                                                                            
</span>
+                                                                        </div>
+                                                                    ) : (
+                                                                        <div 
className="flex items-center gap-1 rounded-full border border-blue-200 
bg-blue-50 px-2.5 py-1 dark:border-blue-800 dark:bg-blue-900/30">
+                                                                            
<div className="h-1.5 w-1.5 rounded-full bg-blue-500"></div>
+                                                                            
<span className="text-xs font-medium text-blue-700 dark:text-blue-300">
+                                                                               
 Replica
+                                                                            
</span>
+                                                                        </div>
+                                                                    )}
+                                                                </div>
+                                                                <div 
className="mt-2 space-y-1">
+                                                                    <Typography
+                                                                        
variant="body2"
+                                                                        
className="flex items-center text-gray-500 dark:text-gray-400"
+                                                                    >
+                                                                        ID:{" 
"}
+                                                                        <span 
className="ml-1 font-mono text-xs">
+                                                                            
{truncateText(
+                                                                               
 node.id,
+                                                                               
 10
+                                                                            )}
+                                                                        </span>
+                                                                    
</Typography>
+                                                                    <Typography
+                                                                        
variant="body2"
+                                                                        
className="flex items-center text-gray-500 dark:text-gray-400"
+                                                                    >
+                                                                        
Address:{" "}
+                                                                        <span 
className="ml-1">
+                                                                            
{node.addr}
+                                                                        </span>
+                                                                    
</Typography>
+                                                                    <Typography
+                                                                        
variant="body2"
+                                                                        
className="flex items-center text-gray-500 dark:text-gray-400"
+                                                                    >
+                                                                        
<AlarmIcon
+                                                                            
fontSize="small"
+                                                                            
className="mr-1 text-gray-400 dark:text-gray-500"
+                                                                        />
+                                                                        
Uptime:{" "}
+                                                                        
{calculateUptime(
+                                                                            
node.created_at
+                                                                        )}
+                                                                    
</Typography>
+                                                                </div>
+                                                            </Link>
+                                                        </div>
+                                                    </div>
                                                 </div>
-                                            </div>
-                                        </ResourceCard>
-                                    </Link>
-                                );
-                            })
+                                            </Paper>
+                                        </div>
+                                    );
+                                })}
+                            </div>
                         ) : (
-                            <Box className="col-span-full">
+                            <div className="p-12">
                                 <EmptyState
-                                    title="No nodes found"
-                                    description="Create a node to get started"
-                                    icon={<DeviceHubIcon sx={{ fontSize: 60 }} 
/>}
+                                    title={
+                                        filterOption !== "all"
+                                            ? "No matching nodes"
+                                            : "No nodes found"
+                                    }
+                                    description={
+                                        filterOption !== "all"
+                                            ? "Try changing your filter 
settings"
+                                            : searchTerm
+                                              ? "Try adjusting your search 
term"
+                                              : "Create a node to get started"
+                                    }
+                                    icon={<DeviceHubIcon sx={{ fontSize: 64 }} 
/>}
                                 />
-                            </Box>
+                            </div>
                         )}
-                    </div>
+                        {filteredAndSortedNodes.length > 0 && (
+                            <div className="bg-gray-50 px-6 py-4 
dark:bg-gray-800/30 sm:px-8">
+                                <Typography
+                                    variant="body2"
+                                    className="text-gray-500 
dark:text-gray-400"
+                                >
+                                    Showing {filteredAndSortedNodes.length} 
of{" "}
+                                    {nodesData.nodes.length} nodes
+                                </Typography>
+                            </div>
+                        )}
+                    </Paper>
                 </Box>
             </div>
         </div>
diff --git a/webui/src/app/ui/formCreation.tsx 
b/webui/src/app/ui/formCreation.tsx
index 0a731aa..ff8cdc4 100644
--- a/webui/src/app/ui/formCreation.tsx
+++ b/webui/src/app/ui/formCreation.tsx
@@ -53,6 +53,7 @@ type NodeFormProps = {
     namespace: string;
     cluster: string;
     shard: string;
+    children?: React.ReactNode;
 };
 
 const containsWhitespace = (value: string): boolean => /\s/.test(value);
@@ -316,7 +317,13 @@ export const MigrateSlot: React.FC<ShardFormProps> = ({ 
position, namespace, clu
     );
 };
 
-export const NodeCreation: React.FC<NodeFormProps> = ({ position, namespace, 
cluster, shard }) => {
+export const NodeCreation: React.FC<NodeFormProps> = ({
+    position,
+    namespace,
+    cluster,
+    shard,
+    children,
+}) => {
     const router = useRouter();
 
     const handleSubmit = async (formData: FormData) => {
@@ -369,6 +376,8 @@ export const NodeCreation: React.FC<NodeFormProps> = ({ 
position, namespace, clu
                 },
             ]}
             onSubmit={handleSubmit}
-        />
+        >
+            {children}
+        </FormDialog>
     );
 };
diff --git a/webui/src/app/ui/formDialog.tsx b/webui/src/app/ui/formDialog.tsx
index d5adc25..10a47bc 100644
--- a/webui/src/app/ui/formDialog.tsx
+++ b/webui/src/app/ui/formDialog.tsx
@@ -170,7 +170,7 @@ const FormDialog: React.FC<FormDialogProps> = ({
                         }}
                     >
                         <Typography
-                            variant="subtitle1"
+                            variant="h6"
                             className="font-semibold text-gray-800 
dark:text-gray-100"
                         >
                             {title}
@@ -281,6 +281,11 @@ const FormDialog: React.FC<FormDialogProps> = ({
                                                 alignItems: "center",
                                                 minHeight: "32px",
                                             },
+                                            "& .MuiInputBase-input": {
+                                                display: "flex",
+                                                alignItems: "center",
+                                                minHeight: "35px",
+                                            },
                                         }}
                                         MenuProps={{
                                             PaperProps: {
diff --git a/webui/src/app/ui/sidebar.tsx b/webui/src/app/ui/sidebar.tsx
index 9fcbe60..dcef803 100644
--- a/webui/src/app/ui/sidebar.tsx
+++ b/webui/src/app/ui/sidebar.tsx
@@ -290,6 +290,17 @@ export function ShardSidebar({ namespace, cluster }: { 
namespace: string; cluste
     const [shards, setShards] = useState<string[]>([]);
     const [error, setError] = useState<string | null>(null);
     const [isOpen, setIsOpen] = useState(true);
+    const [sidebarWidth, setSidebarWidth] = useState(260);
+    const [isMobile, setIsMobile] = useState(false);
+
+    useEffect(() => {
+        const checkMobile = () => {
+            setIsMobile(window.innerWidth < 768);
+        };
+        checkMobile();
+        window.addEventListener("resize", checkMobile);
+        return () => window.removeEventListener("resize", checkMobile);
+    }, []);
 
     useEffect(() => {
         const fetchData = async () => {
@@ -306,57 +317,83 @@ export function ShardSidebar({ namespace, cluster }: { 
namespace: string; cluste
         fetchData();
     }, [namespace, cluster]);
 
+    const toggleSidebar = () => {
+        if (isMobile) {
+            setSidebarWidth(isOpen ? 0 : 260);
+        }
+        setIsOpen(!isOpen);
+    };
+
     return (
         <Paper
-            className="flex h-full w-64 flex-col overflow-hidden border-r 
border-light-border/50 bg-white/90 backdrop-blur-sm dark:border-dark-border/50 
dark:bg-dark-paper/90"
+            className="sidebar-container flex h-full flex-col overflow-hidden 
border-r border-light-border/50 bg-white/90 backdrop-blur-sm transition-all 
duration-300 dark:border-dark-border/50 dark:bg-dark-paper/90"
             elevation={0}
             sx={{
+                width: `${sidebarWidth}px`,
+                minWidth: isMobile ? 0 : "260px",
+                maxWidth: "260px",
                 borderTopRightRadius: "16px",
                 borderBottomRightRadius: "16px",
                 boxShadow: "4px 0 15px rgba(0, 0, 0, 0.03)",
+                transition: "width 0.3s cubic-bezier(0.4, 0, 0.2, 1)",
             }}
         >
-            <Box className="p-4 pb-2">
-                <ShardCreation namespace={namespace} cluster={cluster} 
position="sidebar" />
-            </Box>
+            {isMobile && (
+                <button
+                    onClick={toggleSidebar}
+                    className="sidebar-toggle-btn absolute -right-10 top-4 
z-50 flex h-9 w-9 items-center justify-center rounded-full bg-white 
text-gray-600 shadow-lg transition-all hover:bg-gray-50 dark:bg-dark-paper 
dark:text-gray-300 dark:hover:bg-dark-border"
+                >
+                    {isOpen ? (
+                        <ChevronRightIcon />
+                    ) : (
+                        <ChevronRightIcon sx={{ transform: "rotate(180deg)" }} 
/>
+                    )}
+                </button>
+            )}
 
-            <Box className="px-4 py-2">
-                <SidebarHeader
-                    title="Shards"
-                    count={shards.length}
-                    isOpen={isOpen}
-                    toggleOpen={() => setIsOpen(!isOpen)}
-                    icon={<DnsIcon fontSize="small" />}
-                />
-            </Box>
+            <div className="sidebar-inner w-[260px]">
+                <Box className="p-4 pb-2">
+                    <ShardCreation namespace={namespace} cluster={cluster} 
position="sidebar" />
+                </Box>
 
-            <Collapse in={isOpen} className="flex-1 overflow-hidden">
-                <div className="h-full overflow-hidden px-4">
-                    <div className="custom-scrollbar max-h-[calc(100vh-180px)] 
overflow-y-auto rounded-xl bg-gray-50/50 p-2 dark:bg-dark-border/20">
-                        {error && (
-                            <div className="my-2 rounded-lg bg-red-50 p-2 
text-center text-sm text-red-600 dark:bg-red-900/20 dark:text-red-400">
-                                {error}
-                            </div>
-                        )}
-                        <List className="p-0">
-                            {shards.map((shard, index) => (
-                                <Link
-                                    
href={`/namespaces/${namespace}/clusters/${cluster}/shards/${index}`}
-                                    passHref
-                                    key={index}
-                                >
-                                    <Item
-                                        type="shard"
-                                        item={shard}
-                                        namespace={namespace}
-                                        cluster={cluster}
-                                    />
-                                </Link>
-                            ))}
-                        </List>
+                <Box className="px-4 py-2">
+                    <SidebarHeader
+                        title="Shards"
+                        count={shards.length}
+                        isOpen={isOpen}
+                        toggleOpen={toggleSidebar}
+                        icon={<DnsIcon fontSize="small" />}
+                    />
+                </Box>
+
+                <Collapse in={isOpen} className="flex-1 overflow-hidden">
+                    <div className="h-full overflow-hidden px-4">
+                        <div className="custom-scrollbar 
max-h-[calc(100vh-180px)] overflow-y-auto rounded-xl bg-gray-50/50 p-2 
dark:bg-dark-border/20">
+                            {error && (
+                                <div className="my-2 rounded-lg bg-red-50 p-2 
text-center text-sm text-red-600 dark:bg-red-900/20 dark:text-red-400">
+                                    {error}
+                                </div>
+                            )}
+                            <List className="p-0">
+                                {shards.map((shard, index) => (
+                                    <Link
+                                        
href={`/namespaces/${namespace}/clusters/${cluster}/shards/${index}`}
+                                        passHref
+                                        key={index}
+                                    >
+                                        <Item
+                                            type="shard"
+                                            item={shard}
+                                            namespace={namespace}
+                                            cluster={cluster}
+                                        />
+                                    </Link>
+                                ))}
+                            </List>
+                        </div>
                     </div>
-                </div>
-            </Collapse>
+                </Collapse>
+            </div>
         </Paper>
     );
 }

Reply via email to