From 4fd4aa813892bfde62e8608d56ec207b03275bfb Mon Sep 17 00:00:00 2001 From: Matt Hook Date: Fri, 7 Jun 2024 14:08:08 +1200 Subject: [PATCH] Revert "feat(dashboard): dashboard api [EE-7111]" (#11906) --- api/http/handler/kubernetes/dashboard.go | 37 --- api/http/handler/kubernetes/handler.go | 1 - api/http/models/kubernetes/dashboard.go | 13 - api/internal/concurrent/concurrent.go | 144 ---------- api/kubernetes/cli/applications.go | 154 ----------- api/kubernetes/cli/client.go | 2 - api/kubernetes/cli/dashboard.go | 258 ------------------ .../kubernetes/dashboard/DashboardView.tsx | 94 +++++-- .../dashboard/queries/getDashboardQuery.ts | 43 --- app/react/kubernetes/dashboard/types.ts | 9 - 10 files changed, 69 insertions(+), 686 deletions(-) delete mode 100644 api/http/handler/kubernetes/dashboard.go delete mode 100644 api/http/models/kubernetes/dashboard.go delete mode 100644 api/internal/concurrent/concurrent.go delete mode 100644 api/kubernetes/cli/applications.go delete mode 100644 api/kubernetes/cli/dashboard.go delete mode 100644 app/react/kubernetes/dashboard/queries/getDashboardQuery.ts delete mode 100644 app/react/kubernetes/dashboard/types.ts diff --git a/api/http/handler/kubernetes/dashboard.go b/api/http/handler/kubernetes/dashboard.go deleted file mode 100644 index bb4787bd3..000000000 --- a/api/http/handler/kubernetes/dashboard.go +++ /dev/null @@ -1,37 +0,0 @@ -package kubernetes - -import ( - "net/http" - - httperror "github.com/portainer/portainer/pkg/libhttp/error" - "github.com/portainer/portainer/pkg/libhttp/response" -) - -// @id GetKubernetesDashboard -// @summary Get the dashboard summary data -// @description Get the dashboard summary data which is simply a count of a range of different commonly used kubernetes resources -// @description **Access policy**: authenticated -// @tags kubernetes -// @security ApiKeyAuth -// @security jwt -// @accept json -// @produce json -// @param id path int true "Environment (Endpoint) identifier" -// @success 200 {array} kubernetes.K8sDashboard "Success" -// @failure 400 "Invalid request" -// @failure 500 "Server error" -// @router /kubernetes/{id}/dashboard [get] -func (handler *Handler) getKubernetesDashboard(w http.ResponseWriter, r *http.Request) *httperror.HandlerError { - - cli, httpErr := handler.getProxyKubeClient(r) - if httpErr != nil { - return httpErr - } - - dashboard, err := cli.GetDashboard() - if err != nil { - return httperror.InternalServerError("Unable to retrieve dashboard data", err) - } - - return response.JSON(w, dashboard) -} diff --git a/api/http/handler/kubernetes/handler.go b/api/http/handler/kubernetes/handler.go index 9951cbc52..9b429ada2 100644 --- a/api/http/handler/kubernetes/handler.go +++ b/api/http/handler/kubernetes/handler.go @@ -52,7 +52,6 @@ func NewHandler(bouncer security.BouncerService, authorizationService *authoriza endpointRouter.Use(kubeOnlyMiddleware) endpointRouter.Use(h.kubeClientMiddleware) - endpointRouter.Handle("/dashboard", httperror.LoggerHandler(h.getKubernetesDashboard)).Methods(http.MethodGet) endpointRouter.Handle("/nodes_limits", httperror.LoggerHandler(h.getKubernetesNodesLimits)).Methods(http.MethodGet) endpointRouter.Handle("/max_resource_limits", httperror.LoggerHandler(h.getKubernetesMaxResourceLimits)).Methods(http.MethodGet) endpointRouter.Handle("/metrics/nodes", httperror.LoggerHandler(h.getKubernetesMetricsForAllNodes)).Methods(http.MethodGet) diff --git a/api/http/models/kubernetes/dashboard.go b/api/http/models/kubernetes/dashboard.go deleted file mode 100644 index 544bac265..000000000 --- a/api/http/models/kubernetes/dashboard.go +++ /dev/null @@ -1,13 +0,0 @@ -package kubernetes - -type ( - K8sDashboard struct { - NamespacesCount int64 `json:"namespacesCount"` - ApplicationsCount int64 `json:"applicationsCount"` - ServicesCount int64 `json:"servicesCount"` - IngressesCount int64 `json:"ingressesCount"` - ConfigMapsCount int64 `json:"configMapsCount"` - SecretsCount int64 `json:"secretsCount"` - VolumesCount int64 `json:"volumesCount"` - } -) diff --git a/api/internal/concurrent/concurrent.go b/api/internal/concurrent/concurrent.go deleted file mode 100644 index 47b875b7f..000000000 --- a/api/internal/concurrent/concurrent.go +++ /dev/null @@ -1,144 +0,0 @@ -// Package concurrent provides utilities for running multiple functions concurrently in Go. -// For example, many kubernetes calls can take a while to fulfill. Oftentimes in Portainer -// we need to get a list of objects from multiple kubernetes REST APIs. We can often call these -// apis concurrently to speed up the response time. -// This package provides a clean way to do just that. -// -// Examples: -// The ConfigMaps and Secrets function converted using concurrent.Run. -/* - -// GetConfigMapsAndSecrets gets all the ConfigMaps AND all the Secrets for a -// given namespace in a k8s endpoint. The result is a list of both config maps -// and secrets. The IsSecret boolean property indicates if a given struct is a -// secret or configmap. -func (kcl *KubeClient) GetConfigMapsAndSecrets(namespace string) ([]models.K8sConfigMapOrSecret, error) { - - // use closures to capture the current kube client and namespace by declaring wrapper functions - // that match the interface signature for concurrent.Func - - listConfigMaps := func(ctx context.Context) (interface{}, error) { - return kcl.cli.CoreV1().ConfigMaps(namespace).List(context.Background(), meta.ListOptions{}) - } - - listSecrets := func(ctx context.Context) (interface{}, error) { - return kcl.cli.CoreV1().Secrets(namespace).List(context.Background(), meta.ListOptions{}) - } - - // run the functions concurrently and wait for results. We can also pass in a context to cancel. - // e.g. Deadline timer. - results, err := concurrent.Run(context.TODO(), listConfigMaps, listSecrets) - if err != nil { - return nil, err - } - - var configMapList *core.ConfigMapList - var secretList *core.SecretList - for _, r := range results { - switch v := r.Result.(type) { - case *core.ConfigMapList: - configMapList = v - case *core.SecretList: - secretList = v - } - } - - // TODO: Applications - var combined []models.K8sConfigMapOrSecret - for _, m := range configMapList.Items { - var cm models.K8sConfigMapOrSecret - cm.UID = string(m.UID) - cm.Name = m.Name - cm.Namespace = m.Namespace - cm.Annotations = m.Annotations - cm.Data = m.Data - cm.CreationDate = m.CreationTimestamp.Time.UTC().Format(time.RFC3339) - combined = append(combined, cm) - } - - for _, s := range secretList.Items { - var secret models.K8sConfigMapOrSecret - secret.UID = string(s.UID) - secret.Name = s.Name - secret.Namespace = s.Namespace - secret.Annotations = s.Annotations - secret.Data = msbToMss(s.Data) - secret.CreationDate = s.CreationTimestamp.Time.UTC().Format(time.RFC3339) - secret.IsSecret = true - secret.SecretType = string(s.Type) - combined = append(combined, secret) - } - - return combined, nil -} - -*/ - -package concurrent - -import ( - "context" - "sync" -) - -// Result contains the result and any error returned from running a client task function -type Result struct { - Result any // the result of running the task function - Err error // any error that occurred while running the task function -} - -// Func is a function returns a result or error -type Func func(ctx context.Context) (any, error) - -// Run runs a list of functions returns the results -func Run(ctx context.Context, maxConcurrency int, tasks ...Func) ([]Result, error) { - var wg sync.WaitGroup - resultsChan := make(chan Result, len(tasks)) - taskChan := make(chan Func, len(tasks)) - - localCtx, cancelCtx := context.WithCancel(ctx) - defer cancelCtx() - - runTask := func() { - defer wg.Done() - for fn := range taskChan { - result, err := fn(localCtx) - resultsChan <- Result{Result: result, Err: err} - } - } - - // Set maxConcurrency to the number of tasks if zero or negative - if maxConcurrency <= 0 { - maxConcurrency = len(tasks) - } - - // Start worker goroutines - for i := 0; i < maxConcurrency; i++ { - wg.Add(1) - go runTask() - } - - // Add tasks to the task channel - for _, fn := range tasks { - taskChan <- fn - } - - // Close the task channel to signal workers to stop when all tasks are done - close(taskChan) - - // Wait for all workers to complete - wg.Wait() - close(resultsChan) - - // Collect the results and cancel on error - results := make([]Result, 0, len(tasks)) - for r := range resultsChan { - if r.Err != nil { - cancelCtx() - return nil, r.Err - } - results = append(results, r) - } - - return results, nil -} diff --git a/api/kubernetes/cli/applications.go b/api/kubernetes/cli/applications.go deleted file mode 100644 index 875c4c779..000000000 --- a/api/kubernetes/cli/applications.go +++ /dev/null @@ -1,154 +0,0 @@ -package cli - -import ( - "context" - "strings" - - models "github.com/portainer/portainer/api/http/models/kubernetes" - metav1 "k8s.io/apimachinery/pkg/apis/meta/v1" -) - -// GetApplications gets a list of kubernetes workloads (or applications) by kind. If Kind is not specified, gets the all -func (kcl *KubeClient) GetApplications(namespace, kind string) ([]models.K8sApplication, error) { - applicationList := []models.K8sApplication{} - listOpts := metav1.ListOptions{} - - if kind == "" || strings.EqualFold(kind, "deployment") { - deployments, err := kcl.cli.AppsV1().Deployments(namespace).List(context.TODO(), listOpts) - if err != nil { - return nil, err - } - - for _, d := range deployments.Items { - applicationList = append(applicationList, models.K8sApplication{ - UID: string(d.UID), - Name: d.Name, - Namespace: d.Namespace, - Kind: "Deployment", - Labels: d.Labels, - }) - } - } - - if kind == "" || strings.EqualFold(kind, "statefulset") { - statefulSets, err := kcl.cli.AppsV1().StatefulSets(namespace).List(context.TODO(), listOpts) - if err != nil { - return nil, err - } - - for _, s := range statefulSets.Items { - applicationList = append(applicationList, models.K8sApplication{ - UID: string(s.UID), - Name: s.Name, - Namespace: s.Namespace, - Kind: "StatefulSet", - Labels: s.Labels, - }) - } - } - - if kind == "" || strings.EqualFold(kind, "daemonset") { - daemonSets, err := kcl.cli.AppsV1().DaemonSets(namespace).List(context.TODO(), listOpts) - if err != nil { - return nil, err - } - - for _, d := range daemonSets.Items { - applicationList = append(applicationList, models.K8sApplication{ - UID: string(d.UID), - Name: d.Name, - Namespace: d.Namespace, - Kind: "DaemonSet", - Labels: d.Labels, - }) - } - } - - if kind == "" || strings.EqualFold(kind, "nakedpods") { - pods, _ := kcl.cli.CoreV1().Pods(namespace).List(context.Background(), metav1.ListOptions{}) - for _, pod := range pods.Items { - naked := false - if len(pod.OwnerReferences) == 0 { - naked = true - } else { - managed := false - loop: - for _, ownerRef := range pod.OwnerReferences { - switch ownerRef.Kind { - case "Deployment", "DaemonSet", "ReplicaSet": - managed = true - break loop - } - } - - if !managed { - naked = true - } - } - - if naked { - applicationList = append(applicationList, models.K8sApplication{ - UID: string(pod.UID), - Name: pod.Name, - Namespace: pod.Namespace, - Kind: "Pod", - Labels: pod.Labels, - }) - } - } - } - - return applicationList, nil -} - -// GetApplication gets a kubernetes workload (application) by kind and name. If Kind is not specified, gets the all -func (kcl *KubeClient) GetApplication(namespace, kind, name string) (models.K8sApplication, error) { - - opts := metav1.GetOptions{} - - switch strings.ToLower(kind) { - case "deployment": - d, err := kcl.cli.AppsV1().Deployments(namespace).Get(context.TODO(), name, opts) - if err != nil { - return models.K8sApplication{}, err - } - - return models.K8sApplication{ - UID: string(d.UID), - Name: d.Name, - Namespace: d.Namespace, - Kind: "Deployment", - Labels: d.Labels, - }, nil - - case "statefulset": - s, err := kcl.cli.AppsV1().StatefulSets(namespace).Get(context.TODO(), name, opts) - if err != nil { - return models.K8sApplication{}, err - } - - return models.K8sApplication{ - UID: string(s.UID), - Name: s.Name, - Namespace: s.Namespace, - Kind: "StatefulSet", - Labels: s.Labels, - }, nil - - case "daemonset": - d, err := kcl.cli.AppsV1().DaemonSets(namespace).Get(context.TODO(), name, opts) - if err != nil { - return models.K8sApplication{}, err - } - - return models.K8sApplication{ - UID: string(d.UID), - Name: d.Name, - Namespace: d.Namespace, - Kind: "DaemonSet", - Labels: d.Labels, - }, nil - } - - return models.K8sApplication{}, nil -} diff --git a/api/kubernetes/cli/client.go b/api/kubernetes/cli/client.go index 70cd4fce1..11074aab5 100644 --- a/api/kubernetes/cli/client.go +++ b/api/kubernetes/cli/client.go @@ -25,8 +25,6 @@ const ( DefaultKubeClientBurst = 100 ) -const maxConcurrency = 30 - type ( // ClientFactory is used to create Kubernetes clients ClientFactory struct { diff --git a/api/kubernetes/cli/dashboard.go b/api/kubernetes/cli/dashboard.go deleted file mode 100644 index c96910365..000000000 --- a/api/kubernetes/cli/dashboard.go +++ /dev/null @@ -1,258 +0,0 @@ -package cli - -import ( - "context" - - models "github.com/portainer/portainer/api/http/models/kubernetes" - "github.com/portainer/portainer/api/internal/concurrent" - - "k8s.io/apimachinery/pkg/api/errors" - v1 "k8s.io/apimachinery/pkg/apis/meta/v1" -) - -func (kcl *KubeClient) GetDashboard() (models.K8sDashboard, error) { - dashboardData := models.K8sDashboard{} - - // Get a list of all the namespaces first - namespaces, err := kcl.cli.CoreV1().Namespaces().List(context.TODO(), v1.ListOptions{}) - if err != nil { - return dashboardData, err - } - - getNamespaceCounts := func(namespace string) concurrent.Func { - return func(ctx context.Context) (any, error) { - data := models.K8sDashboard{} - - // apps (deployments, statefulsets, daemonsets) - applicationCount, err := getApplicationsCount(ctx, kcl, namespace) - if err != nil { - // skip namespaces we're not allowed access to. But don't return an error so that we - // can still count the other namespaces. Returning an error here will stop concurrent.Run - if errors.IsForbidden(err) { - return nil, nil - } - return nil, err - } - data.ApplicationsCount = applicationCount - - // services - serviceCount, err := getServicesCount(ctx, kcl, namespace) - if err != nil { - return nil, err - } - data.ServicesCount = serviceCount - - // ingresses - ingressesCount, err := getIngressesCount(ctx, kcl, namespace) - if err != nil { - return nil, err - } - data.IngressesCount = ingressesCount - - // configmaps - configMapCount, err := getConfigMapsCount(ctx, kcl, namespace) - if err != nil { - return nil, err - } - data.ConfigMapsCount = configMapCount - - // secrets - secretsCount, err := getSecretsCount(ctx, kcl, namespace) - if err != nil { - return nil, err - } - data.SecretsCount = secretsCount - - // volumes - volumesCount, err := getVolumesCount(ctx, kcl, namespace) - if err != nil { - return nil, err - } - data.VolumesCount = volumesCount - - // count this namespace for the user - data.NamespacesCount = 1 - - return data, nil - } - } - - dashboardTasks := make([]concurrent.Func, 0) - for _, ns := range namespaces.Items { - dashboardTasks = append(dashboardTasks, getNamespaceCounts(ns.Name)) - } - - // Fetch all the data for each namespace concurrently - results, err := concurrent.Run(context.TODO(), maxConcurrency, dashboardTasks...) - if err != nil { - return dashboardData, err - } - - // Sum up the results - for i := range results { - data, _ := results[i].Result.(models.K8sDashboard) - dashboardData.NamespacesCount += data.NamespacesCount - dashboardData.ApplicationsCount += data.ApplicationsCount - dashboardData.ServicesCount += data.ServicesCount - dashboardData.IngressesCount += data.IngressesCount - dashboardData.ConfigMapsCount += data.ConfigMapsCount - dashboardData.SecretsCount += data.SecretsCount - dashboardData.VolumesCount += data.VolumesCount - } - - return dashboardData, nil -} - -// Get applications excluding nakedpods -func getApplicationsCount(ctx context.Context, kcl *KubeClient, namespace string) (int64, error) { - options := v1.ListOptions{Limit: 1} - count := int64(0) - - // deployments - deployments, err := kcl.cli.AppsV1().Deployments(namespace).List(ctx, options) - if err != nil { - return 0, err - } - - if len(deployments.Items) > 0 { - count = 1 // first deployment - remainingItemsCount := deployments.GetRemainingItemCount() - if remainingItemsCount != nil { - count += *remainingItemsCount // add the remaining deployments if any - } - } - - // StatefulSets - statefulSets, err := kcl.cli.AppsV1().StatefulSets(namespace).List(ctx, options) - if err != nil { - return 0, err - } - - if len(statefulSets.Items) > 0 { - count += 1 // + first statefulset - remainingItemsCount := statefulSets.GetRemainingItemCount() - if remainingItemsCount != nil { - count += *remainingItemsCount // add the remaining statefulsets if any - } - } - - // Daemonsets - daemonsets, err := kcl.cli.AppsV1().DaemonSets(namespace).List(ctx, options) - if err != nil { - return 0, err - } - - if len(daemonsets.Items) > 0 { - count += 1 // + first daemonset - remainingItemsCount := daemonsets.GetRemainingItemCount() - if remainingItemsCount != nil { - count += *remainingItemsCount // add the remaining daemonsets if any - } - } - - // + (naked pods) - nakedPods, err := kcl.GetApplications(namespace, "nakedpods") - if err != nil { - return 0, err - } - - return count + int64(len(nakedPods)), nil -} - -// Get the total count of services for the given namespace -func getServicesCount(ctx context.Context, kcl *KubeClient, namespace string) (int64, error) { - options := v1.ListOptions{ - Limit: 1, - } - var count int64 = 0 - services, err := kcl.cli.CoreV1().Services(namespace).List(ctx, options) - if err != nil { - return 0, err - } - - if len(services.Items) > 0 { - count = 1 // first service - remainingItemsCount := services.GetRemainingItemCount() - if remainingItemsCount != nil { - count += *remainingItemsCount // add the remaining services if any - } - } - - return count, nil -} - -// Get the total count of ingresses for the given namespace -func getIngressesCount(ctx context.Context, kcl *KubeClient, namespace string) (int64, error) { - ingresses, err := kcl.cli.NetworkingV1().Ingresses(namespace).List(ctx, v1.ListOptions{Limit: 1}) - if err != nil { - return 0, err - } - - count := int64(0) - if len(ingresses.Items) > 0 { - count = 1 // first ingress - remainingItemsCount := ingresses.GetRemainingItemCount() - if remainingItemsCount != nil { - count += *remainingItemsCount // add the remaining ingresses if any - } - } - - return count, nil -} - -// Get the total count of configMaps for the given namespace -func getConfigMapsCount(ctx context.Context, kcl *KubeClient, namespace string) (int64, error) { - configMaps, err := kcl.cli.CoreV1().ConfigMaps(namespace).List(ctx, v1.ListOptions{Limit: 1}) - if err != nil { - return 0, err - } - - count := int64(0) - if len(configMaps.Items) > 0 { - count = 1 // first configmap - remainingItemsCount := configMaps.GetRemainingItemCount() - if remainingItemsCount != nil { - count += *remainingItemsCount // add the remaining configmaps if any - } - } - - return count, nil -} - -// Get the total count of secrets for the given namespace -func getSecretsCount(ctx context.Context, kcl *KubeClient, namespace string) (int64, error) { - secrets, err := kcl.cli.CoreV1().Secrets(namespace).List(ctx, v1.ListOptions{Limit: 1}) - if err != nil { - return 0, err - } - - count := int64(0) - if len(secrets.Items) > 0 { - count = 1 // first secret - remainingItemsCount := secrets.GetRemainingItemCount() - if remainingItemsCount != nil { - count += *remainingItemsCount // add the remaining secrets if any - } - } - - return count, nil -} - -// Get the total count of volumes for the given namespace -func getVolumesCount(ctx context.Context, kcl *KubeClient, namespace string) (int64, error) { - volumes, err := kcl.cli.CoreV1().PersistentVolumeClaims(namespace).List(ctx, v1.ListOptions{Limit: 1}) - if err != nil { - return 0, err - } - - count := int64(0) - if len(volumes.Items) > 0 { - count = 1 // first volume - remainingItemsCount := volumes.GetRemainingItemCount() - if remainingItemsCount != nil { - count += *remainingItemsCount // add the remaining volumes if any - } - } - - return count, nil -} diff --git a/app/react/kubernetes/dashboard/DashboardView.tsx b/app/react/kubernetes/dashboard/DashboardView.tsx index e8de4231d..5f459af2d 100644 --- a/app/react/kubernetes/dashboard/DashboardView.tsx +++ b/app/react/kubernetes/dashboard/DashboardView.tsx @@ -8,15 +8,47 @@ import { DashboardGrid } from '@@/DashboardItem/DashboardGrid'; import { DashboardItem } from '@@/DashboardItem/DashboardItem'; import { PageHeader } from '@@/PageHeader'; +import { useApplicationsQuery } from '../applications/application.queries'; +import { usePVCsQuery } from '../volumes/usePVCsQuery'; +import { useServicesForCluster } from '../services/service'; +import { useIngresses } from '../ingresses/queries'; +import { useConfigMapsForCluster } from '../configs/configmap.service'; +import { useSecretsForCluster } from '../configs/secret.service'; +import { useNamespacesQuery } from '../namespaces/queries/useNamespacesQuery'; + import { EnvironmentInfo } from './EnvironmentInfo'; -import { useGetDashboardQuery } from './queries/getDashboardQuery'; export function DashboardView() { const queryClient = useQueryClient(); const environmentId = useEnvironmentId(); - const dashboardQuery = useGetDashboardQuery(environmentId); - - const dashboard = dashboardQuery.data; + const { data: namespaces, ...namespacesQuery } = + useNamespacesQuery(environmentId); + const namespaceNames = namespaces && Object.keys(namespaces); + const { data: applications, ...applicationsQuery } = useApplicationsQuery( + environmentId, + namespaceNames + ); + const { data: pvcs, ...pvcsQuery } = usePVCsQuery( + environmentId, + namespaceNames + ); + const { data: services, ...servicesQuery } = useServicesForCluster( + environmentId, + namespaceNames, + { lookupApplications: false } + ); + const { data: ingresses, ...ingressesQuery } = useIngresses( + environmentId, + namespaceNames + ); + const { data: configMaps, ...configMapsQuery } = useConfigMapsForCluster( + environmentId, + namespaceNames + ); + const { data: secrets, ...secretsQuery } = useSecretsForCluster( + environmentId, + namespaceNames + ); return ( <> @@ -32,36 +64,42 @@ export function DashboardView() { - ['environments', environmentId, 'dashboard'] as const, -}; - -export function useGetDashboardQuery( - environmentId: EnvironmentId, - options?: { autoRefreshRate?: number } -) { - return useQuery( - queryKeys.list(environmentId), - async () => getDashboard(environmentId), - { - ...withError('Unable to get dashboard stats'), - refetchInterval() { - return options?.autoRefreshRate ?? false; - }, - } - ); -} - -async function getDashboard(environmentId: EnvironmentId) { - try { - const { data: dashboard } = await axios.get( - `kubernetes/${environmentId}/dashboard` - ); - - return dashboard; - } catch (e) { - throw parseAxiosError( - e, - 'Unable to get dashboard stats. Some counts may be inaccurate.' - ); - } -} diff --git a/app/react/kubernetes/dashboard/types.ts b/app/react/kubernetes/dashboard/types.ts deleted file mode 100644 index 165b2bd8f..000000000 --- a/app/react/kubernetes/dashboard/types.ts +++ /dev/null @@ -1,9 +0,0 @@ -export type K8sDashboard = { - namespacesCount: number; - applicationsCount: number; - servicesCount: number; - ingressesCount: number; - configMapsCount: number; - secretsCount: number; - volumesCount: number; -};