diff --git a/src/components/services/group.jsx b/src/components/services/group.jsx
index cdbb89f3..f25f7ec1 100644
--- a/src/components/services/group.jsx
+++ b/src/components/services/group.jsx
@@ -3,12 +3,13 @@ import classNames from "classnames";
import { Disclosure, Transition } from "@headlessui/react";
import { MdKeyboardArrowDown } from "react-icons/md";
+import { columnMap } from "../../utils/layout/columns";
+
import List from "components/services/list";
import ResolvedIcon from "components/resolvedicon";
export default function ServicesGroup({
group,
- services,
layout,
fiveColumns,
disableCollapse,
@@ -23,7 +24,7 @@ export default function ServicesGroup({
return (
)}
- {services.name}
+ {group.name}
-
+
+ {group.groups?.length > 0 && (
+
+ {group.groups.map((subgroup) => (
+
+ ))}
+
+ )}
>
diff --git a/src/components/services/item.jsx b/src/components/services/item.jsx
index 54560d6f..adf5fc97 100644
--- a/src/components/services/item.jsx
+++ b/src/components/services/item.jsx
@@ -12,7 +12,7 @@ import Kubernetes from "widgets/kubernetes/component";
import { SettingsContext } from "utils/contexts/settings";
import ResolvedIcon from "components/resolvedicon";
-export default function Item({ service, group, useEqualHeights }) {
+export default function Item({ service, groupName, useEqualHeights }) {
const hasLink = service.href && service.href !== "#";
const { settings } = useContext(SettingsContext);
const showStats = service.showStats === false ? false : settings.showStats;
@@ -90,14 +90,14 @@ export default function Item({ service, group, useEqualHeights }) {
>
{service.ping && (
)}
{service.siteMonitor && (
-
+
Site monitor status
)}
diff --git a/src/components/services/list.jsx b/src/components/services/list.jsx
index f3fd6e2a..c15d6aed 100644
--- a/src/components/services/list.jsx
+++ b/src/components/services/list.jsx
@@ -4,7 +4,7 @@ import { columnMap } from "../../utils/layout/columns";
import Item from "components/services/item";
-export default function List({ group, services, layout, useEqualHeights }) {
+export default function List({ groupName, services, layout, useEqualHeights }) {
return (
s).join("-")}
service={service}
- group={group}
+ groupName={groupName}
useEqualHeights={layout?.useEqualHeights ?? useEqualHeights}
/>
))}
diff --git a/src/components/services/ping.jsx b/src/components/services/ping.jsx
index f72d40b3..670f9d4b 100644
--- a/src/components/services/ping.jsx
+++ b/src/components/services/ping.jsx
@@ -1,9 +1,9 @@
import { useTranslation } from "react-i18next";
import useSWR from "swr";
-export default function Ping({ group, service, style }) {
+export default function Ping({ groupName, serviceName, style }) {
const { t } = useTranslation();
- const { data, error } = useSWR(`/api/ping?${new URLSearchParams({ group, service }).toString()}`, {
+ const { data, error } = useSWR(`/api/ping?${new URLSearchParams({ groupName, serviceName }).toString()}`, {
refreshInterval: 30000,
});
diff --git a/src/components/services/site-monitor.jsx b/src/components/services/site-monitor.jsx
index 3d5ef79e..4dceb44c 100644
--- a/src/components/services/site-monitor.jsx
+++ b/src/components/services/site-monitor.jsx
@@ -1,9 +1,9 @@
import { useTranslation } from "react-i18next";
import useSWR from "swr";
-export default function SiteMonitor({ group, service, style }) {
+export default function SiteMonitor({ groupName, serviceName, style }) {
const { t } = useTranslation();
- const { data, error } = useSWR(`/api/siteMonitor?${new URLSearchParams({ group, service }).toString()}`, {
+ const { data, error } = useSWR(`/api/siteMonitor?${new URLSearchParams({ groupName, serviceName }).toString()}`, {
refreshInterval: 30000,
});
diff --git a/src/pages/api/ping.js b/src/pages/api/ping.js
index e540fa68..8ef64ffc 100644
--- a/src/pages/api/ping.js
+++ b/src/pages/api/ping.js
@@ -6,10 +6,10 @@ import createLogger from "utils/logger";
const logger = createLogger("ping");
export default async function handler(req, res) {
- const { group, service } = req.query;
- const serviceItem = await getServiceItem(group, service);
+ const { groupName, serviceName } = req.query;
+ const serviceItem = await getServiceItem(groupName, serviceName);
if (!serviceItem) {
- logger.debug(`No service item found for group ${group} named ${service}`);
+ logger.debug(`No service item found for group ${groupName} named ${serviceName}`);
return res.status(400).send({
error: "Unable to find service, see log for details.",
});
diff --git a/src/pages/api/siteMonitor.js b/src/pages/api/siteMonitor.js
index 9e030d74..072d3d4c 100644
--- a/src/pages/api/siteMonitor.js
+++ b/src/pages/api/siteMonitor.js
@@ -7,10 +7,10 @@ import { httpProxy } from "utils/proxy/http";
const logger = createLogger("siteMonitor");
export default async function handler(req, res) {
- const { group, service } = req.query;
- const serviceItem = await getServiceItem(group, service);
+ const { groupName, serviceName } = req.query;
+ const serviceItem = await getServiceItem(groupName, serviceName);
if (!serviceItem) {
- logger.debug(`No service item found for group ${group} named ${service}`);
+ logger.debug(`No service item found for group ${groupName} named ${serviceName}`);
return res.status(400).send({
error: "Unable to find service, see log for details.",
});
diff --git a/src/pages/index.jsx b/src/pages/index.jsx
index dd0df95f..7a7fdef0 100644
--- a/src/pages/index.jsx
+++ b/src/pages/index.jsx
@@ -291,8 +291,7 @@ function Home({ initialSettings }) {
group.services ? (
(
{
+ if (group.name === mergedGroup.name) {
+ // eslint-disable-next-line no-param-reassign
+ group.services = mergedGroup.services;
+ } else if (group.groups) {
+ mergeSubgroups(group.groups, mergedGroup);
+ }
+ });
+}
+
export async function servicesResponse() {
let discoveredDockerServices;
let discoveredKubernetesServices;
@@ -140,25 +152,29 @@ export async function servicesResponse() {
const definedLayouts = initialSettings.layout ? Object.keys(initialSettings.layout) : null;
mergedGroupsNames.forEach((groupName) => {
- const discoveredDockerGroup = discoveredDockerServices.find((group) => group.name === groupName) || {
+ const discoveredDockerGroup = findGroupByName(discoveredDockerServices, groupName) || {
services: [],
};
- const discoveredKubernetesGroup = discoveredKubernetesServices.find((group) => group.name === groupName) || {
+ const discoveredKubernetesGroup = findGroupByName(discoveredKubernetesServices, groupName) || {
services: [],
};
- const configuredGroup = configuredServices.find((group) => group.name === groupName) || { services: [] };
+ const configuredGroup = findGroupByName(configuredServices, groupName) || { services: [] };
const mergedGroup = {
name: groupName,
services: [...discoveredDockerGroup.services, ...discoveredKubernetesGroup.services, ...configuredGroup.services]
.filter((service) => service)
.sort(compareServices),
+ groups: [...configuredGroup.groups],
};
if (definedLayouts) {
const layoutIndex = definedLayouts.findIndex((layout) => layout === mergedGroup.name);
if (layoutIndex > -1) sortedGroups[layoutIndex] = mergedGroup;
- else unsortedGroups.push(mergedGroup);
+ else if (configuredGroup.name) {
+ // this is a nested group, so find the parent group and merge the services
+ mergeSubgroups(configuredServices, mergedGroup);
+ } else unsortedGroups.push(mergedGroup);
} else {
unsortedGroups.push(mergedGroup);
}
diff --git a/src/utils/config/service-helpers.js b/src/utils/config/service-helpers.js
index e6ef6173..0f1e2c8c 100644
--- a/src/utils/config/service-helpers.js
+++ b/src/utils/config/service-helpers.js
@@ -13,6 +13,38 @@ import * as shvl from "utils/config/shvl";
const logger = createLogger("service-helpers");
+function parseServicesToGroups(services) {
+ if (!services) {
+ return [];
+ }
+
+ // map easy to write YAML objects into easy to consume JS arrays
+ return services.map((serviceGroup) => {
+ const name = Object.keys(serviceGroup)[0];
+ let groups = [];
+ const serviceGroupServices = [];
+ serviceGroup[name].forEach((entries) => {
+ const entryName = Object.keys(entries)[0];
+ if (Array.isArray(entries[entryName])) {
+ groups = groups.concat(parseServicesToGroups([{ [entryName]: entries[entryName] }]));
+ } else {
+ serviceGroupServices.push({
+ name: entryName,
+ ...entries[entryName],
+ weight: entries[entryName].weight || serviceGroupServices.length * 100, // default weight
+ type: "service",
+ });
+ }
+ });
+ return {
+ name,
+ type: "group",
+ services: serviceGroupServices,
+ groups,
+ };
+ });
+}
+
export async function servicesFromConfig() {
checkAndCopyConfig("services.yaml");
@@ -20,31 +52,7 @@ export async function servicesFromConfig() {
const rawFileContents = await fs.readFile(servicesYaml, "utf8");
const fileContents = substituteEnvironmentVars(rawFileContents);
const services = yaml.load(fileContents);
-
- 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",
- })),
- }));
-
- // add default weight to services based on their position in the configuration
- servicesArray.forEach((group, groupIndex) => {
- group.services.forEach((service, serviceIndex) => {
- if (service.weight === undefined) {
- servicesArray[groupIndex].services[serviceIndex].weight = (serviceIndex + 1) * 100;
- }
- });
- });
-
- return servicesArray;
+ return parseServicesToGroups(services);
}
export async function servicesFromDocker() {
@@ -667,13 +675,30 @@ export function cleanServiceGroups(groups) {
});
return cleanedService;
}),
+ type: serviceGroup.type || "group",
+ groups: serviceGroup.groups ? cleanServiceGroups(serviceGroup.groups) : [],
}));
}
+export function findGroupByName(groups, name) {
+ for (let i = 0; i < groups.length; i += 1) {
+ const group = groups[i];
+ if (group.name === name) {
+ return group;
+ } else if (group.groups) {
+ const foundGroup = findGroupByName(group.groups, name);
+ if (foundGroup) {
+ return foundGroup;
+ }
+ }
+ }
+ return null;
+}
+
export async function getServiceItem(group, service) {
const configuredServices = await servicesFromConfig();
- const serviceGroup = configuredServices.find((g) => g.name === group);
+ const serviceGroup = findGroupByName(configuredServices, group);
if (serviceGroup) {
const serviceEntry = serviceGroup.services.find((s) => s.name === service);
if (serviceEntry) return serviceEntry;
@@ -681,14 +706,14 @@ export async function getServiceItem(group, service) {
const discoveredServices = await servicesFromDocker();
- const dockerServiceGroup = discoveredServices.find((g) => g.name === group);
+ const dockerServiceGroup = findGroupByName(discoveredServices, group);
if (dockerServiceGroup) {
const dockerServiceEntry = dockerServiceGroup.services.find((s) => s.name === service);
if (dockerServiceEntry) return dockerServiceEntry;
}
const kubernetesServices = await servicesFromKubernetes();
- const kubernetesServiceGroup = kubernetesServices.find((g) => g.name === group);
+ const kubernetesServiceGroup = findGroupByName(kubernetesServices, group);
if (kubernetesServiceGroup) {
const kubernetesServiceEntry = kubernetesServiceGroup.services.find((s) => s.name === service);
if (kubernetesServiceEntry) return kubernetesServiceEntry;