Add DHCP settings

This commit is contained in:
cpq 2023-05-23 08:43:57 +01:00
parent a8309ef705
commit 923f2c7635
3 changed files with 164 additions and 14 deletions

View File

@ -20,6 +20,16 @@ struct event {
const char *text;
};
// DHCP configuration
struct dhcp {
bool enabled;
uint8_t address_begin;
uint8_t address_end;
unsigned long lease_time_sec;
};
static struct dhcp s_dhcp = {true, 10, 255, 86400};
// Mocked events
static struct event s_events[] = {
{.type = 0, .prio = 0, .text = "here goes event 1"},
@ -31,12 +41,6 @@ static struct event s_events[] = {
{.type = 1, .prio = 1, .text = "oops. it happened again"},
};
static int event_next(int no, struct event *e) {
if (no < 0 || no >= (int) (sizeof(s_events) / sizeof(s_events[0]))) return 0;
*e = s_events[no];
return no + 1;
}
static const char *s_json_header =
"Content-Type: application/json\r\n"
"Cache-Control: no-cache\r\n";
@ -62,6 +66,12 @@ static const char *s_ssl_key =
"6YbyU/ZGtdGfbaGYYJwatKNMX00OIwtb8A==\n"
"-----END EC PRIVATE KEY-----\n";
static int event_next(int no, struct event *e) {
if (no < 0 || no >= (int) (sizeof(s_events) / sizeof(s_events[0]))) return 0;
*e = s_events[no];
return no + 1;
}
// SNTP connection event handler. When we get a response from an SNTP server,
// adjust s_boot_timestamp. We'll get a valid time from that point on
static void sfn(struct mg_connection *c, int ev, void *ev_data, void *fn_data) {
@ -160,10 +170,10 @@ static size_t print_events(void (*out)(char, void *), void *ptr, va_list *ap) {
int no = 0;
while ((no = event_next(no, &e)) != 0) {
len += mg_xprintf(out, ptr, "%s{%m:%lu,%m:%d,%m:%d,%m:%m}", //
len == 0 ? "" : ",", //
MG_ESC("time"), e.timestamp, //
MG_ESC("type"), e.type, //
MG_ESC("prio"), e.prio, //
len == 0 ? "" : ",", //
MG_ESC("time"), e.timestamp, //
MG_ESC("type"), e.type, //
MG_ESC("prio"), e.prio, //
MG_ESC("text"), MG_ESC(e.text));
}
(void) ap;
@ -174,6 +184,28 @@ static void handle_events_get(struct mg_connection *c) {
mg_http_reply(c, 200, s_json_header, "[%M]", print_events);
}
static void handle_dhcp_set(struct mg_connection *c, struct mg_str body) {
struct dhcp dhcp = {};
mg_json_get_bool(body, "$.enabled", &dhcp.enabled);
dhcp.address_begin = mg_json_get_long(body, "$.address_begin", 0);
dhcp.address_end = mg_json_get_long(body, "$.address_end", 0);
dhcp.lease_time_sec = mg_json_get_long(body, "$.lease_time_sec", 0);
s_dhcp = dhcp; // Save to the device flash, too
bool ok = true;
mg_http_reply(c, 200, s_json_header,
"{%m:%s,%m:%m}", //
MG_ESC("status"), ok ? "true" : "false", //
MG_ESC("message"), MG_ESC(ok ? "Success" : "Failed"));
}
static void handle_dhcp_get(struct mg_connection *c) {
mg_http_reply(c, 200, s_json_header, "{%m:%s,%m:%hhu,%m:%hhu,%m:%lu}", //
MG_ESC("enabled"), s_dhcp.enabled ? "true" : "false", //
MG_ESC("address_begin"), s_dhcp.address_begin, //
MG_ESC("address_end"), s_dhcp.address_end, //
MG_ESC("lease_time_sec"), s_dhcp.lease_time_sec);
}
// HTTP request handler function
static void fn(struct mg_connection *c, int ev, void *ev_data, void *fn_data) {
if (ev == MG_EV_ACCEPT && fn_data != NULL) {
@ -195,6 +227,10 @@ static void fn(struct mg_connection *c, int ev, void *ev_data, void *fn_data) {
handle_stats_get(c);
} else if (mg_http_match_uri(hm, "/api/events/get")) {
handle_events_get(c);
} else if (mg_http_match_uri(hm, "/api/dhcp/get")) {
handle_dhcp_get(c);
} else if (mg_http_match_uri(hm, "/api/dhcp/set")) {
handle_dhcp_set(c, hm->body);
} else {
struct mg_http_serve_opts opts;
memset(&opts, 0, sizeof(opts));

File diff suppressed because one or more lines are too long

View File

@ -55,6 +55,44 @@ function Button({title, onclick, disabled, cls, icon, ref, colors, hovercolor, d
<//>`
};
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`
<div aria-live="assertive" class="z-10 pointer-events-none absolute inset-0 flex items-end px-4 py-6 sm:items-start sm:p-6">
<div class="flex w-full flex-col items-center space-y-4 sm:items-end">
<div class="pointer-events-auto w-full max-w-sm overflow-hidden rounded-lg bg-white shadow-lg ring-1 ring-black ring-opacity-5 transform ease-out duration-300 transition ${tr}">
<div class="p-4">
<div class="flex items-start">
<div class="flex-shrink-0">
<${ok ? IconOk : IconFailed} class="h-6 w-6 ${ok ? 'text-green-400' : 'text-red-400'}" />
<//>
<div class="ml-3 w-0 flex-1 pt-0.5">
<p class="text-sm font-medium text-gray-900">${text}</p>
<p class="hidden mt-1 text-sm text-gray-500">Anyone with a link can now view this file.</p>
<//>
<div class="ml-4 flex flex-shrink-0">
<button type="button" ref=${closebtn} onclick=${onclose} class="inline-flex rounded-md bg-white text-gray-400 hover:text-gray-500 focus:outline-none">
<span class="sr-only">Close</span>
<svg class="h-5 w-5" viewBox="0 0 20 20" fill="currentColor" aria-hidden="true">
<path d="M6.28 5.22a.75.75 0 00-1.06 1.06L8.94 10l-3.72 3.72a.75.75 0 101.06 1.06L10 11.06l3.72 3.72a.75.75 0 101.06-1.06L11.06 10l3.72-3.72a.75.75 0 00-1.06-1.06L10 8.94 6.28 5.22z" />
<//>
<//>
<//>
<//>
<//>
<//>
<//>
<//>`;
};
function Login({login}) {
const [user, setUser] = useState('');
const [pass, setPass] = useState('');
@ -298,8 +336,84 @@ function Main({}) {
<//>`;
};
function Devices({}) {
return html`<div class="p-2">Devices<//>`;
function TextValue({value, setfn, disabled, placeholder, type, addonRight, addonLeft, attr}) {
const f = type == 'number' ? x => setfn(parseInt(x)) : setfn;
return html`
<div class="flex w-full items-center rounded border shadow-sm">
${ addonLeft && html`<span class="inline-flex font-normal truncate py-1 border-r bg-slate-100 items-center border-gray-300 px-2 text-gray-500 text-xs">${addonLeft}</>` }
<input type=${type || 'text'} disabled=${disabled}
oninput=${ev => 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`<span class="inline-flex font-normal truncate py-1 border-l bg-slate-100 items-center border-gray-300 px-2 text-gray-500 text-xs">${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`
<select onchange=${onchange} class="w-full rounded font-normal border py-0.5 px-1 text-gray-600 focus:outline-none text-sm">
${options.map(v => html`<option value=${v[0]} selected=${v[0] == value}>${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`
<button type="button" onclick=${onclick} class="${bg} inline-flex h-6 w-11 flex-shrink-0 cursor-pointer rounded-full border-2 border-transparent transition-colors duration-200 ease-in-out focus:outline-none focus:ring-0 ring-0" role="switch" aria-checked=${!!value}>
<span aria-hidden="true" class="${tr} pointer-events-none inline-block h-5 w-5 transform rounded-full bg-white shadow ring-0 focus:ring-0 transition duration-200 ease-in-out"></span>
</button>`;
};
function Setting(props) {
return html`
<div class=${props.cls || 'grid grid-cols-2 gap-2 my-1'}>
<label class="flex items-center text-sm text-gray-700 mr-2 font-medium">${props.title}<//>
<div class="flex items-center">
${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`
<div class="m-4 grid grid-cols-1 gap-4 md:grid-cols-2">
<div class="py-1 divide-y border rounded bg-white flex flex-col">
<div class="font-light uppercase flex items-center text-gray-600 px-4 py-2">
DHCP Server Settings
<//>
<div class="py-2 px-5 flex-1 flex flex-col relative">
${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} />
<div class="mb-1 mt-3 flex place-content-end"><${Button} icon=${IconSave} onclick=${onsave} title="Save Settings" /><//>
<//>
<//>
<//>`;
};
const App = function({}) {
@ -325,7 +439,7 @@ const App = function({}) {
<div class="${showSidebar && 'pl-72'} transition-all duration-300 transform">
<${Router} onChange=${ev => setUrl(ev.url)} history=${History.createHashHistory()} >
<${Main} default=${true} />
<${Devices} path="aa" />
<${DHCP} path="dhcp" />
<//>
<//>
<//>`;