First commit for custom styles and JS

This commit is contained in:
Yann Le Vagueres 2023-09-09 14:56:38 +02:00
parent 63f952509e
commit 213e7fdd28
No known key found for this signature in database
GPG Key ID: EF41B255CD47BC5E
36 changed files with 160 additions and 50 deletions

12
package-lock.json generated
View File

@ -19,6 +19,7 @@
"js-yaml": "^4.1.0", "js-yaml": "^4.1.0",
"json-rpc-2.0": "^1.4.1", "json-rpc-2.0": "^1.4.1",
"memory-cache": "^0.2.0", "memory-cache": "^0.2.0",
"mime": "^3.0.0",
"minecraft-ping-js": "^1.0.2", "minecraft-ping-js": "^1.0.2",
"next": "^12.3.1", "next": "^12.3.1",
"next-i18next": "^12.0.1", "next-i18next": "^12.0.1",
@ -4166,6 +4167,17 @@
"node": ">=8.6" "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": { "node_modules/mime-db": {
"version": "1.52.0", "version": "1.52.0",
"resolved": "https://registry.npmjs.org/mime-db/-/mime-db-1.52.0.tgz", "resolved": "https://registry.npmjs.org/mime-db/-/mime-db-1.52.0.tgz",

View File

@ -21,6 +21,7 @@
"js-yaml": "^4.1.0", "js-yaml": "^4.1.0",
"json-rpc-2.0": "^1.4.1", "json-rpc-2.0": "^1.4.1",
"memory-cache": "^0.2.0", "memory-cache": "^0.2.0",
"mime": "^3.0.0",
"minecraft-ping-js": "^1.0.2", "minecraft-ping-js": "^1.0.2",
"next": "^12.3.1", "next": "^12.3.1",
"next-i18next": "^12.0.1", "next-i18next": "^12.0.1",

View File

@ -38,6 +38,9 @@ dependencies:
memory-cache: memory-cache:
specifier: ^0.2.0 specifier: ^0.2.0
version: 0.2.0 version: 0.2.0
mime:
specifier: ^3.0.0
version: 3.0.0
minecraft-ping-js: minecraft-ping-js:
specifier: ^1.0.2 specifier: ^1.0.2
version: 1.0.2 version: 1.0.2
@ -2698,6 +2701,12 @@ packages:
mime-db: 1.52.0 mime-db: 1.52.0
dev: false 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: /mimic-fn@2.1.0:
resolution: {integrity: sha512-OqbOk5oEQeAZ8WXWydlu9HJjz9WVdEIvamMCcXmuqUYjTknH/sqsWvhQ3vgwKFRR1HpjvNBKQ37nbJgYzGqGcg==} resolution: {integrity: sha512-OqbOk5oEQeAZ8WXWydlu9HJjz9WVdEIvamMCcXmuqUYjTknH/sqsWvhQ3vgwKFRR1HpjvNBKQ37nbJgYzGqGcg==}
engines: {node: '>=6'} engines: {node: '>=6'}

View File

@ -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)
}

View File

@ -38,7 +38,7 @@ export default function ColorToggle() {
} }
return ( return (
<div className="w-full self-center"> <div id="color" className="w-full self-center">
<Popover className="relative flex items-center"> <Popover className="relative flex items-center">
<Popover.Button className="outline-none"> <Popover.Button className="outline-none">
<IoColorPalette <IoColorPalette

View File

@ -10,7 +10,7 @@ export default function Revalidate() {
}; };
return ( return (
<div className="rounded-full flex align-middle self-center mr-3"> <div id="revalidate" className="rounded-full flex align-middle self-center mr-3">
<MdRefresh onClick={() => revalidate()} className="text-theme-800 dark:text-theme-200 w-6 h-6 cursor-pointer" /> <MdRefresh onClick={() => revalidate()} className="text-theme-800 dark:text-theme-200 w-6 h-6 cursor-pointer" />
</div> </div>
); );

View File

@ -11,7 +11,7 @@ export default function ThemeToggle() {
} }
return ( return (
<div className="rounded-full flex self-end"> <div id="theme" className="rounded-full flex self-end">
<MdLightMode className="text-theme-800 dark:text-theme-200 w-5 h-5 m-1.5" /> <MdLightMode className="text-theme-800 dark:text-theme-200 w-5 h-5 m-1.5" />
{theme === "dark" ? ( {theme === "dark" ? (
<MdToggleOn <MdToggleOn

View File

@ -25,7 +25,7 @@ export default function Version() {
const latestRelease = releaseData?.[0]; const latestRelease = releaseData?.[0];
return ( return (
<div className="flex flex-row items-center"> <div id="version" className="flex flex-row items-center">
<span className="text-xs text-theme-500 dark:text-theme-400"> <span className="text-xs text-theme-500 dark:text-theme-400">
{version === "main" || version === "dev" || version === "nightly" ? ( {version === "main" || version === "dev" || version === "nightly" ? (
<> <>

View File

@ -30,7 +30,7 @@ export default function DateTime({ options }) {
}, [date, setDate, dateLocale, format]); }, [date, setDate, dateLocale, format]);
return ( return (
<Container options={options}> <Container options={options} additionalClassNames="information-widget-datetime">
<Raw> <Raw>
<div className="flex flex-row items-center grow justify-end"> <div className="flex flex-row items-center grow justify-end">
<span className={`text-theme-800 dark:text-theme-200 tabular-nums ${textSizes[textSize || "lg"]}`}> <span className={`text-theme-800 dark:text-theme-200 tabular-nums ${textSizes[textSize || "lg"]}`}>

View File

@ -3,6 +3,7 @@ import { useContext } from "react";
import { FaMemory, FaRegClock, FaThermometerHalf } from "react-icons/fa"; import { FaMemory, FaRegClock, FaThermometerHalf } from "react-icons/fa";
import { FiCpu, FiHardDrive } from "react-icons/fi"; import { FiCpu, FiHardDrive } from "react-icons/fi";
import { useTranslation } from "next-i18next"; import { useTranslation } from "next-i18next";
import classNames from "classnames";
import Error from "../widget/error"; import Error from "../widget/error";
import Resource from "../widget/resource"; import Resource from "../widget/resource";
@ -32,7 +33,7 @@ export default function Widget({ options }) {
} }
if (!data) { if (!data) {
return <Resources options={options}> return <Resources options={options} additionalClassNames="information-widget-glances">
{ options.cpu !== false && <Resource icon={FiCpu} label={t("glances.wait")} percentage="0" /> } { options.cpu !== false && <Resource icon={FiCpu} label={t("glances.wait")} percentage="0" /> }
{ options.mem !== false && <Resource icon={FaMemory} label={t("glances.wait")} percentage="0" /> } { options.mem !== false && <Resource icon={FaMemory} label={t("glances.wait")} percentage="0" /> }
{ options.cputemp && <Resource icon={FaThermometerHalf} label={t("glances.wait")} percentage="0" /> } { options.cputemp && <Resource icon={FaThermometerHalf} label={t("glances.wait")} percentage="0" /> }
@ -69,8 +70,10 @@ export default function Widget({ options }) {
: [data.fs.find((d) => d.mnt_point === options.disk)].filter((d) => d); : [data.fs.find((d) => d.mnt_point === options.disk)].filter((d) => d);
} }
const addedClasses = classNames('information-widget-glances', { 'information-widget-expanded': options.expanded })
return ( return (
<Resources options={options} target={settings.target ?? "_blank"}> <Resources options={options} target={settings.target ?? "_blank"} additionalClassNames={addedClasses}>
{options.cpu !== false && <Resource {options.cpu !== false && <Resource
icon={FiCpu} icon={FiCpu}
value={t("common.number", { value={t("common.number", {

View File

@ -14,7 +14,7 @@ const textSizes = {
export default function Greeting({ options }) { export default function Greeting({ options }) {
if (options.text) { if (options.text) {
return <Container options={options}> return <Container options={options} additionalClassNames="information-widget-greeting">
<Raw> <Raw>
<span className={`text-theme-800 dark:text-theme-200 mr-3 ${textSizes[options.text_size || "xl"]}`}> <span className={`text-theme-800 dark:text-theme-200 mr-3 ${textSizes[options.text_size || "xl"]}`}>
{options.text} {options.text}

View File

@ -36,7 +36,7 @@ export default function Widget({ options }) {
} }
if (!data) { if (!data) {
return <Container options={options}> return <Container options={options} additionalClassNames="information-widget-kubernetes">
<Raw> <Raw>
<div className="flex flex-row self-center flex-wrap justify-between"> <div className="flex flex-row self-center flex-wrap justify-between">
{cluster.show && {cluster.show &&
@ -50,7 +50,7 @@ export default function Widget({ options }) {
</Container>; </Container>;
} }
return <Container options={options}> return <Container options={options} additionalClassNames="information-widget-kubernetes">
<Raw> <Raw>
<div className="flex flex-row self-center flex-wrap justify-between"> <div className="flex flex-row self-center flex-wrap justify-between">
{cluster.show && {cluster.show &&

View File

@ -5,14 +5,14 @@ import ResolvedIcon from "components/resolvedicon"
export default function Logo({ options }) { export default function Logo({ options }) {
return ( return (
<Container options={options}> <Container options={options} additionalClassNames={`information-widget-logo ${ options.icon ? 'resolved' : 'fallback'}`}>
<Raw> <Raw>
{options.icon ? {options.icon ?
<div className="mr-3"> <div className="resolved mr-3">
<ResolvedIcon icon={options.icon} width={48} height={48} /> <ResolvedIcon icon={options.icon} width={48} height={48} />
</div> : </div> :
// fallback to homepage logo // fallback to homepage logo
<div className="w-12 h-12"> <div className="fallback w-12 h-12">
<svg <svg
xmlns="http://www.w3.org/2000/svg" xmlns="http://www.w3.org/2000/svg"
viewBox="0 0 1024 1024" viewBox="0 0 1024 1024"

View File

@ -17,14 +17,14 @@ export default function Longhorn({ options }) {
} }
if (!data) { if (!data) {
return <Container options={options}> return <Container options={options} additionalClassNames="infomation-widget-longhorn">
<Raw> <Raw>
<div className="flex flex-row self-center flex-wrap justify-between" /> <div className="flex flex-row self-center flex-wrap justify-between" />
</Raw> </Raw>
</Container>; </Container>;
} }
return <Container options={options}> return <Container options={options} additionalClassNames="infomation-widget-longhorn">
<Raw> <Raw>
<div className="flex flex-row self-center flex-wrap justify-between"> <div className="flex flex-row self-center flex-wrap justify-between">
{data.nodes {data.nodes

View File

@ -8,6 +8,7 @@ export default function Node({ data, expanded, labels }) {
const { t } = useTranslation(); const { t } = useTranslation();
return <Resource return <Resource
additionalClassNames="information-widget-node"
icon={FaThermometerHalf} icon={FaThermometerHalf}
value={t("common.bytes", { value: data.node.available })} value={t("common.bytes", { value: data.node.available })}
label={t("resources.free")} label={t("resources.free")}

View File

@ -24,7 +24,7 @@ function Widget({ options }) {
} }
if (!data) { if (!data) {
return <Container options={options}> return <Container options={options} additionalClassNames="information-widget-openmeteo">
<PrimaryText>{t("weather.updating")}</PrimaryText> <PrimaryText>{t("weather.updating")}</PrimaryText>
<SecondaryText>{t("weather.wait")}</SecondaryText> <SecondaryText>{t("weather.wait")}</SecondaryText>
<WidgetIcon icon={WiCloudDown} size="l" /> <WidgetIcon icon={WiCloudDown} size="l" />
@ -35,7 +35,7 @@ function Widget({ options }) {
const condition = data.current_weather.weathercode; 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"; const timeOfDay = data.current_weather.time > data.daily.sunrise[0] && data.current_weather.time < data.daily.sunset[0] ? "day" : "night";
return <Container options={options}> return <Container options={options} additionalClassNames="information-widget-openmeteo">
<PrimaryText> <PrimaryText>
{options.label && `${options.label}, `} {options.label && `${options.label}, `}
{t("common.number", { {t("common.number", {
@ -81,7 +81,7 @@ export default function OpenMeteo({ options }) {
// if (!requesting && !location) requestLocation(); // if (!requesting && !location) requestLocation();
if (!location) { if (!location) {
return <ContainerButton options={options} callback={requestLocation} > return <ContainerButton options={options} callback={requestLocation} additionalClassNames="information-widget-openmeteo-location-button">
<PrimaryText>{t("weather.current")}</PrimaryText> <PrimaryText>{t("weather.current")}</PrimaryText>
<SecondaryText>{t("weather.allow")}</SecondaryText> <SecondaryText>{t("weather.allow")}</SecondaryText>
<WidgetIcon icon={ requesting ? MdLocationSearching : MdLocationDisabled} size="m" pulse /> <WidgetIcon icon={ requesting ? MdLocationSearching : MdLocationDisabled} size="m" pulse />

View File

@ -24,7 +24,7 @@ function Widget({ options }) {
} }
if (!data) { if (!data) {
return <Container options={options}> return <Container options={options} additionalClassNames="information-widget-openweathermap">
<PrimaryText>{t("weather.updating")}</PrimaryText> <PrimaryText>{t("weather.updating")}</PrimaryText>
<SecondaryText>{t("weather.wait")}</SecondaryText> <SecondaryText>{t("weather.wait")}</SecondaryText>
<WidgetIcon icon={WiCloudDown} size="l" /> <WidgetIcon icon={WiCloudDown} size="l" />
@ -36,7 +36,7 @@ function Widget({ options }) {
const condition = data.weather[0].id; const condition = data.weather[0].id;
const timeOfDay = data.dt > data.sys.sunrise && data.dt < data.sys.sunset ? "day" : "night"; const timeOfDay = data.dt > data.sys.sunrise && data.dt < data.sys.sunset ? "day" : "night";
return <Container options={options}> return <Container options={options} additionalClassNames="information-widget-openweathermap">
<PrimaryText>{options.label && `${options.label}, ` }{t("common.number", { value: data.main.temp, style: "unit", unit })}</PrimaryText> <PrimaryText>{options.label && `${options.label}, ` }{t("common.number", { value: data.main.temp, style: "unit", unit })}</PrimaryText>
<SecondaryText>{data.weather[0].description}</SecondaryText> <SecondaryText>{data.weather[0].description}</SecondaryText>
<WidgetIcon icon={mapIcon(condition, timeOfDay)} size="xl" /> <WidgetIcon icon={mapIcon(condition, timeOfDay)} size="xl" />

View File

@ -1,6 +1,6 @@
export default function UsageBar({ percent }) { export default function UsageBar({ percent, additionalClassNames='' }) {
return ( return (
<div className="mt-0.5 w-full bg-theme-800/30 rounded-full h-1 dark:bg-theme-200/20"> <div className={`mt-0.5 w-full bg-theme-800/30 rounded-full h-1 dark:bg-theme-200/20 ${ additionalClassNames}`}>
<div <div
className="bg-theme-800/70 h-1 rounded-full dark:bg-theme-200/50 transition-all duration-1000" className="bg-theme-800/70 h-1 rounded-full dark:bg-theme-200/50 transition-all duration-1000"
style={{ style={{

View File

@ -103,7 +103,7 @@ export default function Search({ options }) {
localStorage.setItem(localStorageKey, provider.name); localStorage.setItem(localStorageKey, provider.name);
} }
return <ContainerForm options={options} callback={submitCallback} additionalClassNames="grow" > return <ContainerForm options={options} callback={submitCallback} additionalClassNames="grow information-widget-search" >
<Raw> <Raw>
<div className="flex-col relative h-8 my-4 min-w-fit"> <div className="flex-col relative h-8 my-4 min-w-fit">
<div className="flex absolute inset-y-0 left-0 items-center pl-3 pointer-events-none w-full text-theme-800 dark:text-white" /> <div className="flex absolute inset-y-0 left-0 items-center pl-3 pointer-events-none w-full text-theme-800 dark:text-white" />

View File

@ -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"); const defaultSite = options.site ? statsData?.data.find(s => s.desc === options.site) : statsData?.data?.find(s => s.name === "default");
if (!defaultSite) { if (!defaultSite) {
return <Container options={options}> return <Container options={options} additionalClassNames="information-widget-unify-console">
<PrimaryText>{t("unifi.wait")}</PrimaryText> <PrimaryText>{t("unifi.wait")}</PrimaryText>
<WidgetIcon icon={SiUbiquiti} /> <WidgetIcon icon={SiUbiquiti} />
</Container>; </Container>;
@ -43,7 +43,7 @@ export default function Widget({ options }) {
const dataEmpty = !(wan.show || lan.show || wlan.show || uptime); const dataEmpty = !(wan.show || lan.show || wlan.show || uptime);
return <Container options={options}> return <Container options={options} additionalClassNames="information-widget-unify-console">
<Raw> <Raw>
<div className="flex-none flex flex-row items-center mr-3 py-1.5"> <div className="flex-none flex flex-row items-center mr-3 py-1.5">
<div className="flex flex-col"> <div className="flex flex-col">

View File

@ -24,7 +24,7 @@ function Widget({ options }) {
} }
if (!data) { if (!data) {
return <Container options={options}> return <Container options={options} additionalClassNames="information-widget-weather">
<PrimaryText>{t("weather.updating")}</PrimaryText> <PrimaryText>{t("weather.updating")}</PrimaryText>
<SecondaryText>{t("weather.wait")}</SecondaryText> <SecondaryText>{t("weather.wait")}</SecondaryText>
<WidgetIcon icon={WiCloudDown} size="l" /> <WidgetIcon icon={WiCloudDown} size="l" />
@ -35,7 +35,7 @@ function Widget({ options }) {
const condition = data.current.condition.code; const condition = data.current.condition.code;
const timeOfDay = data.current.is_day ? "day" : "night"; const timeOfDay = data.current.is_day ? "day" : "night";
return <Container options={options}> return <Container options={options} additionalClassNames="information-widget-weather">
<PrimaryText> <PrimaryText>
{options.label && `${options.label}, `} {options.label && `${options.label}, `}
{t("common.number", { {t("common.number", {

View File

@ -35,9 +35,9 @@ export function getAllClasses(options, additionalClassNames = '') {
export function getInnerBlock(children) { export function getInnerBlock(children) {
// children won't be an array if it's Raw component // children won't be an array if it's Raw component
return Array.isArray(children) && <div className="flex flex-row items-center justify-end"> return Array.isArray(children) && <div className="flex flex-row items-center justify-end widget-inner">
<div className="flex flex-col items-center">{children.find(child => child.type === WidgetIcon)}</div> <div className="flex flex-col items-center widget-inner-icon">{children.find(child => child.type === WidgetIcon)}</div>
<div className="flex flex-col ml-3 text-left"> <div className="flex flex-col ml-3 text-left widget-inner-text">
{children.find(child => child.type === PrimaryText)} {children.find(child => child.type === PrimaryText)}
{children.find(child => child.type === SecondaryText)} {children.find(child => child.type === SecondaryText)}
</div> </div>
@ -54,7 +54,7 @@ export function getBottomBlock(children) {
export default function Container({ children = [], options, additionalClassNames = '' }) { export default function Container({ children = [], options, additionalClassNames = '' }) {
return ( return (
<div className={getAllClasses(options, additionalClassNames)}> <div className={getAllClasses(options, `${ additionalClassNames } widget-container`)}>
{getInnerBlock(children)} {getInnerBlock(children)}
{getBottomBlock(children)} {getBottomBlock(children)}
</div> </div>

View File

@ -2,7 +2,7 @@ import { getAllClasses, getInnerBlock, getBottomBlock } from "./container";
export default function ContainerButton ({ children = [], options, additionalClassNames = '', callback }) { export default function ContainerButton ({ children = [], options, additionalClassNames = '', callback }) {
return ( return (
<button type="button" onClick={callback} className={getAllClasses(options, additionalClassNames)}> <button type="button" onClick={callback} className={`${ getAllClasses(options, additionalClassNames) } information-widget-container-button`}>
{getInnerBlock(children)} {getInnerBlock(children)}
{getBottomBlock(children)} {getBottomBlock(children)}
</button> </button>

View File

@ -2,7 +2,7 @@ import { getAllClasses, getInnerBlock, getBottomBlock } from "./container";
export default function ContainerForm ({ children = [], options, additionalClassNames = '', callback }) { export default function ContainerForm ({ children = [], options, additionalClassNames = '', callback }) {
return ( return (
<form type="button" onSubmit={callback} className={getAllClasses(options, additionalClassNames)}> <form type="button" onSubmit={callback} className={`${ getAllClasses(options, additionalClassNames) } information-widget-form`}>
{getInnerBlock(children)} {getInnerBlock(children)}
{getBottomBlock(children)} {getBottomBlock(children)}
</form> </form>

View File

@ -2,7 +2,7 @@ import { getAllClasses, getInnerBlock, getBottomBlock } from "./container";
export default function ContainerLink ({ children = [], options, additionalClassNames = '', target }) { export default function ContainerLink ({ children = [], options, additionalClassNames = '', target }) {
return ( return (
<a href={options.url} target={target} className={getAllClasses(options, additionalClassNames)}> <a href={options.url} target={target} className={`${ getAllClasses(options, additionalClassNames) } information-widget-link`}>
{getInnerBlock(children)} {getInnerBlock(children)}
{getBottomBlock(children)} {getBottomBlock(children)}
</a> </a>

View File

@ -8,7 +8,7 @@ import WidgetIcon from "./widget_icon";
export default function Error({ options }) { export default function Error({ options }) {
const { t } = useTranslation(); const { t } = useTranslation();
return <Container options={options}> return <Container options={options} additionalClassNames="information-widget-error">
<PrimaryText>{t("widget.api_error")}</PrimaryText> <PrimaryText>{t("widget.api_error")}</PrimaryText>
<WidgetIcon icon={BiError} size="l" /> <WidgetIcon icon={BiError} size="l" />
</Container>; </Container>;

View File

@ -1,5 +1,5 @@
export default function PrimaryText({ children }) { export default function PrimaryText({ children }) {
return ( return (
<span className="text-theme-800 dark:text-theme-200 text-sm">{children}</span> <span className="primary-text text-theme-800 dark:text-theme-200 text-sm">{children}</span>
); );
} }

View File

@ -1,11 +1,11 @@
import UsageBar from "../resources/usage-bar"; 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; const Icon = icon;
return <div className="flex-none flex flex-row items-center mr-3 py-1.5"> return <div className={`flex-none flex flex-row items-center mr-3 py-1.5 information-widget-resource ${ additionalClassNames }`}>
<Icon className="text-theme-800 dark:text-theme-200 w-5 h-5"/> <Icon className="text-theme-800 dark:text-theme-200 w-5 h-5 resource-icon"/>
<div className="flex flex-col ml-3 text-left min-w-[85px]"> <div className={ `flex flex-col ml-3 text-left min-w-[85px] resource-label${ expanded ? ' expanded' : ''}`}>
<div className="text-theme-800 dark:text-theme-200 text-xs flex flex-row justify-between"> <div className="text-theme-800 dark:text-theme-200 text-xs flex flex-row justify-between">
<div className="pl-0.5">{value}</div> <div className="pl-0.5">{value}</div>
<div className="pr-1">{label}</div> <div className="pr-1">{label}</div>
@ -15,7 +15,7 @@ export default function Resource({ children, icon, value, label, expandedValue =
<div className="pr-1">{expandedLabel}</div> <div className="pr-1">{expandedLabel}</div>
</div> </div>
} }
{ percentage >= 0 && <UsageBar percent={percentage} /> } { percentage >= 0 && <UsageBar percent={percentage} additionalClassNames="resource-usage" /> }
{ children } { children }
</div> </div>
</div>; </div>;

View File

@ -1,12 +1,15 @@
import classNames from "classnames";
import ContainerLink from "./container_link"; import ContainerLink from "./container_link";
import Resource from "./resource"; import Resource from "./resource";
import Raw from "./raw"; import Raw from "./raw";
import WidgetLabel from "./widget_label"; 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 widgetParts = [].concat(...children);
const addedClassNames = classNames('information-widget-resources', additionalClassNames);
return <ContainerLink options={options} target={target}> return <ContainerLink options={options} target={target} additionalClassNames={ addedClassNames }>
<Raw> <Raw>
<div className="flex flex-row self-center flex-wrap justify-between"> <div className="flex flex-row self-center flex-wrap justify-between">
{ widgetParts.filter(child => child && child.type === Resource) } { widgetParts.filter(child => child && child.type === Resource) }

View File

@ -1,5 +1,5 @@
export default function SecondaryText({ children }) { export default function SecondaryText({ children }) {
return ( return (
<span className="text-theme-800 dark:text-theme-200 text-xs">{children}</span> <span className="secondary-text text-theme-800 dark:text-theme-200 text-xs">{children}</span>
); );
} }

View File

@ -1,6 +1,6 @@
export default function WidgetIcon({ icon, size = "s", pulse = false }) { export default function WidgetIcon({ icon, size = "s", pulse = false }) {
const Icon = icon; 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) { switch (size) {
case "m": additionalClasses += "w-6 h-6 "; break; case "m": additionalClasses += "w-6 h-6 "; break;

View File

@ -1,3 +1,3 @@
export default function WidgetLabel({ label = "" }) { export default function WidgetLabel({ label = "" }) {
return <div className="pt-1 text-center text-theme-800 dark:text-theme-200 text-xs">{label}</div> return <div className="information-widget-label pt-1 text-center text-theme-800 dark:text-theme-200 text-xs">{label}</div>
} }

View File

@ -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');
}
}

View File

@ -8,6 +8,7 @@ import { useEffect, useContext, useState, useMemo } from "react";
import { BiError } from "react-icons/bi"; import { BiError } from "react-icons/bi";
import { serverSideTranslations } from "next-i18next/serverSideTranslations"; import { serverSideTranslations } from "next-i18next/serverSideTranslations";
import FileContent from "components/filecontent";
import ServicesGroup from "components/services/group"; import ServicesGroup from "components/services/group";
import BookmarksGroup from "components/bookmarks/group"; import BookmarksGroup from "components/bookmarks/group";
import Widget from "components/widgets/widget"; import Widget from "components/widgets/widget";
@ -311,6 +312,22 @@ function Home({ initialSettings }) {
<meta name="msapplication-TileColor" content={themes[settings.color || "slate"][settings.theme || "dark"]} /> <meta name="msapplication-TileColor" content={themes[settings.color || "slate"][settings.theme || "dark"]} />
<meta name="theme-color" content={themes[settings.color || "slate"][settings.theme || "dark"]} /> <meta name="theme-color" content={themes[settings.color || "slate"][settings.theme || "dark"]} />
</Head> </Head>
<style alt="custom">
<FileContent path="custom.css"
loadingValue="/* Loading custom CSS... */"
errorValue="/* Failed to load custom CSS... */"
emptyValue="/* No custom CSS */"
/>
</style>
<script alt="custom">
<FileContent path="custom.js"
loadingValue="/* Loading custom JS... */"
errorValue="/* Failed to load custom JS... */"
emptyValue="/* No custom JS */"
/>
</script>
<div className="relative container m-auto flex flex-col justify-start z-10 h-full"> <div className="relative container m-auto flex flex-col justify-start z-10 h-full">
<QuickLaunch <QuickLaunch
servicesAndBookmarks={servicesAndBookmarks} servicesAndBookmarks={servicesAndBookmarks}
@ -321,6 +338,7 @@ function Home({ initialSettings }) {
searchProvider={settings.quicklaunch?.hideInternetSearch ? null : searchProvider} searchProvider={settings.quicklaunch?.hideInternetSearch ? null : searchProvider}
/> />
<div <div
id="information-widgets"
className={classNames( className={classNames(
"flex flex-row flex-wrap justify-between", "flex flex-row flex-wrap justify-between",
headerStyles[headerStyle], headerStyles[headerStyle],
@ -335,7 +353,7 @@ function Home({ initialSettings }) {
<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 className={classNames( <div id="information-widgets-right" className={classNames(
"m-auto flex flex-wrap grow sm:basis-auto justify-between md:justify-end", "m-auto flex flex-wrap grow sm:basis-auto justify-between md:justify-end",
headerStyle === "boxedWidgets" ? "sm:ml-4" : "sm:ml-2" headerStyle === "boxedWidgets" ? "sm:ml-4" : "sm:ml-2"
)}> )}>
@ -351,14 +369,14 @@ function Home({ initialSettings }) {
{servicesAndBookmarksGroups} {servicesAndBookmarksGroups}
<div className="flex flex-col mt-auto p-8 w-full"> <div id="footer" className="flex flex-col mt-auto p-8 w-full">
<div className="flex w-full justify-end"> <div id="style" className="flex w-full justify-end">
{!settings?.color && <ColorToggle />} {!settings?.color && <ColorToggle />}
<Revalidate /> <Revalidate />
{!settings.theme && <ThemeToggle />} {!settings.theme && <ThemeToggle />}
</div> </div>
<div className="flex mt-4 w-full justify-end"> <div id="version" className="flex mt-4 w-full justify-end">
{!settings.hideVersion && <Version />} {!settings.hideVersion && <Version />}
</div> </div>
</div> </div>

0
src/skeleton/custom.css Normal file
View File

0
src/skeleton/custom.js Normal file
View File