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>
This commit is contained in:
@@ -1,10 +1,25 @@
|
||||
import { prisma } from "@streamclipper/db";
|
||||
import { updateYtdlpCookies } from "../actions";
|
||||
import { updateYtdlpCookies, updateSystemSettings, updateNotificationPrefs } from "../actions";
|
||||
|
||||
export const dynamic = "force-dynamic";
|
||||
|
||||
const STALE_COOKIE_HOURS = 4;
|
||||
|
||||
export default async function SettingsPage() {
|
||||
const setting = await prisma.appSetting.findUnique({ where: { key: "ytdlp_cookies" } });
|
||||
const [setting, pollSetting, ttlSetting, notifyStart, notifyRender] = await Promise.all([
|
||||
prisma.appSetting.findUnique({ where: { key: "ytdlp_cookies" } }),
|
||||
prisma.appSetting.findUnique({ where: { key: "poll_interval_ms" } }),
|
||||
prisma.appSetting.findUnique({ where: { key: "raw_segment_ttl_hours" } }),
|
||||
prisma.appSetting.findUnique({ where: { key: "notify_stream_start" } }),
|
||||
prisma.appSetting.findUnique({ where: { key: "notify_render_done" } }),
|
||||
]);
|
||||
|
||||
const cookieAgeHours = setting ? (Date.now() - setting.updatedAt.getTime()) / 3_600_000 : null;
|
||||
const cookieStale = cookieAgeHours !== null && cookieAgeHours > STALE_COOKIE_HOURS;
|
||||
const pollIntervalSec = pollSetting ? Math.round(Number(pollSetting.value) / 1000) : 60;
|
||||
const ttlHours = ttlSetting ? Number(ttlSetting.value) : 24;
|
||||
const notifyStreamStart = notifyStart?.value !== "false";
|
||||
const notifyRenderDone = notifyRender?.value !== "false";
|
||||
|
||||
return (
|
||||
<>
|
||||
@@ -17,6 +32,14 @@ export default async function SettingsPage() {
|
||||
{setting ? `son güncelleme: ${setting.updatedAt.toLocaleString("tr-TR")}` : "hiç ayarlanmadı"}
|
||||
</span>
|
||||
</div>
|
||||
{cookieStale && (
|
||||
<p style={{ marginBottom: "0.5rem" }}>
|
||||
<span className="badge warn">
|
||||
⚠️ {Math.round(cookieAgeHours!)} saattir güncellenmedi — büyük/popüler kanallarda bot-check
|
||||
hatası görülebilir, taze bir cookies.txt ile güncelle
|
||||
</span>
|
||||
</p>
|
||||
)}
|
||||
<p className="empty" style={{ marginBottom: "0.5rem" }}>
|
||||
YouTube canlı yayın kontrolü ve kayıt için kullanılıyor. Google, oturum çerezlerinin bir
|
||||
kısmını birkaç saatte bir yeniliyor — burada eskidiğini fark edersen (kanal detay
|
||||
@@ -59,6 +82,79 @@ export default async function SettingsPage() {
|
||||
</button>
|
||||
</form>
|
||||
</div>
|
||||
|
||||
<div className="card">
|
||||
<div className="card-head">
|
||||
<span>Sistem Parametreleri</span>
|
||||
</div>
|
||||
<p className="empty" style={{ marginBottom: "0.5rem" }}>
|
||||
Bir sonraki döngüden itibaren geçerli olur, redeploy gerekmez.
|
||||
</p>
|
||||
<form action={updateSystemSettings} style={{ display: "flex", flexDirection: "column", gap: "0.6rem" }}>
|
||||
<label className="mono" style={{ fontSize: "0.8rem" }}>
|
||||
Poll aralığı (saniye)
|
||||
<input name="pollIntervalSec" type="number" min={5} defaultValue={pollIntervalSec} style={{ display: "block", marginTop: "0.25rem" }} />
|
||||
</label>
|
||||
<label className="mono" style={{ fontSize: "0.8rem" }}>
|
||||
Ham segment TTL (saat)
|
||||
<input name="ttlHours" type="number" min={1} defaultValue={ttlHours} style={{ display: "block", marginTop: "0.25rem" }} />
|
||||
</label>
|
||||
<p className="empty" style={{ fontSize: "0.75rem", margin: 0 }}>
|
||||
Eşzamanlı capture limiti ve render eşzamanlılığı BullMQ worker'ları başlatılırken
|
||||
sabitleniyor — bunları değiştirmek için Coolify'deki `MAX_CONCURRENT_CAPTURES` /
|
||||
`VIDEO_RENDER_CONCURRENCY` env var'larını güncelleyip redeploy etmek gerekiyor.
|
||||
</p>
|
||||
<button
|
||||
type="submit"
|
||||
style={{
|
||||
alignSelf: "flex-start",
|
||||
background: "var(--accent)",
|
||||
border: "none",
|
||||
borderRadius: "6px",
|
||||
padding: "0.5rem 1rem",
|
||||
color: "#fff",
|
||||
fontWeight: 600,
|
||||
fontSize: "0.9rem",
|
||||
cursor: "pointer",
|
||||
}}
|
||||
>
|
||||
Kaydet
|
||||
</button>
|
||||
</form>
|
||||
</div>
|
||||
|
||||
<div className="card">
|
||||
<div className="card-head">
|
||||
<span>Bildirimler</span>
|
||||
</div>
|
||||
<form action={updateNotificationPrefs} style={{ display: "flex", flexDirection: "column", gap: "0.5rem" }}>
|
||||
<label className="mono" style={{ fontSize: "0.85rem", display: "flex", alignItems: "center", gap: "0.4rem" }}>
|
||||
<input type="checkbox" name="notifyStreamStart" defaultChecked={notifyStreamStart} />
|
||||
Yayın başladığında Telegram bildirimi
|
||||
</label>
|
||||
<label className="mono" style={{ fontSize: "0.85rem", display: "flex", alignItems: "center", gap: "0.4rem" }}>
|
||||
<input type="checkbox" name="notifyRenderDone" defaultChecked={notifyRenderDone} />
|
||||
9:16 render tamamlandığında Telegram bildirimi
|
||||
</label>
|
||||
<button
|
||||
type="submit"
|
||||
style={{
|
||||
alignSelf: "flex-start",
|
||||
marginTop: "0.3rem",
|
||||
background: "var(--accent)",
|
||||
border: "none",
|
||||
borderRadius: "6px",
|
||||
padding: "0.5rem 1rem",
|
||||
color: "#fff",
|
||||
fontWeight: 600,
|
||||
fontSize: "0.9rem",
|
||||
cursor: "pointer",
|
||||
}}
|
||||
>
|
||||
Kaydet
|
||||
</button>
|
||||
</form>
|
||||
</div>
|
||||
</>
|
||||
);
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user