Files
Admin_dash/app/api/services/route.ts
Shivam Patel 8e18bf64b2 Add scrollable widget, expand/collapse toggle, inline name editing
- Fix overflow by making card body scrollable with thin custom scrollbar
- Add expand/collapse chevron button toggling row-span-1 to row-span-2
- Add inline name editing on hover (pencil icon) with Enter/Escape/blur
- Add PATCH /api/services endpoint for renaming with log history update
- Add rename button in detail modal

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
2026-02-09 02:23:19 -05:00

104 lines
3.7 KiB
TypeScript

import { NextResponse } from 'next/server';
import { getDb } from '@/lib/db';
export const dynamic = 'force-dynamic';
export async function GET() {
try {
const db = await getDb();
const services = await db.all('SELECT id, name, url, created_at FROM monitored_services ORDER BY id ASC');
return NextResponse.json(services);
} catch (error) {
console.error('Services list error:', error);
return NextResponse.json([], { status: 500 });
}
}
export async function POST(request: Request) {
try {
const { name, url } = await request.json();
if (!name || !url) {
return NextResponse.json({ error: 'Name and URL are required' }, { status: 400 });
}
// Basic URL validation
try {
new URL(url);
} catch {
return NextResponse.json({ error: 'Invalid URL format' }, { status: 400 });
}
const db = await getDb();
await db.run('INSERT INTO monitored_services (name, url) VALUES (?, ?)', name.trim(), url.trim());
return NextResponse.json({ success: true });
} catch (error: unknown) {
const msg = error instanceof Error ? error.message : 'Unknown error';
if (msg.includes('UNIQUE constraint')) {
return NextResponse.json({ error: 'A service with that name already exists' }, { status: 409 });
}
console.error('Service add error:', error);
return NextResponse.json({ error: 'Failed to add service' }, { status: 500 });
}
}
export async function PATCH(request: Request) {
try {
const { id, name } = await request.json();
if (!id || !name) {
return NextResponse.json({ error: 'ID and new name are required' }, { status: 400 });
}
const db = await getDb();
const service = await db.get('SELECT name FROM monitored_services WHERE id = ?', id);
if (!service) {
return NextResponse.json({ error: 'Service not found' }, { status: 404 });
}
const oldName = service.name;
const newName = name.trim();
await db.run('UPDATE monitored_services SET name = ? WHERE id = ?', newName, id);
// Update existing logs to match the new name
await db.run('UPDATE uptime_logs SET service_name = ? WHERE service_name = ?', newName, oldName);
return NextResponse.json({ success: true });
} catch (error: unknown) {
const msg = error instanceof Error ? error.message : 'Unknown error';
if (msg.includes('UNIQUE constraint')) {
return NextResponse.json({ error: 'A service with that name already exists' }, { status: 409 });
}
console.error('Service rename error:', error);
return NextResponse.json({ error: 'Failed to rename service' }, { status: 500 });
}
}
export async function DELETE(request: Request) {
try {
const { id } = await request.json();
if (!id) {
return NextResponse.json({ error: 'Service ID is required' }, { status: 400 });
}
const db = await getDb();
// Get service name before deleting so we can clean up logs
const service = await db.get('SELECT name FROM monitored_services WHERE id = ?', id);
if (!service) {
return NextResponse.json({ error: 'Service not found' }, { status: 404 });
}
await db.run('DELETE FROM monitored_services WHERE id = ?', id);
await db.run('DELETE FROM uptime_logs WHERE service_name = ?', service.name);
return NextResponse.json({ success: true });
} catch (error) {
console.error('Service delete error:', error);
return NextResponse.json({ error: 'Failed to delete service' }, { status: 500 });
}
}