Browse Source

Align BGP Connection & OSPF with MikroTik WinBox v7 reference screens and FortiGate IPSec Phase 1 & 2

Proxmox Router 1 tuần trước cách đây
mục cha
commit
a071c371b1

+ 14 - 0
internal/dynamicrouting/types.go

@@ -19,34 +19,48 @@ type BGPPeer struct {
 	Name             string   `json:"name"`
 	Enabled          bool     `json:"enabled"`
 	Comment          string   `json:"comment,omitempty"`
+	Instance         string   `json:"instance,omitempty"`
+	Template         string   `json:"template,omitempty"`
 	LocalAS          uint32   `json:"local_as,omitempty"` // Multi-Instance Local AS (if 0, uses global AS)
 	RouterID         string   `json:"router_id,omitempty"`
 	AFI              string   `json:"afi,omitempty"` // "ip", "ipv6"
 	RemoteAddress    string   `json:"remote_address"`
 	RemotePort       int      `json:"remote_port,omitempty"`
 	RemoteAS         uint32   `json:"remote_as"`
+	RemoteAllowAS    string   `json:"remote_allow_as,omitempty"`
 	LocalAddress     string   `json:"local_address,omitempty"`
 	LocalPort        int      `json:"local_port,omitempty"`
 	LocalRole        string   `json:"local_role,omitempty"` // ebgp, ibgp
 	Password         string   `json:"password,omitempty"`   // TCP MD5 Key
 	Multihop         bool     `json:"multihop"`
+	TxTTL            int      `json:"tx_ttl,omitempty"`
+	RxMinTTL         int      `json:"rx_min_ttl,omitempty"`
 	Connect          bool     `json:"connect"`
 	Listen           bool     `json:"listen"`
 	HoldTime         int      `json:"hold_time,omitempty"`
 	KeepaliveTime    int      `json:"keepalive_time,omitempty"`
+	UseBFD           bool     `json:"use_bfd,omitempty"`
 	RoutingTable     string   `json:"routing_table,omitempty"`
+	VRF              string   `json:"vrf,omitempty"`
+	NoClientReflect  bool     `json:"no_client_reflect,omitempty"`
 	RedistConnected  bool     `json:"redist_connected"`
 	RedistStatic     bool     `json:"redist_static"`
 	RedistOSPF       bool     `json:"redist_ospf"`
 	RedistBGP        bool     `json:"redist_bgp"`
+	RedistRIP        bool     `json:"redist_rip,omitempty"`
+	RedistVPN        bool     `json:"redist_vpn,omitempty"`
+	RedistDHCP       bool     `json:"redist_dhcp,omitempty"`
 	DefaultOriginate bool     `json:"default_originate"`
 	NexthopChoice    string   `json:"nexthop_choice,omitempty"` // default, force-self
 	NexthopSelf      bool     `json:"nexthop_self"`
 	DefaultPrepend   int      `json:"default_prepend,omitempty"`
 	AllowASIn        int      `json:"allow_as_in,omitempty"`
 	RemovePrivateAS  bool     `json:"remove_private_as"`
+	ASOverride       bool     `json:"as_override,omitempty"`
+	IgnoreASPathLen  bool     `json:"ignore_as_path_len,omitempty"`
 	InFilter         string   `json:"in_filter,omitempty"`        // Routing Filter chain for incoming routes
 	OutFilter        string   `json:"out_filter,omitempty"`       // Routing Filter chain for outgoing routes
+	OutputNetwork    string   `json:"output_network,omitempty"`
 	OutputNetworks   []string `json:"output_networks,omitempty"` // Dynamic [+] / [-] output networks
 	Disabled         bool     `json:"disabled"`
 	Passive          bool     `json:"passive"`

+ 209 - 28
internal/vpn/ipsec.go

@@ -77,6 +77,9 @@ func (m *IPSecManager) SaveConnection(conn IPSecConnection) error {
 	if conn.Name == "" {
 		return fmt.Errorf("connection name is required")
 	}
+	if conn.IKEVersion == "" {
+		conn.IKEVersion = "ikev2"
+	}
 	if conn.IKEProposals == "" {
 		conn.IKEProposals = "aes256-sha256-modp2048,default"
 	}
@@ -86,6 +89,29 @@ func (m *IPSecManager) SaveConnection(conn IPSecConnection) error {
 	if conn.LocalAddrs == "" {
 		conn.LocalAddrs = "%any"
 	}
+	if conn.IKEKeylife <= 0 {
+		conn.IKEKeylife = 86400
+	}
+	if conn.ESPKeylife <= 0 {
+		conn.ESPKeylife = 28800
+	}
+	if conn.Phase2Name == "" {
+		conn.Phase2Name = conn.Name + "_p2"
+	}
+
+	// Normalize subnets
+	if len(conn.LocalSubnets) == 0 && conn.LocalSubnet != "" {
+		conn.LocalSubnets = []string{conn.LocalSubnet}
+	}
+	if len(conn.LocalSubnets) > 0 {
+		conn.LocalSubnet = conn.LocalSubnets[0]
+	}
+	if len(conn.RemoteSubnets) == 0 && conn.RemoteSubnet != "" {
+		conn.RemoteSubnets = []string{conn.RemoteSubnet}
+	}
+	if len(conn.RemoteSubnets) > 0 {
+		conn.RemoteSubnet = conn.RemoteSubnets[0]
+	}
 
 	found := false
 	for i, c := range m.config.Connections {
@@ -126,34 +152,135 @@ func (m *IPSecManager) generateSwanctlConfig() error {
 	_ = os.MkdirAll("/etc/swanctl/conf.d", 0755)
 
 	var sb strings.Builder
-	sb.WriteString("# Generated by Proxmox RouterOS - strongSwan IPSec Configuration\n\n")
+	sb.WriteString("# Generated by DebTik - strongSwan IPSec Configuration (FortiGate Phase 1 & 2 Engine)\n\n")
 
 	sb.WriteString("connections {\n")
 	for _, c := range m.config.Connections {
-		sb.WriteString(fmt.Sprintf("    %s {\n", c.Name))
-		sb.WriteString(fmt.Sprintf("        local_addrs = %s\n", c.LocalAddrs))
-		sb.WriteString(fmt.Sprintf("        remote_addrs = %s\n", c.RemoteAddrs))
+		if c.Disabled {
+			continue
+		}
+		connName := sanitizeConnName(c.Name)
+		sb.WriteString(fmt.Sprintf("    %s {\n", connName))
+
+		// IKE Version
+		if strings.EqualFold(c.IKEVersion, "ikev1") {
+			sb.WriteString("        version = 1\n")
+		} else {
+			sb.WriteString("        version = 2\n")
+		}
+
+		localAddrs := strings.TrimSpace(c.LocalAddrs)
+		if localAddrs == "" {
+			localAddrs = "%any"
+		}
+		sb.WriteString(fmt.Sprintf("        local_addrs = %s\n", localAddrs))
+		sb.WriteString(fmt.Sprintf("        remote_addrs = %s\n", strings.TrimSpace(c.RemoteAddrs)))
+
+		// Local auth
 		sb.WriteString("        local {\n")
 		sb.WriteString("            auth = psk\n")
+		if strings.TrimSpace(c.LocalID) != "" {
+			sb.WriteString(fmt.Sprintf("            id = %s\n", strings.TrimSpace(c.LocalID)))
+		}
 		sb.WriteString("        }\n")
+
+		// Remote auth
 		sb.WriteString("        remote {\n")
 		sb.WriteString("            auth = psk\n")
+		if strings.TrimSpace(c.RemoteID) != "" {
+			sb.WriteString(fmt.Sprintf("            id = %s\n", strings.TrimSpace(c.RemoteID)))
+		}
 		sb.WriteString("        }\n")
+
+		// DPD
+		dpd := strings.ToLower(strings.TrimSpace(c.DPD))
+		if dpd == "disabled" {
+			// DPD disabled
+		} else if dpd == "periodic" {
+			sb.WriteString("        dpd_delay = 20s\n")
+			sb.WriteString("        dpd_timeout = 60s\n")
+		} else {
+			// default on-demand
+			sb.WriteString("        dpd_delay = 30s\n")
+			sb.WriteString("        dpd_timeout = 120s\n")
+		}
+
+		// IKE Proposals & Rekey
+		ikeProps := strings.TrimSpace(c.IKEProposals)
+		if ikeProps == "" {
+			ikeProps = "aes256-sha256-modp2048,aes128-sha256-modp2048,default"
+		}
+		sb.WriteString(fmt.Sprintf("        proposals = %s\n", ikeProps))
+		ikeKeylife := 86400
+		if c.IKEKeylife > 0 {
+			ikeKeylife = c.IKEKeylife
+		}
+		sb.WriteString(fmt.Sprintf("        rekey_time = %ds\n", ikeKeylife))
+
+		// Children (Phase 2)
 		sb.WriteString("        children {\n")
-		sb.WriteString(fmt.Sprintf("            %s {\n", c.Name))
-		if c.LocalSubnet != "" {
-			sb.WriteString(fmt.Sprintf("                local_ts = %s\n", c.LocalSubnet))
+		p2Name := strings.TrimSpace(c.Phase2Name)
+		if p2Name == "" {
+			p2Name = connName + "_p2"
+		} else {
+			p2Name = sanitizeConnName(p2Name)
+		}
+		sb.WriteString(fmt.Sprintf("            %s {\n", p2Name))
+
+		// Traffic Selectors: Collect all local and remote subnets!
+		var localNets []string
+		for _, s := range c.LocalSubnets {
+			s = strings.TrimSpace(s)
+			if s != "" {
+				localNets = append(localNets, s)
+			}
+		}
+		if len(localNets) == 0 && strings.TrimSpace(c.LocalSubnet) != "" {
+			localNets = append(localNets, strings.TrimSpace(c.LocalSubnet))
+		}
+		if len(localNets) == 0 {
+			localNets = []string{"0.0.0.0/0"}
+		}
+
+		var remoteNets []string
+		for _, s := range c.RemoteSubnets {
+			s = strings.TrimSpace(s)
+			if s != "" {
+				remoteNets = append(remoteNets, s)
+			}
 		}
-		if c.RemoteSubnet != "" {
-			sb.WriteString(fmt.Sprintf("                remote_ts = %s\n", c.RemoteSubnet))
+		if len(remoteNets) == 0 && strings.TrimSpace(c.RemoteSubnet) != "" {
+			remoteNets = append(remoteNets, strings.TrimSpace(c.RemoteSubnet))
 		}
-		sb.WriteString(fmt.Sprintf("                esp_proposals = %s\n", c.ESPProposals))
+		if len(remoteNets) == 0 {
+			remoteNets = []string{"0.0.0.0/0"}
+		}
+
+		sb.WriteString(fmt.Sprintf("                local_ts = %s\n", strings.Join(localNets, ",")))
+		sb.WriteString(fmt.Sprintf("                remote_ts = %s\n", strings.Join(remoteNets, ",")))
+
+		// ESP Proposals
+		espProps := strings.TrimSpace(c.ESPProposals)
+		if espProps == "" {
+			espProps = "aes256-sha256-modp2048,aes128-sha256,default"
+		}
+		sb.WriteString(fmt.Sprintf("                esp_proposals = %s\n", espProps))
+
+		espKeylife := 28800
+		if c.ESPKeylife > 0 {
+			espKeylife = c.ESPKeylife
+		}
+		sb.WriteString(fmt.Sprintf("                rekey_time = %ds\n", espKeylife))
+
 		if c.AutoStart {
 			sb.WriteString("                start_action = start\n")
+			sb.WriteString("                dpd_action = restart\n")
+		} else {
+			sb.WriteString("                start_action = none\n")
 		}
+
 		sb.WriteString("            }\n")
 		sb.WriteString("        }\n")
-		sb.WriteString(fmt.Sprintf("        proposals = %s\n", c.IKEProposals))
 		sb.WriteString("    }\n\n")
 	}
 	sb.WriteString("}\n\n")
@@ -161,16 +288,47 @@ func (m *IPSecManager) generateSwanctlConfig() error {
 	// Secrets
 	sb.WriteString("secrets {\n")
 	for _, c := range m.config.Connections {
+		if c.Disabled {
+			continue
+		}
 		if c.PresharedKey != "" {
-			sb.WriteString(fmt.Sprintf("    ike-%s {\n", c.Name))
-			sb.WriteString(fmt.Sprintf("        id = %s\n", c.RemoteAddrs))
+			connName := sanitizeConnName(c.Name)
+			remoteAddrs := strings.TrimSpace(c.RemoteAddrs)
+			sb.WriteString(fmt.Sprintf("    ike-%s {\n", connName))
+			if remoteAddrs != "" && remoteAddrs != "%any" {
+				sb.WriteString(fmt.Sprintf("        id = %s\n", remoteAddrs))
+			}
 			sb.WriteString(fmt.Sprintf("        secret = %q\n", c.PresharedKey))
 			sb.WriteString("    }\n")
 		}
 	}
 	sb.WriteString("}\n")
 
-	return os.WriteFile(SwanctlConfPath, []byte(sb.String()), 0600)
+	if err := os.WriteFile(SwanctlConfPath, []byte(sb.String()), 0600); err != nil {
+		return err
+	}
+
+	_ = exec.Command("systemctl", "start", "strongswan").Run()
+	_ = exec.Command("swanctl", "--load-all").Run()
+
+	return nil
+}
+
+func sanitizeConnName(raw string) string {
+	raw = strings.TrimSpace(raw)
+	var b strings.Builder
+	for _, r := range raw {
+		if (r >= 'a' && r <= 'z') || (r >= 'A' && r <= 'Z') || (r >= '0' && r <= '9') || r == '-' || r == '_' {
+			b.WriteRune(r)
+		} else {
+			b.WriteRune('_')
+		}
+	}
+	res := b.String()
+	if res == "" {
+		res = "tunnel1"
+	}
+	return res
 }
 
 func (m *IPSecManager) StartTunnel(name string) error {
@@ -180,10 +338,45 @@ func (m *IPSecManager) StartTunnel(name string) error {
 	_ = exec.Command("systemctl", "start", "strongswan").Run()
 	_ = exec.Command("swanctl", "--load-all").Run()
 
-	cmd := exec.Command("swanctl", "--initiate", "--child", name)
+	var p2Name string
+	for _, c := range m.config.Connections {
+		if c.Name == name {
+			p2Name = c.Phase2Name
+			break
+		}
+	}
+	if p2Name == "" {
+		p2Name = name + "_p2"
+	}
+
+	cmd := exec.Command("swanctl", "--initiate", "--child", p2Name)
 	if out, err := cmd.CombinedOutput(); err != nil {
-		return fmt.Errorf("failed to initiate IPSec tunnel %s: %s: %w", name, string(out), err)
+		cmd2 := exec.Command("swanctl", "--initiate", "--child", name)
+		if out2, err2 := cmd2.CombinedOutput(); err2 != nil {
+			return fmt.Errorf("failed to initiate IPSec tunnel %s: %s / %s", name, string(out), string(out2))
+		}
+	}
+	return nil
+}
+
+func (m *IPSecManager) StopTunnel(name string) error {
+	m.mu.Lock()
+	defer m.mu.Unlock()
+
+	var p2Name string
+	for _, c := range m.config.Connections {
+		if c.Name == name {
+			p2Name = c.Phase2Name
+			break
+		}
 	}
+	if p2Name == "" {
+		p2Name = name + "_p2"
+	}
+
+	_ = exec.Command("swanctl", "--terminate", "--child", p2Name).Run()
+	_ = exec.Command("swanctl", "--terminate", "--child", name).Run()
+	_ = exec.Command("swanctl", "--terminate", "--ike", name).Run()
 	return nil
 }
 
@@ -211,18 +404,6 @@ func (m *IPSecManager) List() []IPSecConnection {
 	return st.Connections
 }
 
-func (m *IPSecManager) StopTunnel(name string) error {
-	m.mu.Lock()
-	defer m.mu.Unlock()
-
-	cmd := exec.Command("swanctl", "--terminate", "--child", name)
-	out, err := cmd.CombinedOutput()
-	if err != nil {
-		return fmt.Errorf("failed to terminate tunnel %s: %s: %w", name, string(out), err)
-	}
-	return nil
-}
-
 func (m *IPSecManager) GetStatus() IPSecStatus {
 	m.mu.Lock()
 	defer m.mu.Unlock()

+ 35 - 13
internal/vpn/types.go

@@ -117,7 +117,7 @@ type L2TPInstance struct {
 	LastError     string `json:"last_error,omitempty"`
 }
 
-// ======================== IPSec Connection / Policy (MikroTik style) ========================
+// ======================== IPSec Connection / Policy (FortiGate Phase 1 & 2 Style) ========================
 type IPSecConfig struct {
 	Connections []IPSecConnection `json:"connections"`
 }
@@ -130,16 +130,38 @@ type IPSecStatus struct {
 }
 
 type IPSecConnection struct {
-	Name         string `json:"name"`          // e.g. "ipsec-peer1", "site-to-site"
-	LocalAddrs   string `json:"local_addrs"`   // "%any" or local IP
-	RemoteAddrs  string `json:"remote_addrs"`  // Remote Gateway IP or FQDN
-	LocalSubnet  string `json:"local_subnet"`  // e.g. "192.168.10.0/24"
-	RemoteSubnet string `json:"remote_subnet"` // e.g. "192.168.20.0/24"
-	PresharedKey string `json:"preshared_key"`
-	IKEProposals string `json:"ike_proposals"` // default: aes256-sha256-modp2048
-	ESPProposals string `json:"esp_proposals"` // default: aes256-sha256-modp2048
-	AutoStart    bool   `json:"auto_start"`
-	Disabled     bool   `json:"disabled"`
-	Running      bool   `json:"running"`       // Active SA installed
-	Status       string `json:"status"`        // "ESTABLISHED", "INSTALLED", "DISCONNECTED"
+	Name          string   `json:"name"`           // Tunnel name, e.g. "VPN_HQ", "to_branch"
+	Comments      string   `json:"comments"`       // FortiGate Comments
+	
+	// Network & Phase 1 (IKE Gateway)
+	Interface     string   `json:"interface"`      // Local interface or %any
+	LocalAddrs    string   `json:"local_addrs"`    // "%any" or local IP
+	RemoteAddrs   string   `json:"remote_addrs"`   // Remote Gateway IP or FQDN
+	IKEVersion    string   `json:"ike_version"`    // "ikev2" (default) or "ikev1"
+	AuthMethod    string   `json:"auth_method"`    // "psk"
+	PresharedKey  string   `json:"preshared_key"`
+	LocalID       string   `json:"local_id,omitempty"`
+	RemoteID      string   `json:"remote_id,omitempty"`
+	IKEProposals  string   `json:"ike_proposals"`  // default: aes256-sha256-modp2048,default
+	IKEKeylife    int      `json:"ike_keylife"`    // seconds, default 86400
+	DPD           string   `json:"dpd"`            // "on-demand" (default), "periodic", "disabled"
+	NATTraversal  bool     `json:"nat_traversal"`  // default true
+
+	// Phase 2 (IPsec / Selectors)
+	Phase2Name    string   `json:"phase2_name"`    // e.g. "VPN_HQ_p2"
+	ESPProposals  string   `json:"esp_proposals"`  // default: aes256-sha256-modp2048,default
+	PFS           bool     `json:"pfs"`            // Perfect Forward Secrecy, default true
+	PFSGroup      string   `json:"pfs_group"`      // e.g. "modp2048" (DH 14), "ecp256" (DH 19)
+	ESPKeylife    int      `json:"esp_keylife"`    // seconds, default 28800
+	AutoStart     bool     `json:"auto_start"`     // default true
+
+	// Traffic Selectors: Multiple Subnets support!
+	LocalSubnets  []string `json:"local_subnets"`  // e.g. ["192.168.10.0/24", "10.0.1.0/24", "172.16.0.0/16"]
+	RemoteSubnets []string `json:"remote_subnets"` // e.g. ["192.168.20.0/24", "10.0.2.0/24", "172.17.0.0/16"]
+	LocalSubnet   string   `json:"local_subnet"`   // Backward compatibility
+	RemoteSubnet  string   `json:"remote_subnet"`  // Backward compatibility
+
+	Disabled      bool     `json:"disabled"`
+	Running       bool     `json:"running"`        // Active SA installed
+	Status        string   `json:"status"`         // "ESTABLISHED", "INSTALLED", "DISCONNECTED"
 }

+ 317 - 36
internal/web/static/app.js

@@ -1762,7 +1762,86 @@ async function deleteWGPeer(iface, pubkey) {
   }
 }
 
-// ======================== IPSec Multi-Tunnel ========================
+// ======================== IPSec FortiGate Phase 1 & 2 Multi-Tunnel Engine ========================
+function switchIpsecModalTab(tab) {
+  const p1Tab = document.getElementById('tab-btn-ipsec-p1');
+  const p2Tab = document.getElementById('tab-btn-ipsec-p2');
+  const p1Content = document.getElementById('ipsec-tab-content-p1');
+  const p2Content = document.getElementById('ipsec-tab-content-p2');
+  const stepBtn = document.getElementById('btn-ipsec-switch-step');
+
+  if (tab === 'p1') {
+    if (p1Tab) {
+      p1Tab.className = 'px-4 py-2 text-xs font-bold border-b-2 border-amber-500 text-amber-300 transition flex items-center gap-2';
+    }
+    if (p2Tab) {
+      p2Tab.className = 'px-4 py-2 text-xs font-bold border-b-2 border-transparent text-slate-400 hover:text-slate-200 transition flex items-center gap-2';
+    }
+    if (p1Content) p1Content.classList.remove('hidden');
+    if (p2Content) p2Content.classList.add('hidden');
+    if (stepBtn) stepBtn.innerHTML = 'Next: Phase 2 Selectors &rarr;';
+  } else {
+    if (p1Tab) {
+      p1Tab.className = 'px-4 py-2 text-xs font-bold border-b-2 border-transparent text-slate-400 hover:text-slate-200 transition flex items-center gap-2';
+    }
+    if (p2Tab) {
+      p2Tab.className = 'px-4 py-2 text-xs font-bold border-b-2 border-cyan-500 text-cyan-300 transition flex items-center gap-2';
+    }
+    if (p1Content) p1Content.classList.add('hidden');
+    if (p2Content) p2Content.classList.remove('hidden');
+    if (stepBtn) stepBtn.innerHTML = '&larr; Back: Phase 1 (IKE)';
+  }
+}
+
+function toggleIpsecPhaseStep() {
+  const p1Content = document.getElementById('ipsec-tab-content-p1');
+  if (p1Content && !p1Content.classList.contains('hidden')) {
+    switchIpsecModalTab('p2');
+  } else {
+    switchIpsecModalTab('p1');
+  }
+}
+
+function toggleIpsecPskVisibility() {
+  const pskInput = document.getElementById('ipsec-modal-psk');
+  if (!pskInput) return;
+  pskInput.type = pskInput.type === 'password' ? 'text' : 'password';
+}
+
+function addIpsecLocalSubnet(val = '') {
+  const container = document.getElementById('ipsec-modal-local-subnets-container');
+  if (!container) return;
+  const row = document.createElement('div');
+  row.className = 'flex items-center gap-2 ipsec-subnet-row';
+  row.innerHTML = `
+    <input type="text" value="${escapeHtml(val)}" placeholder="e.g. 192.168.10.0/24 or 10.0.1.0/24" class="flex-1 bg-slate-900 border border-slate-700 rounded-lg px-3 py-1.5 text-white font-mono text-xs focus:outline-none focus:border-cyan-500">
+    <button type="button" onclick="this.closest('.ipsec-subnet-row').remove()" class="w-7 h-7 flex items-center justify-center bg-slate-800 hover:bg-rose-950 text-slate-400 hover:text-rose-400 border border-slate-700 rounded-lg text-base font-bold transition" title="Remove local subnet">-</button>
+  `;
+  container.appendChild(row);
+}
+
+function addIpsecRemoteSubnet(val = '') {
+  const container = document.getElementById('ipsec-modal-remote-subnets-container');
+  if (!container) return;
+  const row = document.createElement('div');
+  row.className = 'flex items-center gap-2 ipsec-subnet-row';
+  row.innerHTML = `
+    <input type="text" value="${escapeHtml(val)}" placeholder="e.g. 192.168.20.0/24 or 10.0.2.0/24" class="flex-1 bg-slate-900 border border-slate-700 rounded-lg px-3 py-1.5 text-white font-mono text-xs focus:outline-none focus:border-purple-500">
+    <button type="button" onclick="this.closest('.ipsec-subnet-row').remove()" class="w-7 h-7 flex items-center justify-center bg-slate-800 hover:bg-rose-950 text-slate-400 hover:text-rose-400 border border-slate-700 rounded-lg text-base font-bold transition" title="Remove remote subnet">-</button>
+  `;
+  container.appendChild(row);
+}
+
+function updateIpsecEspProposalFromPFS(val) {
+  const prop = document.getElementById('ipsec-modal-espprop');
+  if (!prop) return;
+  if (val === 'none') {
+    prop.value = 'aes256-sha256,aes128-sha256,default';
+  } else {
+    prop.value = `aes256-sha256-${val},aes128-sha256-${val},default`;
+  }
+}
+
 async function loadIPSec(populateInputs = true) {
   try {
     const res = await fetch(`${API_BASE}/vpn/ipsec/tunnels`);
@@ -1785,28 +1864,82 @@ async function loadIPSec(populateInputs = true) {
       state.ipsecTunnels.forEach(c => {
         const isEstablished = c.running || c.status === 'ESTABLISHED';
         const tr = document.createElement('tr');
-        tr.className = 'hover:bg-slate-800/40 transition text-xs';
+        tr.className = 'hover:bg-slate-800/40 transition text-xs select-none';
+
+        // Local subnets tags
+        const localList = (c.local_subnets && c.local_subnets.length > 0) ? c.local_subnets : (c.local_subnet ? [c.local_subnet] : ['0.0.0.0/0']);
+        const localBadges = localList.map(s => `<span class="px-1.5 py-0.5 rounded bg-cyan-950/60 border border-cyan-800/60 text-cyan-300 font-mono text-[10px] mr-1">${escapeHtml(s)}</span>`).join('');
+
+        // Remote subnets tags
+        const remoteList = (c.remote_subnets && c.remote_subnets.length > 0) ? c.remote_subnets : (c.remote_subnet ? [c.remote_subnet] : ['0.0.0.0/0']);
+        const remoteBadges = remoteList.map(s => `<span class="px-1.5 py-0.5 rounded bg-purple-950/60 border border-purple-800/60 text-purple-300 font-mono text-[10px] mr-1">${escapeHtml(s)}</span>`).join('');
+
+        const ikeVersionBadge = (c.ike_version || 'ikev2').toUpperCase();
+        const p2Name = c.phase2_name || `${c.name}_p2`;
+
         tr.innerHTML = `
-          <td class="py-2.5 px-3 text-center">${renderMikroTikFlag(isEstablished, c.disabled)}</td>
-          <td class="py-2.5 px-4 font-bold text-white">${c.name}</td>
-          <td class="py-2.5 px-4 text-emerald-400 font-mono">${c.local_subnet || '-'}</td>
-          <td class="py-2.5 px-4 text-slate-300 font-mono">${c.remote_addrs || '-'}</td>
-          <td class="py-2.5 px-4 text-cyan-400 font-mono">${c.remote_subnet || '-'}</td>
+          <td class="py-2.5 px-3 text-center">
+            ${isEstablished ? `
+              <span class="inline-flex items-center gap-1.5 text-emerald-400 font-mono font-bold text-[11px]" title="Active SA Established">
+                <span class="w-2.5 h-2.5 rounded-full bg-emerald-400 animate-pulse"></span>
+                <span>UP</span>
+              </span>
+            ` : (c.disabled ? `
+              <span class="inline-flex items-center gap-1.5 text-slate-500 font-mono text-[11px]" title="Tunnel Disabled">
+                <span class="w-2 h-2 rounded-full bg-rose-500"></span>
+                <span>DOWN</span>
+              </span>
+            ` : `
+              <span class="inline-flex items-center gap-1.5 text-amber-400 font-mono text-[11px]" title="Disconnected / Idle">
+                <span class="w-2 h-2 rounded-full bg-amber-400"></span>
+                <span>IDLE</span>
+              </span>
+            `)}
+          </td>
+          <td class="py-2.5 px-4 font-bold text-white">
+            <div class="flex items-center gap-2">
+              <span class="text-amber-400">🛡</span>
+              <div>
+                <div>${escapeHtml(c.name)}</div>
+                <div class="text-[10px] text-slate-500 font-normal">P2: ${escapeHtml(p2Name)}</div>
+              </div>
+            </div>
+          </td>
+          <td class="py-2.5 px-4 text-slate-300 font-mono font-medium">${escapeHtml(c.remote_addrs || '%any')}</td>
           <td class="py-2.5 px-4 font-sans">
-            <span class="px-2 py-0.5 rounded-full text-[10px] font-semibold ${
-              isEstablished ? 'bg-emerald-500/10 text-emerald-400 border border-emerald-500/30' : 'bg-slate-800 text-slate-400'
-            }">${c.status || (isEstablished ? 'ESTABLISHED' : 'DISCONNECTED')}</span>
+            <div class="flex items-center gap-1.5">
+              <span class="px-1.5 py-0.5 rounded bg-amber-500/10 text-amber-300 border border-amber-500/30 font-mono text-[10px] font-semibold">${ikeVersionBadge}</span>
+              <span class="text-[10px] text-slate-400">${c.auth_method || 'PSK'}</span>
+            </div>
+          </td>
+          <td class="py-2.5 px-4">
+            <div class="space-y-1">
+              <div class="flex items-center flex-wrap gap-1">
+                <span class="text-[9px] text-cyan-400 uppercase font-bold mr-1">Local:</span>
+                ${localBadges}
+              </div>
+              <div class="flex items-center flex-wrap gap-1">
+                <span class="text-[9px] text-purple-400 uppercase font-bold mr-1">Remote:</span>
+                ${remoteBadges}
+              </div>
+            </div>
+          </td>
+          <td class="py-2.5 px-4 font-mono text-[11px] text-slate-400">
+            <div class="truncate max-w-[140px]" title="${escapeHtml(c.esp_proposals || '')}">
+              ${escapeHtml(c.esp_proposals || 'aes256-sha256')}
+            </div>
+            <div class="text-[10px] text-slate-500">PFS: ${escapeHtml(c.pfs_group || 'modp2048')}</div>
           </td>
           <td class="py-2.5 px-4 text-right space-x-2 font-sans">
             ${isEstablished ? `
-              <button onclick="stopIPSecTunnel('${c.name}')" class="text-xs text-rose-400 hover:text-rose-300">Terminate</button>
+              <button onclick="stopIPSecTunnel('${c.name}')" class="px-2 py-0.5 text-xs bg-rose-500/20 text-rose-400 hover:bg-rose-500/30 rounded border border-rose-500/30 font-medium">Bring Down</button>
             ` : `
-              <button onclick="startIPSecTunnel('${c.name}')" class="text-xs bg-amber-600/30 hover:bg-amber-600/50 text-amber-300 px-2 py-0.5 rounded border border-amber-500/30">Initiate</button>
+              <button onclick="startIPSecTunnel('${c.name}')" class="px-2 py-0.5 text-xs bg-emerald-500/20 text-emerald-400 hover:bg-emerald-500/30 rounded border border-emerald-500/30 font-semibold">Bring Up</button>
             `}
             <button onclick="toggleIPSecState('${c.name}', ${c.disabled ? 'true' : 'false'})" class="text-xs text-slate-400 hover:text-white">
               ${c.disabled ? 'Enable' : 'Disable'}
             </button>
-            <button onclick="openEditIPSecModal('${c.name}')" class="text-xs text-slate-400 hover:text-white">Edit</button>
+            <button onclick="openEditIPSecModal('${c.name}')" class="text-xs text-amber-400 hover:text-amber-300">Edit</button>
             <button onclick="deleteIPSecConn('${c.name}')" class="text-xs text-slate-500 hover:text-rose-400">Delete</button>
           </td>
         `;
@@ -1819,18 +1952,40 @@ async function loadIPSec(populateInputs = true) {
 }
 
 function openAddIPSecModal() {
-  document.getElementById('ipsec-modal-title').innerText = 'Configure IPSec IKEv2 Tunnel';
-  document.getElementById('ipsec-modal-name').value = `ipsec-${(state.ipsecTunnels || []).length + 1}`;
+  document.getElementById('ipsec-modal-title').innerText = 'Configure IPSec Site-to-Site Tunnel';
+  
+  // Phase 1 Fields
+  document.getElementById('ipsec-modal-name').value = `tunnel-${(state.ipsecTunnels || []).length + 1}`;
   document.getElementById('ipsec-modal-name').readOnly = false;
-  document.getElementById('ipsec-modal-localts').value = '192.168.10.0/24';
-  document.getElementById('ipsec-modal-remotets').value = '192.168.20.0/24';
+  document.getElementById('ipsec-modal-ikeversion').value = 'ikev2';
   document.getElementById('ipsec-modal-remotegw').value = '';
   document.getElementById('ipsec-modal-psk').value = '';
-  document.getElementById('ipsec-modal-ikeprop').value = 'aes256-sha256-modp2048,default';
-  document.getElementById('ipsec-modal-espprop').value = 'aes256-sha256-modp2048,default';
+  document.getElementById('ipsec-modal-ikeprop').value = 'aes256-sha256-modp2048,aes128-sha256-modp2048,default';
+  document.getElementById('ipsec-modal-ikekeylife').value = '86400';
+  document.getElementById('ipsec-modal-localid').value = '';
+  document.getElementById('ipsec-modal-remoteid').value = '';
+  document.getElementById('ipsec-modal-dpd').value = 'on-demand';
+
+  // Phase 2 Fields
+  document.getElementById('ipsec-modal-p2name').value = `tunnel-${(state.ipsecTunnels || []).length + 1}_p2`;
+  document.getElementById('ipsec-modal-espprop').value = 'aes256-sha256-modp2048,aes128-sha256-modp2048,default';
+  document.getElementById('ipsec-modal-pfsgroup').value = 'modp2048';
   document.getElementById('ipsec-modal-autostart').checked = true;
   document.getElementById('ipsec-modal-disabled').checked = false;
 
+  // Clear & Populate default dynamic subnets
+  const localContainer = document.getElementById('ipsec-modal-local-subnets-container');
+  const remoteContainer = document.getElementById('ipsec-modal-remote-subnets-container');
+  if (localContainer) {
+    localContainer.innerHTML = '';
+    addIpsecLocalSubnet('192.168.10.0/24');
+  }
+  if (remoteContainer) {
+    remoteContainer.innerHTML = '';
+    addIpsecRemoteSubnet('192.168.20.0/24');
+  }
+
+  switchIpsecModalTab('p1');
   document.getElementById('ipsec-modal').classList.remove('hidden');
 }
 
@@ -1839,17 +1994,43 @@ function openEditIPSecModal(name) {
   if (!conn) return;
 
   document.getElementById('ipsec-modal-title').innerText = `Edit IPSec Tunnel: ${name}`;
+  
+  // Phase 1 Fields
   document.getElementById('ipsec-modal-name').value = conn.name;
   document.getElementById('ipsec-modal-name').readOnly = true;
-  document.getElementById('ipsec-modal-localts').value = conn.local_subnet || '';
-  document.getElementById('ipsec-modal-remotets').value = conn.remote_subnet || '';
+  document.getElementById('ipsec-modal-ikeversion').value = conn.ike_version || 'ikev2';
   document.getElementById('ipsec-modal-remotegw').value = conn.remote_addrs || '';
   document.getElementById('ipsec-modal-psk').value = conn.preshared_key || '';
   document.getElementById('ipsec-modal-ikeprop').value = conn.ike_proposals || 'aes256-sha256-modp2048,default';
+  document.getElementById('ipsec-modal-ikekeylife').value = conn.ike_keylife || 86400;
+  document.getElementById('ipsec-modal-localid').value = conn.local_id || '';
+  document.getElementById('ipsec-modal-remoteid').value = conn.remote_id || '';
+  document.getElementById('ipsec-modal-dpd').value = conn.dpd || 'on-demand';
+
+  // Phase 2 Fields
+  document.getElementById('ipsec-modal-p2name').value = conn.phase2_name || `${conn.name}_p2`;
   document.getElementById('ipsec-modal-espprop').value = conn.esp_proposals || 'aes256-sha256-modp2048,default';
+  document.getElementById('ipsec-modal-pfsgroup').value = conn.pfs_group || 'modp2048';
   document.getElementById('ipsec-modal-autostart').checked = conn.auto_start ?? true;
   document.getElementById('ipsec-modal-disabled').checked = !!conn.disabled;
 
+  // Populate dynamic subnets
+  const localContainer = document.getElementById('ipsec-modal-local-subnets-container');
+  const remoteContainer = document.getElementById('ipsec-modal-remote-subnets-container');
+  
+  if (localContainer) {
+    localContainer.innerHTML = '';
+    const locals = (conn.local_subnets && conn.local_subnets.length > 0) ? conn.local_subnets : (conn.local_subnet ? [conn.local_subnet] : ['192.168.10.0/24']);
+    locals.forEach(s => addIpsecLocalSubnet(s));
+  }
+
+  if (remoteContainer) {
+    remoteContainer.innerHTML = '';
+    const remotes = (conn.remote_subnets && conn.remote_subnets.length > 0) ? conn.remote_subnets : (conn.remote_subnet ? [conn.remote_subnet] : ['192.168.20.0/24']);
+    remotes.forEach(s => addIpsecRemoteSubnet(s));
+  }
+
+  switchIpsecModalTab('p1');
   document.getElementById('ipsec-modal').classList.remove('hidden');
 }
 
@@ -1860,37 +2041,70 @@ function closeAddIPSecModal() {
 async function submitSaveIPSec(e) {
   e.preventDefault();
   const name = document.getElementById('ipsec-modal-name').value.trim();
-  const localts = document.getElementById('ipsec-modal-localts').value.trim();
-  const remotets = document.getElementById('ipsec-modal-remotets').value.trim();
+  const ikeVersion = document.getElementById('ipsec-modal-ikeversion').value;
   const remotegw = document.getElementById('ipsec-modal-remotegw').value.trim();
   const psk = document.getElementById('ipsec-modal-psk').value;
-  const ike = document.getElementById('ipsec-modal-ikeprop').value.trim();
-  const esp = document.getElementById('ipsec-modal-espprop').value.trim();
+  const ikeProposals = document.getElementById('ipsec-modal-ikeprop').value.trim();
+  const ikeKeylife = parseInt(document.getElementById('ipsec-modal-ikekeylife').value, 10) || 86400;
+  const localId = document.getElementById('ipsec-modal-localid').value.trim();
+  const remoteId = document.getElementById('ipsec-modal-remoteid').value.trim();
+  const dpd = document.getElementById('ipsec-modal-dpd').value;
+
+  const phase2Name = document.getElementById('ipsec-modal-p2name').value.trim() || `${name}_p2`;
+  const espProposals = document.getElementById('ipsec-modal-espprop').value.trim();
+  const pfsGroup = document.getElementById('ipsec-modal-pfsgroup').value;
   const autostart = document.getElementById('ipsec-modal-autostart').checked;
   const disabled = document.getElementById('ipsec-modal-disabled').checked;
 
+  // Scrape dynamic local and remote subnets
+  const localInputs = document.querySelectorAll('#ipsec-modal-local-subnets-container input');
+  const localSubnets = [];
+  localInputs.forEach(inp => {
+    const v = inp.value.trim();
+    if (v) localSubnets.push(v);
+  });
+
+  const remoteInputs = document.querySelectorAll('#ipsec-modal-remote-subnets-container input');
+  const remoteSubnets = [];
+  remoteInputs.forEach(inp => {
+    const v = inp.value.trim();
+    if (v) remoteSubnets.push(v);
+  });
+
   try {
     const res = await fetch(`${API_BASE}/vpn/ipsec/tunnels`, {
       method: 'POST',
       headers: { 'Content-Type': 'application/json' },
       body: JSON.stringify({
         name,
+        ike_version: ikeVersion,
         local_addrs: '%any',
         remote_addrs: remotegw,
-        local_subnet: localts,
-        remote_subnet: remotets,
         preshared_key: psk,
-        ike_proposals: ike,
-        esp_proposals: esp,
+        ike_proposals: ikeProposals,
+        ike_keylife: ikeKeylife,
+        local_id: localId,
+        remote_id: remoteId,
+        dpd: dpd,
+        phase2_name: phase2Name,
+        esp_proposals: espProposals,
+        pfs_group: pfsGroup,
+        pfs: pfsGroup !== 'none',
+        local_subnets: localSubnets,
+        remote_subnets: remoteSubnets,
+        local_subnet: localSubnets[0] || '0.0.0.0/0',
+        remote_subnet: remoteSubnets[0] || '0.0.0.0/0',
         auto_start: autostart,
         disabled
       })
     });
     if (res.ok) {
-      showToast('IPSec Saved', `Tunnel ${name} configured`, 'success');
+      showToast('IPSec Saved', `FortiGate Tunnel ${name} configured`, 'success');
       closeAddIPSecModal();
       loadIPSec(false);
-      fetchData(false);
+    } else {
+      const err = await res.json();
+      showToast('Error', err.error || 'Failed to save IPSec tunnel', 'error');
     }
   } catch (err) {
     showToast('Error', err.message, 'error');
@@ -7684,20 +7898,61 @@ function openAddBgpPeerModal() {
   document.getElementById('modal-bgp-peer-id').value = '';
   document.getElementById('bgp-peer-modal-title').innerText = 'New...';
   
+  // General Tab
   document.getElementById('modal-bgp-peer-enabled').checked = true;
+  document.getElementById('modal-bgp-peer-comment').value = '';
+  document.getElementById('modal-bgp-peer-name').value = '';
+  if (document.getElementById('modal-bgp-peer-instance')) document.getElementById('modal-bgp-peer-instance').value = 'bgp-instance-1';
+  if (document.getElementById('modal-bgp-peer-template')) document.getElementById('modal-bgp-peer-template').value = '';
+  document.getElementById('modal-bgp-peer-local-as').value = '';
   document.getElementById('modal-bgp-peer-afi-ip').checked = true;
   document.getElementById('modal-bgp-peer-afi-ipv6').checked = false;
+  if (document.getElementById('modal-bgp-peer-afi-l2vpn')) document.getElementById('modal-bgp-peer-afi-l2vpn').checked = false;
+  if (document.getElementById('modal-bgp-peer-afi-vpnv4')) document.getElementById('modal-bgp-peer-afi-vpnv4').checked = false;
+  if (document.getElementById('modal-bgp-peer-afi-l2vpn-cisco')) document.getElementById('modal-bgp-peer-afi-l2vpn-cisco').checked = false;
+  document.getElementById('modal-bgp-peer-router-id').value = '';
+  document.getElementById('modal-bgp-peer-remote-addr').value = '';
+  document.getElementById('modal-bgp-peer-remote-port').value = '';
+  document.getElementById('modal-bgp-peer-remote-as').value = '';
+  if (document.getElementById('modal-bgp-peer-remote-allow-as')) document.getElementById('modal-bgp-peer-remote-allow-as').value = '';
+  document.getElementById('modal-bgp-peer-local-addr').value = '';
+  document.getElementById('modal-bgp-peer-local-port').value = '';
+  document.getElementById('modal-bgp-peer-local-role').value = 'ebgp';
+  document.getElementById('modal-bgp-peer-password').value = '';
+  document.getElementById('modal-bgp-peer-multihop').checked = false;
+  if (document.getElementById('modal-bgp-peer-tx-ttl')) document.getElementById('modal-bgp-peer-tx-ttl').value = '';
+  if (document.getElementById('modal-bgp-peer-rx-min-ttl')) document.getElementById('modal-bgp-peer-rx-min-ttl').value = '';
   document.getElementById('modal-bgp-peer-connect').checked = true;
   document.getElementById('modal-bgp-peer-listen').checked = true;
-  document.getElementById('modal-bgp-peer-holdtime').value = '00:03:00';
-  document.getElementById('modal-bgp-peer-keepalive').value = '00:01:00';
-  document.getElementById('modal-bgp-peer-nexthop-choice').value = 'force-self';
+  
+  // Extra Tab
+  document.getElementById('modal-bgp-peer-holdtime').value = '00:00:30';
+  document.getElementById('modal-bgp-peer-keepalive').value = '00:00:10';
+  if (document.getElementById('modal-bgp-peer-use-bfd')) document.getElementById('modal-bgp-peer-use-bfd').checked = false;
+  if (document.getElementById('modal-bgp-peer-vrf')) document.getElementById('modal-bgp-peer-vrf').value = 'main';
+  if (document.getElementById('modal-bgp-peer-no-client-reflect')) document.getElementById('modal-bgp-peer-no-client-reflect').checked = true;
   document.getElementById('modal-bgp-peer-redist-conn').checked = false;
   document.getElementById('modal-bgp-peer-redist-static').checked = true;
+  if (document.getElementById('modal-bgp-peer-redist-rip')) document.getElementById('modal-bgp-peer-redist-rip').checked = false;
   document.getElementById('modal-bgp-peer-redist-ospf').checked = false;
   document.getElementById('modal-bgp-peer-redist-bgp').checked = false;
+  if (document.getElementById('modal-bgp-peer-redist-vpn')) document.getElementById('modal-bgp-peer-redist-vpn').checked = false;
+  if (document.getElementById('modal-bgp-peer-redist-dhcp')) document.getElementById('modal-bgp-peer-redist-dhcp').checked = false;
   document.getElementById('modal-bgp-peer-default-orig').checked = false;
   
+  // Attributes Tab
+  document.getElementById('modal-bgp-peer-nexthop-choice').value = 'force-self';
+  document.getElementById('modal-bgp-peer-default-prepend').value = 0;
+  document.getElementById('modal-bgp-peer-allow-as-in').value = 0;
+  if (document.getElementById('modal-bgp-peer-ignore-as-path-len')) document.getElementById('modal-bgp-peer-ignore-as-path-len').checked = false;
+  document.getElementById('modal-bgp-peer-remove-priv-as').checked = false;
+  document.getElementById('modal-bgp-peer-as-override').checked = false;
+
+  // Filter Tab
+  document.getElementById('modal-bgp-peer-in-filter').value = '';
+  document.getElementById('modal-bgp-peer-out-filter').value = '';
+  if (document.getElementById('modal-bgp-peer-out-network-name')) document.getElementById('modal-bgp-peer-out-network-name').value = '';
+
   populateBgpRoutingTables();
 
   const container = document.getElementById('modal-bgp-output-networks-container');
@@ -7726,6 +7981,8 @@ function openEditBgpPeerModal(id) {
   document.getElementById('modal-bgp-peer-enabled').checked = p.enabled !== false && !p.disabled;
   document.getElementById('modal-bgp-peer-comment').value = p.comment || '';
   document.getElementById('modal-bgp-peer-name').value = p.name || '';
+  if (document.getElementById('modal-bgp-peer-instance')) document.getElementById('modal-bgp-peer-instance').value = p.instance || 'bgp-instance-1';
+  if (document.getElementById('modal-bgp-peer-template')) document.getElementById('modal-bgp-peer-template').value = p.template || '';
   document.getElementById('modal-bgp-peer-local-as').value = p.local_as || '';
   document.getElementById('modal-bgp-peer-router-id').value = p.router_id || '';
   document.getElementById('modal-bgp-peer-afi-ip').checked = true;
@@ -7733,34 +7990,45 @@ function openEditBgpPeerModal(id) {
   document.getElementById('modal-bgp-peer-remote-addr').value = p.remote_address || '';
   document.getElementById('modal-bgp-peer-remote-port').value = p.remote_port || '';
   document.getElementById('modal-bgp-peer-remote-as').value = p.remote_as || '';
+  if (document.getElementById('modal-bgp-peer-remote-allow-as')) document.getElementById('modal-bgp-peer-remote-allow-as').value = p.remote_allow_as || '';
   document.getElementById('modal-bgp-peer-local-addr').value = p.local_address || '';
   document.getElementById('modal-bgp-peer-local-port').value = p.local_port || '';
   document.getElementById('modal-bgp-peer-local-role').value = p.local_role || 'ebgp';
   document.getElementById('modal-bgp-peer-password').value = p.password || '';
   document.getElementById('modal-bgp-peer-multihop').checked = !!p.multihop;
+  if (document.getElementById('modal-bgp-peer-tx-ttl')) document.getElementById('modal-bgp-peer-tx-ttl').value = p.tx_ttl || '';
+  if (document.getElementById('modal-bgp-peer-rx-min-ttl')) document.getElementById('modal-bgp-peer-rx-min-ttl').value = p.rx_min_ttl || '';
   document.getElementById('modal-bgp-peer-connect').checked = p.connect !== false;
   document.getElementById('modal-bgp-peer-listen').checked = p.listen !== false;
 
   // Extra Tab
-  document.getElementById('modal-bgp-peer-holdtime').value = p.hold_time ? formatBgpTime(p.hold_time) : '00:03:00';
-  document.getElementById('modal-bgp-peer-keepalive').value = p.keepalive_time ? formatBgpTime(p.keepalive_time) : '00:01:00';
+  document.getElementById('modal-bgp-peer-holdtime').value = p.hold_time ? formatBgpTime(p.hold_time) : '00:00:30';
+  document.getElementById('modal-bgp-peer-keepalive').value = p.keepalive_time ? formatBgpTime(p.keepalive_time) : '00:00:10';
+  if (document.getElementById('modal-bgp-peer-use-bfd')) document.getElementById('modal-bgp-peer-use-bfd').checked = !!p.use_bfd;
   document.getElementById('modal-bgp-peer-routing-table').value = p.routing_table || 'main';
+  if (document.getElementById('modal-bgp-peer-vrf')) document.getElementById('modal-bgp-peer-vrf').value = p.vrf || 'main';
+  if (document.getElementById('modal-bgp-peer-no-client-reflect')) document.getElementById('modal-bgp-peer-no-client-reflect').checked = p.no_client_reflect !== false;
   document.getElementById('modal-bgp-peer-redist-conn').checked = !!p.redist_connected;
   document.getElementById('modal-bgp-peer-redist-static').checked = p.redist_static !== false;
+  if (document.getElementById('modal-bgp-peer-redist-rip')) document.getElementById('modal-bgp-peer-redist-rip').checked = !!p.redist_rip;
   document.getElementById('modal-bgp-peer-redist-ospf').checked = !!p.redist_ospf;
   document.getElementById('modal-bgp-peer-redist-bgp').checked = !!p.redist_bgp;
+  if (document.getElementById('modal-bgp-peer-redist-vpn')) document.getElementById('modal-bgp-peer-redist-vpn').checked = !!p.redist_vpn;
+  if (document.getElementById('modal-bgp-peer-redist-dhcp')) document.getElementById('modal-bgp-peer-redist-dhcp').checked = !!p.redist_dhcp;
   document.getElementById('modal-bgp-peer-default-orig').checked = !!p.default_originate;
 
   // Attributes Tab
   document.getElementById('modal-bgp-peer-nexthop-choice').value = p.nexthop_choice || (p.nexthop_self ? 'force-self' : 'default');
   document.getElementById('modal-bgp-peer-default-prepend').value = p.default_prepend || 0;
   document.getElementById('modal-bgp-peer-allow-as-in').value = p.allow_as_in || 0;
+  if (document.getElementById('modal-bgp-peer-ignore-as-path-len')) document.getElementById('modal-bgp-peer-ignore-as-path-len').checked = !!p.ignore_as_path_len;
   document.getElementById('modal-bgp-peer-remove-priv-as').checked = !!p.remove_private_as;
   document.getElementById('modal-bgp-peer-as-override').checked = !!p.as_override;
 
   // Filter Tab
   document.getElementById('modal-bgp-peer-in-filter').value = p.in_filter || '';
   document.getElementById('modal-bgp-peer-out-filter').value = p.out_filter || '';
+  if (document.getElementById('modal-bgp-peer-out-network-name')) document.getElementById('modal-bgp-peer-out-network-name').value = p.output_network || '';
 
   // Output Networks
   const container = document.getElementById('modal-bgp-output-networks-container');
@@ -7817,35 +8085,48 @@ function getBgpPeerFormData() {
     enabled: enabled,
     disabled: !enabled,
     comment: document.getElementById('modal-bgp-peer-comment').value.trim(),
+    instance: document.getElementById('modal-bgp-peer-instance')?.value.trim() || 'bgp-instance-1',
+    template: document.getElementById('modal-bgp-peer-template')?.value.trim() || '',
     local_as: parseInt(document.getElementById('modal-bgp-peer-local-as')?.value, 10) || 0,
     router_id: document.getElementById('modal-bgp-peer-router-id').value.trim(),
     afi: document.getElementById('modal-bgp-peer-afi-ipv6').checked ? 'ipv6' : 'ip',
     remote_address: document.getElementById('modal-bgp-peer-remote-addr').value.trim(),
     remote_port: parseInt(document.getElementById('modal-bgp-peer-remote-port').value, 10) || 0,
     remote_as: parseInt(document.getElementById('modal-bgp-peer-remote-as').value, 10) || 0,
+    remote_allow_as: document.getElementById('modal-bgp-peer-remote-allow-as')?.value.trim() || '',
     local_address: document.getElementById('modal-bgp-peer-local-addr').value.trim(),
     local_port: parseInt(document.getElementById('modal-bgp-peer-local-port').value, 10) || 0,
     local_role: document.getElementById('modal-bgp-peer-local-role').value,
     password: document.getElementById('modal-bgp-peer-password').value,
     multihop: document.getElementById('modal-bgp-peer-multihop').checked,
+    tx_ttl: parseInt(document.getElementById('modal-bgp-peer-tx-ttl')?.value, 10) || 0,
+    rx_min_ttl: parseInt(document.getElementById('modal-bgp-peer-rx-min-ttl')?.value, 10) || 0,
     connect: document.getElementById('modal-bgp-peer-connect').checked,
     listen: document.getElementById('modal-bgp-peer-listen').checked,
     hold_time: parseBgpTime(document.getElementById('modal-bgp-peer-holdtime').value),
     keepalive_time: parseBgpTime(document.getElementById('modal-bgp-peer-keepalive').value),
+    use_bfd: document.getElementById('modal-bgp-peer-use-bfd')?.checked || false,
     routing_table: document.getElementById('modal-bgp-peer-routing-table').value,
+    vrf: document.getElementById('modal-bgp-peer-vrf')?.value.trim() || 'main',
+    no_client_reflect: document.getElementById('modal-bgp-peer-no-client-reflect')?.checked || false,
     redist_connected: document.getElementById('modal-bgp-peer-redist-conn').checked,
     redist_static: document.getElementById('modal-bgp-peer-redist-static').checked,
+    redist_rip: document.getElementById('modal-bgp-peer-redist-rip')?.checked || false,
     redist_ospf: document.getElementById('modal-bgp-peer-redist-ospf').checked,
     redist_bgp: document.getElementById('modal-bgp-peer-redist-bgp').checked,
+    redist_vpn: document.getElementById('modal-bgp-peer-redist-vpn')?.checked || false,
+    redist_dhcp: document.getElementById('modal-bgp-peer-redist-dhcp')?.checked || false,
     default_originate: document.getElementById('modal-bgp-peer-default-orig').checked,
     nexthop_choice: document.getElementById('modal-bgp-peer-nexthop-choice').value,
     nexthop_self: document.getElementById('modal-bgp-peer-nexthop-choice').value === 'force-self',
     default_prepend: parseInt(document.getElementById('modal-bgp-peer-default-prepend').value, 10) || 0,
     allow_as_in: parseInt(document.getElementById('modal-bgp-peer-allow-as-in').value, 10) || 0,
+    ignore_as_path_len: document.getElementById('modal-bgp-peer-ignore-as-path-len')?.checked || false,
     remove_private_as: document.getElementById('modal-bgp-peer-remove-priv-as').checked,
     as_override: document.getElementById('modal-bgp-peer-as-override').checked,
     in_filter: (document.getElementById('modal-bgp-peer-in-filter')?.value || '').trim(),
     out_filter: (document.getElementById('modal-bgp-peer-out-filter')?.value || '').trim(),
+    output_network: (document.getElementById('modal-bgp-peer-out-network-name')?.value || '').trim(),
     output_networks: outputNetworks
   };
 }

+ 359 - 53
internal/web/static/index.html

@@ -1025,12 +1025,12 @@
               <table class="w-full text-left text-xs font-mono">
                 <thead class="text-slate-400 bg-slate-950/60 border-b border-slate-800 uppercase tracking-wider text-[10px] font-sans">
                   <tr>
-                    <th class="py-2.5 px-3 w-12 text-center" title="Flags: R=Established, X=Disabled">Flag</th>
-                    <th class="py-2.5 px-4">Connection Name</th>
-                    <th class="py-2.5 px-4">Local Subnet</th>
+                    <th class="py-2.5 px-3 w-12 text-center" title="Tunnel Status: Green=Active SA, Red=Down">Status</th>
+                    <th class="py-2.5 px-4">Tunnel Name</th>
                     <th class="py-2.5 px-4">Remote Gateway</th>
-                    <th class="py-2.5 px-4">Remote Subnet</th>
-                    <th class="py-2.5 px-4">Status</th>
+                    <th class="py-2.5 px-4">Phase 1 (IKE)</th>
+                    <th class="py-2.5 px-4">Phase 2 Selectors (Subnets)</th>
+                    <th class="py-2.5 px-4">Phase 2 (ESP)</th>
                     <th class="py-2.5 px-4 text-right font-sans">Actions</th>
                   </tr>
                 </thead>
@@ -3624,59 +3624,243 @@
     </div>
   </div>
 
-  <!-- ================= MODAL: IPSEC TUNNEL ================= -->
+  <!-- ================= MODAL: IPSEC TUNNEL (FORTIGATE PHASE 1 & PHASE 2 STYLE) ================= -->
   <div id="ipsec-modal" class="fixed inset-0 bg-slate-950/80 backdrop-blur-sm z-50 hidden flex items-center justify-center p-4">
-    <div class="bg-slate-900 border border-slate-800 rounded-2xl w-full max-w-lg shadow-2xl overflow-hidden animate-in">
-      <div class="px-6 py-4 border-b border-slate-800 flex items-center justify-between bg-slate-950/40">
-        <h3 id="ipsec-modal-title" class="text-sm font-bold text-white">Configure IPSec IKEv2 Tunnel</h3>
-        <button onclick="closeAddIPSecModal()" class="text-slate-400 hover:text-white p-1 rounded-lg">✕</button>
-      </div>
-      <form onsubmit="submitSaveIPSec(event)" class="p-6 space-y-4">
-        <div>
-          <label class="block text-xs font-semibold text-slate-300 mb-1.5">Connection Name *</label>
-          <input type="text" id="ipsec-modal-name" placeholder="e.g. site-to-site-office" required class="w-full bg-slate-800 border border-slate-700 rounded-lg px-3 py-2 text-xs text-white font-mono">
-        </div>
-        <div class="grid grid-cols-2 gap-4">
-          <div>
-            <label class="block text-xs font-semibold text-slate-300 mb-1.5">Local Subnet *</label>
-            <input type="text" id="ipsec-modal-localts" placeholder="192.168.10.0/24" required class="w-full bg-slate-800 border border-slate-700 rounded-lg px-3 py-2 text-xs text-white font-mono">
+    <div class="bg-slate-900 border border-slate-800 rounded-2xl w-full max-w-2xl shadow-2xl overflow-hidden animate-in">
+      <!-- Modal Header -->
+      <div class="px-6 py-4 border-b border-slate-800 flex items-center justify-between bg-slate-950/60">
+        <div class="flex items-center gap-2.5">
+          <div class="w-8 h-8 rounded-lg bg-amber-500/10 border border-amber-500/30 flex items-center justify-center text-amber-400">
+            <svg class="w-4 h-4" fill="none" stroke="currentColor" viewBox="0 0 24 24">
+              <path stroke-linecap="round" stroke-linejoin="round" stroke-width="2" d="M12 15v2m-6 4h12a2 2 0 002-2v-6a2 2 0 00-2-2H6a2 2 0 00-2 2v6a2 2 0 002 2zm10-10V7a4 4 0 00-8 0v4h8z" />
+            </svg>
           </div>
           <div>
-            <label class="block text-xs font-semibold text-slate-300 mb-1.5">Remote Subnet *</label>
-            <input type="text" id="ipsec-modal-remotets" placeholder="192.168.20.0/24" required class="w-full bg-slate-800 border border-slate-700 rounded-lg px-3 py-2 text-xs text-white font-mono">
+            <h3 id="ipsec-modal-title" class="text-sm font-bold text-white">IPsec Tunnel (FortiGate Style)</h3>
+            <p class="text-[11px] text-slate-400">Phase 1 (IKE Gateway) &amp; Phase 2 (Traffic Selectors / Multi-Subnet)</p>
           </div>
         </div>
-        <div>
-          <label class="block text-xs font-semibold text-slate-300 mb-1.5">Remote Gateway IP / FQDN *</label>
-          <input type="text" id="ipsec-modal-remotegw" placeholder="203.0.113.50 or vpn.remote.com" required class="w-full bg-slate-800 border border-slate-700 rounded-lg px-3 py-2 text-xs text-white font-mono">
-        </div>
-        <div>
-          <label class="block text-xs font-semibold text-slate-300 mb-1.5">Pre-Shared Key (PSK) *</label>
-          <input type="password" id="ipsec-modal-psk" placeholder="Secret Pre-Shared Key" required class="w-full bg-slate-800 border border-slate-700 rounded-lg px-3 py-2 text-xs text-white font-mono">
-        </div>
-        <div class="grid grid-cols-2 gap-4">
-          <div>
-            <label class="block text-xs font-semibold text-slate-300 mb-1.5">IKE Proposals</label>
-            <input type="text" id="ipsec-modal-ikeprop" value="aes256-sha256-modp2048,default" class="w-full bg-slate-800 border border-slate-700 rounded-lg px-3 py-2 text-[11px] text-white font-mono">
+        <button onclick="closeAddIPSecModal()" class="text-slate-400 hover:text-white p-1 rounded-lg">✕</button>
+      </div>
+
+      <!-- FortiGate Phase Navigation Tabs -->
+      <div class="flex border-b border-slate-800 bg-slate-950/40 px-6 pt-2 gap-2 text-xs">
+        <button type="button" id="tab-btn-ipsec-p1" onclick="switchIpsecModalTab('p1')" class="px-4 py-2 font-bold rounded-t-lg transition border-b-2 border-amber-500 text-amber-300 bg-slate-900/80">
+          Phase 1 (Network &amp; IKE)
+        </button>
+        <button type="button" id="tab-btn-ipsec-p2" onclick="switchIpsecModalTab('p2')" class="px-4 py-2 font-medium rounded-t-lg transition text-slate-400 hover:text-white hover:bg-slate-800/60">
+          Phase 2 (Selectors &amp; Subnets)
+        </button>
+      </div>
+
+      <form onsubmit="submitSaveIPSec(event)" class="p-6 space-y-4 max-h-[75vh] overflow-y-auto text-xs">
+        <!-- PHASE 1 TAB CONTENT -->
+        <div id="ipsec-tab-content-p1" class="space-y-4">
+          <!-- General / Network -->
+          <div class="p-4 bg-slate-950/40 border border-slate-800 rounded-xl space-y-3">
+            <div class="text-[11px] font-bold text-amber-400 uppercase tracking-wider">Network &amp; Gateway</div>
+            
+            <div class="grid grid-cols-2 gap-3">
+              <div>
+                <label class="block font-semibold text-slate-300 mb-1">Tunnel Name *</label>
+                <input type="text" id="ipsec-modal-name" placeholder="e.g. to_branch_office" required class="w-full bg-slate-800 border border-slate-700 rounded-lg px-3 py-1.5 text-white font-mono focus:border-amber-500">
+              </div>
+              <div>
+                <label class="block font-semibold text-slate-300 mb-1">Remote Gateway IP / FQDN *</label>
+                <input type="text" id="ipsec-modal-remotegw" placeholder="e.g. 198.51.100.2 or vpn.remote.com" required class="w-full bg-slate-800 border border-slate-700 rounded-lg px-3 py-1.5 text-white font-mono focus:border-amber-500">
+              </div>
+            </div>
+
+            <div class="grid grid-cols-2 gap-3">
+              <div>
+                <label class="block font-semibold text-slate-300 mb-1">Outgoing Interface</label>
+                <select id="ipsec-modal-iface" class="w-full bg-slate-800 border border-slate-700 rounded-lg px-3 py-1.5 text-white font-mono">
+                  <option value="%any">Auto (%any)</option>
+                </select>
+              </div>
+              <div>
+                <label class="block font-semibold text-slate-300 mb-1">Local Gateway IP (Optional)</label>
+                <input type="text" id="ipsec-modal-localaddrs" placeholder="%any (Default)" class="w-full bg-slate-800 border border-slate-700 rounded-lg px-3 py-1.5 text-white font-mono">
+              </div>
+            </div>
+
+            <div>
+              <label class="block font-semibold text-slate-300 mb-1">Comments</label>
+              <input type="text" id="ipsec-modal-comments" placeholder="P2P Tunnel to Branch Office FortiGate" class="w-full bg-slate-800 border border-slate-700 rounded-lg px-3 py-1.5 text-white">
+            </div>
           </div>
-          <div>
-            <label class="block text-xs font-semibold text-slate-300 mb-1.5">ESP Proposals</label>
-            <input type="text" id="ipsec-modal-espprop" value="aes256-sha256-modp2048,default" class="w-full bg-slate-800 border border-slate-700 rounded-lg px-3 py-2 text-[11px] text-white font-mono">
+
+          <!-- Authentication -->
+          <div class="p-4 bg-slate-950/40 border border-slate-800 rounded-xl space-y-3">
+            <div class="text-[11px] font-bold text-amber-400 uppercase tracking-wider">Authentication</div>
+
+            <div class="grid grid-cols-3 gap-3">
+              <div>
+                <label class="block font-semibold text-slate-300 mb-1">IKE Version</label>
+                <select id="ipsec-modal-ikever" class="w-full bg-slate-800 border border-slate-700 rounded-lg px-3 py-1.5 text-white font-mono">
+                  <option value="ikev2" selected>IKEv2 (Recommended)</option>
+                  <option value="ikev1">IKEv1</option>
+                </select>
+              </div>
+              <div class="col-span-2">
+                <label class="block font-semibold text-slate-300 mb-1">Pre-Shared Key (PSK) *</label>
+                <div class="relative">
+                  <input type="password" id="ipsec-modal-psk" placeholder="Enter secure pre-shared key" required class="w-full bg-slate-800 border border-slate-700 rounded-lg pl-3 pr-10 py-1.5 text-white font-mono focus:border-amber-500">
+                  <button type="button" onclick="toggleIpsecPskVisibility()" class="absolute right-2.5 top-2 text-slate-400 hover:text-white" title="Show/Hide Key">
+                    <svg class="w-4 h-4" fill="none" stroke="currentColor" viewBox="0 0 24 24">
+                      <path stroke-linecap="round" stroke-linejoin="round" stroke-width="2" d="M15 12a3 3 0 11-6 0 3 3 0 016 0z" />
+                      <path stroke-linecap="round" stroke-linejoin="round" stroke-width="2" d="M2.458 12C3.732 7.943 7.523 5 12 5c4.478 0 8.268 2.943 9.542 7-1.274 4.057-5.064 7-9.542 7-4.477 0-8.268-2.943-9.542-7z" />
+                    </svg>
+                  </button>
+                </div>
+              </div>
+            </div>
+
+            <div class="grid grid-cols-2 gap-3">
+              <div>
+                <label class="block font-medium text-slate-400 mb-1">Local ID (Optional)</label>
+                <input type="text" id="ipsec-modal-localid" placeholder="e.g. my-router-id" class="w-full bg-slate-800 border border-slate-700 rounded-lg px-3 py-1.5 text-white font-mono">
+              </div>
+              <div>
+                <label class="block font-medium text-slate-400 mb-1">Remote ID (Optional)</label>
+                <input type="text" id="ipsec-modal-remoteid" placeholder="e.g. remote-router-id" class="w-full bg-slate-800 border border-slate-700 rounded-lg px-3 py-1.5 text-white font-mono">
+              </div>
+            </div>
+          </div>
+
+          <!-- Phase 1 Proposals & Timers -->
+          <div class="p-4 bg-slate-950/40 border border-slate-800 rounded-xl space-y-3">
+            <div class="text-[11px] font-bold text-amber-400 uppercase tracking-wider">Phase 1 Cryptographic Proposals</div>
+
+            <div class="grid grid-cols-2 gap-3">
+              <div>
+                <label class="block font-semibold text-slate-300 mb-1">IKE Proposals</label>
+                <input type="text" id="ipsec-modal-ikeprop" value="aes256-sha256-modp2048,default" class="w-full bg-slate-800 border border-slate-700 rounded-lg px-3 py-1.5 text-white font-mono">
+                <p class="text-[10px] text-slate-500 mt-0.5">e.g. aes256-sha256-modp2048,default</p>
+              </div>
+              <div>
+                <label class="block font-semibold text-slate-300 mb-1">Diffie-Hellman Group</label>
+                <select id="ipsec-modal-dhgroup" onchange="updateIpsecIkeProposalFromDH(this.value)" class="w-full bg-slate-800 border border-slate-700 rounded-lg px-3 py-1.5 text-white font-mono">
+                  <option value="modp2048" selected>Group 14 (2048-bit MODP)</option>
+                  <option value="ecp256">Group 19 (256-bit ECP)</option>
+                  <option value="ecp384">Group 20 (384-bit ECP)</option>
+                  <option value="modp1536">Group 5 (1536-bit MODP)</option>
+                  <option value="modp1024">Group 2 (1024-bit MODP)</option>
+                </select>
+              </div>
+            </div>
+
+            <div class="grid grid-cols-2 gap-3">
+              <div>
+                <label class="block font-semibold text-slate-300 mb-1">IKE Keylife (seconds)</label>
+                <input type="number" id="ipsec-modal-ikekeylife" value="86400" min="60" max="604800" class="w-full bg-slate-800 border border-slate-700 rounded-lg px-3 py-1.5 text-white font-mono">
+              </div>
+              <div>
+                <label class="block font-semibold text-slate-300 mb-1">Dead Peer Detection (DPD)</label>
+                <select id="ipsec-modal-dpd" class="w-full bg-slate-800 border border-slate-700 rounded-lg px-3 py-1.5 text-white font-mono">
+                  <option value="on-demand" selected>On-Demand (Recommended)</option>
+                  <option value="periodic">Periodic</option>
+                  <option value="disabled">Disabled</option>
+                </select>
+              </div>
+            </div>
           </div>
         </div>
-        <div class="grid grid-cols-2 gap-4 pt-2">
-          <label class="flex items-center space-x-2 cursor-pointer">
-            <input type="checkbox" id="ipsec-modal-autostart" checked class="rounded bg-slate-800 border-slate-700 text-amber-500">
-            <span class="text-xs text-slate-300 font-medium">Auto-start on boot</span>
-          </label>
-          <label class="flex items-center space-x-2 cursor-pointer">
-            <input type="checkbox" id="ipsec-modal-disabled" class="rounded bg-slate-800 border-slate-700 text-rose-500">
-            <span class="text-xs text-slate-300 font-medium">Disabled (X)</span>
-          </label>
+
+        <!-- PHASE 2 TAB CONTENT -->
+        <div id="ipsec-tab-content-p2" class="hidden space-y-4">
+          <!-- Phase 2 Parameters -->
+          <div class="p-4 bg-slate-950/40 border border-slate-800 rounded-xl space-y-3">
+            <div class="text-[11px] font-bold text-amber-400 uppercase tracking-wider">Phase 2 Parameters &amp; PFS</div>
+
+            <div class="grid grid-cols-2 gap-3">
+              <div>
+                <label class="block font-semibold text-slate-300 mb-1">Phase 2 Name</label>
+                <input type="text" id="ipsec-modal-p2name" placeholder="Auto: <Name>_p2" class="w-full bg-slate-800 border border-slate-700 rounded-lg px-3 py-1.5 text-white font-mono">
+              </div>
+              <div>
+                <label class="block font-semibold text-slate-300 mb-1">ESP Keylife (seconds)</label>
+                <input type="number" id="ipsec-modal-espkeylife" value="28800" min="60" max="604800" class="w-full bg-slate-800 border border-slate-700 rounded-lg px-3 py-1.5 text-white font-mono">
+              </div>
+            </div>
+
+            <div class="grid grid-cols-2 gap-3">
+              <div>
+                <label class="block font-semibold text-slate-300 mb-1">ESP Proposals</label>
+                <input type="text" id="ipsec-modal-espprop" value="aes256-sha256-modp2048,default" class="w-full bg-slate-800 border border-slate-700 rounded-lg px-3 py-1.5 text-white font-mono">
+                <p class="text-[10px] text-slate-500 mt-0.5">e.g. aes256-sha256-modp2048,default</p>
+              </div>
+              <div>
+                <label class="block font-semibold text-slate-300 mb-1">PFS Key Group (Perfect Forward Secrecy)</label>
+                <select id="ipsec-modal-pfsgroup" onchange="updateIpsecEspProposalFromPFS(this.value)" class="w-full bg-slate-800 border border-slate-700 rounded-lg px-3 py-1.5 text-white font-mono">
+                  <option value="modp2048" selected>Group 14 (2048-bit MODP)</option>
+                  <option value="ecp256">Group 19 (256-bit ECP)</option>
+                  <option value="ecp384">Group 20 (384-bit ECP)</option>
+                  <option value="none">None (No PFS)</option>
+                </select>
+              </div>
+            </div>
+
+            <div class="flex items-center gap-6 pt-1">
+              <label class="flex items-center space-x-2 cursor-pointer">
+                <input type="checkbox" id="ipsec-modal-autostart" checked class="rounded bg-slate-800 border-slate-700 text-amber-500">
+                <span class="font-medium text-slate-300">Auto-negotiate / Always-On (Start)</span>
+              </label>
+              <label class="flex items-center space-x-2 cursor-pointer">
+                <input type="checkbox" id="ipsec-modal-disabled" class="rounded bg-slate-800 border-slate-700 text-rose-500">
+                <span class="font-medium text-slate-300">Disabled (X)</span>
+              </label>
+            </div>
+          </div>
+
+          <!-- PHASE 2 SELECTORS: DYNAMIC MULTI-SUBNETS (FortiGate Style) -->
+          <div class="grid grid-cols-1 md:grid-cols-2 gap-4">
+            <!-- Local Subnets (Local Address / Selectors) -->
+            <div class="p-4 bg-slate-950/60 border border-slate-800 rounded-xl space-y-3">
+              <div class="flex items-center justify-between">
+                <div>
+                  <div class="text-[11px] font-bold text-cyan-400 uppercase tracking-wider">Local Subnets (Selectors)</div>
+                  <p class="text-[10px] text-slate-400">Subnets on this router to encrypt</p>
+                </div>
+                <button type="button" onclick="addIpsecLocalSubnet('')" class="px-2 py-1 text-[11px] font-semibold bg-cyan-600 hover:bg-cyan-500 text-white rounded shadow-sm flex items-center gap-1">
+                  <span>+ Subnet</span>
+                </button>
+              </div>
+
+              <!-- Container for dynamic local subnets -->
+              <div id="ipsec-modal-local-subnets-container" class="space-y-2 max-h-48 overflow-y-auto pr-1"></div>
+            </div>
+
+            <!-- Remote Subnets (Remote Address / Selectors) -->
+            <div class="p-4 bg-slate-950/60 border border-slate-800 rounded-xl space-y-3">
+              <div class="flex items-center justify-between">
+                <div>
+                  <div class="text-[11px] font-bold text-purple-400 uppercase tracking-wider">Remote Subnets (Selectors)</div>
+                  <p class="text-[10px] text-slate-400">Subnets on remote router to access</p>
+                </div>
+                <button type="button" onclick="addIpsecRemoteSubnet('')" class="px-2 py-1 text-[11px] font-semibold bg-purple-600 hover:bg-purple-500 text-white rounded shadow-sm flex items-center gap-1">
+                  <span>+ Subnet</span>
+                </button>
+              </div>
+
+              <!-- Container for dynamic remote subnets -->
+              <div id="ipsec-modal-remote-subnets-container" class="space-y-2 max-h-48 overflow-y-auto pr-1"></div>
+            </div>
+          </div>
         </div>
-        <div class="pt-4 border-t border-slate-800 flex justify-end space-x-3">
+
+        <!-- Footer -->
+        <div class="pt-4 border-t border-slate-800 flex items-center justify-between">
           <button type="button" onclick="closeAddIPSecModal()" class="px-4 py-2 rounded-lg text-xs font-medium text-slate-400 hover:bg-slate-800">Cancel</button>
-          <button type="submit" class="px-5 py-2 rounded-lg text-xs font-semibold bg-amber-600 hover:bg-amber-500 text-white">Save Tunnel</button>
+
+          <div class="flex items-center space-x-2">
+            <button type="button" id="btn-ipsec-switch-step" onclick="toggleIpsecPhaseStep()" class="px-4 py-2 rounded-lg text-xs font-semibold bg-slate-800 hover:bg-slate-700 text-amber-300 border border-slate-700">
+              Next: Phase 2 Selectors &rarr;
+            </button>
+            <button type="submit" class="px-5 py-2 rounded-lg text-xs font-bold bg-amber-600 hover:bg-amber-500 text-white shadow-lg shadow-amber-500/20">
+              Save Tunnel (OK)
+            </button>
+          </div>
         </div>
       </form>
     </div>
@@ -7461,6 +7645,21 @@
                 </div>
               </div>
 
+              <div class="grid grid-cols-3 items-center gap-3">
+                <label class="text-slate-400 font-medium">Instance</label>
+                <div class="col-span-2 flex items-center gap-1.5">
+                  <input type="text" id="modal-bgp-peer-instance" placeholder="bgp-instance-1" class="flex-1 bg-slate-800 border border-slate-700 rounded-lg px-3 py-1.5 text-white font-mono">
+                  <button type="button" onclick="openBgpInstanceModal()" class="px-2 py-1 bg-slate-800 hover:bg-slate-700 border border-slate-700 rounded text-slate-300 font-mono text-xs">...</button>
+                </div>
+              </div>
+
+              <div class="grid grid-cols-3 items-center gap-3">
+                <label class="text-slate-400 font-medium">Template</label>
+                <div class="col-span-2">
+                  <input type="text" id="modal-bgp-peer-template" placeholder="AWMN-ebgp (Optional)" class="w-full bg-slate-800 border border-slate-700 rounded-lg px-3 py-1.5 text-white font-mono">
+                </div>
+              </div>
+
               <div class="grid grid-cols-3 items-center gap-3">
                 <label class="text-slate-400 font-medium">AS (Local AS)</label>
                 <div class="col-span-2">
@@ -7471,15 +7670,27 @@
 
               <div class="grid grid-cols-3 items-center gap-3">
                 <label class="text-slate-400 font-medium">AFI</label>
-                <div class="col-span-2 flex items-center gap-4 text-slate-300">
+                <div class="col-span-2 flex items-center flex-wrap gap-3 text-slate-300">
                   <label class="flex items-center gap-1.5 cursor-pointer">
                     <input type="checkbox" id="modal-bgp-peer-afi-ip" checked class="rounded text-cyan-500 bg-slate-800 border-slate-700">
-                    <span>ip (IPv4)</span>
+                    <span>ip</span>
                   </label>
                   <label class="flex items-center gap-1.5 cursor-pointer">
                     <input type="checkbox" id="modal-bgp-peer-afi-ipv6" class="rounded text-cyan-500 bg-slate-800 border-slate-700">
                     <span>ipv6</span>
                   </label>
+                  <label class="flex items-center gap-1.5 cursor-pointer">
+                    <input type="checkbox" id="modal-bgp-peer-afi-l2vpn" class="rounded text-cyan-500 bg-slate-800 border-slate-700">
+                    <span>l2vpn</span>
+                  </label>
+                  <label class="flex items-center gap-1.5 cursor-pointer">
+                    <input type="checkbox" id="modal-bgp-peer-afi-vpnv4" class="rounded text-cyan-500 bg-slate-800 border-slate-700">
+                    <span>vpnv4</span>
+                  </label>
+                  <label class="flex items-center gap-1.5 cursor-pointer">
+                    <input type="checkbox" id="modal-bgp-peer-afi-l2vpn-cisco" class="rounded text-cyan-500 bg-slate-800 border-slate-700">
+                    <span>l2vpn cisco</span>
+                  </label>
                 </div>
               </div>
 
@@ -7513,6 +7724,13 @@
                 </div>
               </div>
 
+              <div class="grid grid-cols-3 items-center gap-3">
+                <label class="text-slate-400 font-medium">Remote Allow AS</label>
+                <div class="col-span-2">
+                  <input type="text" id="modal-bgp-peer-remote-allow-as" placeholder="e.g. 17244,65000 (Optional)" class="w-full bg-slate-800 border border-slate-700 rounded-lg px-3 py-1.5 text-white font-mono">
+                </div>
+              </div>
+
               <hr class="border-slate-800/80 my-2">
 
               <div class="grid grid-cols-3 items-center gap-3">
@@ -7558,6 +7776,20 @@
                 </div>
               </div>
 
+              <div class="grid grid-cols-3 items-center gap-3">
+                <label class="text-slate-400 font-medium">TTL Limits</label>
+                <div class="col-span-2 grid grid-cols-2 gap-3">
+                  <div>
+                    <span class="text-[10px] text-slate-500 block mb-0.5">Tx TTL</span>
+                    <input type="number" id="modal-bgp-peer-tx-ttl" min="1" max="255" placeholder="64 (default)" class="w-full bg-slate-800 border border-slate-700 rounded-lg px-3 py-1 text-white font-mono">
+                  </div>
+                  <div>
+                    <span class="text-[10px] text-slate-500 block mb-0.5">Rx Min TTL</span>
+                    <input type="number" id="modal-bgp-peer-rx-min-ttl" min="0" max="255" placeholder="0 (any)" class="w-full bg-slate-800 border border-slate-700 rounded-lg px-3 py-1 text-white font-mono">
+                  </div>
+                </div>
+              </div>
+
               <div class="grid grid-cols-3 items-center gap-3">
                 <label class="text-slate-400 font-medium">Session Mode</label>
                 <div class="col-span-2 flex items-center gap-6 text-slate-300">
@@ -7578,14 +7810,24 @@
               <div class="grid grid-cols-3 items-center gap-3">
                 <label class="text-slate-400 font-medium">Hold Time</label>
                 <div class="col-span-2">
-                  <input type="text" id="modal-bgp-peer-holdtime" placeholder="00:03:00 (180s)" class="w-full bg-slate-800 border border-slate-700 rounded-lg px-3 py-1.5 text-white font-mono">
+                  <input type="text" id="modal-bgp-peer-holdtime" placeholder="00:00:30 (30s) or 00:03:00" class="w-full bg-slate-800 border border-slate-700 rounded-lg px-3 py-1.5 text-white font-mono">
                 </div>
               </div>
 
               <div class="grid grid-cols-3 items-center gap-3">
                 <label class="text-slate-400 font-medium">Keepalive Time</label>
                 <div class="col-span-2">
-                  <input type="text" id="modal-bgp-peer-keepalive" placeholder="00:01:00 (60s)" class="w-full bg-slate-800 border border-slate-700 rounded-lg px-3 py-1.5 text-white font-mono">
+                  <input type="text" id="modal-bgp-peer-keepalive" placeholder="00:00:10 (10s) or 00:01:00" class="w-full bg-slate-800 border border-slate-700 rounded-lg px-3 py-1.5 text-white font-mono">
+                </div>
+              </div>
+
+              <div class="grid grid-cols-3 items-center gap-3">
+                <label class="text-slate-400 font-medium">Use BFD</label>
+                <div class="col-span-2">
+                  <label class="flex items-center gap-2 cursor-pointer text-slate-300">
+                    <input type="checkbox" id="modal-bgp-peer-use-bfd" class="rounded text-cyan-500 bg-slate-800 border-slate-700">
+                    <span>Enable Bidirectional Forwarding Detection (BFD)</span>
+                  </label>
                 </div>
               </div>
 
@@ -7598,11 +7840,28 @@
                 </div>
               </div>
 
+              <div class="grid grid-cols-3 items-center gap-3">
+                <label class="text-slate-400 font-medium">VRF</label>
+                <div class="col-span-2">
+                  <input type="text" id="modal-bgp-peer-vrf" placeholder="main (default)" class="w-full bg-slate-800 border border-slate-700 rounded-lg px-3 py-1.5 text-white font-mono">
+                </div>
+              </div>
+
+              <div class="grid grid-cols-3 items-center gap-3">
+                <label class="text-slate-400 font-medium">Client Reflection</label>
+                <div class="col-span-2">
+                  <label class="flex items-center gap-2 cursor-pointer text-slate-300">
+                    <input type="checkbox" id="modal-bgp-peer-no-client-reflect" checked class="rounded text-cyan-500 bg-slate-800 border-slate-700">
+                    <span>No Client To Client Reflection</span>
+                  </label>
+                </div>
+              </div>
+
               <hr class="border-slate-800/80 my-2">
 
               <div class="grid grid-cols-3 items-start gap-3">
                 <label class="text-slate-400 font-medium pt-1">Output Redistribute</label>
-                <div class="col-span-2 grid grid-cols-2 gap-2 text-slate-300">
+                <div class="col-span-2 grid grid-cols-3 gap-2 text-slate-300">
                   <label class="flex items-center gap-2 cursor-pointer">
                     <input type="checkbox" id="modal-bgp-peer-redist-conn" class="rounded text-cyan-500 bg-slate-800 border-slate-700">
                     <span>connected</span>
@@ -7611,6 +7870,10 @@
                     <input type="checkbox" id="modal-bgp-peer-redist-static" checked class="rounded text-cyan-500 bg-slate-800 border-slate-700">
                     <span>static</span>
                   </label>
+                  <label class="flex items-center gap-2 cursor-pointer">
+                    <input type="checkbox" id="modal-bgp-peer-redist-rip" class="rounded text-cyan-500 bg-slate-800 border-slate-700">
+                    <span>rip</span>
+                  </label>
                   <label class="flex items-center gap-2 cursor-pointer">
                     <input type="checkbox" id="modal-bgp-peer-redist-ospf" class="rounded text-cyan-500 bg-slate-800 border-slate-700">
                     <span>ospf</span>
@@ -7619,6 +7882,22 @@
                     <input type="checkbox" id="modal-bgp-peer-redist-bgp" class="rounded text-cyan-500 bg-slate-800 border-slate-700">
                     <span>bgp</span>
                   </label>
+                  <label class="flex items-center gap-2 cursor-pointer">
+                    <input type="checkbox" id="modal-bgp-peer-redist-vpn" class="rounded text-cyan-500 bg-slate-800 border-slate-700">
+                    <span>vpn</span>
+                  </label>
+                  <label class="flex items-center gap-2 cursor-pointer">
+                    <input type="checkbox" id="modal-bgp-peer-redist-dhcp" class="rounded text-cyan-500 bg-slate-800 border-slate-700">
+                    <span>dhcp</span>
+                  </label>
+                  <label class="flex items-center gap-2 cursor-pointer">
+                    <input type="checkbox" id="modal-bgp-peer-redist-fantasy" class="rounded text-cyan-500 bg-slate-800 border-slate-700">
+                    <span>fantasy</span>
+                  </label>
+                  <label class="flex items-center gap-2 cursor-pointer">
+                    <input type="checkbox" id="modal-bgp-peer-redist-modem" class="rounded text-cyan-500 bg-slate-800 border-slate-700">
+                    <span>modem</span>
+                  </label>
                 </div>
               </div>
 
@@ -7662,6 +7941,16 @@
                 </div>
               </div>
 
+              <div class="grid grid-cols-3 items-center gap-3">
+                <label class="text-slate-400 font-medium">Ignore AS Path Length</label>
+                <div class="col-span-2">
+                  <label class="flex items-center gap-2 cursor-pointer text-slate-300">
+                    <input type="checkbox" id="modal-bgp-peer-ignore-as-path-len" class="rounded text-cyan-500 bg-slate-800 border-slate-700">
+                    <span>Ignore AS Path Length in route selection</span>
+                  </label>
+                </div>
+              </div>
+
               <div class="grid grid-cols-3 items-center gap-3">
                 <label class="text-slate-400 font-medium">Remove Private AS</label>
                 <div class="col-span-2">
@@ -7681,6 +7970,16 @@
                   </label>
                 </div>
               </div>
+
+              <div class="grid grid-cols-3 items-center gap-3">
+                <label class="text-slate-400 font-medium">Cisco VPLS NLRI</label>
+                <div class="col-span-2">
+                  <label class="flex items-center gap-2 cursor-pointer text-slate-300">
+                    <input type="checkbox" id="modal-bgp-peer-vpls-nlri" class="rounded text-cyan-500 bg-slate-800 border-slate-700">
+                    <span>Cisco VPLS NLRI Length Format</span>
+                  </label>
+                </div>
+              </div>
             </div>
 
             <!-- TAB 4: FILTER -->
@@ -7701,6 +8000,13 @@
                 </div>
               </div>
 
+              <div class="grid grid-cols-3 items-center gap-3">
+                <label class="text-slate-400 font-medium">Output Network</label>
+                <div class="col-span-2">
+                  <input type="text" id="modal-bgp-peer-out-network-name" placeholder="e.g. BGP-AWMN" class="w-full bg-slate-800 border border-slate-700 rounded-lg px-3 py-1.5 text-white font-mono">
+                </div>
+              </div>
+
               <hr class="border-slate-800/80 my-3">
 
               <!-- Output Networks section with [+] and [-] buttons as requested! -->