💄 Improve UI
This commit is contained in:
parent
a8050fa958
commit
1a1dd92808
@ -133,7 +133,7 @@
|
||||
}
|
||||
|
||||
.tile, *[class^='tile-'], *[class*=' tile-'] {
|
||||
background-color: #ebebeb;
|
||||
background-color: var(--tile-color);
|
||||
border-radius: 0.25rem;
|
||||
overflow: hidden;
|
||||
width: 100%;
|
||||
|
||||
@ -6,10 +6,16 @@
|
||||
margin: 0;
|
||||
font-weight: 600;
|
||||
font-family: "Source Code Pro", monospace;
|
||||
text-decoration: none;
|
||||
}
|
||||
|
||||
html {
|
||||
--primary-color: rgb(87, 75, 255);
|
||||
background-color: #f1f1f1;
|
||||
--tile-color: rgb(226, 226, 226);
|
||||
}
|
||||
html * {
|
||||
color: rgb(31, 31, 31);
|
||||
}
|
||||
|
||||
html, body, #__nuxt {
|
||||
@ -21,14 +27,14 @@ html, body, #__nuxt {
|
||||
display: grid;
|
||||
grid-template-columns: auto auto auto 1fr auto auto;
|
||||
align-content: start;
|
||||
background-color: #ebebeb;
|
||||
background-color: rgb(228, 228, 228);
|
||||
}
|
||||
.resource, .header {
|
||||
display: contents;
|
||||
}
|
||||
.header > * {
|
||||
padding: 0.75rem;
|
||||
background-color: rgb(29, 29, 29);
|
||||
background-color: rgb(12, 12, 12);
|
||||
color: white;
|
||||
font-weight: bold;
|
||||
position: sticky;
|
||||
@ -44,5 +50,5 @@ html, body, #__nuxt {
|
||||
font-family: "Source Code Pro", monospace;
|
||||
}
|
||||
.even > .grid-element {
|
||||
background-color: rgb(216, 216, 216);
|
||||
background-color: rgb(202, 202, 202);
|
||||
}
|
||||
@ -2,14 +2,20 @@ import type { Metadata } from "./Metadata";
|
||||
|
||||
export class NodeStats
|
||||
{
|
||||
node?: Node;
|
||||
constructor (
|
||||
public node: Node,
|
||||
) { }
|
||||
|
||||
relativeCpuUsage?: number;
|
||||
relativeMemory?: number;
|
||||
}
|
||||
|
||||
class Node
|
||||
export class Node
|
||||
{
|
||||
metadata?: Metadata;
|
||||
constructor (
|
||||
public metadata: Metadata
|
||||
) { }
|
||||
|
||||
status?: Status;
|
||||
}
|
||||
|
||||
|
||||
@ -4,9 +4,12 @@ import type { Metadata } from "./Metadata";
|
||||
|
||||
export class Pod
|
||||
{
|
||||
metadata?: Metadata
|
||||
status?: Status
|
||||
spec?: Spec
|
||||
|
||||
constructor (
|
||||
public metadata: Metadata
|
||||
) { }
|
||||
}
|
||||
|
||||
class Spec {
|
||||
|
||||
12
classes/Threshold.ts
Normal file
12
classes/Threshold.ts
Normal file
@ -0,0 +1,12 @@
|
||||
export class Threshold
|
||||
{
|
||||
constructor (
|
||||
public value: number,
|
||||
public calc: (value: number) => State
|
||||
) { }
|
||||
}
|
||||
|
||||
export enum State
|
||||
{
|
||||
GREEN = "GREEN", ORANGE = "ORANGE", RED = "RED"
|
||||
}
|
||||
31
classes/Ticker.ts
Normal file
31
classes/Ticker.ts
Normal file
@ -0,0 +1,31 @@
|
||||
export function useTicker(interval: string)
|
||||
{
|
||||
const seconds = Duration.parse(interval);
|
||||
const ticker = ref(new Date());
|
||||
setInterval(() => {
|
||||
ticker.value = new Date();
|
||||
}, seconds * 1000);
|
||||
return ticker;
|
||||
}
|
||||
|
||||
export class Duration
|
||||
{
|
||||
static parse(input: string): number {
|
||||
if (input.length > 1) {
|
||||
const unit = input.charAt(input.length - 1);
|
||||
const value = parseInt(input.substring(0, input.length - 1), 10);
|
||||
switch (unit)
|
||||
{
|
||||
case 's':
|
||||
return value;
|
||||
case 'm':
|
||||
return value * 60;
|
||||
case 'h':
|
||||
return value * 60 * 60;
|
||||
default:
|
||||
throw new Error(`Invalid unit ${unit}`);
|
||||
}
|
||||
}
|
||||
throw new Error("Invalid input");
|
||||
}
|
||||
}
|
||||
@ -1,13 +1,13 @@
|
||||
<template>
|
||||
<nav class="sidebar">
|
||||
<SidebarTemplate>
|
||||
<div class="content-l">
|
||||
<h2>Kubooboo</h2>
|
||||
<div class="nav">
|
||||
<NuxtLink class="resources" to="/dashboard/nodes">Nodes</NuxtLink>
|
||||
<NuxtLink class="resources" to="/dashboard/ingresses">Ingresses</NuxtLink>
|
||||
<NuxtLink class="resources" to="/dashboard/services">Services</NuxtLink>
|
||||
<NuxtLink class="resources" to="/dashboard/deployments">Deployments</NuxtLink>
|
||||
<NuxtLink class="resources" to="/dashboard/pods">Pods</NuxtLink>
|
||||
<NuxtLink class="resources" to="/account/inspect/nodes">Nodes</NuxtLink>
|
||||
<NuxtLink class="resources" to="/account/inspect/ingresses">Ingresses</NuxtLink>
|
||||
<NuxtLink class="resources" to="/account/inspect/services">Services</NuxtLink>
|
||||
<NuxtLink class="resources" to="/account/inspect/deployments">Deployments</NuxtLink>
|
||||
<NuxtLink class="resources" to="/account/inspect/pods">Pods</NuxtLink>
|
||||
</div>
|
||||
<div class="divider" :class="{ hide: !inNamespaceScopedResource }"></div>
|
||||
<div :class="{ hide: !inNamespaceScopedResource }">
|
||||
@ -23,11 +23,12 @@
|
||||
<UiIcon>account_circle</UiIcon>
|
||||
<p>{{ user.username }}</p>
|
||||
</div>
|
||||
</nav>
|
||||
<AccountPopup ref="accountPopup"></AccountPopup>
|
||||
</SidebarTemplate>
|
||||
</template>
|
||||
|
||||
<script setup lang="ts">
|
||||
import SidebarTemplate from './SidebarTemplate.vue';
|
||||
|
||||
import { useNamespaceStore } from '#imports';
|
||||
|
||||
const namespaceStore = useNamespaceStore();
|
||||
@ -41,46 +42,10 @@ const user = getUser();
|
||||
const accountPopup = ref();
|
||||
|
||||
const inNamespaceScopedResource: ComputedRef<boolean> = computed(() => {
|
||||
if(useRoute().fullPath.startsWith('/dashboard/nodes'))
|
||||
if(useRoute().fullPath.startsWith('/account/inspect/nodes'))
|
||||
{
|
||||
return false;
|
||||
}
|
||||
return true;
|
||||
});
|
||||
</script>
|
||||
|
||||
<style scoped>
|
||||
.sidebar {
|
||||
padding: 0.75rem;
|
||||
background-color: rgb(29, 29, 29);
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
justify-content: space-between;
|
||||
gap: 1rem;
|
||||
height: 100%;
|
||||
}
|
||||
.sidebar * {
|
||||
color: white;
|
||||
}
|
||||
.namespace, .resources {
|
||||
padding: 0.5rem;
|
||||
border-radius: 0.25rem;
|
||||
}
|
||||
.namespace.active, .resources.router-link-active {
|
||||
background-color: var(--primary-color)
|
||||
}
|
||||
.namespace.active *, .resources.router-link-active {
|
||||
color: white;
|
||||
}
|
||||
.divider {
|
||||
height: 1px;
|
||||
background-color: lightgray;
|
||||
}
|
||||
.nav > * {
|
||||
display: block;
|
||||
text-decoration: none;
|
||||
}
|
||||
.hide {
|
||||
visibility: hidden;
|
||||
}
|
||||
</style>
|
||||
@ -45,6 +45,7 @@ defineExpose({
|
||||
white-space: pre-wrap;
|
||||
color: white;
|
||||
padding: 0.25rem 1rem;
|
||||
line-height: 1.0.5rem;
|
||||
}
|
||||
.console {
|
||||
background-color: black;
|
||||
|
||||
74
components/PeriodPicker.vue
Normal file
74
components/PeriodPicker.vue
Normal file
@ -0,0 +1,74 @@
|
||||
<template>
|
||||
<UiInput class="timeframe">
|
||||
<select name="" id="" v-model="timeframe">
|
||||
<option :value="Timeframe.LAST_HOUR">Letzte Stunde</option>
|
||||
<option :value="Timeframe.LAST_3_HOURS">Letzte 3 Stunden</option>
|
||||
<option :value="Timeframe.LAST_6_HOURS">Letzte 6 Stunden</option>
|
||||
<option :value="Timeframe.LAST_12_HOURS">Letzte 12 Stunden</option>
|
||||
<option :value="Timeframe.LAST_24_HOURS">Letzte 24 Stunden</option>
|
||||
<option :value="Timeframe.LAST_7_DAYS">Letzte 7 Tage</option>
|
||||
<option :value="Timeframe.LAST_30_DAYS">Letzte 30 Tage</option>
|
||||
<option :value="Timeframe.LAST_90_DAYS">Letzte 90 Tage</option>
|
||||
<option :value="undefined">Custom...</option>
|
||||
</select>
|
||||
</UiInput>
|
||||
</template>
|
||||
|
||||
<script setup lang="ts">
|
||||
import dayjs, { Dayjs } from 'dayjs';
|
||||
import utc from 'dayjs/plugin/utc';
|
||||
|
||||
const enum Timeframe
|
||||
{
|
||||
LAST_HOUR = "LAST_HOUR",
|
||||
LAST_3_HOURS = "LAST_3_HOURS",
|
||||
LAST_6_HOURS = "LAST_6_HOURS",
|
||||
LAST_12_HOURS = "LAST_12_HOURS",
|
||||
TODAY = "TODAY",
|
||||
LAST_24_HOURS = "LAST_24_HOURS",
|
||||
LAST_7_DAYS = "LAST_7_DAYS",
|
||||
LAST_30_DAYS = "LAST_30_DAYS",
|
||||
LAST_90_DAYS = "LAST_90_DAYS"
|
||||
}
|
||||
|
||||
const timeframe: Ref<string | undefined> = ref(Timeframe.LAST_HOUR);
|
||||
|
||||
const emits = defineEmits<{
|
||||
(e: 'update:modelValue', value?: Date): void
|
||||
}>();
|
||||
|
||||
const startTime = computed(() => {
|
||||
dayjs.extend(utc)
|
||||
switch (timeframe.value)
|
||||
{
|
||||
case Timeframe.LAST_HOUR: {
|
||||
return dayjs().utc().subtract(1, 'hours');
|
||||
}
|
||||
case Timeframe.LAST_3_HOURS: {
|
||||
return dayjs().utc().subtract(3, 'hours');
|
||||
}
|
||||
case Timeframe.LAST_6_HOURS: {
|
||||
return dayjs().utc().subtract(6, 'hours');
|
||||
}
|
||||
case Timeframe.LAST_12_HOURS: {
|
||||
return dayjs().utc().subtract(12, 'hours');
|
||||
}
|
||||
case Timeframe.LAST_24_HOURS: {
|
||||
return dayjs().utc().subtract(24, 'hours');
|
||||
}
|
||||
case Timeframe.LAST_7_DAYS: {
|
||||
return dayjs().utc().subtract(7, 'days');
|
||||
}
|
||||
case Timeframe.LAST_30_DAYS: {
|
||||
return dayjs().utc().subtract(30, 'days');
|
||||
}
|
||||
case Timeframe.LAST_90_DAYS: {
|
||||
return dayjs().utc().subtract(90, 'days');
|
||||
}
|
||||
}
|
||||
})
|
||||
|
||||
watch(startTime, (startTime) => {
|
||||
emits('update:modelValue', startTime?.toDate());
|
||||
}, { immediate: true });
|
||||
</script>
|
||||
@ -55,15 +55,15 @@ function enableScrolling()
|
||||
|
||||
<style scoped>
|
||||
.overlay {
|
||||
background-color: rgba(0, 0, 0, 0.514);
|
||||
background-color: rgba(223, 223, 223, 0.514);
|
||||
backdrop-filter: blur(0.1rem);
|
||||
position: fixed;
|
||||
top: 0;
|
||||
bottom: 0;
|
||||
left: 0;
|
||||
right: 0;
|
||||
padding: 1rem;
|
||||
padding: 2rem;
|
||||
padding: 0.5rem;
|
||||
padding: 0.5rem;
|
||||
z-index: 2;
|
||||
}
|
||||
|
||||
@ -74,7 +74,8 @@ function enableScrolling()
|
||||
width: 100%;
|
||||
height: 100%;
|
||||
padding: 1rem;
|
||||
background-color: white;
|
||||
background-color: rgb(255, 255, 255);
|
||||
border-radius: 0.5rem;
|
||||
}
|
||||
|
||||
.popup__header {
|
||||
|
||||
65
components/Pulse.vue
Normal file
65
components/Pulse.vue
Normal file
@ -0,0 +1,65 @@
|
||||
<template>
|
||||
<div class="outer center" :class="threshold.calc(threshold.value).toString().toLocaleLowerCase()">
|
||||
<div class="inner">
|
||||
|
||||
</div>
|
||||
</div>
|
||||
</template>
|
||||
|
||||
<script setup lang="ts">
|
||||
import type { Threshold } from '~/classes/Threshold';
|
||||
|
||||
defineProps<{
|
||||
threshold: Threshold
|
||||
}>();
|
||||
</script>
|
||||
|
||||
<style scoped>
|
||||
.outer {
|
||||
width: 1.25rem;
|
||||
height: 1.25rem;
|
||||
border-radius: 0.75rem;
|
||||
user-select: none;
|
||||
}
|
||||
.inner {
|
||||
width: 0.75rem;
|
||||
height: 0.75rem;
|
||||
border-radius: 0.5rem;
|
||||
animation: pulse 2s infinite;
|
||||
}
|
||||
.outer.green {
|
||||
background-color: rgb(139, 255, 139);
|
||||
}
|
||||
.outer.green .inner {
|
||||
background-color: green;
|
||||
}
|
||||
|
||||
.outer.orange {
|
||||
background-color: rgb(255, 192, 151);
|
||||
}
|
||||
.outer.orange .inner {
|
||||
background-color: rgb(255, 132, 31);
|
||||
}
|
||||
|
||||
.outer.red {
|
||||
background-color: rgb(255, 130, 130);
|
||||
}
|
||||
.outer.red .inner {
|
||||
background-color: rgb(219, 12, 12);
|
||||
}
|
||||
|
||||
@keyframes pulse {
|
||||
0% {
|
||||
transform: scale(1);
|
||||
opacity: 1;
|
||||
}
|
||||
50% {
|
||||
transform: scale(1.1);
|
||||
opacity: 0.5;
|
||||
}
|
||||
100% {
|
||||
transform: scale(1);
|
||||
opacity: 1;
|
||||
}
|
||||
}
|
||||
</style>
|
||||
44
components/SidebarTemplate.vue
Normal file
44
components/SidebarTemplate.vue
Normal file
@ -0,0 +1,44 @@
|
||||
<template>
|
||||
<nav class="sidebar">
|
||||
<slot></slot>
|
||||
</nav>
|
||||
<AccountPopup ref="accountPopup"></AccountPopup>
|
||||
</template>
|
||||
|
||||
<script setup lang="ts">
|
||||
|
||||
</script>
|
||||
|
||||
<style>
|
||||
.sidebar {
|
||||
padding: 1rem;
|
||||
background-color: var(--tile-color);
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
justify-content: space-between;
|
||||
gap: 0.5rem;
|
||||
height: 100%;
|
||||
}
|
||||
.namespace, .resources {
|
||||
padding: 0.35rem 0.5rem;
|
||||
border-radius: 0.25rem;
|
||||
cursor: pointer;
|
||||
}
|
||||
.namespace.active, .resources.router-link-active {
|
||||
background-color: var(--primary-color)
|
||||
}
|
||||
.namespace.active *, .resources.router-link-active {
|
||||
color: white;
|
||||
}
|
||||
.divider {
|
||||
height: 1px;
|
||||
background-color: rgb(36, 36, 36);
|
||||
}
|
||||
.nav > * {
|
||||
display: block;
|
||||
text-decoration: none;
|
||||
}
|
||||
.hide {
|
||||
visibility: hidden;
|
||||
}
|
||||
</style>
|
||||
69
components/chart/Chart.vue
Normal file
69
components/chart/Chart.vue
Normal file
@ -0,0 +1,69 @@
|
||||
<template>
|
||||
<div :id="id" class="chart">
|
||||
|
||||
</div>
|
||||
</template>
|
||||
|
||||
<script setup lang="ts">
|
||||
import type { Dataset } from './Dataset';
|
||||
import { TimeScale, LinearScale, LineController, PointElement, LineElement, Chart, type ChartConfiguration } from 'chart.js';
|
||||
import 'chartjs-adapter-moment';
|
||||
|
||||
Chart.register(TimeScale)
|
||||
Chart.register(LinearScale)
|
||||
Chart.register(PointElement)
|
||||
Chart.register(LineController)
|
||||
Chart.register(LineElement)
|
||||
|
||||
|
||||
const id = Math.random().toString().replaceAll(".", "");
|
||||
|
||||
const props = defineProps<{
|
||||
datasets: Dataset,
|
||||
config: any
|
||||
}>();
|
||||
|
||||
onMounted(() => {
|
||||
watch(props, () => {
|
||||
renderChart();
|
||||
}, { immediate: true })
|
||||
})
|
||||
|
||||
function renderChart()
|
||||
{
|
||||
const wrapper = document.getElementById(id);
|
||||
if (wrapper != null)
|
||||
{
|
||||
wrapper.replaceChildren();
|
||||
let data = {
|
||||
labels: [] as string[],
|
||||
datasets: [{
|
||||
data: [] as number[],
|
||||
borderColor: '#574BFF',
|
||||
backgroundColor: 'rgba(0, 0, 255, 0.2)', // area fill color
|
||||
fill: 'origin',
|
||||
pointRadius: 1,
|
||||
borderWidth: 2,
|
||||
tension: 0.3
|
||||
}]
|
||||
}
|
||||
for (const dataset of props.datasets.data)
|
||||
{
|
||||
data.labels.push(new Date(dataset.label).toISOString());
|
||||
data.datasets.at(0)!.data.push(dataset.value);
|
||||
}
|
||||
const canvas = document.createElement('canvas');
|
||||
new Chart(canvas, getConfig(data, props.datasets.unit));
|
||||
wrapper.appendChild(canvas)
|
||||
}
|
||||
}
|
||||
|
||||
function getConfig(data: any, unit: string): ChartConfiguration {
|
||||
const c = props.config;
|
||||
c.data = data;
|
||||
c.options.scales.y.ticks.callback = function(value: number) {
|
||||
return value + " " + unit;
|
||||
}
|
||||
return c;
|
||||
}
|
||||
</script>
|
||||
24
components/chart/Dataset.ts
Normal file
24
components/chart/Dataset.ts
Normal file
@ -0,0 +1,24 @@
|
||||
export class Dataset
|
||||
{
|
||||
constructor (
|
||||
public unit: string,
|
||||
public data: Data[]
|
||||
) { }
|
||||
|
||||
getLatest()
|
||||
{
|
||||
if (this.data.length > 0)
|
||||
{
|
||||
return this.data.toSorted((lower, higher) => new Date(higher.label).getTime() - new Date(lower.label).getTime()).at(this.data.length - 1);
|
||||
}
|
||||
return undefined;
|
||||
}
|
||||
}
|
||||
|
||||
export class Data
|
||||
{
|
||||
constructor (
|
||||
public value: number,
|
||||
public label: Date
|
||||
) { }
|
||||
}
|
||||
66
components/monitorings/MonitoredResource.ts
Normal file
66
components/monitorings/MonitoredResource.ts
Normal file
@ -0,0 +1,66 @@
|
||||
import axios from "axios";
|
||||
import type { Node } from "~/classes/Node";
|
||||
import type { Pod } from "~/classes/Pod";
|
||||
import { Data } from "../chart/Dataset";
|
||||
import dayjs from "dayjs";
|
||||
|
||||
export class MonitoredResource<T>
|
||||
{
|
||||
constructor (
|
||||
public resource: T,
|
||||
public jobs: IndexCollection[]
|
||||
) { }
|
||||
|
||||
static getMonitorings(monitoringId: string, onSuccess: (monitoredPod: MonitoredResource<Pod>[]) => void)
|
||||
{
|
||||
axios.get<MonitoredResource<Pod>[]>(useRuntimeConfig().public.apiBase + '/monitorings/' + monitoringId + '/jobs')
|
||||
.then(response => {
|
||||
onSuccess(response.data)
|
||||
});
|
||||
}
|
||||
|
||||
static getNodeMonitorings(from: Date, to: Date, onSuccess: (monitoredPod: MonitoredResource<Node>[]) => void)
|
||||
{
|
||||
axios.get<MonitoredResource<Node>[]>(useRuntimeConfig().public.apiBase + '/monitorings/nodes/jobs', {
|
||||
params: {
|
||||
from: from,
|
||||
to: to
|
||||
}
|
||||
})
|
||||
.then(response => {
|
||||
onSuccess(response.data)
|
||||
});
|
||||
}
|
||||
|
||||
static extractDataset(key: string, jobs: IndexCollection[]): Data[]
|
||||
{
|
||||
const data = [] as Data[];
|
||||
for (const job of jobs)
|
||||
{
|
||||
const metrics = job.metrics[key];
|
||||
if (metrics != null)
|
||||
{
|
||||
data.push(new Data(metrics.average, dayjs.utc(job.timestamp).local().toDate()));
|
||||
}
|
||||
}
|
||||
console.log(data);
|
||||
return data;
|
||||
}
|
||||
}
|
||||
|
||||
export class IndexCollection
|
||||
{
|
||||
constructor (
|
||||
public metrics: Record<string, Metric>,
|
||||
public timestamp: Date
|
||||
) { }
|
||||
}
|
||||
|
||||
export class Metric
|
||||
{
|
||||
constructor (
|
||||
public count: number,
|
||||
public sum: number,
|
||||
public average: number
|
||||
) { }
|
||||
}
|
||||
26
components/monitorings/MonitoringConfig.ts
Normal file
26
components/monitorings/MonitoringConfig.ts
Normal file
@ -0,0 +1,26 @@
|
||||
import axios from "axios";
|
||||
|
||||
export class VolumeMonitoringConfig
|
||||
{
|
||||
constructor (
|
||||
public id: string,
|
||||
public configName: string,
|
||||
public type: string,
|
||||
public volumeConfig: VolumeConfig
|
||||
) {}
|
||||
|
||||
static get(onSuccess: (monitorings: VolumeMonitoringConfig[]) => void)
|
||||
{
|
||||
axios.get<VolumeMonitoringConfig[]>(useRuntimeConfig().public.apiBase + '/monitorings')
|
||||
.then(response => {
|
||||
onSuccess(response.data)
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
class VolumeConfig
|
||||
{
|
||||
constructor (
|
||||
public mountPath: string
|
||||
) {}
|
||||
}
|
||||
92
components/monitorings/NodeMonitoringPage.vue
Normal file
92
components/monitorings/NodeMonitoringPage.vue
Normal file
@ -0,0 +1,92 @@
|
||||
<template>
|
||||
<div class="content-l">
|
||||
<div class="spaced-center">
|
||||
<h1>Nodes</h1>
|
||||
<PeriodPicker v-model="startDate"></PeriodPicker>
|
||||
</div>
|
||||
<div class="content-l">
|
||||
<h2>CPU</h2>
|
||||
<div class="col-3">
|
||||
<div v-for="node in nodes">
|
||||
<div class="tile-m content-m" v-for="dataset in [new Dataset('%', MonitoredResource.extractDataset('RELATIVE_CPU', node.jobs))]">
|
||||
<div class="left-center">
|
||||
<Pulse :threshold="new Threshold(dataset.getLatest()!.value, calcCpuThreshold)"></Pulse>
|
||||
<h3>{{ node.resource.metadata.name }}</h3>
|
||||
</div>
|
||||
<div>
|
||||
<Chart :datasets="dataset" :config="VOLUME_CHART_CONFIG(startDate, endDate)"></Chart>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
<div class="content-l">
|
||||
<h2>Memory</h2>
|
||||
<div class="col-3">
|
||||
<div v-for="node in nodes">
|
||||
<div class="tile-m content-m" v-for="dataset in [new Dataset('%', MonitoredResource.extractDataset('RELATIVE_MEMORY', node.jobs))]">
|
||||
<div class="left-center">
|
||||
<Pulse :threshold="new Threshold(dataset.getLatest()!.value, calcMemoryThreshold)"></Pulse>
|
||||
<h3>{{ node.resource.metadata.name }}</h3>
|
||||
</div>
|
||||
<div>
|
||||
<Chart :datasets="dataset" :config="VOLUME_CHART_CONFIG(startDate, endDate)"></Chart>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</template>
|
||||
|
||||
<script setup lang="ts">
|
||||
import type { Node } from '~/classes/Node';
|
||||
import { MonitoredResource } from './MonitoredResource';
|
||||
import { Dataset } from '../chart/Dataset';
|
||||
import { VOLUME_CHART_CONFIG } from './volumes/ChartConfig';
|
||||
import Pulse from '../Pulse.vue';
|
||||
import PeriodPicker from '../PeriodPicker.vue';
|
||||
import { useTicker } from '~/classes/Ticker';
|
||||
import { State, Threshold } from '~/classes/Threshold';
|
||||
|
||||
const nodes: Ref<MonitoredResource<Node>[] | undefined> = ref(undefined);
|
||||
|
||||
const startDate = ref();
|
||||
|
||||
const endDate = useTicker("10s");
|
||||
|
||||
function calcCpuThreshold(value: number)
|
||||
{
|
||||
if (value > 85)
|
||||
{
|
||||
return State.RED;
|
||||
}
|
||||
if (value > 60)
|
||||
{
|
||||
return State.ORANGE;
|
||||
}
|
||||
return State.GREEN;
|
||||
}
|
||||
|
||||
function calcMemoryThreshold(value: number)
|
||||
{
|
||||
if (value > 90)
|
||||
{
|
||||
return State.RED;
|
||||
}
|
||||
if (value > 70)
|
||||
{
|
||||
return State.ORANGE;
|
||||
}
|
||||
return State.GREEN;
|
||||
}
|
||||
|
||||
watch([startDate, endDate], ([startDate, endDate]) => {
|
||||
if (startDate != null && endDate != null)
|
||||
{
|
||||
MonitoredResource.getNodeMonitorings(startDate, endDate, (_nodes: MonitoredResource<Node>[]) => {
|
||||
nodes.value = _nodes;
|
||||
});
|
||||
}
|
||||
}, { immediate: true })
|
||||
</script>
|
||||
102
components/monitorings/VolumeMonitoringPage.vue
Normal file
102
components/monitorings/VolumeMonitoringPage.vue
Normal file
@ -0,0 +1,102 @@
|
||||
<template>
|
||||
<div class="padding-xl content-l">
|
||||
<h1>Monitorings</h1>
|
||||
<div class="left-center">
|
||||
<UiInput class="timeframe">
|
||||
<select name="" id="" v-model="timeframe">
|
||||
<option :value="Timeframe.LAST_HOUR">Letzte Stunde</option>
|
||||
<option :value="Timeframe.LAST_3_HOURS">Letzte 3 Stunden</option>
|
||||
<option :value="Timeframe.LAST_6_HOURS">Letzte 6 Stunden</option>
|
||||
<option :value="Timeframe.LAST_12_HOURS">Letzte 12 Stunden</option>
|
||||
<option :value="Timeframe.LAST_24_HOURS">Letzte 24 Stunden</option>
|
||||
<option :value="Timeframe.LAST_7_DAYS">Letzte 7 Tage</option>
|
||||
<option :value="Timeframe.LAST_30_DAYS">Letzte 30 Tage</option>
|
||||
<option :value="Timeframe.LAST_90_DAYS">Letzte 90 Tage</option>
|
||||
<option :value="undefined">Custom...</option>
|
||||
</select>
|
||||
</UiInput>
|
||||
<div class="left-center" v-if="timeframe == null">
|
||||
<UiInput>
|
||||
<input type="datetime-local">
|
||||
</UiInput>
|
||||
<p>bis</p>
|
||||
<UiInput>
|
||||
<input type="datetime-local">
|
||||
</UiInput>
|
||||
</div>
|
||||
</div>
|
||||
<div v-for="monitoring in monitorings">
|
||||
<VolumeMonitoringConfigComponent :monitoring="monitoring" v-if="monitoring.type === 'VOLUME'"></VolumeMonitoringConfigComponent>
|
||||
<MemoryMonitoringConfigComponent :monitoring="monitoring" v-if="monitoring.type === 'MEMORY'"></MemoryMonitoringConfigComponent>
|
||||
</div>
|
||||
</div>
|
||||
</template>
|
||||
|
||||
<script setup lang="ts">
|
||||
import 'chartjs-adapter-luxon';
|
||||
import { VolumeMonitoringConfig } from './MonitoringConfig';
|
||||
import VolumeMonitoringConfigComponent from './volumes/VolumeMonitoringConfigComponent.vue';
|
||||
import MemoryMonitoringConfigComponent from './memory/MemoryMonitoringConfigComponent.vue';
|
||||
import utc from 'dayjs/plugin/utc';
|
||||
import dayjs from 'dayjs';
|
||||
|
||||
const enum Timeframe
|
||||
{
|
||||
LAST_HOUR = "LAST_HOUR",
|
||||
LAST_3_HOURS = "LAST_3_HOURS",
|
||||
LAST_6_HOURS = "LAST_6_HOURS",
|
||||
LAST_12_HOURS = "LAST_12_HOURS",
|
||||
TODAY = "TODAY",
|
||||
LAST_24_HOURS = "LAST_24_HOURS",
|
||||
LAST_7_DAYS = "LAST_7_DAYS",
|
||||
LAST_30_DAYS = "LAST_30_DAYS",
|
||||
LAST_90_DAYS = "LAST_90_DAYS"
|
||||
}
|
||||
|
||||
const monitorings: Ref<VolumeMonitoringConfig[] | undefined> = ref(undefined);
|
||||
|
||||
const timeframe: Ref<string | undefined> = ref("LAST_HOUR");
|
||||
|
||||
onMounted(() => {
|
||||
VolumeMonitoringConfig.get((_monitorings) => {
|
||||
monitorings.value = _monitorings;
|
||||
});
|
||||
});
|
||||
|
||||
const startTime = computed(() => {
|
||||
dayjs.extend(utc)
|
||||
switch (timeframe.value)
|
||||
{
|
||||
case Timeframe.LAST_HOUR: {
|
||||
return dayjs().utc().subtract(1, 'hours');
|
||||
}
|
||||
case Timeframe.LAST_3_HOURS: {
|
||||
return dayjs().utc().subtract(3, 'hours');
|
||||
}
|
||||
case Timeframe.LAST_6_HOURS: {
|
||||
return dayjs().utc().subtract(6, 'hours');
|
||||
}
|
||||
case Timeframe.LAST_12_HOURS: {
|
||||
return dayjs().utc().subtract(12, 'hours');
|
||||
}
|
||||
case Timeframe.LAST_24_HOURS: {
|
||||
return dayjs().utc().subtract(24, 'hours');
|
||||
}
|
||||
case Timeframe.LAST_7_DAYS: {
|
||||
return dayjs().utc().subtract(7, 'days');
|
||||
}
|
||||
case Timeframe.LAST_30_DAYS: {
|
||||
return dayjs().utc().subtract(30, 'days');
|
||||
}
|
||||
case Timeframe.LAST_90_DAYS: {
|
||||
return dayjs().utc().subtract(90, 'days');
|
||||
}
|
||||
}
|
||||
})
|
||||
</script>
|
||||
|
||||
<style scoped>
|
||||
.timeframe {
|
||||
width: 13rem;
|
||||
}
|
||||
</style>
|
||||
52
components/monitorings/memory/ChartConfig.ts
Normal file
52
components/monitorings/memory/ChartConfig.ts
Normal file
@ -0,0 +1,52 @@
|
||||
import { elements } from "chart.js";
|
||||
import dayjs from "dayjs"
|
||||
|
||||
export function MEMORY_CHART_CONFIG() {
|
||||
return {
|
||||
type: 'line',
|
||||
options: {
|
||||
plugins: {
|
||||
title: {
|
||||
text: 'Chart.js Time Scale',
|
||||
display: true
|
||||
}
|
||||
},
|
||||
aspectRatio: 2.75,
|
||||
scales: {
|
||||
x: {
|
||||
type: 'time',
|
||||
time: {
|
||||
// Luxon format string
|
||||
tooltipFormat: 'DD.MM'
|
||||
},
|
||||
ticks: {
|
||||
autoSkip: true,
|
||||
maxTicksLimit: 6,
|
||||
align: 'center',
|
||||
color: '#cacacaff',
|
||||
callback: function (value: Date) {
|
||||
return dayjs(value).format("HH:mm");
|
||||
}
|
||||
},
|
||||
grid: {
|
||||
color: '#cacacaff',
|
||||
borderColor: '#cacacaff',
|
||||
lineWidth: 1
|
||||
},
|
||||
},
|
||||
y: {
|
||||
beginAtZero: true,
|
||||
ticks: {
|
||||
maxTicksLimit: 6,
|
||||
color: '#cacacaff'
|
||||
},
|
||||
grid: {
|
||||
color: '#cacacaff',
|
||||
borderColor: '#cacacaff',
|
||||
lineWidth: 1
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
};
|
||||
@ -0,0 +1,54 @@
|
||||
<template>
|
||||
<div class="content-l">
|
||||
<p>{{ pod.pod.metadata.namespace }} / {{ pod.pod.metadata.name }}</p>
|
||||
<Chart :datasets="dataset" :config="MEMORY_CHART_CONFIG()"></Chart>
|
||||
</div>
|
||||
</template>
|
||||
|
||||
<script setup lang="ts">
|
||||
import { Data, Dataset } from '~/components/chart/Dataset';
|
||||
import { MonitoredPod } from '../MonitoredResource';
|
||||
import { MEMORY_CHART_CONFIG } from './ChartConfig';
|
||||
|
||||
const units = new Map<number, string>([[0, "B"], [1, "Ki"], [2, "Mi"], [3, "Gi"], [4, "Ti"]]);
|
||||
|
||||
const props = defineProps<{
|
||||
pod: MonitoredPod
|
||||
}>();
|
||||
|
||||
const dataset = computed(() => {
|
||||
const result = [] as Data[];
|
||||
const max = Math.max(...props.pod.jobs.map(job => job.average));
|
||||
const dimension = getDimension(max);
|
||||
for (const job of props.pod.jobs)
|
||||
{
|
||||
result.push(new Data(job.average / (Math.pow(1024, dimension)), job.timestamp));
|
||||
}
|
||||
return new Dataset(units.get(dimension)!, result);
|
||||
});
|
||||
|
||||
function getDimension(max: number)
|
||||
{
|
||||
if (max <= 1024)
|
||||
{
|
||||
return 0;
|
||||
}
|
||||
else if (max <= (1024 * 1024))
|
||||
{
|
||||
return 1;
|
||||
}
|
||||
else if (max <= (1024 * 1024 * 1024))
|
||||
{
|
||||
return 2;
|
||||
}
|
||||
else if (max <= (1024 * 1024 * 1024 * 1024))
|
||||
{
|
||||
return 3;
|
||||
}
|
||||
else if (max <= (1024 * 1024 * 1024 * 1024 * 1024))
|
||||
{
|
||||
return 4;
|
||||
}
|
||||
return -1;
|
||||
}
|
||||
</script>
|
||||
@ -0,0 +1,32 @@
|
||||
<template>
|
||||
<div class="content-l">
|
||||
<h2>{{ createLabel(monitoring.type) }} Monitoring: {{ monitoring.configName }}</h2>
|
||||
<div class="col-3">
|
||||
<MemoryMonitoredPodComponent class="tile-l" v-for="pod in pods" :pod="pod"></MemoryMonitoredPodComponent>
|
||||
</div>
|
||||
</div>
|
||||
</template>
|
||||
|
||||
<script setup lang="ts">
|
||||
import type { Pod } from '~/classes/Pod';
|
||||
import { MonitoredResource } from '../MonitoredResource';
|
||||
import { VolumeMonitoringConfig } from '../MonitoringConfig';
|
||||
import MemoryMonitoredPodComponent from './MemoryMonitoredPodComponent.vue';
|
||||
|
||||
const props = defineProps<{
|
||||
monitoring: VolumeMonitoringConfig
|
||||
}>();
|
||||
|
||||
const pods: Ref<MonitoredResource<Pod>[] | undefined> = ref(undefined);
|
||||
|
||||
onMounted(() => {
|
||||
MonitoredResource.getMonitorings(props.monitoring.id, (_pods: MonitoredResource<Pod>[]) => {
|
||||
pods.value = _pods;
|
||||
});
|
||||
});
|
||||
|
||||
function createLabel(input: string)
|
||||
{
|
||||
return input.at(0)?.toUpperCase() + input.substring(1, input.length).toLowerCase();
|
||||
}
|
||||
</script>
|
||||
59
components/monitorings/volumes/ChartConfig.ts
Normal file
59
components/monitorings/volumes/ChartConfig.ts
Normal file
@ -0,0 +1,59 @@
|
||||
import dayjs, { Dayjs } from "dayjs";
|
||||
import utc from 'dayjs/plugin/utc';
|
||||
|
||||
export function VOLUME_CHART_CONFIG(from: Date, to: Date) {
|
||||
dayjs.extend(utc);
|
||||
return {
|
||||
type: 'line',
|
||||
options: {
|
||||
animation: false,
|
||||
plugins: {
|
||||
title: {
|
||||
text: 'Chart.js Time Scale',
|
||||
display: true
|
||||
}
|
||||
},
|
||||
aspectRatio: 2.75,
|
||||
scales: {
|
||||
x: {
|
||||
type: 'time',
|
||||
time: {
|
||||
// Luxon format string
|
||||
tooltipFormat: 'DD.MM'
|
||||
},
|
||||
ticks: {
|
||||
autoSkip: true,
|
||||
maxTicksLimit: 6,
|
||||
align: 'center',
|
||||
color: '#cacacaff',
|
||||
callback: function (value: Date) {
|
||||
const offset = new Date().getTimezoneOffset();
|
||||
const date: Dayjs = dayjs(value).utcOffset(-offset);
|
||||
return date.format("HH:mm");
|
||||
}
|
||||
},
|
||||
min: from,
|
||||
max: to,
|
||||
grid: {
|
||||
color: '#cacacaff',
|
||||
borderColor: '#cacacaff',
|
||||
lineWidth: 1
|
||||
}
|
||||
},
|
||||
y: {
|
||||
beginAtZero: true,
|
||||
max: 100,
|
||||
ticks: {
|
||||
stepSize: 20,
|
||||
color: '#cacacaff',
|
||||
},
|
||||
grid: {
|
||||
color: '#cacacaff',
|
||||
borderColor: '#cacacaff',
|
||||
lineWidth: 1
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
@ -0,0 +1,26 @@
|
||||
<template>
|
||||
<div class="content-l">
|
||||
<p>{{ pod.pod.metadata.namespace }} / {{ pod.pod.metadata.name }}</p>
|
||||
<Chart :datasets="dataset" :config="VOLUME_CHART_CONFIG()"></Chart>
|
||||
</div>
|
||||
</template>
|
||||
|
||||
<script setup lang="ts">
|
||||
import { Data, Dataset } from '~/components/chart/Dataset';
|
||||
import { MonitoredPod } from '../MonitoredResource';
|
||||
import { VOLUME_CHART_CONFIG } from './ChartConfig';
|
||||
|
||||
|
||||
const props = defineProps<{
|
||||
pod: MonitoredPod
|
||||
}>();
|
||||
|
||||
const dataset = computed(() => {
|
||||
const result = [] as Data[];
|
||||
for (const job of props.pod.jobs)
|
||||
{
|
||||
result.push(new Data(job.average, job.timestamp));
|
||||
}
|
||||
return new Dataset("", result);
|
||||
});
|
||||
</script>
|
||||
@ -0,0 +1,34 @@
|
||||
<template>
|
||||
<div class="content-l">
|
||||
<div class="content-s">
|
||||
<h2>{{ createLabel(monitoring.type) }} Monitoring: {{ monitoring.configName }}</h2>
|
||||
<p>{{ monitoring.volumeConfig!.mountPath }}</p>
|
||||
</div>
|
||||
<div class="col-3">
|
||||
<MonitoredPodComponent class="tile-l" v-for="pod in pods" :pod="pod"></MonitoredPodComponent>
|
||||
</div>
|
||||
</div>
|
||||
</template>
|
||||
|
||||
<script setup lang="ts">
|
||||
import type { VolumeMonitoringConfig } from '../MonitoringConfig';
|
||||
import MonitoredPodComponent from './VolumeMonitoredPodComponent.vue';
|
||||
import { MonitoredPod } from '../MonitoredResource';
|
||||
|
||||
const props = defineProps<{
|
||||
monitoring: VolumeMonitoringConfig
|
||||
}>();
|
||||
|
||||
const pods: Ref<MonitoredPod[] | undefined> = ref(undefined);
|
||||
|
||||
onMounted(() => {
|
||||
MonitoredPod.get(props.monitoring.id, (_pods: MonitoredPod[]) => {
|
||||
pods.value = _pods;
|
||||
});
|
||||
});
|
||||
|
||||
function createLabel(input: string)
|
||||
{
|
||||
return input.at(0)?.toUpperCase() + input.substring(1, input.length).toLowerCase();
|
||||
}
|
||||
</script>
|
||||
@ -27,9 +27,9 @@ defineProps<{
|
||||
.field input, .field .input, .field textarea, .field select {
|
||||
min-height: 2.5rem;
|
||||
border-radius: 0.25rem;
|
||||
background-color: rgb(255, 255, 255);
|
||||
background-color: rgb(51, 51, 51);
|
||||
width: 100%;
|
||||
border: 2px solid #303030;
|
||||
border: 2px solid #444444;
|
||||
outline: none;
|
||||
padding: 0.25rem;
|
||||
font-size: 1rem;
|
||||
|
||||
53
package-lock.json
generated
53
package-lock.json
generated
@ -9,8 +9,13 @@
|
||||
"dependencies": {
|
||||
"@pinia/nuxt": "^0.11.0",
|
||||
"axios": "^1.9.0",
|
||||
"chart.js": "^4.5.0",
|
||||
"chartjs-adapter-luxon": "^1.3.1",
|
||||
"chartjs-adapter-moment": "^1.0.1",
|
||||
"dayjs": "^1.11.13",
|
||||
"jwt-decode": "^4.0.0",
|
||||
"luxon": "^3.7.2",
|
||||
"moment": "^2.30.1",
|
||||
"nuxt": "^3.17.4",
|
||||
"pinia": "^3.0.2",
|
||||
"vue": "^3.5.15",
|
||||
@ -981,6 +986,11 @@
|
||||
"@jridgewell/sourcemap-codec": "^1.4.14"
|
||||
}
|
||||
},
|
||||
"node_modules/@kurkle/color": {
|
||||
"version": "0.3.4",
|
||||
"resolved": "https://registry.npmjs.org/@kurkle/color/-/color-0.3.4.tgz",
|
||||
"integrity": "sha512-M5UknZPHRu3DEDWoipU6sE8PdkZ6Z/S+v4dD+Ke8IaNlpdSQah50lz1KtcFBa2vsdOnwbbnxJwVM4wty6udA5w=="
|
||||
},
|
||||
"node_modules/@kwsites/file-exists": {
|
||||
"version": "1.1.1",
|
||||
"resolved": "https://registry.npmjs.org/@kwsites/file-exists/-/file-exists-1.1.1.tgz",
|
||||
@ -3999,6 +4009,35 @@
|
||||
}
|
||||
]
|
||||
},
|
||||
"node_modules/chart.js": {
|
||||
"version": "4.5.0",
|
||||
"resolved": "https://registry.npmjs.org/chart.js/-/chart.js-4.5.0.tgz",
|
||||
"integrity": "sha512-aYeC/jDgSEx8SHWZvANYMioYMZ2KX02W6f6uVfyteuCGcadDLcYVHdfdygsTQkQ4TKn5lghoojAsPj5pu0SnvQ==",
|
||||
"dependencies": {
|
||||
"@kurkle/color": "^0.3.0"
|
||||
},
|
||||
"engines": {
|
||||
"pnpm": ">=8"
|
||||
}
|
||||
},
|
||||
"node_modules/chartjs-adapter-luxon": {
|
||||
"version": "1.3.1",
|
||||
"resolved": "https://registry.npmjs.org/chartjs-adapter-luxon/-/chartjs-adapter-luxon-1.3.1.tgz",
|
||||
"integrity": "sha512-yxHov3X8y+reIibl1o+j18xzrcdddCLqsXhriV2+aQ4hCR66IYFchlRXUvrJVoxglJ380pgytU7YWtoqdIgqhg==",
|
||||
"peerDependencies": {
|
||||
"chart.js": ">=3.0.0",
|
||||
"luxon": ">=1.0.0"
|
||||
}
|
||||
},
|
||||
"node_modules/chartjs-adapter-moment": {
|
||||
"version": "1.0.1",
|
||||
"resolved": "https://registry.npmjs.org/chartjs-adapter-moment/-/chartjs-adapter-moment-1.0.1.tgz",
|
||||
"integrity": "sha512-Uz+nTX/GxocuqXpGylxK19YG4R3OSVf8326D+HwSTsNw1LgzyIGRo+Qujwro1wy6X+soNSnfj5t2vZ+r6EaDmA==",
|
||||
"peerDependencies": {
|
||||
"chart.js": ">=3.0.0",
|
||||
"moment": "^2.10.2"
|
||||
}
|
||||
},
|
||||
"node_modules/chokidar": {
|
||||
"version": "4.0.3",
|
||||
"resolved": "https://registry.npmjs.org/chokidar/-/chokidar-4.0.3.tgz",
|
||||
@ -6563,9 +6602,9 @@
|
||||
}
|
||||
},
|
||||
"node_modules/luxon": {
|
||||
"version": "3.6.1",
|
||||
"resolved": "https://registry.npmjs.org/luxon/-/luxon-3.6.1.tgz",
|
||||
"integrity": "sha512-tJLxrKJhO2ukZ5z0gyjY1zPh3Rh88Ej9P7jNrZiHMUXHae1yvI2imgOZtL1TO8TW6biMMKfTtAOoEJANgtWBMQ==",
|
||||
"version": "3.7.2",
|
||||
"resolved": "https://registry.npmjs.org/luxon/-/luxon-3.7.2.tgz",
|
||||
"integrity": "sha512-vtEhXh/gNjI9Yg1u4jX/0YVPMvxzHuGgCm6tC5kZyb08yjGWGnqAjGJvcXbqQR2P3MyMEFnRbpcdFS6PBcLqew==",
|
||||
"engines": {
|
||||
"node": ">=12"
|
||||
}
|
||||
@ -6817,6 +6856,14 @@
|
||||
"node": ">=18"
|
||||
}
|
||||
},
|
||||
"node_modules/moment": {
|
||||
"version": "2.30.1",
|
||||
"resolved": "https://registry.npmjs.org/moment/-/moment-2.30.1.tgz",
|
||||
"integrity": "sha512-uEmtNhbDOrWPFS+hdjFCBfy9f2YoyzRpwcl+DqpC6taX21FzsTLQVbMV/W7PzNSX6x/bhC1zA3c2UQ5NzH6how==",
|
||||
"engines": {
|
||||
"node": "*"
|
||||
}
|
||||
},
|
||||
"node_modules/mrmime": {
|
||||
"version": "2.0.1",
|
||||
"resolved": "https://registry.npmjs.org/mrmime/-/mrmime-2.0.1.tgz",
|
||||
|
||||
@ -12,8 +12,13 @@
|
||||
"dependencies": {
|
||||
"@pinia/nuxt": "^0.11.0",
|
||||
"axios": "^1.9.0",
|
||||
"chart.js": "^4.5.0",
|
||||
"chartjs-adapter-luxon": "^1.3.1",
|
||||
"chartjs-adapter-moment": "^1.0.1",
|
||||
"dayjs": "^1.11.13",
|
||||
"jwt-decode": "^4.0.0",
|
||||
"luxon": "^3.7.2",
|
||||
"moment": "^2.30.1",
|
||||
"nuxt": "^3.17.4",
|
||||
"pinia": "^3.0.2",
|
||||
"vue": "^3.5.15",
|
||||
|
||||
21
pages/account.vue
Normal file
21
pages/account.vue
Normal file
@ -0,0 +1,21 @@
|
||||
<template>
|
||||
<div class="account-page">
|
||||
<NuxtPage></NuxtPage>
|
||||
<div class="left-center footer">
|
||||
<NuxtLink to="/account/inspect">Inspect</NuxtLink>
|
||||
<NuxtLink to="/account/monitorings/nodes">Monitorings</NuxtLink>
|
||||
</div>
|
||||
</div>
|
||||
</template>
|
||||
|
||||
<style scoped>
|
||||
.account-page {
|
||||
height: 100%;
|
||||
display: grid;
|
||||
grid-template-rows: 1fr auto;
|
||||
}
|
||||
.footer {
|
||||
background-color: rgb(12, 12, 12);
|
||||
padding: 1rem;
|
||||
}
|
||||
</style>
|
||||
@ -1,6 +1,6 @@
|
||||
<template>
|
||||
<div id="app">
|
||||
<Sidebar></Sidebar>
|
||||
<InspectSidebar></InspectSidebar>
|
||||
<ScrollComponent>
|
||||
<NuxtPage></NuxtPage>
|
||||
</ScrollComponent>
|
||||
@ -9,6 +9,7 @@
|
||||
|
||||
<script setup lang="ts">
|
||||
import { useNamespaceStore } from '#imports';
|
||||
import InspectSidebar from '~/components/InspectSidebar.vue';
|
||||
|
||||
const namespaceStore = useNamespaceStore();
|
||||
onMounted(() => {
|
||||
25
pages/account/monitorings.vue
Normal file
25
pages/account/monitorings.vue
Normal file
@ -0,0 +1,25 @@
|
||||
<template>
|
||||
<div class="monitorings-page">
|
||||
<SidebarTemplate>
|
||||
<div class="content-l">
|
||||
<h2>Kubooboo</h2>
|
||||
<NuxtLink class="resources" to="/account/monitorings/nodes">Nodes</NuxtLink>
|
||||
</div>
|
||||
</SidebarTemplate>
|
||||
<NuxtPage></NuxtPage>
|
||||
</div>
|
||||
</template>
|
||||
|
||||
<script setup lang="ts">
|
||||
import SidebarTemplate from '~/components/SidebarTemplate.vue';
|
||||
</script>
|
||||
|
||||
<style scoped>
|
||||
.monitorings-page {
|
||||
display: grid;
|
||||
grid-template-columns: auto 1fr;
|
||||
}
|
||||
.monitorings-page > * {
|
||||
padding: 1rem;
|
||||
}
|
||||
</style>
|
||||
14
pages/account/monitorings/applications/1/index.vue
Normal file
14
pages/account/monitorings/applications/1/index.vue
Normal file
@ -0,0 +1,14 @@
|
||||
<template>
|
||||
<VolumeMonitoringPage></VolumeMonitoringPage>
|
||||
</template>
|
||||
|
||||
<script setup lang="ts">
|
||||
import VolumeMonitoringPage from '~/components/monitorings/VolumeMonitoringPage.vue';
|
||||
</script>
|
||||
|
||||
<style scoped>
|
||||
.monitorings-page {
|
||||
display: grid;
|
||||
grid-template-columns: auto 1fr;
|
||||
}
|
||||
</style>
|
||||
7
pages/account/monitorings/nodes/index.vue
Normal file
7
pages/account/monitorings/nodes/index.vue
Normal file
@ -0,0 +1,7 @@
|
||||
<template>
|
||||
<NodeMonitoringPage></NodeMonitoringPage>
|
||||
</template>
|
||||
|
||||
<script setup lang="ts">
|
||||
import NodeMonitoringPage from '~/components/monitorings/NodeMonitoringPage.vue';
|
||||
</script>
|
||||
@ -39,7 +39,7 @@ function doLogin()
|
||||
const decode = jwtDecode(token) as any;
|
||||
getUser(decode.upn, token, (user: User) => {
|
||||
setSessionCookie(new Session(user, token), decode.exp as number);
|
||||
useRouter().push('/dashboard/nodes');
|
||||
useRouter().push('/account/inspect');
|
||||
});
|
||||
});
|
||||
}
|
||||
|
||||
Loading…
x
Reference in New Issue
Block a user