diff --git a/assets/style.css b/assets/style.css index 1a70d90..a03763e 100644 --- a/assets/style.css +++ b/assets/style.css @@ -58,6 +58,8 @@ html, body, #__nuxt { display: flex; align-items: center; background-color: #f3f3f3; + font-size: 0.95rem; + height: 2rem; } .resource:hover > .grid-element { background-color: #dbdbdb; @@ -68,4 +70,4 @@ html, body, #__nuxt { } .even > .grid-element { background-color: rgb(233, 233, 233); -} +} \ No newline at end of file diff --git a/assets/transparent_logo.png b/assets/transparent_logo.png index f508012..7eecb51 100644 Binary files a/assets/transparent_logo.png and b/assets/transparent_logo.png differ diff --git a/classes/ConfigMap.ts b/classes/ConfigMap.ts index 7ca7294..3846057 100644 --- a/classes/ConfigMap.ts +++ b/classes/ConfigMap.ts @@ -1,9 +1,10 @@ import type { Metadata } from "./Metadata"; +import type { HasMetadata } from "./repo/ResourceRepo"; -export class ConfigMap +export class ConfigMap implements HasMetadata { constructor ( public metadata: Metadata, - public data: Record + public data?: Record ) {} } \ No newline at end of file diff --git a/classes/Node.ts b/classes/Node.ts index 37898ad..fe1e118 100644 --- a/classes/Node.ts +++ b/classes/Node.ts @@ -1,5 +1,5 @@ import type { Metadata } from "./Metadata"; -import type { HasMetadata } from "./ResourceRepo"; +import type { HasMetadata } from "./repo/ResourceRepo"; export class Node implements HasMetadata { diff --git a/classes/Pod.ts b/classes/Pod.ts index fcbabca..40d9334 100644 --- a/classes/Pod.ts +++ b/classes/Pod.ts @@ -1,7 +1,7 @@ import dayjs from "dayjs"; import advancedFormat from "dayjs/plugin/advancedFormat"; import type { Metadata } from "./Metadata"; -import type { HasMetadata } from "./ResourceRepo"; +import type { HasMetadata } from "./repo/ResourceRepo"; export class Pod implements HasMetadata { diff --git a/classes/ResourceRepo.ts b/classes/ResourceRepo.ts deleted file mode 100644 index c9793ce..0000000 --- a/classes/ResourceRepo.ts +++ /dev/null @@ -1,140 +0,0 @@ -import axios from "axios"; -import type { Metadata } from "./Metadata"; - -export interface HasMetadata { - metadata: Metadata -} - -export class ResourceEvent -{ - constructor ( - public type: string, - public resources: T[] - ) {} -} - -export class ResourceRepo -{ - private resources: Ref = ref([]); - private interval: NodeJS.Timeout | undefined = undefined; - private websocket: WebSocket | undefined = undefined; - - static init() - { - return new ResourceRepo(); - } - - listen(resource: string) - { - const websocket = new WebSocket(StringUtils.format("%s/watch/%s/%s", ApiConfig.getWsBase(), resource, this.getNamespace())); - websocket.addEventListener('open', () => { - console.info("Opened Websocket."); - }) - websocket.addEventListener("message", (event) => { - const data = JSON.parse(event.data) as ResourceEvent; - console.info(StringUtils.format("[%s] Resource", data.type)); - switch (data.type) - { - case "INIT": - { - this.add(data.resources); - break; - } - case "ADDED": - { - this.add(data.resources); - break; - } - case "MODIFIED": - { - this.update(data.resources); - break; - } - case "DELETED": - { - this.delete(data.resources); - break; - } - } - }); - const interval = setInterval(() => { - console.info("[PING]"); - websocket.send('[PING]'); - }, 5000); - websocket.addEventListener("close", () => { - console.info("Closing websocket."); - clearTimeout(interval); - }); - this.websocket = websocket; - } - - private add(resources: T[]) - { - this.resources.value.push(...resources); - } - - private delete(resources: T[]) - { - for (const resource of resources) - { - const index = this.resources.value.findIndex(item => item.metadata.uid === resource.metadata.uid); - if (index != null) - { - this.resources.value.splice(index, 1); - } - } - } - - private update(resources: T[]) - { - for (const resource of resources) - { - const index = this.resources.value.findIndex(item => item.metadata.uid === resource.metadata.uid); - if (index != null) - { - this.resources.value[index] = resource; - } - } - } - - load(resourceType: string) - { - this.refresh(resourceType); - return this; - } - - clear() - { - clearTimeout(this.interval); - if (this.websocket != null) - { - this.websocket.close(); - } - } - - get() - { - return computed(() => { - return this.resources.value.toSorted((a, b) => a.metadata.name.localeCompare(b.metadata.name)); - }) - } - - private refresh(resourceType: string) - { - const namespace = this.getNamespace(); - let url = StringUtils.format("%s/resources/%s", ApiConfig.getHttpBase(), resourceType); - if (namespace) - { - url = StringUtils.format("%s/%s", url, namespace); - } - axios.get(url) - .then((response) => { - this.resources.value = response.data; - }); - } - - private getNamespace() - { - return useRoute().params.namespace as string; - } -} \ No newline at end of file diff --git a/classes/Secret.ts b/classes/Secret.ts index 0e631c5..738832b 100644 --- a/classes/Secret.ts +++ b/classes/Secret.ts @@ -1,5 +1,5 @@ import { Metadata } from "./Metadata"; -import type { HasMetadata } from "./ResourceRepo"; +import type { HasMetadata } from "./repo/ResourceRepo"; export class Secret implements HasMetadata { diff --git a/classes/Service.ts b/classes/Service.ts index 1bf6ea6..7c1282e 100644 --- a/classes/Service.ts +++ b/classes/Service.ts @@ -1,5 +1,5 @@ import type { Metadata } from "./Metadata"; -import type { HasMetadata } from "./ResourceRepo"; +import type { HasMetadata } from "./repo/ResourceRepo"; export class Service implements HasMetadata { diff --git a/classes/repo/AdvancedWebSocket.ts b/classes/repo/AdvancedWebSocket.ts new file mode 100644 index 0000000..d96a2d3 --- /dev/null +++ b/classes/repo/AdvancedWebSocket.ts @@ -0,0 +1,24 @@ +export class AdvancedWebSocket +{ + constructor ( + private websocket: WebSocket, + private timeout: NodeJS.Timeout + ) {} + + static open(url: string, onMessage: (event: MessageEvent) => void, onClose: (event: CloseEvent) => void) + { + const websocket = new WebSocket(url); + websocket.addEventListener('message', onMessage); + websocket.addEventListener('close', onClose); + const timeout = setInterval(() => { + websocket.send("[PING]"); + }, 5000); + return new AdvancedWebSocket(websocket, timeout); + } + + close() + { + this.websocket.close(); + clearTimeout(this.timeout); + } +} \ No newline at end of file diff --git a/classes/repo/ResourceEventType.ts b/classes/repo/ResourceEventType.ts new file mode 100644 index 0000000..7855eb6 --- /dev/null +++ b/classes/repo/ResourceEventType.ts @@ -0,0 +1,7 @@ +export enum ResourceEventType +{ + INIT = "INIT", + ADDED = "ADDED", + MODIFIED = "MODIFIED", + DELETED = "DELETED" +} \ No newline at end of file diff --git a/classes/repo/ResourceRepo.ts b/classes/repo/ResourceRepo.ts new file mode 100644 index 0000000..8a5e085 --- /dev/null +++ b/classes/repo/ResourceRepo.ts @@ -0,0 +1,149 @@ +import axios from "axios"; +import type { Metadata } from "../Metadata"; +import { ResourceEventType } from "./ResourceEventType"; +import { AdvancedWebSocket } from "./AdvancedWebSocket"; +import { WebsocketSession } from "./WebsocketSession"; + +export interface HasMetadata { + metadata: Metadata +} + +export class ResourceEvent +{ + constructor ( + public type: ResourceEventType, + public resources: T[] + ) {} +} + +export class ResourceRepo +{ + private resources: Ref = ref(undefined); + private websocket?: AdvancedWebSocket; + + static init() + { + return new ResourceRepo(); + } + + listen(resource: string) + { + WebsocketSession.get((token: string) => { + const webSocket = AdvancedWebSocket.open(StringUtils.format("%s/watch/%s/%s?token=%s", ApiConfig.getWsBase(), resource, this.getNamespace(), token), + (event) => { + const data = JSON.parse(event.data) as ResourceEvent; + switch (data.type) + { + case ResourceEventType.INIT: + { + this.add(data.resources); + break; + } + case ResourceEventType.ADDED: + { + this.add(data.resources); + break; + } + case ResourceEventType.MODIFIED: + { + this.update(data.resources); + break; + } + case ResourceEventType.DELETED: + { + this.delete(data.resources); + break; + } + } + }, + () => { } + ); + this.websocket = webSocket; + }); + } + + private add(resources: T[]) + { + if (this.resources.value == null) + { + this.resources.value = []; + } + this.resources.value.push(...resources); + } + + private delete(resources: T[]) + { + if (this.resources.value == null) + { + this.resources.value = []; + } + for (const resource of resources) + { + const index = this.resources.value.findIndex(item => item.metadata.uid === resource.metadata.uid); + if (index != null) + { + this.resources.value.splice(index, 1); + } + } + } + + private update(resources: T[]) + { + if (this.resources.value == null) + { + this.resources.value = []; + } + for (const resource of resources) + { + const index = this.resources.value.findIndex(item => item.metadata.uid === resource.metadata.uid); + if (index != null) + { + this.resources.value[index] = resource; + } + } + } + + load(resourceType: string) + { + this.refresh(resourceType); + return this; + } + + clear() + { + if (this.websocket) + { + this.websocket.close(); + } + } + + get() + { + return computed(() => { + if (this.resources.value) + { + return this.resources.value.toSorted((a, b) => a.metadata.name.localeCompare(b.metadata.name)); + } + return undefined; + }) + } + + private refresh(resourceType: string) + { + const namespace = this.getNamespace(); + let url = StringUtils.format("%s/resources/%s", ApiConfig.getHttpBase(), resourceType); + if (namespace) + { + url = StringUtils.format("%s/%s", url, namespace); + } + axios.get(url) + .then((response) => { + this.resources.value = response.data; + }); + } + + private getNamespace() + { + return useRoute().params.namespace as string; + } +} \ No newline at end of file diff --git a/classes/repo/WebsocketSession.ts b/classes/repo/WebsocketSession.ts new file mode 100644 index 0000000..9b78eea --- /dev/null +++ b/classes/repo/WebsocketSession.ts @@ -0,0 +1,16 @@ +import axios from "axios"; + +export class WebsocketSession +{ + static get(onSucces: (token: string) => void) + { + axios.post(ApiConfig.getHttpBase() + '/websocket-session', undefined, { + headers: { + Authorization: StringUtils.format("Bearer %s", requireToken()) + } + }) + .then((response) => { + onSucces(response.data); + }); + } +} \ No newline at end of file diff --git a/components/ConfigMapComponent.vue b/components/ConfigMapComponent.vue index 9bafdd3..81307b6 100644 --- a/components/ConfigMapComponent.vue +++ b/components/ConfigMapComponent.vue @@ -2,16 +2,15 @@

{{ configMap.metadata.name }}

{{ configMap.metadata.namespace }}

-

{{ Object.keys(configMap.data).length }}

+

{{ Object.keys(configMap.data).length }}-

- delete + delete
\ No newline at end of file