Files
screenclipper/apps/frontend/app/page.tsx
T
ayrisdevandClaude Sonnet 5 43ccbd9ec7 feat: interaktif panel kontrolleri (Faz 3)
Yayın durdurma, kanal duraklat/devam (tekli+toplu), şimdi kontrol et,
manuel URL ile zorla kayıt başlatma, canlı player+log önizleme, oturum
süre sayacı, başarısız/takılı render için tekrar dene ve iptal, poll
aralığı + ham segment TTL'yi panelden canlı değiştirme, cookie bayatlık
uyarısı, Telegram bildirim aç/kapa, ve kullanım/maliyet özeti (/stats).

api-daemon'ın yeni state-değiştiren endpoint'leri (stop/force-start/
render/cancel) INTERNAL_API_TOKEN ile korunuyor — bu daemon'ın portu
Coolify'de public'e açık olduğu için korumasız bırakmak güvenlik açığı
olurdu.

Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
2026-09-02 13:38:47 +03:00

110 lines
4.0 KiB
TypeScript
Raw Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
import Link from "next/link";
import { prisma } from "@streamclipper/db";
import { addChannel, toggleChannelActive, toggleAllChannels, checkChannelNow } from "./actions";
import { fetchChannelStatuses } from "../lib/apiDaemon";
export const dynamic = "force-dynamic";
export default async function DashboardPage() {
const [channels, statuses] = await Promise.all([
prisma.channel.findMany({
orderBy: { name: "asc" },
include: {
sessions: {
where: { endedAt: null },
orderBy: { startedAt: "desc" },
take: 1,
},
},
}),
fetchChannelStatuses(),
]);
return (
<>
<h1>Kanal Durumu</h1>
<form action={addChannel} className="card add-channel-form">
<input name="name" placeholder="Kanal adı" required />
<input name="youtubeHandle" placeholder="@handle" required />
<input name="channelId" placeholder="channel_id (UC...)" required />
<button type="submit">Kanal Ekle</button>
</form>
{channels.length === 0 ? (
<p className="empty">Henüz kanal eklenmedi.</p>
) : (
<>
<div style={{ display: "flex", gap: "0.5rem", marginBottom: "0.6rem" }}>
<form action={toggleAllChannels.bind(null, true)}>
<button type="submit" className="badge ok" style={{ border: "none", cursor: "pointer" }}>
Tümünü Devam Ettir
</button>
</form>
<form action={toggleAllChannels.bind(null, false)}>
<button type="submit" className="badge muted" style={{ border: "none", cursor: "pointer" }}>
Tümünü Duraklat
</button>
</form>
</div>
<table>
<thead>
<tr>
<th>Kanal</th>
<th>Durum</th>
<th>Kayıt</th>
<th>Son Kontrol</th>
<th></th>
</tr>
</thead>
<tbody>
{channels.map((c) => {
const recording = c.sessions.length > 0;
const status = statuses.get(c.id);
return (
<tr key={c.id}>
<td>
<Link href={`/channels/${c.id}`}>{c.name}</Link>{" "}
<span className="mono">{c.youtubeHandle}</span>
</td>
<td>
<span className={`badge ${c.isActive ? "ok" : "muted"}`}>
{c.isActive ? "Aktif" : "Pasif"}
</span>
</td>
<td>
<span className={`badge ${recording ? "warn" : "muted"}`}>
{recording ? "🔴 Kayıtta" : "—"}
</span>
{status?.lastPollError && (
<span className="badge err" style={{ marginLeft: "0.4rem" }}>
hata
</span>
)}
</td>
<td className="mono">
{c.lastCheckedAt ? new Date(c.lastCheckedAt).toLocaleString("tr-TR") : "—"}
</td>
<td style={{ display: "flex", gap: "0.35rem" }}>
<form action={toggleChannelActive.bind(null, c.id, !c.isActive)}>
<button type="submit" className="badge muted" style={{ border: "none", cursor: "pointer" }}>
{c.isActive ? "Duraklat" : "Devam Ettir"}
</button>
</form>
<form action={checkChannelNow.bind(null, c.id)}>
<button type="submit" className="badge muted" style={{ border: "none", cursor: "pointer" }}>
Şimdi Kontrol Et
</button>
</form>
</td>
</tr>
);
})}
</tbody>
</table>
</>
)}
</>
);
}