// Peças comuns do console de gestão.
const DSc = window.WiFi_eeb21e;
const { Button: BtnC, Badge: BadgeC, Skeleton: SkeletonC } = DSc;
// Caixa de estado de carregamento: Skeleton enquanto carrega, mensagem + "Tentar de novo" no
// erro (com o motivo quando o servidor manda um), senão renderiza o conteúdo.
function CargaBox({ carregando, erro, onTentar, linhas, children }) {
if (carregando) {
return
{Array.from({ length: linhas || 3 }).map((_, i) => )}
;
}
if (erro) {
const motivo = (erro && erro.corpo && erro.corpo.detail) || (erro && erro.status === 401 ? 'sessão expirada — recarregue a página' : (erro && erro.status ? 'erro ' + erro.status : 'falha de rede'));
return
Não foi possível carregar: {motivo}.
{onTentar ?
Tentar de novo
: null}
;
}
return children;
}
const GESTAO_ICONES = {
grid: ' ',
lista: ' ',
escudo: ' ',
wifi: ' ',
tela: ' ',
mail: ' ',
cpu: ' ',
atividade: ' '
};
function Icone({ nome, size = 16 }) {
return ;
}
const GESTAO_DOT = { online: '#22c55e', ativo: '#22c55e', offline: 'hsl(var(--destructive))', aviso: 'hsl(var(--ring))', dormente: 'hsl(var(--muted-foreground))' };
function Dot({ s }) {
return
{s}
;
}
function GTh({ children, right }) { return {children} ; }
function GTd({ children, mono, right, wrap }) { return {children} ; }
function ChipFiltro({ ativo, onClick, children }) {
return {children} ;
}
function BtnMini({ children, tom, ...p }) {
const cor = tom === 'perigo' ? 'hsl(var(--destructive))' : 'var(--brand-orange-text)';
return {children} ;
}
function TermoDica({ dica, children }) { return {children} ; }
function SelectG(p) {
return {p.children} ;
}
// Painel lateral: comando gerado para revisão — a interface NÃO executa escrita.
function CmdPainel({ cmd, onClose }) {
const [copiado, setCopiado] = React.useState(false);
const [exec, setExec] = React.useState({ estado: 'idle' }); // idle | executando | ok | erro
React.useEffect(() => { setCopiado(false); setExec({ estado: 'idle' }); }, [cmd]);
if (!cmd) return null;
const texto = cmd.linhas.join('\n');
const copiar = () => {
try { navigator.clipboard.writeText(texto).then(() => setCopiado(true)); }
catch (e) { setCopiado(false); }
};
// Executa só quando o ponto está no piloto (GESTAO_ESCRITA) e o comando declara uma ação.
const podeExecutar = !!(cmd.slug && window.GESTAO_ESCRITA[cmd.slug] && cmd.acao);
const executar = async () => {
setExec({ estado: 'executando' });
try {
const API = window.GestaoAPI; let r;
if (cmd.acao === 'binding-add') r = await API.bindingAdd(cmd.slug, cmd.mac, cmd.comment);
else if (cmd.acao === 'binding-del') r = await API.bindingDel(cmd.slug, cmd.mac);
else if (cmd.acao === 'ssid-add') r = await API.ssidAdd(cmd.slug, { nome: cmd.nome, banda: cmd.banda, seg: cmd.seg });
else if (cmd.acao === 'ssid-bridge') r = await API.ssidBridge(cmd.slug, { cfg: cmd.cfg, bridge: cmd.bridge });
else throw new Error('ação desconhecida');
setExec({ estado: 'ok', saida: (r && (r.saida || r.nota)) || 'Executado.' });
if (cmd.onSucesso) cmd.onSucesso();
} catch (e) {
const msg = (e && e.corpo && e.corpo.detail) || (e && e.status ? 'erro ' + e.status : 'falha de rede');
setExec({ estado: 'erro', saida: msg });
}
};
return
e.stopPropagation()} style={{ width: 480, maxWidth: '92vw', height: '100%', background: 'hsl(var(--card))', borderLeft: '1px solid hsl(var(--card-border))', boxShadow: 'var(--shadow-lg)', display: 'flex', flexDirection: 'column', boxSizing: 'border-box' }}>
{podeExecutar ? `Alvo: ${cmd.alvo} · executa no roteador (piloto MF)` : `Alvo: ${cmd.alvo} · comando para o seu terminal`}
{cmd.linhas.map((l, i) =>
{l} )}
{cmd.nota ?
{cmd.nota}
: null}
{exec.estado === 'ok' ?
✓ {exec.saida}
: null}
{exec.estado === 'erro' ?
Não executado: {exec.saida}
: null}
{podeExecutar
?
{exec.estado === 'executando' ? 'Executando…' : (exec.estado === 'ok' ? 'Feito ✓' : 'Executar no roteador')}
: null}
{copiado ? 'Copiado ✓' : 'Copiar comandos'}
Fechar
{!podeExecutar ?
Fora do piloto MF: execute no seu terminal.
: null}
;
}
function mkLinha(alvo, comando) { return `app/.venv/bin/python ops/mk.py ${alvo} cmd '${comando}'`; }
function slugSsid(t) { return (t || '').toLowerCase().normalize('NFD').replace(/[̀-ͯ]/g, '').replace(/[^a-z0-9]+/g, '-').replace(/(^-|-$)/g, ''); }
// Menu de seleção de pontos — um ou vários clientes ao mesmo tempo.
function PontoMenu({ sel, setSel }) {
const sites = window.GESTAO_META;
const chaves = Object.keys(sites);
const [aberto, setAberto] = React.useState(false);
const todos = sel.length === chaves.length;
const rotulo = todos ? 'Todos os pontos' : sel.length === 1 ? sites[sel[0]].label : `${sel.length} pontos`;
const alterna = k => setSel(s => s.includes(k) ? (s.length > 1 ? s.filter(x => x !== k) : s) : [...s, k]);
return
setAberto(a => !a)} className="hover-elevate" aria-haspopup="true" aria-expanded={aberto} style={{ display: 'flex', alignItems: 'center', gap: 10, background: 'hsl(var(--card))', border: '1px solid hsl(var(--border))', borderRadius: 'var(--radius-md)', padding: '7px 12px', fontSize: 13, fontFamily: 'var(--app-font-sans)', color: 'hsl(var(--foreground))', cursor: 'pointer', minWidth: 176, justifyContent: 'space-between' }}>
{rotulo}
{aberto ?
setAberto(false)} style={{ position: 'fixed', inset: 0, zIndex: 40 }} />
: null}
;
}
Object.assign(window, { Icone, Dot, GTh, GTd, ChipFiltro, BtnMini, TermoDica, SelectG, CmdPainel, PontoMenu, mkLinha, slugSsid, CargaBox });