Run pre-commit hooks over existing codebase

Co-Authored-By: Ben Phelps <ben@phelps.io>
This commit is contained in:
shamoon 2023-10-17 23:26:55 -07:00
parent fa50bbad9c
commit 19c25713c4
387 changed files with 4785 additions and 4109 deletions

View file

@ -23,7 +23,10 @@ function MyApp({ Component, pageProps }) {
>
<Head>
{/* https://nextjs.org/docs/messages/no-document-viewport-meta */}
<meta name="viewport" content="width=device-width, initial-scale=1.0, minimum-scale=1.0, maximum-scale=1.0, user-scalable=no" />
<meta
name="viewport"
content="width=device-width, initial-scale=1.0, minimum-scale=1.0, maximum-scale=1.0, user-scalable=no"
/>
</Head>
<ColorProvider>
<ThemeProvider>

View file

@ -1,35 +1,34 @@
import path from "path";
import fs from "fs";
import { CONF_DIR } from "utils/config/config";
import createLogger from "utils/logger";
const logger = createLogger("configFileService");
/**
* @param {import("next").NextApiRequest} req
* @param {import("next").NextApiResponse} res
*/
export default async function handler(req, res) {
const { path: relativePath } = req.query;
// only two supported files, for now
if (!['custom.css', 'custom.js'].includes(relativePath))
{
return res.status(422).end('Unsupported file');
}
const filePath = path.join(CONF_DIR, relativePath);
try {
// Read the content of the file or return empty content
const fileContent = fs.existsSync(filePath) ? fs.readFileSync(filePath, 'utf-8') : '';
// hard-coded since we only support two known files for now
const mimeType = (relativePath === 'custom.css') ? 'text/css' : 'text/javascript';
res.setHeader('Content-Type', mimeType);
return res.status(200).send(fileContent);
} catch (error) {
logger.error(error);
return res.status(500).end('Internal Server Error');
}
}
import path from "path";
import fs from "fs";
import { CONF_DIR } from "utils/config/config";
import createLogger from "utils/logger";
const logger = createLogger("configFileService");
/**
* @param {import("next").NextApiRequest} req
* @param {import("next").NextApiResponse} res
*/
export default async function handler(req, res) {
const { path: relativePath } = req.query;
// only two supported files, for now
if (!["custom.css", "custom.js"].includes(relativePath)) {
return res.status(422).end("Unsupported file");
}
const filePath = path.join(CONF_DIR, relativePath);
try {
// Read the content of the file or return empty content
const fileContent = fs.existsSync(filePath) ? fs.readFileSync(filePath, "utf-8") : "";
// hard-coded since we only support two known files for now
const mimeType = relativePath === "custom.css" ? "text/css" : "text/javascript";
res.setHeader("Content-Type", mimeType);
return res.status(200).send(fileContent);
} catch (error) {
logger.error(error);
return res.status(500).end("Internal Server Error");
}
}

View file

@ -44,7 +44,8 @@ export default async function handler(req, res) {
// Try with a service deployed in Docker Swarm, if enabled
if (dockerArgs.swarm) {
const tasks = await docker.listTasks({
const tasks = await docker
.listTasks({
filters: {
service: [containerName],
// A service can have several offline containers, so we only look for an active one.
@ -55,10 +56,10 @@ export default async function handler(req, res) {
// TODO: Show the result for all replicas/containers?
// We can only get stats for 'local' containers so try to find one
const localContainerIDs = containers.map(c => c.Id);
const task = tasks.find(t => localContainerIDs.includes(t.Status?.ContainerStatus?.ContainerID)) ?? tasks.at(0);
const localContainerIDs = containers.map((c) => c.Id);
const task = tasks.find((t) => localContainerIDs.includes(t.Status?.ContainerStatus?.ContainerID)) ?? tasks.at(0);
const taskContainerId = task?.Status?.ContainerStatus?.ContainerID;
if (taskContainerId) {
try {
const container = docker.getContainer(taskContainerId);
@ -69,8 +70,8 @@ export default async function handler(req, res) {
});
} catch (e) {
return res.status(200).json({
error: "Unable to retrieve stats"
})
error: "Unable to retrieve stats",
});
}
}
}
@ -81,7 +82,7 @@ export default async function handler(req, res) {
} catch (e) {
logger.error(e);
return res.status(500).send({
error: {message: e?.message ?? "Unknown error"},
error: { message: e?.message ?? "Unknown error" },
});
}
}

View file

@ -44,7 +44,9 @@ export default async function handler(req, res) {
}
if (dockerArgs.swarm) {
const serviceInfo = await docker.getService(containerName).inspect()
const serviceInfo = await docker
.getService(containerName)
.inspect()
.catch(() => undefined);
if (!serviceInfo) {
@ -77,15 +79,16 @@ export default async function handler(req, res) {
}
} else {
// Global service, prefer 'local' containers
const localContainerIDs = containers.map(c => c.Id);
const task = tasks.find(t => localContainerIDs.includes(t.Status?.ContainerStatus?.ContainerID)) ?? tasks.at(0);
const localContainerIDs = containers.map((c) => c.Id);
const task =
tasks.find((t) => localContainerIDs.includes(t.Status?.ContainerStatus?.ContainerID)) ?? tasks.at(0);
const taskContainerId = task?.Status?.ContainerStatus?.ContainerID;
if (taskContainerId) {
try {
const container = docker.getContainer(taskContainerId);
const info = await container.inspect();
return res.status(200).json({
status: info.State.Status,
health: info.State.Health?.Status,
@ -93,8 +96,8 @@ export default async function handler(req, res) {
} catch (e) {
if (task) {
return res.status(200).json({
status: task.Status.State
})
status: task.Status.State,
});
}
}
}
@ -107,7 +110,7 @@ export default async function handler(req, res) {
} catch (e) {
logger.error(e);
return res.status(500).send({
error: {message: e?.message ?? "Unknown error"},
error: { message: e?.message ?? "Unknown error" },
});
}
}

View file

@ -4,7 +4,15 @@ import { readFileSync } from "fs";
import checkAndCopyConfig, { CONF_DIR } from "utils/config/config";
const configs = ["docker.yaml", "settings.yaml", "services.yaml", "bookmarks.yaml", "widgets.yaml", "custom.css", "custom.js"];
const configs = [
"docker.yaml",
"settings.yaml",
"services.yaml",
"bookmarks.yaml",
"widgets.yaml",
"custom.css",
"custom.js",
];
function hash(buffer) {
const hashSum = createHash("sha256");
@ -20,7 +28,7 @@ export default async function handler(req, res) {
});
// set to date by docker entrypoint, will force revalidation between restarts/recreates
const buildTime = process.env.HOMEPAGE_BUILDTIME?.length ? process.env.HOMEPAGE_BUILDTIME : '';
const buildTime = process.env.HOMEPAGE_BUILDTIME?.length ? process.env.HOMEPAGE_BUILDTIME : "";
const combinedHash = hash(hashes.join("") + buildTime);

View file

@ -13,7 +13,7 @@ export default async function handler(req, res) {
const [namespace, appName] = service;
if (!namespace && !appName) {
res.status(400).send({
error: "kubernetes query parameters are required"
error: "kubernetes query parameters are required",
});
return;
}
@ -23,13 +23,14 @@ export default async function handler(req, res) {
const kc = getKubeConfig();
if (!kc) {
res.status(500).send({
error: "No kubernetes configuration"
error: "No kubernetes configuration",
});
return;
}
const coreApi = kc.makeApiClient(CoreV1Api);
const metricsApi = new Metrics(kc);
const podsResponse = await coreApi.listNamespacedPod(namespace, null, null, null, null, labelSelector)
const podsResponse = await coreApi
.listNamespacedPod(namespace, null, null, null, null, labelSelector)
.then((response) => response.body)
.catch((err) => {
logger.error("Error getting pods: %d %s %s", err.statusCode, err.body, err.response);
@ -37,7 +38,7 @@ export default async function handler(req, res) {
});
if (!podsResponse) {
res.status(500).send({
error: "Error communicating with kubernetes"
error: "Error communicating with kubernetes",
});
return;
}
@ -63,33 +64,36 @@ export default async function handler(req, res) {
});
});
const podStatsList = await Promise.all(pods.map(async (pod) => {
let depMem = 0;
let depCpu = 0;
const podMetrics = await metricsApi.getPodMetrics(namespace, pod.metadata.name)
.then((response) => response)
.catch((err) => {
// 404 generally means that the metrics have not been populated yet
if (err.statusCode !== 404) {
logger.error("Error getting pod metrics: %d %s %s", err.statusCode, err.body, err.response);
}
return null;
});
if (podMetrics) {
podMetrics.containers.forEach((container) => {
depMem += parseMemory(container.usage.memory);
depCpu += parseCpu(container.usage.cpu);
});
}
return {
mem: depMem,
cpu: depCpu
};
}));
const podStatsList = await Promise.all(
pods.map(async (pod) => {
let depMem = 0;
let depCpu = 0;
const podMetrics = await metricsApi
.getPodMetrics(namespace, pod.metadata.name)
.then((response) => response)
.catch((err) => {
// 404 generally means that the metrics have not been populated yet
if (err.statusCode !== 404) {
logger.error("Error getting pod metrics: %d %s %s", err.statusCode, err.body, err.response);
}
return null;
});
if (podMetrics) {
podMetrics.containers.forEach((container) => {
depMem += parseMemory(container.usage.memory);
depCpu += parseCpu(container.usage.cpu);
});
}
return {
mem: depMem,
cpu: depCpu,
};
}),
);
const stats = {
mem: 0,
cpu: 0
}
cpu: 0,
};
podStatsList.forEach((podStat) => {
stats.mem += podStat.mem;
stats.cpu += podStat.cpu;
@ -99,12 +103,12 @@ export default async function handler(req, res) {
stats.cpuUsage = cpuLimit ? stats.cpu / cpuLimit : 0;
stats.memUsage = memLimit ? stats.mem / memLimit : 0;
res.status(200).json({
stats
stats,
});
} catch (e) {
logger.error(e);
res.status(500).send({
error: "unknown error"
error: "unknown error",
});
}
}

View file

@ -6,7 +6,7 @@ import createLogger from "../../../../utils/logger";
const logger = createLogger("kubernetesStatusService");
export default async function handler(req, res) {
const APP_LABEL = "app.kubernetes.io/name";
const APP_LABEL = "app.kubernetes.io/name";
const { service, podSelector } = req.query;
const [namespace, appName] = service;
@ -21,12 +21,13 @@ export default async function handler(req, res) {
const kc = getKubeConfig();
if (!kc) {
res.status(500).send({
error: "No kubernetes configuration"
error: "No kubernetes configuration",
});
return;
}
const coreApi = kc.makeApiClient(CoreV1Api);
const podsResponse = await coreApi.listNamespacedPod(namespace, null, null, null, null, labelSelector)
const podsResponse = await coreApi
.listNamespacedPod(namespace, null, null, null, null, labelSelector)
.then((response) => response.body)
.catch((err) => {
logger.error("Error getting pods: %d %s %s", err.statusCode, err.body, err.response);
@ -34,7 +35,7 @@ export default async function handler(req, res) {
});
if (!podsResponse) {
res.status(500).send({
error: "Error communicating with kubernetes"
error: "Error communicating with kubernetes",
});
return;
}
@ -46,7 +47,7 @@ export default async function handler(req, res) {
});
return;
}
const someReady = pods.find(pod => pod.status.phase === "Running");
const someReady = pods.find((pod) => pod.status.phase === "Running");
const allReady = pods.every((pod) => pod.status.phase === "Running");
let status = "down";
if (allReady) {
@ -55,7 +56,7 @@ export default async function handler(req, res) {
status = "partial";
}
res.status(200).json({
status
status,
});
} catch (e) {
logger.error(e);

View file

@ -7,46 +7,46 @@ import { httpProxy } from "utils/proxy/http";
const logger = createLogger("ping");
export default async function handler(req, res) {
const { group, service } = req.query;
const serviceItem = await getServiceItem(group, service);
if (!serviceItem) {
logger.debug(`No service item found for group ${group} named ${service}`);
return res.status(400).send({
error: "Unable to find service, see log for details.",
});
const { group, service } = req.query;
const serviceItem = await getServiceItem(group, service);
if (!serviceItem) {
logger.debug(`No service item found for group ${group} named ${service}`);
return res.status(400).send({
error: "Unable to find service, see log for details.",
});
}
const { ping: pingURL } = serviceItem;
if (!pingURL) {
logger.debug("No ping URL specified");
return res.status(400).send({
error: "No ping URL given",
});
}
try {
let startTime = performance.now();
let [status] = await httpProxy(pingURL, {
method: "HEAD",
});
let endTime = performance.now();
if (status > 403) {
// try one more time as a GET in case HEAD is rejected for whatever reason
startTime = performance.now();
[status] = await httpProxy(pingURL);
endTime = performance.now();
}
const { ping: pingURL } = serviceItem;
if (!pingURL) {
logger.debug("No ping URL specified");
return res.status(400).send({
error: "No ping URL given",
});
}
try {
let startTime = performance.now();
let [status] = await httpProxy(pingURL, {
method: "HEAD"
});
let endTime = performance.now();
if (status > 403) {
// try one more time as a GET in case HEAD is rejected for whatever reason
startTime = performance.now();
[status] = await httpProxy(pingURL);
endTime = performance.now();
}
return res.status(200).json({
status,
latency: endTime - startTime
});
} catch (e) {
logger.debug("Error attempting ping: %s", JSON.stringify(e));
return res.status(400).send({
error: 'Error attempting ping, see logs.',
});
}
return res.status(200).json({
status,
latency: endTime - startTime,
});
} catch (e) {
logger.debug("Error attempting ping: %s", JSON.stringify(e));
return res.status(400).send({
error: "Error attempting ping, see logs.",
});
}
}

View file

@ -44,13 +44,13 @@ export default async function handler(req, res) {
if (req.query.query && (mappingParams || optionalParams)) {
const queryParams = JSON.parse(req.query.query);
let filteredOptionalParams = []
if (optionalParams) filteredOptionalParams = optionalParams.filter(p => queryParams[p] !== undefined);
let filteredOptionalParams = [];
if (optionalParams) filteredOptionalParams = optionalParams.filter((p) => queryParams[p] !== undefined);
let params = [];
if (mappingParams) params = params.concat(mappingParams);
if (filteredOptionalParams) params = params.concat(filteredOptionalParams);
const query = new URLSearchParams(params.map((p) => [p, queryParams[p]]));
req.query.endpoint = `${req.query.endpoint}?${query}`;
}

View file

@ -15,23 +15,25 @@ async function retrieveFromGlancesAPI(privateWidgetOptions, endpoint) {
const apiUrl = `${url}/api/3/${endpoint}`;
const headers = {
"Accept-Encoding": "application/json"
"Accept-Encoding": "application/json",
};
if (privateWidgetOptions.username && privateWidgetOptions.password) {
headers.Authorization = `Basic ${Buffer.from(`${privateWidgetOptions.username}:${privateWidgetOptions.password}`).toString("base64")}`
headers.Authorization = `Basic ${Buffer.from(
`${privateWidgetOptions.username}:${privateWidgetOptions.password}`,
).toString("base64")}`;
}
const params = { method: "GET", headers };
const [status, , data] = await httpProxy(apiUrl, params);
if (status === 401) {
errorMessage = `Authorization failure getting data from glances API. Data: ${data.toString()}`
errorMessage = `Authorization failure getting data from glances API. Data: ${data.toString()}`;
logger.error(errorMessage);
throw new Error(errorMessage);
}
if (status !== 200) {
errorMessage = `HTTP ${status} getting data from glances API. Data: ${data.toString()}`
errorMessage = `HTTP ${status} getting data from glances API. Data: ${data.toString()}`;
logger.error(errorMessage);
throw new Error(errorMessage);
}
@ -52,7 +54,7 @@ export default async function handler(req, res) {
cpu: cpuData,
load: loadData,
mem: memoryData,
}
};
// Disabled by default, dont call unless needed
if (includeUptime) {

View file

@ -11,13 +11,14 @@ export default async function handler(req, res) {
const kc = getKubeConfig();
if (!kc) {
return res.status(500).send({
error: "No kubernetes configuration"
error: "No kubernetes configuration",
});
}
const coreApi = kc.makeApiClient(CoreV1Api);
const metricsApi = new Metrics(kc);
const nodes = await coreApi.listNode()
const nodes = await coreApi
.listNode()
.then((response) => response.body)
.catch((error) => {
logger.error("Error getting ingresses: %d %s %s", error.statusCode, error.body, error.response);
@ -25,7 +26,7 @@ export default async function handler(req, res) {
});
if (!nodes) {
return res.status(500).send({
error: "unknown error"
error: "unknown error",
});
}
let cpuTotal = 0;
@ -37,16 +38,18 @@ export default async function handler(req, res) {
nodes.items.forEach((node) => {
const cpu = Number.parseInt(node.status.capacity.cpu, 10);
const mem = parseMemory(node.status.capacity.memory);
const ready = node.status.conditions.filter(condition => condition.type === "Ready" && condition.status === "True").length > 0;
const ready =
node.status.conditions.filter((condition) => condition.type === "Ready" && condition.status === "True").length >
0;
nodeMap[node.metadata.name] = {
name: node.metadata.name,
ready,
cpu: {
total: cpu
total: cpu,
},
memory: {
total: mem
}
total: mem,
},
};
cpuTotal += cpu;
memTotal += mem;
@ -68,7 +71,7 @@ export default async function handler(req, res) {
} catch (error) {
logger.error("Error getting metrics, ensure you have metrics-server installed: s", JSON.stringify(error));
return res.status(500).send({
error: "Error getting metrics, check logs for more details"
error: "Error getting metrics, check logs for more details",
});
}
@ -76,24 +79,24 @@ export default async function handler(req, res) {
cpu: {
load: cpuUsage,
total: cpuTotal,
percent: (cpuUsage / cpuTotal) * 100
percent: (cpuUsage / cpuTotal) * 100,
},
memory: {
used: memUsage,
total: memTotal,
free: (memTotal - memUsage),
percent: (memUsage / memTotal) * 100
}
free: memTotal - memUsage,
percent: (memUsage / memTotal) * 100,
},
};
return res.status(200).json({
cluster,
nodes: Object.entries(nodeMap).map(([name, node]) => ({ name, ...node }))
nodes: Object.entries(nodeMap).map(([name, node]) => ({ name, ...node })),
});
} catch (e) {
logger.error("exception %s", e);
return res.status(500).send({
error: "unknown error"
error: "unknown error",
});
}
}

View file

@ -47,7 +47,7 @@ function parseLonghornData(data) {
export default async function handler(req, res) {
const settings = getSettings();
const longhornSettings = settings?.providers?.longhorn || {};
const {url, username, password} = longhornSettings;
const { url, username, password } = longhornSettings;
if (!url) {
const errorMessage = "Missing Longhorn URL";
@ -57,10 +57,10 @@ export default async function handler(req, res) {
const apiUrl = `${url}/v1/nodes`;
const headers = {
"Accept-Encoding": "application/json"
"Accept-Encoding": "application/json",
};
if (username && password) {
headers.Authorization = `Basic ${Buffer.from(`${username}:${password}`).toString("base64")}`
headers.Authorization = `Basic ${Buffer.from(`${username}:${password}`).toString("base64")}`;
}
const params = { method: "GET", headers };

View file

@ -3,7 +3,7 @@ import cachedFetch from "utils/proxy/cached-fetch";
export default async function handler(req, res) {
const { latitude, longitude, units, cache, timezone } = req.query;
const degrees = units === "imperial" ? "fahrenheit" : "celsius";
const timezeone = timezone ?? 'auto'
const timezeone = timezone ?? "auto";
const apiUrl = `https://api.open-meteo.com/v1/forecast?latitude=${latitude}&longitude=${longitude}&daily=sunrise,sunset&current_weather=true&temperature_unit=${degrees}&timezone=${timezeone}`;
return res.send(await cachedFetch(apiUrl, cache));
}
}

View file

@ -1,6 +1,6 @@
import { existsSync } from "fs";
const si = require('systeminformation');
const si = require("systeminformation");
export default async function handler(req, res) {
const { type, target } = req.query;
@ -25,7 +25,7 @@ export default async function handler(req, res) {
const fsSize = await si.fsSize();
return res.status(200).json({
drive: fsSize.find(fs => fs.mount === target) ?? fsSize.find(fs => fs.mount === "/")
drive: fsSize.find((fs) => fs.mount === target) ?? fsSize.find((fs) => fs.mount === "/"),
});
}
@ -44,7 +44,7 @@ export default async function handler(req, res) {
if (type === "uptime") {
const timeData = await si.time();
return res.status(200).json({
uptime: timeData.uptime
uptime: timeData.uptime,
});
}

View file

@ -183,7 +183,10 @@ function Home({ initialSettings }) {
const { data: bookmarks } = useSWR("api/bookmarks");
const { data: widgets } = useSWR("api/widgets");
const servicesAndBookmarks = [...services.map(sg => sg.services).flat(), ...bookmarks.map(bg => bg.bookmarks).flat()].filter(i => i?.href);
const servicesAndBookmarks = [
...services.map((sg) => sg.services).flat(),
...bookmarks.map((bg) => bg.bookmarks).flat(),
].filter((i) => i?.href);
useEffect(() => {
if (settings.language) {
@ -202,15 +205,15 @@ function Home({ initialSettings }) {
const [searching, setSearching] = useState(false);
const [searchString, setSearchString] = useState("");
let searchProvider = null;
const searchWidget = Object.values(widgets).find(w => w.type === "search");
const searchWidget = Object.values(widgets).find((w) => w.type === "search");
if (searchWidget) {
if (Array.isArray(searchWidget.options?.provider)) {
// if search provider is a list, try to retrieve from localstorage, fall back to the first
searchProvider = getStoredProvider() ?? searchProviders[searchWidget.options.provider[0]];
} else if (searchWidget.options?.provider === 'custom') {
} else if (searchWidget.options?.provider === "custom") {
searchProvider = {
url: searchWidget.options.url
}
url: searchWidget.options.url,
};
} else {
searchProvider = searchProviders[searchWidget.options?.provider];
}
@ -229,35 +232,38 @@ function Home({ initialSettings }) {
}
}
document.addEventListener('keydown', handleKeyDown);
document.addEventListener("keydown", handleKeyDown);
return function cleanup() {
document.removeEventListener('keydown', handleKeyDown);
}
})
document.removeEventListener("keydown", handleKeyDown);
};
});
const tabs = useMemo( () => [
...new Set(
Object.keys(settings.layout ?? {}).map(
(groupName) => settings.layout[groupName]?.tab?.toString()
).filter(group => group)
)
], [settings.layout]);
const tabs = useMemo(
() => [
...new Set(
Object.keys(settings.layout ?? {})
.map((groupName) => settings.layout[groupName]?.tab?.toString())
.filter((group) => group),
),
],
[settings.layout],
);
useEffect(() => {
if (!activeTab) {
const initialTab = decodeURI(asPath.substring(asPath.indexOf("#") + 1));
setActiveTab(initialTab === '/' ? slugify(tabs['0']) : initialTab)
setActiveTab(initialTab === "/" ? slugify(tabs["0"]) : initialTab);
}
})
});
const servicesAndBookmarksGroups = useMemo(() => {
const tabGroupFilter = g => g && [activeTab, ''].includes(slugify(settings.layout?.[g.name]?.tab));
const undefinedGroupFilter = g => settings.layout?.[g.name] === undefined;
const tabGroupFilter = (g) => g && [activeTab, ""].includes(slugify(settings.layout?.[g.name]?.tab));
const undefinedGroupFilter = (g) => settings.layout?.[g.name] === undefined;
const layoutGroups = Object.keys(settings.layout ?? {}).map(
(groupName) => services?.find(g => g.name === groupName) ?? bookmarks?.find(b => b.name === groupName)
).filter(tabGroupFilter);
const layoutGroups = Object.keys(settings.layout ?? {})
.map((groupName) => services?.find((g) => g.name === groupName) ?? bookmarks?.find((b) => b.name === groupName))
.filter(tabGroupFilter);
if (!settings.layout && JSON.stringify(settings.layout) !== JSON.stringify(initialSettings.layout)) {
// wait for settings to populate (if different from initial settings), otherwise all the widgets will be requested initially even if we are on a single tab
@ -267,58 +273,77 @@ function Home({ initialSettings }) {
const serviceGroups = services?.filter(tabGroupFilter).filter(undefinedGroupFilter);
const bookmarkGroups = bookmarks.filter(tabGroupFilter).filter(undefinedGroupFilter);
return <>
{tabs.length > 0 && <div key="tabs" id="tabs" className="m-6 sm:m-9 sm:mt-4 sm:mb-0">
<ul className={classNames(
"sm:flex rounded-md bg-theme-100/20 dark:bg-white/5",
settings.cardBlur !== undefined && `backdrop-blur${settings.cardBlur.length ? '-': "" }${settings.cardBlur}`
)} id="myTab" data-tabs-toggle="#myTabContent" role="tablist" >
{tabs.map(tab => <Tab key={tab} tab={tab} />)}
</ul>
</div>}
{layoutGroups.length > 0 && <div key="layoutGroups" id="layout-groups" className="flex flex-wrap m-4 sm:m-8 sm:mt-4 items-start mb-2">
{layoutGroups.map((group) => (
group.services ?
(<ServicesGroup
key={group.name}
group={group.name}
services={group}
layout={settings.layout?.[group.name]}
fiveColumns={settings.fiveColumns}
disableCollapse={settings.disableCollapse}
/>) :
(<BookmarksGroup
key={group.name}
bookmarks={group}
layout={settings.layout?.[group.name]}
disableCollapse={settings.disableCollapse}
/>)
)
)}
</div>}
{serviceGroups?.length > 0 && <div key="services" id="services" className="flex flex-wrap m-4 sm:m-8 sm:mt-4 items-start mb-2">
{serviceGroups.map((group) => (
<ServicesGroup
key={group.name}
group={group.name}
services={group}
layout={settings.layout?.[group.name]}
fiveColumns={settings.fiveColumns}
disableCollapse={settings.disableCollapse}
/>
))}
</div>}
{bookmarkGroups?.length > 0 && <div key="bookmarks" id="bookmarks" className="flex flex-wrap m-4 sm:m-8 sm:mt-4 items-start mb-2">
{bookmarkGroups.map((group) => (
<BookmarksGroup
key={group.name}
bookmarks={group}
layout={settings.layout?.[group.name]}
disableCollapse={settings.disableCollapse}
/>
))}
</div>}
return (
<>
{tabs.length > 0 && (
<div key="tabs" id="tabs" className="m-6 sm:m-9 sm:mt-4 sm:mb-0">
<ul
className={classNames(
"sm:flex rounded-md bg-theme-100/20 dark:bg-white/5",
settings.cardBlur !== undefined &&
`backdrop-blur${settings.cardBlur.length ? "-" : ""}${settings.cardBlur}`,
)}
id="myTab"
data-tabs-toggle="#myTabContent"
role="tablist"
>
{tabs.map((tab) => (
<Tab key={tab} tab={tab} />
))}
</ul>
</div>
)}
{layoutGroups.length > 0 && (
<div key="layoutGroups" id="layout-groups" className="flex flex-wrap m-4 sm:m-8 sm:mt-4 items-start mb-2">
{layoutGroups.map((group) =>
group.services ? (
<ServicesGroup
key={group.name}
group={group.name}
services={group}
layout={settings.layout?.[group.name]}
fiveColumns={settings.fiveColumns}
disableCollapse={settings.disableCollapse}
/>
) : (
<BookmarksGroup
key={group.name}
bookmarks={group}
layout={settings.layout?.[group.name]}
disableCollapse={settings.disableCollapse}
/>
),
)}
</div>
)}
{serviceGroups?.length > 0 && (
<div key="services" id="services" className="flex flex-wrap m-4 sm:m-8 sm:mt-4 items-start mb-2">
{serviceGroups.map((group) => (
<ServicesGroup
key={group.name}
group={group.name}
services={group}
layout={settings.layout?.[group.name]}
fiveColumns={settings.fiveColumns}
disableCollapse={settings.disableCollapse}
/>
))}
</div>
)}
{bookmarkGroups?.length > 0 && (
<div key="bookmarks" id="bookmarks" className="flex flex-wrap m-4 sm:m-8 sm:mt-4 items-start mb-2">
{bookmarkGroups.map((group) => (
<BookmarksGroup
key={group.name}
bookmarks={group}
layout={settings.layout?.[group.name]}
disableCollapse={settings.disableCollapse}
/>
))}
</div>
)}
</>
);
}, [
tabs,
activeTab,
@ -328,7 +353,7 @@ function Home({ initialSettings }) {
settings.fiveColumns,
settings.disableCollapse,
settings.cardBlur,
initialSettings.layout
initialSettings.layout,
]);
return (
@ -355,7 +380,8 @@ function Home({ initialSettings }) {
<link rel="preload" href="api/config/custom.css" as="fetch" crossOrigin="anonymous" />
<style data-name="custom.css">
<FileContent path="custom.css"
<FileContent
path="custom.css"
loadingValue="/* Loading custom CSS... */"
errorValue="/* Failed to load custom CSS... */"
emptyValue="/* No custom CSS */"
@ -378,31 +404,43 @@ function Home({ initialSettings }) {
className={classNames(
"flex flex-row flex-wrap justify-between",
headerStyles[headerStyle],
settings.cardBlur !== undefined && headerStyle === "boxed" && `backdrop-blur${settings.cardBlur.length ? '-' : ""}${settings.cardBlur}`
settings.cardBlur !== undefined &&
headerStyle === "boxed" &&
`backdrop-blur${settings.cardBlur.length ? "-" : ""}${settings.cardBlur}`,
)}
>
<div id="widgets-wrap"
style={{width: 'calc(100% + 1rem)'}}
className={classNames(
"flex flex-row w-full flex-wrap justify-between -ml-2 -mr-2"
)}
<div
id="widgets-wrap"
style={{ width: "calc(100% + 1rem)" }}
className={classNames("flex flex-row w-full flex-wrap justify-between -ml-2 -mr-2")}
>
{widgets && (
<>
{widgets
.filter((widget) => !rightAlignedWidgets.includes(widget.type))
.map((widget, i) => (
<Widget key={i} widget={widget} style={{ header: headerStyle, isRightAligned: false, cardBlur: settings.cardBlur }} />
<Widget
key={i}
widget={widget}
style={{ header: headerStyle, isRightAligned: false, cardBlur: settings.cardBlur }}
/>
))}
<div id="information-widgets-right" className={classNames(
"m-auto flex flex-wrap grow sm:basis-auto justify-between md:justify-end",
headerStyle === "boxedWidgets" ? "sm:ml-4" : "sm:ml-2"
)}>
<div
id="information-widgets-right"
className={classNames(
"m-auto flex flex-wrap grow sm:basis-auto justify-between md:justify-end",
headerStyle === "boxedWidgets" ? "sm:ml-4" : "sm:ml-2",
)}
>
{widgets
.filter((widget) => rightAlignedWidgets.includes(widget.type))
.map((widget, i) => (
<Widget key={i} widget={widget} style={{ header: headerStyle, isRightAligned: true, cardBlur: settings.cardBlur }} />
<Widget
key={i}
widget={widget}
style={{ header: headerStyle, isRightAligned: true, cardBlur: settings.cardBlur }}
/>
))}
</div>
</>
@ -436,7 +474,7 @@ export default function Wrapper({ initialSettings, fallback }) {
if (initialSettings && initialSettings.background) {
let opacity = initialSettings.backgroundOpacity ?? 1;
let backgroundImage = initialSettings.background;
if (typeof initialSettings.background === 'object') {
if (typeof initialSettings.background === "object") {
backgroundImage = initialSettings.background.image;
backgroundBlur = initialSettings.background.blur !== undefined;
backgroundSaturate = initialSettings.background.saturate !== undefined;
@ -460,7 +498,7 @@ export default function Wrapper({ initialSettings, fallback }) {
className={classNames(
"relative",
initialSettings.theme && initialSettings.theme,
initialSettings.color && `theme-${initialSettings.color}`
initialSettings.color && `theme-${initialSettings.color}`,
)}
>
<div
@ -469,14 +507,16 @@ export default function Wrapper({ initialSettings, fallback }) {
style={wrappedStyle}
>
<div
id="inner_wrapper"
tabIndex="-1"
className={classNames(
'fixed overflow-auto w-full h-full',
backgroundBlur && `backdrop-blur${initialSettings.background.blur.length ? '-' : ""}${initialSettings.background.blur}`,
backgroundSaturate && `backdrop-saturate-${initialSettings.background.saturate}`,
backgroundBrightness && `backdrop-brightness-${initialSettings.background.brightness}`,
)}>
id="inner_wrapper"
tabIndex="-1"
className={classNames(
"fixed overflow-auto w-full h-full",
backgroundBlur &&
`backdrop-blur${initialSettings.background.blur.length ? "-" : ""}${initialSettings.background.blur}`,
backgroundSaturate && `backdrop-saturate-${initialSettings.background.saturate}`,
backgroundBrightness && `backdrop-brightness-${initialSettings.background.brightness}`,
)}
>
<Index initialSettings={initialSettings} fallback={fallback} />
</div>
</div>