Adiciona autenticação opcional, VNC integrado, GPU por stream, proxy HLS e melhorias de segurança
---
- Adicionado sistema de autenticação opcional via AUTH_USER/AUTH_PASS: middleware Next.js, página de login, cookie rolling de
30 dias, timingSafeEqual para comparação segura de credenciais;
- Adicionado proxy HLS em /api/hls/[...path] que roteia para localhost:8888 internamente; player e player-static atualizados
para usar a rota proxy;
- Adicionada página /vnc/[id] integrada na UI (iframe + botão Back com auto-hide), substituindo abertura em nova aba;
- Adicionado campo gpu: boolean por stream; controlado via {{GPU_FLAGS}} no template do Chromium e no reprovision.mjs;
- Ajustado delay da primeira thumbnail para stream.delay + 60 para garantir conclusão do autologin antes da captura;
- Atualizado docker-compose.yml: porta 6080 vinculada a localhost, portas 1935 e 8888 comentadas por padrão;
- Traduzidos todos os comentários de código do português para o inglês;
- Adicionado crédito riguetto.dev no header com underline no hover;
- README e CLAUDE.md atualizados com arquitetura, portas e features corretas;
---
This commit is contained in:
+21
-6
@@ -1,7 +1,7 @@
|
||||
"use client"
|
||||
|
||||
import { useEffect, useState, useCallback } from "react"
|
||||
import { Plus, Download, RefreshCw, Settings, X } from "lucide-react"
|
||||
import { Plus, Download, RefreshCw, Settings, X, LogOut } from "lucide-react"
|
||||
import { StreamCard } from "@/components/StreamCard"
|
||||
import type { Stream } from "@/types/stream"
|
||||
import { DndContext, closestCenter, PointerSensor, useSensor, useSensors } from "@dnd-kit/core"
|
||||
@@ -54,7 +54,7 @@ function SkeletonCard({ size = "sm" }: { size?: CardSize }) {
|
||||
)
|
||||
}
|
||||
|
||||
// #7 — settings popup
|
||||
// settings popup
|
||||
function SettingsPopup({ cardSize, onCardSize, onClose }: {
|
||||
cardSize: CardSize
|
||||
onCardSize: (s: CardSize) => void
|
||||
@@ -101,6 +101,7 @@ export default function GalleryPage() {
|
||||
const [refreshing, setRefreshing] = useState(false)
|
||||
const [cardSize, setCardSize] = useState<CardSize>("md") // md = Medium = antigo Big
|
||||
const [settingsOpen, setSettingsOpen] = useState(false)
|
||||
const [authEnabled, setAuthEnabled] = useState(false)
|
||||
|
||||
const sensors = useSensors(useSensor(PointerSensor, { activationConstraint: { distance: 8 } }))
|
||||
|
||||
@@ -144,7 +145,10 @@ export default function GalleryPage() {
|
||||
setStatuses(Object.fromEntries(results))
|
||||
}, [])
|
||||
|
||||
useEffect(() => { fetchStreams() }, [fetchStreams])
|
||||
useEffect(() => {
|
||||
fetchStreams()
|
||||
fetch("/api/auth/status").then(r => r.json()).then(d => setAuthEnabled(d.enabled))
|
||||
}, [fetchStreams])
|
||||
|
||||
useEffect(() => {
|
||||
if (streams.length === 0) return
|
||||
@@ -163,7 +167,7 @@ export default function GalleryPage() {
|
||||
|
||||
const showSkeleton = loading || refreshing
|
||||
|
||||
// #6 — todos os botões do header com mesmo padding e tamanho
|
||||
// all header buttons share the same padding and height
|
||||
const btnBase = "flex items-center gap-1.5 text-sm px-3 py-1.5 h-8 rounded border border-border hover:bg-[#2a2a2a] active:bg-[#333] transition-colors cursor-pointer"
|
||||
const btnPrimary = "flex items-center gap-1.5 text-sm px-3 py-1.5 h-8 rounded border border-primary bg-primary text-primary-foreground hover:bg-[#2a2a2a] hover:text-foreground hover:border-border active:bg-[#333] transition-colors cursor-pointer"
|
||||
|
||||
@@ -173,9 +177,11 @@ export default function GalleryPage() {
|
||||
<div className="flex items-center gap-2">
|
||||
<img src="/web-app-manifest-192x192.png" alt="Decap Stream" className="w-6 h-6 rounded" />
|
||||
<h1 className="text-lg font-semibold tracking-tight">Decap Stream</h1>
|
||||
<span className="text-muted-foreground/40 text-sm select-none">·</span>
|
||||
<a href="https://riguetto.dev" target="_blank" rel="noopener noreferrer" className="text-xs text-[#888] hover:text-[#ededed] hover:underline transition-colors">riguetto.dev</a>
|
||||
</div>
|
||||
<div className="flex items-center gap-2">
|
||||
{/* #6 — refresh com h-8 explícito igual aos outros */}
|
||||
{/* explicit h-8 to match other header buttons */}
|
||||
<button onClick={() => fetchStreams(true)} className={btnBase} title="Atualizar">
|
||||
<RefreshCw className={`w-3.5 h-3.5 ${refreshing ? "animate-spin" : ""}`} />
|
||||
</button>
|
||||
@@ -189,10 +195,19 @@ export default function GalleryPage() {
|
||||
<button onClick={() => window.location.href = "/streams/new"} className={btnPrimary}>
|
||||
<Plus className="w-3.5 h-3.5" /> New stream
|
||||
</button>
|
||||
{authEnabled && (
|
||||
<button
|
||||
onClick={async () => { await fetch("/api/auth/logout", { method: "POST" }); window.location.href = "/login" }}
|
||||
className={btnBase}
|
||||
title="Sign out"
|
||||
>
|
||||
<LogOut className="w-3.5 h-3.5" />
|
||||
</button>
|
||||
)}
|
||||
</div>
|
||||
</header>
|
||||
|
||||
{/* #7 — popup de configurações */}
|
||||
{/* settings popup */}
|
||||
{settingsOpen && (
|
||||
<SettingsPopup
|
||||
cardSize={cardSize}
|
||||
|
||||
Reference in New Issue
Block a user