Feature: Added discover bookmarks from compose file (#1930)
This commit is contained in:
parent
b9f1ddd284
commit
2fa46066ae
@ -1,10 +1,9 @@
|
||||
/* eslint-disable no-console */
|
||||
import { promises as fs } from "fs";
|
||||
import path from "path";
|
||||
|
||||
import yaml from "js-yaml";
|
||||
|
||||
import checkAndCopyConfig, { getSettings, substituteEnvironmentVars, CONF_DIR } from "utils/config/config";
|
||||
import {getSettings} from "utils/config/config";
|
||||
import {
|
||||
bookmarksFromConfig,
|
||||
bookmarksFromDocker
|
||||
} from "utils/config/bookmark-helpers";
|
||||
import {
|
||||
servicesFromConfig,
|
||||
servicesFromDocker,
|
||||
@ -25,16 +24,29 @@ function compareServices(service1, service2) {
|
||||
}
|
||||
|
||||
export async function bookmarksResponse() {
|
||||
checkAndCopyConfig("bookmarks.yaml");
|
||||
|
||||
const bookmarksYaml = path.join(CONF_DIR, "bookmarks.yaml");
|
||||
const rawFileContents = await fs.readFile(bookmarksYaml, "utf8");
|
||||
const fileContents = substituteEnvironmentVars(rawFileContents);
|
||||
const bookmarks = yaml.load(fileContents);
|
||||
|
||||
if (!bookmarks) return [];
|
||||
|
||||
let discoveredDockerBookmarks;
|
||||
let configuredBookmarks;
|
||||
let initialSettings;
|
||||
console.debug("Bookmark response called");
|
||||
|
||||
try {
|
||||
discoveredDockerBookmarks = await bookmarksFromDocker();
|
||||
if (discoveredDockerBookmarks?.length === 0) {
|
||||
console.debug("No containers were found with bookmark labels");
|
||||
}
|
||||
} catch (e) {
|
||||
console.error("Failed to discover bookmarks, please check docker.yaml for errors or remove example entries.")
|
||||
if (e) console.error(e.toString());
|
||||
discoveredDockerBookmarks = [];
|
||||
}
|
||||
|
||||
try {
|
||||
configuredBookmarks = await bookmarksFromConfig();
|
||||
} catch (e) {
|
||||
console.error("Failed to load bookmarks.yaml");
|
||||
if (e) console.error(e.toString());
|
||||
configuredBookmarks = [];
|
||||
}
|
||||
|
||||
try {
|
||||
initialSettings = await getSettings();
|
||||
@ -44,26 +56,39 @@ export async function bookmarksResponse() {
|
||||
initialSettings = {};
|
||||
}
|
||||
|
||||
// map easy to write YAML objects into easy to consume JS arrays
|
||||
const bookmarksArray = bookmarks.map((group) => ({
|
||||
name: Object.keys(group)[0],
|
||||
bookmarks: group[Object.keys(group)[0]].map((entries) => ({
|
||||
name: Object.keys(entries)[0],
|
||||
...entries[Object.keys(entries)[0]][0],
|
||||
})),
|
||||
}));
|
||||
|
||||
const sortedGroups = [];
|
||||
const unsortedGroups = [];
|
||||
const definedLayouts = initialSettings.layout ? Object.keys(initialSettings.layout) : null;
|
||||
|
||||
bookmarksArray.forEach((group) => {
|
||||
if (definedLayouts) {
|
||||
const layoutIndex = definedLayouts.findIndex((layout) => layout === group.name);
|
||||
if (layoutIndex > -1) sortedGroups[layoutIndex] = group;
|
||||
else unsortedGroups.push(group);
|
||||
const mergedGroupsNames = [
|
||||
...new Set(
|
||||
[
|
||||
discoveredDockerBookmarks.map((group) => group.name),
|
||||
configuredBookmarks.map((group) => group.name),
|
||||
].flat(),
|
||||
)
|
||||
]
|
||||
mergedGroupsNames.forEach((groupName) => {
|
||||
|
||||
const discoveredDockerGroup = discoveredDockerBookmarks.find((group) => group.name === groupName) || {
|
||||
bookmarks: [],
|
||||
};
|
||||
const configuredGroup = configuredBookmarks.find((group) => group.name === groupName) || { bookmarks: [] };
|
||||
|
||||
|
||||
const mergedGroup = {
|
||||
name: groupName,
|
||||
bookmarks: [...discoveredDockerGroup.bookmarks, ...configuredGroup.bookmarks]
|
||||
.filter((bookmark) => bookmark)
|
||||
// .sort(compareBookmarks), // TODO is a sort needed?
|
||||
}
|
||||
|
||||
if (definedLayouts) {
|
||||
const layoutIndex = definedLayouts.findIndex((layout) => layout === mergedGroup.name);
|
||||
if (layoutIndex > -1) sortedGroups[layoutIndex] = mergedGroup;
|
||||
else unsortedGroups.push(mergedGroup);
|
||||
} else {
|
||||
unsortedGroups.push(group);
|
||||
unsortedGroups.push(mergedGroup);
|
||||
}
|
||||
});
|
||||
|
||||
|
||||
138
src/utils/config/bookmark-helpers.js
Normal file
138
src/utils/config/bookmark-helpers.js
Normal file
@ -0,0 +1,138 @@
|
||||
import { promises as fs } from "fs";
|
||||
import path from "path";
|
||||
|
||||
import yaml from "js-yaml";
|
||||
import Docker from "dockerode";
|
||||
|
||||
import checkAndCopyConfig, { CONF_DIR, substituteEnvironmentVars } from "utils/config/config";
|
||||
import getDockerArguments from "utils/config/docker";
|
||||
import * as shvl from "utils/config/shvl";
|
||||
|
||||
export async function bookmarksFromConfig() {
|
||||
|
||||
checkAndCopyConfig("bookmarks.yaml");
|
||||
|
||||
const bookmarksYaml = path.join(CONF_DIR, "bookmarks.yaml");
|
||||
const rawFileContents = await fs.readFile(bookmarksYaml, "utf8");
|
||||
const fileContents = substituteEnvironmentVars(rawFileContents);
|
||||
const bookmarks = yaml.load(fileContents);
|
||||
|
||||
if (!bookmarks) return [];
|
||||
|
||||
// map easy to write YAML objects into easy to consume JS arrays
|
||||
const bookmarksArray = bookmarks.map((group) => ({
|
||||
name: Object.keys(group)[0],
|
||||
bookmarks: group[Object.keys(group)[0]].map((entries) => ({
|
||||
name: Object.keys(entries)[0],
|
||||
...entries[Object.keys(entries)[0]][0],
|
||||
})),
|
||||
}));
|
||||
|
||||
return bookmarksArray;
|
||||
|
||||
}
|
||||
|
||||
const getDockerServers = async () => {
|
||||
checkAndCopyConfig("docker.yaml");
|
||||
const dockerYaml = path.join(CONF_DIR, "docker.yaml");
|
||||
const rawDockerFileContents = await fs.readFile(dockerYaml, "utf8");
|
||||
const dockerFileContents = substituteEnvironmentVars(rawDockerFileContents);
|
||||
return yaml.load(dockerFileContents);
|
||||
}
|
||||
|
||||
const listDockerContainers = async (servers, serverName) => {
|
||||
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));
|
||||
|
||||
// bad docker connections can result in a <Buffer ...> object?
|
||||
// in any case, this ensures the result is the expected array
|
||||
if (!Array.isArray(containers)) {
|
||||
return [];
|
||||
}
|
||||
return containers;
|
||||
}
|
||||
|
||||
const mapObjectsToGroup = (servers, objectName) => {
|
||||
const mappedObjectGroups = [];
|
||||
|
||||
servers.forEach((server) => {
|
||||
server[objectName].forEach((serverObject) => {
|
||||
let serverGroup = mappedObjectGroups.find((searchedGroup) => searchedGroup.name === serverObject.group);
|
||||
if (!serverGroup) {
|
||||
const gObject = {name: serverObject.group}
|
||||
gObject[objectName] = []
|
||||
mappedObjectGroups.push(gObject);
|
||||
serverGroup = mappedObjectGroups[mappedObjectGroups.length - 1];
|
||||
}
|
||||
|
||||
const { name: serverObjectName, group: serverObjectGroup, ...pushedObject } = serverObject;
|
||||
const result = {
|
||||
name: serverObjectName,
|
||||
...pushedObject,
|
||||
};
|
||||
|
||||
serverGroup[objectName].push(result);
|
||||
});
|
||||
});
|
||||
|
||||
return mappedObjectGroups;
|
||||
|
||||
}
|
||||
|
||||
export async function bookmarksFromDocker() {
|
||||
|
||||
const servers = await getDockerServers();
|
||||
|
||||
if (!servers) {
|
||||
return [];
|
||||
}
|
||||
|
||||
const bookmarkServers = await Promise.all(
|
||||
Object.keys(servers).map(async (serverName) => {
|
||||
try {
|
||||
const isSwarm = !!servers[serverName].swarm;
|
||||
const containers = await listDockerContainers(servers, serverName);
|
||||
const discovered = containers.map((container) => {
|
||||
let constructedBookmark = null;
|
||||
const containerLabels = isSwarm ? shvl.get(container, "Spec.Labels") : container.Labels;
|
||||
|
||||
Object.keys(containerLabels).forEach((label) => {
|
||||
if (label.startsWith("homepage.bookmarks.")) {
|
||||
const cleanLabel = label.replace("homepage.bookmarks.", "");
|
||||
|
||||
// homepage.bookmarks.this_is_bookmark_group.this_is_bookmark_name.abbr = "href"
|
||||
|
||||
// this_is_bookmark_group.this_is_bookmark_name.abbr = "href"
|
||||
// TODO should I add error handling for badly formatted labels?
|
||||
const [bookmarkGroup, bookmarkName, bookmarkAbbr] = cleanLabel.split('.');
|
||||
const bookmarkHref= containerLabels[label];
|
||||
|
||||
constructedBookmark = {
|
||||
group: bookmarkGroup,
|
||||
name: bookmarkName,
|
||||
href: bookmarkHref,
|
||||
abbr: bookmarkAbbr,
|
||||
type: "bookmark"
|
||||
};
|
||||
|
||||
}
|
||||
});
|
||||
|
||||
return constructedBookmark;
|
||||
});
|
||||
|
||||
return { server: serverName, bookmarks: discovered.filter((filteredBookmark) => filteredBookmark) };
|
||||
} catch (e) {
|
||||
// a server failed, but others may succeed
|
||||
return { server: serverName, bookmarks: [] };
|
||||
}
|
||||
}),
|
||||
);
|
||||
|
||||
const mappedBookmarkGroups = mapObjectsToGroup(bookmarkServers, 'bookmarks');
|
||||
return mappedBookmarkGroups;
|
||||
}
|
||||
Loading…
Reference in New Issue
Block a user