Skip to content
Merged
Show file tree
Hide file tree
Changes from 3 commits
Commits
Show all changes
27 commits
Select commit Hold shift + click to select a range
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
16 changes: 16 additions & 0 deletions frontend/src/components/application-details/breadcrumb.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,16 @@
import type { Application } from "@/lib/applications"
import { Link } from "@tanstack/react-router"
import { ArrowLeft } from "lucide-react"

export function Breadcrumb({ app }: { app: Application }) {
return (
<div className="flex items-center gap-2 text-sm text-muted-foreground">
<Link to="/applications" className="hover:text-foreground flex items-center gap-1">
<ArrowLeft className="h-4 w-4" />
Applications
</Link>
<span>/</span>
<span className="text-foreground">{app.name}</span>
</div>
)
}
63 changes: 63 additions & 0 deletions frontend/src/components/application-details/header.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,63 @@
import { Box, MoreVertical, RefreshCw, Settings } from "lucide-react";
import { StatusBadge } from "@/components/status-badge";
import type { Application } from "@/lib/applications";
import { Button } from "@/components/ui/button";
import { Link } from "@tanstack/react-router";
import { DropdownMenu, DropdownMenuContent, DropdownMenuItem, DropdownMenuTrigger } from "@/components/ui/dropdown-menu";
import { useState } from "react";

interface Props {
app: Application;
id: string;
}

export function Header({ app, id }: Props) {
const [syncing, setSyncing] = useState(false)

const handleSync = async () => {
setSyncing(true)
await new Promise((resolve) => setTimeout(resolve, 2000))
setSyncing(false)
}
return (
<div className="flex flex-col lg:flex-row lg:items-start justify-between gap-4">
<div className="flex items-start gap-4">
<div className="h-14 w-14 rounded-xl bg-primary/10 flex items-center justify-center">
<Box className="h-7 w-7 text-primary" />
</div>
<div>
<div className="flex items-center gap-3">
<h1 className="text-2xl font-bold">{app.name}</h1>
<StatusBadge status={app.syncStatus} />
<StatusBadge status={app.healthStatus} />
</div>
<p className="text-muted-foreground mt-1">{app.project}</p>
</div>
</div>
<div className="flex items-center gap-2">
<Button variant="outline" onClick={handleSync} disabled={syncing}>
<RefreshCw className={`mr-2 h-4 w-4 ${syncing ? "animate-spin" : ""}`} />
{syncing ? "Syncing..." : "Sync"}
</Button>
<Button variant="outline" asChild>
<Link to="/applications/$id/settings" params={{ id: id }}>
<Settings className="mr-2 h-4 w-4" />
Settings
</Link>
</Button>
<DropdownMenu>
<DropdownMenuTrigger asChild>
<Button variant="outline" size="icon">
<MoreVertical className="h-4 w-4" />
</Button>
</DropdownMenuTrigger>
<DropdownMenuContent align="end">
<DropdownMenuItem>Hard Refresh</DropdownMenuItem>
<DropdownMenuItem>Rollback</DropdownMenuItem>
<DropdownMenuItem className="text-destructive">Delete</DropdownMenuItem>
</DropdownMenuContent>
</DropdownMenu>
</div>
</div>
)
}
67 changes: 67 additions & 0 deletions frontend/src/components/application-details/info-cards.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,67 @@
import type { Application } from "@/lib/applications";
import { Clock, ExternalLink, GitBranch, GitCommit, Server } from "lucide-react";

interface InfoCardProps {
icon: React.ReactNode;
label: string;
value: string;
subValue?: string;
isLink?: boolean;
}

function InfoCard({ icon, label, value, subValue, isLink }: InfoCardProps) {
return (
<div className="bg-card border border-border rounded-lg p-4">
<div className="flex items-center gap-2 text-muted-foreground mb-2">
{icon}
<span className="text-sm">{label}</span>
</div>
<div className="font-medium truncate">
{isLink ? (
<a href="#" className="hover:text-primary flex items-center gap-1">
{value} <ExternalLink className="h-3 w-3" />
</a>
) : (
<span className={label.includes("Commit") ? "font-mono" : ""}>
{value}
</span>
)}
</div>
{subValue && (
<p className="text-sm text-muted-foreground mt-1 truncate">{subValue}</p>
)}
</div>
);
}

export function InfoCards({ app }: { app: Application }) {
return (
<div className="grid grid-cols-1 md:grid-cols-2 lg:grid-cols-4 gap-4">
<InfoCard
icon={<GitBranch className="h-4 w-4" />}
label="Repository"
value={app.repo}
subValue={app.branch}
isLink
/>
<InfoCard
icon={<GitCommit className="h-4 w-4" />}
label="Latest Commit"
value={app.commit}
subValue={app.commitMessage}
/>
<InfoCard
icon={<Server className="h-4 w-4" />}
label="Target Host"
value={app.targetHost}
subValue={app.path}
/>
<InfoCard
icon={<Clock className="h-4 w-4" />}
label="Last Sync"
value={app.lastSync}
subValue="Auto-sync enabled"
/>
</div>
);
}
172 changes: 172 additions & 0 deletions frontend/src/components/application-details/properties.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,172 @@
import { Tabs, TabsContent, TabsList, TabsTrigger } from "@/components/ui/tabs";
import type { Application } from "@/lib/applications";
import { Box, Cpu, HardDrive, MoreVertical, RotateCcw, Square, Terminal } from "lucide-react";
import { DropdownMenu, DropdownMenuContent, DropdownMenuItem, DropdownMenuTrigger } from "@/components/ui/dropdown-menu";
import { Button } from "../ui/button";
import { Link } from "@tanstack/react-router";

interface Props {
app: Application;
id: string;
}

export function Properties({ app, id }: Props) {
return (
<Tabs defaultValue="containers" className="space-y-4">
<TabsList className="bg-muted">
<TabsTrigger value="containers">Containers</TabsTrigger>
<TabsTrigger value="events">Events</TabsTrigger>
<TabsTrigger value="logs">Logs</TabsTrigger>
<TabsTrigger value="manifest">Manifest</TabsTrigger>
</TabsList>

<TabsContent value="containers" className="space-y-4">
<div className="bg-card border border-border rounded-lg overflow-hidden">
<table className="w-full">
<thead>
<tr className="border-b border-border">
<th className="text-left p-4 text-sm font-medium text-muted-foreground">Container</th>
<th className="text-left p-4 text-sm font-medium text-muted-foreground hidden sm:table-cell">
Image
</th>
<th className="text-left p-4 text-sm font-medium text-muted-foreground">Status</th>
<th className="text-left p-4 text-sm font-medium text-muted-foreground hidden md:table-cell">
CPU
</th>
<th className="text-left p-4 text-sm font-medium text-muted-foreground hidden md:table-cell">
Memory
</th>
<th className="text-left p-4 text-sm font-medium text-muted-foreground hidden lg:table-cell">
Ports
</th>
<th className="p-4"></th>
</tr>
</thead>
<tbody>
{app.containers.map((container) => (
<tr key={container.id} className="border-b border-border last:border-0 hover:bg-muted/50">
<td className="p-4">
<div className="flex items-center gap-2">
<Box className="h-4 w-4 text-primary" />
<span className="font-medium">{container.name}</span>
</div>
</td>
<td className="p-4 text-muted-foreground font-mono text-sm hidden sm:table-cell">
{container.image}
</td>
<td className="p-4">
<span className="inline-flex items-center gap-1.5 px-2 py-1 rounded-full text-xs font-medium bg-emerald-500/20 text-emerald-400 border border-emerald-500/30">
<span className="h-1.5 w-1.5 rounded-full bg-emerald-400" />
{container.status}
</span>
</td>
<td className="p-4 hidden md:table-cell">
<div className="flex items-center gap-2">
<Cpu className="h-4 w-4 text-muted-foreground" />
<span>{container.cpu}</span>
</div>
</td>
<td className="p-4 hidden md:table-cell">
<div className="flex items-center gap-2">
<HardDrive className="h-4 w-4 text-muted-foreground" />
<span>{container.memory}</span>
</div>
</td>
<td className="p-4 text-muted-foreground text-sm hidden lg:table-cell">{container.ports}</td>
<td className="p-4">
<DropdownMenu>
<DropdownMenuTrigger asChild>
<Button variant="ghost" size="icon" className="h-8 w-8">
<MoreVertical className="h-4 w-4" />
</Button>
</DropdownMenuTrigger>
<DropdownMenuContent align="end">
<DropdownMenuItem>
<Terminal className="mr-2 h-4 w-4" />
View Logs
</DropdownMenuItem>
<DropdownMenuItem>
<RotateCcw className="mr-2 h-4 w-4" />
Restart
</DropdownMenuItem>
<DropdownMenuItem>
<Square className="mr-2 h-4 w-4" />
Stop
</DropdownMenuItem>
</DropdownMenuContent>
</DropdownMenu>
</td>
</tr>
))}
</tbody>
</table>
</div>
</TabsContent>

<TabsContent value="events" className="space-y-4">
<div className="bg-card border border-border rounded-lg p-4 space-y-4">
{app.events.map((event, i) => (
<div key={i} className="flex items-start gap-3">
<div
className={`h-2 w-2 rounded-full mt-2 ${event.type === "success"
? "bg-emerald-400"
: event.type === "warning"
? "bg-amber-400"
: "bg-blue-400"
}`}
/>
<div className="flex-1">
<p className="text-sm">{event.message}</p>
<p className="text-xs text-muted-foreground mt-1">{event.time}</p>
</div>
</div>
))}
</div>
</TabsContent>

<TabsContent value="logs" className="space-y-4">
<div className="bg-card border border-border rounded-lg p-4">
<Link to="/applications/$id/logs" params={{ id: id }}>
<Button className="w-full sm:w-auto">
<Terminal className="mr-2 h-4 w-4" />
Open Log Viewer
</Button>
</Link>
</div>
</TabsContent>

<TabsContent value="manifest" className="space-y-4">
<div className="bg-card border border-border rounded-lg p-4">
<pre className="text-sm font-mono text-muted-foreground overflow-x-auto">
{`version: "3.8"
services:
api-gateway:
image: org/api-gateway:v2.1.0
ports:
- "8080:80"
environment:
- NODE_ENV=production
depends_on:
- redis-cache

redis-cache:
image: redis:7-alpine
volumes:
- redis-data:/data

nginx-proxy:
image: nginx:alpine
ports:
- "443:443"
- "80:80"
volumes:
- ./nginx.conf:/etc/nginx/nginx.conf

volumes:
redis-data:`}
</pre>
</div>
</TabsContent>
</Tabs>
)
}
17 changes: 17 additions & 0 deletions frontend/src/components/application-logs/breadcrumb.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,17 @@
import { Link } from "@tanstack/react-router";

export function Breadcrumb({ id }: { id: string }) {
return (
<div className="flex items-center gap-2 text-sm text-muted-foreground">
<Link to="/applications" className="hover:text-foreground">
Applications
</Link>
<span>/</span>
<Link to="/applications/$id" params={{ id: id }} className="hover:text-foreground">
api-gateway
</Link>
<span>/</span>
<span className="text-foreground">Logs</span>
</div>
)
}
Loading
Loading