diff --git a/package-lock.json b/package-lock.json index 6810ed19..2cb65cdc 100644 --- a/package-lock.json +++ b/package-lock.json @@ -19,6 +19,7 @@ "js-yaml": "^4.1.0", "json-rpc-2.0": "^1.4.1", "memory-cache": "^0.2.0", + "mime": "^3.0.0", "minecraft-ping-js": "^1.0.2", "next": "^12.3.1", "next-i18next": "^12.0.1", @@ -4166,6 +4167,17 @@ "node": ">=8.6" } }, + "node_modules/mime": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/mime/-/mime-3.0.0.tgz", + "integrity": "sha512-jSCU7/VB1loIWBZe14aEYHU/+1UMEHoaO7qxCOVJOw9GgH72VAWppxNcjU+x9a2k3GSIBXNKxXQFqRvvZ7vr3A==", + "bin": { + "mime": "cli.js" + }, + "engines": { + "node": ">=10.0.0" + } + }, "node_modules/mime-db": { "version": "1.52.0", "resolved": "https://registry.npmjs.org/mime-db/-/mime-db-1.52.0.tgz", diff --git a/package.json b/package.json index 34493efb..c982d1b4 100644 --- a/package.json +++ b/package.json @@ -21,6 +21,7 @@ "js-yaml": "^4.1.0", "json-rpc-2.0": "^1.4.1", "memory-cache": "^0.2.0", + "mime": "^3.0.0", "minecraft-ping-js": "^1.0.2", "next": "^12.3.1", "next-i18next": "^12.0.1", diff --git a/pnpm-lock.yaml b/pnpm-lock.yaml index b1854a23..b6c91a37 100644 --- a/pnpm-lock.yaml +++ b/pnpm-lock.yaml @@ -38,6 +38,9 @@ dependencies: memory-cache: specifier: ^0.2.0 version: 0.2.0 + mime: + specifier: ^3.0.0 + version: 3.0.0 minecraft-ping-js: specifier: ^1.0.2 version: 1.0.2 @@ -2698,6 +2701,12 @@ packages: mime-db: 1.52.0 dev: false + /mime@3.0.0: + resolution: {integrity: sha512-jSCU7/VB1loIWBZe14aEYHU/+1UMEHoaO7qxCOVJOw9GgH72VAWppxNcjU+x9a2k3GSIBXNKxXQFqRvvZ7vr3A==} + engines: {node: '>=10.0.0'} + hasBin: true + dev: false + /mimic-fn@2.1.0: resolution: {integrity: sha512-OqbOk5oEQeAZ8WXWydlu9HJjz9WVdEIvamMCcXmuqUYjTknH/sqsWvhQ3vgwKFRR1HpjvNBKQ37nbJgYzGqGcg==} engines: {node: '>=6'} diff --git a/src/components/filecontent.jsx b/src/components/filecontent.jsx new file mode 100644 index 00000000..6db30d18 --- /dev/null +++ b/src/components/filecontent.jsx @@ -0,0 +1,12 @@ +import useSWR from "swr" + +export default function FileContent({ path, loadingValue, errorValue, emptyValue = '', refresh = 1500 }) { + const fetcher = (url) => fetch(url).then((res) => res.text()) + const { data, error, isLoading } = useSWR(`/api/config/${ path }`, fetcher, { + refreshInterval: refresh, + }) + + if (error) return (errorValue) + if (isLoading) return (loadingValue) + return (data || emptyValue) +} diff --git a/src/components/toggles/color.jsx b/src/components/toggles/color.jsx index 29e81c0b..d65d5391 100644 --- a/src/components/toggles/color.jsx +++ b/src/components/toggles/color.jsx @@ -38,7 +38,7 @@ export default function ColorToggle() { } return ( -
+
+
revalidate()} className="text-theme-800 dark:text-theme-200 w-6 h-6 cursor-pointer" />
); diff --git a/src/components/toggles/theme.jsx b/src/components/toggles/theme.jsx index 3cc1fccf..d2e3e21b 100644 --- a/src/components/toggles/theme.jsx +++ b/src/components/toggles/theme.jsx @@ -11,7 +11,7 @@ export default function ThemeToggle() { } return ( -
+
{theme === "dark" ? ( +
{version === "main" || version === "dev" || version === "nightly" ? ( <> diff --git a/src/components/widgets/datetime/datetime.jsx b/src/components/widgets/datetime/datetime.jsx index 454d004d..4e1cd6f1 100644 --- a/src/components/widgets/datetime/datetime.jsx +++ b/src/components/widgets/datetime/datetime.jsx @@ -30,7 +30,7 @@ export default function DateTime({ options }) { }, [date, setDate, dateLocale, format]); return ( - +
diff --git a/src/components/widgets/glances/glances.jsx b/src/components/widgets/glances/glances.jsx index 092636aa..69224e60 100644 --- a/src/components/widgets/glances/glances.jsx +++ b/src/components/widgets/glances/glances.jsx @@ -3,6 +3,7 @@ import { useContext } from "react"; import { FaMemory, FaRegClock, FaThermometerHalf } from "react-icons/fa"; import { FiCpu, FiHardDrive } from "react-icons/fi"; import { useTranslation } from "next-i18next"; +import classNames from "classnames"; import Error from "../widget/error"; import Resource from "../widget/resource"; @@ -32,7 +33,7 @@ export default function Widget({ options }) { } if (!data) { - return + return { options.cpu !== false && } { options.mem !== false && } { options.cputemp && } @@ -69,8 +70,10 @@ export default function Widget({ options }) { : [data.fs.find((d) => d.mnt_point === options.disk)].filter((d) => d); } + const addedClasses = classNames('information-widget-glances', { 'information-widget-expanded': options.expanded }) + return ( - + {options.cpu !== false && + return {options.text} diff --git a/src/components/widgets/kubernetes/kubernetes.jsx b/src/components/widgets/kubernetes/kubernetes.jsx index 2d1f55e4..2a9da46a 100644 --- a/src/components/widgets/kubernetes/kubernetes.jsx +++ b/src/components/widgets/kubernetes/kubernetes.jsx @@ -36,7 +36,7 @@ export default function Widget({ options }) { } if (!data) { - return + return
{cluster.show && @@ -50,7 +50,7 @@ export default function Widget({ options }) { ; } - return + return
{cluster.show && diff --git a/src/components/widgets/logo/logo.jsx b/src/components/widgets/logo/logo.jsx index 83432561..70b33ec0 100644 --- a/src/components/widgets/logo/logo.jsx +++ b/src/components/widgets/logo/logo.jsx @@ -5,14 +5,14 @@ import ResolvedIcon from "components/resolvedicon" export default function Logo({ options }) { return ( - + {options.icon ? -
+
: // fallback to homepage logo -
+
+ return
; } - return + return
{data.nodes diff --git a/src/components/widgets/longhorn/node.jsx b/src/components/widgets/longhorn/node.jsx index 5235698a..64a7f6c4 100644 --- a/src/components/widgets/longhorn/node.jsx +++ b/src/components/widgets/longhorn/node.jsx @@ -8,6 +8,7 @@ export default function Node({ data, expanded, labels }) { const { t } = useTranslation(); return + return {t("weather.updating")} {t("weather.wait")} @@ -35,7 +35,7 @@ function Widget({ options }) { const condition = data.current_weather.weathercode; const timeOfDay = data.current_weather.time > data.daily.sunrise[0] && data.current_weather.time < data.daily.sunset[0] ? "day" : "night"; - return + return {options.label && `${options.label}, `} {t("common.number", { @@ -81,7 +81,7 @@ export default function OpenMeteo({ options }) { // if (!requesting && !location) requestLocation(); if (!location) { - return + return {t("weather.current")} {t("weather.allow")} diff --git a/src/components/widgets/openweathermap/weather.jsx b/src/components/widgets/openweathermap/weather.jsx index 32c81f06..2458e3c6 100644 --- a/src/components/widgets/openweathermap/weather.jsx +++ b/src/components/widgets/openweathermap/weather.jsx @@ -24,7 +24,7 @@ function Widget({ options }) { } if (!data) { - return + return {t("weather.updating")} {t("weather.wait")} @@ -36,7 +36,7 @@ function Widget({ options }) { const condition = data.weather[0].id; const timeOfDay = data.dt > data.sys.sunrise && data.dt < data.sys.sunset ? "day" : "night"; - return + return {options.label && `${options.label}, ` }{t("common.number", { value: data.main.temp, style: "unit", unit })} {data.weather[0].description} diff --git a/src/components/widgets/resources/usage-bar.jsx b/src/components/widgets/resources/usage-bar.jsx index c817db4c..2e18c40d 100644 --- a/src/components/widgets/resources/usage-bar.jsx +++ b/src/components/widgets/resources/usage-bar.jsx @@ -1,6 +1,6 @@ -export default function UsageBar({ percent }) { +export default function UsageBar({ percent, additionalClassNames='' }) { return ( -
+
+ return
diff --git a/src/components/widgets/unifi_console/unifi_console.jsx b/src/components/widgets/unifi_console/unifi_console.jsx index dad92cc7..a7f2f1c7 100644 --- a/src/components/widgets/unifi_console/unifi_console.jsx +++ b/src/components/widgets/unifi_console/unifi_console.jsx @@ -25,7 +25,7 @@ export default function Widget({ options }) { const defaultSite = options.site ? statsData?.data.find(s => s.desc === options.site) : statsData?.data?.find(s => s.name === "default"); if (!defaultSite) { - return + return {t("unifi.wait")} ; @@ -43,7 +43,7 @@ export default function Widget({ options }) { const dataEmpty = !(wan.show || lan.show || wlan.show || uptime); - return + return
diff --git a/src/components/widgets/weather/weather.jsx b/src/components/widgets/weather/weather.jsx index 20d4eeee..08ebf774 100644 --- a/src/components/widgets/weather/weather.jsx +++ b/src/components/widgets/weather/weather.jsx @@ -24,7 +24,7 @@ function Widget({ options }) { } if (!data) { - return + return {t("weather.updating")} {t("weather.wait")} @@ -35,7 +35,7 @@ function Widget({ options }) { const condition = data.current.condition.code; const timeOfDay = data.current.is_day ? "day" : "night"; - return + return {options.label && `${options.label}, `} {t("common.number", { diff --git a/src/components/widgets/widget/container.jsx b/src/components/widgets/widget/container.jsx index f06ce6cf..965a8aa6 100644 --- a/src/components/widgets/widget/container.jsx +++ b/src/components/widgets/widget/container.jsx @@ -35,9 +35,9 @@ export function getAllClasses(options, additionalClassNames = '') { export function getInnerBlock(children) { // children won't be an array if it's Raw component - return Array.isArray(children) &&
-
{children.find(child => child.type === WidgetIcon)}
-
+ return Array.isArray(children) &&
+
{children.find(child => child.type === WidgetIcon)}
+
{children.find(child => child.type === PrimaryText)} {children.find(child => child.type === SecondaryText)}
@@ -54,7 +54,7 @@ export function getBottomBlock(children) { export default function Container({ children = [], options, additionalClassNames = '' }) { return ( -
+
{getInnerBlock(children)} {getBottomBlock(children)}
diff --git a/src/components/widgets/widget/container_button.jsx b/src/components/widgets/widget/container_button.jsx index 92d8a416..36c2f67a 100644 --- a/src/components/widgets/widget/container_button.jsx +++ b/src/components/widgets/widget/container_button.jsx @@ -2,7 +2,7 @@ import { getAllClasses, getInnerBlock, getBottomBlock } from "./container"; export default function ContainerButton ({ children = [], options, additionalClassNames = '', callback }) { return ( - diff --git a/src/components/widgets/widget/container_form.jsx b/src/components/widgets/widget/container_form.jsx index 7d28a1bb..a0e4b7dd 100644 --- a/src/components/widgets/widget/container_form.jsx +++ b/src/components/widgets/widget/container_form.jsx @@ -2,7 +2,7 @@ import { getAllClasses, getInnerBlock, getBottomBlock } from "./container"; export default function ContainerForm ({ children = [], options, additionalClassNames = '', callback }) { return ( -
+ {getInnerBlock(children)} {getBottomBlock(children)}
diff --git a/src/components/widgets/widget/container_link.jsx b/src/components/widgets/widget/container_link.jsx index 8ef0e80a..6b373bb8 100644 --- a/src/components/widgets/widget/container_link.jsx +++ b/src/components/widgets/widget/container_link.jsx @@ -2,7 +2,7 @@ import { getAllClasses, getInnerBlock, getBottomBlock } from "./container"; export default function ContainerLink ({ children = [], options, additionalClassNames = '', target }) { return ( - + {getInnerBlock(children)} {getBottomBlock(children)} diff --git a/src/components/widgets/widget/error.jsx b/src/components/widgets/widget/error.jsx index a3dbab85..04bcd209 100644 --- a/src/components/widgets/widget/error.jsx +++ b/src/components/widgets/widget/error.jsx @@ -8,7 +8,7 @@ import WidgetIcon from "./widget_icon"; export default function Error({ options }) { const { t } = useTranslation(); - return + return {t("widget.api_error")} ; diff --git a/src/components/widgets/widget/primary_text.jsx b/src/components/widgets/widget/primary_text.jsx index 3418b92c..3c21d273 100644 --- a/src/components/widgets/widget/primary_text.jsx +++ b/src/components/widgets/widget/primary_text.jsx @@ -1,5 +1,5 @@ export default function PrimaryText({ children }) { return ( - {children} + {children} ); } diff --git a/src/components/widgets/widget/resource.jsx b/src/components/widgets/widget/resource.jsx index 7ca50367..8d7f6e72 100644 --- a/src/components/widgets/widget/resource.jsx +++ b/src/components/widgets/widget/resource.jsx @@ -1,11 +1,11 @@ import UsageBar from "../resources/usage-bar"; -export default function Resource({ children, icon, value, label, expandedValue = "", expandedLabel = "", percentage, expanded = false }) { +export default function Resource({ children, icon, value, label, expandedValue = "", expandedLabel = "", percentage, expanded = false, additionalClassNames='' }) { const Icon = icon; - return
- -
+ return
+ +
{value}
{label}
@@ -15,7 +15,7 @@ export default function Resource({ children, icon, value, label, expandedValue =
{expandedLabel}
} - { percentage >= 0 && } + { percentage >= 0 && } { children }
; diff --git a/src/components/widgets/widget/resources.jsx b/src/components/widgets/widget/resources.jsx index 394a3058..ca375865 100644 --- a/src/components/widgets/widget/resources.jsx +++ b/src/components/widgets/widget/resources.jsx @@ -1,12 +1,15 @@ +import classNames from "classnames"; + import ContainerLink from "./container_link"; import Resource from "./resource"; import Raw from "./raw"; import WidgetLabel from "./widget_label"; -export default function Resources({ options, children, target }) { +export default function Resources({ options, children, target, additionalClassNames }) { const widgetParts = [].concat(...children); + const addedClassNames = classNames('information-widget-resources', additionalClassNames); - return + return
{ widgetParts.filter(child => child && child.type === Resource) } diff --git a/src/components/widgets/widget/secondary_text.jsx b/src/components/widgets/widget/secondary_text.jsx index 363d1bd0..bae4023e 100644 --- a/src/components/widgets/widget/secondary_text.jsx +++ b/src/components/widgets/widget/secondary_text.jsx @@ -1,5 +1,5 @@ export default function SecondaryText({ children }) { return ( - {children} + {children} ); } diff --git a/src/components/widgets/widget/widget_icon.jsx b/src/components/widgets/widget/widget_icon.jsx index 557cba01..94a7e8ac 100644 --- a/src/components/widgets/widget/widget_icon.jsx +++ b/src/components/widgets/widget/widget_icon.jsx @@ -1,6 +1,6 @@ export default function WidgetIcon({ icon, size = "s", pulse = false }) { const Icon = icon; - let additionalClasses = "text-theme-800 dark:text-theme-200 "; + let additionalClasses = "information-widget-icon text-theme-800 dark:text-theme-200 "; switch (size) { case "m": additionalClasses += "w-6 h-6 "; break; diff --git a/src/components/widgets/widget/widget_label.jsx b/src/components/widgets/widget/widget_label.jsx index 5fc6ced0..8612733d 100644 --- a/src/components/widgets/widget/widget_label.jsx +++ b/src/components/widgets/widget/widget_label.jsx @@ -1,3 +1,3 @@ export default function WidgetLabel({ label = "" }) { - return
{label}
+ return
{label}
} diff --git a/src/pages/api/config/[path].js b/src/pages/api/config/[path].js new file mode 100644 index 00000000..e9988b82 --- /dev/null +++ b/src/pages/api/config/[path].js @@ -0,0 +1,51 @@ +import path from "path"; +import fs from "fs"; + +import mime from "mime"; + +import { CONF_DIR } from "utils/config/config"; +import createLogger from "utils/logger"; + +const logger = createLogger("configFileService"); + +/** + * Verifies that the config file paths are in subdirectory + * @param {string} parent Parent initial folder + * @param {string} child Supposed child path + * @returns {boolean} true if in a subdirectory + */ +function isSubDirectory(parent, child) { + return path.relative(child, parent).startsWith('..'); +} + +/** + * @param {import("next").NextApiRequest} req + * @param {import("next").NextApiResponse} res + */ +export default async function handler(req, res) { + const { path: relativePath } = req.query; + + const filePath = path.join(CONF_DIR, relativePath); + + if(!isSubDirectory(CONF_DIR, filePath)) + { + logger.error(`Forbidden access to parent file: ${ filePath }`); + res.status(403).end('Forbidden access to parent file'); + } + + const mimeType = mime.getType(relativePath); + + try { + // Read the content of the CSS file + const fileContent = fs.readFileSync(filePath, 'utf-8'); + + // Set the response header to indicate that this is a CSS file + res.setHeader('Content-Type', mimeType); + + // Send the CSS content as the API response + res.status(200).send(fileContent); + } catch (error) { + logger.error(error); + res.status(500).end('Internal Server Error'); + } +} diff --git a/src/pages/index.jsx b/src/pages/index.jsx index e4cde99d..588e3d1d 100644 --- a/src/pages/index.jsx +++ b/src/pages/index.jsx @@ -8,6 +8,7 @@ import { useEffect, useContext, useState, useMemo } from "react"; import { BiError } from "react-icons/bi"; import { serverSideTranslations } from "next-i18next/serverSideTranslations"; +import FileContent from "components/filecontent"; import ServicesGroup from "components/services/group"; import BookmarksGroup from "components/bookmarks/group"; import Widget from "components/widgets/widget"; @@ -311,6 +312,22 @@ function Home({ initialSettings }) { + + + +
))} -
@@ -351,14 +369,14 @@ function Home({ initialSettings }) { {servicesAndBookmarksGroups} -
-
+