✨ Add secrets and view popups
This commit is contained in:
parent
fe3dcd2165
commit
ee5ed78d35
6
app.vue
6
app.vue
@ -16,10 +16,14 @@ guard(useRoute().fullPath);
|
|||||||
|
|
||||||
function guard(route: string)
|
function guard(route: string)
|
||||||
{
|
{
|
||||||
if(route.startsWith('/dashboard') && getToken() == null)
|
if(route.startsWith('/account') && getToken() == null)
|
||||||
{
|
{
|
||||||
useRouter().push('/');
|
useRouter().push('/');
|
||||||
}
|
}
|
||||||
|
if(route === '/' && getToken() != null)
|
||||||
|
{
|
||||||
|
useRouter().push('/account/inspect/nodes/_all');
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
useHead({
|
useHead({
|
||||||
|
|||||||
@ -236,4 +236,9 @@
|
|||||||
.base-shape {
|
.base-shape {
|
||||||
height: 2.5rem;
|
height: 2.5rem;
|
||||||
padding: 0.5rem;
|
padding: 0.5rem;
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
.width-6rem {
|
||||||
|
width: 6rem;
|
||||||
}
|
}
|
||||||
@ -11,8 +11,10 @@
|
|||||||
|
|
||||||
html {
|
html {
|
||||||
--primary-color: rgb(87, 75, 255);
|
--primary-color: rgb(87, 75, 255);
|
||||||
--tile-color: rgb(255, 255, 255);
|
--tile-color: #ebf1ff;
|
||||||
background-color: rgb(240, 240, 240);
|
background-color: rgb(255, 255, 255);
|
||||||
|
--shade-light: #f3f3f3;
|
||||||
|
--shade-dark: rgb(233, 233, 233);
|
||||||
}
|
}
|
||||||
|
|
||||||
html * {
|
html * {
|
||||||
@ -28,26 +30,36 @@ html, body, #__nuxt {
|
|||||||
display: grid;
|
display: grid;
|
||||||
grid-template-columns: auto auto auto 1fr auto auto;
|
grid-template-columns: auto auto auto 1fr auto auto;
|
||||||
align-content: start;
|
align-content: start;
|
||||||
background-color: rgb(247, 247, 247);
|
background-color: rgb(255, 255, 255);
|
||||||
}
|
}
|
||||||
.resource, .header {
|
.resource, .header {
|
||||||
display: contents;
|
display: contents;
|
||||||
}
|
}
|
||||||
.header > * {
|
.header > * {
|
||||||
padding: 0.75rem;
|
padding: 0.75rem;
|
||||||
background-color: rgb(12, 12, 12);
|
background-color: var(--tile-color);
|
||||||
color: white;
|
color: rgb(0, 0, 0);
|
||||||
font-weight: bold;
|
font-weight: bold;
|
||||||
position: sticky;
|
position: sticky;
|
||||||
top: 0;
|
top: 0;
|
||||||
|
margin-bottom: 1rem;
|
||||||
|
}
|
||||||
|
.header *:first-of-type {
|
||||||
|
border-top-left-radius: 0rem;
|
||||||
|
border-bottom-left-radius: 0rem;
|
||||||
|
}
|
||||||
|
.header *:last-of-type {
|
||||||
|
border-top-right-radius: 0rem;
|
||||||
|
border-bottom-right-radius: 0rem;
|
||||||
}
|
}
|
||||||
.resource > .grid-element {
|
.resource > .grid-element {
|
||||||
padding: 0.25rem 0.75rem;
|
padding: 0.25rem 0.75rem;
|
||||||
display: flex;
|
display: flex;
|
||||||
align-items: center;
|
align-items: center;
|
||||||
|
background-color: #f3f3f3;
|
||||||
}
|
}
|
||||||
.resource:hover > .grid-element {
|
.resource:hover > .grid-element {
|
||||||
background-color: #c2c2c2;
|
background-color: #dbdbdb;
|
||||||
}
|
}
|
||||||
.resource p {
|
.resource p {
|
||||||
font-weight: 600;
|
font-weight: 600;
|
||||||
@ -55,4 +67,4 @@ html, body, #__nuxt {
|
|||||||
}
|
}
|
||||||
.even > .grid-element {
|
.even > .grid-element {
|
||||||
background-color: rgb(233, 233, 233);
|
background-color: rgb(233, 233, 233);
|
||||||
}
|
}
|
||||||
|
|||||||
@ -2,5 +2,63 @@ import type { Metadata } from "./Metadata";
|
|||||||
|
|
||||||
export class Ingress
|
export class Ingress
|
||||||
{
|
{
|
||||||
metadata?: Metadata;
|
constructor (
|
||||||
|
public metadata: Metadata,
|
||||||
|
public spec: IngressSpec
|
||||||
|
) { }
|
||||||
|
}
|
||||||
|
|
||||||
|
export class IngressSpec
|
||||||
|
{
|
||||||
|
constructor (
|
||||||
|
public ingressClassName: string,
|
||||||
|
public rules: IngressRule[],
|
||||||
|
public tls: IngressTLS[]
|
||||||
|
) { }
|
||||||
|
}
|
||||||
|
|
||||||
|
export class IngressRule
|
||||||
|
{
|
||||||
|
constructor (
|
||||||
|
public host: string,
|
||||||
|
public http: IngressRuleHttp
|
||||||
|
) { }
|
||||||
|
}
|
||||||
|
|
||||||
|
export class IngressRuleHttp
|
||||||
|
{
|
||||||
|
constructor (
|
||||||
|
public paths: IngressRulePath[]
|
||||||
|
) { }
|
||||||
|
}
|
||||||
|
|
||||||
|
export class IngressRulePath
|
||||||
|
{
|
||||||
|
constructor (
|
||||||
|
public path: string,
|
||||||
|
public pathType: string,
|
||||||
|
public backend: IngressRuleBackend
|
||||||
|
) { }
|
||||||
|
}
|
||||||
|
|
||||||
|
export class IngressRuleBackend
|
||||||
|
{
|
||||||
|
constructor (
|
||||||
|
public service: IngressRuleBackendService
|
||||||
|
) { }
|
||||||
|
}
|
||||||
|
|
||||||
|
export class IngressRuleBackendService
|
||||||
|
{
|
||||||
|
constructor (
|
||||||
|
public name: string
|
||||||
|
) { }
|
||||||
|
}
|
||||||
|
|
||||||
|
export class IngressTLS
|
||||||
|
{
|
||||||
|
constructor (
|
||||||
|
public hosts: string[],
|
||||||
|
public secretName: string
|
||||||
|
) {}
|
||||||
}
|
}
|
||||||
@ -1,7 +1,10 @@
|
|||||||
export class Metadata
|
export class Metadata
|
||||||
{
|
{
|
||||||
name?: string;
|
|
||||||
namespace?: string;
|
|
||||||
creationTimestamp?: string;
|
creationTimestamp?: string;
|
||||||
uid?: string;
|
uid?: string;
|
||||||
|
|
||||||
|
constructor (
|
||||||
|
public namespace: string,
|
||||||
|
public name: string
|
||||||
|
) { }
|
||||||
}
|
}
|
||||||
8
classes/Secret.ts
Normal file
8
classes/Secret.ts
Normal file
@ -0,0 +1,8 @@
|
|||||||
|
import type { Metadata } from "./Metadata";
|
||||||
|
|
||||||
|
export class Secret
|
||||||
|
{
|
||||||
|
constructor (
|
||||||
|
public metadata: Metadata
|
||||||
|
) { }
|
||||||
|
}
|
||||||
@ -1,14 +0,0 @@
|
|||||||
<template>
|
|
||||||
<div>
|
|
||||||
<p class="grid-element" v-if="ingress.metadata">{{ ingress.metadata.name }}</p>
|
|
||||||
<p class="grid-element" v-if="ingress.metadata">{{ ingress.metadata.namespace }}</p>
|
|
||||||
</div>
|
|
||||||
</template>
|
|
||||||
|
|
||||||
<script setup lang="ts">
|
|
||||||
import type { Ingress } from '~/classes/Ingress';
|
|
||||||
|
|
||||||
defineProps<{
|
|
||||||
ingress: Ingress
|
|
||||||
}>();
|
|
||||||
</script>
|
|
||||||
@ -3,12 +3,11 @@
|
|||||||
<div class="content-l">
|
<div class="content-l">
|
||||||
<h2>Kubooboo</h2>
|
<h2>Kubooboo</h2>
|
||||||
<div class="nav">
|
<div class="nav">
|
||||||
<NuxtLink class="resources" v-for="[key, value] of resources" :to="'/account/inspect/' + key + '/_all'" :class="{ 'router-link-active': useRoute().params.resource === key }">{{ value }}</NuxtLink>
|
<NuxtLink class="resources" v-for="[key, value] of resources" :to="getRoute(key, namespace)" :class="{ 'router-link-active': useRoute().params.resource === key }">{{ value }}</NuxtLink>
|
||||||
</div>
|
</div>
|
||||||
<div class="divider" :class="{ hide: !inNamespaceScopedResource }"></div>
|
<div class="divider" :class="{ hide: !inNamespaceScopedResource }"></div>
|
||||||
<div class="nav" :class="{ hide: !inNamespaceScopedResource }">
|
<div class="nav" :class="{ hide: !inNamespaceScopedResource }">
|
||||||
<NuxtLink class="namespace" :to="base + '/_all'">Alle</NuxtLink>
|
<NuxtLink v-for="[key, value] in namespaces" class="namespace" :to="getRoute(resource, key)">{{ value }}</NuxtLink>
|
||||||
<NuxtLink v-for="namespace in namespaces" class="namespace" :to="base + '/' + namespace.metadata.name">{{ namespace.metadata.name }}</NuxtLink>
|
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
<div class="left-center" v-if="user" @click="() => accountPopup.open()">
|
<div class="left-center" v-if="user" @click="() => accountPopup.open()">
|
||||||
@ -21,20 +20,30 @@
|
|||||||
<script setup lang="ts">
|
<script setup lang="ts">
|
||||||
import SidebarTemplate from './SidebarTemplate.vue';
|
import SidebarTemplate from './SidebarTemplate.vue';
|
||||||
|
|
||||||
import { useNamespaceStore } from '#imports';
|
import { StringUtils, useNamespaceStore } from '#imports';
|
||||||
|
|
||||||
const resources = new Map<string, string>([["nodes", "Nodes"], ["ingresses", "Ingresses"], ["services", "Services"], ["deployments", "Deployments"], ["pods", "Pods"], ["custom-resource-definitions", "CDRs"]]);
|
const resources = new Map<string, string>([["nodes", "Nodes"], ["ingresses", "Ingresses"], ["services", "Services"], ["deployments", "Deployments"], ["stateful-sets", "Stateful Sets"], ["pods", "Pods"], ["secrets", "Secrets"], ["custom-resource-definitions", "CDRs"]]);
|
||||||
|
|
||||||
const namespaceStore = useNamespaceStore();
|
const namespaceStore = useNamespaceStore();
|
||||||
|
|
||||||
const namespaces = computed(namespaceStore.getNamespaces);
|
const namespaces = computed(() => namespaceStore.getNamespaceNames(true));
|
||||||
|
|
||||||
const currentNamespace = computed(namespaceStore.getCurrentNamespace)
|
|
||||||
|
|
||||||
const user = getUser();
|
const user = getUser();
|
||||||
|
|
||||||
const accountPopup = ref();
|
const accountPopup = ref();
|
||||||
|
|
||||||
|
const resource = computed(() => {
|
||||||
|
return useRoute().params.resource as string;
|
||||||
|
});
|
||||||
|
|
||||||
|
const namespace = computed(() => {
|
||||||
|
return useRoute().params.namespace as string;
|
||||||
|
});
|
||||||
|
|
||||||
|
function getRoute(resource: string, namespace: string) {
|
||||||
|
return StringUtils.format("/account/inspect/%s/%s", resource, namespace)
|
||||||
|
}
|
||||||
|
|
||||||
const base = computed(() => {
|
const base = computed(() => {
|
||||||
const resource = useRoute().params.resource as string;
|
const resource = useRoute().params.resource as string;
|
||||||
if (resource)
|
if (resource)
|
||||||
|
|||||||
@ -1,20 +1,25 @@
|
|||||||
<template>
|
<template>
|
||||||
<PopupTemplate :heading="'Logs: ' + pod.metadata?.namespace + '/' + pod.metadata?.name" ref="base">
|
<PopupTemplate :heading="'Logs: ' + pod.metadata?.namespace + '/' + pod.metadata?.name" ref="base">
|
||||||
<ScrollComponent ref="scrollComponent" v-show="logs && logs.length > 0">
|
<ScrollComponent ref="scrollComponent" v-show="logs">
|
||||||
<div class="console">
|
<div class="console" v-if="logs">
|
||||||
<p class="log" v-for="log, index in logs" :class="{ even: index % 2 }">{{ log }}</p>
|
<p class="log" v-for="log, index in logs" :class="{ even: index % 2 }"><span>[{{ dayjs(log.timestamp).format('YYYY-MM-DD HH:mm') }}]</span><span>{{ log.message }}</span></p>
|
||||||
</div>
|
</div>
|
||||||
</ScrollComponent>
|
</ScrollComponent>
|
||||||
|
<div v-if="logs == null" class="center" style="height: 100%; width: 100%">
|
||||||
|
<UiLoadingIcon size="2rem"></UiLoadingIcon>
|
||||||
|
</div>
|
||||||
</PopupTemplate>
|
</PopupTemplate>
|
||||||
</template>
|
</template>
|
||||||
|
|
||||||
<script setup lang="ts">
|
<script setup lang="ts">
|
||||||
|
import dayjs from 'dayjs';
|
||||||
import type { Pod } from '~/classes/Pod';
|
import type { Pod } from '~/classes/Pod';
|
||||||
import { getLogs } from '~/requests/logs';
|
import { getLogs } from '~/requests/logs';
|
||||||
|
import { Log } from '~/requests/logs';
|
||||||
|
|
||||||
const base = ref();
|
const base = ref();
|
||||||
|
|
||||||
const logs: Ref<string[] | undefined> = ref(undefined);
|
const logs: Ref<Log[] | undefined> = ref(undefined);
|
||||||
|
|
||||||
const props = defineProps<{
|
const props = defineProps<{
|
||||||
pod: Pod
|
pod: Pod
|
||||||
@ -26,7 +31,7 @@ function open()
|
|||||||
{
|
{
|
||||||
logs.value = undefined;
|
logs.value = undefined;
|
||||||
base.value.open();
|
base.value.open();
|
||||||
getLogs(props.pod.metadata?.uid, (_logs: string[]) => {
|
getLogs(props.pod.metadata?.uid, (_logs: Log[]) => {
|
||||||
logs.value = _logs;
|
logs.value = _logs;
|
||||||
scrollComponent.value.scrollToBottom();
|
scrollComponent.value.scrollToBottom();
|
||||||
});
|
});
|
||||||
@ -39,19 +44,27 @@ defineExpose({
|
|||||||
|
|
||||||
<style scoped>
|
<style scoped>
|
||||||
.log {
|
.log {
|
||||||
|
display: grid;
|
||||||
|
grid-template-columns: auto 1fr;
|
||||||
|
padding: 0.25rem 1rem;
|
||||||
|
gap: 1rem;
|
||||||
|
}
|
||||||
|
.log * {
|
||||||
width: 100%;
|
width: 100%;
|
||||||
max-width: 100%;
|
max-width: 100%;
|
||||||
word-break: break-all;
|
word-break: break-all;
|
||||||
white-space: pre-wrap;
|
white-space: pre-wrap;
|
||||||
color: white;
|
color: white;
|
||||||
padding: 0.25rem 1rem;
|
line-height: 1.15rem;
|
||||||
line-height: 1.0.5rem;
|
font-weight: 600;
|
||||||
}
|
}
|
||||||
|
|
||||||
.console {
|
.console {
|
||||||
background-color: black;
|
background-color: black;
|
||||||
border-radius: 0.5rem;
|
border-radius: 0.5rem;
|
||||||
display: grid;
|
display: grid;
|
||||||
padding: 1rem 0;
|
padding: 1rem 0;
|
||||||
|
align-content: start;
|
||||||
}
|
}
|
||||||
.even {
|
.even {
|
||||||
background-color: rgb(24, 24, 24);
|
background-color: rgb(24, 24, 24);
|
||||||
|
|||||||
17
components/NamespacePicker.vue
Normal file
17
components/NamespacePicker.vue
Normal file
@ -0,0 +1,17 @@
|
|||||||
|
<template>
|
||||||
|
<div>
|
||||||
|
<UiInput>
|
||||||
|
<select name="" id="">
|
||||||
|
<option :value="key" v-for="[key, value] in namespaces">{{ value }}</option>
|
||||||
|
</select>
|
||||||
|
</UiInput>
|
||||||
|
</div>
|
||||||
|
</template>
|
||||||
|
|
||||||
|
<script setup lang="ts">
|
||||||
|
const namespaces = computed(() => useNamespaceStore().getNamespaceNames(false));
|
||||||
|
</script>
|
||||||
|
|
||||||
|
<style scoped>
|
||||||
|
|
||||||
|
</style>
|
||||||
@ -2,7 +2,7 @@
|
|||||||
<div class="table">
|
<div class="table">
|
||||||
<slot v-if="!loading"></slot>
|
<slot v-if="!loading"></slot>
|
||||||
<div class="loading center" v-if="loading">
|
<div class="loading center" v-if="loading">
|
||||||
<UiLoadingIcon></UiLoadingIcon>
|
<UiLoadingIcon size="2.5rem"></UiLoadingIcon>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
</template>
|
</template>
|
||||||
|
|||||||
23
components/ingress/IngressComponent.vue
Normal file
23
components/ingress/IngressComponent.vue
Normal file
@ -0,0 +1,23 @@
|
|||||||
|
<template>
|
||||||
|
<div @click="() => viewIngressPopup.open()">
|
||||||
|
<p class="grid-element">{{ ingress.metadata.name }}</p>
|
||||||
|
<p class="grid-element">{{ ingress.metadata.namespace }}</p>
|
||||||
|
<p class="grid-element">{{ ingress.spec.ingressClassName }}</p>
|
||||||
|
<p class="grid-element">{{ ingress.spec.rules.length }}</p>
|
||||||
|
<div class="grid-element action-buttons">
|
||||||
|
<ActionButton>delete</ActionButton>
|
||||||
|
</div>
|
||||||
|
<IngressViewPopup :ingress="ingress" ref="viewIngressPopup"></IngressViewPopup>
|
||||||
|
</div>
|
||||||
|
</template>
|
||||||
|
|
||||||
|
<script setup lang="ts">
|
||||||
|
import type { Ingress } from '~/classes/Ingress';
|
||||||
|
import IngressViewPopup from './view/IngressViewPopup.vue';
|
||||||
|
|
||||||
|
defineProps<{
|
||||||
|
ingress: Ingress
|
||||||
|
}>();
|
||||||
|
|
||||||
|
const viewIngressPopup = ref();
|
||||||
|
</script>
|
||||||
47
components/ingress/view/IngressViewPopup.vue
Normal file
47
components/ingress/view/IngressViewPopup.vue
Normal file
@ -0,0 +1,47 @@
|
|||||||
|
<template>
|
||||||
|
<PopupTemplate ref="base" :heading="ingress.metadata?.name + '/' + ingress.metadata?.namespace">
|
||||||
|
<div class="col-2">
|
||||||
|
<div class="content-l">
|
||||||
|
<h2>General</h2>
|
||||||
|
<div class="content-m">
|
||||||
|
<h3>Ingress Class Name</h3>
|
||||||
|
<p class="tile-m">{{ ingress.spec.ingressClassName }}</p>
|
||||||
|
</div>
|
||||||
|
<h2>TLS</h2>
|
||||||
|
<div class="content-m" v-for="tls in ingress.spec.tls">
|
||||||
|
<h3>Host(s): {{ tls.hosts.join(", ") }}</h3>
|
||||||
|
<p class="tile-m">Secret Name: {{ tls.secretName }}</p>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
<div class="content-l">
|
||||||
|
<h2>Routes</h2>
|
||||||
|
<div class="content-m" v-for="rule in ingress.spec.rules">
|
||||||
|
<h3>Host: {{ rule.host }}</h3>
|
||||||
|
<div class="tile-m" v-for="path in rule.http.paths">
|
||||||
|
<p>[{{ path.pathType }}] {{ path.path }}</p>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</PopupTemplate>
|
||||||
|
</template>
|
||||||
|
|
||||||
|
<script setup lang="ts">
|
||||||
|
import type { Ingress } from '~/classes/Ingress';
|
||||||
|
import PopupTemplate from '~/components/popup/PopupTemplate.vue';
|
||||||
|
|
||||||
|
const base = ref();
|
||||||
|
|
||||||
|
defineProps<{
|
||||||
|
ingress: Ingress
|
||||||
|
}>();
|
||||||
|
|
||||||
|
function open()
|
||||||
|
{
|
||||||
|
base.value.open();
|
||||||
|
}
|
||||||
|
|
||||||
|
defineExpose({
|
||||||
|
open
|
||||||
|
});
|
||||||
|
</script>
|
||||||
@ -4,6 +4,9 @@
|
|||||||
<div class="header">
|
<div class="header">
|
||||||
<p>Name</p>
|
<p>Name</p>
|
||||||
<p>Namespace</p>
|
<p>Namespace</p>
|
||||||
|
<p>Ingress Class Name</p>
|
||||||
|
<p>Rules</p>
|
||||||
|
<p>Actions</p>
|
||||||
</div>
|
</div>
|
||||||
<IngressComponent :ingress="ingress" v-for="ingress, index in ingresses" class="resource" :class="{ even: index % 2 }"></IngressComponent>
|
<IngressComponent :ingress="ingress" v-for="ingress, index in ingresses" class="resource" :class="{ even: index % 2 }"></IngressComponent>
|
||||||
</div>
|
</div>
|
||||||
@ -19,6 +22,6 @@ const ingresses = ResourceRepo.init<Ingress>().load('ingresses').get();
|
|||||||
|
|
||||||
<style scoped>
|
<style scoped>
|
||||||
.ingress-container {
|
.ingress-container {
|
||||||
grid-template-columns: 1fr 1fr;
|
grid-template-columns: 1fr 1fr 1fr 1fr auto;
|
||||||
}
|
}
|
||||||
</style>
|
</style>
|
||||||
33
components/inspect/resources/SecretComponent.vue
Normal file
33
components/inspect/resources/SecretComponent.vue
Normal file
@ -0,0 +1,33 @@
|
|||||||
|
<template>
|
||||||
|
<div class="resource-container secret-container">
|
||||||
|
<div class="header">
|
||||||
|
<div class="left-center">
|
||||||
|
<p>Name</p>
|
||||||
|
<UiButton icon="add" class="extra-small" reverse @click="() => secretAddComponent.open()">Add</UiButton>
|
||||||
|
</div>
|
||||||
|
<p>Namespace</p>
|
||||||
|
<p>Alter</p>
|
||||||
|
<p>Aktionen</p>
|
||||||
|
</div>
|
||||||
|
<SecretComponent v-for="secret, index in secrets" :secret="secret" class="resource" :class="{ even: index % 2 }"></SecretComponent>
|
||||||
|
<SecretAddComponent ref="secretAddComponent"></SecretAddComponent>
|
||||||
|
</div>
|
||||||
|
</template>
|
||||||
|
|
||||||
|
<script setup lang="ts">
|
||||||
|
import { ResourceRepo } from '~/classes/ResourceRepo';
|
||||||
|
import type { Secret } from '~/classes/Secret';
|
||||||
|
import SecretComponent from '~/components/secrets/SecretComponent.vue';
|
||||||
|
import SecretAddComponent from '~/components/secrets/SecretAddComponent.vue';
|
||||||
|
|
||||||
|
const secrets = ResourceRepo.init<Secret>().load("secrets").get();
|
||||||
|
|
||||||
|
const secretAddComponent = ref();
|
||||||
|
</script>
|
||||||
|
|
||||||
|
<style scoped>
|
||||||
|
.secret-container {
|
||||||
|
display: grid;
|
||||||
|
grid-template-columns: auto auto 1fr auto;
|
||||||
|
}
|
||||||
|
</style>
|
||||||
@ -1,5 +1,5 @@
|
|||||||
<template>
|
<template>
|
||||||
<div>
|
<div @click="() => podViewPopup.open()">
|
||||||
<p class="grid-element" v-if="pod.metadata">{{ pod.metadata.name }}</p>
|
<p class="grid-element" v-if="pod.metadata">{{ pod.metadata.name }}</p>
|
||||||
<p class="grid-element" v-if="pod.metadata">{{ pod.metadata.namespace }}</p>
|
<p class="grid-element" v-if="pod.metadata">{{ pod.metadata.namespace }}</p>
|
||||||
<p class="grid-element" v-if="pod.metadata">{{ calcAge(pod.metadata.creationTimestamp) }}</p>
|
<p class="grid-element" v-if="pod.metadata">{{ calcAge(pod.metadata.creationTimestamp) }}</p>
|
||||||
@ -13,6 +13,7 @@
|
|||||||
<ActionButton @click="() => deletePod(pod.metadata?.uid, () => {})" v-if="hasAnyRole(getUser(), ['admin', 'maintainer'])">delete</ActionButton>
|
<ActionButton @click="() => deletePod(pod.metadata?.uid, () => {})" v-if="hasAnyRole(getUser(), ['admin', 'maintainer'])">delete</ActionButton>
|
||||||
</div>
|
</div>
|
||||||
<LogPopup :pod="pod" ref="logPopup"></LogPopup>
|
<LogPopup :pod="pod" ref="logPopup"></LogPopup>
|
||||||
|
<PodViewPopup :pod="pod" ref="podViewPopup"></PodViewPopup>
|
||||||
</div>
|
</div>
|
||||||
</template>
|
</template>
|
||||||
|
|
||||||
@ -27,4 +28,6 @@ defineProps<{
|
|||||||
}>();
|
}>();
|
||||||
|
|
||||||
const logPopup = ref();
|
const logPopup = ref();
|
||||||
|
|
||||||
|
const podViewPopup = ref();
|
||||||
</script>
|
</script>
|
||||||
18
components/pod/env/EnvVar.ts
vendored
Normal file
18
components/pod/env/EnvVar.ts
vendored
Normal file
@ -0,0 +1,18 @@
|
|||||||
|
import axios from "axios";
|
||||||
|
|
||||||
|
export class EnvVar
|
||||||
|
{
|
||||||
|
constructor (
|
||||||
|
public key: string,
|
||||||
|
public value: string
|
||||||
|
) {}
|
||||||
|
|
||||||
|
static get(namespace: string, name: string, onSuccess: (envVars: EnvVar[]) => void)
|
||||||
|
{
|
||||||
|
const url = StringUtils.format('%s/pods/%s/%s/env', useRuntimeConfig().public.apiBase, namespace, name);
|
||||||
|
axios.get<EnvVar[]>(url)
|
||||||
|
.then((response) => {
|
||||||
|
onSuccess(response.data);
|
||||||
|
})
|
||||||
|
}
|
||||||
|
}
|
||||||
90
components/pod/view/PodViewPopup.vue
Normal file
90
components/pod/view/PodViewPopup.vue
Normal file
@ -0,0 +1,90 @@
|
|||||||
|
<template>
|
||||||
|
<PopupTemplate ref="base" :heading="StringUtils.format('%s/%s', pod.metadata.namespace, pod.metadata.name)">
|
||||||
|
<div class="col-2 expand">
|
||||||
|
<div class="content-l">
|
||||||
|
<h2>General</h2>
|
||||||
|
<div class="content-m">
|
||||||
|
<h3>Pod</h3>
|
||||||
|
<p class="tile-m">{{ StringUtils.format('%s/%s', pod.metadata.namespace, pod.metadata.name) }}</p>
|
||||||
|
</div>
|
||||||
|
<div class="content-m">
|
||||||
|
<h3>Age</h3>
|
||||||
|
<p class="tile-m">{{ calcAge(pod.metadata.creationTimestamp) }}</p>
|
||||||
|
</div>
|
||||||
|
<div class="content-m">
|
||||||
|
<h3>Status</h3>
|
||||||
|
<div class="left-center">
|
||||||
|
<PhaseComponent :phase="pod.status?.phase"></PhaseComponent>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
<div class="content-l">
|
||||||
|
<h2>Env</h2>
|
||||||
|
<UiInput label="Filter">
|
||||||
|
<input type="text" name="" id="" v-model="filter">
|
||||||
|
</UiInput>
|
||||||
|
<ScrollComponent>
|
||||||
|
<div>
|
||||||
|
<p class="env" v-for="env, index in filteredEnv">{{ env.key }} = {{ env.value }}</p>
|
||||||
|
</div>
|
||||||
|
</ScrollComponent>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</PopupTemplate>
|
||||||
|
</template>
|
||||||
|
|
||||||
|
<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';
|
||||||
|
|
||||||
|
const base = ref();
|
||||||
|
|
||||||
|
const props = defineProps<{
|
||||||
|
pod: Pod
|
||||||
|
}>();
|
||||||
|
|
||||||
|
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();
|
||||||
|
}
|
||||||
|
|
||||||
|
onMounted(() => {
|
||||||
|
document.addEventListener('keydown', (event) => {
|
||||||
|
if(event.key === 'Escape')
|
||||||
|
{
|
||||||
|
base.value.close();
|
||||||
|
}
|
||||||
|
});
|
||||||
|
})
|
||||||
|
|
||||||
|
defineExpose({ open });
|
||||||
|
</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;
|
||||||
|
}
|
||||||
|
</style>
|
||||||
@ -1,6 +1,6 @@
|
|||||||
<template>
|
<template>
|
||||||
<div class="overlay" @click="close" v-show="visible">
|
<div class="overlay center" @click="close" v-show="visible">
|
||||||
<div class="popup" @click.stop>
|
<div class="popup" :class="size" @click.stop>
|
||||||
<div class="popup__header">
|
<div class="popup__header">
|
||||||
<h2>{{ heading }}</h2>
|
<h2>{{ heading }}</h2>
|
||||||
<UiButton icon="close" @click="() => close()" class="square"></UiButton>
|
<UiButton icon="close" @click="() => close()" class="square"></UiButton>
|
||||||
@ -26,7 +26,8 @@ function open() {
|
|||||||
}
|
}
|
||||||
|
|
||||||
defineProps({
|
defineProps({
|
||||||
heading: String
|
heading: String,
|
||||||
|
size: String
|
||||||
})
|
})
|
||||||
|
|
||||||
defineExpose({
|
defineExpose({
|
||||||
@ -55,7 +56,7 @@ function enableScrolling()
|
|||||||
|
|
||||||
<style scoped>
|
<style scoped>
|
||||||
.overlay {
|
.overlay {
|
||||||
background-color: rgba(223, 223, 223, 0.514);
|
background-color: rgba(54, 54, 54, 0.514);
|
||||||
backdrop-filter: blur(0.1rem);
|
backdrop-filter: blur(0.1rem);
|
||||||
position: fixed;
|
position: fixed;
|
||||||
top: 0;
|
top: 0;
|
||||||
@ -87,4 +88,8 @@ function enableScrolling()
|
|||||||
height: 100%;
|
height: 100%;
|
||||||
overflow: hidden;
|
overflow: hidden;
|
||||||
}
|
}
|
||||||
|
.MEDIUM {
|
||||||
|
height: auto;
|
||||||
|
width: 740px;
|
||||||
|
}
|
||||||
</style>
|
</style>
|
||||||
52
components/secrets/SecretAddComponent.vue
Normal file
52
components/secrets/SecretAddComponent.vue
Normal file
@ -0,0 +1,52 @@
|
|||||||
|
<template>
|
||||||
|
<PopupTemplate heading="Add Secret" size="MEDIUM" ref="base">
|
||||||
|
<div class="content-l">
|
||||||
|
<h3>Type</h3>
|
||||||
|
<UiInput>
|
||||||
|
<select name="" id="">
|
||||||
|
<option value="generic">Generic</option>
|
||||||
|
<option value="tls">TLS</option>
|
||||||
|
<option value="basic-auth">Basic Auth</option>
|
||||||
|
<option value="docker-registry">Docker Registry</option>
|
||||||
|
</select>
|
||||||
|
</UiInput>
|
||||||
|
<h3>Metadata</h3>
|
||||||
|
<div class="tile-m col-2">
|
||||||
|
<UiInput label="Name">
|
||||||
|
<input type="text">
|
||||||
|
</UiInput>
|
||||||
|
<UiInput label="Namespace">
|
||||||
|
<NamespacePicker></NamespacePicker>
|
||||||
|
</UiInput>
|
||||||
|
</div>
|
||||||
|
<h3>Data</h3>
|
||||||
|
<div class="col-2 tile-m">
|
||||||
|
<UiInput label="Key">
|
||||||
|
<input type="text">
|
||||||
|
</UiInput>
|
||||||
|
<UiInput label="Value">
|
||||||
|
<input type="text">
|
||||||
|
</UiInput>
|
||||||
|
</div>
|
||||||
|
<div class="center">
|
||||||
|
<UiButton class="hollow width-6rem">Cancel</UiButton>
|
||||||
|
<UiButton class="width-6rem">Create</UiButton>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</PopupTemplate>
|
||||||
|
</template>
|
||||||
|
|
||||||
|
<script setup lang="ts">
|
||||||
|
import type PopupTemplate from '../popup/PopupTemplate.vue';
|
||||||
|
import NamespacePicker from '../NamespacePicker.vue';
|
||||||
|
|
||||||
|
const base = ref();
|
||||||
|
|
||||||
|
defineExpose({
|
||||||
|
open: () => base.value.open()
|
||||||
|
});
|
||||||
|
</script>
|
||||||
|
|
||||||
|
<style scoped>
|
||||||
|
|
||||||
|
</style>
|
||||||
23
components/secrets/SecretComponent.vue
Normal file
23
components/secrets/SecretComponent.vue
Normal file
@ -0,0 +1,23 @@
|
|||||||
|
<template>
|
||||||
|
<div class="resource">
|
||||||
|
<p class="grid-element">{{ secret.metadata.name }}</p>
|
||||||
|
<p class="grid-element">{{ secret.metadata.namespace }}</p>
|
||||||
|
<p class="grid-element">-</p>
|
||||||
|
<div class="grid-element action-buttons">
|
||||||
|
<ActionButton>edit</ActionButton>
|
||||||
|
<ActionButton>delete</ActionButton>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</template>
|
||||||
|
|
||||||
|
<script setup lang="ts">
|
||||||
|
import type { Secret } from '~/classes/Secret';
|
||||||
|
|
||||||
|
defineProps<{
|
||||||
|
secret: Secret
|
||||||
|
}>();
|
||||||
|
</script>
|
||||||
|
|
||||||
|
<style scoped>
|
||||||
|
|
||||||
|
</style>
|
||||||
13
components/secrets/types/AddTlsSecret.vue
Normal file
13
components/secrets/types/AddTlsSecret.vue
Normal file
@ -0,0 +1,13 @@
|
|||||||
|
<template>
|
||||||
|
<div>
|
||||||
|
|
||||||
|
</div>
|
||||||
|
</template>
|
||||||
|
|
||||||
|
<script setup lang="ts">
|
||||||
|
|
||||||
|
</script>
|
||||||
|
|
||||||
|
<style scoped>
|
||||||
|
|
||||||
|
</style>
|
||||||
@ -94,4 +94,10 @@ const props = defineProps<{
|
|||||||
.button.small * {
|
.button.small * {
|
||||||
font-size: 1.25rem;
|
font-size: 1.25rem;
|
||||||
}
|
}
|
||||||
|
.button.extra-small {
|
||||||
|
height: 1.25rem;
|
||||||
|
padding: 0 0.25rem;
|
||||||
|
border-radius: 0.25rem;
|
||||||
|
gap: 0.25rem;
|
||||||
|
}
|
||||||
</style>
|
</style>
|
||||||
@ -4,12 +4,20 @@
|
|||||||
|
|
||||||
<script setup lang="ts">
|
<script setup lang="ts">
|
||||||
import UiIcon from './UiIcon.vue';
|
import UiIcon from './UiIcon.vue';
|
||||||
|
import { Optional } from '#imports';
|
||||||
|
|
||||||
|
const props = defineProps<{
|
||||||
|
size?: string
|
||||||
|
}>();
|
||||||
|
|
||||||
|
const fontSize = Optional.ofNullable(props.size).orElse("1rem");
|
||||||
</script>
|
</script>
|
||||||
|
|
||||||
<style scoped>
|
<style scoped>
|
||||||
.rotate{
|
.rotate{
|
||||||
animation: rotate 1s linear infinite;
|
animation: rotate 1s linear infinite;
|
||||||
user-select: none;
|
user-select: none;
|
||||||
|
font-size: v-bind(fontSize);
|
||||||
}
|
}
|
||||||
@keyframes rotate{
|
@keyframes rotate{
|
||||||
to{ transform: rotate(360deg); }
|
to{ transform: rotate(360deg); }
|
||||||
|
|||||||
@ -2,8 +2,9 @@
|
|||||||
<div class="account-page">
|
<div class="account-page">
|
||||||
<NuxtPage></NuxtPage>
|
<NuxtPage></NuxtPage>
|
||||||
<div class="left-center footer">
|
<div class="left-center footer">
|
||||||
<NuxtLink to="/account/inspect">Inspect</NuxtLink>
|
<NuxtLink to="/account/inspect/nodes/_all">Inspect</NuxtLink>
|
||||||
<NuxtLink to="/account/monitorings/nodes">Monitorings</NuxtLink>
|
<NuxtLink to="/account/monitorings/nodes">Monitorings</NuxtLink>
|
||||||
|
<NuxtLink to="/account/settings">Settings</NuxtLink>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
</template>
|
</template>
|
||||||
@ -15,7 +16,7 @@
|
|||||||
grid-template-rows: 1fr auto;
|
grid-template-rows: 1fr auto;
|
||||||
}
|
}
|
||||||
.footer {
|
.footer {
|
||||||
background-color: rgb(12, 12, 12);
|
background-color: rgb(235, 235, 235);
|
||||||
padding: 1rem;
|
padding: 1rem;
|
||||||
}
|
}
|
||||||
</style>
|
</style>
|
||||||
@ -1,9 +1,11 @@
|
|||||||
<template>
|
<template>
|
||||||
<div id="app">
|
<div id="app">
|
||||||
<InspectSidebar></InspectSidebar>
|
<InspectSidebar></InspectSidebar>
|
||||||
<ScrollComponent>
|
<div class="page">
|
||||||
<NuxtPage></NuxtPage>
|
<ScrollComponent>
|
||||||
</ScrollComponent>
|
<NuxtPage></NuxtPage>
|
||||||
|
</ScrollComponent>
|
||||||
|
</div>
|
||||||
</div>
|
</div>
|
||||||
</template>
|
</template>
|
||||||
|
|
||||||
@ -24,4 +26,10 @@ onMounted(() => {
|
|||||||
min-height: 100%;
|
min-height: 100%;
|
||||||
align-items: flex-start;
|
align-items: flex-start;
|
||||||
}
|
}
|
||||||
|
.page {
|
||||||
|
padding: 1rem;
|
||||||
|
height: 100%;
|
||||||
|
width: 100%;
|
||||||
|
border-radius: 0.5rem;
|
||||||
|
}
|
||||||
</style>
|
</style>
|
||||||
@ -5,6 +5,7 @@
|
|||||||
<ServiceComponent v-else-if="resource === 'services'"></ServiceComponent>
|
<ServiceComponent v-else-if="resource === 'services'"></ServiceComponent>
|
||||||
<DeploymentComponent v-else-if="resource === 'deployments'"></DeploymentComponent>
|
<DeploymentComponent v-else-if="resource === 'deployments'"></DeploymentComponent>
|
||||||
<NodeComponent v-else-if="resource === 'nodes'"></NodeComponent>
|
<NodeComponent v-else-if="resource === 'nodes'"></NodeComponent>
|
||||||
|
<SecretComponent v-else-if="resource === 'secrets'"></SecretComponent>
|
||||||
<p v-else>Invalid resource</p>
|
<p v-else>Invalid resource</p>
|
||||||
</template>
|
</template>
|
||||||
|
|
||||||
@ -15,6 +16,7 @@ import IngressComponent from '~/components/inspect/resources/IngressComponent.vu
|
|||||||
import ServiceComponent from '~/components/inspect/resources/ServiceComponent.vue';
|
import ServiceComponent from '~/components/inspect/resources/ServiceComponent.vue';
|
||||||
import DeploymentComponent from '~/components/inspect/resources/DeploymentComponent.vue';
|
import DeploymentComponent from '~/components/inspect/resources/DeploymentComponent.vue';
|
||||||
import NodeComponent from '~/components/inspect/resources/NodeComponent.vue';
|
import NodeComponent from '~/components/inspect/resources/NodeComponent.vue';
|
||||||
|
import SecretComponent from '~/components/inspect/resources/SecretComponent.vue';
|
||||||
|
|
||||||
const resource = useRoute().params.resource as string;
|
const resource = useRoute().params.resource as string;
|
||||||
</script>
|
</script>
|
||||||
13
pages/account/settings/index.vue
Normal file
13
pages/account/settings/index.vue
Normal file
@ -0,0 +1,13 @@
|
|||||||
|
<template>
|
||||||
|
<div>
|
||||||
|
|
||||||
|
</div>
|
||||||
|
</template>
|
||||||
|
|
||||||
|
<script setup lang="ts">
|
||||||
|
|
||||||
|
</script>
|
||||||
|
|
||||||
|
<style scoped>
|
||||||
|
|
||||||
|
</style>
|
||||||
@ -1,8 +1,16 @@
|
|||||||
import axios from "axios";
|
import axios from "axios";
|
||||||
|
|
||||||
export function getLogs(podId: string | undefined, onSuccess: (logs: string[]) => void)
|
export class Log
|
||||||
{
|
{
|
||||||
axios.get<string[]>(useRuntimeConfig().public.apiBase + '/pods/' + podId + "/logs", {
|
constructor (
|
||||||
|
public timestamp: string,
|
||||||
|
public message: string
|
||||||
|
) {}
|
||||||
|
}
|
||||||
|
|
||||||
|
export function getLogs(podId: string | undefined, onSuccess: (logs: Log[]) => void)
|
||||||
|
{
|
||||||
|
axios.get<Log[]>(useRuntimeConfig().public.apiBase + '/pods/' + podId + "/logs", {
|
||||||
headers: {
|
headers: {
|
||||||
Authorization: "Bearer " + requireToken()
|
Authorization: "Bearer " + requireToken()
|
||||||
}
|
}
|
||||||
|
|||||||
@ -12,6 +12,21 @@ export const useNamespaceStore = defineStore('namespace', {
|
|||||||
return state.namespaces;
|
return state.namespaces;
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
|
getNamespaceNames: (state) => {
|
||||||
|
return (includeAll?: boolean): Map<string, string> | undefined => {
|
||||||
|
if (state.namespaces)
|
||||||
|
{
|
||||||
|
const result = new Map<string, string>();
|
||||||
|
if (includeAll === true)
|
||||||
|
{
|
||||||
|
result.set('_all', 'Alle')
|
||||||
|
}
|
||||||
|
state.namespaces.forEach(namespace => { result.set(namespace.metadata.name, namespace.metadata.name) });
|
||||||
|
return result;
|
||||||
|
}
|
||||||
|
return undefined;
|
||||||
|
}
|
||||||
|
},
|
||||||
getCurrentNamespace: (state) => {
|
getCurrentNamespace: (state) => {
|
||||||
return (): Namespace | undefined => state.currentNamespace;
|
return (): Namespace | undefined => state.currentNamespace;
|
||||||
}
|
}
|
||||||
|
|||||||
42
utils/Optional.ts
Normal file
42
utils/Optional.ts
Normal file
@ -0,0 +1,42 @@
|
|||||||
|
export class Optional<T>
|
||||||
|
{
|
||||||
|
private object?: T;
|
||||||
|
|
||||||
|
private constructor(object?: T)
|
||||||
|
{
|
||||||
|
this.object = object;
|
||||||
|
}
|
||||||
|
|
||||||
|
static ofNullable<T>(object?: T): Optional<T>
|
||||||
|
{
|
||||||
|
return new Optional(object);
|
||||||
|
}
|
||||||
|
|
||||||
|
isEmpty()
|
||||||
|
{
|
||||||
|
return this.object == null;
|
||||||
|
}
|
||||||
|
|
||||||
|
isPresent()
|
||||||
|
{
|
||||||
|
return this.object != null;
|
||||||
|
}
|
||||||
|
|
||||||
|
orElse(other: T): T
|
||||||
|
{
|
||||||
|
if(this.object != null)
|
||||||
|
{
|
||||||
|
return this.object;
|
||||||
|
}
|
||||||
|
return other;
|
||||||
|
}
|
||||||
|
|
||||||
|
get(): T
|
||||||
|
{
|
||||||
|
if(this.object != null)
|
||||||
|
{
|
||||||
|
return this.object;
|
||||||
|
}
|
||||||
|
throw new Error("No value present in Optional.");
|
||||||
|
}
|
||||||
|
}
|
||||||
22
utils/StringUtils.ts
Normal file
22
utils/StringUtils.ts
Normal file
@ -0,0 +1,22 @@
|
|||||||
|
export class StringUtils
|
||||||
|
{
|
||||||
|
static format(template: string, ...varContext: string[]): string
|
||||||
|
{
|
||||||
|
const context = Array.from(varContext);
|
||||||
|
let contextIndex = 0;
|
||||||
|
while (template.includes("%s"))
|
||||||
|
{
|
||||||
|
const currentContext = context.at(contextIndex);
|
||||||
|
if (currentContext != null)
|
||||||
|
{
|
||||||
|
template = template.replace("%s", currentContext);
|
||||||
|
contextIndex++;
|
||||||
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
|
return template;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return template;
|
||||||
|
}
|
||||||
|
}
|
||||||
Loading…
x
Reference in New Issue
Block a user