Add per-page titles
Root layout now defines a title template (%s · TriggerShell) so every page just sets its own short title instead of repeating the brand name. Static titles for the dashboard, login, and run history pages; dynamic generateMetadata for the script and run detail pages, since those need the script/run name which isn't known until the route params resolve. Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
This commit is contained in:
@@ -1,10 +1,13 @@
|
||||
export const dynamic = "force-dynamic";
|
||||
|
||||
import type { Metadata } from "next";
|
||||
import Link from "next/link";
|
||||
import { ChevronRight, PlayCircle } from "lucide-react";
|
||||
import { getConfig } from "@/lib/config/load";
|
||||
import { Card, CardContent, CardHeader, CardTitle } from "@/components/ui/card";
|
||||
|
||||
export const metadata: Metadata = { title: "Scripts" };
|
||||
|
||||
export default function DashboardPage() {
|
||||
const { config } = getConfig();
|
||||
|
||||
|
||||
@@ -1,6 +1,7 @@
|
||||
export const dynamic = "force-dynamic";
|
||||
|
||||
import fs from "node:fs";
|
||||
import type { Metadata } from "next";
|
||||
import { eq } from "drizzle-orm";
|
||||
import { notFound } from "next/navigation";
|
||||
import { getDb } from "@/lib/db/client";
|
||||
@@ -10,6 +11,22 @@ import { Card, CardContent, CardHeader, CardTitle } from "@/components/ui/card";
|
||||
|
||||
const MAX_INITIAL_LOG_BYTES = 200_000;
|
||||
|
||||
interface RunDetailPageProps {
|
||||
params: Promise<{ runId: string }>;
|
||||
}
|
||||
|
||||
export async function generateMetadata({
|
||||
params,
|
||||
}: RunDetailPageProps): Promise<Metadata> {
|
||||
const { runId } = await params;
|
||||
const run = getDb()
|
||||
.select({ scriptName: runs.scriptName })
|
||||
.from(runs)
|
||||
.where(eq(runs.id, runId))
|
||||
.get();
|
||||
return { title: run ? `${run.scriptName} run` : "Run not found" };
|
||||
}
|
||||
|
||||
function readTail(logFilePath: string): string {
|
||||
if (!fs.existsSync(logFilePath)) return "";
|
||||
const { size } = fs.statSync(logFilePath);
|
||||
@@ -24,11 +41,7 @@ function readTail(logFilePath: string): string {
|
||||
}
|
||||
}
|
||||
|
||||
export default async function RunDetailPage({
|
||||
params,
|
||||
}: {
|
||||
params: Promise<{ runId: string }>;
|
||||
}) {
|
||||
export default async function RunDetailPage({ params }: RunDetailPageProps) {
|
||||
const { runId } = await params;
|
||||
const db = getDb();
|
||||
const run = db.select().from(runs).where(eq(runs.id, runId)).get();
|
||||
|
||||
@@ -1,5 +1,6 @@
|
||||
export const dynamic = "force-dynamic";
|
||||
|
||||
import type { Metadata } from "next";
|
||||
import Link from "next/link";
|
||||
import { desc } from "drizzle-orm";
|
||||
import { getDb } from "@/lib/db/client";
|
||||
@@ -14,6 +15,8 @@ import {
|
||||
TableRow,
|
||||
} from "@/components/ui/table";
|
||||
|
||||
export const metadata: Metadata = { title: "Run History" };
|
||||
|
||||
function formatDuration(startedAt: Date | null, endedAt: Date | null): string {
|
||||
if (!startedAt) return "-";
|
||||
const end = endedAt ?? new Date();
|
||||
|
||||
@@ -1,5 +1,6 @@
|
||||
export const dynamic = "force-dynamic";
|
||||
|
||||
import type { Metadata } from "next";
|
||||
import { notFound } from "next/navigation";
|
||||
import { getScript } from "@/lib/config/load";
|
||||
import { serializeScriptForClient } from "@/lib/config/serialize";
|
||||
@@ -12,11 +13,19 @@ import {
|
||||
CardTitle,
|
||||
} from "@/components/ui/card";
|
||||
|
||||
export default async function ScriptPage({
|
||||
params,
|
||||
}: {
|
||||
interface ScriptPageProps {
|
||||
params: Promise<{ scriptId: string }>;
|
||||
}) {
|
||||
}
|
||||
|
||||
export async function generateMetadata({
|
||||
params,
|
||||
}: ScriptPageProps): Promise<Metadata> {
|
||||
const { scriptId } = await params;
|
||||
const script = getScript(scriptId);
|
||||
return { title: script?.name ?? "Script not found" };
|
||||
}
|
||||
|
||||
export default async function ScriptPage({ params }: ScriptPageProps) {
|
||||
const { scriptId } = await params;
|
||||
const script = getScript(scriptId);
|
||||
if (!script) notFound();
|
||||
|
||||
@@ -15,7 +15,10 @@ const geistMono = Geist_Mono({
|
||||
});
|
||||
|
||||
export const metadata: Metadata = {
|
||||
title: "TriggerShell",
|
||||
title: {
|
||||
default: "TriggerShell",
|
||||
template: "%s · TriggerShell",
|
||||
},
|
||||
description: "Run configured shell scripts from a web UI.",
|
||||
};
|
||||
|
||||
@@ -29,7 +32,10 @@ export default function RootLayout({
|
||||
lang="en"
|
||||
className={`${geistSans.variable} ${geistMono.variable} h-full antialiased`}
|
||||
>
|
||||
<body className="bg-background text-foreground flex min-h-full flex-col" suppressHydrationWarning>
|
||||
<body
|
||||
className="bg-background text-foreground flex min-h-full flex-col"
|
||||
suppressHydrationWarning
|
||||
>
|
||||
<TooltipProvider>
|
||||
{children}
|
||||
<Toaster />
|
||||
|
||||
@@ -1,10 +1,13 @@
|
||||
export const dynamic = "force-dynamic";
|
||||
|
||||
import type { Metadata } from "next";
|
||||
import { redirect } from "next/navigation";
|
||||
import { getConfig } from "@/lib/config/load";
|
||||
import { getSession } from "@/lib/auth/session";
|
||||
import { LoginForm } from "./login-form";
|
||||
|
||||
export const metadata: Metadata = { title: "Sign in" };
|
||||
|
||||
export default async function LoginPage() {
|
||||
const { config } = getConfig();
|
||||
if (!config.auth.enabled) redirect("/");
|
||||
|
||||
Reference in New Issue
Block a user