Add Popup for node inspection

This commit is contained in:
Andreas Dinauer 2026-01-04 16:32:48 +01:00
parent 9afc5d8eab
commit 5104631bd2
41 changed files with 418 additions and 235 deletions

View File

@ -1,11 +1,13 @@
<template>
<ClientOnly>
<NuxtPage></NuxtPage>
<component :is="usePopup().get()"></component>
</ClientOnly>
</template>
<script setup lang="ts">
import type { RouteLocation } from 'vue-router';
import {usePopup} from "~/components/popup/Popup";
// Guard dashboard and redirect to login
useRouter().beforeEach((route: RouteLocation) => {

View File

@ -1,29 +1,100 @@
import { Metadata } from "../Metadata";
import { type HasMetadata } from "../repo/ResourceRepo";
import {State} from "~/classes/Threshold";
export class Node implements HasMetadata
{
constructor (
public metadata: Metadata,
public metrics: NodeMetrics,
public status: Status
public status: Status,
public spec: Spec
) { }
static cpuUsageFlag(usage: number): State
{
if(usage < 20)
{
return State.GREEN;
}
if(usage < 60)
{
return State.ORANGE;
}
return State.RED;
}
static memoryUsageFlag(usage: number)
{
if(usage < 30)
{
return State.GREEN;
}
if(usage < 75)
{
return State.ORANGE;
}
return State.RED;
}
static diskUsageFlag(usage: number)
{
if(usage < 30)
{
return State.GREEN;
}
if(usage < 70)
{
return State.ORANGE;
}
return State.RED;
}
static isReady(node: Node): boolean | undefined
{
const conditions = node.status.conditions;
if(conditions != undefined)
{
for(const condition of conditions)
{
if(condition.type === "Ready" && condition.status === "True")
{
return true;
}
}
return false;
}
return undefined;
}
}
class NodeMetrics
{
absoluteCpuUsage?: number;
relativeCpuUsage?: number;
totalCpu?: number;
absoluteMemory?: number;
relativeMemory?: number;
totalMemory?: number;
runningPods?: number;
relativeDiskUsage?: number;
totalDiskSpace?: number;
}
class Spec
{
constructor (
public taints?: Taint[]
) {}
}
class Status
{
constructor (
public nodeInfo: NodeInfo
) {}
conditions?: Condition[]
}
@ -32,3 +103,19 @@ class Condition
type?: string;
status?: string;
}
class Taint
{
constructor (
public key: string,
public effect: string
) {}
}
class NodeInfo
{
constructor (
public osImage: string,
public kubeletVersion: string
) {}
}

View File

@ -31,7 +31,7 @@ defineProps<{
background-color: rgb(139, 255, 139);
}
.outer.green .inner {
background-color: green;
background-color: #11cc11;
}
.outer.orange {

View File

@ -11,7 +11,7 @@
<script setup lang="ts">
import type { ConfigMap } from '~/classes/ConfigMap';
import PopupTemplate from '~/components/popup/PopupTemplate.vue';
import { Prompt, PromptType } from '~/components/ui/Prompt';
import { Prompt, PromptType } from '~/components/ui/prompt/Prompt';
defineProps<{
configMap: ConfigMap

View File

@ -1,32 +1,26 @@
<template>
<div>
<p class="grid-element" @click="() => showViewPopup = true">{{ deployment.metadata.name }}</p>
<p class="grid-element" @click="usePopup().open(DeploymentViewPopup, deployment)">{{ deployment.metadata.name }}</p>
<p class="grid-element">{{ deployment.metadata.namespace }}</p>
<p class="grid-element">{{ calcAge(deployment.metadata.creationTimestamp) }}</p>
<p class="grid-element">{{ deployment.spec.replicas }}</p>
<div class="grid-element action-buttons">
<ActionButton @click="() => showLogsPopup = true">text_snippet</ActionButton>
<ActionButton @click="usePopup().open(DeploymentLogPopup, deployment)">text_snippet</ActionButton>
<ActionButton>autorenew</ActionButton>
<ActionButton @click="() => rescaleDeploymentPopup.open()">height</ActionButton>
<ActionButton @click="usePopup().open(DeploymentRescalePopup, deployment)">height</ActionButton>
</div>
<RescaleDeploymentPopup ref="rescaleDeploymentPopup" :deployment="deployment"></RescaleDeploymentPopup>
<DeploymentViewPopup v-if="showViewPopup" :deployment="deployment" @close="() => showViewPopup = false"></DeploymentViewPopup>
<DeploymentLogPopup v-if="showLogsPopup" :deployment="deployment" @close="() => showLogsPopup = false"></DeploymentLogPopup>
</div>
</template>
<script setup lang="ts">
import { Deployment } from '~/classes/Deployment';
import { calcAge } from '~/classes/Pod';
import RescaleDeploymentPopup from '../RescaleDeploymentPopup.vue';
import DeploymentRescalePopup from './view/DeploymentRescalePopup.vue';
import DeploymentViewPopup from "~/components/deployment/view/DeploymentViewPopup.vue";
import DeploymentLogPopup from "~/components/inspect/logs/DeploymentLogPopup.vue";
import DeploymentLogPopup from "~/components/deployment/view/DeploymentLogPopup.vue";
import {usePopup} from "~/components/popup/Popup";
defineProps<{
deployment: Deployment
}>();
const rescaleDeploymentPopup = ref();
const showViewPopup = ref(false);
const showLogsPopup = ref(false);
</script>

View File

@ -1,6 +1,6 @@
<template>
<PopupTemplate :heading="'Logs: ' + deployment.metadata.namespace + '/' + deployment.metadata.name" ref="base" @close="emits('close')">
<div class="log-container" v-if="pods">
<PopupTemplate :heading="StringUtils.format('Logs: %s/%s', deployment.metadata.namespace, deployment.metadata.name)">
<div class="log-container">
<div class="spaced-center">
<div class="left-center">
<UiButton @click="() => config.timestamps = true" v-if="!config.timestamps"><span>Show Timestamp</span> <UiIcon>visibility</UiIcon></UiButton>
@ -20,47 +20,27 @@
import { LogRepo } from '~/classes/LogRepo';
import { Pod } from '~/classes/Pod';
import { Log } from '~/requests/logs';
import Console from "~/components/inspect/logs/Console.vue";
import Console from "~/components/inspect/console/Console.vue";
import type {Deployment} from "~/classes/Deployment";
import UiIcon from "~/components/ui/UiIcon.vue";
import { ConsoleConfig } from '~/components/inspect/logs/ConsoleConfig'
import { ConsoleConfig } from '~/components/inspect/console/ConsoleConfig'
import {usePopup} from "~/components/popup/Popup";
const config = ref(ConsoleConfig.default(true, true, false));
const base = ref();
const logs: Ref<Log[]> = ref([]);
const pods: Ref<Pod[] | undefined> = ref(undefined);
const props = defineProps<{
deployment: Deployment
}>();
const emits = defineEmits<{
(e: 'close'): void
}>()
const deployment = usePopup().data() as Deployment;
const logRepo = new LogRepo();
onMounted(() => {
logRepo.listen("deployments", props.deployment.metadata.namespace, props.deployment.metadata.name,(_logs: Log[]) => {
logRepo.listen("deployments", deployment.metadata.namespace, deployment.metadata.name,(_logs: Log[]) => {
logs.value.push(..._logs);
});
Pod.getByDeployment(props.deployment.metadata.namespace, props.deployment.metadata.name, (_pods: Pod[]) => {
pods.value = _pods;
});
});
onUnmounted(() => {
logRepo.clear();
});
onMounted(() => {
document.addEventListener('keydown', (event) => {
if(event.key === 'Escape')
{
emits('close');
}
});
})
</script>
<style scoped>

View File

@ -22,28 +22,16 @@
<script setup lang="ts">
import type { Deployment } from '~/classes/Deployment';
import { rescaleDeployment } from '~/requests/deployments';
import {usePopup} from "~/components/popup/Popup";
const base = ref();
defineProps<{
deployment: Deployment
}>();
function open()
{
base.value.open();
}
defineExpose({
open
})
const deployment = usePopup().data() as Deployment;
function rescale(deployment: Deployment)
{
if(deployment.spec && deployment.spec.replicas)
{
rescaleDeployment(deployment, deployment.spec.replicas, () => {
base.value.close();
usePopup().close();
});
}
else

View File

@ -71,10 +71,9 @@
import PopupTemplate from "~/components/popup/PopupTemplate.vue";
import type { Deployment } from "~/classes/Deployment";
import {calcAge, Pod} from "~/classes/Pod";
import {usePopup} from "~/components/popup/Popup";
const props = defineProps<{
deployment: Deployment
}>()
const deployment = usePopup().data() as Deployment;
const emits = defineEmits<{
(e: 'close'): void
@ -92,7 +91,7 @@ onMounted(() => {
const pods: Ref<Pod[] | undefined> = ref();
onMounted(() => {
Pod.getByDeployment(props.deployment.metadata.namespace, props.deployment.metadata.name, (_pods: Pod[]) => {
Pod.getByDeployment(deployment.metadata.namespace, deployment.metadata.name, (_pods: Pod[]) => {
pods.value = _pods;
});
})

View File

@ -0,0 +1,24 @@
export enum ResourceType
{
// Cluster Resources
NODE = "nodes",
NAMESPACE = "namespaces",
CUSTOM_RESOURCE_DEFINITION = "custom-resource-definitions",
// Workloads
STATEFUL_SET = "stateful-sets",
DEPLOYMENT = "deployments",
POD = "pods",
// Networking
SERVICE = "services",
INGRESS = "ingresses",
// Config
SECRET = "secrets",
CONFIG_MAP = "config-maps",
// Storage
PVC = "pvcs",
PV = "pvs",
}

View File

@ -12,7 +12,7 @@
<script setup lang="ts">
import type {Log} from "~/requests/logs";
import dayjs from "dayjs";
import type {ConsoleConfig} from "~/components/inspect/logs/ConsoleConfig";
import type {ConsoleConfig} from "~/components/inspect/console/ConsoleConfig";
const props = defineProps<{
config: ConsoleConfig

View File

@ -16,10 +16,11 @@
import type { ConfigMap } from '~/classes/ConfigMap';
import { ResourceRepo } from '~/classes/repo/ResourceRepo';
import ConfigMapComponent from '~/components/configmap/ConfigMapComponent.vue';
import {ResourceType} from "~/classes/ResourceTypes";
const repo = ResourceRepo.init<ConfigMap>();
onMounted(() => {
repo.listen("config-maps");
repo.listen(ResourceType.CONFIG_MAP);
});
onUnmounted(() => {
repo.clear();

View File

@ -16,10 +16,11 @@
<script setup lang="ts">
import { ResourceRepo } from '~/classes/repo/ResourceRepo';
import type {CustomResourceDefinition} from "~/classes/CustomResourceDefinition";
import {ResourceType} from "~/classes/ResourceTypes";
const repo = ResourceRepo.init<CustomResourceDefinition>();
onMounted(() => {
repo.listen("custom-resource-definitions");
repo.listen(ResourceType.CUSTOM_RESOURCE_DEFINITION);
});
onUnmounted(() => {
repo.clear();

View File

@ -17,10 +17,11 @@
import type { Deployment } from '~/classes/Deployment';
import { ResourceRepo } from '~/classes/repo/ResourceRepo';
import DeploymentComponent from "~/components/deployment/DeploymentComponent.vue";
import {ResourceType} from "~/classes/ResourceTypes";
const repo = ResourceRepo.init<Deployment>();
onMounted(() => {
repo.listen("deployments");
repo.listen(ResourceType.DEPLOYMENT);
});
onUnmounted(() => {
repo.clear();

View File

@ -16,10 +16,11 @@
<script setup lang="ts">
import type { Ingress } from '~/classes/Ingress';
import { ResourceRepo } from '~/classes/repo/ResourceRepo';
import {ResourceType} from "~/classes/ResourceTypes";
const repo = ResourceRepo.init<Ingress>();
onMounted(() => {
repo.listen("ingresses");
repo.listen(ResourceType.INGRESS);
});
onUnmounted(() => {
repo.clear();

View File

@ -14,10 +14,11 @@
import type { Namespace } from '~/classes/Namespace';
import { ResourceRepo } from '~/classes/repo/ResourceRepo';
import NamespaceComponent from '~/components/NamespaceComponent.vue';
import {ResourceType} from "~/classes/ResourceTypes";
const repo = ResourceRepo.init<Namespace>();
onMounted(() => {
repo.listen("namespaces");
repo.listen(ResourceType.NAMESPACE);
});
onUnmounted(() => {
repo.clear();

View File

@ -10,7 +10,7 @@
<p>Memory</p>
<p>Disk Usage</p>
</div>
<NodeComponent :node-stats="ns" v-for="(ns, index) in node" class="resource" :class="{ even: index % 2 }"></NodeComponent>
<NodeComponent :node="ns" v-for="(ns, index) in node" class="resource" :class="{ even: index % 2 }"></NodeComponent>
</div>
</TableComponent>
</template>
@ -18,9 +18,10 @@
<script setup lang="ts">
import type { Node } from '~/classes/node/Node';
import { ResourceRepo } from '~/classes/repo/ResourceRepo';
import NodeComponent from '~/components/NodeComponent.vue';
import NodeComponent from '~/components/node/NodeComponent.vue';
import {ResourceType} from "~/classes/ResourceTypes";
const node = ResourceRepo.init<Node>().load('nodes').get();
const node = ResourceRepo.init<Node>().load(ResourceType.NODE).get();
</script>
<style>

View File

@ -17,11 +17,12 @@
import type { PersistentVolumeClaim } from '~/classes/PersistentVolumeClaim';
import { ResourceRepo } from '~/classes/repo/ResourceRepo';
import PersistentVolumeClaimComponent from '~/components/PersistentVolumeClaimComponent.vue';
import {ResourceType} from "~/classes/ResourceTypes";
const repo = ResourceRepo.init<PersistentVolumeClaim>();
onMounted(() => {
repo.listen("pvcs");
repo.listen(ResourceType.PVC);
});
onUnmounted(() => {
repo.clear();

View File

@ -14,11 +14,12 @@
import type { PersistentVolume } from '~/classes/PersistentVolume';
import { ResourceRepo } from '~/classes/repo/ResourceRepo';
import PersistentVolumeComponent from '~/components/PersistentVolumeComponent.vue';
import {ResourceType} from "~/classes/ResourceTypes";
const repo = ResourceRepo.init<PersistentVolume>();
onMounted(() => {
repo.listen("pvs");
repo.listen(ResourceType.PV);
});
onUnmounted(() => {
repo.clear();

View File

@ -20,10 +20,11 @@
import type { Pod } from '~/classes/Pod';
import { ResourceRepo } from '~/classes/repo/ResourceRepo';
import PodComponent from '~/components/pod/PodComponent.vue';
import {ResourceType} from "~/classes/ResourceTypes";
const repo = ResourceRepo.init<Pod>();
onMounted(() => {
repo.listen("pods");
repo.listen(ResourceType.POD);
});
onUnmounted(() => {
repo.clear();

View File

@ -20,11 +20,12 @@
import { ResourceRepo } from '~/classes/repo/ResourceRepo';
import type { Secret } from '~/classes/Secret';
import SecretComponent from '~/components/secrets/SecretComponent.vue';
import {ResourceType} from "~/classes/ResourceTypes";
const repo = ResourceRepo.init<Secret>();
onMounted(() => {
repo.listen("secrets");
repo.listen(ResourceType.SECRET);
});
onUnmounted(() => {
repo.clear();

View File

@ -15,10 +15,11 @@
<script setup lang="ts">
import { ResourceRepo } from '~/classes/repo/ResourceRepo';
import type { Service } from '~/classes/Service';
import {ResourceType} from "~/classes/ResourceTypes";
const repo = ResourceRepo.init<Service>();
onMounted(() => {
repo.listen("services");
repo.listen(ResourceType.SERVICE);
});
onUnmounted(() => {
repo.clear();

View File

@ -16,10 +16,11 @@
import { ResourceRepo } from '~/classes/repo/ResourceRepo';
import type { StatefulSet } from '~/classes/StatefulSet';
import StatefulSetComponent from '~/components/statefulset/StatefulSetComponent.vue';
import {ResourceType} from "~/classes/ResourceTypes";
const repo = ResourceRepo.init<StatefulSet>();
onMounted(() => {
repo.listen("stateful-sets");
repo.listen(ResourceType.STATEFUL_SET);
});
onUnmounted(() => {
repo.clear();

View File

@ -1,37 +1,41 @@
<template>
<div>
<p class="grid-element" v-if="nodeStats && nodeStats.metadata">{{ nodeStats.metadata.name }}</p>
<p class="grid-element" v-if="node && node.metadata" @click="usePopup().open(NodeViewComponent, node)">{{
node.metadata.name
}}</p>
<div class="grid-element">
<p>{{ calcAge(nodeStats.metadata?.creationTimestamp) }}</p>
<p>{{ calcAge(node.metadata.creationTimestamp) }}</p>
</div>
<p class="grid-element">{{ nodeStats.metrics.runningPods }}</p>
<p class="grid-element">{{ node.metrics.runningPods }}</p>
<div class="grid-element">
<NodeReadyComponent :ready="isReady(nodeStats)"></NodeReadyComponent>
<NodeReadyComponent :ready="isReady(node)"></NodeReadyComponent>
</div>
<div class="grid-element">
<p class="usage" :class="cpuUsageFlag(nodeStats.metrics.relativeCpuUsage)">{{ nodeStats.metrics.relativeCpuUsage }}%</p>
<p class="usage" :class="cpuUsageFlag(node.metrics.relativeCpuUsage)">{{ node.metrics.relativeCpuUsage }}%</p>
</div>
<div class="grid-element">
<p class="usage" :class="ramUsageFlag(nodeStats.metrics.relativeMemory)">{{ nodeStats.metrics.relativeMemory }}%</p>
<p class="usage" :class="ramUsageFlag(node.metrics.relativeMemory)">{{ node.metrics.relativeMemory }}%</p>
</div>
<div class="grid-element">
<p class="usage" :class="diskUsageFlag(nodeStats.metrics.relativeDiskUsage)"><span v-if="nodeStats.metrics.relativeDiskUsage">{{ nodeStats.metrics.relativeDiskUsage }}%</span><span v-else>-</span></p>
<p class="usage" :class="diskUsageFlag(node.metrics.relativeDiskUsage)"><span v-if="node.metrics.relativeDiskUsage">{{ node.metrics.relativeDiskUsage }}%</span><span v-else>-</span></p>
</div>
</div>
</template>
<script setup lang="ts">
import type { Node } from '~/classes/node/Node';
import NodeReadyComponent from './NodeReadyComponent.vue';
import NodeReadyComponent from '../NodeReadyComponent.vue';
import { calcAge } from '~/classes/Pod';
import {usePopup} from "~/components/popup/Popup";
import NodeViewComponent from "~/components/node/view/NodeViewComponent.vue";
defineProps<{
nodeStats: Node;
node: Node;
}>();
function isReady(nodeStats: Node): boolean | undefined
function isReady(node: Node): boolean | undefined
{
const conditions = nodeStats.status.conditions;
const conditions = node.status.conditions;
if(conditions != undefined)
{
for(const condition of conditions)

View File

@ -0,0 +1,96 @@
<template>
<PopupTemplate :heading="StringUtils.format('Node: %s', node.metadata.name)">
<div class="content-l">
<h2>General</h2>
<div class="content-l">
<div class="col-3">
<div class="content-m">
<h3>Node</h3>
<p class="tile-m">{{ node.metadata.name }}</p>
</div>
<div class="content-m">
<h3>Status</h3>
<div class="left-center">
<NodeReadyComponent :ready="Node.isReady(node)"></NodeReadyComponent>
</div>
</div>
</div>
<div class="content-m">
<h3>Metrics</h3>
<div class="col-3">
<div class="tile-l content-m">
<p>Total CPU</p>
<p class="metric" v-if="node.metrics.totalCpu != null">{{ (node.metrics.totalCpu / 1000).toFixed(2) }}</p>
</div>
<div class="tile-l content-m">
<p>Total Disk Space</p>
<p class="metric" v-if="node.metrics.totalDiskSpace != null" v-for="diskSpace in [Memory.format(node.metrics.totalDiskSpace)]">{{ diskSpace.value.toFixed(2) }} {{ formatUnit(diskSpace.unit) }}</p>
</div>
<div class="tile-l content-m">
<p>Total Memory</p>
<p class="metric" v-if="node.metrics.totalMemory != null" v-for="memory in [Memory.format(node.metrics.totalMemory)]">{{ memory.value.toFixed(2) }} {{ formatUnit(memory.unit) }}</p>
</div>
<div class="tile-l content-m" v-if="node.metrics.relativeCpuUsage">
<div class="left-center">
<p>Used CPU</p>
<Pulse :threshold="new Threshold(node.metrics.relativeCpuUsage, Node.cpuUsageFlag)"></Pulse>
</div>
<p class="metric">{{ node.metrics.relativeCpuUsage }}%</p>
</div>
<div class="tile-l content-m" v-if="node.metrics.relativeDiskUsage != null">
<div class="left-center">
<p>Used Disk Space</p>
<Pulse :threshold="new Threshold(node.metrics.relativeDiskUsage, Node.diskUsageFlag)"></Pulse>
</div>
<p class="metric">{{ node.metrics.relativeDiskUsage }}%</p>
</div>
<div class="tile-l content-m" v-if="node.metrics.relativeMemory != null">
<div class="left-center">
<p>Used Memory</p>
<Pulse :threshold="new Threshold(node.metrics.relativeMemory, Node.memoryUsageFlag)"></Pulse>
</div>
<p class="metric">{{ node.metrics.relativeMemory }}%</p>
</div>
</div>
</div>
<div class="content-m" v-if="node.spec.taints">
<h3>Taints ({{ node.spec.taints.length }})</h3>
<div class="content-s">
<p class="tile-m" v-for="taint in node.spec.taints">{{ taint.key }}: {{ taint.effect }}</p>
</div>
</div>
<div class="col-3">
<div class="content-m">
<h3>Operating System</h3>
<p class="tile-m">{{ node.status.nodeInfo.osImage }}</p>
</div>
<div class="content-m">
<h3>Kubelet Version</h3>
<p class="tile-m">{{ node.status.nodeInfo.kubeletVersion }}</p>
</div>
</div>
</div>
</div>
</PopupTemplate>
</template>
<script setup lang="ts">
import PopupTemplate from "~/components/popup/PopupTemplate.vue";
import {usePopup} from "~/components/popup/Popup";
import {Node} from "~/classes/node/Node";
import { Memory, MemoryUnity } from '~/utils/Memory';
import { Threshold } from '~/classes/Threshold';
const node = usePopup().data() as Node;
function formatUnit(unit: MemoryUnity)
{
return new Map<MemoryUnity, String>([[MemoryUnity.RAW, "Bytes"], [MemoryUnity.KI, "Ki"], [MemoryUnity.MI, "Mi"], [MemoryUnity.GI, "Gi"], [MemoryUnity.TI, "Ti"]]).get(unit);
}
</script>
<style scoped>
.metric {
font-size: 2rem;
}
</style>

View File

@ -1,6 +1,6 @@
<template>
<div>
<p class="grid-element pointer" v-if="pod.metadata" @click="() => showViewPopup = true">{{ pod.metadata.name }}</p>
<p class="grid-element pointer" v-if="pod.metadata" @click="usePopup().open(PodViewPopup, props.pod)">{{ pod.metadata.name }}</p>
<p class="grid-element" v-if="pod.metadata">{{ pod.metadata.namespace }}</p>
<p class="grid-element no-wrap" v-if="pod.metadata">{{ calcAge(pod.metadata.creationTimestamp) }}</p>
<p class="grid-element" v-if="pod.spec">{{ pod.spec.nodeName }}</p>
@ -10,12 +10,9 @@
<PhaseComponent v-if="pod.status" :phase="pod.status.phase"></PhaseComponent>
</div>
<div class="grid-element action-buttons">
<ActionButton @click="showLogPopup = true">text_snippet</ActionButton>
<ActionButton @click="showDeletePopup = true">delete</ActionButton>
<ActionButton @click="usePopup().open(PodLogPopup, props.pod)">text_snippet</ActionButton>
<ActionButton @click="usePopup().open(PodDeletePopup, props.pod)">delete</ActionButton>
</div>
<PodDeletePopup v-if="showDeletePopup" :pod="pod" @close="showDeletePopup = false"></PodDeletePopup>
<PodLogPopup v-if="showLogPopup" :pod="pod" @close="showLogPopup = false"></PodLogPopup>
<PodViewPopup v-if="showViewPopup" :pod="pod" @close="showViewPopup = false"></PodViewPopup>
</div>
</template>
@ -23,13 +20,11 @@
import type { Pod } from '~/classes/Pod';
import { calcAge } from '~/classes/Pod';
import PodDeletePopup from './view/PodDeletePopup.vue';
import PodLogPopup from "~/components/inspect/logs/PodLogPopup.vue";
import PodLogPopup from "~/components/pod/view/PodLogPopup.vue";
import PodViewPopup from "~/components/pod/view/PodViewPopup.vue";
import {usePopup} from "~/components/popup/Popup";
defineProps<{
const props = defineProps<{
pod: Pod
}>();
const showDeletePopup = ref(false);
const showLogPopup = ref(false);
const showViewPopup = ref(false);
</script>

View File

@ -11,7 +11,7 @@
<UiMask class="spaced-center" :prefix="env.key" :enabled="isSensitive(env.key)" :value="env.value"></UiMask>
</div>
<div v-if="error === true">
<UiPompt :prompt="new Prompt('This pod does not support retrieving environment variables using \'env\'', PromptType.ERROR)"></UiPompt>
<UiPrompt :prompt="new Prompt('This pod does not support retrieving environment variables using \'env\'', PromptType.ERROR)"></UiPrompt>
</div>
</div>
</ScrollComponent>
@ -19,12 +19,12 @@
</template>
<script setup lang="ts">
import { Prompt, PromptType } from '~/components/ui/prompt/Prompt';
import type { Container, Pod } from '~/classes/Pod';
import { EnvVar } from '../env/EnvVar';
import {EnvVar} from "~/components/pod/env/EnvVar";
import ContainerPicker from '~/components/ui/ContainerPicker.vue';
import UiMask from '~/components/ui/UiMask.vue';
import { Prompt, PromptType } from '~/components/ui/Prompt';
import UiPompt from '~/components/ui/UiPompt.vue';
import UiPrompt from '~/components/ui/prompt/UiPrompt.vue'
const props = defineProps<{
pod: Pod

View File

@ -1,5 +1,5 @@
<template>
<PopupTemplate size="SMALL" heading="Delete Pod" @close="emits('close')">
<PopupTemplate size="SMALL" heading="Delete Pod">
<div class="content-l">
<div class="content-m">
<div class="tile-m">
@ -12,7 +12,7 @@
</div>
</div>
<div class="center">
<UiButton :loading="loading" class="width-6rem hollow">Cancel</UiButton>
<UiButton :loading="loading" class="width-6rem hollow" @click="usePopup().close()">Cancel</UiButton>
<UiButton :loading="loading" class="width-6rem" icon="delete" reverse :onclick="() => del()">Delete</UiButton>
</div>
</div>
@ -23,24 +23,19 @@
import type { Metadata } from '~/classes/Metadata';
import { type Pod } from '~/classes/Pod';
import { deletePod } from '~/requests/pod';
import { usePopup } from "~/components/popup/Popup";
const props = defineProps<{
pod: Pod
}>();
const pod = usePopup().data() as Pod;
const loading = ref(false);
function del()
{
const metadata: Metadata = props.pod.metadata;
loading.value = true;
const metadata: Metadata = pod.metadata;
deletePod(metadata.namespace, metadata.name, () => {
emits('close');
usePopup().close();
});
}
const emits = defineEmits<{
(e: 'close'): void
}>()
</script>
<style scoped>

View File

@ -16,9 +16,10 @@
import { LogRepo } from '~/classes/LogRepo';
import type { Pod } from '~/classes/Pod';
import { Log } from '~/requests/logs';
import Console from "~/components/inspect/logs/Console.vue";
import Console from "~/components/inspect/console/Console.vue";
import UiIcon from "~/components/ui/UiIcon.vue";
import { ConsoleConfig } from '~/components/inspect/logs/ConsoleConfig'
import { ConsoleConfig } from '~/components/inspect/console/ConsoleConfig'
import {usePopup} from "~/components/popup/Popup";
const config = ref(ConsoleConfig.default(true, false, false));
@ -26,9 +27,7 @@ const base = ref();
const logs: Ref<Log[]> = ref([]);
const props = defineProps<{
pod: Pod
}>();
const pod = usePopup().data() as Pod;
const emits = defineEmits<{
(e: 'close'): void
@ -36,7 +35,7 @@ const emits = defineEmits<{
const logRepo = new LogRepo();
onMounted(() => {
logRepo.listen("pods", props.pod.metadata.namespace, props.pod.metadata.name,(_logs: Log[]) => {
logRepo.listen("pods", pod.metadata.namespace, pod.metadata.name,(_logs: Log[]) => {
logs.value.push(..._logs);
});
});

View File

@ -1,5 +1,5 @@
<template>
<PopupTemplate ref="base" :heading="StringUtils.format('%s/%s', pod.metadata.namespace, pod.metadata.name)" @close="emits('close')">
<PopupTemplate ref="base" :heading="StringUtils.format('%s/%s', pod.metadata.namespace, pod.metadata.name)">
<div class="col-2 expand">
<ScrollComponent>
<div class="content-l">
@ -54,29 +54,7 @@ import { calcAge, type Pod } from '~/classes/Pod';
import PhaseComponent from '~/components/PhaseComponent.vue';
import ScrollComponent from '~/components/ScrollComponent.vue';
import EnvironmentViewer from './EnvironmentViewer.vue';
import {usePopup} from "~/components/popup/Popup";
const base = ref();
const props = defineProps<{
pod: Pod
}>();
const emits = defineEmits<{
(e: 'close'): void
}>()
function open() {
base.value.open();
}
onMounted(() => {
document.addEventListener('keydown', (event) => {
if(event.key === 'Escape')
{
base.value.close();
}
});
})
defineExpose({ open });
const pod = usePopup().data() as Pod;
</script>

54
components/popup/Popup.ts Normal file
View File

@ -0,0 +1,54 @@
import type {DefineComponent} from "vue";
export const usePopup = defineStore('popup', {
state: () => ({
component: shallowRef<DefineComponent<any, any, any> | undefined>(undefined),
payload: undefined as any,
isOpen: false as boolean
}),
getters: {
data(): any {
return () => {
return this.payload;
}
},
get: (state) => {
return () => {
return state.component;
}
}
},
actions: {
open(component: DefineComponent<any, any, any>, payload?: any) {
this.component = component;
this.payload = payload;
this.isOpen = true
disableScrolling();
},
close() {
this.component = undefined;
this.payload = undefined;
this.isOpen = false;
enableScrolling();
}
}
})
function disableScrolling()
{
const body = document.getElementsByTagName('body');
for(const element of body)
{
element.style.overflow = "hidden";
}
}
function enableScrolling()
{
const body = document.getElementsByTagName('body');
for(const element of body)
{
element.style.overflow = "visible";
}
}

View File

@ -1,9 +1,9 @@
<template>
<div class="overlay center" @click="close" v-show="visible || true">
<div class="overlay center" @click="usePopup().close()">
<div class="popup" :class="size" @click.stop>
<div class="popup__header">
<h2>{{ heading }}</h2>
<UiButton icon="close" @click="() => close()" class="square"></UiButton>
<UiButton icon="close" @click="usePopup().close()" class="square"></UiButton>
</div>
<div class="popup__body">
<slot></slot>
@ -13,50 +13,19 @@
</template>
<script lang="ts" setup>
const visible = ref(false);
const emits = defineEmits<{
(e: 'close'): void
}>()
function close() {
enableScrolling();
emits('close');
visible.value = false;
}
function open() {
disableScrolling();
visible.value = true;
}
import {usePopup} from "~/components/popup/Popup";
defineProps({
heading: String,
size: String
})
defineExpose({
close,
open
})
function disableScrolling()
document.addEventListener('keydown', (event) => {
if(event.key === 'Escape')
{
const body = document.getElementsByTagName('body');
for(const element of body)
{
element.style.overflow = "hidden";
}
}
function enableScrolling()
{
const body = document.getElementsByTagName('body');
for(const element of body)
{
element.style.overflow = "visible";
}
usePopup().close();
}
});
</script>
<style scoped>
@ -69,7 +38,6 @@ function enableScrolling()
left: 0;
right: 0;
padding: 0.5rem;
padding: 0.5rem;
z-index: 2;
}

View File

@ -22,7 +22,6 @@ const emits = defineEmits<{
watch(selected, (selection) => {
if (selection != null)
{
console.log("X");
emits('selected', selection);
}
}, { immediate: true })

View File

@ -1,32 +1,42 @@
<template>
<PodComponent v-if="resource === 'pods'"></PodComponent>
<CustomResourceDefinitionComponent v-else-if="resource === 'custom-resource-definitions'"></CustomResourceDefinitionComponent>
<IngressComponent v-else-if="resource === 'ingresses'"></IngressComponent>
<ServiceComponent v-else-if="resource === 'services'"></ServiceComponent>
<DeploymentList v-else-if="resource === 'deployments'"></DeploymentList>
<NodeComponent v-else-if="resource === 'nodes'"></NodeComponent>
<SecretComponent v-else-if="resource === 'secrets'"></SecretComponent>
<ConfigMapList v-else-if="resource === 'config-maps'"></ConfigMapList>
<StatefulSetList v-else-if="resource === 'stateful-sets'"></StatefulSetList>
<PersistentVolumeList v-else-if="resource === 'pvs'"></PersistentVolumeList>
<PersistentVolumeClaimList v-else-if="resource === 'pvcs'"></PersistentVolumeClaimList>
<NamespaceList v-else-if="resource === 'namespaces'"></NamespaceList>
<Component v-if="components[resource]" :is="components[resource]"></Component>
<p v-else>Invalid resource</p>
</template>
<script setup lang="ts">
import PodComponent from '~/components/inspect/resources/PodList.vue';
import CustomResourceDefinitionComponent from '~/components/inspect/resources/CustomResourceDefinitionList.vue';
import IngressComponent from '~/components/inspect/resources/IngressList.vue';
import ServiceComponent from '~/components/inspect/resources/ServiceList.vue';
import PodList from '~/components/inspect/resources/PodList.vue';
import CustomResourceDefinitionList from '~/components/inspect/resources/CustomResourceDefinitionList.vue';
import IngressList from '~/components/inspect/resources/IngressList.vue';
import ServiceList from '~/components/inspect/resources/ServiceList.vue';
import DeploymentList from "~/components/inspect/resources/DeploymentList.vue";
import NodeComponent from '~/components/inspect/resources/NodeList.vue';
import SecretComponent from '~/components/inspect/resources/SecretList.vue';
import NodeList from '~/components/inspect/resources/NodeList.vue';
import SecretList from '~/components/inspect/resources/SecretList.vue';
import ConfigMapList from '~/components/inspect/resources/ConfigMapList.vue';
import StatefulSetList from '~/components/inspect/resources/StatefulSetList.vue';
import PersistentVolumeList from '~/components/inspect/resources/PersistentVolumeList.vue';
import PersistentVolumeClaimList from '~/components/inspect/resources/PersistentVolumeClaimList.vue';
import NamespaceList from '~/components/inspect/resources/NamespaceList.vue';
import type { Component } from "vue";
import {ResourceType} from "~/components/inspect/ResourceType";
const resource = useRoute().params.resource as string;
const resource = useRoute().params.resource as ResourceType;
const components: Record<ResourceType, Component> = {
[ResourceType.NODE]: NodeList,
[ResourceType.NAMESPACE]: NamespaceList,
[ResourceType.CUSTOM_RESOURCE_DEFINITION]: CustomResourceDefinitionList,
[ResourceType.POD]: PodList,
[ResourceType.DEPLOYMENT]: DeploymentList,
[ResourceType.STATEFUL_SET]: StatefulSetList,
[ResourceType.SERVICE]: ServiceList,
[ResourceType.INGRESS]: IngressList,
[ResourceType.SECRET]: SecretList,
[ResourceType.CONFIG_MAP]: ConfigMapList,
[ResourceType.PV]: PersistentVolumeList,
[ResourceType.PVC]: PersistentVolumeClaimList,
};
</script>

View File

@ -1,7 +1,7 @@
<template>
<div class="content-l">
<h2>Change Password</h2>
<UiPompt :prompt="prompt" @close="prompt = undefined"></UiPompt>
<UiPrompt :prompt="prompt" @close="prompt = undefined"></UiPrompt>
<div class="col-2 tile-l">
<UiInput label="New Password" required>
<input type="password" v-model="password">
@ -17,9 +17,9 @@
</template>
<script setup lang="ts">
import { Prompt, PromptType } from '~/components/ui/Prompt';
import { Prompt, PromptType } from '~/components/ui/prompt/Prompt';
import { changePassword } from '~/requests/user';
import UiPompt from '~/components/ui/UiPompt.vue';
import UiPrompt from '~/components/ui/prompt/UiPrompt.vue';
const password = ref('');
const repeatPassword = ref('');

View File

@ -4,7 +4,7 @@
<div class="content-l">
<h2 class="center">Kubooboo</h2>
<div class="center">
<img class="logo" src="@/assets/transparent_logo.png" alt=""></img>
<img class="logo" src="@/assets/transparent_logo.png" alt="">
</div>
<div class="center">
<h1>Login</h1>

View File

@ -1,5 +1,5 @@
import axios from "axios";
import { Node } from "~/classes/Node";
import {Node} from "~/classes/node/Node";
export function getNodes(onSuccess: (nodes: Node[]) => void)
{

View File

@ -1,24 +0,0 @@
import type {DefineComponent} from "vue";
export const usePopup = defineStore('namespace', {
state: () => ({
component: undefined as DefineComponent | undefined,
data: undefined as any,
isOpen: false as boolean
}),
getters: {
},
actions: {
open(component: DefineComponent, data?: any) {
this.component = component;
this.data = data;
this.isOpen = true;
},
close() {
this.component = undefined;
this.data = undefined;
this.isOpen = false;
}
}
})

23
utils/Memory.ts Normal file
View File

@ -0,0 +1,23 @@
export class Memory
{
constructor (
public value: number,
public unit: MemoryUnity
) {}
static format(input: number)
{
let dimension: number = 0;
while (input > 1024)
{
input = input / 1024;
dimension++;
}
return new Memory(input, dimension as MemoryUnity);
}
}
export enum MemoryUnity
{
RAW, KI, MI, GI, TI
}