mirror of
https://github.com/gethomepage/homepage.git
synced 2025-05-24 02:02:35 -04:00
Merge branch 'dev'
This commit is contained in:
commit
63e521ae93
@ -16,11 +16,11 @@
|
||||
**/compose*
|
||||
**/Dockerfile*
|
||||
**/node_modules
|
||||
!.next/standalone/node_modules
|
||||
**/npm-debug.log
|
||||
**/obj
|
||||
**/secrets.dev.yaml
|
||||
**/values.dev.yaml
|
||||
**/.next
|
||||
README.md
|
||||
config/
|
||||
k3d/
|
||||
|
138
.github/workflows/docker-publish.yml
vendored
138
.github/workflows/docker-publish.yml
vendored
@ -1,9 +1,4 @@
|
||||
name: Docker
|
||||
|
||||
# This workflow uses actions that are not certified by GitHub.
|
||||
# They are provided by a third-party and are governed by
|
||||
# separate terms of service, privacy policy, and support
|
||||
# documentation.
|
||||
name: Docker CI
|
||||
|
||||
on:
|
||||
schedule:
|
||||
@ -13,7 +8,6 @@ on:
|
||||
- main
|
||||
- feature/**
|
||||
- dev
|
||||
# Publish semver tags as releases.
|
||||
tags: [ 'v*.*.*' ]
|
||||
paths-ignore:
|
||||
- 'docs/**'
|
||||
@ -26,89 +20,56 @@ on:
|
||||
merge_group:
|
||||
|
||||
env:
|
||||
# github.repository as <account>/<repo>
|
||||
IMAGE_NAME: ${{ github.repository }}
|
||||
|
||||
|
||||
jobs:
|
||||
pre-commit:
|
||||
name: Linting Checks
|
||||
runs-on: ubuntu-22.04
|
||||
steps:
|
||||
-
|
||||
name: Checkout repository
|
||||
- name: Checkout repository
|
||||
uses: actions/checkout@v4
|
||||
-
|
||||
name: Install python
|
||||
|
||||
- name: Install python
|
||||
uses: actions/setup-python@v5
|
||||
with:
|
||||
python-version: 3.x
|
||||
-
|
||||
name: Check files
|
||||
|
||||
- name: Check files
|
||||
uses: pre-commit/action@v3.0.1
|
||||
-
|
||||
name: Install pnpm
|
||||
|
||||
- name: Install pnpm
|
||||
uses: pnpm/action-setup@v4
|
||||
with:
|
||||
version: 10
|
||||
run_install: false
|
||||
-
|
||||
name: Install Node.js
|
||||
|
||||
- name: Setup Node.js
|
||||
uses: actions/setup-node@v4
|
||||
with:
|
||||
node-version: 20
|
||||
cache: 'pnpm'
|
||||
-
|
||||
name: Install dependencies
|
||||
|
||||
- name: Install dependencies
|
||||
run: pnpm install
|
||||
-
|
||||
name: Lint frontend
|
||||
|
||||
- name: Lint frontend
|
||||
run: pnpm run lint
|
||||
|
||||
build:
|
||||
name: Docker Build & Push
|
||||
if: github.repository == 'gethomepage/homepage'
|
||||
runs-on: self-hosted
|
||||
needs:
|
||||
- pre-commit
|
||||
needs: [ pre-commit ]
|
||||
permissions:
|
||||
contents: read
|
||||
packages: write
|
||||
# This is used to complete the identity challenge
|
||||
# with sigstore/fulcio when running outside of PRs.
|
||||
id-token: write
|
||||
|
||||
steps:
|
||||
- name: Checkout repository
|
||||
uses: actions/checkout@v4
|
||||
|
||||
# Login to Docker Registry
|
||||
# https://github.com/docker/login-action
|
||||
- name: Log into registry ${{ env.REGISTRY }}
|
||||
if: github.event_name != 'pull_request'
|
||||
uses: docker/login-action@v3
|
||||
with:
|
||||
registry: ghcr.io
|
||||
username: ${{ github.actor }}
|
||||
password: ${{ secrets.GITHUB_TOKEN }}
|
||||
- name: Login to Docker Hub
|
||||
if: github.event_name != 'pull_request'
|
||||
uses: docker/login-action@v3
|
||||
with:
|
||||
username: ${{ secrets.DOCKERHUB_USERNAME }}
|
||||
password: ${{ secrets.DOCKERHUB_TOKEN }}
|
||||
|
||||
# Setup QEMU
|
||||
# https://github.com/marketplace/actions/docker-setup-buildx#with-qemu
|
||||
- name: Setup QEMU
|
||||
uses: docker/setup-qemu-action@v3.6.0
|
||||
|
||||
# Workaround: https://github.com/docker/build-push-action/issues/461
|
||||
- name: Setup Docker buildx
|
||||
uses: docker/setup-buildx-action@v3
|
||||
|
||||
# Extract metadata (tags, labels) for Docker
|
||||
# https://github.com/docker/metadata-action
|
||||
- name: Extract Docker metadata
|
||||
id: meta
|
||||
uses: docker/metadata-action@v5
|
||||
@ -116,11 +77,69 @@ jobs:
|
||||
images: |
|
||||
${{ env.IMAGE_NAME }}
|
||||
ghcr.io/${{ env.IMAGE_NAME }}
|
||||
tags: |
|
||||
# Default tags
|
||||
type=schedule,pattern=nightly
|
||||
type=ref,event=branch
|
||||
type=ref,event=tag
|
||||
# Versioning tags
|
||||
type=semver,pattern=v{{version}}
|
||||
type=semver,pattern=v{{major}}.{{minor}}
|
||||
type=semver,pattern=v{{major}}
|
||||
flavor: |
|
||||
latest=auto
|
||||
|
||||
# Build and push Docker image with Buildx (don't push on PR)
|
||||
# https://github.com/docker/build-push-action
|
||||
- name: Next.js build cache
|
||||
uses: actions/cache@v4
|
||||
with:
|
||||
path: .next/cache
|
||||
key: nextjs-${{ runner.os }}-${{ hashFiles('pnpm-lock.yaml') }}-${{ hashFiles('**/*.js', '**/*.jsx') }}
|
||||
restore-keys: |
|
||||
nextjs-${{ runner.os }}-${{ hashFiles('pnpm-lock.yaml') }}
|
||||
|
||||
- name: Install pnpm
|
||||
uses: pnpm/action-setup@v4
|
||||
with:
|
||||
version: 10
|
||||
run_install: false
|
||||
|
||||
- name: Setup Node.js
|
||||
uses: actions/setup-node@v4
|
||||
with:
|
||||
node-version: 20
|
||||
cache: 'pnpm'
|
||||
|
||||
- name: Install dependencies
|
||||
run: pnpm install
|
||||
|
||||
- name: Build app
|
||||
run: |
|
||||
NEXT_PUBLIC_BUILDTIME="${{ fromJSON(steps.meta.outputs.json).labels['org.opencontainers.image.created'] }}" \
|
||||
NEXT_PUBLIC_VERSION="${{ fromJSON(steps.meta.outputs.json).labels['org.opencontainers.image.version'] }}" \
|
||||
NEXT_PUBLIC_REVISION="${{ fromJSON(steps.meta.outputs.json).labels['org.opencontainers.image.revision'] }}" \
|
||||
pnpm run build
|
||||
|
||||
- name: Log into registry ${{ env.REGISTRY }}
|
||||
if: github.event_name != 'pull_request'
|
||||
uses: docker/login-action@v3
|
||||
with:
|
||||
registry: ghcr.io
|
||||
username: ${{ github.actor }}
|
||||
password: ${{ secrets.GITHUB_TOKEN }}
|
||||
|
||||
- name: Login to Docker Hub
|
||||
if: github.event_name != 'pull_request'
|
||||
uses: docker/login-action@v3
|
||||
with:
|
||||
username: ${{ secrets.DOCKERHUB_USERNAME }}
|
||||
password: ${{ secrets.DOCKERHUB_TOKEN }}
|
||||
|
||||
- name: Setup QEMU
|
||||
uses: docker/setup-qemu-action@v3.6.0
|
||||
|
||||
- name: Setup Docker buildx
|
||||
uses: docker/setup-buildx-action@v3
|
||||
|
||||
- name: Build and push Docker image
|
||||
id: build-and-push
|
||||
uses: docker/build-push-action@v6
|
||||
@ -130,18 +149,15 @@ jobs:
|
||||
tags: ${{ steps.meta.outputs.tags }}
|
||||
labels: ${{ steps.meta.outputs.labels }}
|
||||
build-args: |
|
||||
CI=true
|
||||
BUILDTIME=${{ fromJSON(steps.meta.outputs.json).labels['org.opencontainers.image.created'] }}
|
||||
VERSION=${{ fromJSON(steps.meta.outputs.json).labels['org.opencontainers.image.version'] }}
|
||||
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
|
||||
cache-from: type=local,src=/tmp/.buildx-cache
|
||||
cache-to: type=local,dest=/tmp/.buildx-cache-new,mode=max
|
||||
|
||||
# Temp fix
|
||||
# https://github.com/docker/build-push-action/issues/252
|
||||
# https://github.com/moby/buildkit/issues/1896
|
||||
# https://github.com/docker/build-push-action/issues/252 / https://github.com/moby/buildkit/issues/1896
|
||||
- name: Move cache
|
||||
run: |
|
||||
rm -rf /tmp/.buildx-cache
|
||||
|
@ -9,11 +9,14 @@ repos:
|
||||
- id: check-yaml
|
||||
exclude: "(^mkdocs\\.yml$)"
|
||||
- id: check-added-large-files
|
||||
- repo: https://github.com/pre-commit/mirrors-prettier
|
||||
rev: 'v3.0.3'
|
||||
- repo: https://github.com/rbubley/mirrors-prettier
|
||||
rev: 'v3.3.3'
|
||||
hooks:
|
||||
- id: prettier
|
||||
types_or:
|
||||
- javascript
|
||||
- markdown
|
||||
- jsx
|
||||
additional_dependencies:
|
||||
- prettier@3.3.3
|
||||
- 'prettier-plugin-organize-imports@4.1.0'
|
||||
|
@ -1 +0,0 @@
|
||||
{}
|
5
.prettierrc.js
Normal file
5
.prettierrc.js
Normal file
@ -0,0 +1,5 @@
|
||||
const config = {
|
||||
plugins: [require("prettier-plugin-organize-imports")],
|
||||
};
|
||||
|
||||
module.exports = config;
|
74
Dockerfile
74
Dockerfile
@ -1,67 +1,63 @@
|
||||
# Install dependencies only when needed
|
||||
FROM docker.io/node:22-alpine AS deps
|
||||
|
||||
WORKDIR /app
|
||||
|
||||
COPY --link package.json pnpm-lock.yaml* ./
|
||||
|
||||
SHELL ["/bin/ash", "-xeo", "pipefail", "-c"]
|
||||
RUN apk add --no-cache libc6-compat \
|
||||
&& apk add --no-cache --virtual .gyp python3 make g++ \
|
||||
&& npm install -g pnpm
|
||||
|
||||
RUN --mount=type=cache,id=pnpm-store,target=/root/.local/share/pnpm/store pnpm fetch | grep -v "cross-device link not permitted\|Falling back to copying packages from store"
|
||||
|
||||
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:22-alpine AS builder
|
||||
# =========================
|
||||
# Builder Stage
|
||||
# =========================
|
||||
FROM node:22-slim AS builder
|
||||
WORKDIR /app
|
||||
|
||||
# Setup
|
||||
RUN mkdir config
|
||||
COPY . .
|
||||
|
||||
ARG CI
|
||||
ARG BUILDTIME
|
||||
ARG VERSION
|
||||
ARG REVISION
|
||||
ENV CI=$CI
|
||||
|
||||
COPY --link --from=deps /app/node_modules ./node_modules/
|
||||
COPY . .
|
||||
# Install and build only outside CI
|
||||
RUN if [ "$CI" != "true" ]; then \
|
||||
corepack enable && corepack prepare pnpm@latest --activate && \
|
||||
pnpm install --frozen-lockfile --prefer-offline && \
|
||||
NEXT_TELEMETRY_DISABLED=1 \
|
||||
NEXT_PUBLIC_BUILDTIME=$BUILDTIME \
|
||||
NEXT_PUBLIC_VERSION=$VERSION \
|
||||
NEXT_PUBLIC_REVISION=$REVISION \
|
||||
pnpm run build; \
|
||||
else \
|
||||
echo "✅ Using prebuilt app from CI context"; \
|
||||
fi
|
||||
|
||||
SHELL ["/bin/ash", "-xeo", "pipefail", "-c"]
|
||||
RUN npm install -g pnpm \
|
||||
&& pnpm run telemetry \
|
||||
&& NEXT_PUBLIC_BUILDTIME=$BUILDTIME NEXT_PUBLIC_VERSION=$VERSION NEXT_PUBLIC_REVISION=$REVISION pnpm run build
|
||||
|
||||
# Production image, copy all the files and run next
|
||||
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."
|
||||
# =========================
|
||||
# Runtime Stage
|
||||
# =========================
|
||||
FROM 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"
|
||||
LABEL org.opencontainers.image.documentation='https://github.com/gethomepage/homepage/wiki'
|
||||
LABEL org.opencontainers.image.source='https://github.com/gethomepage/homepage'
|
||||
LABEL org.opencontainers.image.licenses='Apache-2.0'
|
||||
|
||||
ENV NODE_ENV=production
|
||||
|
||||
# Setup
|
||||
WORKDIR /app
|
||||
|
||||
# Copy files from context (this allows the files to copy before the builder stage is done).
|
||||
COPY --link --chown=1000:1000 package.json next.config.js ./
|
||||
# Copy some files from context
|
||||
COPY --link --chown=1000:1000 /public ./public/
|
||||
|
||||
# Copy files from builder
|
||||
COPY --link --from=builder --chown=1000:1000 /app/.next/standalone ./
|
||||
COPY --link --from=builder --chown=1000:1000 /app/.next/static/ ./.next/static/
|
||||
COPY --link --chmod=755 docker-entrypoint.sh /usr/local/bin/
|
||||
|
||||
RUN apk add --no-cache su-exec
|
||||
# Copy only necessary files from the build stage
|
||||
COPY --link --from=builder --chown=1000:1000 /app/.next/standalone/ ./
|
||||
COPY --link --from=builder --chown=1000:1000 /app/.next/static/ ./.next/static
|
||||
|
||||
RUN apk add --no-cache su-exec iputils-ping
|
||||
|
||||
ENV NODE_ENV=production
|
||||
ENV HOSTNAME=0.0.0.0
|
||||
ENV PORT=3000
|
||||
EXPOSE $PORT
|
||||
|
||||
HEALTHCHECK --interval=10s --timeout=3s --start-period=20s \
|
||||
CMD wget --no-verbose --tries=1 --spider --no-check-certificate http://127.0.0.1:$PORT/api/healthcheck || exit 1
|
||||
CMD wget --no-verbose --tries=1 --spider http://127.0.0.1:$PORT/api/healthcheck || exit 1
|
||||
|
||||
ENTRYPOINT ["docker-entrypoint.sh"]
|
||||
CMD ["node", "server.js"]
|
||||
|
@ -189,6 +189,7 @@ widget:
|
||||
name: id # required, field in each item to use as the item name (left side)
|
||||
label: ip_address # required, field in each item to use as the item label (right side)
|
||||
limit: 5 # optional, limit the number of items to display
|
||||
format: text # optional - format of the label field
|
||||
target: https://example.com/server/{id} # optional, makes items clickable with template support
|
||||
```
|
||||
|
||||
|
@ -1,17 +0,0 @@
|
||||
---
|
||||
title: Hoarder
|
||||
description: Hoarder Widget Configuration
|
||||
---
|
||||
|
||||
Learn more about [Hoarder](https://hoarder.app).
|
||||
|
||||
Generate an API key for your user at `User Settings > API Keys`.
|
||||
|
||||
Allowed fields: `["bookmarks", "favorites", "archived", "highlights", "lists", "tags"]` (maximum of 4).
|
||||
|
||||
```yaml
|
||||
widget:
|
||||
type: hoarder
|
||||
url: http[s]://hoarder.host.or.ip[:port]
|
||||
key: hoarderapikey
|
||||
```
|
@ -51,7 +51,7 @@ You can also find a list of all available service widgets in the sidebar navigat
|
||||
- [HDHomeRun](hdhomerun.md)
|
||||
- [Headscale](headscale.md)
|
||||
- [Healthchecks](healthchecks.md)
|
||||
- [Hoarder](hoarder.md)
|
||||
- [Karakeep](karakeep.md)
|
||||
- [Home Assistant](homeassistant.md)
|
||||
- [HomeBox](homebox.md)
|
||||
- [Homebridge](homebridge.md)
|
||||
|
17
docs/widgets/services/karakeep.md
Normal file
17
docs/widgets/services/karakeep.md
Normal file
@ -0,0 +1,17 @@
|
||||
---
|
||||
title: Karakeep
|
||||
description: Karakeep Widget Configuration
|
||||
---
|
||||
|
||||
Learn more about [Karakeep](https://karakeep.app) (formerly known as Hoarder).
|
||||
|
||||
Generate an API key for your user at `User Settings > API Keys`.
|
||||
|
||||
Allowed fields: `["bookmarks", "favorites", "archived", "highlights", "lists", "tags"]` (maximum of 4).
|
||||
|
||||
```yaml
|
||||
widget:
|
||||
type: karakeep
|
||||
url: http[s]://karakeep.host.or.ip[:port]
|
||||
key: karakeep_api_key
|
||||
```
|
@ -74,7 +74,7 @@ nav:
|
||||
- widgets/services/hdhomerun.md
|
||||
- widgets/services/headscale.md
|
||||
- widgets/services/healthchecks.md
|
||||
- widgets/services/hoarder.md
|
||||
- widgets/services/karakeep.md
|
||||
- widgets/services/homeassistant.md
|
||||
- widgets/services/homebox.md
|
||||
- widgets/services/homebridge.md
|
||||
|
15
package.json
15
package.json
@ -1,6 +1,6 @@
|
||||
{
|
||||
"name": "homepage",
|
||||
"version": "1.1.1",
|
||||
"version": "1.1.2",
|
||||
"private": true,
|
||||
"scripts": {
|
||||
"preinstall": "npx only-allow pnpm",
|
||||
@ -19,13 +19,13 @@
|
||||
"dockerode": "^4.0.4",
|
||||
"follow-redirects": "^1.15.9",
|
||||
"gamedig": "^5.2.0",
|
||||
"i18next": "^21.10.0",
|
||||
"i18next": "^24.2.3",
|
||||
"js-yaml": "^4.1.0",
|
||||
"json-rpc-2.0": "^1.7.0",
|
||||
"luxon": "^3.5.0",
|
||||
"memory-cache": "^0.2.0",
|
||||
"minecraftstatuspinger": "^1.2.2",
|
||||
"next": "^15.2.3",
|
||||
"next": "^15.2.4",
|
||||
"next-i18next": "^12.1.0",
|
||||
"ping": "^0.4.4",
|
||||
"pretty-bytes": "^6.1.1",
|
||||
@ -36,7 +36,7 @@
|
||||
"react-icons": "^5.4.0",
|
||||
"recharts": "^2.15.1",
|
||||
"rrule": "^2.8.1",
|
||||
"swr": "^1.3.0",
|
||||
"swr": "^2.3.3",
|
||||
"systeminformation": "^5.25.11",
|
||||
"tough-cookie": "^5.1.2",
|
||||
"urbackup-server-api": "^0.8.9",
|
||||
@ -47,15 +47,16 @@
|
||||
"@tailwindcss/forms": "^0.5.10",
|
||||
"@tailwindcss/postcss": "^4.0.9",
|
||||
"eslint": "^9.21.0",
|
||||
"eslint-config-next": "^15.1.7",
|
||||
"eslint-config-prettier": "^10.0.2",
|
||||
"eslint-config-next": "^15.2.4",
|
||||
"eslint-config-prettier": "^10.1.1",
|
||||
"eslint-plugin-import": "^2.31.0",
|
||||
"eslint-plugin-jsx-a11y": "^6.10.2",
|
||||
"eslint-plugin-prettier": "^5.2.3",
|
||||
"eslint-plugin-react": "^7.37.4",
|
||||
"eslint-plugin-react-hooks": "^5.1.0",
|
||||
"postcss": "^8.5.2",
|
||||
"postcss": "^8.5.3",
|
||||
"prettier": "^3.5.2",
|
||||
"prettier-plugin-organize-imports": "^4.1.0",
|
||||
"tailwind-scrollbar": "^4.0.1",
|
||||
"tailwindcss": "^4.0.9",
|
||||
"typescript": "^5.7.3"
|
||||
|
628
pnpm-lock.yaml
generated
628
pnpm-lock.yaml
generated
File diff suppressed because it is too large
Load Diff
@ -1024,7 +1024,7 @@
|
||||
"bcharge":"Battery Charge",
|
||||
"timeleft":"Time Left"
|
||||
},
|
||||
"hoarder": {
|
||||
"karakeep": {
|
||||
"bookmarks": "Bookmarks",
|
||||
"favorites": "Favorites",
|
||||
"archived": "Archived",
|
||||
|
@ -1,10 +1,10 @@
|
||||
import { useRef, useEffect } from "react";
|
||||
import classNames from "classnames";
|
||||
import { Disclosure, Transition } from "@headlessui/react";
|
||||
import { MdKeyboardArrowDown } from "react-icons/md";
|
||||
import ErrorBoundary from "components/errorboundry";
|
||||
import classNames from "classnames";
|
||||
import List from "components/bookmarks/list";
|
||||
import ErrorBoundary from "components/errorboundry";
|
||||
import ResolvedIcon from "components/resolvedicon";
|
||||
import { useEffect, useRef } from "react";
|
||||
import { MdKeyboardArrowDown } from "react-icons/md";
|
||||
|
||||
export default function BookmarksGroup({
|
||||
bookmarks,
|
||||
|
@ -1,7 +1,7 @@
|
||||
import { useContext } from "react";
|
||||
import classNames from "classnames";
|
||||
import { SettingsContext } from "utils/contexts/settings";
|
||||
import ResolvedIcon from "components/resolvedicon";
|
||||
import { useContext } from "react";
|
||||
import { SettingsContext } from "utils/contexts/settings";
|
||||
|
||||
export default function Item({ bookmark, iconOnly = false }) {
|
||||
const description = bookmark.description ?? new URL(bookmark.href).hostname;
|
||||
|
@ -1,6 +1,6 @@
|
||||
/* eslint-disable @next/next/no-img-element */
|
||||
/* eslint-disable jsx-a11y/alt-text */
|
||||
import { useRef, useEffect, useContext } from "react";
|
||||
import { useContext, useEffect, useRef } from "react";
|
||||
import { ColorContext } from "utils/contexts/color";
|
||||
|
||||
import themes from "utils/styles/themes";
|
||||
|
@ -1,6 +1,6 @@
|
||||
import { useTranslation } from "react-i18next";
|
||||
import { useEffect, useState, useRef, useCallback, useContext } from "react";
|
||||
import classNames from "classnames";
|
||||
import { useTranslation } from "next-i18next";
|
||||
import { useCallback, useContext, useEffect, useRef, useState } from "react";
|
||||
import useSWR from "swr";
|
||||
import { SettingsContext } from "utils/contexts/settings";
|
||||
|
||||
@ -53,7 +53,7 @@ export default function QuickLaunch({ servicesAndBookmarks, searchString, setSea
|
||||
const result = results[currentItemIndex];
|
||||
window.open(
|
||||
result.href,
|
||||
newWindow ? "_blank" : result.target ?? searchProvider?.target ?? settings.target ?? "_blank",
|
||||
newWindow ? "_blank" : (result.target ?? searchProvider?.target ?? settings.target ?? "_blank"),
|
||||
"noreferrer",
|
||||
);
|
||||
}
|
||||
@ -204,7 +204,8 @@ export default function QuickLaunch({ servicesAndBookmarks, searchString, setSea
|
||||
return () => {
|
||||
abortController.abort();
|
||||
};
|
||||
}, [searchString, servicesAndBookmarks, searchDescriptions, hideVisitURL, searchSuggestions, searchProvider, url, t]);
|
||||
// eslint-disable-next-line react-hooks/exhaustive-deps
|
||||
}, [searchString, servicesAndBookmarks, searchDescriptions, hideVisitURL, searchSuggestions, searchProvider, url]);
|
||||
|
||||
const [hidden, setHidden] = useState(true);
|
||||
useEffect(() => {
|
||||
|
@ -1,5 +1,5 @@
|
||||
import { useContext } from "react";
|
||||
import Image from "next/image";
|
||||
import { useContext } from "react";
|
||||
import { SettingsContext } from "utils/contexts/settings";
|
||||
import { ThemeContext } from "utils/contexts/theme";
|
||||
|
||||
|
@ -1,7 +1,7 @@
|
||||
import { Fragment } from "react";
|
||||
import { Menu, Transition } from "@headlessui/react";
|
||||
import { BiCog } from "react-icons/bi";
|
||||
import classNames from "classnames";
|
||||
import { Fragment } from "react";
|
||||
import { BiCog } from "react-icons/bi";
|
||||
|
||||
export default function Dropdown({ options, value, setValue }) {
|
||||
return (
|
||||
|
@ -1,9 +1,9 @@
|
||||
import { useRef, useEffect } from "react";
|
||||
import classNames from "classnames";
|
||||
import { Disclosure, Transition } from "@headlessui/react";
|
||||
import { MdKeyboardArrowDown } from "react-icons/md";
|
||||
import List from "components/services/list";
|
||||
import classNames from "classnames";
|
||||
import ResolvedIcon from "components/resolvedicon";
|
||||
import List from "components/services/list";
|
||||
import { useEffect, useRef } from "react";
|
||||
import { MdKeyboardArrowDown } from "react-icons/md";
|
||||
|
||||
import { columnMap } from "../../utils/layout/columns";
|
||||
|
||||
|
@ -1,15 +1,15 @@
|
||||
import classNames from "classnames";
|
||||
import ResolvedIcon from "components/resolvedicon";
|
||||
import { useContext, useState } from "react";
|
||||
import { SettingsContext } from "utils/contexts/settings";
|
||||
import Docker from "widgets/docker/component";
|
||||
import Kubernetes from "widgets/kubernetes/component";
|
||||
import { SettingsContext } from "utils/contexts/settings";
|
||||
import ResolvedIcon from "components/resolvedicon";
|
||||
|
||||
import Status from "./status";
|
||||
import Widget from "./widget";
|
||||
import KubernetesStatus from "./kubernetes-status";
|
||||
import Ping from "./ping";
|
||||
import SiteMonitor from "./site-monitor";
|
||||
import KubernetesStatus from "./kubernetes-status";
|
||||
import Status from "./status";
|
||||
import Widget from "./widget";
|
||||
|
||||
export default function Item({ service, groupName, useEqualHeights }) {
|
||||
const hasLink = service.href && service.href !== "#";
|
||||
|
@ -1,5 +1,5 @@
|
||||
import useSWR from "swr";
|
||||
import { t } from "i18next";
|
||||
import useSWR from "swr";
|
||||
|
||||
export default function KubernetesStatus({ service, style }) {
|
||||
const podSelectorString = service.podSelector !== undefined ? `podSelector=${service.podSelector}` : "";
|
||||
|
@ -1,4 +1,4 @@
|
||||
import { useTranslation } from "react-i18next";
|
||||
import { useTranslation } from "next-i18next";
|
||||
import useSWR from "swr";
|
||||
|
||||
export default function Ping({ groupName, serviceName, style }) {
|
||||
|
@ -1,4 +1,4 @@
|
||||
import { useTranslation } from "react-i18next";
|
||||
import { useTranslation } from "next-i18next";
|
||||
import useSWR from "swr";
|
||||
|
||||
export default function SiteMonitor({ groupName, serviceName, style }) {
|
||||
|
@ -1,4 +1,4 @@
|
||||
import { useTranslation } from "react-i18next";
|
||||
import { useTranslation } from "next-i18next";
|
||||
import useSWR from "swr";
|
||||
|
||||
export default function Status({ service, style }) {
|
||||
|
@ -1,5 +1,5 @@
|
||||
import { useTranslation } from "next-i18next";
|
||||
import ErrorBoundary from "components/errorboundry";
|
||||
import { useTranslation } from "next-i18next";
|
||||
|
||||
import components from "widgets/components";
|
||||
|
||||
|
@ -1,5 +1,5 @@
|
||||
import { useTranslation } from "next-i18next";
|
||||
import classNames from "classnames";
|
||||
import { useTranslation } from "next-i18next";
|
||||
|
||||
export default function Block({ value, label }) {
|
||||
const { t } = useTranslation();
|
||||
|
@ -3,6 +3,11 @@ import { SettingsContext } from "utils/contexts/settings";
|
||||
|
||||
import Error from "./error";
|
||||
|
||||
const ALIASED_WIDGETS = {
|
||||
pialert: "netalertx",
|
||||
hoarder: "karakeep",
|
||||
};
|
||||
|
||||
export default function Container({ error = false, children, service }) {
|
||||
const { settings } = useContext(SettingsContext);
|
||||
|
||||
@ -32,7 +37,17 @@ export default function Container({ error = false, children, service }) {
|
||||
if (!field.includes(".")) {
|
||||
fullField = `${type}.${field}`;
|
||||
}
|
||||
return fullField === child?.props?.label;
|
||||
let matches = fullField === child?.props?.label;
|
||||
// check if the field is an 'alias'
|
||||
if (matches) {
|
||||
return true;
|
||||
} else if (ALIASED_WIDGETS[type]) {
|
||||
matches = fullField.replace(type, ALIASED_WIDGETS[type]) === child?.props?.label;
|
||||
|
||||
return matches;
|
||||
}
|
||||
// no match
|
||||
return false;
|
||||
}),
|
||||
);
|
||||
}
|
||||
|
@ -1,4 +1,4 @@
|
||||
import { useTranslation } from "react-i18next";
|
||||
import { useTranslation } from "next-i18next";
|
||||
import { IoAlertCircle } from "react-icons/io5";
|
||||
|
||||
function displayError(error) {
|
||||
|
@ -1,5 +1,5 @@
|
||||
import { useContext } from "react";
|
||||
import classNames from "classnames";
|
||||
import { useContext } from "react";
|
||||
import { TabContext } from "utils/contexts/tab";
|
||||
|
||||
function slugify(tabName) {
|
||||
|
@ -1,7 +1,7 @@
|
||||
import { useContext, Fragment } from "react";
|
||||
import { IoColorPalette } from "react-icons/io5";
|
||||
import { Popover, Transition } from "@headlessui/react";
|
||||
import classNames from "classnames";
|
||||
import { Fragment, useContext } from "react";
|
||||
import { IoColorPalette } from "react-icons/io5";
|
||||
import { ColorContext } from "utils/contexts/color";
|
||||
|
||||
const colors = [
|
||||
|
@ -1,8 +1,8 @@
|
||||
import { compareVersions, validate } from "compare-versions";
|
||||
import cache from "memory-cache";
|
||||
import { useTranslation } from "next-i18next";
|
||||
import useSWR from "swr";
|
||||
import { compareVersions, validate } from "compare-versions";
|
||||
import { MdNewReleases } from "react-icons/md";
|
||||
import useSWR from "swr";
|
||||
|
||||
const LATEST_RELEASE_CACHE_KEY = "latestRelease";
|
||||
|
||||
|
@ -1,5 +1,5 @@
|
||||
import { useState, useEffect } from "react";
|
||||
import { useTranslation } from "next-i18next";
|
||||
import { useEffect, useState } from "react";
|
||||
|
||||
import Container from "../widget/container";
|
||||
import Raw from "../widget/raw";
|
||||
|
@ -1,9 +1,9 @@
|
||||
import useSWR from "swr";
|
||||
import classNames from "classnames";
|
||||
import { useTranslation } from "next-i18next";
|
||||
import { useContext } from "react";
|
||||
import { FaMemory, FaRegClock, FaThermometerHalf } from "react-icons/fa";
|
||||
import { FiCpu, FiHardDrive } from "react-icons/fi";
|
||||
import { useTranslation } from "next-i18next";
|
||||
import classNames from "classnames";
|
||||
import useSWR from "swr";
|
||||
import { SettingsContext } from "utils/contexts/settings";
|
||||
|
||||
import Error from "../widget/error";
|
||||
|
@ -1,8 +1,8 @@
|
||||
import useSWR from "swr";
|
||||
import { useTranslation } from "next-i18next";
|
||||
import useSWR from "swr";
|
||||
|
||||
import Error from "../widget/error";
|
||||
import Container from "../widget/container";
|
||||
import Error from "../widget/error";
|
||||
import Raw from "../widget/raw";
|
||||
|
||||
import Node from "./node";
|
||||
|
@ -1,7 +1,7 @@
|
||||
import { useTranslation } from "next-i18next";
|
||||
import { FaMemory } from "react-icons/fa";
|
||||
import { FiAlertTriangle, FiCpu, FiServer } from "react-icons/fi";
|
||||
import { SiKubernetes } from "react-icons/si";
|
||||
import { useTranslation } from "next-i18next";
|
||||
|
||||
import UsageBar from "../resources/usage-bar";
|
||||
|
||||
|
@ -1,7 +1,7 @@
|
||||
import useSWR from "swr";
|
||||
|
||||
import Error from "../widget/error";
|
||||
import Container from "../widget/container";
|
||||
import Error from "../widget/error";
|
||||
import Raw from "../widget/raw";
|
||||
|
||||
import Node from "./node";
|
||||
@ -32,8 +32,8 @@ export default function Longhorn({ options }) {
|
||||
<div className="flex flex-row self-center flex-wrap justify-between">
|
||||
{data.nodes
|
||||
.filter((node) => {
|
||||
if (node.id === "total" && total) {
|
||||
return true;
|
||||
if (node.id === "total") {
|
||||
return total;
|
||||
}
|
||||
if (!nodes) {
|
||||
return false;
|
||||
|
@ -1,16 +1,16 @@
|
||||
import useSWR from "swr";
|
||||
import { useState } from "react";
|
||||
import { WiCloudDown } from "react-icons/wi";
|
||||
import { MdLocationDisabled, MdLocationSearching } from "react-icons/md";
|
||||
import { useTranslation } from "next-i18next";
|
||||
import { useState } from "react";
|
||||
import { MdLocationDisabled, MdLocationSearching } from "react-icons/md";
|
||||
import { WiCloudDown } from "react-icons/wi";
|
||||
import useSWR from "swr";
|
||||
|
||||
import Error from "../widget/error";
|
||||
import mapIcon from "../../../utils/weather/openmeteo-condition-map";
|
||||
import Container from "../widget/container";
|
||||
import ContainerButton from "../widget/container_button";
|
||||
import WidgetIcon from "../widget/widget_icon";
|
||||
import Error from "../widget/error";
|
||||
import PrimaryText from "../widget/primary_text";
|
||||
import SecondaryText from "../widget/secondary_text";
|
||||
import mapIcon from "../../../utils/weather/openmeteo-condition-map";
|
||||
import WidgetIcon from "../widget/widget_icon";
|
||||
|
||||
function Widget({ options }) {
|
||||
const { t } = useTranslation();
|
||||
|
@ -1,16 +1,16 @@
|
||||
import useSWR from "swr";
|
||||
import { useState } from "react";
|
||||
import { WiCloudDown } from "react-icons/wi";
|
||||
import { MdLocationDisabled, MdLocationSearching } from "react-icons/md";
|
||||
import { useTranslation } from "next-i18next";
|
||||
import { useState } from "react";
|
||||
import { MdLocationDisabled, MdLocationSearching } from "react-icons/md";
|
||||
import { WiCloudDown } from "react-icons/wi";
|
||||
import useSWR from "swr";
|
||||
|
||||
import Error from "../widget/error";
|
||||
import mapIcon from "../../../utils/weather/owm-condition-map";
|
||||
import Container from "../widget/container";
|
||||
import ContainerButton from "../widget/container_button";
|
||||
import Error from "../widget/error";
|
||||
import PrimaryText from "../widget/primary_text";
|
||||
import SecondaryText from "../widget/secondary_text";
|
||||
import WidgetIcon from "../widget/widget_icon";
|
||||
import mapIcon from "../../../utils/weather/owm-condition-map";
|
||||
|
||||
function Widget({ options }) {
|
||||
const { t, i18n } = useTranslation();
|
||||
|
@ -1,9 +1,9 @@
|
||||
import useSWR from "swr";
|
||||
import { FiCpu } from "react-icons/fi";
|
||||
import { useTranslation } from "next-i18next";
|
||||
import { FiCpu } from "react-icons/fi";
|
||||
import useSWR from "swr";
|
||||
|
||||
import Resource from "../widget/resource";
|
||||
import Error from "../widget/error";
|
||||
import Resource from "../widget/resource";
|
||||
|
||||
export default function Cpu({ expanded, refresh = 1500 }) {
|
||||
const { t } = useTranslation();
|
||||
|
@ -1,9 +1,9 @@
|
||||
import useSWR from "swr";
|
||||
import { FaThermometerHalf } from "react-icons/fa";
|
||||
import { useTranslation } from "next-i18next";
|
||||
import { FaThermometerHalf } from "react-icons/fa";
|
||||
import useSWR from "swr";
|
||||
|
||||
import Resource from "../widget/resource";
|
||||
import Error from "../widget/error";
|
||||
import Resource from "../widget/resource";
|
||||
|
||||
function convertToFahrenheit(t) {
|
||||
return (t * 9) / 5 + 32;
|
||||
|
@ -1,9 +1,9 @@
|
||||
import useSWR from "swr";
|
||||
import { FiHardDrive } from "react-icons/fi";
|
||||
import { useTranslation } from "next-i18next";
|
||||
import { FiHardDrive } from "react-icons/fi";
|
||||
import useSWR from "swr";
|
||||
|
||||
import Resource from "../widget/resource";
|
||||
import Error from "../widget/error";
|
||||
import Resource from "../widget/resource";
|
||||
|
||||
export default function Disk({ options, expanded, diskUnits, refresh = 1500 }) {
|
||||
const { t } = useTranslation();
|
||||
|
@ -1,9 +1,9 @@
|
||||
import useSWR from "swr";
|
||||
import { FaMemory } from "react-icons/fa";
|
||||
import { useTranslation } from "next-i18next";
|
||||
import { FaMemory } from "react-icons/fa";
|
||||
import useSWR from "swr";
|
||||
|
||||
import Resource from "../widget/resource";
|
||||
import Error from "../widget/error";
|
||||
import Resource from "../widget/resource";
|
||||
|
||||
export default function Memory({ expanded, refresh = 1500 }) {
|
||||
const { t } = useTranslation();
|
||||
|
@ -1,9 +1,9 @@
|
||||
import useSWR from "swr";
|
||||
import { FaNetworkWired } from "react-icons/fa";
|
||||
import { useTranslation } from "next-i18next";
|
||||
import { FaNetworkWired } from "react-icons/fa";
|
||||
import useSWR from "swr";
|
||||
|
||||
import Resource from "../widget/resource";
|
||||
import Error from "../widget/error";
|
||||
import Resource from "../widget/resource";
|
||||
|
||||
export default function Network({ options, refresh = 1500 }) {
|
||||
const { t } = useTranslation();
|
||||
|
@ -1,12 +1,12 @@
|
||||
import Container from "../widget/container";
|
||||
import Raw from "../widget/raw";
|
||||
|
||||
import Disk from "./disk";
|
||||
import Cpu from "./cpu";
|
||||
import Memory from "./memory";
|
||||
import CpuTemp from "./cputemp";
|
||||
import Uptime from "./uptime";
|
||||
import Disk from "./disk";
|
||||
import Memory from "./memory";
|
||||
import Network from "./network";
|
||||
import Uptime from "./uptime";
|
||||
|
||||
export default function Resources({ options }) {
|
||||
const { expanded, units, diskUnits, tempmin, tempmax } = options;
|
||||
|
@ -1,9 +1,9 @@
|
||||
import useSWR from "swr";
|
||||
import { FaRegClock } from "react-icons/fa";
|
||||
import { useTranslation } from "next-i18next";
|
||||
import { FaRegClock } from "react-icons/fa";
|
||||
import useSWR from "swr";
|
||||
|
||||
import Resource from "../widget/resource";
|
||||
import Error from "../widget/error";
|
||||
import Resource from "../widget/resource";
|
||||
|
||||
export default function Uptime({ refresh = 1500 }) {
|
||||
const { t } = useTranslation();
|
||||
|
@ -1,10 +1,10 @@
|
||||
import { useState, useEffect, Fragment } from "react";
|
||||
import { useTranslation } from "next-i18next";
|
||||
import { FiSearch } from "react-icons/fi";
|
||||
import { SiDuckduckgo, SiGoogle, SiBaidu, SiBrave } from "react-icons/si";
|
||||
import { BiLogoBing } from "react-icons/bi";
|
||||
import { Listbox, Transition, Combobox } from "@headlessui/react";
|
||||
import { Combobox, Listbox, Transition } from "@headlessui/react";
|
||||
import classNames from "classnames";
|
||||
import { useTranslation } from "next-i18next";
|
||||
import { Fragment, useEffect, useState } from "react";
|
||||
import { BiLogoBing } from "react-icons/bi";
|
||||
import { FiSearch } from "react-icons/fi";
|
||||
import { SiBaidu, SiBrave, SiDuckduckgo, SiGoogle } from "react-icons/si";
|
||||
|
||||
import ContainerForm from "../widget/container_form";
|
||||
import Raw from "../widget/raw";
|
||||
|
@ -1,13 +1,13 @@
|
||||
import useSWR from "swr";
|
||||
import { useState } from "react";
|
||||
import { useTranslation } from "next-i18next";
|
||||
import { useState } from "react";
|
||||
import { FaChartLine } from "react-icons/fa6";
|
||||
import useSWR from "swr";
|
||||
|
||||
import Error from "../widget/error";
|
||||
import Container from "../widget/container";
|
||||
import Error from "../widget/error";
|
||||
import PrimaryText from "../widget/primary_text";
|
||||
import WidgetIcon from "../widget/widget_icon";
|
||||
import Raw from "../widget/raw";
|
||||
import WidgetIcon from "../widget/widget_icon";
|
||||
|
||||
export default function Widget({ options }) {
|
||||
const { t, i18n } = useTranslation();
|
||||
|
@ -1,13 +1,13 @@
|
||||
import { BiError, BiWifi, BiCheckCircle, BiXCircle, BiNetworkChart } from "react-icons/bi";
|
||||
import { MdSettingsEthernet } from "react-icons/md";
|
||||
import { useTranslation } from "next-i18next";
|
||||
import { BiCheckCircle, BiError, BiNetworkChart, BiWifi, BiXCircle } from "react-icons/bi";
|
||||
import { MdSettingsEthernet } from "react-icons/md";
|
||||
import { SiUbiquiti } from "react-icons/si";
|
||||
|
||||
import Error from "../widget/error";
|
||||
import Container from "../widget/container";
|
||||
import Error from "../widget/error";
|
||||
import PrimaryText from "../widget/primary_text";
|
||||
import Raw from "../widget/raw";
|
||||
import WidgetIcon from "../widget/widget_icon";
|
||||
import PrimaryText from "../widget/primary_text";
|
||||
|
||||
import useWidgetAPI from "utils/proxy/use-widget-api";
|
||||
|
||||
|
@ -1,16 +1,16 @@
|
||||
import useSWR from "swr";
|
||||
import { useState } from "react";
|
||||
import { WiCloudDown } from "react-icons/wi";
|
||||
import { MdLocationDisabled, MdLocationSearching } from "react-icons/md";
|
||||
import { useTranslation } from "next-i18next";
|
||||
import { useState } from "react";
|
||||
import { MdLocationDisabled, MdLocationSearching } from "react-icons/md";
|
||||
import { WiCloudDown } from "react-icons/wi";
|
||||
import useSWR from "swr";
|
||||
|
||||
import Error from "../widget/error";
|
||||
import mapIcon from "../../../utils/weather/condition-map";
|
||||
import Container from "../widget/container";
|
||||
import ContainerButton from "../widget/container_button";
|
||||
import Error from "../widget/error";
|
||||
import PrimaryText from "../widget/primary_text";
|
||||
import SecondaryText from "../widget/secondary_text";
|
||||
import WidgetIcon from "../widget/widget_icon";
|
||||
import ContainerButton from "../widget/container_button";
|
||||
import mapIcon from "../../../utils/weather/condition-map";
|
||||
|
||||
function Widget({ options }) {
|
||||
const { t, i18n } = useTranslation();
|
||||
|
@ -1,5 +1,5 @@
|
||||
import dynamic from "next/dynamic";
|
||||
import ErrorBoundary from "components/errorboundry";
|
||||
import dynamic from "next/dynamic";
|
||||
|
||||
const widgetMappings = {
|
||||
weatherapi: dynamic(() => import("components/widgets/weather/weather")),
|
||||
|
@ -2,10 +2,10 @@ import classNames from "classnames";
|
||||
import { useContext } from "react";
|
||||
import { SettingsContext } from "utils/contexts/settings";
|
||||
|
||||
import WidgetIcon from "./widget_icon";
|
||||
import PrimaryText from "./primary_text";
|
||||
import SecondaryText from "./secondary_text";
|
||||
import Raw from "./raw";
|
||||
import SecondaryText from "./secondary_text";
|
||||
import WidgetIcon from "./widget_icon";
|
||||
|
||||
export function getAllClasses(options, additionalClassNames = "") {
|
||||
if (options?.style?.header === "boxedWidgets") {
|
||||
|
@ -1,4 +1,4 @@
|
||||
import { getAllClasses, getInnerBlock, getBottomBlock } from "./container";
|
||||
import { getAllClasses, getBottomBlock, getInnerBlock } from "./container";
|
||||
|
||||
export default function ContainerButton({ children = [], options, additionalClassNames = "", callback }) {
|
||||
return (
|
||||
|
@ -1,4 +1,4 @@
|
||||
import { getAllClasses, getInnerBlock, getBottomBlock } from "./container";
|
||||
import { getAllClasses, getBottomBlock, getInnerBlock } from "./container";
|
||||
|
||||
export default function ContainerForm({ children = [], options, additionalClassNames = "", callback }) {
|
||||
return (
|
||||
|
@ -1,4 +1,4 @@
|
||||
import { getAllClasses, getInnerBlock, getBottomBlock } from "./container";
|
||||
import { getAllClasses, getBottomBlock, getInnerBlock } from "./container";
|
||||
|
||||
export default function ContainerLink({ children = [], options, additionalClassNames = "", target }) {
|
||||
return (
|
||||
|
@ -1,4 +1,4 @@
|
||||
import { useTranslation } from "react-i18next";
|
||||
import { useTranslation } from "next-i18next";
|
||||
import { BiError } from "react-icons/bi";
|
||||
|
||||
import Container from "./container";
|
||||
|
@ -1,8 +1,8 @@
|
||||
import classNames from "classnames";
|
||||
|
||||
import ContainerLink from "./container_link";
|
||||
import Resource from "./resource";
|
||||
import Raw from "./raw";
|
||||
import Resource from "./resource";
|
||||
import WidgetLabel from "./widget_label";
|
||||
|
||||
export default function Resources({ options, children, target, additionalClassNames }) {
|
||||
|
@ -1,14 +1,14 @@
|
||||
/* eslint-disable react/jsx-props-no-spreading */
|
||||
import { SWRConfig } from "swr";
|
||||
import { appWithTranslation } from "next-i18next";
|
||||
import Head from "next/head";
|
||||
import "styles/globals.css";
|
||||
import "styles/theme.css";
|
||||
import "styles/manrope.css";
|
||||
import "styles/theme.css";
|
||||
import { SWRConfig } from "swr";
|
||||
import { ColorProvider } from "utils/contexts/color";
|
||||
import { ThemeProvider } from "utils/contexts/theme";
|
||||
import { SettingsProvider } from "utils/contexts/settings";
|
||||
import { TabProvider } from "utils/contexts/tab";
|
||||
import { ThemeProvider } from "utils/contexts/theme";
|
||||
|
||||
import nextI18nextConfig from "../../next-i18next.config";
|
||||
|
||||
|
@ -1,4 +1,4 @@
|
||||
import { Html, Head, Main, NextScript } from "next/document";
|
||||
import { Head, Html, Main, NextScript } from "next/document";
|
||||
|
||||
export default function Document() {
|
||||
return (
|
||||
|
@ -1,5 +1,5 @@
|
||||
import path from "path";
|
||||
import fs from "fs";
|
||||
import path from "path";
|
||||
|
||||
import { CONF_DIR } from "utils/config/config";
|
||||
import createLogger from "utils/logger";
|
||||
|
@ -1,6 +1,6 @@
|
||||
import { join } from "path";
|
||||
import { createHash } from "crypto";
|
||||
import { readFileSync } from "fs";
|
||||
import { join } from "path";
|
||||
|
||||
import checkAndCopyConfig, { CONF_DIR } from "utils/config/config";
|
||||
|
||||
|
@ -53,9 +53,11 @@ export default async function handler(req, res) {
|
||||
return;
|
||||
}
|
||||
|
||||
const podNames = new Set();
|
||||
let cpuLimit = 0;
|
||||
let memLimit = 0;
|
||||
pods.forEach((pod) => {
|
||||
podNames.add(pod.metadata.name);
|
||||
pod.spec.containers.forEach((container) => {
|
||||
if (container?.resources?.limits?.cpu) {
|
||||
cpuLimit += parseCpu(container?.resources?.limits?.cpu);
|
||||
@ -66,12 +68,8 @@ export default async function handler(req, res) {
|
||||
});
|
||||
});
|
||||
|
||||
const podStatsList = await Promise.all(
|
||||
pods.map(async (pod) => {
|
||||
let depMem = 0;
|
||||
let depCpu = 0;
|
||||
const podMetrics = await metricsApi
|
||||
.getPodMetrics(namespace, pod.items)
|
||||
const namespaceMetrics = await metricsApi
|
||||
.getPodMetrics(namespace)
|
||||
.then((response) => response.items)
|
||||
.catch((err) => {
|
||||
// 404 generally means that the metrics have not been populated yet
|
||||
@ -80,28 +78,22 @@ export default async function handler(req, res) {
|
||||
}
|
||||
return null;
|
||||
});
|
||||
if (podMetrics) {
|
||||
podMetrics.forEach((metrics) => {
|
||||
metrics.containers.forEach((container) => {
|
||||
depMem += parseMemory(container.usage.memory);
|
||||
depCpu += parseCpu(container.usage.cpu);
|
||||
});
|
||||
});
|
||||
}
|
||||
return {
|
||||
mem: depMem,
|
||||
cpu: depCpu,
|
||||
};
|
||||
}),
|
||||
);
|
||||
|
||||
const stats = {
|
||||
mem: 0,
|
||||
cpu: 0,
|
||||
};
|
||||
podStatsList.forEach((podStat) => {
|
||||
stats.mem += podStat.mem;
|
||||
stats.cpu += podStat.cpu;
|
||||
|
||||
if (namespaceMetrics) {
|
||||
const podMetrics = namespaceMetrics.filter((item) => podNames.has(item.metadata.name));
|
||||
podMetrics.forEach((metrics) => {
|
||||
metrics.containers.forEach((container) => {
|
||||
stats.mem += parseMemory(container.usage.memory);
|
||||
stats.cpu += parseCpu(container.usage.cpu);
|
||||
});
|
||||
});
|
||||
}
|
||||
|
||||
stats.cpuLimit = cpuLimit;
|
||||
stats.memLimit = memLimit;
|
||||
stats.cpuUsage = cpuLimit ? 100 * (stats.cpu / cpuLimit) : 0;
|
||||
|
@ -1,5 +1,5 @@
|
||||
import { cachedRequest } from "utils/proxy/http";
|
||||
import createLogger from "utils/logger";
|
||||
import { cachedRequest } from "utils/proxy/http";
|
||||
|
||||
const logger = createLogger("releases");
|
||||
|
||||
|
@ -1,8 +1,8 @@
|
||||
import { searchProviders } from "components/widgets/search/search";
|
||||
|
||||
import { getSettings } from "utils/config/config";
|
||||
import { cachedRequest } from "utils/proxy/http";
|
||||
import { widgetsFromConfig } from "utils/config/widget-helpers";
|
||||
import { cachedRequest } from "utils/proxy/http";
|
||||
|
||||
export default async function handler(req, res) {
|
||||
const { query, providerName } = req.query;
|
||||
|
@ -1,9 +1,9 @@
|
||||
import { formatApiCall } from "utils/proxy/api-helpers";
|
||||
import createLogger from "utils/logger";
|
||||
import genericProxyHandler from "utils/proxy/handlers/generic";
|
||||
import widgets from "widgets/widgets";
|
||||
import calendarProxyHandler from "widgets/calendar/proxy";
|
||||
import getServiceWidget from "utils/config/service-helpers";
|
||||
import createLogger from "utils/logger";
|
||||
import { formatApiCall } from "utils/proxy/api-helpers";
|
||||
import genericProxyHandler from "utils/proxy/handlers/generic";
|
||||
import calendarProxyHandler from "widgets/calendar/proxy";
|
||||
import widgets from "widgets/widgets";
|
||||
|
||||
const logger = createLogger("servicesProxy");
|
||||
|
||||
|
@ -1,6 +1,6 @@
|
||||
import { httpProxy } from "utils/proxy/http";
|
||||
import createLogger from "utils/logger";
|
||||
import { getPrivateWidgetOptions } from "utils/config/widget-helpers";
|
||||
import createLogger from "utils/logger";
|
||||
import { httpProxy } from "utils/proxy/http";
|
||||
|
||||
const logger = createLogger("glances");
|
||||
|
||||
|
@ -1,6 +1,6 @@
|
||||
import { httpProxy } from "../../../utils/proxy/http";
|
||||
import createLogger from "../../../utils/logger";
|
||||
import { getSettings } from "../../../utils/config/config";
|
||||
import createLogger from "../../../utils/logger";
|
||||
import { httpProxy } from "../../../utils/proxy/http";
|
||||
|
||||
const logger = createLogger("longhorn");
|
||||
|
||||
|
@ -1,6 +1,6 @@
|
||||
import { cachedRequest } from "utils/proxy/http";
|
||||
import { getSettings } from "utils/config/config";
|
||||
import { getPrivateWidgetOptions } from "utils/config/widget-helpers";
|
||||
import { cachedRequest } from "utils/proxy/http";
|
||||
|
||||
export default async function handler(req, res) {
|
||||
const { latitude, longitude, units, provider, cache, lang, index } = req.query;
|
||||
|
@ -1,6 +1,6 @@
|
||||
import { cachedRequest } from "utils/proxy/http";
|
||||
import { getSettings } from "utils/config/config";
|
||||
import createLogger from "utils/logger";
|
||||
import { cachedRequest } from "utils/proxy/http";
|
||||
|
||||
const logger = createLogger("stocks");
|
||||
|
||||
|
@ -1,6 +1,6 @@
|
||||
import { cachedRequest } from "utils/proxy/http";
|
||||
import { getSettings } from "utils/config/config";
|
||||
import { getPrivateWidgetOptions } from "utils/config/widget-helpers";
|
||||
import { cachedRequest } from "utils/proxy/http";
|
||||
|
||||
export default async function handler(req, res) {
|
||||
const { latitude, longitude, provider, cache, lang, index } = req.query;
|
||||
|
@ -1,31 +1,31 @@
|
||||
/* eslint-disable react/no-array-index-key */
|
||||
import useSWR, { SWRConfig } from "swr";
|
||||
import Head from "next/head";
|
||||
import Script from "next/script";
|
||||
import dynamic from "next/dynamic";
|
||||
import classNames from "classnames";
|
||||
import { useTranslation } from "next-i18next";
|
||||
import { useEffect, useContext, useState, useMemo } from "react";
|
||||
import { BiError } from "react-icons/bi";
|
||||
import { serverSideTranslations } from "next-i18next/serverSideTranslations";
|
||||
import { useRouter } from "next/router";
|
||||
import Tab, { slugifyAndEncode } from "components/tab";
|
||||
import ServicesGroup from "components/services/group";
|
||||
import BookmarksGroup from "components/bookmarks/group";
|
||||
import Widget from "components/widgets/widget";
|
||||
import Revalidate from "components/toggles/revalidate";
|
||||
import { ColorContext } from "utils/contexts/color";
|
||||
import { ThemeContext } from "utils/contexts/theme";
|
||||
import { SettingsContext } from "utils/contexts/settings";
|
||||
import { TabContext } from "utils/contexts/tab";
|
||||
import ErrorBoundary from "components/errorboundry";
|
||||
import QuickLaunch from "components/quicklaunch";
|
||||
import ServicesGroup from "components/services/group";
|
||||
import Tab, { slugifyAndEncode } from "components/tab";
|
||||
import Revalidate from "components/toggles/revalidate";
|
||||
import Widget from "components/widgets/widget";
|
||||
import { useTranslation } from "next-i18next";
|
||||
import { serverSideTranslations } from "next-i18next/serverSideTranslations";
|
||||
import dynamic from "next/dynamic";
|
||||
import Head from "next/head";
|
||||
import { useRouter } from "next/router";
|
||||
import Script from "next/script";
|
||||
import { useContext, useEffect, useMemo, useState } from "react";
|
||||
import { BiError } from "react-icons/bi";
|
||||
import useSWR, { SWRConfig } from "swr";
|
||||
import { ColorContext } from "utils/contexts/color";
|
||||
import { SettingsContext } from "utils/contexts/settings";
|
||||
import { TabContext } from "utils/contexts/tab";
|
||||
import { ThemeContext } from "utils/contexts/theme";
|
||||
|
||||
import { bookmarksResponse, servicesResponse, widgetsResponse } from "utils/config/api-response";
|
||||
import themes from "utils/styles/themes";
|
||||
import { getSettings } from "utils/config/config";
|
||||
import useWindowFocus from "utils/hooks/window-focus";
|
||||
import createLogger from "utils/logger";
|
||||
import themes from "utils/styles/themes";
|
||||
|
||||
const ThemeToggle = dynamic(() => import("components/toggles/theme"), {
|
||||
ssr: false,
|
||||
|
@ -4,13 +4,13 @@ import path from "path";
|
||||
|
||||
import yaml from "js-yaml";
|
||||
|
||||
import checkAndCopyConfig, { getSettings, substituteEnvironmentVars, CONF_DIR } from "utils/config/config";
|
||||
import checkAndCopyConfig, { CONF_DIR, getSettings, substituteEnvironmentVars } from "utils/config/config";
|
||||
import {
|
||||
cleanServiceGroups,
|
||||
findGroupByName,
|
||||
servicesFromConfig,
|
||||
servicesFromDocker,
|
||||
cleanServiceGroups,
|
||||
servicesFromKubernetes,
|
||||
findGroupByName,
|
||||
} from "utils/config/service-helpers";
|
||||
import { cleanWidgetGroups, widgetsFromConfig } from "utils/config/widget-helpers";
|
||||
|
||||
|
@ -1,9 +1,9 @@
|
||||
/* eslint-disable no-console */
|
||||
import { join } from "path";
|
||||
import { copyFileSync, existsSync, mkdirSync, readFileSync } from "fs";
|
||||
import { join } from "path";
|
||||
|
||||
import cache from "memory-cache";
|
||||
import yaml from "js-yaml";
|
||||
import cache from "memory-cache";
|
||||
|
||||
const cacheKey = "homepageEnvironmentVariables";
|
||||
const homepageVarPrefix = "HOMEPAGE_VAR_";
|
||||
|
@ -1,5 +1,5 @@
|
||||
import path from "path";
|
||||
import { readFileSync } from "fs";
|
||||
import path from "path";
|
||||
|
||||
import yaml from "js-yaml";
|
||||
|
||||
|
@ -1,8 +1,8 @@
|
||||
import path from "path";
|
||||
import { readFileSync } from "fs";
|
||||
import path from "path";
|
||||
|
||||
import { ApiextensionsV1Api, KubeConfig } from "@kubernetes/client-node";
|
||||
import yaml from "js-yaml";
|
||||
import { KubeConfig, ApiextensionsV1Api } from "@kubernetes/client-node";
|
||||
|
||||
import checkAndCopyConfig, { CONF_DIR, substituteEnvironmentVars } from "utils/config/config";
|
||||
|
||||
|
@ -1,15 +1,15 @@
|
||||
import { promises as fs } from "fs";
|
||||
import path from "path";
|
||||
|
||||
import yaml from "js-yaml";
|
||||
import Docker from "dockerode";
|
||||
import yaml from "js-yaml";
|
||||
|
||||
import createLogger from "utils/logger";
|
||||
import checkAndCopyConfig, { CONF_DIR, getSettings, substituteEnvironmentVars } from "utils/config/config";
|
||||
import getDockerArguments from "utils/config/docker";
|
||||
import kubernetes from "utils/kubernetes/export";
|
||||
import { getKubeConfig } from "utils/config/kubernetes";
|
||||
import * as shvl from "utils/config/shvl";
|
||||
import kubernetes from "utils/kubernetes/export";
|
||||
import createLogger from "utils/logger";
|
||||
|
||||
const logger = createLogger("service-helpers");
|
||||
|
||||
@ -362,6 +362,9 @@ export function cleanServiceGroups(groups) {
|
||||
// proxmox
|
||||
node,
|
||||
|
||||
// proxmoxbackupserver
|
||||
datastore,
|
||||
|
||||
// speedtest
|
||||
bitratePrecision,
|
||||
|
||||
@ -437,6 +440,9 @@ export function cleanServiceGroups(groups) {
|
||||
if (type === "proxmox") {
|
||||
if (node) widget.node = node;
|
||||
}
|
||||
if (type === "proxmoxbackupserver") {
|
||||
if (datastore) widget.datastore = datastore;
|
||||
}
|
||||
if (type === "kubernetes") {
|
||||
if (namespace) widget.namespace = namespace;
|
||||
if (app) widget.app = app;
|
||||
|
@ -1,4 +1,4 @@
|
||||
import { createContext, useState, useEffect, useMemo } from "react";
|
||||
import { createContext, useEffect, useMemo, useState } from "react";
|
||||
|
||||
let lastColor = false;
|
||||
|
||||
|
@ -1,4 +1,4 @@
|
||||
import { createContext, useState, useMemo } from "react";
|
||||
import { createContext, useMemo, useState } from "react";
|
||||
|
||||
export const SettingsContext = createContext();
|
||||
|
||||
|
@ -1,4 +1,4 @@
|
||||
import { createContext, useState, useMemo } from "react";
|
||||
import { createContext, useMemo, useState } from "react";
|
||||
|
||||
export const TabContext = createContext();
|
||||
|
||||
|
@ -1,4 +1,4 @@
|
||||
import { createContext, useState, useEffect, useMemo } from "react";
|
||||
import { createContext, useEffect, useMemo, useState } from "react";
|
||||
|
||||
const getInitialTheme = () => {
|
||||
if (typeof window !== "undefined" && window.localStorage) {
|
||||
|
@ -1,4 +1,4 @@
|
||||
import { useState, useEffect } from "react";
|
||||
import { useEffect, useState } from "react";
|
||||
|
||||
const hasFocus = () => typeof document !== "undefined" && document.hasFocus();
|
||||
|
||||
|
@ -1,7 +1,7 @@
|
||||
import listIngress from "utils/kubernetes/ingress-list";
|
||||
import listTraefikIngress from "utils/kubernetes/traefik-list";
|
||||
import listHttpRoute from "utils/kubernetes/httproute-list";
|
||||
import { isDiscoverable, constructedServiceFromResource } from "utils/kubernetes/resource-helpers";
|
||||
import listIngress from "utils/kubernetes/ingress-list";
|
||||
import { constructedServiceFromResource, isDiscoverable } from "utils/kubernetes/resource-helpers";
|
||||
import listTraefikIngress from "utils/kubernetes/traefik-list";
|
||||
|
||||
const kubernetes = {
|
||||
listIngress,
|
||||
|
@ -1,6 +1,6 @@
|
||||
import { NetworkingV1Api } from "@kubernetes/client-node";
|
||||
|
||||
import { getKubernetes, getKubeConfig } from "utils/config/kubernetes";
|
||||
import { getKubeConfig, getKubernetes } from "utils/config/kubernetes";
|
||||
import createLogger from "utils/logger";
|
||||
|
||||
const logger = createLogger("ingress-list");
|
||||
|
@ -1,15 +1,15 @@
|
||||
import { CustomObjectsApi } from "@kubernetes/client-node";
|
||||
|
||||
import { substituteEnvironmentVars } from "utils/config/config";
|
||||
import {
|
||||
getKubeConfig,
|
||||
ANNOTATION_BASE,
|
||||
ANNOTATION_WIDGET_BASE,
|
||||
getKubeConfig,
|
||||
HTTPROUTE_API_GROUP,
|
||||
HTTPROUTE_API_VERSION,
|
||||
} from "utils/config/kubernetes";
|
||||
import { substituteEnvironmentVars } from "utils/config/config";
|
||||
import createLogger from "utils/logger";
|
||||
import * as shvl from "utils/config/shvl";
|
||||
import createLogger from "utils/logger";
|
||||
|
||||
const logger = createLogger("resource-helpers");
|
||||
const kc = getKubeConfig();
|
||||
|
@ -1,6 +1,6 @@
|
||||
import { CustomObjectsApi } from "@kubernetes/client-node";
|
||||
|
||||
import { getKubernetes, getKubeConfig, checkCRD, ANNOTATION_BASE } from "utils/config/kubernetes";
|
||||
import { ANNOTATION_BASE, checkCRD, getKubeConfig, getKubernetes } from "utils/config/kubernetes";
|
||||
import createLogger from "utils/logger";
|
||||
|
||||
const logger = createLogger("traefik-list");
|
||||
|
@ -1,9 +1,9 @@
|
||||
import getServiceWidget from "utils/config/service-helpers";
|
||||
import { formatApiCall, sanitizeErrorURL } from "utils/proxy/api-helpers";
|
||||
import validateWidgetData from "utils/proxy/validate-widget-data";
|
||||
import { httpProxy } from "utils/proxy/http";
|
||||
import createLogger from "utils/logger";
|
||||
import { getSettings } from "utils/config/config";
|
||||
import getServiceWidget from "utils/config/service-helpers";
|
||||
import createLogger from "utils/logger";
|
||||
import { formatApiCall, sanitizeErrorURL } from "utils/proxy/api-helpers";
|
||||
import { httpProxy } from "utils/proxy/http";
|
||||
import validateWidgetData from "utils/proxy/validate-widget-data";
|
||||
import widgets from "widgets/widgets";
|
||||
|
||||
const logger = createLogger("credentialedProxyHandler");
|
||||
@ -42,6 +42,7 @@ export default async function credentialedProxyHandler(req, res, map) {
|
||||
"ghostfolio",
|
||||
"headscale",
|
||||
"hoarder",
|
||||
"karakeep",
|
||||
"linkwarden",
|
||||
"mealie",
|
||||
"netalertx",
|
||||
|
@ -1,8 +1,8 @@
|
||||
import getServiceWidget from "utils/config/service-helpers";
|
||||
import { formatApiCall, sanitizeErrorURL } from "utils/proxy/api-helpers";
|
||||
import validateWidgetData from "utils/proxy/validate-widget-data";
|
||||
import { httpProxy } from "utils/proxy/http";
|
||||
import createLogger from "utils/logger";
|
||||
import { formatApiCall, sanitizeErrorURL } from "utils/proxy/api-helpers";
|
||||
import { httpProxy } from "utils/proxy/http";
|
||||
import validateWidgetData from "utils/proxy/validate-widget-data";
|
||||
import widgets from "widgets/widgets";
|
||||
|
||||
const logger = createLogger("genericProxyHandler");
|
||||
|
@ -1,9 +1,9 @@
|
||||
import { JSONRPCClient, JSONRPCErrorException } from "json-rpc-2.0";
|
||||
|
||||
import { formatApiCall } from "utils/proxy/api-helpers";
|
||||
import { httpProxy } from "utils/proxy/http";
|
||||
import getServiceWidget from "utils/config/service-helpers";
|
||||
import createLogger from "utils/logger";
|
||||
import { formatApiCall } from "utils/proxy/api-helpers";
|
||||
import { httpProxy } from "utils/proxy/http";
|
||||
import widgets from "widgets/widgets";
|
||||
|
||||
const logger = createLogger("jsonrpcProxyHandler");
|
||||
|
@ -1,9 +1,9 @@
|
||||
import cache from "memory-cache";
|
||||
|
||||
import getServiceWidget from "utils/config/service-helpers";
|
||||
import createLogger from "utils/logger";
|
||||
import { asJson, formatApiCall } from "utils/proxy/api-helpers";
|
||||
import { httpProxy } from "utils/proxy/http";
|
||||
import createLogger from "utils/logger";
|
||||
import widgets from "widgets/widgets";
|
||||
|
||||
const INFO_ENDPOINT = "{url}/webapi/query.cgi?api=SYNO.API.Info&version=1&method=query";
|
||||
|
@ -5,8 +5,8 @@ import { createUnzip, constants as zlibConstants } from "node:zlib";
|
||||
import { http, https } from "follow-redirects";
|
||||
import cache from "memory-cache";
|
||||
|
||||
import { addCookieToJar, setCookieHeader } from "./cookie-jar";
|
||||
import { sanitizeErrorURL } from "./api-helpers";
|
||||
import { addCookieToJar, setCookieHeader } from "./cookie-jar";
|
||||
|
||||
import createLogger from "utils/logger";
|
||||
|
||||
|
@ -1,6 +1,6 @@
|
||||
/* eslint-disable no-console */
|
||||
import widgets from "widgets/widgets";
|
||||
import createLogger from "utils/logger";
|
||||
import widgets from "widgets/widgets";
|
||||
|
||||
const logger = createLogger("validateWidgetData");
|
||||
|
||||
|
@ -1,6 +1,6 @@
|
||||
import { useTranslation } from "next-i18next";
|
||||
import Container from "components/services/widget/container";
|
||||
import Block from "components/services/widget/block";
|
||||
import Container from "components/services/widget/container";
|
||||
import { useTranslation } from "next-i18next";
|
||||
|
||||
import useWidgetAPI from "utils/proxy/use-widget-api";
|
||||
|
||||
|
@ -1,5 +1,5 @@
|
||||
import Container from "components/services/widget/container";
|
||||
import Block from "components/services/widget/block";
|
||||
import Container from "components/services/widget/container";
|
||||
|
||||
import useWidgetAPI from "utils/proxy/use-widget-api";
|
||||
|
||||
|
@ -1,5 +1,5 @@
|
||||
import net from "node:net";
|
||||
import { Buffer } from "node:buffer";
|
||||
import net from "node:net";
|
||||
|
||||
import getServiceWidget from "utils/config/service-helpers";
|
||||
import createLogger from "utils/logger";
|
||||
|
@ -1,5 +1,5 @@
|
||||
import Container from "components/services/widget/container";
|
||||
import Block from "components/services/widget/block";
|
||||
import Container from "components/services/widget/container";
|
||||
|
||||
import useWidgetAPI from "utils/proxy/use-widget-api";
|
||||
|
||||
|
@ -1,6 +1,6 @@
|
||||
import { useTranslation } from "next-i18next";
|
||||
import Container from "components/services/widget/container";
|
||||
import Block from "components/services/widget/block";
|
||||
import Container from "components/services/widget/container";
|
||||
import { useTranslation } from "next-i18next";
|
||||
|
||||
import useWidgetAPI from "utils/proxy/use-widget-api";
|
||||
|
||||
|
@ -1,6 +1,6 @@
|
||||
import { useTranslation } from "next-i18next";
|
||||
import Container from "components/services/widget/container";
|
||||
import Block from "components/services/widget/block";
|
||||
import Container from "components/services/widget/container";
|
||||
import { useTranslation } from "next-i18next";
|
||||
|
||||
import useWidgetAPI from "utils/proxy/use-widget-api";
|
||||
|
||||
|
@ -1,7 +1,7 @@
|
||||
import { httpProxy } from "utils/proxy/http";
|
||||
import { formatApiCall } from "utils/proxy/api-helpers";
|
||||
import getServiceWidget from "utils/config/service-helpers";
|
||||
import createLogger from "utils/logger";
|
||||
import { formatApiCall } from "utils/proxy/api-helpers";
|
||||
import { httpProxy } from "utils/proxy/http";
|
||||
import widgets from "widgets/widgets";
|
||||
|
||||
const proxyName = "audiobookshelfProxyHandler";
|
||||
|
@ -1,6 +1,6 @@
|
||||
import { useTranslation } from "next-i18next";
|
||||
import Container from "components/services/widget/container";
|
||||
import Block from "components/services/widget/block";
|
||||
import Container from "components/services/widget/container";
|
||||
import { useTranslation } from "next-i18next";
|
||||
|
||||
import useWidgetAPI from "utils/proxy/use-widget-api";
|
||||
|
||||
|
Some files were not shown because too many files have changed in this diff Show More
Loading…
x
Reference in New Issue
Block a user