fix(dashboard): use lightweight ping endpoint for MaintenanceBanner (fixes #3040) (#3043)

Integrated into release/v3.8.8. Applied review fixes: moved the SELECT 1 into a pingDb() db helper (no raw SQL in route, Hard Rule #5) + the 503 catch no longer leaks err.message (Hard Rule #12). Thanks @herjarsa!
This commit is contained in:
Hernan Javier Ardila Sanchez
2026-06-01 19:30:17 +02:00
committed by GitHub
parent 009ad13a91
commit fd26e601a2
1266 changed files with 153840 additions and 50869 deletions

View File

@@ -0,0 +1,82 @@
"use client";
import Link from "next/link";
import { useTranslations } from "next-intl";
import { cn } from "@/shared/utils/cn";
import type { CliConceptType } from "./CliConceptCard";
export interface CliComparisonCardProps {
currentType: CliConceptType;
}
const TYPE_HREFS: Record<CliConceptType, string> = {
code: "/dashboard/cli-code",
agent: "/dashboard/cli-agents",
acp: "/dashboard/acp-agents",
};
export default function CliComparisonCard({ currentType }: CliComparisonCardProps) {
const t = useTranslations("cliCommon");
const types: CliConceptType[] = ["code", "agent", "acp"];
return (
<div className="bg-surface border border-black/5 dark:border-white/5 rounded-lg shadow-sm p-4">
<div className="grid grid-cols-3 gap-3">
{types.map((type) => {
const isCurrent = type === currentType;
return (
<div
key={type}
className={cn(
"flex flex-col gap-2 p-3 rounded-lg",
isCurrent
? "bg-primary/10 border border-primary/30"
: "bg-black/[0.02] dark:bg-white/[0.02] border border-transparent"
)}
>
{/* Title */}
<div className="flex items-center justify-between gap-1 flex-wrap">
<span
className={cn(
"text-xs font-semibold uppercase tracking-wider",
isCurrent ? "text-primary" : "text-text-muted"
)}
>
{t(`comparison.${type}.title`)}
</span>
{isCurrent ? (
<span className="inline-flex items-center gap-0.5 px-1.5 py-0.5 text-[10px] font-medium rounded-full bg-primary/20 text-primary whitespace-nowrap">
{t("comparison.thisPage")}
</span>
) : (
<Link
href={TYPE_HREFS[type]}
className="inline-flex items-center px-1.5 py-0.5 text-[10px] font-medium rounded-full bg-black/5 dark:bg-white/5 text-text-muted hover:text-primary hover:bg-primary/10 transition-colors whitespace-nowrap"
>
Ver
</Link>
)}
</div>
{/* Description */}
<p className="text-[11px] text-text-muted leading-relaxed">
{t(`comparison.${type}.desc`)}
</p>
{/* Flow */}
<p className="text-[10px] text-text-muted font-mono">
{t(`comparison.${type}.flow`)}
</p>
{/* Examples */}
<p className="text-[10px] text-text-muted italic">
{t(`comparison.${type}.examples`)}
</p>
</div>
);
})}
</div>
</div>
);
}

View File

@@ -0,0 +1,58 @@
"use client";
import Link from "next/link";
import { useTranslations } from "next-intl";
import { cn } from "@/shared/utils/cn";
export type CliConceptType = "code" | "agent" | "acp";
export interface CliConceptCardProps {
currentType: CliConceptType;
}
const TYPE_HREFS: Record<CliConceptType, string> = {
code: "/dashboard/cli-code",
agent: "/dashboard/cli-agents",
acp: "/dashboard/acp-agents",
};
export default function CliConceptCard({ currentType }: CliConceptCardProps) {
const t = useTranslations("cliCommon");
const types: CliConceptType[] = ["code", "agent", "acp"];
return (
<div
className={cn(
"bg-surface border rounded-lg shadow-sm p-4",
"border-primary/30 bg-primary/5"
)}
>
<div className="flex flex-col gap-3">
{/* Current type — highlighted */}
<div className="flex flex-col gap-1">
<span className="text-xs font-semibold uppercase tracking-wider text-primary">
{t(`concept.${currentType}.title`)}
</span>
<p className="text-sm text-text-muted">{t(`concept.${currentType}.phrase`)}</p>
<p className="text-[11px] text-text-muted font-mono">{t(`concept.${currentType}.flow`)}</p>
</div>
{/* Other types as chips */}
<div className="flex items-center gap-2 flex-wrap pt-1 border-t border-black/5 dark:border-white/5">
{types
.filter((type) => type !== currentType)
.map((type) => (
<Link
key={type}
href={TYPE_HREFS[type]}
className="inline-flex items-center gap-1 px-2.5 py-1 text-xs font-medium rounded-full bg-black/5 dark:bg-white/5 text-text-muted hover:text-primary hover:bg-primary/10 transition-colors"
>
{t(`concept.${type}.title`)} {t(`concept.${type}.seeOther`)}
</Link>
))}
</div>
</div>
</div>
);
}

View File

@@ -0,0 +1,152 @@
"use client";
import Link from "next/link";
import Image from "next/image";
import type { CliCatalogEntry } from "@/shared/schemas/cliCatalog";
import type { ToolBatchStatus } from "@/shared/types/cliBatchStatus";
import CliStatusBadge from "@/app/(dashboard)/dashboard/cli-code/components/CliStatusBadge";
import { cn } from "@/shared/utils/cn";
export interface CliToolCardProps {
tool: CliCatalogEntry;
batchStatus: ToolBatchStatus | null;
detailHref: string;
hasActiveProviders: boolean;
}
export default function CliToolCard({
tool,
batchStatus,
detailHref,
hasActiveProviders,
}: CliToolCardProps) {
const installed = batchStatus?.detection.installed ?? false;
const configStatus = batchStatus?.config.status ?? null;
const version = batchStatus?.detection.version ?? "not found";
const endpoint = batchStatus?.config.endpoint ?? null;
const showInstallChips = !installed && tool.configType !== "guide";
const title = (
<div className="flex items-center gap-2.5">
{/* Icon / image */}
{tool.image ? (
<Image
src={tool.image}
alt={tool.name}
width={32}
height={32}
className="rounded-md object-contain flex-shrink-0"
/>
) : (
<span
className="material-symbols-outlined text-[20px] flex-shrink-0"
style={{ color: tool.color }}
aria-hidden="true"
>
{tool.icon ?? "terminal"}
</span>
)}
<div className="min-w-0 flex-1">
<div className="flex items-center gap-2 flex-wrap">
<span className="font-semibold text-text-main text-sm leading-tight truncate">
{tool.name}
</span>
<span className="text-[11px] text-text-muted font-mono bg-black/5 dark:bg-white/5 px-1.5 py-0.5 rounded">
{version}
</span>
</div>
<p className="text-xs text-text-muted line-clamp-1 mt-0.5">{tool.description}</p>
</div>
<span className="material-symbols-outlined text-[18px] text-text-muted flex-shrink-0">
chevron_right
</span>
</div>
);
return (
<Link
href={detailHref}
className={cn(
"block min-h-[180px]",
"bg-surface border border-black/5 dark:border-white/5 rounded-lg shadow-sm",
"hover:shadow-md hover:border-primary/30 transition-all",
"p-4 flex flex-col gap-3"
)}
>
{/* Header */}
{title}
{/* Status strip */}
<div className="flex items-center gap-2 flex-wrap">
{/* Detection */}
<span
className={cn(
"inline-flex items-center gap-1 text-[11px] font-medium px-1.5 py-0.5 rounded",
installed
? "text-green-600 dark:text-green-400"
: "text-zinc-500 dark:text-zinc-400"
)}
>
<span aria-hidden="true">{installed ? "✓" : "✗"}</span>
{installed ? "Detectado" : "Não detectado"}
</span>
{/* Config status */}
{configStatus && (
<CliStatusBadge
effectiveConfigStatus={configStatus}
batchStatus={null}
lastConfiguredAt={batchStatus?.config.lastConfiguredAt ?? null}
/>
)}
{/* Endpoint */}
{endpoint && (
<span className="text-[10px] text-text-muted font-mono truncate max-w-[140px]">
{endpoint}
</span>
)}
</div>
{/* Badges row */}
<div className="flex items-center gap-1.5 flex-wrap">
{tool.baseUrlSupport === "partial" && (
<span className="inline-flex items-center gap-1 px-2 py-0.5 text-[11px] font-medium rounded-full bg-amber-500/10 text-amber-600 dark:text-amber-400">
<span aria-hidden="true"></span> Base URL parcial
</span>
)}
{tool.acpSpawnable === true && (
<span className="inline-flex items-center gap-1 px-2 py-0.5 text-[11px] font-medium rounded-full bg-blue-500/10 text-blue-600 dark:text-blue-400">
também ACP
</span>
)}
{showInstallChips && (
<>
<span className="inline-flex items-center gap-1 px-2 py-0.5 text-[11px] font-medium rounded-full bg-black/5 dark:bg-white/5 text-text-muted">
📋 Manual config
</span>
<span className="inline-flex items-center gap-1 px-2 py-0.5 text-[11px] font-medium rounded-full bg-black/5 dark:bg-white/5 text-text-muted">
Install
</span>
</>
)}
</div>
{/* Footer */}
<div className="mt-auto pt-1 flex items-center justify-between">
<span className="text-xs text-primary font-medium">
{installed ? "Configurar →" : "Como instalar →"}
</span>
{!hasActiveProviders && (
<span
className="text-[10px] text-text-muted italic"
title="Conecte um provider em Providers"
>
Conecte um provider em Providers
</span>
)}
</div>
</Link>
);
}

View File

@@ -0,0 +1,8 @@
export { default as CliToolCard } from "./CliToolCard";
export type { CliToolCardProps } from "./CliToolCard";
export { default as CliConceptCard } from "./CliConceptCard";
export type { CliConceptCardProps, CliConceptType } from "./CliConceptCard";
export { default as CliComparisonCard } from "./CliComparisonCard";
export type { CliComparisonCardProps } from "./CliComparisonCard";