Compare commits
2
Commits
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
cd8c7d9016 | ||
|
|
9021182c2c |
No files matched your search
@@ -46,24 +46,6 @@ jobs:
|
|||||||
-
|
-
|
||||||
name: Check files
|
name: Check files
|
||||||
uses: pre-commit/action@v3.0.1
|
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:
|
build:
|
||||||
name: Docker Build & Push
|
name: Docker Build & Push
|
||||||
@@ -145,7 +127,7 @@ jobs:
|
|||||||
REVISION=${{ fromJSON(steps.meta.outputs.json).labels['org.opencontainers.image.revision'] }}
|
REVISION=${{ fromJSON(steps.meta.outputs.json).labels['org.opencontainers.image.revision'] }}
|
||||||
# https://github.com/docker/setup-qemu-action#about
|
# 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/riscv64,linux/ppc64le,linux/s390x,linux/386,linux/mips64le,linux/mips64,linux/arm/v7,linux/arm/v6
|
||||||
platforms: linux/amd64,linux/arm64
|
platforms: linux/amd64,linux/arm64,linux/arm/v7,linux/arm/v6
|
||||||
cache-from: type=local,src=/tmp/.buildx-cache
|
cache-from: type=local,src=/tmp/.buildx-cache
|
||||||
cache-to: type=local,dest=/tmp/.buildx-cache-new,mode=max
|
cache-to: type=local,dest=/tmp/.buildx-cache-new,mode=max
|
||||||
|
|
||||||
|
|||||||
+5
-6
@@ -1,7 +1,7 @@
|
|||||||
# syntax = docker/dockerfile:latest
|
# syntax = docker/dockerfile:latest
|
||||||
|
|
||||||
# Install dependencies only when needed
|
# Install dependencies only when needed
|
||||||
FROM docker.io/node:22-alpine AS deps
|
FROM docker.io/node:18-alpine AS deps
|
||||||
|
|
||||||
WORKDIR /app
|
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
|
RUN --mount=type=cache,id=pnpm-store,target=/root/.local/share/pnpm/store pnpm install -r --offline
|
||||||
|
|
||||||
# Rebuild the source code only when needed
|
# Rebuild the source code only when needed
|
||||||
FROM docker.io/node:22-alpine AS builder
|
FROM docker.io/node:18-alpine AS builder
|
||||||
WORKDIR /app
|
WORKDIR /app
|
||||||
|
|
||||||
ARG BUILDTIME
|
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
|
&& NEXT_PUBLIC_BUILDTIME=$BUILDTIME NEXT_PUBLIC_VERSION=$VERSION NEXT_PUBLIC_REVISION=$REVISION npm run build
|
||||||
|
|
||||||
# Production image, copy all the files and run next
|
# Production image, copy all the files and run next
|
||||||
FROM docker.io/node:22-alpine AS runner
|
FROM docker.io/node:18-alpine AS runner
|
||||||
LABEL org.opencontainers.image.title "Homepage"
|
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.description "A self-hosted services landing page, with docker and service integrations."
|
||||||
LABEL org.opencontainers.image.url="https://github.com/gethomepage/homepage"
|
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.source='https://github.com/gethomepage/homepage'
|
||||||
LABEL org.opencontainers.image.licenses='Apache-2.0'
|
LABEL org.opencontainers.image.licenses='Apache-2.0'
|
||||||
|
|
||||||
ENV NODE_ENV=production
|
ENV NODE_ENV production
|
||||||
|
|
||||||
WORKDIR /app
|
WORKDIR /app
|
||||||
|
|
||||||
@@ -56,8 +56,7 @@ COPY --link --chmod=755 docker-entrypoint.sh /usr/local/bin/
|
|||||||
|
|
||||||
RUN apk add --no-cache su-exec
|
RUN apk add --no-cache su-exec
|
||||||
|
|
||||||
ENV HOSTNAME=::
|
ENV PORT 3000
|
||||||
ENV PORT=3000
|
|
||||||
EXPOSE $PORT
|
EXPOSE $PORT
|
||||||
|
|
||||||
HEALTHCHECK --interval=10s --timeout=3s --start-period=20s \
|
HEALTHCHECK --interval=10s --timeout=3s --start-period=20s \
|
||||||
|
|||||||
@@ -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.
|
- **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.
|
- **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.
|
- **For Everyone** - Images built for AMD64, ARM64, ARMv7, and ARMv6.
|
||||||
- **Full i18n** - Support for over 40 languages.
|
- **Full i18n** - Support for over 40 languages.
|
||||||
- **Service & Web Bookmarks** - Add custom links to the homepage.
|
- **Service & Web Bookmarks** - Add custom links to the homepage.
|
||||||
- **Docker Integration** - Container status and stats. Automatic service discovery via labels.
|
- **Docker Integration** - Container status and stats. Automatic service discovery via labels.
|
||||||
|
|||||||
@@ -97,6 +97,10 @@ Each service can have multiple widgets attached to it, for example:
|
|||||||
slug: statuspageslug
|
slug: statuspageslug
|
||||||
```
|
```
|
||||||
|
|
||||||
|
!!! note
|
||||||
|
|
||||||
|
Multiple widgets per service are not yet supported with Kubernetes ingress annotations.
|
||||||
|
|
||||||
#### Field Visibility
|
#### Field Visibility
|
||||||
|
|
||||||
Each widget can optionally provide a list of which fields should be visible via the `fields` widget property. If no fields are specified, then all fields will be displayed. The `fields` property must be a valid YAML array of strings. As an example, here is the entry for Sonarr showing only a couple of fields.
|
Each widget can optionally provide a list of which fields should be visible via the `fields` widget property. If no fields are specified, then all fields will be displayed. The `fields` property must be a valid YAML array of strings. As an example, here is the entry for Sonarr showing only a couple of fields.
|
||||||
@@ -178,6 +182,10 @@ To use a local icon, first create a Docker mount to `/app/public/icons` and then
|
|||||||
|
|
||||||
Services may have an optional `ping` property that allows you to monitor the availability of an external host. As of v0.8.0, the ping feature attempts to use a true (ICMP) ping command on the underlying host. Currently, only IPv4 is supported.
|
Services may have an optional `ping` property that allows you to monitor the availability of an external host. As of v0.8.0, the ping feature attempts to use a true (ICMP) ping command on the underlying host. Currently, only IPv4 is supported.
|
||||||
|
|
||||||
|
!!! note
|
||||||
|
|
||||||
|
Because ping uses the ping command on the underlying host, in some cases you may need to install e.g. the `iputils-ping` package on the host system.
|
||||||
|
|
||||||
```yaml
|
```yaml
|
||||||
- Group A:
|
- Group A:
|
||||||
- Sonarr:
|
- Sonarr:
|
||||||
|
|||||||
@@ -19,7 +19,7 @@ Allowed fields: `["uptime", "wan", "lan", "lan_users", "lan_devices", "wlan", "w
|
|||||||
|
|
||||||
!!! hint
|
!!! hint
|
||||||
|
|
||||||
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.
|
If you enter e.g. incorrect credentials and receive an "API Error", you may need to recreate the container to clear the cache.
|
||||||
|
|
||||||
```yaml
|
```yaml
|
||||||
widget:
|
widget:
|
||||||
|
|||||||
Generated
+842
-1482
File diff suppressed because it is too large.
Load diff
+11
-11
@@ -10,7 +10,7 @@
|
|||||||
"telemetry": "next telemetry disable"
|
"telemetry": "next telemetry disable"
|
||||||
},
|
},
|
||||||
"dependencies": {
|
"dependencies": {
|
||||||
"@headlessui/react": "^1.7.19",
|
"@headlessui/react": "^1.7.18",
|
||||||
"@kubernetes/client-node": "^0.17.1",
|
"@kubernetes/client-node": "^0.17.1",
|
||||||
"cal-parser": "^1.0.2",
|
"cal-parser": "^1.0.2",
|
||||||
"classnames": "^2.5.1",
|
"classnames": "^2.5.1",
|
||||||
@@ -24,22 +24,22 @@
|
|||||||
"luxon": "^3.5.0",
|
"luxon": "^3.5.0",
|
||||||
"memory-cache": "^0.2.0",
|
"memory-cache": "^0.2.0",
|
||||||
"minecraft-ping-js": "^1.0.2",
|
"minecraft-ping-js": "^1.0.2",
|
||||||
"next": "^15.0.3",
|
"next": "^12.3.4",
|
||||||
"next-i18next": "^12.1.0",
|
"next-i18next": "^12.1.0",
|
||||||
"ping": "^0.4.4",
|
"ping": "^0.4.4",
|
||||||
"pretty-bytes": "^6.1.1",
|
"pretty-bytes": "^6.1.1",
|
||||||
"raw-body": "^3.0.0",
|
"raw-body": "^3.0.0",
|
||||||
"react": "^18.3.1",
|
"react": "^18.3.1",
|
||||||
"react-dom": "^18.3.1",
|
"react-dom": "^18.2.0",
|
||||||
"react-i18next": "^11.18.6",
|
"react-i18next": "^11.18.6",
|
||||||
"react-icons": "^5.4.0",
|
"react-icons": "^5.4.0",
|
||||||
"recharts": "^2.12.7",
|
"recharts": "^2.12.6",
|
||||||
"rrule": "^2.8.1",
|
"rrule": "^2.8.1",
|
||||||
"swr": "^1.3.0",
|
"swr": "^1.3.0",
|
||||||
"systeminformation": "^5.24.3",
|
"systeminformation": "^5.24.3",
|
||||||
"tough-cookie": "^4.1.4",
|
"tough-cookie": "^4.1.3",
|
||||||
"urbackup-server-api": "^0.8.9",
|
"urbackup-server-api": "^0.52.1",
|
||||||
"winston": "^3.13.0",
|
"winston": "^3.11.0",
|
||||||
"xml-js": "^1.6.11"
|
"xml-js": "^1.6.11"
|
||||||
},
|
},
|
||||||
"devDependencies": {
|
"devDependencies": {
|
||||||
@@ -47,15 +47,15 @@
|
|||||||
"autoprefixer": "^10.4.20",
|
"autoprefixer": "^10.4.20",
|
||||||
"eslint": "^8.57.1",
|
"eslint": "^8.57.1",
|
||||||
"eslint-config-airbnb": "^19.0.4",
|
"eslint-config-airbnb": "^19.0.4",
|
||||||
"eslint-config-next": "^14.2.4",
|
"eslint-config-next": "^14.2.3",
|
||||||
"eslint-config-prettier": "^9.1.0",
|
"eslint-config-prettier": "^9.1.0",
|
||||||
"eslint-plugin-import": "^2.31.0",
|
"eslint-plugin-import": "^2.31.0",
|
||||||
"eslint-plugin-jsx-a11y": "^6.9.0",
|
"eslint-plugin-jsx-a11y": "^6.8.0",
|
||||||
"eslint-plugin-prettier": "^5.2.1",
|
"eslint-plugin-prettier": "^5.2.1",
|
||||||
"eslint-plugin-react": "^7.37.3",
|
"eslint-plugin-react": "^7.37.3",
|
||||||
"eslint-plugin-react-hooks": "^4.6.2",
|
"eslint-plugin-react-hooks": "^4.6.2",
|
||||||
"postcss": "^8.4.48",
|
"postcss": "^8.4.47",
|
||||||
"prettier": "^3.3.2",
|
"prettier": "^3.2.5",
|
||||||
"tailwind-scrollbar": "^3.0.5",
|
"tailwind-scrollbar": "^3.0.5",
|
||||||
"tailwindcss": "^3.4.17",
|
"tailwindcss": "^3.4.17",
|
||||||
"typescript": "^5.6.3"
|
"typescript": "^5.6.3"
|
||||||
|
|||||||
Generated
+1490
-1172
File diff suppressed because it is too large.
Load diff
@@ -1,5 +1,5 @@
|
|||||||
import { useContext } from "react";
|
import { useContext } from "react";
|
||||||
import Image from "next/image";
|
import Image from "next/future/image";
|
||||||
|
|
||||||
import { SettingsContext } from "utils/contexts/settings";
|
import { SettingsContext } from "utils/contexts/settings";
|
||||||
import { ThemeContext } from "utils/contexts/theme";
|
import { ThemeContext } from "utils/contexts/theme";
|
||||||
|
|||||||
@@ -1,6 +1,6 @@
|
|||||||
import { useTranslation } from "next-i18next";
|
import { useTranslation } from "next-i18next";
|
||||||
import useSWR from "swr";
|
import useSWR from "swr";
|
||||||
import { compareVersions, validate } from "compare-versions";
|
import { compareVersions } from "compare-versions";
|
||||||
import { MdNewReleases } from "react-icons/md";
|
import { MdNewReleases } from "react-icons/md";
|
||||||
|
|
||||||
export default function Version() {
|
export default function Version() {
|
||||||
@@ -44,7 +44,7 @@ export default function Version() {
|
|||||||
</a>
|
</a>
|
||||||
)}
|
)}
|
||||||
</span>
|
</span>
|
||||||
{!validate(version)
|
{version === "main" || version === "dev" || version === "nightly"
|
||||||
? null
|
? null
|
||||||
: releaseData &&
|
: releaseData &&
|
||||||
latestRelease &&
|
latestRelease &&
|
||||||
|
|||||||
@@ -1,22 +0,0 @@
|
|||||||
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*",
|
|
||||||
};
|
|
||||||
@@ -86,7 +86,6 @@ function Index({ initialSettings, fallback }) {
|
|||||||
const windowFocused = useWindowFocus();
|
const windowFocused = useWindowFocus();
|
||||||
const [stale, setStale] = useState(false);
|
const [stale, setStale] = useState(false);
|
||||||
const { data: errorsData } = useSWR("/api/validate");
|
const { data: errorsData } = useSWR("/api/validate");
|
||||||
const { error: validateError } = errorsData || {};
|
|
||||||
const { data: hashData, mutate: mutateHash } = useSWR("/api/hash");
|
const { data: hashData, mutate: mutateHash } = useSWR("/api/hash");
|
||||||
|
|
||||||
useEffect(() => {
|
useEffect(() => {
|
||||||
@@ -118,24 +117,6 @@ function Index({ initialSettings, fallback }) {
|
|||||||
}
|
}
|
||||||
}, [hashData]);
|
}, [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) {
|
if (stale) {
|
||||||
return (
|
return (
|
||||||
<div className="flex items-center justify-center h-screen">
|
<div className="flex items-center justify-center h-screen">
|
||||||
|
|||||||
@@ -67,8 +67,3 @@ dialog ::-webkit-scrollbar {
|
|||||||
.chart + .chart {
|
.chart + .chart {
|
||||||
margin-top: 2em;
|
margin-top: 2em;
|
||||||
}
|
}
|
||||||
|
|
||||||
.service-container + .chart {
|
|
||||||
margin-top: 2.5rem;
|
|
||||||
margin-bottom: .5rem;
|
|
||||||
}
|
|
||||||
@@ -79,7 +79,7 @@ export default async function genericProxyHandler(req, res, map) {
|
|||||||
error: {
|
error: {
|
||||||
message: "HTTP Error",
|
message: "HTTP Error",
|
||||||
url: sanitizeErrorURL(url),
|
url: sanitizeErrorURL(url),
|
||||||
data: Buffer.isBuffer(resultData) ? Buffer.from(resultData).toString() : resultData,
|
resultData: Buffer.isBuffer(resultData) ? Buffer.from(resultData).toString() : resultData,
|
||||||
},
|
},
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -29,13 +29,10 @@ 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 (
|
return (
|
||||||
<Container service={service}>
|
<Container service={service}>
|
||||||
<Block label="evcc.pv_power" value={`${toKilowatts(t, stateData.result.pvPower)} ${t("evcc.kilowatt")}`} />
|
<Block label="evcc.pv_power" value={`${toKilowatts(t, stateData.result.pvPower)} ${t("evcc.kilowatt")}`} />
|
||||||
<Block label="evcc.grid_power" value={`${toKilowatts(t, gridPower)} ${t("evcc.kilowatt")}`} />
|
<Block label="evcc.grid_power" value={`${toKilowatts(t, stateData.result.gridPower)} ${t("evcc.kilowatt")}`} />
|
||||||
<Block label="evcc.home_power" value={`${toKilowatts(t, stateData.result.homePower)} ${t("evcc.kilowatt")}`} />
|
<Block label="evcc.home_power" value={`${toKilowatts(t, stateData.result.homePower)} ${t("evcc.kilowatt")}`} />
|
||||||
<Block
|
<Block
|
||||||
label="evcc.charge_power"
|
label="evcc.charge_power"
|
||||||
|
|||||||
Reference in new issue
Block a user