Преглед на файлове

fix(rip): allow string/int version in RIP & OSPF instance JSON unmarshaling and form (v1.8.18)

Proxmox Router преди 17 часа
родител
ревизия
6a30b5468d
променени са 10 файла, в които са добавени 143 реда и са изтрити 9 реда
  1. 1 1
      VERSION
  2. BIN
      bin/debtik
  3. BIN
      bin/debtik-arm64
  4. BIN
      bin/debtik-cli
  5. BIN
      bin/debtik-cli-arm64
  6. 8 1
      internal/cli/executor.go
  7. 55 0
      internal/dynamicrouting/protocols_test.go
  8. 72 0
      internal/dynamicrouting/types.go
  9. 4 4
      internal/web/static/app.js
  10. 3 3
      internal/web/static/index.html

+ 1 - 1
VERSION

@@ -1 +1 @@
-1.8.17
+1.8.18

BIN
bin/debtik


BIN
bin/debtik-arm64


BIN
bin/debtik-cli


BIN
bin/debtik-cli-arm64


+ 8 - 1
internal/cli/executor.go

@@ -2835,7 +2835,14 @@ func (e *Executor) handleRIPInstance(parts []string, user *auth.PublicUser) stri
 		}
 		ver := 2
 		if v, ok := params["version"]; ok {
-			fmt.Sscanf(v, "%d", &ver)
+			vLower := strings.ToLower(strings.TrimSpace(v))
+			if strings.Contains(vLower, "ng") || vLower == "1" || vLower == "ripng" || vLower == "ipv6" {
+				ver = 1
+			} else if strings.Contains(vLower, "2") || vLower == "rip2" || vLower == "ipv4" {
+				ver = 2
+			} else {
+				fmt.Sscanf(v, "%d", &ver)
+			}
 		}
 		inst := dynamicrouting.RIPInstance{
 			Name:                  name,

+ 55 - 0
internal/dynamicrouting/protocols_test.go

@@ -1,6 +1,7 @@
 package dynamicrouting
 
 import (
+	"encoding/json"
 	"os"
 	"os/exec"
 	"strings"
@@ -344,3 +345,57 @@ func TestBFDAndRIPAndRAdvManagement(t *testing.T) {
 		}
 	}
 }
+
+func TestRIPAndOSPFInstanceJSONUnmarshal(t *testing.T) {
+	testCasesRIP := []struct {
+		jsonStr         string
+		expectedVersion int
+	}{
+		{`{"name":"rip1","version":2}`, 2},
+		{`{"name":"rip1","version":"2"}`, 2},
+		{`{"name":"rip1","version":"rip2"}`, 2},
+		{`{"name":"rip1","version":"ripv2"}`, 2},
+		{`{"name":"ripng1","version":1}`, 1},
+		{`{"name":"ripng1","version":"1"}`, 1},
+		{`{"name":"ripng1","version":"ripng"}`, 1},
+		{`{"name":"ripng1","version":"ng"}`, 1},
+		{`{"name":"ripng1","version":"ipv6"}`, 1},
+		{`{"name":"rip_default"}`, 2},
+	}
+
+	for idx, tc := range testCasesRIP {
+		var inst RIPInstance
+		if err := json.Unmarshal([]byte(tc.jsonStr), &inst); err != nil {
+			t.Fatalf("Case %d failed to unmarshal: %v", idx, err)
+		}
+		if inst.Version != tc.expectedVersion {
+			t.Errorf("Case %d: expected version %d, got %d", idx, tc.expectedVersion, inst.Version)
+		}
+	}
+
+	testCasesOSPF := []struct {
+		jsonStr         string
+		expectedVersion int
+	}{
+		{`{"name":"ospf1","version":2}`, 2},
+		{`{"name":"ospf1","version":"2"}`, 2},
+		{`{"name":"ospf1","version":"ospfv2"}`, 2},
+		{`{"name":"ospf1","version":"v2"}`, 2},
+		{`{"name":"ospf3","version":3}`, 3},
+		{`{"name":"ospf3","version":"3"}`, 3},
+		{`{"name":"ospf3","version":"ospfv3"}`, 3},
+		{`{"name":"ospf3","version":"v3"}`, 3},
+		{`{"name":"ospf3","version":"ipv6"}`, 3},
+		{`{"name":"ospf_default"}`, 2},
+	}
+
+	for idx, tc := range testCasesOSPF {
+		var inst OSPFInstance
+		if err := json.Unmarshal([]byte(tc.jsonStr), &inst); err != nil {
+			t.Fatalf("OSPF Case %d failed to unmarshal: %v", idx, err)
+		}
+		if inst.Version != tc.expectedVersion {
+			t.Errorf("OSPF Case %d: expected version %d, got %d", idx, tc.expectedVersion, inst.Version)
+		}
+	}
+}

+ 72 - 0
internal/dynamicrouting/types.go

@@ -1,5 +1,11 @@
 package dynamicrouting
 
+import (
+	"encoding/json"
+	"strconv"
+	"strings"
+)
+
 // BGPInstance represents global BGP settings (/routing bgp instance)
 type BGPInstance struct {
 	Enabled               bool   `json:"enabled"`
@@ -128,6 +134,39 @@ type OSPFInstance struct {
 	Running               bool   `json:"running"`
 }
 
+// UnmarshalJSON supports both int (2, 3) and string ("2", "3", "ospfv2", "ospfv3") for Version.
+func (o *OSPFInstance) UnmarshalJSON(data []byte) error {
+	type Alias OSPFInstance
+	aux := struct {
+		RawVersion interface{} `json:"version"`
+		*Alias
+	}{
+		Alias: (*Alias)(o),
+	}
+	if err := json.Unmarshal(data, &aux); err != nil {
+		return err
+	}
+	switch v := aux.RawVersion.(type) {
+	case float64:
+		o.Version = int(v)
+	case int:
+		o.Version = v
+	case string:
+		vStr := strings.ToLower(strings.TrimSpace(v))
+		if strings.Contains(vStr, "3") || strings.Contains(vStr, "v3") || vStr == "ipv6" {
+			o.Version = 3
+		} else if strings.Contains(vStr, "2") || strings.Contains(vStr, "v2") || vStr == "ipv4" {
+			o.Version = 2
+		} else if parsed, err := strconv.Atoi(vStr); err == nil {
+			o.Version = parsed
+		}
+	}
+	if o.Version <= 0 {
+		o.Version = 2
+	}
+	return nil
+}
+
 // OSPFArea represents an OSPF area (/routing ospf area)
 type OSPFArea struct {
 	ID          string `json:"id"`
@@ -311,6 +350,39 @@ type RIPInstance struct {
 	Running               bool   `json:"running"`
 }
 
+// UnmarshalJSON supports both int (2, 1) and string ("2", "1", "rip2", "ripng") for Version.
+func (r *RIPInstance) UnmarshalJSON(data []byte) error {
+	type Alias RIPInstance
+	aux := struct {
+		RawVersion interface{} `json:"version"`
+		*Alias
+	}{
+		Alias: (*Alias)(r),
+	}
+	if err := json.Unmarshal(data, &aux); err != nil {
+		return err
+	}
+	switch v := aux.RawVersion.(type) {
+	case float64:
+		r.Version = int(v)
+	case int:
+		r.Version = v
+	case string:
+		vStr := strings.ToLower(strings.TrimSpace(v))
+		if strings.Contains(vStr, "ng") || strings.Contains(vStr, "v1") || vStr == "1" || vStr == "ipv6" {
+			r.Version = 1
+		} else if strings.Contains(vStr, "2") || vStr == "ipv4" {
+			r.Version = 2
+		} else if parsed, err := strconv.Atoi(vStr); err == nil {
+			r.Version = parsed
+		}
+	}
+	if r.Version == 0 {
+		r.Version = 2
+	}
+	return nil
+}
+
 // RIPInterface represents a RIP interface template (/routing rip interface)
 type RIPInterface struct {
 	ID        string `json:"id"`

+ 4 - 4
internal/web/static/app.js

@@ -19695,7 +19695,7 @@ function renderRipInstances() {
         <td class="py-1.5 px-3 font-semibold text-white flex items-center gap-1.5">
           <span class="text-rose-400">↳</span> <span>${escapeHtml(inst.name || 'default')}</span>
         </td>
-        <td class="py-1.5 px-3 uppercase text-rose-300 font-bold">${escapeHtml(inst.version || 'rip2')}</td>
+        <td class="py-1.5 px-3 uppercase text-rose-300 font-bold">${inst.version == 1 || inst.version === 'ripng' ? 'RIPng' : 'RIPv2'}</td>
         <td class="py-1.5 px-3 text-slate-400">${inst.update_time || 30}s</td>
         <td class="py-1.5 px-3 text-slate-400">${inst.timeout_time || 180}s</td>
         <td class="py-1.5 px-3 font-mono text-cyan-300">${redist.join(', ') || 'none'}</td>
@@ -19774,7 +19774,7 @@ function openRipInstanceModal() {
   document.getElementById('rip-instance-modal-title').innerText = 'New RIP Instance';
   document.getElementById('modal-rip-instance-id').value = '';
   document.getElementById('modal-rip-instance-name').value = 'rip1';
-  document.getElementById('modal-rip-instance-version').value = 'rip2';
+  document.getElementById('modal-rip-instance-version').value = '2';
   document.getElementById('modal-rip-instance-update').value = '30';
   document.getElementById('modal-rip-instance-timeout').value = '180';
   document.getElementById('modal-rip-instance-garbage').value = '120';
@@ -19795,7 +19795,7 @@ function openEditRipInstanceModal(id) {
   document.getElementById('rip-instance-modal-title').innerText = `Edit RIP Instance: ${inst.name}`;
   document.getElementById('modal-rip-instance-id').value = inst.id;
   document.getElementById('modal-rip-instance-name').value = inst.name || '';
-  document.getElementById('modal-rip-instance-version').value = inst.version || 'rip2';
+  document.getElementById('modal-rip-instance-version').value = (inst.version == 1 || inst.version === 'ripng') ? '1' : '2';
   document.getElementById('modal-rip-instance-update').value = inst.update_time || 30;
   document.getElementById('modal-rip-instance-timeout').value = inst.timeout_time || 180;
   document.getElementById('modal-rip-instance-garbage').value = inst.garbage_time || 120;
@@ -19820,7 +19820,7 @@ async function submitRipInstance(e) {
   const payload = {
     id: id || undefined,
     name: document.getElementById('modal-rip-instance-name').value.trim(),
-    version: document.getElementById('modal-rip-instance-version').value,
+    version: parseInt(document.getElementById('modal-rip-instance-version').value, 10) || 2,
     update_time: parseInt(document.getElementById('modal-rip-instance-update').value, 10) || 30,
     timeout_time: parseInt(document.getElementById('modal-rip-instance-timeout').value, 10) || 180,
     garbage_time: parseInt(document.getElementById('modal-rip-instance-garbage').value, 10) || 120,

+ 3 - 3
internal/web/static/index.html

@@ -11893,8 +11893,8 @@
           <div>
             <label class="block font-semibold text-slate-300 mb-1">Protocol Version</label>
             <select id="modal-rip-instance-version" class="w-full bg-slate-800 border border-slate-700 rounded-lg px-3 py-2 text-white font-mono">
-              <option value="rip2">RIPv2 (IPv4)</option>
-              <option value="ripng">RIPng (IPv6)</option>
+              <option value="2">RIPv2 (IPv4)</option>
+              <option value="1">RIPng (IPv6)</option>
             </select>
           </div>
         </div>
@@ -13689,6 +13689,6 @@
   </div>
 
   <datalist id="fw-addrlist-datalist"></datalist>
-  <script src="app.js?v=26.0"></script>
+  <script src="app.js?v=27.0"></script>
 </body>
 </html>