Modern Web UI for OpenWrt

Hey all,

I'm new to OpenWrt but have been using it for a few months now. LuCI is great but I wanted something cleaner and faster, so I built an alternative web interface.

What it is

  • Pure vanilla JavaScript SPA
  • Uses OpenWrt's native ubus API
  • Works alongside LuCI - doesn't replace it, just gives you another option

Repo

Features

  • Dashboard with live stats, bandwidth graphs, system logs
  • Network management (interfaces, WiFi, firewall, DHCP, DNS)
  • System tools (backup/restore, packages, services, SSH keys)
  • WireGuard VPN, DDNS, QoS

Important disclaimer
I've only tested this on QEMU and my own router (running as a downstream AP). It should work on standard router configs, but I'd love to hear if it breaks on your setup. The WAN detection tries to be generic (finds any interface with a default route), but there are probably edge cases I haven't hit.

Contributing
If you find bugs, have suggestions, or want to add features - PRs welcome.
If this is useful to anyone, great. If not, no worries - I learned a lot building it. Either way, feedback appreciated!

3 Likes

Nice :wink:


Probably luci can benefit concatenating javascript hmmmm

2 Likes

Thanks for the callout, this wasn't an accurate representation of the attack surface and I've updated it - the main difference is execution server-side vs client-side.

Edit: Hmm, looking into LuCI more closely it appears I may still be mistaken - if most operations are currently done via ucode / rpcd rather than server side LUA then security is equivalent... Would appreciate any correction here.

1 Like

It looks like you put a lot of effort into this - so well done.

My view: You will get a few curious techies giving it a try.
But if you want to learn a lot more about some other aspects of OpenWrt AND gain some real traction with this, consider reorganising your Github with version branches, introduce semantic versioning and build an apk/ipk beta.
Initially more people are likely to test - particularly the less teckkie. Then do a proper release (git release tag) and submit it to the OpenWrt package feeds.

1 Like

The lua-ectomy is pretty much complete, there is very little lua left in LuCI, and what is still there is in older packages, not the base code.

But, back to regular programming. How modular is it? I've been looking at adding a LuCI "collection" (the top-level package you install to get certain features, like luci vs luci-ssl vs luci-light), that is suitable for use on layer 2 devices: APs and switches. It would have no DHCP, firewall or other layer 3 stuff that's not applicable... It would mainly be just a status tool with some VLAN and interface management tools. Would your approach be amenable to this sort of thing?

1 Like

The lua-ectomy is pretty much complete, there is very little lua left in LuCI, and what is still there is in older packages, not the base code.

Good to know, thanks!

But, back to regular programming. How modular is it? I've been looking at adding a LuCI "collection" (the top-level package you install to get certain features, like luci vs luci-ssl vs luci-light), that is suitable for use on layer 2 devices: APs and switches. It would have no DHCP, firewall or other layer 3 stuff that's not applicable... It would mainly be just a status tool with some VLAN and interface management tools. Would your approach be amenable to this sort of thing?

The answer to this was not modular at all initially, but based on your comment I added modularity through a configuration file with a couple of templates to cover common scenarios (open to adding more!). The frontend will then conditionally import / display elements based on enabled / disabled feature flags.

Appreciate the feedback here, implementing this now.

First, the demo link takes me to the sales-pitch, so I don't really know what it looks like in real-life.

Then I very briefly looked at the source code and I think this needs some love. At the very least the distributed code should be minimized. Probably a linter wouldn't hurt as well. That's on top of all the above comments about packages.

There are multiple places where the only thing an async function does is await another call without error handling or returning the result. Which can be reduced to a non-async function returning the result of the call.

return await is pointless if it's not inside a try..catch as you're wrapping a result of a promise inside of another promise.

Speaking of which, wrapping whole function bodies inside a try..catch is overkill, and error handling should be more granular. In general there's almost no error handling.

Mixing async/await and method chaining looks odd. Pick a style and stick to it.

Lots of methods mix domain and presentation logic, e.g. parseSSHKeys.

There's some very creative ubus and shell pipeline code, e.g loadWireGuardConfig. Does it even work? A server-side helper script would make more sense, even though it goes against the main selling point.

And why are all ubus file write calls so different: some are base64, some not?

Interacting with UCI is very inconsistent. Sometimes helper methods are used, and sometimes direct ubus calls.

On a side note, commit messages look unprofessional and don't describe the changes.

Overall, I'd be very weary letting a multi-thousand line long JS script from an unofficial repo that's allowed to execute code run on a router.

Also, the name - Based?

Oh, and check out:

There's some creative math wrt units in the code.

2 Likes

Brutal but fair, appreciate the feedback. Will update the demo to display proper screenshots and feature walkthrough.

In terms of the code - yep it's rough. This started out as a project just for myself then I figured I'd try to expand it, but that of course led to a mess. Most of the issues are addressed or will be addressed by the first release; tons of refactoring is already underway in the dev branch.

As for the name, I like it, but I'm open to other suggestions.

Updates since last post

  • Released version 0.0.6 addressing much of the feedback from this thread
    • ubus calls now have proper timeouts (AbortController) and retry with exponential backoff
    • Network and System modules fully rewritten with granular error handling instead of whole-function try/catch
    • Added consistent UCI helper usage throughout (uciGet, uciSet, uciAdd, uciDelete)
    • Polling pauses when the tab is hidden, resumes on focus
    • Build pipeline minifies all JS and CSS for distribution
    • Fixed unit conversion math for data rates
  • Rebranded the interface to MoCI. I had originally named it luci-theme-based, but after attempting and failing to design within the bounds of the LUA system, moved to a web based application. The name "Based" stuck around, but it ultimately wasn't fitting. The new name is Modern Configuration Interface which is much more appropriate.
  • Began work on a Desktop App. The goal behind this is to allow users who may not wish or may not be able to host the UI on their router an alternative method of using the interface. Since the files are static anyways, it's relatively easy to bundle this into a cross-platform application and extend functionality slightly to allow the user to select the target router IP to forward commands to. The app is self-contained and the existing, primarily recommended approach of deploying interface files to the router is unchanged.
  • Live demo at https://hudsongraeme.github.io/MoCI/ which stays up-to-date and provides a full example interface to play around with, rather than a landing page.