Add per container env

This commit is contained in:
Andreas Dinauer 2025-12-20 12:05:57 +01:00
parent f0e072bc70
commit 48780e5164
17 changed files with 212 additions and 75 deletions

View File

@ -26,9 +26,12 @@ class Status
phase?: PodStatus
}
class Container
export class Container
{
constructor (
public name: string,
public image: string
) {}
}
enum PodStatus

View File

@ -1,18 +1,22 @@
<template>
<div>
<p class="grid-element">{{ configMap.metadata.name }}</p>
<p class="grid-element" @click="() => show = true">{{ configMap.metadata.name }}</p>
<p class="grid-element">{{ configMap.metadata.namespace }}</p>
<p class="grid-element">{{ calcAge(configMap.metadata.creationTimestamp) }}</p>
<p class="grid-element"><span v-if="configMap.data">{{ Object.keys(configMap.data).length }}</span><span v-else>-</span></p>
<div class="grid-element">
<ActionButton>delete</ActionButton>
</div>
<ConfigmapViewComponent v-if="show" :config-map="configMap" @close="show = false"></ConfigmapViewComponent>
</div>
</template>
<script setup lang="ts">
import type { ConfigMap } from '~/classes/ConfigMap';
import { calcAge } from '~/classes/Pod';
import ConfigmapViewComponent from './view/ConfigmapViewComponent.vue';
const show = ref(false);
defineProps<{
configMap: ConfigMap

View File

@ -0,0 +1,23 @@
<template>
<PopupTemplate :heading="StringUtils.format('%s/%s', configMap.metadata.namespace, configMap.metadata.name)" @close="emits('close')">
<div class="content-m">
<h3>Data</h3>
<p class="tile-m" v-if="configMap.data" v-for="[key, value] in Object.entries(configMap.data)">{{ key }}: {{ value }}</p>
<UiPompt v-else :prompt="new Prompt('This config map contains no data.', PromptType.INFO)"></UiPompt>
</div>
</PopupTemplate>
</template>
<script setup lang="ts">
import type { ConfigMap } from '~/classes/ConfigMap';
import PopupTemplate from '~/components/popup/PopupTemplate.vue';
import { Prompt, PromptType } from '~/components/ui/Prompt';
defineProps<{
configMap: ConfigMap
}>();
const emits = defineEmits<{
(e: 'close'): void
}>()
</script>

View File

@ -0,0 +1,13 @@
<template>
<PopupTemplate>
</PopupTemplate>
</template>
<script setup lang="ts">
import PopupTemplate from "~/components/popup/PopupTemplate.vue";
</script>
<style scoped>
</style>

View File

@ -16,6 +16,7 @@
<script setup lang="ts">
import type { ConfigMap } from '~/classes/ConfigMap';
import { ResourceRepo } from '~/classes/repo/ResourceRepo';
import ConfigMapComponent from '~/components/configmap/ConfigMapComponent.vue';
const repo = ResourceRepo.init<ConfigMap>();
onMounted(() => {

View File

@ -16,11 +16,11 @@
<script setup lang="ts">
import type { Deployment } from '~/classes/Deployment';
import { ResourceRepo } from '~/classes/repo/ResourceRepo';
import DeploymentComponent from '~/components/deployments/DeploymentComponent.vue';
import DeploymentComponent from '~/components/deployment/DeploymentComponent.vue';
const repo = ResourceRepo.init<Deployment>();
onMounted(() => {
repo.listen("deployments");
repo.listen("deployment");
});
onUnmounted(() => {
repo.clear();

View File

@ -15,7 +15,7 @@
<script setup lang="ts">
import { ResourceRepo } from '~/classes/repo/ResourceRepo';
import type { StatefulSet } from '~/classes/StatefulSet';
import StatefulSetComponent from '~/components/deployments/StatefulSetComponent.vue';
import StatefulSetComponent from '~/components/statefulset/StatefulSetComponent.vue';
const repo = ResourceRepo.init<StatefulSet>();
onMounted(() => {

View File

@ -7,12 +7,15 @@ export class EnvVar
public value: string
) {}
static get(namespace: string, name: string, onSuccess: (envVars: EnvVar[]) => void)
static get(namespace: string, name: string, containerName: string, onSuccess: (envVars: EnvVar[]) => void, onError: () => void)
{
const url = StringUtils.format('%s/pods/%s/%s/env', ApiConfig.getHttpBase(), namespace, name);
const url = StringUtils.format('%s/pods/%s/%s/%s/env', ApiConfig.getHttpBase(), namespace, name, containerName);
axios.get<EnvVar[]>(url)
.then((response) => {
onSuccess(response.data);
})
.catch(() => {
onError();
});
}
}

View File

@ -0,0 +1,86 @@
<template>
<div class="content-l">
<h2>Env</h2>
<ContainerPicker :containers="pod.spec.containers" v-if="pod.spec.containers.length > 0" @selected="payload => container = payload"></ContainerPicker>
<UiInput v-if="error === false">
<input type="text" name="" id="" v-model="filter" placeholder="Filter...">
</UiInput>
<ScrollComponent>
<div>
<div class="env" v-for="env in filteredEnv" v-if="error === false">
<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>
</div>
</div>
</ScrollComponent>
</div>
</template>
<script setup lang="ts">
import type { Container, Pod } from '~/classes/Pod';
import { EnvVar } from '../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';
const props = defineProps<{
pod: Pod
}>();
const container: Ref<Container | undefined> = ref(undefined);
const error = ref(false);
const envs: Ref<EnvVar[] | undefined> = ref(undefined);
watch(container, (container) => {
if (container && container.name)
{
EnvVar.get(props.pod.metadata.namespace, props.pod.metadata.name, container.name, (_envs: EnvVar[]) => {
error.value = false;
envs.value = _envs;
}, () => {
error.value = true;
});
}
}, { immediate: true });
const filter = ref("");
const filteredEnv = computed(() => {
if (envs.value)
{
return envs.value.filter(item => item.key.toLowerCase().includes(filter.value.toLowerCase()));
}
});
function isSensitive(key: string)
{
const markers = ["token", "key", "password", "secret", "private", "credential", "auth", "jwt"];
for (const marker of markers)
{
if (key.toLowerCase().includes(marker) && !key.toLowerCase().includes("public"))
{
return true;
}
}
return false;
}
</script>
<style scoped>
.env {
background-color: var(--shade-light);
padding: 0.25rem 0.5rem;
}
.env.env:nth-of-type(2n) {
background-color: var(--shade-dark);
}
.env:hover {
background-color: #cecece;
}
.env {
display: grid;
grid-template-columns: auto 1fr;
}
</style>

View File

@ -38,21 +38,13 @@
<p class="tile-m" v-for="[key, value] in Object.entries(pod.metadata.labels)"><span>{{ key }}:</span>&nbsp;<span>{{ value }}</span></p>
</div>
</div>
</div>
</ScrollComponent>
<div class="content-l">
<h2>Env</h2>
<UiInput>
<input type="text" name="" id="" v-model="filter" placeholder="Filter...">
</UiInput>
<ScrollComponent>
<div>
<div class="env" v-for="env in filteredEnv">
<UiMask class="spaced-center" :prefix="env.key" :enabled="isSensitive(env.key)" :value="env.value"></UiMask>
<div class="content-m">
<h3>Containers</h3>
<p class="tile-m" v-for="container in pod.spec.containers">{{ container.name }}: {{ container.image }}</p>
</div>
</div>
</ScrollComponent>
</div>
<EnvironmentViewer :pod="pod"></EnvironmentViewer>
</div>
</PopupTemplate>
</template>
@ -60,9 +52,8 @@
<script setup lang="ts">
import { calcAge, type Pod } from '~/classes/Pod';
import PhaseComponent from '~/components/PhaseComponent.vue';
import { EnvVar } from '../env/EnvVar';
import ScrollComponent from '~/components/ScrollComponent.vue';
import UiMask from '~/components/ui/UiMask.vue';
import EnvironmentViewer from './EnvironmentViewer.vue';
const base = ref();
@ -74,21 +65,6 @@ const emits = defineEmits<{
(e: 'close'): void
}>()
const envs: Ref<EnvVar[] | undefined> = ref(undefined);
onMounted(() => {
EnvVar.get(props.pod.metadata.namespace, props.pod.metadata.name, (_envs: EnvVar[]) => {
envs.value = _envs;
});
})
const filter = ref("");
const filteredEnv = computed(() => {
if (envs.value)
{
return envs.value.filter(item => item.key.toLowerCase().includes(filter.value.toLowerCase()));
}
});
function open() {
base.value.open();
}
@ -103,34 +79,4 @@ onMounted(() => {
})
defineExpose({ open });
function isSensitive(key: string)
{
const markers = ["token", "key", "password", "secret", "private", "credential", "auth", "jwt"];
for (const marker of markers)
{
if (key.toLowerCase().includes(marker) && !key.toLowerCase().includes("public"))
{
return true;
}
}
return false;
}
</script>
<style scoped>
.env {
background-color: var(--shade-light);
padding: 0.25rem 0.5rem;
}
.env.env:nth-of-type(2n) {
background-color: var(--shade-dark);
}
.env:hover {
background-color: #cecece;
}
.env {
display: grid;
grid-template-columns: auto 1fr;
}
</style>

View File

@ -0,0 +1,54 @@
<template>
<div class="tile container-picker">
<div class="inner-container-picker">
<div class="container pointer" v-for="container in containers" :class="{ selected: container.name === selected?.name }" @click="() => selected = container">{{ container.name }}</div>
</div>
</div>
</template>
<script setup lang="ts">
import type { Container } from '~/classes/Pod';
const props = defineProps<{
containers: Container[]
}>();
const selected: Ref<Container | undefined> = ref(props.containers.at(0));
const emits = defineEmits<{
(e: 'selected', payload: Container): void
}>();
watch(selected, (selection) => {
if (selection != null)
{
console.log("X");
emits('selected', selection);
}
}, { immediate: true })
</script>
<style scoped>
.container-picker {
padding: 0.5rem;
height: 3rem;
display: flex;
align-items: flex-start;
}
.inner-container-picker {
background-color: white;
display: flex;
justify-content: flex-start;
border-radius: 0.25rem;
gap: 0.5rem;
}
.container {
padding: 0.25rem 0.5rem;
color: black;
border-radius: 0.25rem;
}
.container.selected {
background-color: var(--primary-color);
color: rgb(255, 255, 255);
}
</style>

View File

@ -9,5 +9,6 @@ export class Prompt
export enum PromptType
{
ERROR = "error",
SUCCESS = "success"
SUCCESS = "success",
INFO = "info"
}

View File

@ -20,15 +20,18 @@ const emit = defineEmits<{
<style scoped>
.prompt {
border-radius: 0.25rem;
height: 2.5rem;
min-height: 2.5rem;
}
.error {
background-color: rgb(218, 57, 57);
background-color: rgb(255, 74, 74);
}
.success {
background-color: rgb(43, 161, 49);
}
.info {
background-color: rgb(22, 79, 163);
}
.prompt * {
color: white;
color: rgb(255, 255, 255);
}
</style>

View File

@ -6,7 +6,7 @@
<h3>Kubooboo</h3>
<div class="left-center">
<NuxtLink to="/account/inspect/nodes/_all">Inspect</NuxtLink>
<NuxtLink to="/account/monitorings/nodes">Monitorings</NuxtLink>
<NuxtLink v-if="false" to="/account/monitorings/nodes">Monitorings</NuxtLink>
<NuxtLink to="/account/settings/password">Settings</NuxtLink>
<p class="pointer" @click="logout()">Logout</p>
</div>

View File

@ -3,7 +3,7 @@
<CustomResourceDefinitionComponent v-else-if="resource === 'custom-resource-definitions'"></CustomResourceDefinitionComponent>
<IngressComponent v-else-if="resource === 'ingresses'"></IngressComponent>
<ServiceComponent v-else-if="resource === 'services'"></ServiceComponent>
<DeploymentComponent v-else-if="resource === 'deployments'"></DeploymentComponent>
<DeploymentComponent v-else-if="resource === 'deployment'"></DeploymentComponent>
<NodeComponent v-else-if="resource === 'nodes'"></NodeComponent>
<SecretComponent v-else-if="resource === 'secrets'"></SecretComponent>
<ConfigMapList v-else-if="resource === 'config-maps'"></ConfigMapList>