Initial commit: StackPilot Phase 1 (Core)

Self-hosted Docker Compose manager.
- Backend: FastAPI + docker-py + SQLite (JWT auth, file-first stacks,
  lifecycle, live status, WebSocket logs, docker-run converter, audit log)
- Frontend: React + Vite + Tailwind (login/setup, dashboard, stacks,
  stack detail, Monaco editor, dark/light theme)
- Deployment: docker-compose.yml, Dockerfiles, nginx reverse proxy

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
This commit is contained in:
menzeljandClaude Opus 4.8 committed 2026-06-07 16:04:58 +00:00
commit f732cb080b
59 files changed
+3677

No files matched your search

+138
View File
@@ -0,0 +1,138 @@
import { cn } from "@/lib/utils";
import { Loader2 } from "lucide-react";
import type { ButtonHTMLAttributes, InputHTMLAttributes, ReactNode } from "react";
import type { StackStatus } from "@/types";
export function Card({
className,
children,
}: {
className?: string;
children: ReactNode;
}) {
return (
<div
className={cn(
"rounded-xl border border-slate-200 bg-card p-4 shadow-sm",
"dark:border-slate-700 dark:bg-card-dark",
className
)}
>
{children}
</div>
);
}
type Variant = "primary" | "ghost" | "danger" | "outline";
const variantClasses: Record<Variant, string> = {
primary:
"bg-accent text-white hover:bg-sky-600 dark:bg-accent-dark dark:text-slate-900 dark:hover:bg-sky-300",
ghost:
"bg-transparent text-slate-600 hover:bg-slate-100 dark:text-slate-300 dark:hover:bg-slate-700",
danger: "bg-red-600 text-white hover:bg-red-700",
outline:
"border border-slate-300 bg-transparent text-slate-700 hover:bg-slate-100 dark:border-slate-600 dark:text-slate-200 dark:hover:bg-slate-700",
};
interface ButtonProps extends ButtonHTMLAttributes<HTMLButtonElement> {
variant?: Variant;
loading?: boolean;
}
export function Button({
variant = "primary",
loading,
className,
children,
disabled,
...props
}: ButtonProps) {
return (
<button
className={cn(
"inline-flex items-center justify-center gap-2 rounded-lg px-3 py-2 text-sm font-medium transition-colors",
"disabled:cursor-not-allowed disabled:opacity-50",
variantClasses[variant],
className
)}
disabled={disabled || loading}
{...props}
>
{loading && <Loader2 className="h-4 w-4 animate-spin" />}
{children}
</button>
);
}
export function Input({
className,
...props
}: InputHTMLAttributes<HTMLInputElement>) {
return (
<input
className={cn(
"w-full rounded-lg border border-slate-300 bg-white px-3 py-2 text-sm text-slate-900 outline-none",
"focus:border-accent focus:ring-1 focus:ring-accent",
"dark:border-slate-600 dark:bg-slate-800 dark:text-slate-100 dark:focus:border-accent-dark",
className
)}
{...props}
/>
);
}
const statusColor: Record<StackStatus, string> = {
running: "bg-green-500",
partial: "bg-yellow-500",
stopped: "bg-slate-400",
error: "bg-red-500",
updating: "bg-sky-500 animate-pulse",
unknown: "bg-slate-300",
};
export function StatusDot({ status }: { status: StackStatus }) {
return (
<span
className={cn("inline-block h-2.5 w-2.5 rounded-full", statusColor[status])}
title={status}
/>
);
}
export function Badge({
status,
children,
}: {
status?: StackStatus;
children: ReactNode;
}) {
const tone = status
? {
running: "bg-green-100 text-green-700 dark:bg-green-900/40 dark:text-green-300",
partial: "bg-yellow-100 text-yellow-700 dark:bg-yellow-900/40 dark:text-yellow-300",
stopped: "bg-slate-100 text-slate-600 dark:bg-slate-700 dark:text-slate-300",
error: "bg-red-100 text-red-700 dark:bg-red-900/40 dark:text-red-300",
updating: "bg-sky-100 text-sky-700 dark:bg-sky-900/40 dark:text-sky-300",
unknown: "bg-slate-100 text-slate-500 dark:bg-slate-700 dark:text-slate-400",
}[status]
: "bg-slate-100 text-slate-600 dark:bg-slate-700 dark:text-slate-300";
return (
<span
className={cn(
"inline-flex items-center gap-1 rounded-full px-2 py-0.5 text-xs font-medium",
tone
)}
>
{children}
</span>
);
}
export function Spinner() {
return (
<div className="flex h-full w-full items-center justify-center p-8">
<Loader2 className="h-6 w-6 animate-spin text-accent dark:text-accent-dark" />
</div>
);
}