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";
|
export const dynamic = "force-dynamic";
|
||||||
|
|
||||||
|
import type { Metadata } from "next";
|
||||||
import Link from "next/link";
|
import Link from "next/link";
|
||||||
import { ChevronRight, PlayCircle } from "lucide-react";
|
import { ChevronRight, PlayCircle } from "lucide-react";
|
||||||
import { getConfig } from "@/lib/config/load";
|
import { getConfig } from "@/lib/config/load";
|
||||||
import { Card, CardContent, CardHeader, CardTitle } from "@/components/ui/card";
|
import { Card, CardContent, CardHeader, CardTitle } from "@/components/ui/card";
|
||||||
|
|
||||||
|
export const metadata: Metadata = { title: "Scripts" };
|
||||||
|
|
||||||
export default function DashboardPage() {
|
export default function DashboardPage() {
|
||||||
const { config } = getConfig();
|
const { config } = getConfig();
|
||||||
|
|
||||||
|
|||||||
@@ -1,6 +1,7 @@
|
|||||||
export const dynamic = "force-dynamic";
|
export const dynamic = "force-dynamic";
|
||||||
|
|
||||||
import fs from "node:fs";
|
import fs from "node:fs";
|
||||||
|
import type { Metadata } from "next";
|
||||||
import { eq } from "drizzle-orm";
|
import { eq } from "drizzle-orm";
|
||||||
import { notFound } from "next/navigation";
|
import { notFound } from "next/navigation";
|
||||||
import { getDb } from "@/lib/db/client";
|
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;
|
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 {
|
function readTail(logFilePath: string): string {
|
||||||
if (!fs.existsSync(logFilePath)) return "";
|
if (!fs.existsSync(logFilePath)) return "";
|
||||||
const { size } = fs.statSync(logFilePath);
|
const { size } = fs.statSync(logFilePath);
|
||||||
@@ -24,11 +41,7 @@ function readTail(logFilePath: string): string {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
export default async function RunDetailPage({
|
export default async function RunDetailPage({ params }: RunDetailPageProps) {
|
||||||
params,
|
|
||||||
}: {
|
|
||||||
params: Promise<{ runId: string }>;
|
|
||||||
}) {
|
|
||||||
const { runId } = await params;
|
const { runId } = await params;
|
||||||
const db = getDb();
|
const db = getDb();
|
||||||
const run = db.select().from(runs).where(eq(runs.id, runId)).get();
|
const run = db.select().from(runs).where(eq(runs.id, runId)).get();
|
||||||
|
|||||||
@@ -1,5 +1,6 @@
|
|||||||
export const dynamic = "force-dynamic";
|
export const dynamic = "force-dynamic";
|
||||||
|
|
||||||
|
import type { Metadata } from "next";
|
||||||
import Link from "next/link";
|
import Link from "next/link";
|
||||||
import { desc } from "drizzle-orm";
|
import { desc } from "drizzle-orm";
|
||||||
import { getDb } from "@/lib/db/client";
|
import { getDb } from "@/lib/db/client";
|
||||||
@@ -14,6 +15,8 @@ import {
|
|||||||
TableRow,
|
TableRow,
|
||||||
} from "@/components/ui/table";
|
} from "@/components/ui/table";
|
||||||
|
|
||||||
|
export const metadata: Metadata = { title: "Run History" };
|
||||||
|
|
||||||
function formatDuration(startedAt: Date | null, endedAt: Date | null): string {
|
function formatDuration(startedAt: Date | null, endedAt: Date | null): string {
|
||||||
if (!startedAt) return "-";
|
if (!startedAt) return "-";
|
||||||
const end = endedAt ?? new Date();
|
const end = endedAt ?? new Date();
|
||||||
|
|||||||
@@ -1,5 +1,6 @@
|
|||||||
export const dynamic = "force-dynamic";
|
export const dynamic = "force-dynamic";
|
||||||
|
|
||||||
|
import type { Metadata } from "next";
|
||||||
import { notFound } from "next/navigation";
|
import { notFound } from "next/navigation";
|
||||||
import { getScript } from "@/lib/config/load";
|
import { getScript } from "@/lib/config/load";
|
||||||
import { serializeScriptForClient } from "@/lib/config/serialize";
|
import { serializeScriptForClient } from "@/lib/config/serialize";
|
||||||
@@ -12,11 +13,19 @@ import {
|
|||||||
CardTitle,
|
CardTitle,
|
||||||
} from "@/components/ui/card";
|
} from "@/components/ui/card";
|
||||||
|
|
||||||
export default async function ScriptPage({
|
interface ScriptPageProps {
|
||||||
params,
|
|
||||||
}: {
|
|
||||||
params: Promise<{ scriptId: string }>;
|
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 { scriptId } = await params;
|
||||||
const script = getScript(scriptId);
|
const script = getScript(scriptId);
|
||||||
if (!script) notFound();
|
if (!script) notFound();
|
||||||
|
|||||||
@@ -15,7 +15,10 @@ const geistMono = Geist_Mono({
|
|||||||
});
|
});
|
||||||
|
|
||||||
export const metadata: Metadata = {
|
export const metadata: Metadata = {
|
||||||
title: "TriggerShell",
|
title: {
|
||||||
|
default: "TriggerShell",
|
||||||
|
template: "%s · TriggerShell",
|
||||||
|
},
|
||||||
description: "Run configured shell scripts from a web UI.",
|
description: "Run configured shell scripts from a web UI.",
|
||||||
};
|
};
|
||||||
|
|
||||||
@@ -29,7 +32,10 @@ export default function RootLayout({
|
|||||||
lang="en"
|
lang="en"
|
||||||
className={`${geistSans.variable} ${geistMono.variable} h-full antialiased`}
|
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>
|
<TooltipProvider>
|
||||||
{children}
|
{children}
|
||||||
<Toaster />
|
<Toaster />
|
||||||
|
|||||||
@@ -1,10 +1,13 @@
|
|||||||
export const dynamic = "force-dynamic";
|
export const dynamic = "force-dynamic";
|
||||||
|
|
||||||
|
import type { Metadata } from "next";
|
||||||
import { redirect } from "next/navigation";
|
import { redirect } from "next/navigation";
|
||||||
import { getConfig } from "@/lib/config/load";
|
import { getConfig } from "@/lib/config/load";
|
||||||
import { getSession } from "@/lib/auth/session";
|
import { getSession } from "@/lib/auth/session";
|
||||||
import { LoginForm } from "./login-form";
|
import { LoginForm } from "./login-form";
|
||||||
|
|
||||||
|
export const metadata: Metadata = { title: "Sign in" };
|
||||||
|
|
||||||
export default async function LoginPage() {
|
export default async function LoginPage() {
|
||||||
const { config } = getConfig();
|
const { config } = getConfig();
|
||||||
if (!config.auth.enabled) redirect("/");
|
if (!config.auth.enabled) redirect("/");
|
||||||
|
|||||||
Reference in New Issue
Block a user