fix(ui): botão de copiar senha não fazia nada em HTTP puro (fora de localhost)
navigator.clipboard só existe em contexto seguro (HTTPS ou localhost) —
este ambiente é HTTP puro, acessado pelo IP real da VM. SecretReveal
(usado na criação de ramal e em "ver senha atual") chamava
navigator.clipboard.writeText sem tratamento de erro, então em contexto
inseguro o botão simplesmente não fazia nada, sem nenhum aviso.
Fix: fallback pra document.execCommand("copy") (textarea temporário)
quando a Clipboard API moderna não existe ou falha, funciona em qualquer
contexto. Se as duas formas falharem, mostra aviso pra copiar manualmente
em vez de falhar em silêncio.
Testado com Playwright acessando o IP real da VM (não localhost, pra
reproduzir o contexto inseguro de verdade): confirmado
navigator.clipboard.writeText undefined nesse contexto (reproduz o
sintoma exato), e os dois fluxos de senha mostram sucesso via o fallback.
Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01BFaBaBSQGhyXGEgtTYZGV8
This commit is contained in:
@@ -1,9 +1,36 @@
|
||||
"use client";
|
||||
|
||||
import { useState } from "react";
|
||||
import { Copy, Check, TriangleAlert } from "lucide-react";
|
||||
import { Copy, Check, TriangleAlert, X } from "lucide-react";
|
||||
import { cn } from "@/lib/utils";
|
||||
|
||||
/**
|
||||
* `navigator.clipboard` só existe em "contexto seguro" (HTTPS ou
|
||||
* localhost) — achado real reportado pelo usuário: o botão de copiar não
|
||||
* fazia nada (sem erro nenhum, silencioso) quando acessado pelo IP da VM
|
||||
* em HTTP puro (agente.md: ambiente de laboratório sem HTTPS ainda).
|
||||
* `execCommand("copy")` sobre um textarea temporário fora da tela é o
|
||||
* fallback clássico que funciona em qualquer contexto, inclusive HTTP.
|
||||
*/
|
||||
function legacyCopy(value: string): boolean {
|
||||
const textarea = document.createElement("textarea");
|
||||
textarea.value = value;
|
||||
textarea.style.position = "fixed";
|
||||
textarea.style.opacity = "0";
|
||||
textarea.style.pointerEvents = "none";
|
||||
document.body.appendChild(textarea);
|
||||
textarea.focus();
|
||||
textarea.select();
|
||||
let ok = false;
|
||||
try {
|
||||
ok = document.execCommand("copy");
|
||||
} catch {
|
||||
ok = false;
|
||||
}
|
||||
document.body.removeChild(textarea);
|
||||
return ok;
|
||||
}
|
||||
|
||||
/**
|
||||
* Mostra um segredo em texto puro UMA vez (senha SIP recém-gerada,
|
||||
* agente.md secao 39: "nunca mostrar novamente a senha inteira" depois
|
||||
@@ -12,11 +39,31 @@ import { cn } from "@/lib/utils";
|
||||
*/
|
||||
export function SecretReveal({ label, value }: { label: string; value: string }) {
|
||||
const [copied, setCopied] = useState(false);
|
||||
const [error, setError] = useState(false);
|
||||
|
||||
async function onCopy() {
|
||||
await navigator.clipboard.writeText(value);
|
||||
setCopied(true);
|
||||
setTimeout(() => setCopied(false), 2000);
|
||||
setError(false);
|
||||
let ok = false;
|
||||
if (navigator.clipboard?.writeText) {
|
||||
try {
|
||||
await navigator.clipboard.writeText(value);
|
||||
ok = true;
|
||||
} catch {
|
||||
ok = false;
|
||||
}
|
||||
}
|
||||
if (!ok) ok = legacyCopy(value);
|
||||
|
||||
if (ok) {
|
||||
setCopied(true);
|
||||
setTimeout(() => setCopied(false), 2000);
|
||||
} else {
|
||||
// Nenhuma das duas formas funcionou — o texto já está selecionável
|
||||
// (`select-all` no <code> abaixo), avisa pra copiar manualmente
|
||||
// (Ctrl+C) em vez de falhar em silêncio.
|
||||
setError(true);
|
||||
setTimeout(() => setError(false), 4000);
|
||||
}
|
||||
}
|
||||
|
||||
return (
|
||||
@@ -35,9 +82,20 @@ export function SecretReveal({ label, value }: { label: string; value: string })
|
||||
className="flex h-9 w-9 shrink-0 items-center justify-center rounded-md border border-border text-muted-foreground transition-colors hover:bg-muted hover:text-foreground focus-visible:outline-none focus-visible:ring-2 focus-visible:ring-ring"
|
||||
aria-label="Copiar senha"
|
||||
>
|
||||
{copied ? <Check className="h-4 w-4 text-status-green" aria-hidden /> : <Copy className="h-4 w-4" aria-hidden />}
|
||||
{copied ? (
|
||||
<Check className="h-4 w-4 text-status-green" aria-hidden />
|
||||
) : error ? (
|
||||
<X className="h-4 w-4 text-destructive" aria-hidden />
|
||||
) : (
|
||||
<Copy className="h-4 w-4" aria-hidden />
|
||||
)}
|
||||
</button>
|
||||
</div>
|
||||
{error && (
|
||||
<p className="mt-2 text-xs text-destructive">
|
||||
Não deu pra copiar automaticamente — selecione o texto acima e copie com Ctrl+C (ou Cmd+C).
|
||||
</p>
|
||||
)}
|
||||
</div>
|
||||
);
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user