v0.5.43-beta - van-tot omrekenen naar een CIDR-blok
Some checks failed
Deploy server-up (dev) / deploy (push) Has been cancelled
Some checks failed
Deploy server-up (dev) / deploy (push) Has been cancelled
"Ik wil van 10.0.20.200 tot .254" bestaat niet als CIDR-notatie: 55 adressen, terwijl Docker alleen uitgelijnde blokken van een macht van twee accepteert. Dat is niet iets wat een gebruiker zelf hoort uit te rekenen. - Nieuwe range_options(): vertaalt een gewenst begin- en eindadres naar twee bruikbare blokken. "Ruim" is het kleinste blok dat de hele wens omvat, "krap" het grootste blok dat er volledig binnen valt. Met per optie hoeveel adressen erbij komen of ontbreken. - Endpoint POST /api/networks/range en een uitklapbare van-tot-hulp onder het IP-range-veld; klikken op een optie vult het veld. - Voor 10.0.20.200-254 levert dat 10.0.20.192/26 op: 62 adressen, .193 t/m .254. Co-Authored-By: Claude Opus 5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01C7oLCRYzY5ixJ5Sv8Y8EFb
This commit is contained in:
parent
cbb232d63f
commit
b30f83866f
8 changed files with 219 additions and 3 deletions
18
CHANGELOG.md
18
CHANGELOG.md
|
|
@ -1,3 +1,21 @@
|
|||
# v0.5.43-beta — Van-tot omrekenen naar een CIDR-blok
|
||||
|
||||
"Ik wil van 10.0.20.200 tot .254" is een volstrekt redelijke wens, maar bestaat
|
||||
niet als CIDR-notatie: dat zijn 55 adressen, en Docker accepteert alleen
|
||||
uitgelijnde blokken van 2, 4, 8, 16 … adressen.
|
||||
|
||||
Onder het IP-range-veld zit nu een **van-tot-hulp**. Vul de eerste en laatste
|
||||
gewenste adressen in en Server Up rekent uit welke blokken in de buurt komen:
|
||||
|
||||
* **dekt alles** — het kleinste blok dat je hele wens omvat (pakt er onderaan
|
||||
wat bij)
|
||||
* **past binnen** — het grootste blok dat volledig binnen je wens valt
|
||||
|
||||
Bij t/m levert dat op: 62 adressen, t/m
|
||||
. Klikken vult het veld in.
|
||||
|
||||
---
|
||||
|
||||
# v0.5.42-beta — Netwerk toevoegen: twee blokkades weg
|
||||
|
||||
Een netwerk aanmaken lukte niet, met wisselend gedrag: soms een foutmelding dat
|
||||
|
|
|
|||
2
VERSION
2
VERSION
|
|
@ -1 +1 @@
|
|||
0.5.42-beta
|
||||
0.5.43-beta
|
||||
|
|
|
|||
|
|
@ -1402,6 +1402,14 @@ def api_networks():
|
|||
drivers=list(networks.DRIVERS))
|
||||
|
||||
|
||||
@app.route("/api/networks/range", methods=["POST"])
|
||||
def api_networks_range():
|
||||
"""Zet "van adres X tot adres Y" om in bruikbare CIDR-blokken."""
|
||||
d = request.json or {}
|
||||
return jsonify(networks.range_options(
|
||||
d.get("first", ""), d.get("last", ""), d.get("subnet", "")))
|
||||
|
||||
|
||||
@app.route("/api/networks/review", methods=["POST"])
|
||||
def api_networks_review():
|
||||
"""Beoordeel een definitie zonder iets aan te maken (live in het formulier)."""
|
||||
|
|
|
|||
|
|
@ -390,6 +390,74 @@ def _bruikbaar_bereik(net, gateway: str = ""):
|
|||
return eerste, laatste, max(aantal, 0)
|
||||
|
||||
|
||||
def range_options(eerste: str, laatste: str, subnet: str = "") -> dict:
|
||||
"""Vertaal "ik wil van X tot Y" naar bruikbare CIDR-blokken.
|
||||
|
||||
Docker accepteert één `--ip-range`, en dat moet een uitgelijnd blok zijn met
|
||||
een macht van twee als grootte. Een wens als `.200 t/m .254` (55 adressen)
|
||||
past daar niet in. Deze functie geeft daarom twee bruikbare antwoorden:
|
||||
|
||||
* **ruim** — het kleinste blok dat je hele wens omvat; pakt er wat adressen
|
||||
onderaan bij
|
||||
* **krap** — het grootste blok dat volledig bínnen je wens valt; nooit te
|
||||
veel, maar mogelijk minder dan gevraagd
|
||||
|
||||
Bijna altijd is "ruim" wat je wilt, mits je DHCP-bereik eronder blijft.
|
||||
"""
|
||||
try:
|
||||
f = ipaddress.ip_address(eerste.strip())
|
||||
l = ipaddress.ip_address(laatste.strip())
|
||||
except (ValueError, AttributeError):
|
||||
return {"ok": False, "msg": "Vul twee geldige IP-adressen in.", "options": []}
|
||||
if f.version != 4 or l.version != 4:
|
||||
return {"ok": False, "msg": "Alleen IPv4 wordt ondersteund.", "options": []}
|
||||
if int(l) < int(f):
|
||||
f, l = l, f
|
||||
|
||||
net = None
|
||||
if subnet:
|
||||
try:
|
||||
net = ipaddress.ip_network(subnet, strict=False)
|
||||
if f not in net or l not in net:
|
||||
return {"ok": False, "options": [],
|
||||
"msg": f"Die adressen liggen niet allebei binnen {net}."}
|
||||
except ValueError:
|
||||
net = None
|
||||
|
||||
gewenst = int(l) - int(f) + 1
|
||||
opties = []
|
||||
|
||||
# Ruim: opklimmen tot beide adressen erin passen.
|
||||
omvattend = ipaddress.ip_network(f"{f}/32")
|
||||
for _ in range(32):
|
||||
if l in omvattend:
|
||||
break
|
||||
omvattend = omvattend.supernet()
|
||||
if l in omvattend and (net is None or omvattend.subnet_of(net)):
|
||||
opties.append(_range_optie("ruim", omvattend, gewenst))
|
||||
|
||||
# Krap: het grootste blok uit de exacte opdeling van het bereik.
|
||||
try:
|
||||
stukken = list(ipaddress.summarize_address_range(f, l))
|
||||
except Exception:
|
||||
stukken = []
|
||||
if stukken:
|
||||
grootste = max(stukken, key=lambda n: n.num_addresses)
|
||||
if not opties or str(grootste) != opties[0]["cidr"]:
|
||||
opties.append(_range_optie("krap", grootste, gewenst))
|
||||
|
||||
return {"ok": bool(opties), "options": opties, "wanted": gewenst,
|
||||
"msg": "" if opties else "Geen passend blok gevonden."}
|
||||
|
||||
|
||||
def _range_optie(soort: str, blok, gewenst: int) -> dict:
|
||||
eerste, laatste, aantal = _bruikbaar_bereik(blok)
|
||||
return {"kind": soort, "cidr": str(blok), "first": str(eerste),
|
||||
"last": str(laatste), "count": aantal,
|
||||
"extra": max(blok.num_addresses - gewenst, 0),
|
||||
"short": max(gewenst - blok.num_addresses, 0)}
|
||||
|
||||
|
||||
def normalize(net: dict) -> dict:
|
||||
"""Zet subnet en ip_range om naar hun canonieke vorm.
|
||||
|
||||
|
|
|
|||
|
|
@ -972,6 +972,41 @@ tailwind.config = {
|
|||
</button>
|
||||
</div>
|
||||
<p class="text-[11px] text-dm dark:text-dm-dark mt-1" x-text="t('ip_range_hint')"></p>
|
||||
|
||||
<!-- Van-tot omrekenen. CIDR kan alleen uitgelijnde blokken van een
|
||||
macht van twee, dus '.200 tot .254' bestaat niet als notatie. -->
|
||||
<details class="mt-2 rounded-xl border border-bd dark:border-bd-dark overflow-hidden">
|
||||
<summary class="px-3 py-2 cursor-pointer text-[11px] font-bold text-wh dark:text-wh-dark bg-s2/60 dark:bg-s2-dark/60 flex items-center gap-1.5">
|
||||
<span class="mdi mdi-calculator-variant-outline"></span>
|
||||
<span x-text="t('range_helper')">Ik wil een bepaald bereik</span>
|
||||
</summary>
|
||||
<div class="p-3 space-y-2">
|
||||
<div class="flex items-center gap-2">
|
||||
<span class="text-[11px] text-t2 dark:text-t2-dark shrink-0" x-text="t('from')">van</span>
|
||||
<input class="form-input !h-9 font-mono !text-xs" x-model="rangeCalc.first"
|
||||
@input="calcRange()" placeholder="10.0.20.200">
|
||||
<span class="text-[11px] text-t2 dark:text-t2-dark shrink-0" x-text="t('to')">tot</span>
|
||||
<input class="form-input !h-9 font-mono !text-xs" x-model="rangeCalc.last"
|
||||
@input="calcRange()" placeholder="10.0.20.254">
|
||||
</div>
|
||||
<p class="text-[11px] text-dm dark:text-dm-dark" x-text="t('range_helper_hint')"></p>
|
||||
<p x-show="rangeCalc.msg" class="text-[11px] text-wr" x-text="rangeCalc.msg"></p>
|
||||
<template x-for="o in (rangeCalc.options || [])" :key="o.cidr">
|
||||
<button type="button" @click="newNet.ip_range = o.cidr; reviewNet()"
|
||||
class="w-full text-left px-3 py-2 rounded-lg border border-bd dark:border-bd-dark hover:border-ac transition">
|
||||
<div class="flex items-center gap-2">
|
||||
<span class="font-mono text-xs font-semibold text-wh dark:text-wh-dark" x-text="o.cidr"></span>
|
||||
<span class="badge" :class="o.kind === 'ruim' ? 'badge-ok' : 'badge-dim'"
|
||||
x-text="t('range_' + o.kind)"></span>
|
||||
<span class="text-[11px] text-t2 dark:text-t2-dark ml-auto"
|
||||
x-text="t('range_count', {count: o.count})"></span>
|
||||
</div>
|
||||
<div class="text-[10px] font-mono text-dm dark:text-dm-dark mt-0.5"
|
||||
x-text="o.first + ' … ' + o.last"></div>
|
||||
</button>
|
||||
</template>
|
||||
</div>
|
||||
</details>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
|
|
@ -1309,6 +1344,7 @@ function app() {
|
|||
// Macvlan/ipvlan-netwerken waarmee stacks een eigen IP krijgen
|
||||
netList: [], netIfaces: [], netDetected: [], netDetectError: '',
|
||||
netReview: {}, netReviewTimer: null, netReviewSeq: 0,
|
||||
rangeCalc: { first: '', last: '', options: [], msg: '' }, rangeCalcTimer: null,
|
||||
newNet: { name: '', driver: 'macvlan', parent: '', subnet: '', gateway: '', ip_range: '' },
|
||||
|
||||
docker: { running: 0, containers: 0, images: 0 },
|
||||
|
|
@ -1760,6 +1796,19 @@ function app() {
|
|||
}
|
||||
this.reviewNet();
|
||||
},
|
||||
// 'Van adres X tot Y' omrekenen naar een CIDR-blok dat Docker accepteert.
|
||||
calcRange() {
|
||||
if (this.rangeCalcTimer) clearTimeout(this.rangeCalcTimer);
|
||||
this.rangeCalcTimer = setTimeout(async () => {
|
||||
const { first, last } = this.rangeCalc;
|
||||
if (!first || !last) { this.rangeCalc.options = []; this.rangeCalc.msg = ''; return; }
|
||||
const d = await this.rpc('/api/networks/range',
|
||||
{ first, last, subnet: this.newNet.subnet });
|
||||
this.rangeCalc.options = d.options || [];
|
||||
this.rangeCalc.msg = d.ok ? '' : (d.msg || '');
|
||||
}, 350);
|
||||
},
|
||||
|
||||
// Live meelezen terwijl je typt; even wachten zodat niet elke toetsaanslag
|
||||
// een verzoek stuurt.
|
||||
reviewNet() {
|
||||
|
|
|
|||
|
|
@ -232,5 +232,12 @@
|
|||
"tip_example_title": "Example with a router at 192.168.1.1",
|
||||
"tip_example": "Subnet 192.168.1.0/24 ← same as your router\nGateway 192.168.1.1 ← your router\nIP range 192.168.1.240/28 ← .240 through .255\n\nSet your router's DHCP pool to 192.168.1.20 – 192.168.1.239 so that\nthose last sixteen addresses are reserved for your containers.",
|
||||
"suggest_range": "Suggest a free block",
|
||||
"range_result": "{count} addresses available: {first} through {last}"
|
||||
"range_result": "{count} addresses available: {first} through {last}",
|
||||
"range_helper": "I want a specific range",
|
||||
"range_helper_hint": "Docker works with blocks of 2, 4, 8, 16 … addresses starting on a fixed boundary. A wish like '.200 to .254' does not exist as a notation; the closest blocks are listed below.",
|
||||
"from": "from",
|
||||
"to": "to",
|
||||
"range_ruim": "covers all",
|
||||
"range_krap": "fits inside",
|
||||
"range_count": "{count} addresses"
|
||||
}
|
||||
|
|
|
|||
|
|
@ -232,5 +232,12 @@
|
|||
"tip_example_title": "Voorbeeld bij een router op 192.168.1.1",
|
||||
"tip_example": "Subnet 192.168.1.0/24 ← hetzelfde als je router\nGateway 192.168.1.1 ← je router\nIP-range 192.168.1.240/28 ← .240 t/m .255\n\nStel je DHCP-bereik in je router in op 192.168.1.20 – 192.168.1.239,\nzodat die laatste zestien adressen alleen voor je containers zijn.",
|
||||
"suggest_range": "Stel een vrij blok voor",
|
||||
"range_result": "{count} adressen beschikbaar: {first} tot en met {last}"
|
||||
"range_result": "{count} adressen beschikbaar: {first} tot en met {last}",
|
||||
"range_helper": "Ik wil een bepaald bereik",
|
||||
"range_helper_hint": "Docker werkt met blokken van 2, 4, 8, 16 … adressen die op een vaste grens beginnen. Een wens als '.200 tot .254' bestaat niet als notatie; hieronder staan de dichtstbijzijnde blokken.",
|
||||
"from": "van",
|
||||
"to": "tot",
|
||||
"range_ruim": "dekt alles",
|
||||
"range_krap": "past binnen",
|
||||
"range_count": "{count} adressen"
|
||||
}
|
||||
|
|
|
|||
|
|
@ -290,3 +290,62 @@ def test_detectiefout_wordt_doorgegeven(client, monkeypatch):
|
|||
d = client.get("/api/networks").get_json()
|
||||
assert d["detect_error"] == "docker niet bereikbaar"
|
||||
assert d["detected"] == []
|
||||
|
||||
|
||||
# ── Van-tot omrekenen ────────────────────────────────────────────────────────
|
||||
|
||||
def test_range_options_voor_200_tot_254():
|
||||
"""De vraag uit de praktijk: 55 adressen, wat geen geldig CIDR-blok is."""
|
||||
r = networks.range_options("10.0.20.200", "10.0.20.254", "10.0.20.0/24")
|
||||
assert r["ok"] is True
|
||||
ruim = next(o for o in r["options"] if o["kind"] == "ruim")
|
||||
assert ruim["cidr"] == "10.0.20.192/26"
|
||||
assert ruim["count"] == 62
|
||||
assert (ruim["first"], ruim["last"]) == ("10.0.20.193", "10.0.20.254")
|
||||
krap = next(o for o in r["options"] if o["kind"] == "krap")
|
||||
assert krap["cidr"] == "10.0.20.208/28"
|
||||
|
||||
|
||||
def test_range_options_exact_passend_blok():
|
||||
"""Valt de wens toevallig samen met een blok, dan is er maar één antwoord."""
|
||||
r = networks.range_options("10.0.20.240", "10.0.20.255", "10.0.20.0/24")
|
||||
assert [o["cidr"] for o in r["options"]] == ["10.0.20.240/28"]
|
||||
|
||||
|
||||
def test_range_options_draait_omgekeerde_invoer_om():
|
||||
r = networks.range_options("10.0.20.254", "10.0.20.200")
|
||||
assert r["ok"] is True
|
||||
assert r["options"][0]["cidr"] == "10.0.20.192/26"
|
||||
|
||||
|
||||
def test_range_options_buiten_het_subnet():
|
||||
r = networks.range_options("192.168.9.10", "192.168.9.20", "10.0.20.0/24")
|
||||
assert r["ok"] is False
|
||||
assert "niet allebei" in r["msg"]
|
||||
|
||||
|
||||
@pytest.mark.parametrize("eerste,laatste", [
|
||||
("geen-ip", "10.0.20.254"), ("10.0.20.200", ""), ("", ""),
|
||||
])
|
||||
def test_range_options_ongeldige_invoer(eerste, laatste):
|
||||
r = networks.range_options(eerste, laatste)
|
||||
assert r["ok"] is False
|
||||
assert r["options"] == []
|
||||
|
||||
|
||||
def test_range_options_meldt_hoeveel_er_bij_komt():
|
||||
r = networks.range_options("10.0.20.200", "10.0.20.254", "10.0.20.0/24")
|
||||
ruim = next(o for o in r["options"] if o["kind"] == "ruim")
|
||||
assert ruim["extra"] == 9 # 64 adressen in het blok, 55 gevraagd
|
||||
|
||||
|
||||
def test_range_endpoint(client):
|
||||
from conftest import login
|
||||
csrf = login(client)
|
||||
r = client.post("/api/networks/range",
|
||||
json={"first": "10.0.20.200", "last": "10.0.20.254",
|
||||
"subnet": "10.0.20.0/24"},
|
||||
headers={"X-CSRF-Token": csrf})
|
||||
d = r.get_json()
|
||||
assert d["ok"] is True
|
||||
assert d["options"][0]["cidr"] == "10.0.20.192/26"
|
||||
|
|
|
|||
Loading…
Reference in a new issue