Phase 3: env masking, image updates, port conflicts, resources, templates (0.3.0)
Backend:
- update_service: registry manifest digest check (Docker Hub/ghcr/lscr/private
v2 token auth) vs local RepoDigests; in-memory cache + background loop
- port_service: parse compose ports, check /proc/net/tcp[6] + docker bindings
- template_service + bundled templates (jellyfin/vaultwarden/uptime-kuma/
paperless-ngx/gitea) with {{VAR}} placeholders; custom templates in DB
- compose_edit set_resources (deploy.resources.limits/reservations)
- routers: images, ports, templates, editor/set-resources
- Template model; background update task wired into lifespan
Frontend:
- EnvEditor (table + raw, sensitive masking, quick-insert)
- Images page + UpdateBadge + dashboard 'updates available' banner
- PortConflictDialog pre-deploy check on Deploy
- ResourcePanel (CPU/RAM sliders) as editor Limits tab
- Templates page with per-variable instantiate form
Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
This commit is contained in:
co-authored by
Claude Opus 4.8
parent
b553c1b861
commit
22d9864436
@@ -1,9 +1,11 @@
|
||||
import { Link } from "react-router-dom";
|
||||
import { useQuery } from "@tanstack/react-query";
|
||||
import { Cpu, MemoryStick, HardDrive, Container, Clock } from "lucide-react";
|
||||
import { Cpu, MemoryStick, HardDrive, Container, Clock, ArrowUpCircle } from "lucide-react";
|
||||
import { Card, Spinner } from "@/components/ui";
|
||||
import { StackCard } from "@/components/stacks/StackCard";
|
||||
import { stacksApi } from "@/api/stacks";
|
||||
import { systemApi } from "@/api/system";
|
||||
import { imagesApi } from "@/api/images";
|
||||
import { formatBytes, formatUptime, relativeTime } from "@/lib/utils";
|
||||
import { useAuthStore } from "@/store/auth";
|
||||
import { useStackActions } from "@/hooks/useStackActions";
|
||||
@@ -15,9 +17,22 @@ export function Dashboard() {
|
||||
const stacks = useQuery({ queryKey: ["stacks"], queryFn: stacksApi.list, refetchInterval: 5000 });
|
||||
const info = useQuery({ queryKey: ["system"], queryFn: systemApi.info, refetchInterval: 5000 });
|
||||
const audit = useQuery({ queryKey: ["audit"], queryFn: () => systemApi.audit(10), refetchInterval: 10000 });
|
||||
const updates = useQuery({ queryKey: ["image-updates"], queryFn: imagesApi.updates, refetchInterval: 60000 });
|
||||
|
||||
const updateCount = Object.values(updates.data ?? {}).filter((u) => u.update_available).length;
|
||||
|
||||
return (
|
||||
<div className="space-y-6">
|
||||
{updateCount > 0 && (
|
||||
<Link
|
||||
to="/images"
|
||||
className="flex items-center gap-2 rounded-lg border border-amber-300 bg-amber-50 px-4 py-3 text-sm text-amber-700 hover:bg-amber-100 dark:border-amber-700 dark:bg-amber-900/20 dark:text-amber-300"
|
||||
>
|
||||
<ArrowUpCircle className="h-5 w-5" />
|
||||
{updateCount} image update{updateCount > 1 ? "s" : ""} available — view on the Images page.
|
||||
</Link>
|
||||
)}
|
||||
|
||||
{/* Resource bar */}
|
||||
<div className="grid grid-cols-2 gap-4 md:grid-cols-4">
|
||||
<Stat icon={<Cpu className="h-5 w-5" />} label="CPU cores" value={info.data?.cpu_cores ?? "—"} />
|
||||
|
||||
Reference in New Issue
Block a user