✨ 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)
|
||||
{
|
||||
if(route.startsWith('/dashboard') && getToken() == null)
|
||||
if(route.startsWith('/account') && getToken() == null)
|
||||
{
|
||||
useRouter().push('/');
|
||||
}
|
||||
if(route === '/' && getToken() != null)
|
||||
{
|
||||
useRouter().push('/account/inspect/nodes/_all');
|
||||
}
|
||||
}
|
||||
|
||||
useHead({
|
||||
|
||||
@ -236,4 +236,9 @@
|
||||
.base-shape {
|
||||
height: 2.5rem;
|
||||
padding: 0.5rem;
|
||||
}
|
||||
|
||||
|
||||
.width-6rem {
|
||||
width: 6rem;
|
||||
}
|
||||
@ -11,8 +11,10 @@
|
||||
|
||||
html {
|
||||
--primary-color: rgb(87, 75, 255);
|
||||
--tile-color: rgb(255, 255, 255);
|
||||
background-color: rgb(240, 240, 240);
|
||||
--tile-color: #ebf1ff;
|
||||
background-color: rgb(255, 255, 255);
|
||||
--shade-light: #f3f3f3;
|
||||
--shade-dark: rgb(233, 233, 233);
|
||||
}
|
||||
|
||||
html * {
|
||||
@ -28,26 +30,36 @@ html, body, #__nuxt {
|
||||
display: grid;
|
||||
grid-template-columns: auto auto auto 1fr auto auto;
|
||||
align-content: start;
|
||||
background-color: rgb(247, 247, 247);
|
||||
background-color: rgb(255, 255, 255);
|
||||
}
|
||||
.resource, .header {
|
||||
display: contents;
|
||||
}
|
||||
.header > * {
|
||||
padding: 0.75rem;
|
||||
background-color: rgb(12, 12, 12);
|
||||
color: white;
|
||||
background-color: var(--tile-color);
|
||||
color: rgb(0, 0, 0);
|
||||
font-weight: bold;
|
||||
position: sticky;
|
||||
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 {
|
||||
padding: 0.25rem 0.75rem;
|
||||
display: flex;
|
||||
align-items: center;
|
||||
background-color: #f3f3f3;
|
||||
}
|
||||
.resource:hover > .grid-element {
|
||||
background-color: #c2c2c2;
|
||||
background-color: #dbdbdb;
|
||||
}
|
||||
.resource p {
|
||||
font-weight: 600;
|
||||
@ -55,4 +67,4 @@ html, body, #__nuxt {
|
||||
}
|
||||
.even > .grid-element {
|
||||
background-color: rgb(233, 233, 233);
|
||||
}
|
||||
}
|
||||
|
||||
@ -2,5 +2,63 @@ import type { Metadata } from "./Metadata";
|
||||
|
||||
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
|
||||
{
|
||||
name?: string;
|
||||
namespace?: string;
|
||||
creationTimestamp?: 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">
|
||||
<h2>Kubooboo</h2>
|
||||
<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 class="divider" :class="{ hide: !inNamespaceScopedResource }"></div>
|
||||
<div class="nav" :class="{ hide: !inNamespaceScopedResource }">
|
||||
<NuxtLink class="namespace" :to="base + '/_all'">Alle</NuxtLink>
|
||||
<NuxtLink v-for="namespace in namespaces" class="namespace" :to="base + '/' + namespace.metadata.name">{{ namespace.metadata.name }}</NuxtLink>
|
||||
<NuxtLink v-for="[key, value] in namespaces" class="namespace" :to="getRoute(resource, key)">{{ value }}</NuxtLink>
|
||||
</div>
|
||||
</div>
|
||||
<div class="left-center" v-if="user" @click="() => accountPopup.open()">
|
||||
@ -21,20 +20,30 @@
|
||||
<script setup lang="ts">
|
||||
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 namespaces = computed(namespaceStore.getNamespaces);
|
||||
|
||||
const currentNamespace = computed(namespaceStore.getCurrentNamespace)
|
||||
const namespaces = computed(() => namespaceStore.getNamespaceNames(true));
|
||||
|
||||
const user = getUser();
|
||||
|
||||
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 resource = useRoute().params.resource as string;
|
||||
if (resource)
|
||||
|
||||
@ -1,20 +1,25 @@
|
||||
<template>
|
||||
<PopupTemplate :heading="'Logs: ' + pod.metadata?.namespace + '/' + pod.metadata?.name" ref="base">
|
||||
<ScrollComponent ref="scrollComponent" v-show="logs && logs.length > 0">
|
||||
<div class="console">
|
||||
<p class="log" v-for="log, index in logs" :class="{ even: index % 2 }">{{ log }}</p>
|
||||
<ScrollComponent ref="scrollComponent" v-show="logs">
|
||||
<div class="console" v-if="logs">
|
||||
<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>
|
||||
</ScrollComponent>
|
||||
<div v-if="logs == null" class="center" style="height: 100%; width: 100%">
|
||||
<UiLoadingIcon size="2rem"></UiLoadingIcon>
|
||||
</div>
|
||||
</PopupTemplate>
|
||||
</template>
|
||||
|
||||
<script setup lang="ts">
|
||||
import dayjs from 'dayjs';
|
||||
import type { Pod } from '~/classes/Pod';
|
||||
import { getLogs } from '~/requests/logs';
|
||||
import { Log } from '~/requests/logs';
|
||||
|
||||
const base = ref();
|
||||
|
||||
const logs: Ref<string[] | undefined> = ref(undefined);
|
||||
const logs: Ref<Log[] | undefined> = ref(undefined);
|
||||
|
||||
const props = defineProps<{
|
||||
pod: Pod
|
||||
@ -26,7 +31,7 @@ function open()
|
||||
{
|
||||
logs.value = undefined;
|
||||
base.value.open();
|
||||
getLogs(props.pod.metadata?.uid, (_logs: string[]) => {
|
||||
getLogs(props.pod.metadata?.uid, (_logs: Log[]) => {
|
||||
logs.value = _logs;
|
||||
scrollComponent.value.scrollToBottom();
|
||||
});
|
||||
@ -39,19 +44,27 @@ defineExpose({
|
||||
|
||||
<style scoped>
|
||||
.log {
|
||||
display: grid;
|
||||
grid-template-columns: auto 1fr;
|
||||
padding: 0.25rem 1rem;
|
||||
gap: 1rem;
|
||||
}
|
||||
.log * {
|
||||
width: 100%;
|
||||
max-width: 100%;
|
||||
word-break: break-all;
|
||||
white-space: pre-wrap;
|
||||
color: white;
|
||||
padding: 0.25rem 1rem;
|
||||
line-height: 1.0.5rem;
|
||||
line-height: 1.15rem;
|
||||
font-weight: 600;
|
||||
}
|
||||
|
||||
.console {
|
||||
background-color: black;
|
||||
border-radius: 0.5rem;
|
||||
display: grid;
|
||||
padding: 1rem 0;
|
||||
align-content: start;
|
||||
}
|
||||
.even {
|
||||
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">
|
||||
<slot v-if="!loading"></slot>
|
||||
<div class="loading center" v-if="loading">
|
||||
<UiLoadingIcon></UiLoadingIcon>
|
||||
<UiLoadingIcon size="2.5rem"></UiLoadingIcon>
|
||||
</div>
|
||||
</div>
|
||||
</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">
|
||||
<p>Name</p>
|
||||
<p>Namespace</p>
|
||||
<p>Ingress Class Name</p>
|
||||
<p>Rules</p>
|
||||
<p>Actions</p>
|
||||
</div>
|
||||
<IngressComponent :ingress="ingress" v-for="ingress, index in ingresses" class="resource" :class="{ even: index % 2 }"></IngressComponent>
|
||||
</div>
|
||||
@ -19,6 +22,6 @@ const ingresses = ResourceRepo.init<Ingress>().load('ingresses').get();
|
||||
|
||||
<style scoped>
|
||||
.ingress-container {
|
||||
grid-template-columns: 1fr 1fr;
|
||||
grid-template-columns: 1fr 1fr 1fr 1fr auto;
|
||||
}
|
||||
</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>
|
||||
<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.namespace }}</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>
|
||||
</div>
|
||||
<LogPopup :pod="pod" ref="logPopup"></LogPopup>
|
||||
<PodViewPopup :pod="pod" ref="podViewPopup"></PodViewPopup>
|
||||
</div>
|
||||
</template>
|
||||
|
||||
@ -27,4 +28,6 @@ defineProps<{
|
||||
}>();
|
||||
|
||||
const logPopup = ref();
|
||||
|
||||
const podViewPopup = ref();
|
||||
</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>
|
||||
<div class="overlay" @click="close" v-show="visible">
|
||||
<div class="popup" @click.stop>
|
||||
<div class="overlay center" @click="close" v-show="visible">
|
||||
<div class="popup" :class="size" @click.stop>
|
||||
<div class="popup__header">
|
||||
<h2>{{ heading }}</h2>
|
||||
<UiButton icon="close" @click="() => close()" class="square"></UiButton>
|
||||
@ -26,7 +26,8 @@ function open() {
|
||||
}
|
||||
|
||||
defineProps({
|
||||
heading: String
|
||||
heading: String,
|
||||
size: String
|
||||
})
|
||||
|
||||
defineExpose({
|
||||
@ -55,7 +56,7 @@ function enableScrolling()
|
||||
|
||||
<style scoped>
|
||||
.overlay {
|
||||
background-color: rgba(223, 223, 223, 0.514);
|
||||
background-color: rgba(54, 54, 54, 0.514);
|
||||
backdrop-filter: blur(0.1rem);
|
||||
position: fixed;
|
||||
top: 0;
|
||||
@ -87,4 +88,8 @@ function enableScrolling()
|
||||
height: 100%;
|
||||
overflow: hidden;
|
||||
}
|
||||
.MEDIUM {
|
||||
height: auto;
|
||||
width: 740px;
|
||||
}
|
||||
</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 * {
|
||||
font-size: 1.25rem;
|
||||
}
|
||||
.button.extra-small {
|
||||
height: 1.25rem;
|
||||
padding: 0 0.25rem;
|
||||
border-radius: 0.25rem;
|
||||
gap: 0.25rem;
|
||||
}
|
||||
</style>
|
||||
@ -4,12 +4,20 @@
|
||||
|
||||
<script setup lang="ts">
|
||||
import UiIcon from './UiIcon.vue';
|
||||
import { Optional } from '#imports';
|
||||
|
||||
const props = defineProps<{
|
||||
size?: string
|
||||
}>();
|
||||
|
||||
const fontSize = Optional.ofNullable(props.size).orElse("1rem");
|
||||
</script>
|
||||
|
||||
<style scoped>
|
||||
.rotate{
|
||||
animation: rotate 1s linear infinite;
|
||||
user-select: none;
|
||||
font-size: v-bind(fontSize);
|
||||
}
|
||||
@keyframes rotate{
|
||||
to{ transform: rotate(360deg); }
|
||||
|
||||
@ -2,8 +2,9 @@
|
||||
<div class="account-page">
|
||||
<NuxtPage></NuxtPage>
|
||||
<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/settings">Settings</NuxtLink>
|
||||
</div>
|
||||
</div>
|
||||
</template>
|
||||
@ -15,7 +16,7 @@
|
||||
grid-template-rows: 1fr auto;
|
||||
}
|
||||
.footer {
|
||||
background-color: rgb(12, 12, 12);
|
||||
background-color: rgb(235, 235, 235);
|
||||
padding: 1rem;
|
||||
}
|
||||
</style>
|
||||
@ -1,9 +1,11 @@
|
||||
<template>
|
||||
<div id="app">
|
||||
<InspectSidebar></InspectSidebar>
|
||||
<ScrollComponent>
|
||||
<NuxtPage></NuxtPage>
|
||||
</ScrollComponent>
|
||||
<div class="page">
|
||||
<ScrollComponent>
|
||||
<NuxtPage></NuxtPage>
|
||||
</ScrollComponent>
|
||||
</div>
|
||||
</div>
|
||||
</template>
|
||||
|
||||
@ -24,4 +26,10 @@ onMounted(() => {
|
||||
min-height: 100%;
|
||||
align-items: flex-start;
|
||||
}
|
||||
.page {
|
||||
padding: 1rem;
|
||||
height: 100%;
|
||||
width: 100%;
|
||||
border-radius: 0.5rem;
|
||||
}
|
||||
</style>
|
||||
@ -5,6 +5,7 @@
|
||||
<ServiceComponent v-else-if="resource === 'services'"></ServiceComponent>
|
||||
<DeploymentComponent v-else-if="resource === 'deployments'"></DeploymentComponent>
|
||||
<NodeComponent v-else-if="resource === 'nodes'"></NodeComponent>
|
||||
<SecretComponent v-else-if="resource === 'secrets'"></SecretComponent>
|
||||
<p v-else>Invalid resource</p>
|
||||
</template>
|
||||
|
||||
@ -15,6 +16,7 @@ import IngressComponent from '~/components/inspect/resources/IngressComponent.vu
|
||||
import ServiceComponent from '~/components/inspect/resources/ServiceComponent.vue';
|
||||
import DeploymentComponent from '~/components/inspect/resources/DeploymentComponent.vue';
|
||||
import NodeComponent from '~/components/inspect/resources/NodeComponent.vue';
|
||||
import SecretComponent from '~/components/inspect/resources/SecretComponent.vue';
|
||||
|
||||
const resource = useRoute().params.resource as string;
|
||||
</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";
|
||||
|
||||
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: {
|
||||
Authorization: "Bearer " + requireToken()
|
||||
}
|
||||
|
||||
@ -12,6 +12,21 @@ export const useNamespaceStore = defineStore('namespace', {
|
||||
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) => {
|
||||
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