From 02f24d3abddbef0260c131a192ac7e961f135a85 Mon Sep 17 00:00:00 2001 From: lukylix Date: Wed, 5 Jul 2023 21:52:59 +0200 Subject: [PATCH] Sub columns for services --- src/components/services/list.jsx | 22 +++- src/utils/config/service-helpers.js | 173 ++++++++++++++++++---------- 2 files changed, 127 insertions(+), 68 deletions(-) diff --git a/src/components/services/list.jsx b/src/components/services/list.jsx index 85083af3..1b6dcce1 100644 --- a/src/components/services/list.jsx +++ b/src/components/services/list.jsx @@ -14,17 +14,29 @@ const columnMap = [ "grid-cols-1 md:grid-cols-2 lg:grid-cols-8", ]; -export default function List({ group, services, layout }) { +export default function List({ group, services, layout, isGroup = false }) { + //console.log({ group, services, layout }); + console.log({ services }); return ( ); } diff --git a/src/utils/config/service-helpers.js b/src/utils/config/service-helpers.js index a016a00d..7589ee69 100644 --- a/src/utils/config/service-helpers.js +++ b/src/utils/config/service-helpers.js @@ -13,7 +13,6 @@ import getKubeConfig from "utils/config/kubernetes"; const logger = createLogger("service-helpers"); - export async function servicesFromConfig() { checkAndCopyConfig("services.yaml"); @@ -25,23 +24,43 @@ export async function servicesFromConfig() { if (!services) { return []; } - // map easy to write YAML objects into easy to consume JS arrays const servicesArray = services.map((servicesGroup) => ({ name: Object.keys(servicesGroup)[0], - services: servicesGroup[Object.keys(servicesGroup)[0]].map((entries) => ({ - name: Object.keys(entries)[0], - ...entries[Object.keys(entries)[0]], - type: 'service' - })), - })); + services: servicesGroup[Object.keys(servicesGroup)[0]].map((entries) => { + if (Array.isArray(entries[Object.keys(entries)[0]])) + return { + name: Object.keys(entries)[0], + services: entries[Object.keys(entries)[0]].map((entry) => ({ + name: Object.keys(entry)[0], + ...entry[Object.keys(entry)[0]], + type: "service", + })), + type: "grouped-service", + }; + return { + name: Object.keys(entries)[0], + ...entries[Object.keys(entries)[0]], + type: "service", + }; + }), + })); // add default weight to services based on their position in the configuration servicesArray.forEach((group, groupIndex) => { group.services.forEach((service, serviceIndex) => { - if(!service.weight) { + if (!service.weight && service.type !== "grouped-service") { servicesArray[groupIndex].services[serviceIndex].weight = (serviceIndex + 1) * 100; } + if (service.type === "grouped-service") { + service.weight = (serviceIndex + 1) * 100; + service.services.forEach((groupedService, groupedServiceIndex) => { + if (!groupedService.weight) { + servicesArray[groupIndex].services[serviceIndex].services[groupedServiceIndex].weight = + (groupedServiceIndex + 1) * 100; + } + }); + } }); }); @@ -66,7 +85,9 @@ export async function servicesFromDocker() { const isSwarm = !!servers[serverName].swarm; const docker = new Docker(getDockerArguments(serverName).conn); const listProperties = { all: true }; - const containers = await ((isSwarm) ? docker.listServices(listProperties) : docker.listContainers(listProperties)); + const containers = await (isSwarm + ? docker.listServices(listProperties) + : docker.listContainers(listProperties)); // bad docker connections can result in a object? // in any case, this ensures the result is the expected array @@ -76,8 +97,8 @@ export async function servicesFromDocker() { const discovered = containers.map((container) => { let constructedService = null; - const containerLabels = isSwarm ? shvl.get(container, 'Spec.Labels') : container.Labels; - const containerName = isSwarm ? shvl.get(container, 'Spec.Name') : container.Names[0]; + const containerLabels = isSwarm ? shvl.get(container, "Spec.Labels") : container.Labels; + const containerName = isSwarm ? shvl.get(container, "Spec.Name") : container.Names[0]; Object.keys(containerLabels).forEach((label) => { if (label.startsWith("homepage.")) { @@ -85,10 +106,14 @@ export async function servicesFromDocker() { constructedService = { container: containerName.replace(/^\//, ""), server: serverName, - type: 'service' + type: "service", }; } - shvl.set(constructedService, label.replace("homepage.", ""), substituteEnvironmentVars(containerLabels[label])); + shvl.set( + constructedService, + label.replace("homepage.", ""), + substituteEnvironmentVars(containerLabels[label]) + ); } }); @@ -132,12 +157,12 @@ export async function servicesFromDocker() { function getUrlFromIngress(ingress) { const urlHost = ingress.spec.rules[0].host; const urlPath = ingress.spec.rules[0].http.paths[0].path; - const urlSchema = ingress.spec.tls ? 'https' : 'http'; + const urlSchema = ingress.spec.tls ? "https" : "http"; return `${urlSchema}://${urlHost}${urlPath}`; } export async function servicesFromKubernetes() { - const ANNOTATION_BASE = 'gethomepage.dev'; + const ANNOTATION_BASE = "gethomepage.dev"; const ANNOTATION_WIDGET_BASE = `${ANNOTATION_BASE}/widget.`; const ANNOTATION_POD_SELECTOR = `${ANNOTATION_BASE}/pod-selector`; @@ -151,23 +176,36 @@ export async function servicesFromKubernetes() { const networking = kc.makeApiClient(NetworkingV1Api); const crd = kc.makeApiClient(CustomObjectsApi); - const ingressList = await networking.listIngressForAllNamespaces(null, null, null, null) + const ingressList = await networking + .listIngressForAllNamespaces(null, null, null, null) .then((response) => response.body) .catch((error) => { logger.error("Error getting ingresses: %d %s %s", error.statusCode, error.body, error.response); return null; }); - const traefikIngressList = await crd.listClusterCustomObject("traefik.io", "v1alpha1", "ingressroutes") + const traefikIngressList = await crd + .listClusterCustomObject("traefik.io", "v1alpha1", "ingressroutes") .then((response) => response.body) .catch(async (error) => { - logger.error("Error getting traefik ingresses from traefik.io: %d %s %s", error.statusCode, error.body, error.response); + logger.error( + "Error getting traefik ingresses from traefik.io: %d %s %s", + error.statusCode, + error.body, + error.response + ); // Fallback to the old traefik CRD group - const fallbackIngressList = await crd.listClusterCustomObject("traefik.containo.us", "v1alpha1", "ingressroutes") + const fallbackIngressList = await crd + .listClusterCustomObject("traefik.containo.us", "v1alpha1", "ingressroutes") .then((response) => response.body) .catch((fallbackError) => { - logger.error("Error getting traefik ingresses from traefik.containo.us: %d %s %s", fallbackError.statusCode, fallbackError.body, fallbackError.response); + logger.error( + "Error getting traefik ingresses from traefik.containo.us: %d %s %s", + fallbackError.statusCode, + fallbackError.body, + fallbackError.response + ); return null; }); @@ -175,8 +213,9 @@ export async function servicesFromKubernetes() { }); if (traefikIngressList && traefikIngressList.items.length > 0) { - const traefikServices = traefikIngressList.items - .filter((ingress) => ingress.metadata.annotations && ingress.metadata.annotations[`${ANNOTATION_BASE}/href`]) + const traefikServices = traefikIngressList.items.filter( + (ingress) => ingress.metadata.annotations && ingress.metadata.annotations[`${ANNOTATION_BASE}/href`] + ); ingressList.items.push(...traefikServices); } @@ -184,44 +223,52 @@ export async function servicesFromKubernetes() { return []; } const services = ingressList.items - .filter((ingress) => ingress.metadata.annotations && ingress.metadata.annotations[`${ANNOTATION_BASE}/enabled`] === 'true') + .filter( + (ingress) => + ingress.metadata.annotations && ingress.metadata.annotations[`${ANNOTATION_BASE}/enabled`] === "true" + ) .map((ingress) => { - let constructedService = { - app: ingress.metadata.name, - namespace: ingress.metadata.namespace, - href: ingress.metadata.annotations[`${ANNOTATION_BASE}/href`] || getUrlFromIngress(ingress), - name: ingress.metadata.annotations[`${ANNOTATION_BASE}/name`] || ingress.metadata.name, - group: ingress.metadata.annotations[`${ANNOTATION_BASE}/group`] || "Kubernetes", - weight: ingress.metadata.annotations[`${ANNOTATION_BASE}/weight`] || '0', - icon: ingress.metadata.annotations[`${ANNOTATION_BASE}/icon`] || '', - description: ingress.metadata.annotations[`${ANNOTATION_BASE}/description`] || '', - external: false, - type: 'service' - }; - if (ingress.metadata.annotations[`${ANNOTATION_BASE}/external`]) { - constructedService.external = String(ingress.metadata.annotations[`${ANNOTATION_BASE}/external`]).toLowerCase() === "true" - } - if (ingress.metadata.annotations[ANNOTATION_POD_SELECTOR]) { - constructedService.podSelector = ingress.metadata.annotations[ANNOTATION_POD_SELECTOR]; - } - if (ingress.metadata.annotations[`${ANNOTATION_BASE}/ping`]) { - constructedService.ping = ingress.metadata.annotations[`${ANNOTATION_BASE}/ping`]; - } - Object.keys(ingress.metadata.annotations).forEach((annotation) => { - if (annotation.startsWith(ANNOTATION_WIDGET_BASE)) { - shvl.set(constructedService, annotation.replace(`${ANNOTATION_BASE}/`, ""), ingress.metadata.annotations[annotation]); + let constructedService = { + app: ingress.metadata.name, + namespace: ingress.metadata.namespace, + href: ingress.metadata.annotations[`${ANNOTATION_BASE}/href`] || getUrlFromIngress(ingress), + name: ingress.metadata.annotations[`${ANNOTATION_BASE}/name`] || ingress.metadata.name, + group: ingress.metadata.annotations[`${ANNOTATION_BASE}/group`] || "Kubernetes", + weight: ingress.metadata.annotations[`${ANNOTATION_BASE}/weight`] || "0", + icon: ingress.metadata.annotations[`${ANNOTATION_BASE}/icon`] || "", + description: ingress.metadata.annotations[`${ANNOTATION_BASE}/description`] || "", + external: false, + type: "service", + }; + if (ingress.metadata.annotations[`${ANNOTATION_BASE}/external`]) { + constructedService.external = + String(ingress.metadata.annotations[`${ANNOTATION_BASE}/external`]).toLowerCase() === "true"; } + if (ingress.metadata.annotations[ANNOTATION_POD_SELECTOR]) { + constructedService.podSelector = ingress.metadata.annotations[ANNOTATION_POD_SELECTOR]; + } + if (ingress.metadata.annotations[`${ANNOTATION_BASE}/ping`]) { + constructedService.ping = ingress.metadata.annotations[`${ANNOTATION_BASE}/ping`]; + } + Object.keys(ingress.metadata.annotations).forEach((annotation) => { + if (annotation.startsWith(ANNOTATION_WIDGET_BASE)) { + shvl.set( + constructedService, + annotation.replace(`${ANNOTATION_BASE}/`, ""), + ingress.metadata.annotations[annotation] + ); + } + }); + + try { + constructedService = JSON.parse(substituteEnvironmentVars(JSON.stringify(constructedService))); + } catch (e) { + logger.error("Error attempting k8s environment variable substitution."); + } + + return constructedService; }); - try { - constructedService = JSON.parse(substituteEnvironmentVars(JSON.stringify(constructedService))); - } catch (e) { - logger.error("Error attempting k8s environment variable substitution."); - } - - return constructedService; - }); - const mappedServiceGroups = []; services.forEach((serverService) => { @@ -244,7 +291,6 @@ export async function servicesFromKubernetes() { }); return mappedServiceGroups; - } catch (e) { logger.error(e); throw e; @@ -257,7 +303,7 @@ export function cleanServiceGroups(groups) { services: serviceGroup.services.map((service) => { const cleanedService = { ...service }; if (cleanedService.showStats !== undefined) cleanedService.showStats = JSON.parse(cleanedService.showStats); - if (typeof service.weight === 'string') { + if (typeof service.weight === "string") { const weight = parseInt(service.weight, 10); if (Number.isNaN(weight)) { cleanedService.weight = 0; @@ -292,14 +338,15 @@ export function cleanServiceGroups(groups) { } = cleanedService.widget; let fieldsList = fields; - if (typeof fields === 'string') { - try { JSON.parse(fields) } - catch (e) { + if (typeof fields === "string") { + try { + JSON.parse(fields); + } catch (e) { logger.error("Invalid fields list detected in config for service '%s'", service.name); fieldsList = null; } } - + cleanedService.widget = { type, fields: fieldsList || null,