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 */
|
/* eslint-disable no-console */
|
||||||
import { promises as fs } from "fs";
|
import {getSettings} from "utils/config/config";
|
||||||
import path from "path";
|
import {
|
||||||
|
bookmarksFromConfig,
|
||||||
import yaml from "js-yaml";
|
bookmarksFromDocker
|
||||||
|
} from "utils/config/bookmark-helpers";
|
||||||
import checkAndCopyConfig, { getSettings, substituteEnvironmentVars, CONF_DIR } from "utils/config/config";
|
|
||||||
import {
|
import {
|
||||||
servicesFromConfig,
|
servicesFromConfig,
|
||||||
servicesFromDocker,
|
servicesFromDocker,
|
||||||
@ -25,16 +24,29 @@ function compareServices(service1, service2) {
|
|||||||
}
|
}
|
||||||
|
|
||||||
export async function bookmarksResponse() {
|
export async function bookmarksResponse() {
|
||||||
checkAndCopyConfig("bookmarks.yaml");
|
let discoveredDockerBookmarks;
|
||||||
|
let configuredBookmarks;
|
||||||
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 initialSettings;
|
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 {
|
try {
|
||||||
initialSettings = await getSettings();
|
initialSettings = await getSettings();
|
||||||
@ -44,26 +56,39 @@ export async function bookmarksResponse() {
|
|||||||
initialSettings = {};
|
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 sortedGroups = [];
|
||||||
const unsortedGroups = [];
|
const unsortedGroups = [];
|
||||||
const definedLayouts = initialSettings.layout ? Object.keys(initialSettings.layout) : null;
|
const definedLayouts = initialSettings.layout ? Object.keys(initialSettings.layout) : null;
|
||||||
|
|
||||||
bookmarksArray.forEach((group) => {
|
const mergedGroupsNames = [
|
||||||
if (definedLayouts) {
|
...new Set(
|
||||||
const layoutIndex = definedLayouts.findIndex((layout) => layout === group.name);
|
[
|
||||||
if (layoutIndex > -1) sortedGroups[layoutIndex] = group;
|
discoveredDockerBookmarks.map((group) => group.name),
|
||||||
else unsortedGroups.push(group);
|
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 {
|
} 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