- **refactor(main):** migrate static HTML to React components
Some checks failed
CI / build-test (push) Successful in 1m25s
CI / docker (push) Failing after 1s

- **feat(ui):** implement `AcknowledgeButton` component for acknowledging images
- **feat(stats):** add dashboard stats for total images, pending updates, and acknowledged status
- **chore(deps):** introduce `bun` dependency management and add required libraries
- **style(ui):** enhance UI with Tailwind-based components and modularity improvements
- **chore:** add drag-and-drop tag assignment using `@dnd-kit/core`
This commit is contained in:
2026-02-25 20:37:15 +01:00
parent 54478dcd4f
commit 6094edc5c8
40 changed files with 2395 additions and 99 deletions

View File

@@ -0,0 +1,41 @@
{
"nextcloud": "nextcloud",
"postgres": "postgresql",
"postgresql": "postgresql",
"redis": "redis",
"nginx": "nginx",
"mysql": "mysql",
"mariadb": "mariadb",
"grafana": "grafana",
"prometheus": "prometheus",
"traefik": "traefik",
"alpine": "alpinelinux",
"ubuntu": "ubuntu",
"debian": "debian",
"node": "nodedotjs",
"python": "python",
"golang": "go",
"elasticsearch": "elasticsearch",
"kibana": "kibana",
"portainer": "portainer",
"gitea": "gitea",
"gitlab": "gitlab",
"jellyfin": "jellyfin",
"plex": "plex",
"rabbitmq": "rabbitmq",
"mongodb": "mongodb",
"influxdb": "influxdb",
"bitwarden": "bitwarden",
"vaultwarden": "bitwarden",
"wordpress": "wordpress",
"homeassistant": "homeassistant",
"ghost": "ghost",
"caddy": "caddy",
"keycloak": "keycloak",
"adguard": "adguard",
"adguardhome": "adguard",
"wireguard": "wireguard",
"syncthing": "syncthing",
"pihole": "pihole",
"sonarqube": "sonarqube"
}

View File

@@ -0,0 +1,31 @@
import * as SimpleIcons from 'simple-icons'
import mapping from './serviceIcons.json'
// simple-icons exports named icons as siSlugInPascalCase e.g. siPostgresql
function slugToKey(slug: string): string {
return 'si' + slug.charAt(0).toUpperCase() + slug.slice(1)
}
export interface ServiceIcon {
title: string
hex: string // e.g. "4169E1" (no leading #)
path: string // SVG path data for a 24×24 viewBox
}
/**
* Given a Docker image string (e.g. "ghcr.io/linuxserver/nextcloud:28-alpine"),
* returns icon data or null for unknown images.
*/
export function getServiceIcon(image: string): ServiceIcon | null {
// Strip tag, take last path segment
const base = image.split(':')[0]
const name = base.split('/').pop() ?? base
// Normalise: lowercase, strip common Docker image suffixes
const normalised = name.toLowerCase().replace(/[_-](ce|oss|alpine|fpm|slim|lts)$/, '')
const slug = (mapping as Record<string, string>)[normalised]
if (!slug) return null
const icon = (SimpleIcons as Record<string, ServiceIcon | undefined>)[slugToKey(slug)]
return icon ?? null
}

17
frontend/src/lib/time.ts Normal file
View File

@@ -0,0 +1,17 @@
export function timeAgo(iso: string): string {
const now = Date.now()
const then = new Date(iso).getTime()
const diff = Math.floor((now - then) / 1000)
if (diff < 60) return 'just now'
if (diff < 3600) {
const m = Math.floor(diff / 60)
return `${m} minute${m !== 1 ? 's' : ''} ago`
}
if (diff < 86400) {
const h = Math.floor(diff / 3600)
return `${h} hour${h !== 1 ? 's' : ''} ago`
}
const d = Math.floor(diff / 86400)
return `${d} day${d !== 1 ? 's' : ''} ago`
}

View File

@@ -0,0 +1,6 @@
import { clsx, type ClassValue } from 'clsx'
import { twMerge } from 'tailwind-merge'
export function cn(...inputs: ClassValue[]) {
return twMerge(clsx(inputs))
}