From 4a1fc13ad8c069198b958c2678b3b85e2f283aed Mon Sep 17 00:00:00 2001 From: Jeroen Nijhuis Date: Tue, 16 Jan 2024 23:33:37 +0100 Subject: [PATCH] feat: re-worked navigation to show available resources + basic support for non-implemented object views --- package-lock.json | 14 ++++ package.json | 1 + src-tauri/src/main.rs | 55 +++++++++++++ src/components/Navigation.vue | 137 ++++++++++++++++++++++++++++++- src/components/tables/generic.ts | 18 ++++ src/router.ts | 5 ++ src/services/Kubernetes.ts | 30 +++++++ src/views/GenericResource.vue | 97 ++++++++++++++++++++++ 8 files changed, 356 insertions(+), 1 deletion(-) create mode 100644 src/components/tables/generic.ts create mode 100644 src/views/GenericResource.vue diff --git a/package-lock.json b/package-lock.json index e2ca4d7..133a1fa 100644 --- a/package-lock.json +++ b/package-lock.json @@ -21,6 +21,7 @@ "js-yaml": "^4.1.0", "monaco-editor": "^0.45.0", "monaco-languageclient": "^7.3.0", + "pluralize": "^8.0.0", "radix-vue": "^1.2.7", "tailwind-merge": "^2.1.0", "tailwindcss-animate": "^1.0.7", @@ -10351,6 +10352,14 @@ "pathe": "^1.1.0" } }, + "node_modules/pluralize": { + "version": "8.0.0", + "resolved": "https://registry.npmjs.org/pluralize/-/pluralize-8.0.0.tgz", + "integrity": "sha512-Nc3IT5yHzflTfbjgqWcCPpo7DaKy4FnpB0l/zCAW0Tc7jxAiuqSxHasntB3D7887LSrA93kDJ9IXovxJYxyLCA==", + "engines": { + "node": ">=4" + } + }, "node_modules/postcss": { "version": "8.4.25", "resolved": "https://registry.npmjs.org/postcss/-/postcss-8.4.25.tgz", @@ -20475,6 +20484,11 @@ "pathe": "^1.1.0" } }, + "pluralize": { + "version": "8.0.0", + "resolved": "https://registry.npmjs.org/pluralize/-/pluralize-8.0.0.tgz", + "integrity": "sha512-Nc3IT5yHzflTfbjgqWcCPpo7DaKy4FnpB0l/zCAW0Tc7jxAiuqSxHasntB3D7887LSrA93kDJ9IXovxJYxyLCA==" + }, "postcss": { "version": "8.4.25", "resolved": "https://registry.npmjs.org/postcss/-/postcss-8.4.25.tgz", diff --git a/package.json b/package.json index c91c0b2..83c5a10 100644 --- a/package.json +++ b/package.json @@ -27,6 +27,7 @@ "js-yaml": "^4.1.0", "monaco-editor": "^0.45.0", "monaco-languageclient": "^7.3.0", + "pluralize": "^8.0.0", "radix-vue": "^1.2.7", "tailwind-merge": "^2.1.0", "tailwindcss-animate": "^1.0.7", diff --git a/src-tauri/src/main.rs b/src-tauri/src/main.rs index 534887e..f0c5c70 100644 --- a/src-tauri/src/main.rs +++ b/src-tauri/src/main.rs @@ -5,6 +5,7 @@ use either::Either; use istio_api_rs::networking::v1beta1::virtual_service::VirtualService; use k8s_openapi::api::batch::v1::{CronJob, Job}; use k8s_openapi::api::networking::v1::Ingress; +use k8s_openapi::apimachinery::pkg::apis::meta::v1::{APIGroup, APIResource}; use tauri::Manager; use k8s_openapi::api::apps::v1::Deployment; @@ -509,6 +510,56 @@ async fn replace_persistentvolumeclaim( .map_err(|err| SerializableKubeError::from(err)); } +#[tauri::command] +async fn get_core_api_versions(context: &str) -> Result, SerializableKubeError> { + let client = client_with_context(context).await?; + + return client + .list_core_api_versions() + .await + .map(|api_versions| api_versions.versions) + .map_err(|err| SerializableKubeError::from(err)); +} + +#[tauri::command] +async fn get_core_api_resources( + context: &str, + core_api_version: &str, +) -> Result, SerializableKubeError> { + let client = client_with_context(context).await?; + + return client + .list_core_api_resources(core_api_version) + .await + .map(|api_resources| api_resources.resources) + .map_err(|err| SerializableKubeError::from(err)); +} + +#[tauri::command] +async fn get_api_groups(context: &str) -> Result, SerializableKubeError> { + let client = client_with_context(context).await?; + + return client + .list_api_groups() + .await + .map(|api_groups| api_groups.groups) + .map_err(|err| SerializableKubeError::from(err)); +} + +#[tauri::command] +async fn get_api_group_resources( + context: &str, + api_group_version: &str, +) -> Result, SerializableKubeError> { + let client = client_with_context(context).await?; + + return client + .list_api_group_resources(api_group_version) + .await + .map(|api_resources| api_resources.resources) + .map_err(|err| SerializableKubeError::from(err)); +} + struct TerminalSession { writer: Arc>>, } @@ -618,6 +669,10 @@ fn main() { list_contexts, get_current_context, list_namespaces, + get_core_api_versions, + get_core_api_resources, + get_api_groups, + get_api_group_resources, list_pods, get_pod, delete_pod, diff --git a/src/components/Navigation.vue b/src/components/Navigation.vue index 2d6f403..2247ada 100644 --- a/src/components/Navigation.vue +++ b/src/components/Navigation.vue @@ -3,6 +3,114 @@ import ContextSwitcher from "./ContextSwitcher.vue"; import NavigationGroup from "./NavigationGroup.vue"; import NavigationItem from "./NavigationItem.vue"; import { ScrollArea } from "@/components/ui/scroll-area"; +import { Kubernetes } from "@/services/Kubernetes"; +import { KubeContextStateKey } from "@/providers/KubeContextProvider"; +import { injectStrict } from "@/lib/utils"; +import { V1APIResource } from "@kubernetes/client-node"; +import pluralize from "pluralize"; + +const { context } = injectStrict(KubeContextStateKey); + +interface NavigationGroup { + title: string; + coreResources: string[]; + apiGroupResources: string[]; +} + +const navigationGroups: NavigationGroup[] = [ + { + title: "Workloads", + coreResources: ["pods"], + apiGroupResources: ["apps", "batch"], + }, + { + title: "Config", + coreResources: ["configmaps", "resourcequotas", "secrets"], + apiGroupResources: [""], + }, + { + title: "Network", + coreResources: ["endpoints", "services", "endpointslices"], + apiGroupResources: ["networking.k8s.io"], + }, + { + title: "Storage", + coreResources: ["persistentvolumeclaims"], + apiGroupResources: ["storage.k8s.io"], + }, + { + title: "Scaling", + coreResources: [], + apiGroupResources: ["autoscaling"], + }, + { + title: "Policies", + coreResources: ["poddisruptionbudgets", "limitranges"], + apiGroupResources: [], + }, + { + title: "Access Control", + coreResources: ["serviceaccounts"], + apiGroupResources: ["rbac.authorization.k8s.io"], + }, +]; + +const clusterResources = ref>(new Map()); + +const getCoreResourcesForGroup = (group: NavigationGroup) => { + return Array.from(clusterResources.value.values()) + .flat() + .filter((resource) => group.coreResources.includes(resource.name)); +}; + +const getApiResourcesForGroup = (group: NavigationGroup) => { + return Array.from(clusterResources.value.keys()) + .filter((key) => group.apiGroupResources.includes(key)) + .map((key) => clusterResources.value.get(key)!) + .flat() + .filter((resource) => resource.singularName !== ""); +}; + +const formatResourceKind = (kind: string) => { + return pluralize(kind); +}; + +onMounted(() => { + Kubernetes.getCoreApiVersions(context.value).then((results) => { + results.forEach((version) => { + Kubernetes.getCoreApiResources(context.value, version).then( + (resources) => { + clusterResources.value.set( + version, + resources.filter((r) => r.namespaced) + ); + } + ); + }); + }); + + Kubernetes.getApiGroups(context.value) + .then((results) => { + results.forEach((group) => { + Kubernetes.getApiGroupResources( + context.value, + group.preferredVersion?.groupVersion ?? "" + ) + .then((resources) => { + clusterResources.value.set( + group.name, + resources.filter((r) => r.namespaced) + ); + }) + .catch((error) => { + console.error(error); + }); + }); + }) + .catch((error) => { + console.error(error); + }); +});