Compare commits

..
Author SHA1 Message Date
shamoon 075a3d4da6 Force host validation 2025-01-27 15:54:41 -08:00
shamoon ee7be33fa1 Security: support host header validation 2025-01-27 15:02:27 -08:00
shamoon e6a821ecc3 Bind all addreses in docker container 2025-01-27 10:50:44 -08:00
shamoon 49036a9738 Fix: fix semver detection bug 2025-01-27 00:23:20 -08:00
shamoon 0197b449ed [BREAKING] Chore: update to next v15 (#4661) 2025-01-26 00:20:41 -08:00
shamoon dc3382447c Fix: fix overlap with glances charts in multi-widgets (#4653) 2025-01-24 16:53:14 -08:00
shamoon 6beaf665fb Development: pnpm lint on ci (#4646) 2025-01-23 21:38:23 -08:00
shamoon b9622f75b0 Fix: generic proxy error data display 2025-01-21 07:13:12 -08:00
shamoon fe462a71d6 Fix: handle evcc breaking change (#4630) 2025-01-20 07:55:26 -08:00
Jesse Houston 1e556e105d Documentation: add note about clearing UniFi cache non-docker (#4585) 2025-01-08 15:42:38 -08:00
shamoon b5f4daa8ef Fix: fix bing search logo (#4571) 2025-01-06 07:25:14 -08:00
shamoon fd50241e2a Update errorboundry.jsx 2025-01-06 00:53:10 -08:00
Bjorn Lammers 6a0fbba407 Chore: migrate dashboard-icons to homarr-labs (#4564) 2025-01-05 13:45:15 -08:00
shamoon 2ee5fd123b code style 2025-01-05 07:58:28 -08:00
shamoon 86a13817df Merge branch 'main' into dev 2025-01-05 07:57:05 -08:00
Adi Vakninandshamoon 2f7d948a5c Fix: quick launch not showing nested services (#4561)
Co-authored-by: shamoon <4887959+shamoon@users.noreply.github.com>
2025-01-05 07:56:12 -08:00
shamoon 1b79e51194 Update source.md 2025-01-04 22:13:28 -08:00
Martin Bjeldbak Madsenandshamoon 2d98ac30f2 Documentation: update gluetun apikey config instructions (#4554)
Co-authored-by: shamoon <4887959+shamoon@users.noreply.github.com>
2025-01-02 21:07:55 -08:00
shamoon 1a85175b15 Fix: toggle light / dark makes background white (#4553) 2025-01-02 19:13:14 -08:00
Peder Toftegaard Olsenandshamoon 6f429a6a4b Enhancement: add duration format to customapi widget (#4549)
Co-authored-by: shamoon <4887959+shamoon@users.noreply.github.com>
2025-01-02 19:11:33 -08:00
shamoon 3020a2d1fd Fix: better fs height (#4552) 2025-01-02 18:56:48 -08:00
shamoon a6e85240c6 Update getting-started.md 2025-01-02 17:13:48 -08:00
shamoon 98e816204a Update getting-started.md 2025-01-02 17:13:36 -08:00
shamoon a0b5ac318a Update PULL_REQUEST_TEMPLATE.md 2025-01-02 16:34:24 -08:00
shamoon 101461b7b5 Update getting-started.md 2025-01-02 16:26:15 -08:00
shamoon ba6f50f21d Merge branch 'main' into dev 2025-01-02 16:26:01 -08:00
shamoon dd3a229559 Update getting-started.md 2025-01-02 16:25:32 -08:00
dependabot[bot] 630d5024ac Chore(deps): Bump systeminformation from 5.23.8 to 5.24.3 (#4546)
Signed-off-by: dependabot[bot] <support@github.com>
Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>
2025-01-01 08:35:15 -08:00
dependabot[bot] d239687e2e Chore(deps): Bump react-icons from 4.12.0 to 5.4.0 (#4536)
Signed-off-by: dependabot[bot] <support@github.com>
Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>
2025-01-01 08:34:05 -08:00
dependabot[bot] 81a3dfbfe4 Chore(deps-dev): Bump tailwindcss from 3.4.14 to 3.4.17 (#4537)
Signed-off-by: dependabot[bot] <support@github.com>
Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>
2025-01-01 08:33:37 -08:00
dependabot[bot] 034720f47a Chore(deps-dev): Bump eslint-plugin-react from 7.37.2 to 7.37.3 (#4534)
Signed-off-by: dependabot[bot] <support@github.com>
Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>
2025-01-01 08:33:23 -08:00
25 changed files with 3660 additions and 2486 deletions

No files matched your search

-3
View File
@@ -1,3 +0,0 @@
{
"presets": ["next/babel"]
}
+3 -5
View File
@@ -6,6 +6,7 @@
==================== STOP ====
⚠️ Before opening this pull request please review the guidelines in the checklist below.
If this PR does not meet those guidelines it will not be accepted, and everyone will be sad.
-->
@@ -14,9 +15,6 @@ If this PR does not meet those guidelines it will not be accepted, and everyone
<!--
Please include a summary of the change. Screenshots and/or videos can also be helpful if appropriate.
*** Please see the development guidelines for new widgets: https://gethomepage.dev/more/development/#service-widget-guidelines
*** If you do not follow these guidelines your PR will likely be closed without review.
New service widgets should include example(s) of relevant API output as well as updates to the docs for the new widget.
-->
@@ -30,13 +28,13 @@ What type of change does your PR introduce to Homepage?
- [ ] New service widget
- [ ] Bug fix (non-breaking change which fixes an issue)
- [ ] New feature (non-breaking change which adds functionality)
- [ ] New feature or enhancement (non-breaking change which adds functionality)
- [ ] Documentation only
- [ ] Other (please explain)
## Checklist:
- [ ] If applicable, I have added corresponding documentation changes.
- [ ] If applicable, I have reviewed the [feature](https://gethomepage.dev/more/development/#new-feature-guidelines) and / or [service widget guidelines](https://gethomepage.dev/more/development/#service-widget-guidelines).
- [ ] If applicable, I have reviewed the [feature / enhancement](https://gethomepage.dev/more/development/#new-feature-guidelines) and / or [service widget guidelines](https://gethomepage.dev/more/development/#service-widget-guidelines).
- [ ] I have checked that all code style checks pass using [pre-commit hooks](https://gethomepage.dev/more/development/#code-formatting-with-pre-commit-hooks) and [linting checks](https://gethomepage.dev/more/development/#code-linting).
- [ ] If applicable, I have tested my code for new features & regressions on both mobile & desktop devices, using the latest version of major browsers.
+19 -1
View File
@@ -46,6 +46,24 @@ jobs:
-
name: Check files
uses: pre-commit/action@v3.0.1
-
name: Install pnpm
uses: pnpm/action-setup@v4
with:
version: 10
run_install: false
-
name: Install Node.js
uses: actions/setup-node@v4
with:
node-version: 20
cache: 'pnpm'
-
name: Install dependencies
run: pnpm install
-
name: Lint frontend
run: pnpm run lint
build:
name: Docker Build & Push
@@ -127,7 +145,7 @@ jobs:
REVISION=${{ fromJSON(steps.meta.outputs.json).labels['org.opencontainers.image.revision'] }}
# https://github.com/docker/setup-qemu-action#about
# platforms: linux/amd64,linux/arm64,linux/riscv64,linux/ppc64le,linux/s390x,linux/386,linux/mips64le,linux/mips64,linux/arm/v7,linux/arm/v6
platforms: linux/amd64,linux/arm64,linux/arm/v7,linux/arm/v6
platforms: linux/amd64,linux/arm64
cache-from: type=local,src=/tmp/.buildx-cache
cache-to: type=local,dest=/tmp/.buildx-cache-new,mode=max
+6 -5
View File
@@ -1,7 +1,7 @@
# syntax = docker/dockerfile:latest
# Install dependencies only when needed
FROM docker.io/node:18-alpine AS deps
FROM docker.io/node:22-alpine AS deps
WORKDIR /app
@@ -17,7 +17,7 @@ RUN --mount=type=cache,id=pnpm-store,target=/root/.local/share/pnpm/store pnpm f
RUN --mount=type=cache,id=pnpm-store,target=/root/.local/share/pnpm/store pnpm install -r --offline
# Rebuild the source code only when needed
FROM docker.io/node:18-alpine AS builder
FROM docker.io/node:22-alpine AS builder
WORKDIR /app
ARG BUILDTIME
@@ -33,7 +33,7 @@ RUN npm run telemetry \
&& NEXT_PUBLIC_BUILDTIME=$BUILDTIME NEXT_PUBLIC_VERSION=$VERSION NEXT_PUBLIC_REVISION=$REVISION npm run build
# Production image, copy all the files and run next
FROM docker.io/node:18-alpine AS runner
FROM docker.io/node:22-alpine AS runner
LABEL org.opencontainers.image.title "Homepage"
LABEL org.opencontainers.image.description "A self-hosted services landing page, with docker and service integrations."
LABEL org.opencontainers.image.url="https://github.com/gethomepage/homepage"
@@ -41,7 +41,7 @@ LABEL org.opencontainers.image.documentation='https://github.com/gethomepage/hom
LABEL org.opencontainers.image.source='https://github.com/gethomepage/homepage'
LABEL org.opencontainers.image.licenses='Apache-2.0'
ENV NODE_ENV production
ENV NODE_ENV=production
WORKDIR /app
@@ -56,7 +56,8 @@ COPY --link --chmod=755 docker-entrypoint.sh /usr/local/bin/
RUN apk add --no-cache su-exec
ENV PORT 3000
ENV HOSTNAME=::
ENV PORT=3000
EXPOSE $PORT
HEALTHCHECK --interval=10s --timeout=3s --start-period=20s \
+1 -1
View File
@@ -38,7 +38,7 @@ With features like quick search, bookmarks, weather support, a wide range of int
- **Fast** - The site is statically generated at build time for instant load times.
- **Secure** - All API requests to backend services are proxied, keeping your API keys hidden. Constantly reviewed for security by the community.
- **For Everyone** - Images built for AMD64, ARM64, ARMv7, and ARMv6.
- **For Everyone** - Images built for AMD64, ARM64.
- **Full i18n** - Support for over 40 languages.
- **Service & Web Bookmarks** - Add custom links to the homepage.
- **Docker Integration** - Container status and stats. Automatic service discovery via labels.
+1 -1
View File
@@ -134,7 +134,7 @@ Services may have descriptions,
## Icons
Services may have an icon attached to them, you can use icons from [Dashboard Icons](https://github.com/walkxcode/dashboard-icons) automatically, by passing the name of the icon, with, or without `.png` or with `.svg` to use the svg version.
Services may have an icon attached to them, you can use icons from [Dashboard Icons](https://github.com/homarr-labs/dashboard-icons) automatically, by passing the name of the icon, with, or without `.png`, `.webp` or `.svg` to specify the desired version.
You can also specify prefixed icons from:
+2
View File
@@ -23,3 +23,5 @@ Finally, run the server:
```bash
pnpm start
```
When updating homepage versions you will need to re-build the static files i.e. repeat the process above.
+2 -2
View File
@@ -46,9 +46,9 @@ See the [pre-commit documentation](https://pre-commit.com/#install) to get start
In general, homepage is meant to be a dashboard for 'self-hosted' services and we believe it is a small way we can help showcase this kind of software. While exceptions are made, mostly when there is no viable
self-hosted / open-source alternative, we ask that any widgets, etc. are developed primarily for a self-hosted tool.
## New Feature Guidelines
## New Feature or Enhancement Guidelines {#new-feature-guidelines}
- New features should be linked to an existing feature request. The purpose of this requirement is to avoid the addition (and maintenance) of features that might only benefit a small number of users.
- New features or enhancements, **no matter how small**, must be linked to an existing feature request. The purpose of this requirement is to avoid the addition (and maintenance) of features that might only benefit a small number of users.
- If you have ideas for a larger feature you may want to open a discussion first.
## Service Widget Guidelines
+3 -1
View File
@@ -62,10 +62,12 @@ widget:
format: size
```
Supported formats for the values are `text`, `number`, `float`, `percent`, `bytes`, `bitrate`, `size`, `date` and `relativeDate`.
Supported formats for the values are `text`, `number`, `float`, `percent`, `duration`, `bytes`, `bitrate`, `size`, `date` and `relativeDate`.
The `dateStyle` and `timeStyle` options of the `date` format are passed directly to [Intl.DateTimeFormat](https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Intl/DateTimeFormat/DateTimeFormat) and the `style` and `numeric` options of `relativeDate` are passed to [Intl.RelativeTimeFormat](https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Intl/RelativeTimeFormat/RelativeTimeFormat).
The `duration` format expects the duration to be specified in seconds. The `scale` transformation tool can be used if a conversion is required.
The `size` format will return the length of the array or string, or the number of keys in an object. This is then formatted as `number`.
## Example
+1 -1
View File
@@ -11,7 +11,7 @@ Learn more about [Gluetun](https://github.com/qdm12/gluetun).
Allowed fields: `["public_ip", "region", "country"]`.
To setup authentication, follow [the official Gluetun documentation](https://github.com/qdm12/gluetun-wiki/blob/main/setup/advanced/control-server.md#authentication).
To setup authentication, follow [the official Gluetun documentation](https://github.com/qdm12/gluetun-wiki/blob/main/setup/advanced/control-server.md#authentication). Note that to use the api key method, you must add the route `GET /v1/publicip/ip` to the `routes` array in your Gluetun config.toml.
```yaml
widget:
+1 -1
View File
@@ -19,7 +19,7 @@ Allowed fields: `["uptime", "wan", "lan", "lan_users", "lan_devices", "wlan", "w
!!! hint
If you enter e.g. incorrect credentials and receive an "API Error", you may need to recreate the container to clear the cache.
If you enter e.g. incorrect credentials and receive an "API Error", you may need to recreate the container or restart the service to clear the cache.
```yaml
widget:
+1940 -1192
View File
File diff suppressed because it is too large. Load diff
+15 -15
View File
@@ -10,7 +10,7 @@
"telemetry": "next telemetry disable"
},
"dependencies": {
"@headlessui/react": "^1.7.18",
"@headlessui/react": "^1.7.19",
"@kubernetes/client-node": "^0.17.1",
"cal-parser": "^1.0.2",
"classnames": "^2.5.1",
@@ -24,22 +24,22 @@
"luxon": "^3.5.0",
"memory-cache": "^0.2.0",
"minecraft-ping-js": "^1.0.2",
"next": "^12.3.4",
"next": "^15.0.3",
"next-i18next": "^12.1.0",
"ping": "^0.4.4",
"pretty-bytes": "^6.1.1",
"raw-body": "^3.0.0",
"react": "^18.3.1",
"react-dom": "^18.2.0",
"react-dom": "^18.3.1",
"react-i18next": "^11.18.6",
"react-icons": "^4.12.0",
"recharts": "^2.12.6",
"react-icons": "^5.4.0",
"recharts": "^2.12.7",
"rrule": "^2.8.1",
"swr": "^1.3.0",
"systeminformation": "^5.23.8",
"tough-cookie": "^4.1.3",
"urbackup-server-api": "^0.52.1",
"winston": "^3.11.0",
"systeminformation": "^5.24.3",
"tough-cookie": "^4.1.4",
"urbackup-server-api": "^0.8.9",
"winston": "^3.13.0",
"xml-js": "^1.6.11"
},
"devDependencies": {
@@ -47,17 +47,17 @@
"autoprefixer": "^10.4.20",
"eslint": "^8.57.1",
"eslint-config-airbnb": "^19.0.4",
"eslint-config-next": "^14.2.3",
"eslint-config-next": "^14.2.4",
"eslint-config-prettier": "^9.1.0",
"eslint-plugin-import": "^2.31.0",
"eslint-plugin-jsx-a11y": "^6.8.0",
"eslint-plugin-jsx-a11y": "^6.9.0",
"eslint-plugin-prettier": "^5.2.1",
"eslint-plugin-react": "^7.37.2",
"eslint-plugin-react": "^7.37.3",
"eslint-plugin-react-hooks": "^4.6.2",
"postcss": "^8.4.47",
"prettier": "^3.2.5",
"postcss": "^8.4.48",
"prettier": "^3.3.2",
"tailwind-scrollbar": "^3.0.5",
"tailwindcss": "^3.4.14",
"tailwindcss": "^3.4.17",
"typescript": "^5.6.3"
},
"optionalDependencies": {
+1562 -1239
View File
File diff suppressed because it is too large. Load diff
+4 -2
View File
@@ -14,8 +14,10 @@ export default class ErrorBoundary extends React.Component {
});
// You can also log error messages to an error reporting service here
// eslint-disable-next-line no-console
console.error(error, errorInfo);
if (error || errorInfo) {
// eslint-disable-next-line no-console
console.error("component error: %s, info: %s", error, errorInfo);
}
}
render() {
+22 -3
View File
@@ -1,5 +1,5 @@
import { useContext } from "react";
import Image from "next/future/image";
import Image from "next/image";
import { SettingsContext } from "utils/contexts/settings";
import { ThemeContext } from "utils/contexts/theme";
@@ -101,7 +101,26 @@ export default function ResolvedIcon({ icon, width = 32, height = 32, alt = "log
const iconName = icon.replace(".svg", "");
return (
<Image
src={`https://cdn.jsdelivr.net/gh/walkxcode/dashboard-icons/svg/${iconName}.svg`}
src={`https://cdn.jsdelivr.net/gh/homarr-labs/dashboard-icons/svg/${iconName}.svg`}
width={width}
height={height}
style={{
width,
height,
objectFit: "contain",
maxHeight: "100%",
maxWidth: "100%",
}}
alt={alt}
/>
);
}
if (icon.endsWith(".webp")) {
const iconName = icon.replace(".webp", "");
return (
<Image
src={`https://cdn.jsdelivr.net/gh/homarr-labs/dashboard-icons/webp/${iconName}.webp`}
width={width}
height={height}
style={{
@@ -119,7 +138,7 @@ export default function ResolvedIcon({ icon, width = 32, height = 32, alt = "log
const iconName = icon.replace(".png", "");
return (
<Image
src={`https://cdn.jsdelivr.net/gh/walkxcode/dashboard-icons/png/${iconName}.png`}
src={`https://cdn.jsdelivr.net/gh/homarr-labs/dashboard-icons/png/${iconName}.png`}
width={width}
height={height}
style={{
+2 -2
View File
@@ -1,6 +1,6 @@
import { useTranslation } from "next-i18next";
import useSWR from "swr";
import { compareVersions } from "compare-versions";
import { compareVersions, validate } from "compare-versions";
import { MdNewReleases } from "react-icons/md";
export default function Version() {
@@ -44,7 +44,7 @@ export default function Version() {
</a>
)}
</span>
{version === "main" || version === "dev" || version === "nightly"
{!validate(version)
? null
: releaseData &&
latestRelease &&
+3 -2
View File
@@ -1,7 +1,8 @@
import { useState, useEffect, Fragment } from "react";
import { useTranslation } from "next-i18next";
import { FiSearch } from "react-icons/fi";
import { SiDuckduckgo, SiMicrosoftbing, SiGoogle, SiBaidu, SiBrave } from "react-icons/si";
import { SiDuckduckgo, SiGoogle, SiBaidu, SiBrave } from "react-icons/si";
import { BiLogoBing } from "react-icons/bi";
import { Listbox, Transition, Combobox } from "@headlessui/react";
import classNames from "classnames";
@@ -25,7 +26,7 @@ export const searchProviders = {
name: "Bing",
url: "https://www.bing.com/search?q=",
suggestionUrl: "https://api.bing.com/osjson.aspx?query=",
icon: SiMicrosoftbing,
icon: BiLogoBing,
},
baidu: {
name: "Baidu",
+22
View File
@@ -0,0 +1,22 @@
import { NextResponse } from "next/server";
export function middleware(req) {
// Check the Host header, if HOMEPAGE_ALLOWED_HOSTS is set
const host = req.headers.get("host");
let allowedHosts = ["localhost:3000"];
if (process.env.HOMEPAGE_ALLOWED_HOSTS) {
allowedHosts = allowedHosts.concat(process.env.HOMEPAGE_ALLOWED_HOSTS.split(","));
}
if (!host || !allowedHosts.includes(host)) {
// eslint-disable-next-line no-console
console.error(
`Host validation failed for: ${host}. Hint: Set HOMEPAGE_ALLOWED_HOSTS to allow requests from this host.`,
);
return NextResponse.json({ error: "Host validation failed. See logs for more details." }, { status: 400 });
}
return NextResponse.next();
}
export const config = {
matcher: "/api/:path*",
};
+37 -7
View File
@@ -86,6 +86,7 @@ function Index({ initialSettings, fallback }) {
const windowFocused = useWindowFocus();
const [stale, setStale] = useState(false);
const { data: errorsData } = useSWR("/api/validate");
const { error: validateError } = errorsData || {};
const { data: hashData, mutate: mutateHash } = useSWR("/api/hash");
useEffect(() => {
@@ -117,6 +118,24 @@ function Index({ initialSettings, fallback }) {
}
}, [hashData]);
if (validateError) {
return (
<div className="w-full h-screen container m-auto justify-center p-10 pointer-events-none">
<div className="flex flex-col">
<div className="basis-1/2 bg-theme-500 dark:bg-theme-600 text-theme-600 dark:text-theme-300 m-2 rounded-md font-mono shadow-md border-4 border-transparent">
<div className="bg-rose-200 text-rose-800 dark:text-rose-200 dark:bg-rose-800 p-2 rounded-md font-bold">
<BiError className="float-right w-6 h-6" />
Error
</div>
<div className="p-2 text-theme-100 dark:text-theme-200">
<pre className="opacity-50 font-bold pb-2">{validateError}</pre>
</div>
</div>
</div>
</div>
);
}
if (stale) {
return (
<div className="flex items-center justify-center h-screen">
@@ -166,6 +185,18 @@ const headerStyles = {
boxedWidgets: "m-5 mb-0 sm:m-9 sm:mb-0 sm:mt-1",
};
function getAllServices(services) {
function getServices(group) {
let nestedServices = [...group.services];
if (group.groups.length > 0) {
nestedServices = [...nestedServices, ...group.groups.map(getServices).flat()];
}
return nestedServices;
}
return [...services.map(getServices).flat()];
}
function Home({ initialSettings }) {
const { i18n } = useTranslation();
const { theme, setTheme } = useContext(ThemeContext);
@@ -182,10 +213,9 @@ 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 = [...bookmarks.map((bg) => bg.bookmarks).flat(), ...getAllServices(services)].filter(
(i) => i?.href,
);
useEffect(() => {
if (settings.language) {
@@ -459,7 +489,7 @@ function Home({ initialSettings }) {
}
export default function Wrapper({ initialSettings, fallback }) {
const { theme } = useContext(ThemeContext);
const { themeContext } = useContext(ThemeContext);
const wrappedStyle = {};
let backgroundBlur = false;
let backgroundSaturate = false;
@@ -490,9 +520,9 @@ export default function Wrapper({ initialSettings, fallback }) {
id="page_wrapper"
className={classNames(
"relative",
theme && theme,
initialSettings.theme && initialSettings.theme,
initialSettings.color && `theme-${initialSettings.color}`,
theme === "dark" ? "scheme-dark" : "scheme-light",
themeContext === "dark" ? "scheme-dark" : "scheme-light",
)}
>
<div
+5
View File
@@ -67,3 +67,8 @@ dialog ::-webkit-scrollbar {
.chart + .chart {
margin-top: 2em;
}
.service-container + .chart {
margin-top: 2.5rem;
margin-bottom: .5rem;
}
+1 -1
View File
@@ -79,7 +79,7 @@ export default async function genericProxyHandler(req, res, map) {
error: {
message: "HTTP Error",
url: sanitizeErrorURL(url),
resultData: Buffer.isBuffer(resultData) ? Buffer.from(resultData).toString() : resultData,
data: Buffer.isBuffer(resultData) ? Buffer.from(resultData).toString() : resultData,
},
});
}
+3
View File
@@ -78,6 +78,9 @@ function formatValue(t, mapping, rawValue) {
case "percent":
value = t("common.percent", { value });
break;
case "duration":
value = t("common.duration", { value });
break;
case "bytes":
value = t("common.bytes", { value });
break;
+4 -1
View File
@@ -29,10 +29,13 @@ export default function Component({ service }) {
);
}
// broken by evcc v0.133.0 https://github.com/evcc-io/evcc/commit/9dcb1fa0a7c08dd926b79309aa1f676a5fc6c8aa
const gridPower = stateData.result.gridPower ?? stateData.result.grid?.power ?? 0;
return (
<Container service={service}>
<Block label="evcc.pv_power" value={`${toKilowatts(t, stateData.result.pvPower)} ${t("evcc.kilowatt")}`} />
<Block label="evcc.grid_power" value={`${toKilowatts(t, stateData.result.gridPower)} ${t("evcc.kilowatt")}`} />
<Block label="evcc.grid_power" value={`${toKilowatts(t, gridPower)} ${t("evcc.kilowatt")}`} />
<Block label="evcc.home_power" value={`${toKilowatts(t, stateData.result.homePower)} ${t("evcc.kilowatt")}`} />
<Block
label="evcc.charge_power"
+1 -1
View File
@@ -46,7 +46,7 @@ export default function Component({ service }) {
<div className="absolute -top-2 -left-2 -right-2 -bottom-2">
<div
style={{
height: `${Math.max(20, fsData.size / fsData.free)}%`,
height: `${Math.max(20, (140 * (fsData.size - fsData.free)) / fsData.size)}px`,
}}
className="absolute bottom-0 border-t border-t-theme-500 bg-gradient-to-b from-theme-500/40 to-theme-500/10 w-full"
/>