frontend/src/App.vue
<script lang="ts">
import { defineComponent } from 'vue';
import { SecretUpdateOptions } from './types/custom';
import NavigationPane from './components/NavigationPane.vue';
import DeploymentsList from './components/DeploymentsList.vue';
import StatefulSetsList from './components/StatefulsetsList.vue';
import PodsList from './components/PodsList.vue';
import ConfigMapsList from './components/ConfigMapsList.vue';
import IngressesList from './components/IngressesList.vue';
import NamespacesList from './components/NamespacesList.vue';
import SecretsList from './components/SecretsList.vue';
import PersistentVolumeClaimsList from './components/PersistentVolumeClaimsList.vue';
import PersistentVolumesList from './components/PersistentVolumesList.vue';
import BottomTabs from './components/BottomTabs.vue';
import { CustomPod } from './types/custom';
import { KubernetesPod } from './types/kubernetes';
import { kubernetesService } from './lib/kubernetes';
import {
KubernetesCluster,
KubernetesDeployment,
KubernetesStatefulSet,
KubernetesConfigMap,
KubernetesIngress,
KubernetesNamespace,
KubernetesSecret,
KubernetesPersistentVolumeClaim,
KubernetesPersistentVolume,
ApiResponse,
} from './types/kubernetes';
export default defineComponent({
components: {
NavigationPane,
DeploymentsList,
StatefulSetsList,
PodsList,
ConfigMapsList,
IngressesList,
NamespacesList,
SecretsList,
PersistentVolumeClaimsList,
PersistentVolumesList,
BottomTabs
},
computed: {
currentResourceComponent() {
if (this.selectedResource === 'pods') {
return 'PodsList';
} else if (this.selectedResource === 'deployments') {
return 'DeploymentsList';
} else if (this.selectedResource === 'statefulsets') {
return 'StatefulSetsList';
} else if (this.selectedResource === 'configmaps') {
return 'ConfigMapsList';
} else if (this.selectedResource === 'namespaces') {
return 'NamespacesList';
} else if (this.selectedResource === 'secrets') {
return 'SecretsList';
} else if (this.selectedResource === 'persistentvolumeclaims') {
return 'PersistentVolumeClaimsList';
} else if (this.selectedResource === 'persistentVolumes') {
return 'PersistentVolumesList';
} else {
return 'IngressesList';
}
}
},
data() {
return {
clusters: [] as KubernetesCluster[],
selectedCluster: null as KubernetesCluster | null,
selectedResource: null as string | null,
selectedResourceItem: null as any | null,
deployments: [] as KubernetesDeployment[],
statefulSets: [] as KubernetesStatefulSet[],
pods: [] as any[],
configMaps: [] as KubernetesConfigMap[],
ingresses: [] as KuberntesIngress[],
namespaces: [] as KubernetesNamespaces[],
secrets: [] as KubernetesSecret[],
persistentvolumeclaims: [] as KubernetesPersistentVolumeClaim[],
persistentVolumes: [] as KubernetesPersistentVolume[],
selectedTab: 'logs' as 'logs' | 'terminal' | 'describe',
};
},
async created() {
await this.loadClusters();
},
methods: {
handleStatefulSetSelect(statefulSet: KubernetesStatefulSet) {
this.selectedResourceItem = statefulSet;
},
handleDeploymentSelect(deployment: KubernetesDeployment) {
this.selectedResourceItem = deployment;
},
handlePodSelect(pod: any) {
this.selectedResourceItem = pod;
},
handleConfigMapSelect(configMap: KubernetesConfigMap) {
this.selectedResourceItem = configMap;
},
handleIngressSelect(ingress: KubernetesIngress) {
this.selectedResourceItem = ingress;
},
handleNamespaceSelect(namespace: KubernetesNamespace) {
this.selectedResourceItem = namespace;
},
handleSecretSelect(secret: KubernetesSecret) {
this.selectedResourceItem = secret;
},
handlePersistentVolumeClaimSelect(persistentVolumeClaim: KubernetesPersistentVolumeClaim) {
this.selectedResourceItem = persistentVolumeClaim;
},
handlePersistentVolumeSelect(persistentVolume: KubernetesPersistentVolume) {
this.selectedResourceItem = persistentVolume;
},
async loadClusters() {
try {
const response = await kubernetesService.getClusters();
if (response.success && response.data) {
this.clusters = response.data.map(cluster => ({
...cluster,
name: cluster.name
}));
}
} catch (error) {
console.error('Failed to load clusters:', error);
}
},
getClusterName(server: string): string {
try {
const url = new URL(server);
return url.hostname;
} catch {
return server;
}
},
async handleUpdateSecret(opts: SecretUpdateOptions) {
try {
const response = await kubernetesService.updateSecret(opts.context, opts.definition, opts.origNamespace, opts.origName);
if (response.success) {
alert(response.msg);
await this.loadResources();
} else {
throw new Error(response.msg);
}
} catch (error: any) {
alert(`Error updating Secret: ${error}`);
}
},
async handleDeleteSecret({cluster, secret}: { cluster: KubernetesCluster, secret: KubernetesSecret}) {
try {
const response = await kubernetesService.deleteSecret(cluster.contextName, secret.metadata.namespace, secret.metadata.name);
if (response.success) {
alert(response.msg);
await this.loadResources();
} else {
throw new Error(response.msg);
}
} catch (error) {
alert(`Error deleting Secret: ${error}`);
}
},
async handleDeleteNamespace({cluster, namespace}: { cluster: KubernetesCluster, namespace: KubernetesNamespace }) {
try {
const response = await kubernetesService.deleteNamespace(cluster.contextName, namespace.metadata.name);
if (response.success) {
alert(`Namespace ${namespace.metadata.name} deleted successfully`);
await this.loadResources();
} else {
throw new Error(response.msg || 'Unknown error');
}
} catch (error) {
alert(`Error deleting Namespace: ${error}`);
}
},
async handleDeletePersistentVolume({cluster, definition}: { cluster: KubernetesCluster, definition: KubernetesPersistentVolume}) {
try {
const response = await kubernetesService.deletePersistentVolume(cluster.contextName, definition.metadata.name);
if (response.success) {
alert(response.msg);
await this.loadResources();
} else {
throw new Error(response.msg);
}
} catch (error) {
alert(`Error deleting Persistent Volume: ${error}`);
}
},
async handleCreateSecret(opts: SecretCreateOptions) {
try {
let response;
response = await kubernetesService.createSecret(opts.context, opts.opts);
if (response.success) {
alert(response.msg)
await this.loadResources();
} else {
throw new Error(response.msg);
}
} catch (error) {
alert(`Error creating Secret: ${error}`);
}
},
async handleCreatePersistentVolumeClaim({cluster, persistentVolumeClaim}: {cluster: KubernetesCluster, persistentVolumeClaim: KubernetesPersistentVolumeClaim}) {
try {
let response;
response = await kubernetesService.createPersistentVolumeClaim(cluster.contextName, persistentVolumeClaim);
if (response.success) {
alert(response.msg)
await this.loadResources();
} else {
throw new Error(response.msg);
}
} catch (error) {
alert(`Error creating Persistent Volume Claim: ${error}`);
}
},
async handleDeletePersistentVolumeClaim({cluster, persistentVolumeClaim}: { cluster: KubernetesCluster, persistentVolumeClaim: KubernetesPersistentVolumeClaim}) {
try {
const response = await kubernetesService.deletePersistentVolumeClaim(cluster.contextName, persistentVolumeClaim.metadata.namespace, persistentVolumeClaim.metadata.name);
if (response.success) {
alert(response.msg);
await this.loadResources();
} else {
throw new Error(response.msg);
}
} catch (error) {
alert(`Error deleting Persistent Volume Claim: ${error}`);
}
},
async handleCreatePersistentVolume({cluster, definition}: {cluster: KubernetesCluster, definition: KubernetesPersistentVolume}) {
try {
let response;
response = await kubernetesService.createPersistentVolume(cluster.contextName, definition);
if (response.success) {
alert(response.msg)
await this.loadResources();
} else {
throw new Error(response.msg);
}
} catch (error) {
alert(`Error creating Persistent Volume: ${error}`);
}
},
async handleCreateNamespace(
{cluster, definition, yamlContent, isYaml}:
{
cluster: { contextName: string },
definition?: any,
yamlContent?: string,
isYaml?: boolean,
}
) {
try {
let response;
if (isYaml && yamlContent) {
response = await kubernetesService.createNamespaceYAML(cluster.contextName, yamlContent);
} else if (!isYaml && definition) {
response = await kubernetesService.createNamespace(cluster.contextName, definition);
} else {
throw new Error("Invalid namespace creation parameters");
}
if (response.success) {
alert(response.msg)
await this.loadResources();
} else {
throw new Error(response.msg);
}
} catch (error) {
alert(`Error creating namespace: ${error}`);
}
},
async handleCreatePod({
cluster,
podDefinition,
yamlContent,
isYaml
}: {
cluster: { contextName: string },
podDefinition?: any,
yamlContent?: string,
isYaml?: boolean
}) {
try {
let response;
if (isYaml && yamlContent) {
response = await kubernetesService.createPodYAML(cluster.contextName, yamlContent);
} else if (!isYaml && podDefinition) {
response = await kubernetesService.createPod(cluster.contextName, podDefinition);
} else {
throw new Error('Invalid pod creation parameters');
}
if (response.success) {
alert(response.msg)
await this.loadResources();
} else {
throw new Error(response.msg);
}
} catch (error) {
console.error('Error creating pod:', error);
alert(`Error creating pod: ${error}`);
}
},
async handleDeletePod({cluster, pod}: { cluster: KubernetesCluster, pod: KubernetesPod }) {
try {
const response = await kubernetesService.deletePod(cluster.contextName, pod.metadata.namespace, pod.metadata.name);
if (response.success) {
alert(`Pod ${pod.metadata.name} deleted successfully`);
await this.loadResources();
} else {
throw new Error(response.msg || 'Unknown error');
}
} catch (error) {
console.error('Error deleting pod:', error);
alert(`Error deleting pod: ${error}`);
}
},
async handleDeleteIngress({cluster, ingress}: { cluster: KubernetesCluster, ingress: KubernetesIngress }) {
try {
const response = await kubernetesService.deleteIngress(cluster.contextName, ingress.metadata.namespace, ingress.metadata.name);
if (response.success) {
alert(`Ingress ${ingress.metadata.name} deleted successfully`);
await this.loadResources();
} else {
throw new Error(response.msg);
}
} catch (error) {
console.error('Error deleting ingress:', error);
alert(`Error deleting ingress: ${error}`);
}
},
async handleRestartDeployment({ cluster, deployment }: { cluster: KubernetesCluster, deployment: KubernetesDeployment }) {
try {
const response = await kubernetesService.restartDeployment(cluster.contextName, deployment.metadata.namespace, deployment.metadata.name);
if (response.success) {
alert(`Deployment ${deployment.metadata.name} restarted successfully`);
await this.loadResources();
} else {
throw new Error(response.msg || 'Unknown error');
}
} catch (error) {
alert(`Error restarting deployment: ${error}`);
}
},
async handleRestartStatefulSet({ cluster, statefulSet }: { cluster: KubernetesCluster, statefulSet: KubernetesStatefulSet }) {
try {
const response = await kubernetesService.restartStatefulSet(cluster.contextName, statefulSet.metadata.namespace, statefulSet.metadata.name);
if (response.success) {
alert(`StatefulSet ${statefulSet.metadata.name} restarted successfully`);
await this.loadResources();
} else {
throw new Error(response.msg || 'Unknown error');
}
} catch (error) {
alert(`Error restarting statefulset: ${error}`);
}
},
handleClusterSelect(cluster: KubernetesCluster) {
this.selectedCluster = cluster;
this.selectedResource = null;
this.deployments = [];
this.pods = [];
this.statefulSets = [];
this.configMaps = [];
console.log("selected cluster: " + cluster.contextName);
},
handleResourceSelect(resource: string) {
this.selectedResource = resource;
this.selectedResourceItem = null; // Reset selected item when changing resource type
this.loadResources();
},
handleResourceItemSelect(item: any) {
this.selectedResourceItem = item;
},
handleTabChange(tab: 'logs' | 'terminal' | 'describe') {
this.selectedTab = tab;
},
async loadResources() {
if (!this.selectedCluster || !this.selectedResource) return;
try {
if (this.selectedResource === 'deployments') {
try {
const response = await kubernetesService.getDeployments(this.selectedCluster.contextName);
if (response.success && response.data) {
this.deployments = response.data;
}
} catch (error) {
console.error('Failed to load deployments:', error);
}
} else if (this.selectedResource === 'pods') {
// Clear existing pods first
this.pods = [];
try {
const response = await kubernetesService.getPods(this.selectedCluster.contextName);
if (response.success && response.data) {
this.pods = response.data;
}
} catch (error) {
console.error('Failed to load pods:', error);
}
} else if (this.selectedResource === 'statefulsets') {
try {
const response = await kubernetesService.getStatefulSets(this.selectedCluster.contextName);
if (response.success && response.data) {
this.statefulSets = response.data;
}
} catch (error) {
console.error('Failed to load statefulsets:', error);
}
} else if (this.selectedResource === 'configmaps') {
try {
const response = await kubernetesService.getConfigMaps(this.selectedCluster.contextName);
if (response.success && response.data) {
this.configMaps = response.data;
}
} catch (error) {
console.error('Failed to load configmaps:', error);
}
} else if (this.selectedResource === 'ingresses') {
try {
const response = await kubernetesService.getIngresses(this.selectedCluster.contextName);
if (response.success && response.data) {
this.ingresses = response.data;
}
} catch (error) {
console.error('Failed to load ingresses:', error);
}
} else if (this.selectedResource === 'namespaces') {
try {
const response = await kubernetesService.getNamespaces(this.selectedCluster.contextName);
if (response.success && response.data) {
this.namespaces = response.data;
}
} catch (error) {
console.error('Failed to load namespaces:', error);
}
} else if (this.selectedResource === 'secrets') {
try {
const response = await kubernetesService.listSecrets(this.selectedCluster.contextName);
if (response.success && response.data) {
this.secrets = response.data;
}
} catch (error) {
console.error('Failed to load Secrets:', error);
}
} else if (this.selectedResource === 'persistentvolumeclaims') {
try {
const response = await kubernetesService.listPersistentVolumeClaims(this.selectedCluster.contextName);
if (response.success && response.data) {
this.persistentvolumeclaims = response.data;
}
} catch (error) {
console.error('Failed to load Persistent Volume Claims:', error);
}
} else if (this.selectedResource === 'persistentVolumes') {
try {
const response = await kubernetesService.listPersistentVolumes(this.selectedCluster.contextName);
if (response.success && response.data) {
this.persistentVolumes = response.data;
}
} catch (error) {
console.error('Failed to load Persistent Volumes:', error);
}
try {
const response = await kubernetesService.listPersistentVolumeClaims(this.selectedCluster.contextName);
if (response.success && response.data) {
this.persistentvolumeclaims = response.data;
}
} catch (error) {
console.error('Failed to load Persistent Volume Claims:', error);
}
}
} catch (error) {
console.error('Failed to load resources:', error);
}
},
},
});
</script>
<template>
<div class="app-container">
<NavigationPane
:clusters="clusters"
:selectedCluster="selectedCluster"
:selectedResource="selectedResource"
@select-cluster="handleClusterSelect"
@select-resource="handleResourceSelect"
/>
<div class="right-container">
<component
:is="currentResourceComponent"
:selectedCluster="selectedCluster"
:deployments="deployments"
:statefulSets="statefulSets"
:configMaps="configMaps"
:pods="pods"
:ingresses="ingresses"
:namespaces="namespaces"
:secrets="secrets"
:persistentvolumeclaims="persistentvolumeclaims"
:persistentVolumes="persistentVolumes"
@create-namespace="handleCreateNamespace"
@create-pod="handleCreatePod"
@create-secret="handleCreateSecret"
@create-persistentvolume="handleCreatePersistentVolume"
@create-persistentvolumeclaim="handleCreatePersistentVolumeClaim"
@delete-ingress="handleDeleteIngress"
@delete-namespace="handleDeleteNamespace"
@delete-pod="handleDeletePod"
@delete-secret="handleDeleteSecret"
@delete-persistentvolume="handleDeletePersistentVolume"
@delete-persistentvolumeclaim="handleDeletePersistentVolumeClaim"
@load-resources="loadResources"
@restart-deployment="handleRestartDeployment"
@restart-statefulset="handleRestartStatefulSet"
@pod-selected="handlePodSelect"
@deployment-selected="handleDeploymentSelect"
@configmap-selected="handleConfigMapSelect"
@namespace-selected="handleNamespaceSelect"
@ingress-selected="handleIngressSelect"
@secret-selected="handleSecretSelect"
@statefulset-selected="handleStatefulSetSelect"
@persistentVolumeClaim-selected="handlePersistentVolumeClaimSelect"
@persistentVolume-selected="handlePersistentVolumeSelect"
@update-secret="handleUpdateSecret"
/>
<BottomTabs
v-if="selectedResourceItem"
:selectedResource="selectedResourceItem"
:selectedCluster="selectedCluster"
:selectedTab="selectedTab"
@tab-changed="handleTabChange"
/>
</div>
</div>
</template>
<style>
.app-container {
display: flex;
height: 100vh;
background-color: #1e1e1e;
color: #ffffff;
}
.right-container {
flex: 1;
display: flex;
flex-direction: column;
border-left: 1px solid #333333;
}
.right-container > :first-child {
flex: 1;
overflow-y: auto;
}
.right-container > :last-child {
border-top: 1px solid #333333;
}
</style>