// Telas 4 e 5 — SSIDs & bridges + Telas do portal. const DSr = window.WiFi_eeb21e; const { Card: CardR, CardContent: CardContentR, CardHeader: CardHeaderR, CardTitle: CardTitleR, Badge: BadgeR, Button: BtnR, Input: InputR, Label: LabelR, Separator: SepR } = DSr; const { GTh: ThR, GTd: TdR, BtnMini: BtnMiniR, TermoDica: TermoR, SelectG: SelR, mkLinha: mkR, slugSsid, Dot: DotR } = window; function TelaSsids({ site, sitesSel, openCmd }) { const meta = window.GESTAO_META; const list = sitesSel && sitesSel.length ? sitesSel : [site]; const multi = list.length > 1; const [alvo, setAlvo] = React.useState(list[0]); React.useEffect(() => setAlvo(a => list.includes(a) ? a : list[0]), [list.join(',')]); const s = meta[alvo]; const carga = window.useCarrega(() => Promise.all(list.map(k => window.GestaoAPI.ssids(k) .then(r => (r.ssids || []).map(c => ({ ...c, __k: k, __rot: meta[k].label }))))) .then(a => a.flat()), [list.join(',')]); const vRows = carga.dados || []; const qcom = s.stack === 'qcom'; const temAcao = multi ? vRows.some(x => meta[x.__k].stack !== 'qcom') : !qcom; const [nome, setNome] = React.useState(''); const [banda, setBanda] = React.useState('2ghz'); const [seg, setSeg] = React.useState('portal'); const criar = () => { const cfg = 'cfg-' + slugSsid(nome); const linhas = qcom ? [mkR(s.alvo, `/interface wifi configuration add name=${cfg} ssid="${nome}"${seg === 'wpa2' ? ' security.authentication-types=wpa2-psk security.passphrase="TROCAR-SENHA"' : ''}`), mkR(s.alvo, `/interface wifi provisioning print`)] : [mkR(s.alvo, `/caps-man configuration add name=${cfg} ssid="${nome}" datapath.bridge=${s.bridge.replace(' (inline)', '')}${banda === '5ghz' ? ' channel.band=5ghz-ac' : ' channel.band=2ghz-g/n'}${seg === 'wpa2' ? ' security.authentication-types=wpa2-psk security.passphrase="TROCAR-SENHA"' : ''}`), mkR(s.alvo, `/caps-man provisioning print detail`)]; openCmd({ titulo: 'Criar SSID', alvo: s.alvo, slug: alvo, acao: 'ssid-add', onSucesso: () => carga.recarrega(), nome, banda, seg, cfg, stack: s.stack, bridge: s.bridge, linhas, nota: qcom ? 'Ponto wifi-qcom: depois de criar a configuration, vincule na provisioning (segunda linha lista as regras). SSID aberto cai no portal do hotspot da bridge.' : 'Molde do playbook: a config nasce apontando para a bridge do hotspot — nada de bridge nova. Vincule na regra de provisioning existente (segunda linha lista).' }); }; const trocarBridge = c => { const sc = meta[c.__k]; const destino = c.__k === 'habibi' ? 'Bridge_Hotspot' : 'dp-bridge-hotspot'; openCmd({ titulo: 'Trocar bridge do SSID', alvo: sc.alvo, slug: c.__k, acao: 'ssid-bridge', cfg: c.cfg, bridge: destino, onSucesso: () => carga.recarrega(), linhas: [mkR(sc.alvo, c.__k === 'habibi' ? `/caps-man configuration set ${c.cfg} datapath.bridge=Bridge_Hotspot` : `/caps-man configuration set ${c.cfg} datapath=dp-bridge-hotspot`)], nota: 'É o comando do swap validado em produção — o servidor confere se a bridge destino tem DHCP ativo antes de aplicar. Rollback: trocar o valor de volta.' }); }; return
{multi ? 'SSIDs — pontos selecionados' : `SSIDs — ${s.label}`}
{multi ? Pilha por ponto: CAPsMAN v1 nos condomínios · wifi-qcom no Campo Gás : qcom ? Pilha wifi-qcom — sem CAPsMAN : Pilha CAPsMAN v1 — datapath aponta a bridge}
{multi ? Ponto : null}ConfigurationSSIDDestino (datapath){temAcao ? Ação : null} {vRows.map((c) => {multi ? {c.__rot} : null} {c.cfg}{c.ssid}{c.datapath || '-'} {temAcao ? {meta[c.__k].stack !== 'qcom' ? trocarBridge(c)}>Trocar bridge : null} : null} )}
Criar SSID {multi ?
Ponto de destino
setAlvo(e.target.value)}>{list.map(k => )}
: null} Nome da rede
setNome(e.target.value)} />
Banda
setBanda(e.target.value)}>
Segurança
setSeg(e.target.value)}>
{`Destino fixo: ${s.bridge}. Regra do playbook: nada de bridge, DHCP ou rede nova.`}
Gerar comando
; } function TelaTelasPortal({ site, sitesSel, openCmd }) { const dados = window.GESTAO_META; const list = sitesSel && sitesSel.length ? sitesSel : [site]; const multi = list.length > 1; const [alvo, setAlvo] = React.useState(list[0]); React.useEffect(() => setAlvo(a => list.includes(a) ? a : list[0]), [list.join(',')]); const tp = window.GESTAO_TELAS_PORTAL; const v0 = tp.venues[alvo]; const [nome, setNome] = React.useState(v0.nome); const [mark, setMark] = React.useState(v0.mark); const [sub, setSub] = React.useState(v0.sub); const [cores, setCores] = React.useState(v0.cores); React.useEffect(() => { const v = tp.venues[alvo]; setNome(v.nome); setMark(v.mark); setSub(v.sub); setCores(v.cores); }, [alvo]); const publicar = () => openCmd({ titulo: 'Gerar e publicar telas do portal', alvo: alvo, linhas: ['app/.venv/bin/python ops/gera_telas.py', `app/.venv/bin/python ops/upload_telas.py ${alvo}`, mkR(alvo, '/file print where name~"hotspot-charles"')], nota: 'gera_telas.py aplica nome, inicial, subtítulo e PONTO por cima da base; upload sobe os 10 arquivos para flash/hotspot-charles/. Páginas autocontidas: nada externo carrega antes do login. A terceira linha confere os arquivos na flash.' }); const cor = (k, rot) =>
setCores({ ...cores, [k]: e.target.value })} aria-label={rot} style={{ width: 34, height: 28, border: '1px solid hsl(var(--border))', borderRadius: 6, padding: 1, background: 'hsl(var(--card))', cursor: 'pointer' }} />
{rot}
{cores[k]}
; return
{`Branding do ponto — ${dados[alvo].label}`} {multi ?
Editar ponto
setAlvo(e.target.value)}>{list.map(k => )}
: null}
Nome do ponto
setNome(e.target.value)} />
Inicial
setMark(e.target.value.toUpperCase())} />
Subtítulo
setSub(e.target.value)} />
{cor('acao', 'Ação')}{cor('fundo', 'Fundo')}{cor('texto', 'Texto')}
Arquivos publicados ({tp.arquivos.length}):
{tp.arquivos.map(a => {a})}
Gerar + publicar
Prévia do login (na flash do roteador)
{mark || '?'}
{nome || '—'}
{sub}
Informe seu e-mail para conectar:
voce@email.com.br
Conectar
Autenticação local no roteador (CHAP + fallback), reconexão por cookie de 3 dias.
; } Object.assign(window, { TelaSsids, TelaTelasPortal });