'use strict'; import { h, render, useState, useEffect, useRef, html, Router } from './bundle.js'; const Logo = props => html``; const IconHeart = props => html``; const IconDownArrowBox = props => html` `; const IconUpArrowBox = props => html` `; const IconSettings = props => html` `; const IconDesktop = props => html` `; const IconScan = props => html` `; const IconAlert = props => html` `; const IconRefresh = props => html` `; const IconBars4 = props => html` `; const IconBars3 = props => html` `; const IconLogout = props => html` `; const IconSave = props => html` `; const IconEmail = props => html` `; const IconExpand = props => html` `; const IconShrink = props => html` `; const IconOk = props => html` `; const IconFailed = props => html` `; const IconUpload = props => html` `; const IconDownload = props => html` `; const IconBolt = props => html` `; const IconHome = props => html` `; const IconLink = props => html` `; const IconShield = props => html` `; const IconBarsDown = props => html` `; const IconArrowDown = props => html` `; const IconArrowUp = props => html` `; const IconExclamationTriangle = props => html` `; const tipColors = { green: 'bg-green-100 text-green-900', yellow: 'bg-yellow-100 text-yellow-900', red: 'bg-red-100 text-red-900', }; function Button({title, onclick, disabled, cls, icon, ref, colors, hovercolor, disabledcolor}) { const [spin, setSpin] = useState(false); const cb = function(ev) { const res = onclick ? onclick() : null; if (res && typeof (res.catch) === 'function') { setSpin(true); res.catch(() => false).then(() => setSpin(false)); } }; if (!colors) colors = 'bg-blue-600 hover:bg-blue-500 disabled:bg-blue-400'; return html` ${title} <${spin ? IconRefresh : icon} class="w-4 ${spin ? 'animate-spin' : ''}" /> />` }; function Toast({ok, text, close}) { const closebtn = useRef(null); const from = 'translate-y-2 opacity-0 sm:translate-y-0 sm:translate-x-2'; const to = 'translate-y-0 opacity-100 sm:translate-x-0'; const [tr, setTr] = useState(from); useEffect(function() { setTr(to); setTimeout(ev => closebtn && closebtn.current.click && closebtn.current.click(), 1500); }, []); const onclose = ev => { setTr(from); setTimeout(close, 300); }; return html` <${ok ? IconOk : IconFailed} class="h-6 w-6 ${ok ? 'text-green-400' : 'text-red-400'}" /> /> ${text} Anyone with a link can now view this file. /> Close /> /> /> /> /> /> /> />`; }; function Login({login}) { const [user, setUser] = useState(''); const [pass, setPass] = useState(''); const onsubmit = function(ev) { const authhdr = 'Basic ' + btoa(user + ':' + pass); const headers = {Authorization: authhdr}; return fetch('api/login', {headers}).then(login).finally(r => setPass('')); }; return html` <${Logo} class="h-12 stroke-cyan-600 stroke-1" /> WiFi Router Login/> /> Username setUser(ev.target.value)} value=${user} /> /> Password setPass(ev.target.value)} value=${pass} onchange=${onsubmit} /> /> <${Button} title="Sign In" icon=${IconLogout} onclick=${onsubmit} cls="flex w-full justify-center" /> /> To login, use: admin/admin, user1/user1, user2/user2 /> /> />`; }; function Header({logout, user, setShowSidebar, showSidebar}) { return html` setShowSidebar(v => !v)} class="text-slate-400"> <${IconBars3} class="h-6" /> /> /> logged in as: ${user}/> /> <${Button} title="Logout" icon=${IconLogout} onclick=${logout} /> /> /> /> />`; }; const NavLink = ({title, icon, href, url}) => html` <${icon} class="w-6 h-6"/> ${title} //> />`; function Sidebar({url, show}) { return html` <${Logo} class="h-full"/> Your Brand /> <${NavLink} title="Dashboard" icon=${IconHome} href="/" url=${url} /> <${NavLink} title="Connected Devices" icon=${IconLink} href="/devices" url=${url} /> <${NavLink} title="Firewall" icon=${IconShield} href="/firewall" url=${url} /> <${NavLink} title="DHCP" icon=${IconBarsDown} href="/dhcp" url=${url} /> <${NavLink} title="Administration" icon=${IconSettings} href="/admin" url=${url} /> /> /> />`; }; function Colored({icon, text, colors}) { return html` ${icon && html`<${icon} class="w-5 h-5" />`} ${text}/> />`; }; function Stat({title, text, tipText, tipIcon, tipColors}) { return html` ${title} /> ${text} /> <${Colored} text=${tipText} icon=${tipIcon} colors=${tipColors} /> /> /> /> />`; }; function Events({}) { const [events, setEvents] = useState([]); const refresh = () => fetch('api/events/get').then(r => r.json()).then(r => setEvents(r)).catch(e => console.log(e)); useEffect(refresh, []); const Th = props => html`${props.title}`; const Td = props => html`${props.text}`; const Prio = ({prio}) => { const text = ['high', 'medium', 'low'][prio]; const colors = [tipColors.red, tipColors.yellow, tipColors.green][prio]; return html`<${Colored} colors=${colors} text=${text} />`; }; const Event = ({e}) => html` <${Td} text=${['network', 'optic', 'power', 'routing'][e.type]} /> <${Td} text=${html`<${Prio} prio=${e.prio}/>`} /> <${Td} text=${e.time || '1970-01-01'} /> <${Td} text=${e.text} /> />`; //console.log(events); return html` Event Log /> <${Th} title="Type" /> <${Th} title="Prio" /> <${Th} title="Time" /> <${Th} title="Description" /> ${events.map(e => h(Event, {e}))} /> />`; }; function ModemStatus({}) { const th = props => html` ${props.title} />`; const td = props => html` ${props.text} />`; const tr = props => html`${props.cols.map(text => h(td, {text}))}/>`; return html` Cable Modem Status /> <${th} title="" /> <${th} title="Status" /> <${th} title="Comment" /> <${tr} cols=${['Downstream Channel (Hz)', '4820000000', 'Locked']} /> <${tr} cols=${['Upstream Channel (Hz)', '3280000', 'Ranged']} /> <${tr} cols=${['Provisioning State', 'Online', 'Operational']} /> /> />`; }; const range = (start, size, step) => Array.from({length: size}, (_, i) => i * (step || 1) + start); function SpeedChart({stats}) { const us = stats.upload_speed, ds = stats.download_speed; const n = us.length /* entries */, w = 20 /* entry width */, ls = 15/* left space */; const h = 100 /* graph height */, yticks = 5 /* Y axis ticks */, bs = 10 /* bottom space */; const ymax = 500; const yt = i => (h - bs) / yticks * (i + 1); const bh = p => (h - bs) * p / 100; // Bar height const by = p => (h - bs) - bh(p); // console.log(ds); return html` Internet Speed, last 24h /> ${range(0, yticks).map(i => html` ${ymax-ymax/yticks*(i+1)}/> `)} ${range(0, n).map(x => html` ${x*2}:00/> `)} /> /> />`; }; function Main({}) { const [stats, setStats] = useState(null); const refresh = () => fetch('api/stats/get').then(r => r.json()).then(r => setStats(r)); useEffect(refresh, []); if (!stats) return ''; return html` <${Stat} title="Download Speed" text="${stats.download_speed[0]} Mbps" tipText="slow" tipIcon=${IconAlert} tipColors=${tipColors.red} /> <${Stat} title="Upload Speed" text="${stats.upload_speed[0]} Mbps" tipText="good" tipIcon=${IconOk} tipColors=${tipColors.green} /> <${Stat} title="Daily Downloaded" text="${stats.downloaded} Mb" tipText="-2.2%" tipIcon=${IconArrowDown} /> <${Stat} title="Daily Uploaded" text="${stats.uploaded} Mb" tipText="+12.7%" tipIcon=${IconArrowUp} /> /> /> <${SpeedChart} stats=${stats} /> <${Events} /> <${ModemStatus} /> /> />`; }; function TextValue({value, setfn, disabled, placeholder, type, addonRight, addonLeft, attr}) { const f = type == 'number' ? x => setfn(parseInt(x)) : setfn; return html` ${ addonLeft && html`${addonLeft}>` } f(ev.target.value)} ...${attr} class="font-normal text-sm rounded w-full flex-1 py-0.5 px-2 text-gray-700 placeholder:text-gray-400 focus:outline-none disabled:cursor-not-allowed disabled:bg-gray-100 disabled:text-gray-500" placeholder=${placeholder} value=${value} /> ${ addonRight && html`${addonRight}>` } />`; }; function SelectValue({value, setfn, options}) { const toInt = x => x == parseInt(x) ? parseInt(x) : x; const onchange = ev => setfn(toInt(ev.target.value)); return html` ${options.map(v => html`${v[1]}/>`) } />`; }; function SwitchValue({value, setfn}) { const onclick = ev => setfn(!value); const bg = !!value ? 'bg-blue-600' : 'bg-gray-200'; const tr = !!value ? 'translate-x-5' : 'translate-x-0'; return html` `; }; function Setting(props) { return html` ${props.title}/> ${props.type == 'switch' ? h(SwitchValue, props) : props.type == 'select' ? h(SelectValue, props) : h(TextValue, props) } /> />`; }; function DHCP({}) { const [dhcp, setDhcp] = useState(null); const [saveResult, setSaveResult] = useState(null); const refresh = () => fetch('api/dhcp/get') .then(r => r.json()) .then(r => setDhcp(r)); useEffect(refresh, []); const mksetfn = k => (v => setDhcp(x => Object.assign({}, x, {[k]: v}))); const onsave = ev => fetch('api/dhcp/set', { method: 'post', body: JSON.stringify(dhcp) }).then(r => r.json()) .then(r => setSaveResult(r)) .then(refresh); if (!dhcp) return ''; return html` DHCP Server Settings /> ${saveResult && html`<${Toast} ok=${saveResult.status} text=${saveResult.message} close=${() => setSaveResult(null)} />`} <${Setting} title="Enabled" value=${dhcp.enabled} setfn=${mksetfn('enabled')} type="switch" /> <${Setting} title="Start Address" value=${dhcp.address_begin} setfn=${mksetfn('address_begin')} type="number" addonLeft="192.168.0." disabled=${!dhcp.enabled} /> <${Setting} title="End Address" value=${dhcp.address_end} setfn=${mksetfn('address_end')} type="number" addonLeft="192.168.0." disabled=${!dhcp.enabled} /> <${Setting} title="Lease Time" value=${dhcp.lease_time_sec} setfn=${mksetfn('lease_time_sec')} type="number" addonRight="seconds" disabled=${!dhcp.enabled} /> <${Button} icon=${IconSave} onclick=${onsave} title="Save Settings" />/> /> /> />`; }; const App = function({}) { const [loading, setLoading] = useState(true); const [url, setUrl] = useState('/'); const [user, setUser] = useState(''); const [showSidebar, setShowSidebar] = useState(true); const logout = () => fetch('api/logout').then(r => setUser('')); const login = r => !r.ok ? setLoading(false) : r.json() .then(r => setUser(r.user)) .finally(r => setLoading(false)); useEffect(() => fetch('api/login').then(login), []); if (loading) return ''; // Show blank page on initial load if (!user) return html`<${Login} login=${login} />`; // If not logged in, show login screen return html` <${Sidebar} url=${url} show=${showSidebar} /> <${Header} logout=${logout} user=${user} showSidebar=${showSidebar} setShowSidebar=${setShowSidebar} /> <${Router} onChange=${ev => setUrl(ev.url)} history=${History.createHashHistory()} > <${Main} default=${true} /> <${DHCP} path="dhcp" /> /> /> />`; }; window.onload = ev => render(h(App), document.body);
${text}
Anyone with a link can now view this file.
${title}