From b39c79bea1dac2327d9580f81d1b81fda1962ac8 Mon Sep 17 00:00:00 2001 From: TheRolf Date: Sun, 10 Sep 2023 23:36:54 +0200 Subject: [PATCH] Custom JS and CSS (#1950) * First commit for custom styles and JS * Adjusted classes * Added ids and classes for services and bookmarks * Apply suggestions from code review * Remove mime dependency * Update settings.json * Detect custom css / js changes, no refresh * Added preload to custom scripts and styles so they can load earlier * Added data attribute name for bookmarks too * Update [path].js * code style, revert some pointer changes --------- Co-authored-by: shamoon <4887959+shamoon@users.noreply.github.com> --- src/components/bookmarks/group.jsx | 5 +-- src/components/bookmarks/item.jsx | 10 +++--- src/components/bookmarks/list.jsx | 2 +- src/components/filecontent.jsx | 10 ++++++ src/components/services/group.jsx | 5 +-- src/components/services/item.jsx | 36 ++++++++++--------- src/components/services/kubernetes-status.jsx | 8 ++--- src/components/services/list.jsx | 2 +- src/components/services/ping.jsx | 8 ++--- src/components/services/status.jsx | 12 +++---- src/components/services/widget.jsx | 2 +- src/components/services/widget/block.jsx | 3 +- src/components/services/widget/container.jsx | 2 +- src/components/toggles/color.jsx | 2 +- src/components/toggles/revalidate.jsx | 2 +- src/components/toggles/theme.jsx | 2 +- src/components/version.jsx | 2 +- src/components/widgets/datetime/datetime.jsx | 2 +- src/components/widgets/glances/glances.jsx | 7 ++-- src/components/widgets/greeting/greeting.jsx | 2 +- .../widgets/kubernetes/kubernetes.jsx | 4 +-- src/components/widgets/logo/logo.jsx | 6 ++-- src/components/widgets/longhorn/longhorn.jsx | 4 +-- src/components/widgets/longhorn/node.jsx | 1 + .../widgets/openmeteo/openmeteo.jsx | 6 ++-- .../widgets/openweathermap/weather.jsx | 4 +-- .../widgets/resources/usage-bar.jsx | 4 +-- src/components/widgets/search/search.jsx | 2 +- .../widgets/unifi_console/unifi_console.jsx | 4 +-- src/components/widgets/weather/weather.jsx | 4 +-- src/components/widgets/widget/container.jsx | 8 ++--- .../widgets/widget/container_button.jsx | 2 +- .../widgets/widget/container_form.jsx | 2 +- .../widgets/widget/container_link.jsx | 2 +- src/components/widgets/widget/error.jsx | 2 +- .../widgets/widget/primary_text.jsx | 2 +- src/components/widgets/widget/resource.jsx | 10 +++--- src/components/widgets/widget/resources.jsx | 7 ++-- .../widgets/widget/secondary_text.jsx | 2 +- src/components/widgets/widget/widget_icon.jsx | 2 +- .../widgets/widget/widget_label.jsx | 2 +- src/pages/api/config/[path].js | 35 ++++++++++++++++++ src/pages/api/hash.js | 2 +- src/pages/index.jsx | 34 ++++++++++++++---- src/skeleton/custom.css | 0 src/skeleton/custom.js | 0 46 files changed, 176 insertions(+), 99 deletions(-) create mode 100644 src/components/filecontent.jsx create mode 100644 src/pages/api/config/[path].js create mode 100644 src/skeleton/custom.css create mode 100644 src/skeleton/custom.js diff --git a/src/components/bookmarks/group.jsx b/src/components/bookmarks/group.jsx index 4cd36b7b8..2cfcad23a 100644 --- a/src/components/bookmarks/group.jsx +++ b/src/components/bookmarks/group.jsx @@ -13,6 +13,7 @@ export default function BookmarksGroup({ bookmarks, layout, disableCollapse }) {
{layout?.icon && ( -
+
)} -

{bookmarks.name}

+

{bookmarks.name}

+
  • -
    +
    {bookmark.icon &&
    @@ -28,9 +28,9 @@ export default function Item({ bookmark }) { } {!bookmark.icon && bookmark.abbr}
    -
    -
    {bookmark.name}
    -
    {hostname}
    +
    +
    {bookmark.name}
    +
    {hostname}
    diff --git a/src/components/bookmarks/list.jsx b/src/components/bookmarks/list.jsx index e0266d303..64b350768 100644 --- a/src/components/bookmarks/list.jsx +++ b/src/components/bookmarks/list.jsx @@ -9,7 +9,7 @@ export default function List({ bookmarks, layout }) {
      {bookmarks.map((bookmark) => ( diff --git a/src/components/filecontent.jsx b/src/components/filecontent.jsx new file mode 100644 index 000000000..e99cbb87b --- /dev/null +++ b/src/components/filecontent.jsx @@ -0,0 +1,10 @@ +import useSWR from "swr" + +export default function FileContent({ path, loadingValue, errorValue, emptyValue = '' }) { + const fetcher = (url) => fetch(url).then((res) => res.text()) + const { data, error, isLoading } = useSWR(`/api/config/${ path }`, fetcher) + + if (error) return (errorValue) + if (isLoading) return (loadingValue) + return (data || emptyValue) +} diff --git a/src/components/services/group.jsx b/src/components/services/group.jsx index bd2212189..7a7ae7058 100644 --- a/src/components/services/group.jsx +++ b/src/components/services/group.jsx @@ -14,6 +14,7 @@ export default function ServicesGroup({ group, services, layout, fiveColumns, di
      {layout?.icon && -
      +
      } -

      {services.name}

      +

      {services.name}

      +
    • -
      +
      {service.icon && (hasLink ? ( ) : ( -
      +
      ))} @@ -60,25 +62,25 @@ export default function Item({ service, group }) { href={service.href} target={service.target ?? settings.target ?? "_blank"} rel="noreferrer" - className="flex-1 flex items-center justify-between rounded-r-md " + className="flex-1 flex items-center justify-between rounded-r-md service-title-text" > -
      +
      {service.name} -

      {service.description}

      +

      {service.description}

      ) : ( -
      -
      +
      +
      {service.name} -

      {service.description}

      +

      {service.description}

      )} -
      +
      {service.ping && ( -
      +
      Ping status
      @@ -88,7 +90,7 @@ export default function Item({ service, group }) { diff --git a/src/components/widgets/widget/container_form.jsx b/src/components/widgets/widget/container_form.jsx index 7d28a1bb3..a0e4b7dd0 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 8ef0e80aa..6b373bb8c 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 a3dbab85e..04bcd2099 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 3418b92c7..3c21d273c 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 7ca50367c..908617b2f 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 394a3058a..ca375865a 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 363d1bd04..bae4023e8 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 557cba011..94a7e8ac6 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 5fc6ced09..8612733d1 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 000000000..165660e7d --- /dev/null +++ b/src/pages/api/config/[path].js @@ -0,0 +1,35 @@ +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'); + } +} diff --git a/src/pages/api/hash.js b/src/pages/api/hash.js index 0e8d0261f..b14a06276 100644 --- a/src/pages/api/hash.js +++ b/src/pages/api/hash.js @@ -4,7 +4,7 @@ import { readFileSync } from "fs"; import checkAndCopyConfig, { CONF_DIR } from "utils/config/config"; -const configs = ["docker.yaml", "settings.yaml", "services.yaml", "bookmarks.yaml", "widgets.yaml"]; +const configs = ["docker.yaml", "settings.yaml", "services.yaml", "bookmarks.yaml", "widgets.yaml", "custom.css", "custom.js"]; function hash(buffer) { const hashSum = createHash("sha256"); diff --git a/src/pages/index.jsx b/src/pages/index.jsx index e4cde99d2..65426862f 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"; @@ -239,7 +240,7 @@ function Home({ initialSettings }) { const bookmarkGroups = bookmarks.filter(group => settings.layout?.[group.name] === undefined); return <> - {layoutGroups.length > 0 &&
      + {layoutGroups.length > 0 &&
      {layoutGroups.map((group) => ( group.services ? (} - {serviceGroups?.length > 0 &&
      + {serviceGroups?.length > 0 &&
      {serviceGroups.map((group) => ( ))}
      } - {bookmarkGroups?.length > 0 &&
      + {bookmarkGroups?.length > 0 &&
      {bookmarkGroups.map((group) => ( + + + + + +
      ))} -
      @@ -351,14 +371,14 @@ function Home({ initialSettings }) { {servicesAndBookmarksGroups} -
      -
      +