feat: improve k8s resource filtering UI
- Change namespace filter from text input to dropdown select - Add name filtering input for all resource types - Fix UI overlap issue with namespace dropdown label - Add automatic namespace list loading - Implement server-side name filtering for all resources - Support combined namespace + name filtering 🤖 Generated with [Qoder][https://qoder.com]
This commit is contained in:
@@ -26,6 +26,8 @@ import {
|
|||||||
DialogContent,
|
DialogContent,
|
||||||
DialogActions,
|
DialogActions,
|
||||||
Snackbar,
|
Snackbar,
|
||||||
|
MenuItem,
|
||||||
|
Select,
|
||||||
} from '@mui/material'
|
} from '@mui/material'
|
||||||
import SettingsIcon from '@mui/icons-material/Settings'
|
import SettingsIcon from '@mui/icons-material/Settings'
|
||||||
import CloseIcon from '@mui/icons-material/Close'
|
import CloseIcon from '@mui/icons-material/Close'
|
||||||
@@ -56,6 +58,8 @@ export default function K8sResourceList() {
|
|||||||
const [kubeconfig, setKubeconfig] = useState('')
|
const [kubeconfig, setKubeconfig] = useState('')
|
||||||
const [kubeconfigError, setKubeconfigError] = useState(false)
|
const [kubeconfigError, setKubeconfigError] = useState(false)
|
||||||
const [namespace, setNamespace] = useState('')
|
const [namespace, setNamespace] = useState('')
|
||||||
|
const [namespaces, setNamespaces] = useState<string[]>([])
|
||||||
|
const [nameFilter, setNameFilter] = useState('')
|
||||||
const [createDialogOpen, setCreateDialogOpen] = useState(false)
|
const [createDialogOpen, setCreateDialogOpen] = useState(false)
|
||||||
const [yamlContent, setYamlContent] = useState('')
|
const [yamlContent, setYamlContent] = useState('')
|
||||||
const [applyLoading, setApplyLoading] = useState(false)
|
const [applyLoading, setApplyLoading] = useState(false)
|
||||||
@@ -80,8 +84,11 @@ export default function K8sResourceList() {
|
|||||||
useEffect(() => {
|
useEffect(() => {
|
||||||
if (kubeconfig) {
|
if (kubeconfig) {
|
||||||
fetchResources()
|
fetchResources()
|
||||||
|
if (selectedKind.key !== 'namespace' && selectedKind.key !== 'pv') {
|
||||||
|
fetchNamespaces()
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}, [selectedKind, namespace])
|
}, [selectedKind, namespace, nameFilter])
|
||||||
|
|
||||||
const fetchKubeconfig = async () => {
|
const fetchKubeconfig = async () => {
|
||||||
try {
|
try {
|
||||||
@@ -222,6 +229,9 @@ export default function K8sResourceList() {
|
|||||||
if (namespace && selectedKind.key !== 'namespace' && selectedKind.key !== 'pv') {
|
if (namespace && selectedKind.key !== 'namespace' && selectedKind.key !== 'pv') {
|
||||||
url.searchParams.set('namespace', namespace)
|
url.searchParams.set('namespace', namespace)
|
||||||
}
|
}
|
||||||
|
if (nameFilter) {
|
||||||
|
url.searchParams.set('name', nameFilter)
|
||||||
|
}
|
||||||
|
|
||||||
const res = await fetch(url.toString())
|
const res = await fetch(url.toString())
|
||||||
if (!res.ok) throw new Error(`HTTP ${res.status}`)
|
if (!res.ok) throw new Error(`HTTP ${res.status}`)
|
||||||
@@ -234,6 +244,18 @@ export default function K8sResourceList() {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
const fetchNamespaces = async () => {
|
||||||
|
try {
|
||||||
|
const res = await fetch('/api/v1/k8s/namespace/list')
|
||||||
|
if (!res.ok) throw new Error(`HTTP ${res.status}`)
|
||||||
|
const result = await res.json()
|
||||||
|
const namespaceList = result.data?.items?.map((ns: any) => ns.metadata.name) || []
|
||||||
|
setNamespaces(namespaceList)
|
||||||
|
} catch (e: any) {
|
||||||
|
console.error('Failed to fetch namespaces:', e)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
const getResourceColumns = () => {
|
const getResourceColumns = () => {
|
||||||
switch (selectedKind.key) {
|
switch (selectedKind.key) {
|
||||||
case 'namespace':
|
case 'namespace':
|
||||||
@@ -412,13 +434,35 @@ export default function K8sResourceList() {
|
|||||||
</Box>
|
</Box>
|
||||||
|
|
||||||
{selectedKind.key !== 'namespace' && selectedKind.key !== 'pv' && (
|
{selectedKind.key !== 'namespace' && selectedKind.key !== 'pv' && (
|
||||||
<Box sx={{ mb: 2 }}>
|
<Box sx={{ mb: 2, display: 'flex', gap: 2 }}>
|
||||||
<TextField
|
<TextField
|
||||||
placeholder="Namespace (空则查询所有)"
|
select
|
||||||
|
label="Namespace"
|
||||||
value={namespace}
|
value={namespace}
|
||||||
onChange={(e) => setNamespace(e.target.value)}
|
onChange={(e) => setNamespace(e.target.value)}
|
||||||
size="small"
|
size="small"
|
||||||
sx={{ width: 300 }}
|
sx={{ width: 200 }}
|
||||||
|
SelectProps={{
|
||||||
|
displayEmpty: true,
|
||||||
|
}}
|
||||||
|
InputLabelProps={{ shrink: true }}
|
||||||
|
>
|
||||||
|
<MenuItem value="">
|
||||||
|
<em>所有命名空间</em>
|
||||||
|
</MenuItem>
|
||||||
|
{namespaces.map((ns) => (
|
||||||
|
<MenuItem key={ns} value={ns}>
|
||||||
|
{ns}
|
||||||
|
</MenuItem>
|
||||||
|
))}
|
||||||
|
</TextField>
|
||||||
|
<TextField
|
||||||
|
label="名称过滤"
|
||||||
|
placeholder="按名称过滤"
|
||||||
|
value={nameFilter}
|
||||||
|
onChange={(e) => setNameFilter(e.target.value)}
|
||||||
|
size="small"
|
||||||
|
sx={{ width: 200 }}
|
||||||
/>
|
/>
|
||||||
</Box>
|
</Box>
|
||||||
)}
|
)}
|
||||||
|
|||||||
@@ -6,12 +6,14 @@ import (
|
|||||||
"encoding/json"
|
"encoding/json"
|
||||||
"fmt"
|
"fmt"
|
||||||
"io"
|
"io"
|
||||||
|
"strings"
|
||||||
|
|
||||||
"gitea.loveuer.com/loveuer/cluster/internal/model"
|
"gitea.loveuer.com/loveuer/cluster/internal/model"
|
||||||
"gitea.loveuer.com/loveuer/cluster/pkg/resp"
|
"gitea.loveuer.com/loveuer/cluster/pkg/resp"
|
||||||
"gitea.loveuer.com/loveuer/cluster/pkg/store"
|
"gitea.loveuer.com/loveuer/cluster/pkg/store"
|
||||||
"github.com/gofiber/fiber/v3"
|
"github.com/gofiber/fiber/v3"
|
||||||
"gorm.io/gorm"
|
"gorm.io/gorm"
|
||||||
|
appsv1 "k8s.io/api/apps/v1"
|
||||||
corev1 "k8s.io/api/core/v1"
|
corev1 "k8s.io/api/core/v1"
|
||||||
"k8s.io/apimachinery/pkg/apis/meta/v1/unstructured"
|
"k8s.io/apimachinery/pkg/apis/meta/v1/unstructured"
|
||||||
"k8s.io/apimachinery/pkg/runtime/schema"
|
"k8s.io/apimachinery/pkg/runtime/schema"
|
||||||
@@ -93,6 +95,7 @@ func K8sNamespaceList(ctx context.Context, db *gorm.DB, store store.Store) fiber
|
|||||||
func K8sDeploymentList(ctx context.Context, db *gorm.DB, store store.Store) fiber.Handler {
|
func K8sDeploymentList(ctx context.Context, db *gorm.DB, store store.Store) fiber.Handler {
|
||||||
return func(c fiber.Ctx) error {
|
return func(c fiber.Ctx) error {
|
||||||
namespace := c.Query("namespace", "")
|
namespace := c.Query("namespace", "")
|
||||||
|
name := c.Query("name", "")
|
||||||
|
|
||||||
clientset, err := getK8sClient(db)
|
clientset, err := getK8sClient(db)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
@@ -104,8 +107,20 @@ func K8sDeploymentList(ctx context.Context, db *gorm.DB, store store.Store) fibe
|
|||||||
return resp.R500(c, "", nil, err)
|
return resp.R500(c, "", nil, err)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// Filter by name if provided
|
||||||
|
var filtered []appsv1.Deployment
|
||||||
|
if name != "" {
|
||||||
|
for _, deployment := range deployments.Items {
|
||||||
|
if strings.Contains(deployment.Name, name) {
|
||||||
|
filtered = append(filtered, deployment)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
filtered = deployments.Items
|
||||||
|
}
|
||||||
|
|
||||||
return resp.R200(c, map[string]any{
|
return resp.R200(c, map[string]any{
|
||||||
"items": deployments.Items,
|
"items": filtered,
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -113,6 +128,7 @@ func K8sDeploymentList(ctx context.Context, db *gorm.DB, store store.Store) fibe
|
|||||||
func K8sStatefulSetList(ctx context.Context, db *gorm.DB, store store.Store) fiber.Handler {
|
func K8sStatefulSetList(ctx context.Context, db *gorm.DB, store store.Store) fiber.Handler {
|
||||||
return func(c fiber.Ctx) error {
|
return func(c fiber.Ctx) error {
|
||||||
namespace := c.Query("namespace", "")
|
namespace := c.Query("namespace", "")
|
||||||
|
name := c.Query("name", "")
|
||||||
|
|
||||||
clientset, err := getK8sClient(db)
|
clientset, err := getK8sClient(db)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
@@ -124,8 +140,20 @@ func K8sStatefulSetList(ctx context.Context, db *gorm.DB, store store.Store) fib
|
|||||||
return resp.R500(c, "", nil, err)
|
return resp.R500(c, "", nil, err)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// Filter by name if provided
|
||||||
|
var filtered []appsv1.StatefulSet
|
||||||
|
if name != "" {
|
||||||
|
for _, statefulset := range statefulsets.Items {
|
||||||
|
if strings.Contains(statefulset.Name, name) {
|
||||||
|
filtered = append(filtered, statefulset)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
filtered = statefulsets.Items
|
||||||
|
}
|
||||||
|
|
||||||
return resp.R200(c, map[string]any{
|
return resp.R200(c, map[string]any{
|
||||||
"items": statefulsets.Items,
|
"items": filtered,
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -133,6 +161,7 @@ func K8sStatefulSetList(ctx context.Context, db *gorm.DB, store store.Store) fib
|
|||||||
func K8sConfigMapList(ctx context.Context, db *gorm.DB, store store.Store) fiber.Handler {
|
func K8sConfigMapList(ctx context.Context, db *gorm.DB, store store.Store) fiber.Handler {
|
||||||
return func(c fiber.Ctx) error {
|
return func(c fiber.Ctx) error {
|
||||||
namespace := c.Query("namespace", "")
|
namespace := c.Query("namespace", "")
|
||||||
|
name := c.Query("name", "")
|
||||||
|
|
||||||
clientset, err := getK8sClient(db)
|
clientset, err := getK8sClient(db)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
@@ -144,8 +173,20 @@ func K8sConfigMapList(ctx context.Context, db *gorm.DB, store store.Store) fiber
|
|||||||
return resp.R500(c, "", nil, err)
|
return resp.R500(c, "", nil, err)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// Filter by name if provided
|
||||||
|
var filtered []corev1.ConfigMap
|
||||||
|
if name != "" {
|
||||||
|
for _, configmap := range configmaps.Items {
|
||||||
|
if strings.Contains(configmap.Name, name) {
|
||||||
|
filtered = append(filtered, configmap)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
filtered = configmaps.Items
|
||||||
|
}
|
||||||
|
|
||||||
return resp.R200(c, map[string]any{
|
return resp.R200(c, map[string]any{
|
||||||
"items": configmaps.Items,
|
"items": filtered,
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -153,6 +194,7 @@ func K8sConfigMapList(ctx context.Context, db *gorm.DB, store store.Store) fiber
|
|||||||
func K8sPodList(ctx context.Context, db *gorm.DB, store store.Store) fiber.Handler {
|
func K8sPodList(ctx context.Context, db *gorm.DB, store store.Store) fiber.Handler {
|
||||||
return func(c fiber.Ctx) error {
|
return func(c fiber.Ctx) error {
|
||||||
namespace := c.Query("namespace", "")
|
namespace := c.Query("namespace", "")
|
||||||
|
name := c.Query("name", "")
|
||||||
|
|
||||||
clientset, err := getK8sClient(db)
|
clientset, err := getK8sClient(db)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
@@ -164,8 +206,20 @@ func K8sPodList(ctx context.Context, db *gorm.DB, store store.Store) fiber.Handl
|
|||||||
return resp.R500(c, "", nil, err)
|
return resp.R500(c, "", nil, err)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// Filter by name if provided
|
||||||
|
var filtered []corev1.Pod
|
||||||
|
if name != "" {
|
||||||
|
for _, pod := range pods.Items {
|
||||||
|
if strings.Contains(pod.Name, name) {
|
||||||
|
filtered = append(filtered, pod)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
filtered = pods.Items
|
||||||
|
}
|
||||||
|
|
||||||
return resp.R200(c, map[string]any{
|
return resp.R200(c, map[string]any{
|
||||||
"items": pods.Items,
|
"items": filtered,
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -191,6 +245,7 @@ func K8sPVList(ctx context.Context, db *gorm.DB, store store.Store) fiber.Handle
|
|||||||
func K8sPVCList(ctx context.Context, db *gorm.DB, store store.Store) fiber.Handler {
|
func K8sPVCList(ctx context.Context, db *gorm.DB, store store.Store) fiber.Handler {
|
||||||
return func(c fiber.Ctx) error {
|
return func(c fiber.Ctx) error {
|
||||||
namespace := c.Query("namespace", "")
|
namespace := c.Query("namespace", "")
|
||||||
|
name := c.Query("name", "")
|
||||||
|
|
||||||
clientset, err := getK8sClient(db)
|
clientset, err := getK8sClient(db)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
@@ -202,8 +257,20 @@ func K8sPVCList(ctx context.Context, db *gorm.DB, store store.Store) fiber.Handl
|
|||||||
return resp.R500(c, "", nil, err)
|
return resp.R500(c, "", nil, err)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// Filter by name if provided
|
||||||
|
var filtered []corev1.PersistentVolumeClaim
|
||||||
|
if name != "" {
|
||||||
|
for _, pvc := range pvcs.Items {
|
||||||
|
if strings.Contains(pvc.Name, name) {
|
||||||
|
filtered = append(filtered, pvc)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
filtered = pvcs.Items
|
||||||
|
}
|
||||||
|
|
||||||
return resp.R200(c, map[string]any{
|
return resp.R200(c, map[string]any{
|
||||||
"items": pvcs.Items,
|
"items": filtered,
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -211,6 +278,7 @@ func K8sPVCList(ctx context.Context, db *gorm.DB, store store.Store) fiber.Handl
|
|||||||
func K8sServiceList(ctx context.Context, db *gorm.DB, store store.Store) fiber.Handler {
|
func K8sServiceList(ctx context.Context, db *gorm.DB, store store.Store) fiber.Handler {
|
||||||
return func(c fiber.Ctx) error {
|
return func(c fiber.Ctx) error {
|
||||||
namespace := c.Query("namespace", "")
|
namespace := c.Query("namespace", "")
|
||||||
|
name := c.Query("name", "")
|
||||||
|
|
||||||
clientset, err := getK8sClient(db)
|
clientset, err := getK8sClient(db)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
@@ -222,8 +290,20 @@ func K8sServiceList(ctx context.Context, db *gorm.DB, store store.Store) fiber.H
|
|||||||
return resp.R500(c, "", nil, err)
|
return resp.R500(c, "", nil, err)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// Filter by name if provided
|
||||||
|
var filtered []corev1.Service
|
||||||
|
if name != "" {
|
||||||
|
for _, service := range services.Items {
|
||||||
|
if strings.Contains(service.Name, name) {
|
||||||
|
filtered = append(filtered, service)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
filtered = services.Items
|
||||||
|
}
|
||||||
|
|
||||||
return resp.R200(c, map[string]any{
|
return resp.R200(c, map[string]any{
|
||||||
"items": services.Items,
|
"items": filtered,
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
Reference in New Issue
Block a user