Loading modules/network/network.cpp +97 −12 Changes for modules/network/network.cpp: 97 added lines, 12 removed lines. Original line number Diff line number Diff line Loading @@ -36,11 +36,17 @@ SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. * start | stop | restart {interface} * reload | forceReload {interface?} (none: all) * * Bonds and bridges (.netdev files; reload without an interface creates * them and adds their members): * createNetdev {netdev, kind: bond|bridge, mode?, miimon?, lacpRate?, hashPolicy?, stp?, members?[]} * updateNetdev {netdev, mode?, miimon?, lacpRate?, hashPolicy?, stp?, members?[]} * Bonds, bridges and WireGuard interfaces (.netdev files; reload without * an interface creates them, adds their members, configures WireGuard): * createNetdev {netdev, kind: bond|bridge|wireguard, mode?, miimon?, lacpRate?, hashPolicy?, stp?, members?[], * privateKey?, generateKey?, listenPort?, routes?, peers?[]} * updateNetdev {netdev, ...the same, without kind} * deleteNetdev {netdev} (also removes the device) * generateKey -> {privateKey, publicKey, presharedKey} * * A WireGuard peer: {publicKey, name?, presharedKey?, endpoint?, allowedIps?[], keepalive?}; * without presharedKey a peer keeps the one it has, "" removes it. Private * and preshared keys are never listed. */ #include <string> Loading Loading @@ -105,9 +111,73 @@ json_object *netdevJson(const sysbus::NetDevInfo &n) { json_object *m = json_object_new_array(); for (auto &x : n.members) json_object_array_add(m, jstr(x)); json_object_object_add(o, "members", m); if (n.kind == "wireguard") { json_object_object_add(o, "publicKey", jstr(n.publicKey)); json_object_object_add(o, "listenPort", json_object_new_int(n.listenPort)); json_object_object_add(o, "routes", json_object_new_boolean(n.routes)); json_object *peers = json_object_new_array(); for (auto &p : n.peers) { json_object *po = json_object_new_object(); json_object_object_add(po, "name", jstr(p.name)); json_object_object_add(po, "publicKey", jstr(p.publicKey)); json_object_object_add(po, "hasPresharedKey", json_object_new_boolean(p.hasPresharedKey)); json_object_object_add(po, "endpoint", jstr(p.endpoint)); json_object *ips = json_object_new_array(); for (auto &ip : p.allowedIps) json_object_array_add(ips, jstr(ip)); json_object_object_add(po, "allowedIps", ips); json_object_object_add(po, "keepalive", json_object_new_int(p.keepalive)); json_object_object_add(po, "currentEndpoint", jstr(p.currentEndpoint)); json_object_object_add(po, "lastHandshake", json_object_new_int64(p.lastHandshake)); json_object_object_add(po, "rxBytes", json_object_new_int64((int64_t)p.rxBytes)); json_object_object_add(po, "txBytes", json_object_new_int64((int64_t)p.txBytes)); json_object_array_add(peers, po); } json_object_object_add(o, "peers", peers); } return o; } // a JSON array of strings (members, AllowedIPs, ...); false if it isn't one bool stringList(json_object *v, std::vector<std::string> &out) { if (!json_object_is_type(v, json_type_array)) return false; for (size_t i = 0; i < json_object_array_length(v); ++i) { const char *s = json_object_get_string(json_object_array_get_idx(v, i)); if (s && *s) out.push_back(s); } return true; } bool peerSettings(json_object *peers, std::vector<sysbus::WireGuardPeerSettings> &out, std::string &err) { if (!json_object_is_type(peers, json_type_array)) { err = tr("peers must be a list", "peers muss eine Liste sein"); return false; } for (size_t i = 0; i < json_object_array_length(peers); ++i) { json_object *p = json_object_array_get_idx(peers, i); if (!json_object_is_type(p, json_type_object)) { err = tr("peers must be a list of objects", "peers muss eine Liste von Objekten sein"); return false; } sysbus::WireGuardPeerSettings ps; ps.publicKey = webctl::str(p, "publicKey"); ps.name = webctl::str(p, "name"); if (webctl::has(p, "presharedKey")) ps.presharedKey = webctl::str(p, "presharedKey"); ps.endpoint = webctl::str(p, "endpoint"); ps.keepalive = (int)webctl::integer(p, "keepalive", 0); json_object *ips = nullptr; if (json_object_object_get_ex(p, "allowedIps", &ips) && ips && !stringList(ips, ps.allowedIps)) { err = tr("allowedIps must be a list of networks", "allowedIps muss eine Liste von Netzen sein"); return false; } if (ps.publicKey.empty()) { err = tr("a peer needs a public key", "ein Peer braucht einen öffentlichen Schlüssel"); return false; } out.push_back(ps); } return true; } bool netdevSettings(json_object *a, sysbus::NetDevSettings &s, std::string &err) { s.netdev = webctl::str(a, "netdev"); s.kind = webctl::str(a, "kind"); Loading @@ -118,17 +188,23 @@ bool netdevSettings(json_object *a, sysbus::NetDevSettings &s, std::string &err) if (webctl::has(a, "stp")) s.stp = webctl::boolean(a, "stp"); json_object *members = nullptr; if (json_object_object_get_ex(a, "members", &members) && members) { if (!json_object_is_type(members, json_type_array)) { std::vector<std::string> list; if (!stringList(members, list)) { err = tr("members must be a list of interfaces", "members muss eine Liste von Interfaces sein"); return false; } std::vector<std::string> list; for (size_t i = 0; i < json_object_array_length(members); ++i) { const char *m = json_object_get_string(json_object_array_get_idx(members, i)); if (m && *m) list.push_back(m); } s.members = list; } if (webctl::has(a, "privateKey")) s.privateKey = webctl::str(a, "privateKey"); s.generateKey = webctl::boolean(a, "generateKey"); if (webctl::has(a, "listenPort")) s.listenPort = (int)webctl::integer(a, "listenPort", 0); if (webctl::has(a, "routes")) s.routes = webctl::boolean(a, "routes"); json_object *peers = nullptr; if (json_object_object_get_ex(a, "peers", &peers) && peers) { std::vector<sysbus::WireGuardPeerSettings> list; if (!peerSettings(peers, list, err)) return false; s.peers = list; } if (s.netdev.empty()) { err = tr("no bond or bridge name", "kein Name für Bond oder Bridge angegeben"); return false; Loading Loading @@ -190,11 +266,20 @@ json_object *call(const webctl_request *req) { sysbus::NetDevSettings s; std::string err; if (!netdevSettings(a, s, err)) return error(err); if (action == "createNetdev" && s.kind != "bond" && s.kind != "bridge") return error(tr("kind must be bond or bridge", "Art muss bond oder bridge sein")); if (action == "createNetdev" && s.kind != "bond" && s.kind != "bridge" && s.kind != "wireguard") return error(tr("kind must be bond, bridge or wireguard", "Art muss bond, bridge oder wireguard sein")); bool done = action == "createNetdev" ? bus.createNetDev(s) : bus.editNetDev(s); return done ? webctl::ok() : busError(bus); } if (action == "generateKey") { sysbus::WireGuardKeys k; if (!bus.generateWireGuardKey(k)) return busError(bus); json_object *out = json_object_new_object(); json_object_object_add(out, "privateKey", jstr(k.privateKey)); json_object_object_add(out, "publicKey", jstr(k.publicKey)); json_object_object_add(out, "presharedKey", jstr(k.presharedKey)); return out; } if (action == "deleteNetdev") { std::string n = webctl::str(a, "netdev"); if (n.empty()) return error(tr("no bond or bridge name", "kein Name für Bond oder Bridge angegeben")); Loading www/app.css +8 −0 Changes for www/app.css: 8 added lines, 0 removed lines. Original line number Diff line number Diff line Loading @@ -141,6 +141,14 @@ dialog h2 { font-size: 1.1rem; } .row { display: flex; gap: 10px; } .row > * { flex: 1; } /* ---- WireGuard (network) ---- */ .wg + .wg { margin-top: 1.4em; padding-top: 1em; border-top: 1px solid var(--line); } .wg .toolbar h3 { margin: 0; color: var(--text); } .wg .toolbar .badge { margin-right: auto; } .wg-info { display: grid; grid-template-columns: max-content 1fr; gap: .25em 1em; margin: 0 0 1em; } .wg-info dt { color: var(--muted); } .wg-info dd { margin: 0; overflow-wrap: anywhere; } /* ---- toasts ---- */ .toasts { position: fixed; right: 16px; bottom: 16px; display: grid; gap: 8px; z-index: 10; } .toast { Loading www/modules/network.js +334 −13 File changed.Preview size limit exceeded, changes collapsed. Show changes Loading
modules/network/network.cpp +97 −12 Changes for modules/network/network.cpp: 97 added lines, 12 removed lines. Original line number Diff line number Diff line Loading @@ -36,11 +36,17 @@ SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. * start | stop | restart {interface} * reload | forceReload {interface?} (none: all) * * Bonds and bridges (.netdev files; reload without an interface creates * them and adds their members): * createNetdev {netdev, kind: bond|bridge, mode?, miimon?, lacpRate?, hashPolicy?, stp?, members?[]} * updateNetdev {netdev, mode?, miimon?, lacpRate?, hashPolicy?, stp?, members?[]} * Bonds, bridges and WireGuard interfaces (.netdev files; reload without * an interface creates them, adds their members, configures WireGuard): * createNetdev {netdev, kind: bond|bridge|wireguard, mode?, miimon?, lacpRate?, hashPolicy?, stp?, members?[], * privateKey?, generateKey?, listenPort?, routes?, peers?[]} * updateNetdev {netdev, ...the same, without kind} * deleteNetdev {netdev} (also removes the device) * generateKey -> {privateKey, publicKey, presharedKey} * * A WireGuard peer: {publicKey, name?, presharedKey?, endpoint?, allowedIps?[], keepalive?}; * without presharedKey a peer keeps the one it has, "" removes it. Private * and preshared keys are never listed. */ #include <string> Loading Loading @@ -105,9 +111,73 @@ json_object *netdevJson(const sysbus::NetDevInfo &n) { json_object *m = json_object_new_array(); for (auto &x : n.members) json_object_array_add(m, jstr(x)); json_object_object_add(o, "members", m); if (n.kind == "wireguard") { json_object_object_add(o, "publicKey", jstr(n.publicKey)); json_object_object_add(o, "listenPort", json_object_new_int(n.listenPort)); json_object_object_add(o, "routes", json_object_new_boolean(n.routes)); json_object *peers = json_object_new_array(); for (auto &p : n.peers) { json_object *po = json_object_new_object(); json_object_object_add(po, "name", jstr(p.name)); json_object_object_add(po, "publicKey", jstr(p.publicKey)); json_object_object_add(po, "hasPresharedKey", json_object_new_boolean(p.hasPresharedKey)); json_object_object_add(po, "endpoint", jstr(p.endpoint)); json_object *ips = json_object_new_array(); for (auto &ip : p.allowedIps) json_object_array_add(ips, jstr(ip)); json_object_object_add(po, "allowedIps", ips); json_object_object_add(po, "keepalive", json_object_new_int(p.keepalive)); json_object_object_add(po, "currentEndpoint", jstr(p.currentEndpoint)); json_object_object_add(po, "lastHandshake", json_object_new_int64(p.lastHandshake)); json_object_object_add(po, "rxBytes", json_object_new_int64((int64_t)p.rxBytes)); json_object_object_add(po, "txBytes", json_object_new_int64((int64_t)p.txBytes)); json_object_array_add(peers, po); } json_object_object_add(o, "peers", peers); } return o; } // a JSON array of strings (members, AllowedIPs, ...); false if it isn't one bool stringList(json_object *v, std::vector<std::string> &out) { if (!json_object_is_type(v, json_type_array)) return false; for (size_t i = 0; i < json_object_array_length(v); ++i) { const char *s = json_object_get_string(json_object_array_get_idx(v, i)); if (s && *s) out.push_back(s); } return true; } bool peerSettings(json_object *peers, std::vector<sysbus::WireGuardPeerSettings> &out, std::string &err) { if (!json_object_is_type(peers, json_type_array)) { err = tr("peers must be a list", "peers muss eine Liste sein"); return false; } for (size_t i = 0; i < json_object_array_length(peers); ++i) { json_object *p = json_object_array_get_idx(peers, i); if (!json_object_is_type(p, json_type_object)) { err = tr("peers must be a list of objects", "peers muss eine Liste von Objekten sein"); return false; } sysbus::WireGuardPeerSettings ps; ps.publicKey = webctl::str(p, "publicKey"); ps.name = webctl::str(p, "name"); if (webctl::has(p, "presharedKey")) ps.presharedKey = webctl::str(p, "presharedKey"); ps.endpoint = webctl::str(p, "endpoint"); ps.keepalive = (int)webctl::integer(p, "keepalive", 0); json_object *ips = nullptr; if (json_object_object_get_ex(p, "allowedIps", &ips) && ips && !stringList(ips, ps.allowedIps)) { err = tr("allowedIps must be a list of networks", "allowedIps muss eine Liste von Netzen sein"); return false; } if (ps.publicKey.empty()) { err = tr("a peer needs a public key", "ein Peer braucht einen öffentlichen Schlüssel"); return false; } out.push_back(ps); } return true; } bool netdevSettings(json_object *a, sysbus::NetDevSettings &s, std::string &err) { s.netdev = webctl::str(a, "netdev"); s.kind = webctl::str(a, "kind"); Loading @@ -118,17 +188,23 @@ bool netdevSettings(json_object *a, sysbus::NetDevSettings &s, std::string &err) if (webctl::has(a, "stp")) s.stp = webctl::boolean(a, "stp"); json_object *members = nullptr; if (json_object_object_get_ex(a, "members", &members) && members) { if (!json_object_is_type(members, json_type_array)) { std::vector<std::string> list; if (!stringList(members, list)) { err = tr("members must be a list of interfaces", "members muss eine Liste von Interfaces sein"); return false; } std::vector<std::string> list; for (size_t i = 0; i < json_object_array_length(members); ++i) { const char *m = json_object_get_string(json_object_array_get_idx(members, i)); if (m && *m) list.push_back(m); } s.members = list; } if (webctl::has(a, "privateKey")) s.privateKey = webctl::str(a, "privateKey"); s.generateKey = webctl::boolean(a, "generateKey"); if (webctl::has(a, "listenPort")) s.listenPort = (int)webctl::integer(a, "listenPort", 0); if (webctl::has(a, "routes")) s.routes = webctl::boolean(a, "routes"); json_object *peers = nullptr; if (json_object_object_get_ex(a, "peers", &peers) && peers) { std::vector<sysbus::WireGuardPeerSettings> list; if (!peerSettings(peers, list, err)) return false; s.peers = list; } if (s.netdev.empty()) { err = tr("no bond or bridge name", "kein Name für Bond oder Bridge angegeben"); return false; Loading Loading @@ -190,11 +266,20 @@ json_object *call(const webctl_request *req) { sysbus::NetDevSettings s; std::string err; if (!netdevSettings(a, s, err)) return error(err); if (action == "createNetdev" && s.kind != "bond" && s.kind != "bridge") return error(tr("kind must be bond or bridge", "Art muss bond oder bridge sein")); if (action == "createNetdev" && s.kind != "bond" && s.kind != "bridge" && s.kind != "wireguard") return error(tr("kind must be bond, bridge or wireguard", "Art muss bond, bridge oder wireguard sein")); bool done = action == "createNetdev" ? bus.createNetDev(s) : bus.editNetDev(s); return done ? webctl::ok() : busError(bus); } if (action == "generateKey") { sysbus::WireGuardKeys k; if (!bus.generateWireGuardKey(k)) return busError(bus); json_object *out = json_object_new_object(); json_object_object_add(out, "privateKey", jstr(k.privateKey)); json_object_object_add(out, "publicKey", jstr(k.publicKey)); json_object_object_add(out, "presharedKey", jstr(k.presharedKey)); return out; } if (action == "deleteNetdev") { std::string n = webctl::str(a, "netdev"); if (n.empty()) return error(tr("no bond or bridge name", "kein Name für Bond oder Bridge angegeben")); Loading
www/app.css +8 −0 Changes for www/app.css: 8 added lines, 0 removed lines. Original line number Diff line number Diff line Loading @@ -141,6 +141,14 @@ dialog h2 { font-size: 1.1rem; } .row { display: flex; gap: 10px; } .row > * { flex: 1; } /* ---- WireGuard (network) ---- */ .wg + .wg { margin-top: 1.4em; padding-top: 1em; border-top: 1px solid var(--line); } .wg .toolbar h3 { margin: 0; color: var(--text); } .wg .toolbar .badge { margin-right: auto; } .wg-info { display: grid; grid-template-columns: max-content 1fr; gap: .25em 1em; margin: 0 0 1em; } .wg-info dt { color: var(--muted); } .wg-info dd { margin: 0; overflow-wrap: anywhere; } /* ---- toasts ---- */ .toasts { position: fixed; right: 16px; bottom: 16px; display: grid; gap: 8px; z-index: 10; } .toast { Loading
www/modules/network.js +334 −13 File changed.Preview size limit exceeded, changes collapsed. Show changes