Compare commits

..
Author SHA1 Message Date
shamoon 8caed1e797 Fix units 2025-01-26 23:37:16 -08:00
shamoon 2fdd8df9f9 Correct units 2025-01-26 23:35:40 -08:00
shamoon d2cb64c7a3 Use top-level dataset info 2025-01-26 23:26:22 -08:00
shamoon b5eae499ec Update version.jsx 2025-01-26 16:34:23 -08:00
shamoon 546d699799 Feature: use datasets for truenas pools 2025-01-26 00:16:00 -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
24 changed files with 1390 additions and 571 deletions

No files matched your search

+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.
+18
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
+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:
+534 -426
View File
File diff suppressed because it is too large. Load diff
+4 -4
View File
@@ -32,11 +32,11 @@
"react": "^18.3.1",
"react-dom": "^18.2.0",
"react-i18next": "^11.18.6",
"react-icons": "^4.12.0",
"react-icons": "^5.4.0",
"recharts": "^2.12.6",
"rrule": "^2.8.1",
"swr": "^1.3.0",
"systeminformation": "^5.23.8",
"systeminformation": "^5.24.3",
"tough-cookie": "^4.1.3",
"urbackup-server-api": "^0.52.1",
"winston": "^3.11.0",
@@ -52,12 +52,12 @@
"eslint-plugin-import": "^2.31.0",
"eslint-plugin-jsx-a11y": "^6.8.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",
"tailwind-scrollbar": "^3.0.5",
"tailwindcss": "^3.4.14",
"tailwindcss": "^3.4.17",
"typescript": "^5.6.3"
},
"optionalDependencies": {
+720 -79
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() {
+21 -2
View File
@@ -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"
{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",
+18 -7
View File
@@ -166,6 +166,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 +194,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 +470,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 +501,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"
/>
+23 -15
View File
@@ -12,14 +12,15 @@ export default function Component({ service }) {
const { data: alertData, error: alertError } = useWidgetAPI(widget, "alerts");
const { data: statusData, error: statusError } = useWidgetAPI(widget, "status");
const { data: poolsData, error: poolsError } = useWidgetAPI(widget, "pools");
const { data: poolsData, error: poolsError } = useWidgetAPI(widget, widget?.enablePools ? "pools" : null);
const { data: datasetData, error: datasetError } = useWidgetAPI(widget, widget?.enablePools ? "dataset" : null);
if (alertError || statusError || poolsError) {
const finalError = alertError ?? statusError ?? poolsError;
const finalError = alertError ?? statusError ?? poolsError ?? datasetError;
return <Container service={service} error={finalError} />;
}
if (!alertData || !statusData) {
if (!alertData || !statusData || (widget?.enablePools && (!poolsData || !datasetData))) {
return (
<Container service={service}>
<Block label="truenas.load" />
@@ -29,7 +30,22 @@ export default function Component({ service }) {
);
}
const enablePools = widget?.enablePools && Array.isArray(poolsData) && poolsData.length > 0;
let pools = [];
const showPools =
Array.isArray(poolsData) && poolsData.length > 0 && Array.isArray(datasetData) && datasetData.length > 0;
if (showPools) {
pools = poolsData.map((pool) => {
const dataset = datasetData.find((d) => d.pool === pool.name && d.name === pool.name);
return {
id: pool.id,
name: pool.name,
healthy: pool.healthy,
allocated: dataset?.used.parsed ?? 0,
free: dataset?.available.parsed ?? 0,
};
});
}
return (
<>
@@ -38,19 +54,11 @@ export default function Component({ service }) {
<Block label="truenas.uptime" value={t("common.duration", { value: statusData.uptime_seconds })} />
<Block label="truenas.alerts" value={t("common.number", { value: alertData.pending })} />
</Container>
{enablePools &&
poolsData
{showPools &&
pools
.sort((a, b) => a.name.localeCompare(b.name))
.map((pool) => (
<Pool
key={pool.id}
name={pool.name}
healthy={pool.healthy}
allocated={pool.allocated}
free={pool.free}
data={pool.data}
nasType={widget?.nasType ?? "scale"}
/>
<Pool key={pool.id} name={pool.name} healthy={pool.healthy} allocated={pool.allocated} free={pool.free} />
))}
</>
);
+13 -15
View File
@@ -1,19 +1,9 @@
import classNames from "classnames";
import prettyBytes from "pretty-bytes";
import { useTranslation } from "next-i18next";
export default function Pool({ name, free, allocated, healthy, data, nasType }) {
let total = 0;
if (nasType === "scale") {
total = free + allocated;
} else {
allocated = 0; // eslint-disable-line no-param-reassign
for (let i = 0; i < data.length; i += 1) {
total += data[i].stats.size;
allocated += data[i].stats.allocated; // eslint-disable-line no-param-reassign
}
}
const usedPercent = Math.round((allocated / total) * 100);
export default function Pool({ name, free, allocated, healthy }) {
const { t } = useTranslation();
const usedPercent = Math.round((allocated / (free + allocated)) * 100);
const statusColor = healthy ? "bg-green-500" : "bg-yellow-500";
return (
@@ -32,7 +22,15 @@ export default function Pool({ name, free, allocated, healthy, data, nasType })
</div>
<div className="self-center text-xs flex justify-end mr-1.5 pl-1 z-10 text-ellipsis overflow-hidden whitespace-nowrap">
<span>
{prettyBytes(allocated)} / {prettyBytes(total)}
{`${t("common.bytes", {
value: allocated,
maximumFractionDigits: 1,
binary: true,
})} / ${t("common.bytes", {
value: free + allocated,
maximumFractionDigits: 1,
binary: true,
})}`}
</span>
<span className="pl-2">({usedPercent}%)</span>
</div>
+3 -3
View File
@@ -23,11 +23,11 @@ const widget = {
id: entry.name,
name: entry.name,
healthy: entry.healthy,
allocated: entry.allocated,
free: entry.free,
data: entry.topology?.data ?? [],
})),
},
dataset: {
endpoint: "pool/dataset",
},
},
};