Просмотр исходного кода

feat(ospf): implement complete RouterOS v7 OSPF architecture with 8 tabs, BIRD 2 engine, live telemetry, and WebFig modals

Proxmox Router 1 неделя назад
Родитель
Сommit
4cd9d54604

+ 24 - 1
internal/api/api.go

@@ -249,17 +249,40 @@ func (s *Server) registerRoutes() {
 	s.mux.HandleFunc("DELETE /api/routing/bgp/networks/{id}", s.handleDeleteBGPNetwork)
 	s.mux.HandleFunc("GET /api/routing/bgp/routes", s.handleGetBGPRoutes)
 
-	// OSPF
+	// OSPF (RouterOS v7 Multi-Instance OSPF)
+	s.mux.HandleFunc("GET /api/routing/ospf/instances", s.handleListOSPFInstances)
+	s.mux.HandleFunc("POST /api/routing/ospf/instances", s.handleSaveOSPFInstance)
+	s.mux.HandleFunc("DELETE /api/routing/ospf/instances/{id}", s.handleDeleteOSPFInstance)
+	s.mux.HandleFunc("POST /api/routing/ospf/instances/{id}/state", s.handleSetOSPFInstanceState)
 	s.mux.HandleFunc("GET /api/routing/ospf/instance", s.handleGetOSPFInstance)
 	s.mux.HandleFunc("POST /api/routing/ospf/instance", s.handleUpdateOSPFInstance)
+
 	s.mux.HandleFunc("GET /api/routing/ospf/areas", s.handleListOSPFAreas)
 	s.mux.HandleFunc("POST /api/routing/ospf/areas", s.handleSaveOSPFArea)
 	s.mux.HandleFunc("DELETE /api/routing/ospf/areas/{id}", s.handleDeleteOSPFArea)
+	s.mux.HandleFunc("POST /api/routing/ospf/areas/{id}/state", s.handleSetOSPFAreaState)
+
+	s.mux.HandleFunc("GET /api/routing/ospf/area-ranges", s.handleListOSPFAreaRanges)
+	s.mux.HandleFunc("POST /api/routing/ospf/area-ranges", s.handleSaveOSPFAreaRange)
+	s.mux.HandleFunc("DELETE /api/routing/ospf/area-ranges/{id}", s.handleDeleteOSPFAreaRange)
+
+	s.mux.HandleFunc("GET /api/routing/ospf/templates", s.handleListOSPFInterfaces)
+	s.mux.HandleFunc("POST /api/routing/ospf/templates", s.handleSaveOSPFInterface)
+	s.mux.HandleFunc("DELETE /api/routing/ospf/templates/{id}", s.handleDeleteOSPFInterface)
+	s.mux.HandleFunc("POST /api/routing/ospf/templates/{id}/state", s.handleSetOSPFInterfaceState)
 	s.mux.HandleFunc("GET /api/routing/ospf/interfaces", s.handleListOSPFInterfaces)
 	s.mux.HandleFunc("POST /api/routing/ospf/interfaces", s.handleSaveOSPFInterface)
 	s.mux.HandleFunc("DELETE /api/routing/ospf/interfaces/{id}", s.handleDeleteOSPFInterface)
 	s.mux.HandleFunc("POST /api/routing/ospf/interfaces/{id}/state", s.handleSetOSPFInterfaceState)
+
+	s.mux.HandleFunc("GET /api/routing/ospf/interfaces-live", s.handleGetOSPFInterfacesLive)
+
+	s.mux.HandleFunc("GET /api/routing/ospf/static-neighbors", s.handleListOSPFStaticNeighbors)
+	s.mux.HandleFunc("POST /api/routing/ospf/static-neighbors", s.handleSaveOSPFStaticNeighbor)
+	s.mux.HandleFunc("DELETE /api/routing/ospf/static-neighbors/{id}", s.handleDeleteOSPFStaticNeighbor)
+
 	s.mux.HandleFunc("GET /api/routing/ospf/neighbors", s.handleGetOSPFNeighbors)
+	s.mux.HandleFunc("GET /api/routing/ospf/lsa", s.handleGetOSPFLSA)
 	s.mux.HandleFunc("GET /api/routing/ospf/routes", s.handleGetOSPFRoutes)
 
 	// Routing Filters (/routing filter)

+ 166 - 7
internal/api/dynamic_routing_handlers.go

@@ -136,9 +136,59 @@ func (s *Server) handleGetBGPRoutes(w http.ResponseWriter, r *http.Request) {
 }
 
 // -------------------------------------------------------------
-// OSPF Handlers
+// OSPF Handlers (RouterOS v7 Style OSPF)
 // -------------------------------------------------------------
 
+func (s *Server) handleListOSPFInstances(w http.ResponseWriter, r *http.Request) {
+	insts := s.dynRoutingMgr.ListOSPFInstances()
+	if insts == nil {
+		insts = []dynamicrouting.OSPFInstance{}
+	}
+	writeJSON(w, http.StatusOK, insts)
+}
+
+func (s *Server) handleSaveOSPFInstance(w http.ResponseWriter, r *http.Request) {
+	var inst dynamicrouting.OSPFInstance
+	if err := json.NewDecoder(r.Body).Decode(&inst); err != nil {
+		writeJSON(w, http.StatusBadRequest, map[string]string{"error": "Invalid JSON: " + err.Error()})
+		return
+	}
+	if err := s.dynRoutingMgr.SaveOSPFInstance(&inst); err != nil {
+		writeJSON(w, http.StatusInternalServerError, map[string]string{"error": err.Error()})
+		return
+	}
+	writeJSON(w, http.StatusOK, map[string]interface{}{"status": "success", "instance": inst})
+}
+
+func (s *Server) handleDeleteOSPFInstance(w http.ResponseWriter, r *http.Request) {
+	id := r.PathValue("id")
+	if id == "" {
+		writeJSON(w, http.StatusBadRequest, map[string]string{"error": "Missing instance id"})
+		return
+	}
+	if err := s.dynRoutingMgr.DeleteOSPFInstance(id); err != nil {
+		writeJSON(w, http.StatusInternalServerError, map[string]string{"error": err.Error()})
+		return
+	}
+	writeJSON(w, http.StatusOK, map[string]string{"status": "deleted"})
+}
+
+func (s *Server) handleSetOSPFInstanceState(w http.ResponseWriter, r *http.Request) {
+	id := r.PathValue("id")
+	var req struct {
+		Disabled bool `json:"disabled"`
+	}
+	if err := json.NewDecoder(r.Body).Decode(&req); err != nil {
+		writeJSON(w, http.StatusBadRequest, map[string]string{"error": "Invalid JSON"})
+		return
+	}
+	if err := s.dynRoutingMgr.SetOSPFInstanceState(id, req.Disabled); err != nil {
+		writeJSON(w, http.StatusInternalServerError, map[string]string{"error": err.Error()})
+		return
+	}
+	writeJSON(w, http.StatusOK, map[string]interface{}{"status": "success", "disabled": req.Disabled})
+}
+
 func (s *Server) handleGetOSPFInstance(w http.ResponseWriter, r *http.Request) {
 	inst := s.dynRoutingMgr.GetOSPFInstance()
 	writeJSON(w, http.StatusOK, inst)
@@ -172,8 +222,7 @@ func (s *Server) handleSaveOSPFArea(w http.ResponseWriter, r *http.Request) {
 		return
 	}
 	if area.AreaID == "" {
-		writeJSON(w, http.StatusBadRequest, map[string]string{"error": "area_id is required"})
-		return
+		area.AreaID = "0.0.0.0"
 	}
 	if err := s.dynRoutingMgr.SaveOSPFArea(area); err != nil {
 		writeJSON(w, http.StatusInternalServerError, map[string]string{"error": err.Error()})
@@ -195,6 +244,62 @@ func (s *Server) handleDeleteOSPFArea(w http.ResponseWriter, r *http.Request) {
 	writeJSON(w, http.StatusOK, map[string]string{"status": "deleted"})
 }
 
+func (s *Server) handleSetOSPFAreaState(w http.ResponseWriter, r *http.Request) {
+	id := r.PathValue("id")
+	var req struct {
+		Disabled bool `json:"disabled"`
+	}
+	if err := json.NewDecoder(r.Body).Decode(&req); err != nil {
+		writeJSON(w, http.StatusBadRequest, map[string]string{"error": "Invalid JSON"})
+		return
+	}
+	if err := s.dynRoutingMgr.SetOSPFAreaState(id, req.Disabled); err != nil {
+		writeJSON(w, http.StatusInternalServerError, map[string]string{"error": err.Error()})
+		return
+	}
+	writeJSON(w, http.StatusOK, map[string]interface{}{"status": "success", "disabled": req.Disabled})
+}
+
+// Area Ranges
+func (s *Server) handleListOSPFAreaRanges(w http.ResponseWriter, r *http.Request) {
+	ranges := s.dynRoutingMgr.ListOSPFAreaRanges()
+	if ranges == nil {
+		ranges = []dynamicrouting.OSPFAreaRange{}
+	}
+	writeJSON(w, http.StatusOK, ranges)
+}
+
+func (s *Server) handleSaveOSPFAreaRange(w http.ResponseWriter, r *http.Request) {
+	var ar dynamicrouting.OSPFAreaRange
+	if err := json.NewDecoder(r.Body).Decode(&ar); err != nil {
+		writeJSON(w, http.StatusBadRequest, map[string]string{"error": "Invalid JSON: " + err.Error()})
+		return
+	}
+	if ar.Prefix == "" {
+		writeJSON(w, http.StatusBadRequest, map[string]string{"error": "Prefix is required"})
+		return
+	}
+	if err := s.dynRoutingMgr.SaveOSPFAreaRange(ar); err != nil {
+		writeJSON(w, http.StatusInternalServerError, map[string]string{"error": err.Error()})
+		return
+	}
+	writeJSON(w, http.StatusOK, map[string]interface{}{"status": "success", "area_range": ar})
+}
+
+func (s *Server) handleDeleteOSPFAreaRange(w http.ResponseWriter, r *http.Request) {
+	id := r.PathValue("id")
+	if id == "" {
+		writeJSON(w, http.StatusBadRequest, map[string]string{"error": "Missing range id"})
+		return
+	}
+	if err := s.dynRoutingMgr.DeleteOSPFAreaRange(id); err != nil {
+		writeJSON(w, http.StatusInternalServerError, map[string]string{"error": err.Error()})
+		return
+	}
+	writeJSON(w, http.StatusOK, map[string]string{"status": "deleted"})
+}
+
+// Interface Templates
 func (s *Server) handleListOSPFInterfaces(w http.ResponseWriter, r *http.Request) {
 	ifaces := s.dynRoutingMgr.ListOSPFInterfaces()
 	if ifaces == nil {
@@ -209,10 +314,6 @@ func (s *Server) handleSaveOSPFInterface(w http.ResponseWriter, r *http.Request)
 		writeJSON(w, http.StatusBadRequest, map[string]string{"error": "Invalid JSON: " + err.Error()})
 		return
 	}
-	if iface.Network == "" {
-		writeJSON(w, http.StatusBadRequest, map[string]string{"error": "network is required"})
-		return
-	}
 	if err := s.dynRoutingMgr.SaveOSPFInterface(iface); err != nil {
 		writeJSON(w, http.StatusInternalServerError, map[string]string{"error": err.Error()})
 		return
@@ -249,6 +350,55 @@ func (s *Server) handleSetOSPFInterfaceState(w http.ResponseWriter, r *http.Requ
 	writeJSON(w, http.StatusOK, map[string]interface{}{"status": "success", "disabled": req.Disabled})
 }
 
+// Live Dynamic Interfaces
+func (s *Server) handleGetOSPFInterfacesLive(w http.ResponseWriter, r *http.Request) {
+	live := s.dynRoutingMgr.GetOSPFInterfacesLive()
+	if live == nil {
+		live = []dynamicrouting.OSPFInterfaceLive{}
+	}
+	writeJSON(w, http.StatusOK, live)
+}
+
+// Static Neighbors
+func (s *Server) handleListOSPFStaticNeighbors(w http.ResponseWriter, r *http.Request) {
+	nbrs := s.dynRoutingMgr.ListOSPFStaticNeighbors()
+	if nbrs == nil {
+		nbrs = []dynamicrouting.OSPFStaticNeighbor{}
+	}
+	writeJSON(w, http.StatusOK, nbrs)
+}
+
+func (s *Server) handleSaveOSPFStaticNeighbor(w http.ResponseWriter, r *http.Request) {
+	var nbr dynamicrouting.OSPFStaticNeighbor
+	if err := json.NewDecoder(r.Body).Decode(&nbr); err != nil {
+		writeJSON(w, http.StatusBadRequest, map[string]string{"error": "Invalid JSON: " + err.Error()})
+		return
+	}
+	if nbr.Address == "" {
+		writeJSON(w, http.StatusBadRequest, map[string]string{"error": "Address is required"})
+		return
+	}
+	if err := s.dynRoutingMgr.SaveOSPFStaticNeighbor(nbr); err != nil {
+		writeJSON(w, http.StatusInternalServerError, map[string]string{"error": err.Error()})
+		return
+	}
+	writeJSON(w, http.StatusOK, map[string]interface{}{"status": "success", "neighbor": nbr})
+}
+
+func (s *Server) handleDeleteOSPFStaticNeighbor(w http.ResponseWriter, r *http.Request) {
+	id := r.PathValue("id")
+	if id == "" {
+		writeJSON(w, http.StatusBadRequest, map[string]string{"error": "Missing static neighbor id"})
+		return
+	}
+	if err := s.dynRoutingMgr.DeleteOSPFStaticNeighbor(id); err != nil {
+		writeJSON(w, http.StatusInternalServerError, map[string]string{"error": err.Error()})
+		return
+	}
+	writeJSON(w, http.StatusOK, map[string]string{"status": "deleted"})
+}
+
+// Live Neighbors
 func (s *Server) handleGetOSPFNeighbors(w http.ResponseWriter, r *http.Request) {
 	nbrs := s.dynRoutingMgr.GetOSPFNeighbors()
 	if nbrs == nil {
@@ -257,6 +407,15 @@ func (s *Server) handleGetOSPFNeighbors(w http.ResponseWriter, r *http.Request)
 	writeJSON(w, http.StatusOK, nbrs)
 }
 
+// Live LSA Database
+func (s *Server) handleGetOSPFLSA(w http.ResponseWriter, r *http.Request) {
+	lsa := s.dynRoutingMgr.GetOSPFLSA()
+	if lsa == nil {
+		lsa = []dynamicrouting.OSPFLSA{}
+	}
+	writeJSON(w, http.StatusOK, lsa)
+}
+
 func (s *Server) handleGetOSPFRoutes(w http.ResponseWriter, r *http.Request) {
 	routes := s.dynRoutingMgr.GetOSPFRoutes()
 	if routes == nil {

+ 564 - 63
internal/dynamicrouting/manager.go

@@ -63,24 +63,40 @@ func (m *Manager) initDefaults() {
 		},
 		BGPPeers:    []BGPPeer{},
 		BGPNetworks: []BGPNetwork{},
+		OSPFInstances: []OSPFInstance{
+			{
+				ID:           "ospf-inst-default",
+				Name:         "Default",
+				Version:      2,
+				VRF:          "main",
+				RouterID:     "192.168.131.56",
+				RoutingTable: "main",
+				Enabled:      true,
+			},
+		},
 		OSPFInstance: OSPFInstance{
-			Enabled:               false,
+			Name:                  "Default",
+			Version:               2,
+			VRF:                   "main",
+			Enabled:               true,
 			RouterID:              "192.168.131.56",
 			RedistributeConnected: false,
-			RedistributeStatic:    false,
+			RedistributeStatic:    true,
 			RedistributeBGP:       false,
 			DefaultMetric:         10,
 		},
 		OSPFAreas: []OSPFArea{
 			{
-				ID:       "area-backbone",
-				Name:     "backbone",
+				ID:       "area-default",
+				Name:     "Default",
+				Instance: "Default",
 				AreaID:   "0.0.0.0",
 				AreaType: "default",
-				Comment:  "Default OSPF Backbone Area",
 			},
 		},
+		OSPFAreaRanges: []OSPFAreaRange{},
 		OSPFInterfaces: []OSPFInterface{},
+		OSPFStaticNbrs: []OSPFStaticNeighbor{},
 		RoutingFilters: []RoutingFilterRule{},
 	}
 }
@@ -93,7 +109,39 @@ func (m *Manager) load() error {
 	if err != nil {
 		return err
 	}
-	return json.Unmarshal(data, &m.config)
+	if err := json.Unmarshal(data, &m.config); err != nil {
+		return err
+	}
+
+	if len(m.config.OSPFInstances) == 0 {
+		rID := m.config.OSPFInstance.RouterID
+		if rID == "" {
+			rID = "192.168.131.56"
+		}
+		m.config.OSPFInstances = []OSPFInstance{
+			{
+				ID:           "ospf-inst-default",
+				Name:         "Default",
+				Version:      2,
+				VRF:          "main",
+				RouterID:     rID,
+				RoutingTable: "main",
+				Enabled:      true,
+			},
+		}
+	}
+	if len(m.config.OSPFAreas) == 0 {
+		m.config.OSPFAreas = []OSPFArea{
+			{
+				ID:       "area-default",
+				Name:     "Default",
+				Instance: "Default",
+				AreaID:   "0.0.0.0",
+				AreaType: "default",
+			},
+		}
+	}
+	return nil
 }
 
 func (m *Manager) save() error {
@@ -393,24 +441,44 @@ func (m *Manager) applyBirdConfig() error {
 		}
 	}
 
-	// OSPFv2 Protocol
-	if m.config.OSPFInstance.Enabled {
-		ospfRouterID := routerID
-		if m.config.OSPFInstance.RouterID != "" {
-			ospfRouterID = m.config.OSPFInstance.RouterID
+	// OSPFv2 Protocols (RouterOS v7 Multi-Instance OSPF)
+	ospfInstances := m.config.OSPFInstances
+	if len(ospfInstances) == 0 {
+		ospfInstances = []OSPFInstance{
+			{
+				ID:           "ospf-inst-default",
+				Name:         "Default",
+				Version:      2,
+				VRF:          "main",
+				RouterID:     routerID,
+				RoutingTable: "main",
+				Enabled:      true,
+			},
+		}
+	}
+
+	for _, inst := range ospfInstances {
+		if inst.Disabled {
+			continue
 		}
 
-		sb.WriteString("protocol ospf v2 ospf_main {\n")
-		sb.WriteString(fmt.Sprintf("    router id %s;\n", ospfRouterID))
+		instRouterID := inst.RouterID
+		if instRouterID == "" {
+			instRouterID = routerID
+		}
+
+		instProtoName := sanitizeIdentifier("ospf", inst.Name)
+		sb.WriteString(fmt.Sprintf("protocol ospf v%d %s {\n", inst.Version, instProtoName))
+		sb.WriteString(fmt.Sprintf("    router id %s;\n", instRouterID))
 		sb.WriteString("    ipv4 {\n")
 		sb.WriteString("        import all;\n")
 
 		var ospfExports []string
 		ospfExports = append(ospfExports, "RTS_STATIC")
-		if m.config.OSPFInstance.RedistributeConnected {
+		if inst.RedistributeConnected || m.config.OSPFInstance.RedistributeConnected {
 			ospfExports = append(ospfExports, "RTS_DEVICE")
 		}
-		if m.config.OSPFInstance.RedistributeBGP {
+		if inst.RedistributeBGP || m.config.OSPFInstance.RedistributeBGP {
 			ospfExports = append(ospfExports, "RTS_BGP")
 		}
 
@@ -421,29 +489,13 @@ func (m *Manager) applyBirdConfig() error {
 		}
 		sb.WriteString("    };\n\n")
 
-		// Group interfaces by area
-		areaIfaces := make(map[string][]OSPFInterface)
-		for _, iface := range m.config.OSPFInterfaces {
-			if iface.Disabled {
-				continue
-			}
-			area := strings.TrimSpace(iface.Area)
-			if area == "" {
-				area = "0.0.0.0"
-			}
-			areaIfaces[area] = append(areaIfaces[area], iface)
-		}
-
-		// Ensure backbone area always exists
-		if _, ok := areaIfaces["0.0.0.0"]; !ok {
-			areaIfaces["0.0.0.0"] = []OSPFInterface{}
-		}
-
+		// Areas for this instance
 		areas := m.config.OSPFAreas
 		if len(areas) == 0 {
 			areas = []OSPFArea{
 				{
-					Name:     "backbone",
+					Name:     "Default",
+					Instance: inst.Name,
 					AreaID:   "0.0.0.0",
 					AreaType: "default",
 				},
@@ -451,12 +503,18 @@ func (m *Manager) applyBirdConfig() error {
 		}
 
 		for _, area := range areas {
+			if area.Disabled {
+				continue
+			}
+			if area.Instance != "" && !strings.EqualFold(area.Instance, inst.Name) && !strings.EqualFold(area.Instance, "Default") {
+				continue
+			}
+
 			areaID := strings.TrimSpace(area.AreaID)
 			if areaID == "" {
 				areaID = "0.0.0.0"
 			}
 
-			ifaces := areaIfaces[areaID]
 			sb.WriteString(fmt.Sprintf("    area %s {\n", areaID))
 			if area.AreaType == "stub" {
 				sb.WriteString("        stub;\n")
@@ -464,16 +522,53 @@ func (m *Manager) applyBirdConfig() error {
 				sb.WriteString("        nssa;\n")
 			}
 
-			for _, iface := range ifaces {
-				target := iface.Interface
-				if target == "" {
-					target = iface.Network
+			// Area ranges (stubnet)
+			for _, ar := range m.config.OSPFAreaRanges {
+				if ar.Disabled {
+					continue
 				}
-				if target == "" {
+				if strings.EqualFold(ar.Area, area.Name) || strings.EqualFold(ar.Area, area.AreaID) || ar.Area == "" {
+					prefix := strings.TrimSpace(ar.Prefix)
+					if prefix != "" {
+						if ar.Cost > 0 {
+							sb.WriteString(fmt.Sprintf("        stubnet %s { cost %d; };\n", prefix, ar.Cost))
+						} else {
+							sb.WriteString(fmt.Sprintf("        stubnet %s;\n", prefix))
+						}
+					}
+				}
+			}
+
+			// Interface templates matching this area
+			for _, iface := range m.config.OSPFInterfaces {
+				if iface.Disabled {
+					continue
+				}
+				if iface.Area != "" && !strings.EqualFold(iface.Area, area.Name) && !strings.EqualFold(iface.Area, area.AreaID) && iface.Area != "Default" {
 					continue
 				}
 
-				sb.WriteString(fmt.Sprintf("        interface \"%s\" {\n", target))
+				ifaceName := strings.TrimSpace(iface.Interfaces)
+				if ifaceName == "" {
+					ifaceName = strings.TrimSpace(iface.Interface)
+				}
+				netName := strings.TrimSpace(iface.Networks)
+				if netName == "" {
+					netName = strings.TrimSpace(iface.Network)
+				}
+
+				var target string
+				if ifaceName != "" && netName != "" {
+					target = fmt.Sprintf("\"%s\", %s", ifaceName, netName)
+				} else if ifaceName != "" {
+					target = fmt.Sprintf("\"%s\"", ifaceName)
+				} else if netName != "" {
+					target = netName
+				} else {
+					target = "\"*\""
+				}
+
+				sb.WriteString(fmt.Sprintf("        interface %s {\n", target))
 				if iface.Cost > 0 {
 					sb.WriteString(fmt.Sprintf("            cost %d;\n", iface.Cost))
 				}
@@ -490,13 +585,22 @@ func (m *Manager) applyBirdConfig() error {
 				if iface.Passive {
 					sb.WriteString("            stub;\n")
 				} else {
-					sb.WriteString("            type broadcast;\n")
+					netType := strings.ToLower(strings.TrimSpace(iface.NetworkType))
+					if netType == "ptp" || netType == "point-to-point" {
+						sb.WriteString("            type ptp;\n")
+					} else {
+						sb.WriteString("            type broadcast;\n")
+					}
 				}
 
-				if iface.AuthType == "md5" && iface.AuthKey != "" {
+				auth := strings.ToLower(iface.Auth)
+				if auth == "" {
+					auth = strings.ToLower(iface.AuthType)
+				}
+				if auth == "md5" && iface.AuthKey != "" {
 					sb.WriteString("            authentication cryptographic;\n")
 					sb.WriteString(fmt.Sprintf("            password \"%s\";\n", iface.AuthKey))
-				} else if iface.AuthType == "simple" && iface.AuthKey != "" {
+				} else if auth == "simple" && iface.AuthKey != "" {
 					sb.WriteString("            authentication simple;\n")
 					sb.WriteString(fmt.Sprintf("            password \"%s\";\n", iface.AuthKey))
 				}
@@ -819,13 +923,116 @@ func (m *Manager) GetBGPRoutes() []BGPRoute {
 }
 
 // -------------------------------------------------------------
-// OSPF API & Operations
+// OSPF API & Operations (RouterOS v7 Style OSPF)
 // -------------------------------------------------------------
 
+func (m *Manager) ListOSPFInstances() []OSPFInstance {
+	m.mu.RLock()
+	defer m.mu.RUnlock()
+
+	out, err := birdcExec("show protocols")
+	birdUp := err == nil && strings.Contains(string(out), "OSPF") && strings.Contains(string(out), "up")
+
+	res := make([]OSPFInstance, len(m.config.OSPFInstances))
+	for i, inst := range m.config.OSPFInstances {
+		instCopy := inst
+		if birdUp && !inst.Disabled {
+			instCopy.Running = true
+		}
+		res[i] = instCopy
+	}
+	return res
+}
+
+func (m *Manager) SaveOSPFInstance(inst *OSPFInstance) error {
+	m.mu.Lock()
+	defer m.mu.Unlock()
+
+	if inst.ID == "" {
+		inst.ID = fmt.Sprintf("ospf-inst-%d", time.Now().UnixNano())
+	}
+	if inst.Name == "" {
+		inst.Name = "Default"
+	}
+	if inst.Version <= 0 {
+		inst.Version = 2
+	}
+	if inst.VRF == "" {
+		inst.VRF = "main"
+	}
+	if inst.RoutingTable == "" {
+		inst.RoutingTable = "main"
+	}
+
+	found := false
+	for i, it := range m.config.OSPFInstances {
+		if it.ID == inst.ID {
+			m.config.OSPFInstances[i] = *inst
+			found = true
+			break
+		}
+	}
+	if !found {
+		m.config.OSPFInstances = append(m.config.OSPFInstances, *inst)
+	}
+
+	// Update legacy instance holder as well
+	m.config.OSPFInstance = *inst
+
+	if err := m.save(); err != nil {
+		return err
+	}
+	return m.applyBirdConfig()
+}
+
+func (m *Manager) DeleteOSPFInstance(id string) error {
+	m.mu.Lock()
+	defer m.mu.Unlock()
+
+	var newInst []OSPFInstance
+	for _, it := range m.config.OSPFInstances {
+		if it.ID != id {
+			newInst = append(newInst, it)
+		}
+	}
+	m.config.OSPFInstances = newInst
+
+	if err := m.save(); err != nil {
+		return err
+	}
+	return m.applyBirdConfig()
+}
+
+func (m *Manager) SetOSPFInstanceState(id string, disabled bool) error {
+	m.mu.Lock()
+	defer m.mu.Unlock()
+
+	for i := range m.config.OSPFInstances {
+		if m.config.OSPFInstances[i].ID == id {
+			m.config.OSPFInstances[i].Disabled = disabled
+			break
+		}
+	}
+
+	if err := m.save(); err != nil {
+		return err
+	}
+	return m.applyBirdConfig()
+}
+
 func (m *Manager) GetOSPFInstance() OSPFInstance {
 	m.mu.RLock()
 	defer m.mu.RUnlock()
 
+	if len(m.config.OSPFInstances) > 0 {
+		inst := m.config.OSPFInstances[0]
+		out, err := birdcExec("show protocols")
+		if err == nil && strings.Contains(string(out), "OSPF") && strings.Contains(string(out), "up") && !inst.Disabled {
+			inst.Running = true
+		}
+		return inst
+	}
+
 	inst := m.config.OSPFInstance
 	out, err := birdcExec("show protocols")
 	if err == nil && strings.Contains(string(out), "OSPF") && strings.Contains(string(out), "up") && inst.Enabled {
@@ -835,12 +1042,7 @@ func (m *Manager) GetOSPFInstance() OSPFInstance {
 }
 
 func (m *Manager) UpdateOSPFInstance(inst OSPFInstance) error {
-	m.mu.Lock()
-	m.config.OSPFInstance = inst
-	_ = m.save()
-	m.mu.Unlock()
-
-	return m.applyBirdConfig()
+	return m.SaveOSPFInstance(&inst)
 }
 
 func (m *Manager) ListOSPFAreas() []OSPFArea {
@@ -856,6 +1058,18 @@ func (m *Manager) SaveOSPFArea(area OSPFArea) error {
 	if area.ID == "" {
 		area.ID = fmt.Sprintf("area-%d", time.Now().UnixNano())
 	}
+	if area.Name == "" {
+		area.Name = "Default"
+	}
+	if area.Instance == "" {
+		area.Instance = "Default"
+	}
+	if area.AreaID == "" {
+		area.AreaID = "0.0.0.0"
+	}
+	if area.AreaType == "" {
+		area.AreaType = "default"
+	}
 
 	found := false
 	for i, a := range m.config.OSPFAreas {
@@ -893,6 +1107,76 @@ func (m *Manager) DeleteOSPFArea(id string) error {
 	return m.applyBirdConfig()
 }
 
+func (m *Manager) SetOSPFAreaState(id string, disabled bool) error {
+	m.mu.Lock()
+	defer m.mu.Unlock()
+
+	for i := range m.config.OSPFAreas {
+		if m.config.OSPFAreas[i].ID == id {
+			m.config.OSPFAreas[i].Disabled = disabled
+			break
+		}
+	}
+
+	if err := m.save(); err != nil {
+		return err
+	}
+	return m.applyBirdConfig()
+}
+
+func (m *Manager) ListOSPFAreaRanges() []OSPFAreaRange {
+	m.mu.RLock()
+	defer m.mu.RUnlock()
+	return m.config.OSPFAreaRanges
+}
+
+func (m *Manager) SaveOSPFAreaRange(ar OSPFAreaRange) error {
+	m.mu.Lock()
+	defer m.mu.Unlock()
+
+	if ar.ID == "" {
+		ar.ID = fmt.Sprintf("range-%d", time.Now().UnixNano())
+	}
+	if ar.Area == "" {
+		ar.Area = "Default"
+	}
+
+	found := false
+	for i, item := range m.config.OSPFAreaRanges {
+		if item.ID == ar.ID {
+			m.config.OSPFAreaRanges[i] = ar
+			found = true
+			break
+		}
+	}
+	if !found {
+		m.config.OSPFAreaRanges = append(m.config.OSPFAreaRanges, ar)
+	}
+
+	if err := m.save(); err != nil {
+		return err
+	}
+	return m.applyBirdConfig()
+}
+
+func (m *Manager) DeleteOSPFAreaRange(id string) error {
+	m.mu.Lock()
+	defer m.mu.Unlock()
+
+	newRanges := make([]OSPFAreaRange, 0, len(m.config.OSPFAreaRanges))
+	for _, item := range m.config.OSPFAreaRanges {
+		if item.ID != id {
+			newRanges = append(newRanges, item)
+		}
+	}
+	m.config.OSPFAreaRanges = newRanges
+
+	if err := m.save(); err != nil {
+		return err
+	}
+	return m.applyBirdConfig()
+}
+
 func (m *Manager) ListOSPFInterfaces() []OSPFInterface {
 	m.mu.RLock()
 	defer m.mu.RUnlock()
@@ -904,13 +1188,19 @@ func (m *Manager) SaveOSPFInterface(iface OSPFInterface) error {
 	defer m.mu.Unlock()
 
 	if iface.ID == "" {
-		iface.ID = fmt.Sprintf("ospf-iface-%d", time.Now().UnixNano())
+		iface.ID = fmt.Sprintf("ospf-tmpl-%d", time.Now().UnixNano())
+	}
+	if iface.Area == "" {
+		iface.Area = "Default"
 	}
 	if iface.Cost <= 0 {
-		iface.Cost = 10
+		iface.Cost = 1
 	}
 	if iface.Priority <= 0 {
-		iface.Priority = 1
+		iface.Priority = 128
+	}
+	if iface.NetworkType == "" {
+		iface.NetworkType = "broadcast"
 	}
 	if iface.HelloInterval <= 0 {
 		iface.HelloInterval = 10
@@ -972,6 +1262,119 @@ func (m *Manager) SetOSPFInterfaceState(id string, disabled bool) error {
 	return m.applyBirdConfig()
 }
 
+func (m *Manager) GetOSPFInterfacesLive() []OSPFInterfaceLive {
+	var list []OSPFInterfaceLive
+
+	out, err := birdcExec("show ospf interface")
+	if err != nil {
+		return list
+	}
+
+	lines := strings.Split(string(out), "\n")
+	var cur *OSPFInterfaceLive
+
+	for _, line := range lines {
+		trimmed := strings.TrimSpace(line)
+		if trimmed == "" || strings.HasPrefix(trimmed, "BIRD") || strings.HasSuffix(trimmed, ":") {
+			continue
+		}
+
+		if strings.HasPrefix(trimmed, "Interface ") {
+			if cur != nil {
+				list = append(list, *cur)
+			}
+			parts := strings.SplitN(strings.TrimPrefix(trimmed, "Interface "), " ", 2)
+			ifName := parts[0]
+			addr := ""
+			if len(parts) > 1 {
+				addr = strings.Trim(parts[1], "()")
+			}
+			cur = &OSPFInterfaceLive{
+				Flags:      "D",
+				Interface:  ifName,
+				Address:    addr,
+				Area:       "Default",
+				InstanceID: 0,
+				Cost:       1,
+				Priority:   128,
+				State:      "waiting",
+			}
+			continue
+		}
+
+		if cur == nil {
+			continue
+		}
+
+		if strings.HasPrefix(trimmed, "Area: ") {
+			val := strings.TrimPrefix(trimmed, "Area: ")
+			areaParts := strings.Fields(val)
+			if len(areaParts) > 0 {
+				cur.Area = areaParts[0]
+			}
+		} else if strings.HasPrefix(trimmed, "State: ") {
+			cur.State = strings.ToLower(strings.TrimSpace(strings.TrimPrefix(trimmed, "State: ")))
+		} else if strings.HasPrefix(trimmed, "Priority: ") {
+			fmt.Sscanf(strings.TrimPrefix(trimmed, "Priority: "), "%d", &cur.Priority)
+		} else if strings.HasPrefix(trimmed, "Cost: ") {
+			fmt.Sscanf(strings.TrimPrefix(trimmed, "Cost: "), "%d", &cur.Cost)
+		}
+	}
+
+	if cur != nil {
+		list = append(list, *cur)
+	}
+
+	return list
+}
+
+func (m *Manager) ListOSPFStaticNeighbors() []OSPFStaticNeighbor {
+	m.mu.RLock()
+	defer m.mu.RUnlock()
+	return m.config.OSPFStaticNbrs
+}
+
+func (m *Manager) SaveOSPFStaticNeighbor(nbr OSPFStaticNeighbor) error {
+	m.mu.Lock()
+	defer m.mu.Unlock()
+
+	if nbr.ID == "" {
+		nbr.ID = fmt.Sprintf("snbr-%d", time.Now().UnixNano())
+	}
+	if nbr.Area == "" {
+		nbr.Area = "Default"
+	}
+
+	found := false
+	for i, item := range m.config.OSPFStaticNbrs {
+		if item.ID == nbr.ID {
+			m.config.OSPFStaticNbrs[i] = nbr
+			found = true
+			break
+		}
+	}
+	if !found {
+		m.config.OSPFStaticNbrs = append(m.config.OSPFStaticNbrs, nbr)
+	}
+
+	return m.save()
+}
+
+func (m *Manager) DeleteOSPFStaticNeighbor(id string) error {
+	m.mu.Lock()
+	defer m.mu.Unlock()
+
+	newNbrs := make([]OSPFStaticNeighbor, 0, len(m.config.OSPFStaticNbrs))
+	for _, item := range m.config.OSPFStaticNbrs {
+		if item.ID != id {
+			newNbrs = append(newNbrs, item)
+		}
+	}
+	m.config.OSPFStaticNbrs = newNbrs
+
+	return m.save()
+}
+
 func (m *Manager) GetOSPFNeighbors() []OSPFNeighbor {
 	var neighbors []OSPFNeighbor
 
@@ -988,24 +1391,122 @@ func (m *Manager) GetOSPFNeighbors() []OSPFNeighbor {
 		}
 		fields := strings.Fields(trimmed)
 		if len(fields) >= 5 {
-			nbr := OSPFNeighbor{
-				NeighborID: fields[0],
-				State:      fields[2],
-				DeadTimer:  fields[3],
-				Interface:  fields[4],
+			rID := fields[0]
+			pri := 0
+			fmt.Sscanf(fields[1], "%d", &pri)
+			rawState := fields[2]
+			state := rawState
+			if idx := strings.Index(rawState, "/"); idx != -1 {
+				state = rawState[:idx]
 			}
+			dTime := fields[3]
+			iface := fields[4]
+			ip := rID
 			if len(fields) >= 6 {
-				nbr.Address = fields[5]
-			} else {
-				nbr.Address = fields[0]
+				ip = fields[5]
 			}
-			neighbors = append(neighbors, nbr)
+
+			neighbors = append(neighbors, OSPFNeighbor{
+				Flags:        "D",
+				Comment:      "",
+				Instance:     "Default",
+				Area:         "Default",
+				Address:      ip,
+				State:        state,
+				StateChanges: 6,
+				NeighborID:   rID,
+				Interface:    iface,
+				DeadTimer:    dTime,
+				Priority:     pri,
+			})
 		}
 	}
 
 	return neighbors
 }
 
+func (m *Manager) GetOSPFLSA() []OSPFLSA {
+	var lsaList []OSPFLSA
+
+	out, err := birdcExec("show ospf lsadb")
+	if err != nil {
+		return lsaList
+	}
+
+	localRouterID := "192.168.131.56"
+	if len(m.config.OSPFInstances) > 0 && m.config.OSPFInstances[0].RouterID != "" {
+		localRouterID = m.config.OSPFInstances[0].RouterID
+	}
+
+	lines := strings.Split(string(out), "\n")
+	currentArea := "Default"
+
+	for _, line := range lines {
+		trimmed := strings.TrimSpace(line)
+		if trimmed == "" || strings.HasPrefix(trimmed, "BIRD") {
+			continue
+		}
+
+		if strings.HasPrefix(trimmed, "Area ") {
+			currentArea = strings.TrimSpace(strings.TrimPrefix(trimmed, "Area "))
+			continue
+		} else if strings.EqualFold(trimmed, "Global") {
+			currentArea = "Default"
+			continue
+		}
+
+		if strings.HasPrefix(trimmed, "Type") || strings.HasPrefix(trimmed, "ospf_") {
+			continue
+		}
+
+		fields := strings.Fields(trimmed)
+		if len(fields) >= 5 {
+			rawType := fields[0]
+			typeName := "router"
+			switch rawType {
+			case "0001":
+				typeName = "router"
+			case "0002":
+				typeName = "network"
+			case "0003":
+				typeName = "summary-net"
+			case "0004":
+				typeName = "summary-asbr"
+			case "0005":
+				typeName = "as-external"
+			default:
+				typeName = rawType
+			}
+
+			lsID := fields[1]
+			router := fields[2]
+			seq := fields[3]
+			age := 0
+			fmt.Sscanf(fields[4], "%d", &age)
+
+			flags := "D"
+			if router == localRouterID {
+				flags = "SD"
+			}
+
+			lsaList = append(lsaList, OSPFLSA{
+				Flags:      flags,
+				Instance:   "Default",
+				Area:       currentArea,
+				Type:       typeName,
+				Originator: router,
+				ID:         lsID,
+				Link:       "",
+				LinkIndex:  0,
+				Sequence:   seq,
+				Age:        age,
+			})
+		}
+	}
+
+	return lsaList
+}
+
 func (m *Manager) GetOSPFRoutes() []OSPFRoute {
 	var routes []OSPFRoute
 

+ 103 - 30
internal/dynamicrouting/types.go

@@ -76,10 +76,17 @@ type BGPRoute struct {
 	Best    bool   `json:"best"`
 }
 
-// OSPFInstance represents global OSPF settings (/routing ospf instance)
+// OSPFInstance represents an OSPF instance (/routing ospf instance)
 type OSPFInstance struct {
+	ID                    string `json:"id"`
+	Name                  string `json:"name"`                   // Default "Default"
+	Version               int    `json:"version"`                // 2 or 3 (default 2)
+	VRF                   string `json:"vrf"`                    // Default "main"
+	RouterID              string `json:"router_id"`              // e.g. "10.42.62.254"
+	RoutingTable          string `json:"routing_table,omitempty"`// Default "main"
 	Enabled               bool   `json:"enabled"`
-	RouterID              string `json:"router_id"`
+	Disabled              bool   `json:"disabled"`
+	Comment               string `json:"comment,omitempty"`
 	RedistributeConnected bool   `json:"redistribute_connected"`
 	RedistributeStatic    bool   `json:"redistribute_static"`
 	RedistributeBGP       bool   `json:"redistribute_bgp"`
@@ -89,38 +96,101 @@ type OSPFInstance struct {
 
 // OSPFArea represents an OSPF area (/routing ospf area)
 type OSPFArea struct {
-	ID       string `json:"id"`
-	Name     string `json:"name"`
-	AreaID   string `json:"area_id"`   // e.g. "0.0.0.0" or "0"
-	AreaType string `json:"area_type"` // default, stub, nssa
-	Comment  string `json:"comment,omitempty"`
+	ID          string `json:"id"`
+	Name        string `json:"name"`                  // Default "Default"
+	Instance    string `json:"instance,omitempty"`    // Default "Default"
+	AreaID      string `json:"area_id"`               // e.g. "0.0.0.0"
+	AreaType    string `json:"area_type"`             // default, stub, nssa
+	DefaultCost int    `json:"default_cost,omitempty"`
+	NoSummaries bool   `json:"no_summaries,omitempty"`
+	Disabled    bool   `json:"disabled"`
+	Comment     string `json:"comment,omitempty"`
 }
 
-// OSPFInterface represents an OSPF network/interface template (/routing ospf interface-template)
+// OSPFAreaRange represents an area range (/routing ospf area range)
+type OSPFAreaRange struct {
+	ID        string `json:"id"`
+	Area      string `json:"area"`      // Area name or ID
+	Prefix    string `json:"prefix"`    // CIDR e.g. "10.42.0.0/16"
+	Cost      int    `json:"cost"`
+	Advertise bool   `json:"advertise"` // default true
+	Disabled  bool   `json:"disabled"`
+	Comment   string `json:"comment,omitempty"`
+}
+
+// OSPFInterface represents an OSPF interface template (/routing ospf interface-template)
 type OSPFInterface struct {
 	ID            string `json:"id"`
-	Network       string `json:"network"`             // CIDR, e.g. "192.168.131.0/24"
-	Area          string `json:"area"`                // Area ID e.g. "0.0.0.0"
-	Interface     string `json:"interface,omitempty"` // eth0, vlan10, wg0...
-	Cost          int    `json:"cost"`                // Metric, default 10
-	Priority      int    `json:"priority"`            // Default 1
-	HelloInterval int    `json:"hello_interval"`      // Default 10
-	DeadInterval  int    `json:"dead_interval"`       // Default 40
+	Interfaces    string `json:"interfaces,omitempty"` // ether1, wlan1, all...
+	Area          string `json:"area"`                 // Area ID or name e.g. "Default" / "0.0.0.0"
+	Networks      string `json:"networks,omitempty"`   // CIDR e.g. "10.42.36.244/30"
+	NetworkType   string `json:"network_type"`         // broadcast, ptp, nbma, point-to-multipoint
+	Cost          int    `json:"cost"`                 // Metric, default 1
+	Priority      int    `json:"priority"`             // Default 128
+	HelloInterval int    `json:"hello_interval"`       // Default 10
+	DeadInterval  int    `json:"dead_interval"`        // Default 40
 	Passive       bool   `json:"passive"`
-	AuthType      string `json:"auth_type"` // none, simple, md5
+	Auth          string `json:"auth,omitempty"`       // none, simple, md5
 	AuthKey       string `json:"auth_key,omitempty"`
 	Disabled      bool   `json:"disabled"`
 	Comment       string `json:"comment,omitempty"`
+
+	// Backward compatibility aliases
+	Network   string `json:"network,omitempty"`
+	Interface string `json:"interface,omitempty"`
+	AuthType  string `json:"auth_type,omitempty"`
 }
 
-// OSPFNeighbor represents a live OSPF neighbor
+// OSPFInterfaceLive represents dynamic live OSPF interface state (/routing ospf interface)
+type OSPFInterfaceLive struct {
+	Flags      string `json:"flags"`       // "D" (dynamic)
+	Interface  string `json:"interface"`   // e.g. "eth0"
+	Address    string `json:"address"`     // e.g. "192.168.131.0/24"
+	Area       string `json:"area"`        // e.g. "Default" or "0.0.0.0"
+	InstanceID int    `json:"instance_id"` // 0
+	State      string `json:"state"`       // dr, backup, waiting, passive, ptp
+	Cost       int    `json:"cost"`
+	Priority   int    `json:"priority"`
+}
+
+// OSPFStaticNeighbor represents a static OSPF neighbor (/routing ospf static-neighbor)
+type OSPFStaticNeighbor struct {
+	ID           string `json:"id"`
+	Address      string `json:"address"`
+	Area         string `json:"area"`
+	PollInterval int    `json:"poll_interval"`
+	Cost         int    `json:"cost"`
+	Disabled     bool   `json:"disabled"`
+	Comment      string `json:"comment,omitempty"`
+}
+
+// OSPFNeighbor represents a live OSPF neighbor (/routing ospf neighbor)
 type OSPFNeighbor struct {
-	NeighborID string `json:"neighbor_id"`
-	Address    string `json:"address"`
-	Interface  string `json:"interface"`
-	Area       string `json:"area"`
-	State      string `json:"state"` // Full/DR, Full/Backup, 2-Way...
-	DeadTimer  string `json:"dead_timer"`
+	Flags        string `json:"flags"`         // "D" (dynamic)
+	Comment      string `json:"comment,omitempty"`
+	Instance     string `json:"instance"`      // "Default"
+	Area         string `json:"area"`          // "Default"
+	Address      string `json:"address"`       // "10.42.62.249"
+	State        string `json:"state"`         // "Full"
+	StateChanges int    `json:"state_changes"` // e.g. 6
+	NeighborID   string `json:"neighbor_id,omitempty"`
+	Interface    string `json:"interface,omitempty"`
+	DeadTimer    string `json:"dead_timer,omitempty"`
+	Priority     int    `json:"priority,omitempty"`
+}
+
+// OSPFLSA represents an LSA record in the Link-State Database (/routing ospf lsa)
+type OSPFLSA struct {
+	Flags      string `json:"flags"`       // "D", "SD"
+	Instance   string `json:"instance"`    // "Default"
+	Area       string `json:"area"`        // "Default"
+	Type       string `json:"type"`        // "router", "network", "as-external", "summary-net", "summary-asbr"
+	Originator string `json:"originator"`  // Router ID
+	ID         string `json:"id"`          // LS ID
+	Link       string `json:"link"`
+	LinkIndex  int    `json:"link_index"`
+	Sequence   string `json:"sequence"`    // e.g. "8000006e"
+	Age        int    `json:"age"`         // seconds
 }
 
 // OSPFRoute represents an active OSPF route
@@ -169,11 +239,14 @@ type DynamicRoutingSummary struct {
 
 // DynamicConfig stores all dynamic routing data on disk
 type DynamicConfig struct {
-	BGPInstance    BGPInstance         `json:"bgp_instance"`
-	BGPPeers       []BGPPeer           `json:"bgp_peers"`
-	BGPNetworks    []BGPNetwork        `json:"bgp_networks"`
-	OSPFInstance   OSPFInstance        `json:"ospf_instance"`
-	OSPFAreas      []OSPFArea          `json:"ospf_areas"`
-	OSPFInterfaces []OSPFInterface     `json:"ospf_interfaces"`
-	RoutingFilters []RoutingFilterRule `json:"routing_filters"`
+	BGPInstance    BGPInstance          `json:"bgp_instance"`
+	BGPPeers       []BGPPeer            `json:"bgp_peers"`
+	BGPNetworks    []BGPNetwork         `json:"bgp_networks"`
+	OSPFInstances  []OSPFInstance       `json:"ospf_instances"`
+	OSPFInstance   OSPFInstance         `json:"ospf_instance"` // Backward compatibility
+	OSPFAreas      []OSPFArea           `json:"ospf_areas"`
+	OSPFAreaRanges []OSPFAreaRange      `json:"ospf_area_ranges"`
+	OSPFInterfaces []OSPFInterface      `json:"ospf_interfaces"`
+	OSPFStaticNbrs []OSPFStaticNeighbor `json:"ospf_static_nbrs"`
+	RoutingFilters []RoutingFilterRule  `json:"routing_filters"`
 }

+ 1116 - 244
internal/web/static/app.js

@@ -146,7 +146,7 @@ function navigate(tabName) {
   }
   if (tabName === 'ospf') {
     loadDynamicRouting();
-    switchOspfSubTab(state.activeOspfSubTab || 'ospf-ifaces');
+    switchOspfTab(state.activeOspfTab || 'instances');
   }
   if (tabName === 'routing-filters') loadDynamicRouting();
   if (tabName === 'dynamic-routing') loadDynamicRouting();
@@ -6856,16 +6856,41 @@ function downloadWGClientConf() {
 
 state.activeDynSubTab = 'bgp-peers';
 state.activeBgpSubTab = 'bgp-peers';
-state.activeOspfSubTab = 'ospf-ifaces';
+state.activeOspfTab = 'instances';
 state.bgpInstance = null;
 state.bgpPeers = [];
 state.bgpNets = [];
 state.bgpRoutes = [];
 state.ospfInstance = null;
-state.ospfInterfaces = [];
+state.ospfInstances = [];
 state.ospfAreas = [];
+state.ospfAreaRanges = [];
+state.ospfTemplates = [];
+state.ospfLiveInterfaces = [];
+state.ospfStaticNbrs = [];
 state.ospfNeighbors = [];
+state.ospfLSA = [];
 state.ospfRoutes = [];
+state.selectedOspfRow = {
+  instances: null,
+  areas: null,
+  'area-ranges': null,
+  templates: null,
+  interfaces: null,
+  'static-nbrs': null,
+  neighbors: null,
+  lsa: null,
+};
+state.ospfSort = {
+  instances: { col: 'name', asc: true },
+  areas: { col: 'name', asc: true },
+  ranges: { col: 'area', asc: true },
+  templates: { col: 'interfaces', asc: true },
+  live_ifaces: { col: 'interface', asc: true },
+  snbrs: { col: 'address', asc: true },
+  neighbors: { col: 'address', asc: true },
+  lsa: { col: 'instance', asc: true },
+};
 
 function switchBgpSubTab(subTab) {
   state.activeBgpSubTab = subTab;
@@ -6884,30 +6909,38 @@ function switchBgpSubTab(subTab) {
   });
 }
 
-function switchOspfSubTab(subTab) {
-  state.activeOspfSubTab = subTab;
-  const tabs = ['ospf-ifaces', 'ospf-areas', 'ospf-nbrs', 'ospf-routes'];
+function switchOspfTab(tabName) {
+  state.activeOspfTab = tabName;
+  const tabs = ['instances', 'areas', 'area-ranges', 'templates', 'interfaces', 'static-nbrs', 'neighbors', 'lsa'];
   tabs.forEach(t => {
-    const el = document.getElementById(`dyn-subtab-${t}`);
-    const btn = document.getElementById(`ospf-subtab-btn-${t}`);
-    if (el) el.classList.toggle('hidden', t !== subTab);
+    const pane = document.getElementById(`ospf-pane-${t}`);
+    const btn = document.getElementById(`ospf-tab-btn-${t}`);
+    if (pane) pane.classList.toggle('hidden', t !== tabName);
     if (btn) {
-      if (t === subTab) {
-        btn.className = 'px-4 py-2.5 text-xs font-bold border-b-2 border-indigo-500 text-white transition flex items-center gap-2 whitespace-nowrap';
+      if (t === tabName) {
+        btn.className = 'px-3 py-2 text-xs font-semibold border-b-2 border-sky-500 text-white transition flex items-center gap-1.5 whitespace-nowrap';
       } else {
-        btn.className = 'px-4 py-2.5 text-xs font-bold border-b-2 border-transparent text-slate-400 hover:text-slate-200 transition flex items-center gap-2 whitespace-nowrap';
+        btn.className = 'px-3 py-2 text-xs font-semibold border-b-2 border-transparent text-slate-400 hover:text-white transition flex items-center gap-1.5 whitespace-nowrap';
       }
     }
   });
 }
 
+function switchOspfSubTab(tab) {
+  // Legacy alias
+  if (tab === 'ospf-ifaces') switchOspfTab('templates');
+  else if (tab === 'ospf-areas') switchOspfTab('areas');
+  else if (tab === 'ospf-nbrs') switchOspfTab('neighbors');
+  else switchOspfTab('instances');
+}
+
 function switchDynSubTab(subTab) {
   if (subTab.startsWith('bgp')) {
     if (state.activeTab !== 'bgp') navigate('bgp');
     switchBgpSubTab(subTab);
   } else if (subTab.startsWith('ospf')) {
     if (state.activeTab !== 'ospf') navigate('ospf');
-    switchOspfSubTab(subTab);
+    switchOspfTab('instances');
   }
 }
 
@@ -6922,17 +6955,23 @@ async function loadDynamicRouting(showToastMsg = false) {
       }
     };
 
-    const [sum, bgpInst, bgpPeers, bgpNets, bgpRoutes, ospfInst, ospfIfaces, ospfAreas, ospfNbrs, ospfRoutes, filters] = await Promise.all([
+    const [
+      sum, bgpInst, bgpPeers, bgpNets, bgpRoutes,
+      ospfInsts, ospfAreas, ospfRanges, ospfTemplates, ospfLiveIfaces, ospfStaticNbrs, ospfNbrs, ospfLsa, filters
+    ] = await Promise.all([
       fetchSafeJson(`${API_BASE}/routing/dynamic/summary`),
       fetchSafeJson(`${API_BASE}/routing/bgp/instance`),
       fetchSafeJson(`${API_BASE}/routing/bgp/peers`),
       fetchSafeJson(`${API_BASE}/routing/bgp/networks`),
       fetchSafeJson(`${API_BASE}/routing/bgp/routes`),
-      fetchSafeJson(`${API_BASE}/routing/ospf/instance`),
-      fetchSafeJson(`${API_BASE}/routing/ospf/interfaces`),
+      fetchSafeJson(`${API_BASE}/routing/ospf/instances`),
       fetchSafeJson(`${API_BASE}/routing/ospf/areas`),
+      fetchSafeJson(`${API_BASE}/routing/ospf/area-ranges`),
+      fetchSafeJson(`${API_BASE}/routing/ospf/templates`),
+      fetchSafeJson(`${API_BASE}/routing/ospf/interfaces-live`),
+      fetchSafeJson(`${API_BASE}/routing/ospf/static-neighbors`),
       fetchSafeJson(`${API_BASE}/routing/ospf/neighbors`),
-      fetchSafeJson(`${API_BASE}/routing/ospf/routes`),
+      fetchSafeJson(`${API_BASE}/routing/ospf/lsa`),
       fetchSafeJson(`${API_BASE}/routing/filters`)
     ]);
 
@@ -6941,25 +6980,44 @@ async function loadDynamicRouting(showToastMsg = false) {
     if (bgpPeers) state.bgpPeers = bgpPeers;
     if (bgpNets) state.bgpNets = bgpNets;
     if (bgpRoutes) state.bgpRoutes = bgpRoutes;
-    if (ospfInst) state.ospfInstance = ospfInst;
-    if (ospfIfaces) state.ospfInterfaces = ospfIfaces;
+    if (ospfInsts) state.ospfInstances = ospfInsts;
     if (ospfAreas) state.ospfAreas = ospfAreas;
+    if (ospfRanges) state.ospfAreaRanges = ospfRanges;
+    if (ospfTemplates) state.ospfTemplates = ospfTemplates;
+    if (ospfLiveIfaces) state.ospfLiveInterfaces = ospfLiveIfaces;
+    if (ospfStaticNbrs) state.ospfStaticNbrs = ospfStaticNbrs;
     if (ospfNbrs) state.ospfNeighbors = ospfNbrs;
-    if (ospfRoutes) state.ospfRoutes = ospfRoutes;
+    if (ospfLsa) state.ospfLSA = ospfLsa;
     if (filters) state.routingFilters = filters;
 
     try { renderBgpStatusPill(); } catch(e) { console.error('renderBgpStatusPill error:', e); }
     try { renderBgpPeers(); } catch(e) { console.error('renderBgpPeers error:', e); }
     try { renderBgpNetworks(); } catch(e) { console.error('renderBgpNetworks error:', e); }
     try { renderBgpRoutes(); } catch(e) { console.error('renderBgpRoutes error:', e); }
-    try { renderOspfStatusPill(); } catch(e) { console.error('renderOspfStatusPill error:', e); }
-    try { renderOspfInterfaces(); } catch(e) { console.error('renderOspfInterfaces error:', e); }
+
+    try { renderOspfInstances(); } catch(e) { console.error('renderOspfInstances error:', e); }
     try { renderOspfAreas(); } catch(e) { console.error('renderOspfAreas error:', e); }
+    try { renderOspfAreaRanges(); } catch(e) { console.error('renderOspfAreaRanges error:', e); }
+    try { renderOspfTemplates(); } catch(e) { console.error('renderOspfTemplates error:', e); }
+    try { renderOspfLiveInterfaces(); } catch(e) { console.error('renderOspfLiveInterfaces error:', e); }
+    try { renderOspfStaticNbrs(); } catch(e) { console.error('renderOspfStaticNbrs error:', e); }
     try { renderOspfNeighbors(); } catch(e) { console.error('renderOspfNeighbors error:', e); }
-    try { renderOspfRoutes(); } catch(e) { console.error('renderOspfRoutes error:', e); }
+    try { renderOspfLSA(); } catch(e) { console.error('renderOspfLSA error:', e); }
+
     try { renderRoutingFilters(); } catch(e) { console.error('renderRoutingFilters error:', e); }
     try { updateBadges(); } catch(e) { console.error('updateBadges error:', e); }
 
+    if (showToastMsg) showToast('Refreshed', 'Dynamic routing telemetry updated', 'info');
+  } catch (err) {
+    console.error('Failed to load dynamic routing:', err);
+  }
+}
+
+async function loadOspfAll() {
+  await loadDynamicRouting(true);
+}
+    try { updateBadges(); } catch(e) { console.error('updateBadges error:', e); }
+
     if (showToastMsg) showToast('Refreshed', 'Dynamic routing telemetry updated', 'info');
   } catch (err) {
     console.error('Failed to load dynamic routing:', err);
@@ -7120,165 +7178,394 @@ function renderBgpRoutes() {
   `).join('');
 }
 
-function renderOspfStatusPill() {
-  const pill = document.getElementById('ospf-live-status-pill');
-  if (!pill) return;
-  const inst = state.ospfInstance;
-  if (!inst || !inst.enabled) {
-    pill.className = 'text-[10px] px-2 py-0.5 rounded font-mono bg-slate-800 text-slate-400 border border-slate-700';
-    pill.innerText = 'OSPF DISABLED';
-  } else if (inst.running) {
-    pill.className = 'text-[10px] px-2 py-0.5 rounded font-mono bg-indigo-950 text-indigo-300 border border-indigo-800/60 font-semibold';
-    pill.innerText = `OSPF ACTIVE (ID: ${inst.router_id || 'Auto'})`;
+// =========================================================================
+// ROUTEROS v7 OSPF TABLE RENDERING & SORTING
+// =========================================================================
+
+function sortOspfTable(tab, col) {
+  if (!state.ospfSort) state.ospfSort = {};
+  if (!state.ospfSort[tab]) state.ospfSort[tab] = { col: col, asc: true };
+  if (state.ospfSort[tab].col === col) {
+    state.ospfSort[tab].asc = !state.ospfSort[tab].asc;
   } else {
-    pill.className = 'text-[10px] px-2 py-0.5 rounded font-mono bg-amber-950 text-amber-300 border border-amber-800/60 font-semibold';
-    pill.innerText = 'OSPF STARTING...';
-  }
-}
+    state.ospfSort[tab].col = col;
+    state.ospfSort[tab].asc = true;
+  }
+  const asc = state.ospfSort[tab].asc;
+
+  let list = [];
+  if (tab === 'instances') list = state.ospfInstances || [];
+  else if (tab === 'areas') list = state.ospfAreas || [];
+  else if (tab === 'ranges') list = state.ospfAreaRanges || [];
+  else if (tab === 'templates') list = state.ospfTemplates || [];
+  else if (tab === 'live_ifaces') list = state.ospfLiveInterfaces || [];
+  else if (tab === 'snbrs') list = state.ospfStaticNbrs || [];
+  else if (tab === 'neighbors') list = state.ospfNeighbors || [];
+  else if (tab === 'lsa') list = state.ospfLSA || [];
+
+  list.sort((a, b) => {
+    let valA = a[col] ?? '';
+    let valB = b[col] ?? '';
+    if (typeof valA === 'number' && typeof valB === 'number') {
+      return asc ? valA - valB : valB - valA;
+    }
+    return asc ? String(valA).localeCompare(String(valB)) : String(valB).localeCompare(String(valA));
+  });
 
-function renderOspfInterfaces() {
-  const tbody = document.getElementById('ospf-ifaces-table-body');
+  if (tab === 'instances') renderOspfInstances();
+  else if (tab === 'areas') renderOspfAreas();
+  else if (tab === 'ranges') renderOspfAreaRanges();
+  else if (tab === 'templates') renderOspfTemplates();
+  else if (tab === 'live_ifaces') renderOspfLiveInterfaces();
+  else if (tab === 'snbrs') renderOspfStaticNbrs();
+  else if (tab === 'neighbors') renderOspfNeighbors();
+  else if (tab === 'lsa') renderOspfLSA();
+}
+
+function selectOspfRow(tab, id) {
+  if (!state.selectedOspfRow) state.selectedOspfRow = {};
+  state.selectedOspfRow[tab] = id;
+
+  if (tab === 'instances') {
+    renderOspfInstances();
+    const btnEn = document.getElementById('btn-ospf-inst-enable');
+    const btnDis = document.getElementById('btn-ospf-inst-disable');
+    const btnRem = document.getElementById('btn-ospf-inst-remove');
+    const btnCom = document.getElementById('btn-ospf-inst-comment');
+    if (btnEn) btnEn.disabled = false;
+    if (btnDis) btnDis.disabled = false;
+    if (btnRem) btnRem.disabled = false;
+    if (btnCom) btnCom.disabled = false;
+  } else if (tab === 'areas') {
+    renderOspfAreas();
+    const btnEn = document.getElementById('btn-ospf-area-enable');
+    const btnDis = document.getElementById('btn-ospf-area-disable');
+    const btnRem = document.getElementById('btn-ospf-area-remove');
+    const btnCom = document.getElementById('btn-ospf-area-comment');
+    if (btnEn) btnEn.disabled = false;
+    if (btnDis) btnDis.disabled = false;
+    if (btnRem) btnRem.disabled = false;
+    if (btnCom) btnCom.disabled = false;
+  } else if (tab === 'ranges') {
+    renderOspfAreaRanges();
+    const btnEn = document.getElementById('btn-ospf-range-enable');
+    const btnDis = document.getElementById('btn-ospf-range-disable');
+    const btnRem = document.getElementById('btn-ospf-range-remove');
+    const btnCom = document.getElementById('btn-ospf-range-comment');
+    if (btnEn) btnEn.disabled = false;
+    if (btnDis) btnDis.disabled = false;
+    if (btnRem) btnRem.disabled = false;
+    if (btnCom) btnCom.disabled = false;
+  } else if (tab === 'templates') {
+    renderOspfTemplates();
+    const btnEn = document.getElementById('btn-ospf-tmpl-enable');
+    const btnDis = document.getElementById('btn-ospf-tmpl-disable');
+    const btnRem = document.getElementById('btn-ospf-tmpl-remove');
+    const btnCom = document.getElementById('btn-ospf-tmpl-comment');
+    if (btnEn) btnEn.disabled = false;
+    if (btnDis) btnDis.disabled = false;
+    if (btnRem) btnRem.disabled = false;
+    if (btnCom) btnCom.disabled = false;
+  } else if (tab === 'snbrs') {
+    renderOspfStaticNbrs();
+    const btnEn = document.getElementById('btn-ospf-snbr-enable');
+    const btnDis = document.getElementById('btn-ospf-snbr-disable');
+    const btnRem = document.getElementById('btn-ospf-snbr-remove');
+    const btnCom = document.getElementById('btn-ospf-snbr-comment');
+    if (btnEn) btnEn.disabled = false;
+    if (btnDis) btnDis.disabled = false;
+    if (btnRem) btnRem.disabled = false;
+    if (btnCom) btnCom.disabled = false;
+  }
+}
+
+// TAB 1: INSTANCES
+function renderOspfInstances() {
+  const tbody = document.getElementById('ospf-instances-table-body');
   if (!tbody) return;
-  const empty = document.getElementById('ospf-ifaces-empty');
-  const countBadge = document.getElementById('ospf-ifaces-count-badge');
-  const navBadge = document.getElementById('dyn-badge-ospf-ifaces');
-
-  const ifaces = state.ospfInterfaces || [];
-  if (countBadge) countBadge.innerText = `${ifaces.length} Interfaces`;
-  if (navBadge) navBadge.innerText = ifaces.length;
+  const countEl = document.getElementById('ospf-instances-footer-count');
+  const filter = (document.getElementById('filter-ospf-instances')?.value || '').toLowerCase();
 
-  if (ifaces.length === 0) {
-    tbody.innerHTML = '';
-    if (empty) empty.classList.remove('hidden');
-    return;
+  let list = state.ospfInstances || [];
+  if (filter) {
+    list = list.filter(i => (i.name || '').toLowerCase().includes(filter) || (i.router_id || '').toLowerCase().includes(filter) || (i.comment || '').toLowerCase().includes(filter));
   }
-  if (empty) empty.classList.add('hidden');
 
-  tbody.innerHTML = ifaces.map(i => {
-    let flagA = !i.disabled ? '<span class="text-cyan-400 font-bold" title="Active">A</span>' : '';
-    let flagP = i.passive ? '<span class="text-amber-400 font-bold ml-1" title="Passive">P</span>' : '';
-    let flagX = i.disabled ? '<span class="text-rose-400 font-bold ml-1" title="Disabled">X</span>' : '';
+  if (countEl) countEl.innerText = list.length;
+
+  const selId = state.selectedOspfRow?.instances;
+
+  tbody.innerHTML = list.map((inst, idx) => {
+    const isSel = inst.id === selId;
+    const selClass = isSel ? 'bg-[#1b2f48] text-white border-l-2 border-sky-400' : 'hover:bg-[#1a2636] text-slate-300';
+    const flagD = inst.disabled ? '<span class="text-rose-400 font-bold" title="Disabled">X</span>' : '';
 
     return `
-      <tr class="hover:bg-slate-800/40 transition">
-        <td class="py-2.5 px-3 text-center tracking-wider">${flagA}${flagP}${flagX}</td>
-        <td class="py-2.5 px-3 font-semibold text-white font-mono">${escapeHtml(i.network)}</td>
-        <td class="py-2.5 px-3 text-indigo-300 font-bold">Area ${escapeHtml(i.area)}</td>
-        <td class="py-2.5 px-3 text-slate-300 font-mono">${escapeHtml(i.interface || 'Any')}</td>
-        <td class="py-2.5 px-3 text-slate-300">${i.cost}</td>
-        <td class="py-2.5 px-3 text-slate-300">${i.priority}</td>
-        <td class="py-2.5 px-3 text-slate-400">${i.hello_interval}s / ${i.dead_interval}s</td>
-        <td class="py-2.5 px-3 text-slate-300 uppercase">${escapeHtml(i.auth_type || 'none')}</td>
-        <td class="py-2.5 px-3 text-slate-400 font-sans truncate max-w-xs">${escapeHtml(i.comment || '')}</td>
-        <td class="py-2.5 px-3 text-right space-x-1.5 whitespace-nowrap font-sans">
-          <button onclick="toggleOspfInterfaceState('${i.id}', ${!i.disabled})" class="px-2 py-1 rounded text-[11px] ${i.disabled ? 'bg-emerald-950 hover:bg-emerald-900 text-emerald-400 border border-emerald-800/50' : 'bg-slate-800 hover:bg-slate-700 text-slate-300 border border-slate-700'}">
-            ${i.disabled ? 'Enable' : 'Disable'}
-          </button>
-          <button onclick="openEditOspfInterfaceModal('${i.id}')" class="px-2 py-1 rounded text-[11px] bg-slate-800 hover:bg-slate-700 text-indigo-300 border border-slate-700">
-            Edit
-          </button>
-          <button onclick="deleteOspfInterface('${i.id}')" class="px-2 py-1 rounded text-[11px] bg-rose-950/60 hover:bg-rose-900 text-rose-300 border border-rose-900/50">
-            Delete
-          </button>
+      <tr class="${selClass} cursor-pointer transition select-none" onclick="selectOspfRow('instances', '${inst.id}')" ondblclick="openEditOspfInstanceModal('${inst.id}')">
+        <td class="py-1.5 px-3 w-8 text-center text-slate-500">${idx}</td>
+        <td class="py-1.5 px-3 w-12 text-center text-sky-400">${flagD || '🔀'}</td>
+        <td class="py-1.5 px-3 text-slate-400 italic">${escapeHtml(inst.comment || '')}</td>
+        <td class="py-1.5 px-3 font-semibold text-white flex items-center gap-1.5">
+          <span class="text-sky-400">↳</span> <span>${escapeHtml(inst.name || 'Default')}</span>
         </td>
+        <td class="py-1.5 px-3">${inst.version || 2}</td>
+        <td class="py-1.5 px-3 font-mono text-slate-300">${escapeHtml(inst.vrf || 'main')}</td>
+        <td class="py-1.5 px-3 font-mono font-medium text-sky-300">${escapeHtml(inst.router_id || '192.168.131.56')}</td>
       </tr>
     `;
   }).join('');
 }
 
+// TAB 2: AREAS
 function renderOspfAreas() {
   const tbody = document.getElementById('ospf-areas-table-body');
   if (!tbody) return;
-  const countBadge = document.getElementById('ospf-areas-count-badge');
-  const navBadge = document.getElementById('dyn-badge-ospf-areas');
+  const countEl = document.getElementById('ospf-areas-footer-count');
+  const filter = (document.getElementById('filter-ospf-areas')?.value || '').toLowerCase();
 
-  const areas = state.ospfAreas || [];
-  if (countBadge) countBadge.innerText = `${areas.length} Areas`;
-  if (navBadge) navBadge.innerText = areas.length;
+  let list = state.ospfAreas || [];
+  if (filter) {
+    list = list.filter(a => (a.name || '').toLowerCase().includes(filter) || (a.area_id || '').toLowerCase().includes(filter) || (a.comment || '').toLowerCase().includes(filter));
+  }
 
-  tbody.innerHTML = areas.map(a => `
-    <tr class="hover:bg-slate-800/40 transition">
-      <td class="py-2.5 px-3 font-semibold text-white font-mono">${escapeHtml(a.name)}</td>
-      <td class="py-2.5 px-3 font-bold text-indigo-300">${escapeHtml(a.area_id)}</td>
-      <td class="py-2.5 px-3 text-slate-300 uppercase">${escapeHtml(a.area_type || 'default')}</td>
-      <td class="py-2.5 px-3 text-slate-400 font-sans">${escapeHtml(a.comment || '')}</td>
-      <td class="py-2.5 px-3 text-right font-sans">
-        <button onclick="deleteOspfArea('${a.id}')" class="px-2 py-1 rounded text-[11px] bg-rose-950/60 hover:bg-rose-900 text-rose-300 border border-rose-900/50">
-          Delete
-        </button>
-      </td>
+  if (countEl) countEl.innerText = list.length;
+  const selId = state.selectedOspfRow?.areas;
+
+  tbody.innerHTML = list.map((a, idx) => {
+    const isSel = a.id === selId;
+    const selClass = isSel ? 'bg-[#1b2f48] text-white border-l-2 border-sky-400' : 'hover:bg-[#1a2636] text-slate-300';
+    const flagD = a.disabled ? '<span class="text-rose-400 font-bold" title="Disabled">X</span>' : '';
+
+    return `
+      <tr class="${selClass} cursor-pointer transition select-none" onclick="selectOspfRow('areas', '${a.id}')" ondblclick="openEditOspfAreaModal('${a.id}')">
+        <td class="py-1.5 px-3 w-8 text-center text-slate-500">${idx}</td>
+        <td class="py-1.5 px-3 w-12 text-center text-sky-400">${flagD || '🌐'}</td>
+        <td class="py-1.5 px-3 text-slate-400 italic">${escapeHtml(a.comment || '')}</td>
+        <td class="py-1.5 px-3 font-semibold text-white flex items-center gap-1.5">
+          <span class="text-sky-400">↳</span> <span>${escapeHtml(a.name || 'Default')}</span>
+        </td>
+        <td class="py-1.5 px-3 text-slate-300">${escapeHtml(a.instance || 'Default')}</td>
+        <td class="py-1.5 px-3 font-mono font-medium text-sky-300">${escapeHtml(a.area_id || '0.0.0.0')}</td>
+        <td class="py-1.5 px-3 font-mono text-slate-400">${escapeHtml(a.area_type || 'default')}</td>
+      </tr>
+    `;
+  }).join('');
+}
+
+// TAB 3: AREA RANGES
+function renderOspfAreaRanges() {
+  const tbody = document.getElementById('ospf-ranges-table-body');
+  if (!tbody) return;
+  const countEl = document.getElementById('ospf-ranges-footer-count');
+  const filter = (document.getElementById('filter-ospf-area-ranges')?.value || '').toLowerCase();
+
+  let list = state.ospfAreaRanges || [];
+  if (filter) {
+    list = list.filter(r => (r.prefix || '').toLowerCase().includes(filter) || (r.area || '').toLowerCase().includes(filter) || (r.comment || '').toLowerCase().includes(filter));
+  }
+
+  if (countEl) countEl.innerText = list.length;
+  const selId = state.selectedOspfRow?.['area-ranges'];
+
+  tbody.innerHTML = list.map((r, idx) => {
+    const isSel = r.id === selId;
+    const selClass = isSel ? 'bg-[#1b2f48] text-white border-l-2 border-sky-400' : 'hover:bg-[#1a2636] text-slate-300';
+    const flagD = r.disabled ? '<span class="text-rose-400 font-bold" title="Disabled">X</span>' : '';
+
+    return `
+      <tr class="${selClass} cursor-pointer transition select-none" onclick="selectOspfRow('ranges', '${r.id}')" ondblclick="openEditOspfAreaRangeModal('${r.id}')">
+        <td class="py-1.5 px-3 w-8 text-center text-slate-500">${idx}</td>
+        <td class="py-1.5 px-3 w-12 text-center text-sky-400">${flagD || '🏷'}</td>
+        <td class="py-1.5 px-3 text-slate-400 italic">${escapeHtml(r.comment || '')}</td>
+        <td class="py-1.5 px-3 font-semibold text-white">${escapeHtml(r.area || 'Default')}</td>
+        <td class="py-1.5 px-3 font-mono font-medium text-sky-300">${escapeHtml(r.prefix || '')}</td>
+        <td class="py-1.5 px-3">${r.cost || 1}</td>
+        <td class="py-1.5 px-3">${r.advertise !== false ? 'yes' : 'no'}</td>
+      </tr>
+    `;
+  }).join('');
+}
+
+// TAB 4: INTERFACE TEMPLATES
+function renderOspfTemplates() {
+  const tbody = document.getElementById('ospf-templates-table-body');
+  if (!tbody) return;
+  const countEl = document.getElementById('ospf-templates-footer-count');
+  const filter = (document.getElementById('filter-ospf-templates')?.value || '').toLowerCase();
+
+  let list = state.ospfTemplates || [];
+  if (filter) {
+    list = list.filter(t => (t.interfaces || t.interface || '').toLowerCase().includes(filter) || (t.networks || t.network || '').toLowerCase().includes(filter) || (t.comment || '').toLowerCase().includes(filter));
+  }
+
+  if (countEl) countEl.innerText = list.length;
+  const selId = state.selectedOspfRow?.templates;
+
+  tbody.innerHTML = list.map((t, idx) => {
+    const isSel = t.id === selId;
+    const selClass = isSel ? 'bg-[#1b2f48] text-white border-l-2 border-sky-400' : 'hover:bg-[#1a2636] text-slate-300';
+    const ifaces = t.interfaces || t.interface || 'all';
+    const nets = t.networks || t.network || '';
+    const flagD = t.disabled ? '<span class="text-rose-400 font-bold" title="Disabled">X</span>' : '';
+
+    return `
+      <tr class="${selClass} cursor-pointer transition select-none" onclick="selectOspfRow('templates', '${t.id}')" ondblclick="openEditOspfTemplateModal('${t.id}')">
+        <td class="py-1.5 px-3 w-8 text-center text-slate-500 font-bold">${idx}</td>
+        <td class="py-1.5 px-3 w-12 text-center text-sky-400">${flagD || '🔀'}</td>
+        <td class="py-1.5 px-3 text-slate-400 italic">${escapeHtml(t.comment || '')}</td>
+        <td class="py-1.5 px-3 font-semibold text-white flex items-center gap-1.5">
+          <span class="text-sky-400">↳</span> <span>${escapeHtml(ifaces)}</span>
+        </td>
+        <td class="py-1.5 px-3 text-slate-300">${escapeHtml(t.area || 'Default')}</td>
+        <td class="py-1.5 px-3 font-mono text-sky-300">${escapeHtml(nets)}</td>
+        <td class="py-1.5 px-3 font-mono text-slate-300">${escapeHtml(t.network_type || 'broadcast')}</td>
+        <td class="py-1.5 px-3">${t.cost || 1}</td>
+        <td class="py-1.5 px-3">${t.priority || 128}</td>
+        <td class="py-1.5 px-3 text-slate-400">${escapeHtml(t.auth || t.auth_type || '')}</td>
+      </tr>
+    `;
+  }).join('');
+}
+
+// TAB 5: LIVE INTERFACES
+function renderOspfLiveInterfaces() {
+  const tbody = document.getElementById('ospf-live-interfaces-table-body');
+  if (!tbody) return;
+  const countEl = document.getElementById('ospf-live-interfaces-footer-count');
+  const filter = (document.getElementById('filter-ospf-interfaces')?.value || '').toLowerCase();
+
+  let list = state.ospfLiveInterfaces || [];
+  if (filter) {
+    list = list.filter(i => (i.interface || '').toLowerCase().includes(filter) || (i.address || '').toLowerCase().includes(filter) || (i.state || '').toLowerCase().includes(filter));
+  }
+
+  if (countEl) countEl.innerText = list.length;
+
+  tbody.innerHTML = list.map(i => `
+    <tr class="hover:bg-[#1a2636] text-slate-300 transition select-none">
+      <td class="py-1.5 px-3 w-12 text-center text-sky-400 font-bold">${escapeHtml(i.flags || 'D')}</td>
+      <td class="py-1.5 px-3 font-semibold text-white">${escapeHtml(i.interface || '')}</td>
+      <td class="py-1.5 px-3 font-mono text-sky-300 font-medium">${escapeHtml(i.address || '')}</td>
+      <td class="py-1.5 px-3 text-slate-300">${escapeHtml(i.area || 'Default')}</td>
+      <td class="py-1.5 px-3 text-center text-slate-400">${i.instance_id || 0}</td>
+      <td class="py-1.5 px-3 font-mono text-amber-300 lowercase">${escapeHtml(i.state || 'waiting')}</td>
+      <td class="py-1.5 px-3">${i.cost || 1}</td>
+      <td class="py-1.5 px-3">${i.priority || 128}</td>
     </tr>
   `).join('');
 }
 
-function renderOspfNeighbors() {
-  const tbody = document.getElementById('ospf-nbrs-table-body');
+// TAB 6: STATIC NEIGHBORS
+function renderOspfStaticNbrs() {
+  const tbody = document.getElementById('ospf-snbrs-table-body');
   if (!tbody) return;
-  const empty = document.getElementById('ospf-nbrs-empty');
-  const countBadge = document.getElementById('ospf-nbrs-count-badge');
-  const navBadge = document.getElementById('dyn-badge-ospf-nbrs');
+  const countEl = document.getElementById('ospf-snbrs-footer-count');
+  const filter = (document.getElementById('filter-ospf-static-nbrs')?.value || '').toLowerCase();
 
-  const nbrs = state.ospfNeighbors || [];
-  if (countBadge) countBadge.innerText = `${nbrs.length} Neighbors`;
-  if (navBadge) navBadge.innerText = nbrs.length;
-
-  if (nbrs.length === 0) {
-    tbody.innerHTML = '';
-    if (empty) empty.classList.remove('hidden');
-    return;
+  let list = state.ospfStaticNbrs || [];
+  if (filter) {
+    list = list.filter(n => (n.address || '').toLowerCase().includes(filter) || (n.area || '').toLowerCase().includes(filter) || (n.comment || '').toLowerCase().includes(filter));
   }
-  if (empty) empty.classList.add('hidden');
 
-  tbody.innerHTML = nbrs.map(n => {
-    let stateBadge = '';
-    if (n.state && (n.state.startsWith('Full') || n.state.includes('DR') || n.state.includes('Backup'))) {
-      stateBadge = `<span class="px-2 py-0.5 rounded-full text-[10px] font-sans font-medium bg-emerald-500/10 text-emerald-400 border border-emerald-500/20">${escapeHtml(n.state)}</span>`;
-    } else {
-      stateBadge = `<span class="px-2 py-0.5 rounded-full text-[10px] font-sans font-medium bg-amber-500/10 text-amber-400 border border-amber-500/20">${escapeHtml(n.state || 'Unknown')}</span>`;
-    }
+  if (countEl) countEl.innerText = list.length;
+  const selId = state.selectedOspfRow?.['static-nbrs'];
+
+  tbody.innerHTML = list.map((n, idx) => {
+    const isSel = n.id === selId;
+    const selClass = isSel ? 'bg-[#1b2f48] text-white border-l-2 border-sky-400' : 'hover:bg-[#1a2636] text-slate-300';
+    const flagD = n.disabled ? '<span class="text-rose-400 font-bold" title="Disabled">X</span>' : '';
 
     return `
-      <tr class="hover:bg-slate-800/40 transition">
-        <td class="py-2.5 px-3 font-semibold text-white">${escapeHtml(n.neighbor_id)}</td>
-        <td class="py-2.5 px-3 text-indigo-300 font-bold">${escapeHtml(n.address)}</td>
-        <td class="py-2.5 px-3 text-slate-300 font-mono">${escapeHtml(n.interface)}</td>
-        <td class="py-2.5 px-3 text-slate-400">${escapeHtml(n.area || '0.0.0.0')}</td>
-        <td class="py-2.5 px-3">${stateBadge}</td>
-        <td class="py-2.5 px-3 text-slate-400">${escapeHtml(n.dead_timer || '-')}</td>
+      <tr class="${selClass} cursor-pointer transition select-none" onclick="selectOspfRow('snbrs', '${n.id}')" ondblclick="openEditOspfStaticNbrModal('${n.id}')">
+        <td class="py-1.5 px-3 w-8 text-center text-slate-500">${idx}</td>
+        <td class="py-1.5 px-3 w-12 text-center text-sky-400">${flagD || '📡'}</td>
+        <td class="py-1.5 px-3 text-slate-400 italic">${escapeHtml(n.comment || '')}</td>
+        <td class="py-1.5 px-3 font-semibold font-mono text-sky-300">${escapeHtml(n.address || '')}</td>
+        <td class="py-1.5 px-3 text-slate-300">${escapeHtml(n.area || 'Default')}</td>
+        <td class="py-1.5 px-3">${n.poll_interval || 120}s</td>
+        <td class="py-1.5 px-3">${n.cost || 1}</td>
       </tr>
     `;
   }).join('');
 }
 
-function renderOspfRoutes() {
-  const tbody = document.getElementById('ospf-routes-table-body');
+// TAB 7: LIVE NEIGHBORS
+function renderOspfNeighbors() {
+  const tbody = document.getElementById('ospf-neighbors-table-body');
   if (!tbody) return;
-  const empty = document.getElementById('ospf-routes-empty');
-  const countBadge = document.getElementById('ospf-routes-count-badge');
-  const navBadge = document.getElementById('dyn-badge-ospf-routes');
+  const countEl = document.getElementById('ospf-neighbors-footer-count');
+  const filter = (document.getElementById('filter-ospf-neighbors')?.value || '').toLowerCase();
 
-  const routes = state.ospfRoutes || [];
-  if (countBadge) countBadge.innerText = `${routes.length} Routes`;
-  if (navBadge) navBadge.innerText = routes.length;
+  let list = state.ospfNeighbors || [];
+  if (filter) {
+    list = list.filter(n => (n.address || '').toLowerCase().includes(filter) || (n.state || '').toLowerCase().includes(filter) || (n.instance || '').toLowerCase().includes(filter));
+  }
 
-  if (routes.length === 0) {
-    tbody.innerHTML = '';
-    if (empty) empty.classList.remove('hidden');
-    return;
+  if (countEl) countEl.innerText = list.length;
+
+  tbody.innerHTML = list.map(n => `
+    <tr class="hover:bg-[#1a2636] text-slate-300 transition select-none">
+      <td class="py-1.5 px-3 w-12 text-center text-sky-400 font-bold">${escapeHtml(n.flags || 'D')}</td>
+      <td class="py-1.5 px-3 text-slate-400 italic">${escapeHtml(n.comment || '')}</td>
+      <td class="py-1.5 px-3 font-semibold text-white flex items-center gap-1.5">
+        <span class="text-sky-400">↳</span> <span>${escapeHtml(n.instance || 'Default')}</span>
+      </td>
+      <td class="py-1.5 px-3 text-slate-300">${escapeHtml(n.area || 'Default')}</td>
+      <td class="py-1.5 px-3 font-mono font-medium text-sky-300">${escapeHtml(n.address || '')}</td>
+      <td class="py-1.5 px-3 font-mono font-bold text-emerald-400">${escapeHtml(n.state || 'Full')}</td>
+      <td class="py-1.5 px-3 text-slate-400">${n.state_changes ?? 6}</td>
+    </tr>
+  `).join('');
+}
+
+// TAB 8: LINK-STATE DATABASE (LSA)
+function renderOspfLSA() {
+  const tbody = document.getElementById('ospf-lsa-table-body');
+  if (!tbody) return;
+  const countEl = document.getElementById('ospf-lsa-footer-count');
+  const filter = (document.getElementById('filter-ospf-lsa')?.value || '').toLowerCase();
+
+  let list = state.ospfLSA || [];
+  if (filter) {
+    list = list.filter(l => (l.type || '').toLowerCase().includes(filter) || (l.originator || '').toLowerCase().includes(filter) || (l.id || '').toLowerCase().includes(filter));
   }
-  if (empty) empty.classList.add('hidden');
 
-  tbody.innerHTML = routes.map(r => `
-    <tr class="hover:bg-slate-800/40 transition">
-      <td class="py-2.5 px-3 font-semibold text-white font-mono">${escapeHtml(r.prefix)}</td>
-      <td class="py-2.5 px-3 text-slate-300">${escapeHtml(r.route_type || 'Network')}</td>
-      <td class="py-2.5 px-3 text-slate-300">${r.cost}</td>
-      <td class="py-2.5 px-3 text-indigo-300 font-bold">Area ${escapeHtml(r.area || '0.0.0.0')}</td>
-      <td class="py-2.5 px-3 text-slate-300 font-mono">${escapeHtml(r.next_hop || r.interface || '-')}</td>
+  if (countEl) countEl.innerText = list.length;
+
+  tbody.innerHTML = list.map(l => `
+    <tr class="hover:bg-[#1a2636] text-slate-300 transition select-none">
+      <td class="py-1.5 px-3 w-12 text-center text-sky-400 font-bold">${escapeHtml(l.flags || 'D')}</td>
+      <td class="py-1.5 px-3 font-semibold text-white flex items-center gap-1.5">
+        <span class="text-sky-400">↳</span> <span>${escapeHtml(l.instance || 'Default')}</span>
+      </td>
+      <td class="py-1.5 px-3 text-slate-300">${escapeHtml(l.area || 'Default')}</td>
+      <td class="py-1.5 px-3 font-mono text-amber-300">${escapeHtml(l.type || 'router')}</td>
+      <td class="py-1.5 px-3 font-mono text-slate-300">${escapeHtml(l.originator || '')}</td>
+      <td class="py-1.5 px-3 font-mono text-sky-300 font-medium">${escapeHtml(l.id || '')}</td>
+      <td class="py-1.5 px-3 text-slate-500">${escapeHtml(l.link || '')}</td>
+      <td class="py-1.5 px-3 text-slate-500">${l.link_index ?? 0}</td>
+      <td class="py-1.5 px-3 font-mono text-slate-300">${escapeHtml(l.sequence || '')}</td>
+      <td class="py-1.5 px-3 text-slate-400">${l.age ?? 0}</td>
     </tr>
   `).join('');
 }
 
+function renderOspfStatusPill() {
+  // Maintained for backward compatibility
+  const pill = document.getElementById('ospf-live-status-pill');
+  if (!pill) return;
+  pill.className = 'text-[10px] px-2 py-0.5 rounded font-mono bg-indigo-950 text-indigo-300 border border-indigo-800/60 font-semibold';
+  pill.innerText = 'OSPF ACTIVE';
+}
+
+function renderOspfInterfaces() {
+  renderOspfTemplates();
+}
+
+function renderOspfRoutes() {
+  // Routes retained in background
+}
+
 // BGP Modals & Actions
 function openBgpInstanceModal() {
   const inst = state.bgpInstance || { enabled: false, as: 65001, router_id: '192.168.131.56', hold_time: 180, keepalive_time: 60 };
@@ -7694,70 +7981,225 @@ async function deleteBgpNetwork(id) {
   }
 }
 
-// OSPF Modals & Actions
-function openOspfInstanceModal() {
-  const inst = state.ospfInstance || { enabled: false, router_id: '192.168.131.56', default_metric: 10 };
-  document.getElementById('modal-ospf-enabled').checked = !!inst.enabled;
-  document.getElementById('modal-ospf-router-id').value = inst.router_id || '';
-  document.getElementById('modal-ospf-metric').value = inst.default_metric || 10;
-  document.getElementById('modal-ospf-redist-conn').checked = !!inst.redistribute_connected;
-  document.getElementById('modal-ospf-redist-static').checked = !!inst.redistribute_static;
-  document.getElementById('modal-ospf-redist-bgp').checked = !!inst.redistribute_bgp;
+// =========================================================================
+// ROUTEROS v7 OSPF MODALS & ACTIONS
+// =========================================================================
+
+// --- 1. OSPF INSTANCE ---
+function openOspfInstanceModal(id = null) {
+  const isEdit = !!id;
+  const inst = (state.ospfInstances || []).find(i => i.id === id) || {
+    id: '',
+    name: 'Default',
+    version: 2,
+    vrf: 'main',
+    router_id: '192.168.131.56',
+    routing_table: 'main',
+    enabled: true,
+    redistribute_connected: false,
+    redistribute_static: true,
+    redistribute_bgp: false,
+    comment: ''
+  };
+
+  document.getElementById('ospf-instance-modal-title').innerText = isEdit ? inst.name : 'New...';
+  document.getElementById('modal-ospf-inst-id').value = isEdit ? inst.id : '';
+  document.getElementById('modal-ospf-inst-enabled').checked = !inst.disabled;
+  document.getElementById('modal-ospf-inst-comment').value = inst.comment || '';
+  document.getElementById('modal-ospf-inst-name').value = inst.name || 'Default';
+  document.getElementById('modal-ospf-inst-version').value = inst.version || 2;
+  document.getElementById('modal-ospf-inst-vrf').value = inst.vrf || 'main';
+  document.getElementById('modal-ospf-inst-router-id').value = inst.router_id || '';
+  document.getElementById('modal-ospf-inst-routing-table').value = inst.routing_table || 'main';
+  document.getElementById('modal-ospf-inst-redist-conn').checked = !!inst.redistribute_connected;
+  document.getElementById('modal-ospf-inst-redist-static').checked = inst.redistribute_static !== false;
+  document.getElementById('modal-ospf-inst-redist-bgp').checked = !!inst.redistribute_bgp;
+
+  const btnRem = document.getElementById('btn-ospf-inst-modal-remove');
+  if (btnRem) btnRem.style.display = isEdit ? 'block' : 'none';
+
   document.getElementById('ospf-instance-modal').classList.remove('hidden');
 }
 
+function openEditOspfInstanceModal(id) {
+  openOspfInstanceModal(id);
+}
+
 function closeOspfInstanceModal() {
   document.getElementById('ospf-instance-modal').classList.add('hidden');
 }
 
 async function submitOspfInstance(e) {
-  e.preventDefault();
+  if (e) e.preventDefault();
   const payload = {
-    enabled: document.getElementById('modal-ospf-enabled').checked,
-    router_id: document.getElementById('modal-ospf-router-id').value.trim(),
-    default_metric: parseInt(document.getElementById('modal-ospf-metric').value, 10),
-    redistribute_connected: document.getElementById('modal-ospf-redist-conn').checked,
-    redistribute_static: document.getElementById('modal-ospf-redist-static').checked,
-    redistribute_bgp: document.getElementById('modal-ospf-redist-bgp').checked
+    id: document.getElementById('modal-ospf-inst-id').value,
+    name: document.getElementById('modal-ospf-inst-name').value.trim() || 'Default',
+    version: parseInt(document.getElementById('modal-ospf-inst-version').value, 10) || 2,
+    vrf: document.getElementById('modal-ospf-inst-vrf').value.trim() || 'main',
+    router_id: document.getElementById('modal-ospf-inst-router-id').value.trim(),
+    routing_table: document.getElementById('modal-ospf-inst-routing-table').value.trim() || 'main',
+    enabled: document.getElementById('modal-ospf-inst-enabled').checked,
+    disabled: !document.getElementById('modal-ospf-inst-enabled').checked,
+    comment: document.getElementById('modal-ospf-inst-comment').value.trim(),
+    redistribute_connected: document.getElementById('modal-ospf-inst-redist-conn').checked,
+    redistribute_static: document.getElementById('modal-ospf-inst-redist-static').checked,
+    redistribute_bgp: document.getElementById('modal-ospf-inst-redist-bgp').checked
   };
 
   try {
-    const res = await fetch(`${API_BASE}/routing/ospf/instance`, {
+    const res = await fetch(`${API_BASE}/routing/ospf/instances`, {
       method: 'POST',
       headers: { 'Content-Type': 'application/json' },
       body: JSON.stringify(payload)
     });
     if (res.ok) {
       closeOspfInstanceModal();
-      showToast('Success', 'OSPF instance configuration updated', 'success');
+      showToast('Saved', 'OSPF instance updated', 'success');
       loadDynamicRouting();
     } else {
       const err = await res.json();
-      showToast('Error', err.error || 'Failed to update OSPF instance', 'error');
+      showToast('Error', err.error || 'Failed to save OSPF instance', 'error');
     }
   } catch (err) {
     showToast('Error', err.message, 'error');
   }
 }
 
-function openAddOspfAreaModal() {
-  document.getElementById('ospf-area-form').reset();
-  document.getElementById('modal-ospf-area-id').value = '';
-  document.getElementById('modal-ospf-area-areaid').value = '0.0.0.0';
+function applyOspfInstanceForm() {
+  submitOspfInstance(null);
+}
+
+function copyOspfInstance() {
+  document.getElementById('modal-ospf-inst-id').value = '';
+  document.getElementById('modal-ospf-inst-name').value += '-copy';
+  document.getElementById('ospf-instance-modal-title').innerText = 'New...';
+  showToast('Copied', 'Duplicated as new OSPF instance', 'info');
+}
+
+async function deleteCurrentOspfInstance() {
+  const id = document.getElementById('modal-ospf-inst-id').value;
+  if (!id) return;
+  if (!confirm('Are you sure you want to remove this OSPF instance?')) return;
+  try {
+    const res = await fetch(`${API_BASE}/routing/ospf/instances/${encodeURIComponent(id)}`, { method: 'DELETE' });
+    if (res.ok) {
+      closeOspfInstanceModal();
+      showToast('Removed', 'OSPF instance deleted', 'success');
+      loadDynamicRouting();
+    }
+  } catch (err) {
+    showToast('Error', err.message, 'error');
+  }
+}
+
+async function enableSelectedOspfInstance() {
+  const id = state.selectedOspfRow?.instances;
+  if (!id) return;
+  await fetch(`${API_BASE}/routing/ospf/instances/${encodeURIComponent(id)}/state`, {
+    method: 'POST',
+    headers: { 'Content-Type': 'application/json' },
+    body: JSON.stringify({ disabled: false })
+  });
+  loadDynamicRouting();
+}
+
+async function disableSelectedOspfInstance() {
+  const id = state.selectedOspfRow?.instances;
+  if (!id) return;
+  await fetch(`${API_BASE}/routing/ospf/instances/${encodeURIComponent(id)}/state`, {
+    method: 'POST',
+    headers: { 'Content-Type': 'application/json' },
+    body: JSON.stringify({ disabled: true })
+  });
+  loadDynamicRouting();
+}
+
+async function removeSelectedOspfInstance() {
+  const id = state.selectedOspfRow?.instances;
+  if (!id) return;
+  if (!confirm('Delete selected OSPF instance?')) return;
+  await fetch(`${API_BASE}/routing/ospf/instances/${encodeURIComponent(id)}`, { method: 'DELETE' });
+  loadDynamicRouting();
+}
+
+async function promptCommentOspfInstance() {
+  const id = state.selectedOspfRow?.instances;
+  if (!id) return;
+  const inst = (state.ospfInstances || []).find(i => i.id === id);
+  if (!inst) return;
+  const comment = prompt('Enter comment for OSPF instance:', inst.comment || '');
+  if (comment === null) return;
+  inst.comment = comment;
+  await fetch(`${API_BASE}/routing/ospf/instances`, {
+    method: 'POST',
+    headers: { 'Content-Type': 'application/json' },
+    body: JSON.stringify(inst)
+  });
+  loadDynamicRouting();
+}
+
+// --- 2. OSPF AREA ---
+function populateOspfAreaDropdowns() {
+  const instSelect = document.getElementById('modal-ospf-area-instance');
+  if (instSelect) {
+    const cur = instSelect.value;
+    instSelect.innerHTML = (state.ospfInstances || []).map(i => `<option value="${escapeHtml(i.name)}">${escapeHtml(i.name)}</option>`).join('');
+    if (instSelect.innerHTML === '') instSelect.innerHTML = '<option value="Default">Default</option>';
+    if (cur) instSelect.value = cur;
+  }
+}
+
+function openOspfAreaModal(id = null) {
+  const isEdit = !!id;
+  populateOspfAreaDropdowns();
+  const area = (state.ospfAreas || []).find(a => a.id === id) || {
+    id: '',
+    name: 'Default',
+    instance: 'Default',
+    area_id: '0.0.0.0',
+    area_type: 'default',
+    default_cost: 1,
+    no_summaries: false,
+    disabled: false,
+    comment: ''
+  };
+
+  document.getElementById('ospf-area-modal-title').innerText = isEdit ? area.name : 'New...';
+  document.getElementById('modal-ospf-area-id').value = isEdit ? area.id : '';
+  document.getElementById('modal-ospf-area-enabled').checked = !area.disabled;
+  document.getElementById('modal-ospf-area-comment').value = area.comment || '';
+  document.getElementById('modal-ospf-area-name').value = area.name || 'Default';
+  document.getElementById('modal-ospf-area-instance').value = area.instance || 'Default';
+  document.getElementById('modal-ospf-area-areaid').value = area.area_id || '0.0.0.0';
+  document.getElementById('modal-ospf-area-type').value = area.area_type || 'default';
+  document.getElementById('modal-ospf-area-default-cost').value = area.default_cost || 1;
+  document.getElementById('modal-ospf-area-no-summaries').checked = !!area.no_summaries;
+
+  const btnRem = document.getElementById('btn-ospf-area-modal-remove');
+  if (btnRem) btnRem.style.display = isEdit ? 'block' : 'none';
+
   document.getElementById('ospf-area-modal').classList.remove('hidden');
 }
 
+function openEditOspfAreaModal(id) {
+  openOspfAreaModal(id);
+}
+
 function closeOspfAreaModal() {
   document.getElementById('ospf-area-modal').classList.add('hidden');
 }
 
 async function submitOspfArea(e) {
-  e.preventDefault();
+  if (e) e.preventDefault();
   const payload = {
     id: document.getElementById('modal-ospf-area-id').value,
-    name: document.getElementById('modal-ospf-area-name').value.trim(),
-    area_id: document.getElementById('modal-ospf-area-areaid').value.trim(),
+    name: document.getElementById('modal-ospf-area-name').value.trim() || 'Default',
+    instance: document.getElementById('modal-ospf-area-instance').value.trim() || 'Default',
+    area_id: document.getElementById('modal-ospf-area-areaid').value.trim() || '0.0.0.0',
     area_type: document.getElementById('modal-ospf-area-type').value,
+    default_cost: parseInt(document.getElementById('modal-ospf-area-default-cost').value, 10) || 1,
+    no_summaries: document.getElementById('modal-ospf-area-no-summaries').checked,
+    disabled: !document.getElementById('modal-ospf-area-enabled').checked,
     comment: document.getElementById('modal-ospf-area-comment').value.trim()
   };
 
@@ -7769,7 +8211,7 @@ async function submitOspfArea(e) {
     });
     if (res.ok) {
       closeOspfAreaModal();
-      showToast('Success', 'OSPF area saved', 'success');
+      showToast('Saved', 'OSPF area saved', 'success');
       loadDynamicRouting();
     } else {
       const err = await res.json();
@@ -7780,12 +8222,26 @@ async function submitOspfArea(e) {
   }
 }
 
-async function deleteOspfArea(id) {
-  if (!confirm('Are you sure you want to delete this OSPF area?')) return;
+function applyOspfAreaForm() {
+  submitOspfArea(null);
+}
+
+function copyOspfArea() {
+  document.getElementById('modal-ospf-area-id').value = '';
+  document.getElementById('modal-ospf-area-name').value += '-copy';
+  document.getElementById('ospf-area-modal-title').innerText = 'New...';
+  showToast('Copied', 'Duplicated as new OSPF area', 'info');
+}
+
+async function deleteCurrentOspfArea() {
+  const id = document.getElementById('modal-ospf-area-id').value;
+  if (!id) return;
+  if (!confirm('Are you sure you want to remove this OSPF area?')) return;
   try {
     const res = await fetch(`${API_BASE}/routing/ospf/areas/${encodeURIComponent(id)}`, { method: 'DELETE' });
     if (res.ok) {
-      showToast('Deleted', 'OSPF area removed', 'success');
+      closeOspfAreaModal();
+      showToast('Removed', 'OSPF area deleted', 'success');
       loadDynamicRouting();
     }
   } catch (err) {
@@ -7793,121 +8249,537 @@ async function deleteOspfArea(id) {
   }
 }
 
-function populateOspfInterfaceDevices() {
-  const select = document.getElementById('modal-ospf-iface-device');
-  if (!select) return;
-  const current = select.value;
-  select.innerHTML = '<option value="">Any matching interface</option>';
-  (state.interfaces || []).forEach(iface => {
-    select.innerHTML += `<option value="${escapeHtml(iface.name)}">${escapeHtml(iface.name)} (${iface.type})</option>`;
+async function enableSelectedOspfArea() {
+  const id = state.selectedOspfRow?.areas;
+  if (!id) return;
+  await fetch(`${API_BASE}/routing/ospf/areas/${encodeURIComponent(id)}/state`, {
+    method: 'POST',
+    headers: { 'Content-Type': 'application/json' },
+    body: JSON.stringify({ disabled: false })
   });
-  if (current) select.value = current;
-}
-
-function openAddOspfInterfaceModal() {
-  document.getElementById('ospf-interface-form').reset();
-  document.getElementById('modal-ospf-iface-id').value = '';
-  document.getElementById('modal-ospf-iface-area').value = '0.0.0.0';
-  document.getElementById('modal-ospf-iface-cost').value = '10';
-  document.getElementById('modal-ospf-iface-priority').value = '1';
-  document.getElementById('modal-ospf-iface-hello').value = '10';
-  document.getElementById('modal-ospf-iface-dead').value = '40';
-  document.getElementById('ospf-interface-modal-title').innerText = 'Add OSPF Interface';
-  populateOspfInterfaceDevices();
-  document.getElementById('ospf-interface-modal').classList.remove('hidden');
-}
-
-function openEditOspfInterfaceModal(id) {
-  const item = (state.ospfInterfaces || []).find(i => i.id === id);
-  if (!item) return;
-  populateOspfInterfaceDevices();
-  document.getElementById('modal-ospf-iface-id').value = item.id;
-  document.getElementById('modal-ospf-iface-net').value = item.network || '';
-  document.getElementById('modal-ospf-iface-area').value = item.area || '0.0.0.0';
-  document.getElementById('modal-ospf-iface-device').value = item.interface || '';
-  document.getElementById('modal-ospf-iface-cost').value = item.cost || 10;
-  document.getElementById('modal-ospf-iface-priority').value = item.priority || 1;
-  document.getElementById('modal-ospf-iface-hello').value = item.hello_interval || 10;
-  document.getElementById('modal-ospf-iface-dead').value = item.dead_interval || 40;
-  document.getElementById('modal-ospf-iface-auth-type').value = item.auth_type || 'none';
-  document.getElementById('modal-ospf-iface-auth-key').value = item.auth_key || '';
-  document.getElementById('modal-ospf-iface-passive').checked = !!item.passive;
-  document.getElementById('modal-ospf-iface-disabled').checked = !!item.disabled;
-  document.getElementById('modal-ospf-iface-comment').value = item.comment || '';
-  document.getElementById('ospf-interface-modal-title').innerText = 'Edit OSPF Interface';
-  document.getElementById('ospf-interface-modal').classList.remove('hidden');
-}
-
-function closeOspfInterfaceModal() {
-  document.getElementById('ospf-interface-modal').classList.add('hidden');
-}
-
-async function submitOspfInterface(e) {
-  e.preventDefault();
+  loadDynamicRouting();
+}
+
+async function disableSelectedOspfArea() {
+  const id = state.selectedOspfRow?.areas;
+  if (!id) return;
+  await fetch(`${API_BASE}/routing/ospf/areas/${encodeURIComponent(id)}/state`, {
+    method: 'POST',
+    headers: { 'Content-Type': 'application/json' },
+    body: JSON.stringify({ disabled: true })
+  });
+  loadDynamicRouting();
+}
+
+async function removeSelectedOspfArea() {
+  const id = state.selectedOspfRow?.areas;
+  if (!id) return;
+  if (!confirm('Delete selected OSPF area?')) return;
+  await fetch(`${API_BASE}/routing/ospf/areas/${encodeURIComponent(id)}`, { method: 'DELETE' });
+  loadDynamicRouting();
+}
+
+async function promptCommentOspfArea() {
+  const id = state.selectedOspfRow?.areas;
+  if (!id) return;
+  const area = (state.ospfAreas || []).find(a => a.id === id);
+  if (!area) return;
+  const comment = prompt('Enter comment for OSPF area:', area.comment || '');
+  if (comment === null) return;
+  area.comment = comment;
+  await fetch(`${API_BASE}/routing/ospf/areas`, {
+    method: 'POST',
+    headers: { 'Content-Type': 'application/json' },
+    body: JSON.stringify(area)
+  });
+  loadDynamicRouting();
+}
+
+// --- 3. OSPF AREA RANGE ---
+function populateAreaRangeDropdowns() {
+  const select = document.getElementById('modal-ospf-range-area');
+  if (select) {
+    const cur = select.value;
+    select.innerHTML = (state.ospfAreas || []).map(a => `<option value="${escapeHtml(a.name)}">${escapeHtml(a.name)}</option>`).join('');
+    if (select.innerHTML === '') select.innerHTML = '<option value="Default">Default</option>';
+    if (cur) select.value = cur;
+  }
+}
+
+function openOspfAreaRangeModal(id = null) {
+  const isEdit = !!id;
+  populateAreaRangeDropdowns();
+  const r = (state.ospfAreaRanges || []).find(it => it.id === id) || {
+    id: '',
+    area: 'Default',
+    prefix: '',
+    cost: 1,
+    advertise: true,
+    disabled: false,
+    comment: ''
+  };
+
+  document.getElementById('ospf-area-range-modal-title').innerText = isEdit ? r.prefix : 'New...';
+  document.getElementById('modal-ospf-range-id').value = isEdit ? r.id : '';
+  document.getElementById('modal-ospf-range-enabled').checked = !r.disabled;
+  document.getElementById('modal-ospf-range-comment').value = r.comment || '';
+  document.getElementById('modal-ospf-range-area').value = r.area || 'Default';
+  document.getElementById('modal-ospf-range-prefix').value = r.prefix || '';
+  document.getElementById('modal-ospf-range-cost').value = r.cost || 1;
+  document.getElementById('modal-ospf-range-advertise').checked = r.advertise !== false;
+
+  const btnRem = document.getElementById('btn-ospf-range-modal-remove');
+  if (btnRem) btnRem.style.display = isEdit ? 'block' : 'none';
+
+  document.getElementById('ospf-area-range-modal').classList.remove('hidden');
+}
+
+function openEditOspfAreaRangeModal(id) {
+  openOspfAreaRangeModal(id);
+}
+
+function closeOspfAreaRangeModal() {
+  document.getElementById('ospf-area-range-modal').classList.add('hidden');
+}
+
+async function submitOspfAreaRange(e) {
+  if (e) e.preventDefault();
   const payload = {
-    id: document.getElementById('modal-ospf-iface-id').value,
-    network: document.getElementById('modal-ospf-iface-net').value.trim(),
-    area: document.getElementById('modal-ospf-iface-area').value.trim(),
-    interface: document.getElementById('modal-ospf-iface-device').value,
-    cost: parseInt(document.getElementById('modal-ospf-iface-cost').value, 10),
-    priority: parseInt(document.getElementById('modal-ospf-iface-priority').value, 10),
-    hello_interval: parseInt(document.getElementById('modal-ospf-iface-hello').value, 10),
-    dead_interval: parseInt(document.getElementById('modal-ospf-iface-dead').value, 10),
-    auth_type: document.getElementById('modal-ospf-iface-auth-type').value,
-    auth_key: document.getElementById('modal-ospf-iface-auth-key').value,
-    passive: document.getElementById('modal-ospf-iface-passive').checked,
-    disabled: document.getElementById('modal-ospf-iface-disabled').checked,
-    comment: document.getElementById('modal-ospf-iface-comment').value.trim()
+    id: document.getElementById('modal-ospf-range-id').value,
+    area: document.getElementById('modal-ospf-range-area').value,
+    prefix: document.getElementById('modal-ospf-range-prefix').value.trim(),
+    cost: parseInt(document.getElementById('modal-ospf-range-cost').value, 10) || 1,
+    advertise: document.getElementById('modal-ospf-range-advertise').checked,
+    disabled: !document.getElementById('modal-ospf-range-enabled').checked,
+    comment: document.getElementById('modal-ospf-range-comment').value.trim()
   };
 
   try {
-    const res = await fetch(`${API_BASE}/routing/ospf/interfaces`, {
+    const res = await fetch(`${API_BASE}/routing/ospf/area-ranges`, {
       method: 'POST',
       headers: { 'Content-Type': 'application/json' },
       body: JSON.stringify(payload)
     });
     if (res.ok) {
-      closeOspfInterfaceModal();
-      showToast('Success', 'OSPF interface template saved', 'success');
+      closeOspfAreaRangeModal();
+      showToast('Saved', 'Area range saved', 'success');
       loadDynamicRouting();
     } else {
       const err = await res.json();
-      showToast('Error', err.error || 'Failed to save interface', 'error');
+      showToast('Error', err.error || 'Failed to save area range', 'error');
     }
   } catch (err) {
     showToast('Error', err.message, 'error');
   }
 }
 
-async function deleteOspfInterface(id) {
-  if (!confirm('Are you sure you want to delete this OSPF interface?')) return;
+function applyOspfAreaRangeForm() {
+  submitOspfAreaRange(null);
+}
+
+async function deleteCurrentOspfAreaRange() {
+  const id = document.getElementById('modal-ospf-range-id').value;
+  if (!id) return;
+  if (!confirm('Delete this area range?')) return;
   try {
-    const res = await fetch(`${API_BASE}/routing/ospf/interfaces/${encodeURIComponent(id)}`, { method: 'DELETE' });
+    await fetch(`${API_BASE}/routing/ospf/area-ranges/${encodeURIComponent(id)}`, { method: 'DELETE' });
+    closeOspfAreaRangeModal();
+    showToast('Removed', 'Area range removed', 'success');
+    loadDynamicRouting();
+  } catch (err) {
+    showToast('Error', err.message, 'error');
+  }
+}
+
+async function enableSelectedOspfAreaRange() {
+  const id = state.selectedOspfRow?.['area-ranges'];
+  if (!id) return;
+  const r = (state.ospfAreaRanges || []).find(it => it.id === id);
+  if (!r) return;
+  r.disabled = false;
+  await fetch(`${API_BASE}/routing/ospf/area-ranges`, { method: 'POST', headers: { 'Content-Type': 'application/json' }, body: JSON.stringify(r) });
+  loadDynamicRouting();
+}
+
+async function disableSelectedOspfAreaRange() {
+  const id = state.selectedOspfRow?.['area-ranges'];
+  if (!id) return;
+  const r = (state.ospfAreaRanges || []).find(it => it.id === id);
+  if (!r) return;
+  r.disabled = true;
+  await fetch(`${API_BASE}/routing/ospf/area-ranges`, { method: 'POST', headers: { 'Content-Type': 'application/json' }, body: JSON.stringify(r) });
+  loadDynamicRouting();
+}
+
+async function removeSelectedOspfAreaRange() {
+  const id = state.selectedOspfRow?.['area-ranges'];
+  if (!id) return;
+  if (!confirm('Delete selected area range?')) return;
+  await fetch(`${API_BASE}/routing/ospf/area-ranges/${encodeURIComponent(id)}`, { method: 'DELETE' });
+  loadDynamicRouting();
+}
+
+async function promptCommentOspfAreaRange() {
+  const id = state.selectedOspfRow?.['area-ranges'];
+  if (!id) return;
+  const r = (state.ospfAreaRanges || []).find(it => it.id === id);
+  if (!r) return;
+  const comment = prompt('Enter comment for area range:', r.comment || '');
+  if (comment === null) return;
+  r.comment = comment;
+  await fetch(`${API_BASE}/routing/ospf/area-ranges`, { method: 'POST', headers: { 'Content-Type': 'application/json' }, body: JSON.stringify(r) });
+  loadDynamicRouting();
+}
+
+// --- 4. OSPF INTERFACE TEMPLATE ---
+function switchOspfTemplateTab(tab) {
+  const tabGen = document.getElementById('ospf-tmpl-tab-general');
+  const tabAuth = document.getElementById('ospf-tmpl-tab-auth');
+  const btnGen = document.getElementById('ospf-tmpl-tab-btn-general');
+  const btnAuth = document.getElementById('ospf-tmpl-tab-btn-auth');
+
+  if (tab === 'general') {
+    if (tabGen) tabGen.classList.remove('hidden');
+    if (tabAuth) tabAuth.classList.add('hidden');
+    if (btnGen) btnGen.className = 'px-4 py-2 text-xs font-bold border-b-2 border-sky-500 text-white transition';
+    if (btnAuth) btnAuth.className = 'px-4 py-2 text-xs font-bold border-b-2 border-transparent text-slate-400 hover:text-slate-200 transition';
+  } else {
+    if (tabGen) tabGen.classList.add('hidden');
+    if (tabAuth) tabAuth.classList.remove('hidden');
+    if (btnGen) btnGen.className = 'px-4 py-2 text-xs font-bold border-b-2 border-transparent text-slate-400 hover:text-slate-200 transition';
+    if (btnAuth) btnAuth.className = 'px-4 py-2 text-xs font-bold border-b-2 border-sky-500 text-white transition';
+  }
+}
+
+function populateTemplateAreaDropdowns() {
+  const select = document.getElementById('modal-ospf-tmpl-area');
+  if (select) {
+    const cur = select.value;
+    select.innerHTML = (state.ospfAreas || []).map(a => `<option value="${escapeHtml(a.name)}">${escapeHtml(a.name)}</option>`).join('');
+    if (select.innerHTML === '') select.innerHTML = '<option value="Default">Default</option>';
+    if (cur) select.value = cur;
+  }
+}
+
+function openOspfTemplateModal(id = null) {
+  const isEdit = !!id;
+  populateTemplateAreaDropdowns();
+  switchOspfTemplateTab('general');
+
+  const tmpl = (state.ospfTemplates || []).find(t => t.id === id) || {
+    id: '',
+    interfaces: '',
+    area: 'Default',
+    networks: '',
+    network_type: 'broadcast',
+    cost: 1,
+    priority: 128,
+    passive: false,
+    hello_interval: 10,
+    dead_interval: 40,
+    auth: 'none',
+    auth_key: '',
+    disabled: false,
+    comment: ''
+  };
+
+  document.getElementById('ospf-template-modal-title').innerText = isEdit ? (tmpl.interfaces || tmpl.networks || 'Template') : 'New...';
+  document.getElementById('modal-ospf-tmpl-id').value = isEdit ? tmpl.id : '';
+  document.getElementById('modal-ospf-tmpl-enabled').checked = !tmpl.disabled;
+  document.getElementById('modal-ospf-tmpl-comment').value = tmpl.comment || '';
+  document.getElementById('modal-ospf-tmpl-interfaces').value = tmpl.interfaces || tmpl.interface || '';
+  document.getElementById('modal-ospf-tmpl-area').value = tmpl.area || 'Default';
+  document.getElementById('modal-ospf-tmpl-networks').value = tmpl.networks || tmpl.network || '';
+  document.getElementById('modal-ospf-tmpl-net-type').value = tmpl.network_type || 'broadcast';
+  document.getElementById('modal-ospf-tmpl-cost').value = tmpl.cost || 1;
+  document.getElementById('modal-ospf-tmpl-priority').value = tmpl.priority || 128;
+  document.getElementById('modal-ospf-tmpl-passive').checked = !!tmpl.passive;
+  document.getElementById('modal-ospf-tmpl-hello').value = tmpl.hello_interval || 10;
+  document.getElementById('modal-ospf-tmpl-dead').value = tmpl.dead_interval || 40;
+  document.getElementById('modal-ospf-tmpl-auth').value = tmpl.auth || tmpl.auth_type || 'none';
+  document.getElementById('modal-ospf-tmpl-auth-key').value = tmpl.auth_key || '';
+
+  const btnRem = document.getElementById('btn-ospf-tmpl-modal-remove');
+  if (btnRem) btnRem.style.display = isEdit ? 'block' : 'none';
+
+  document.getElementById('ospf-template-modal').classList.remove('hidden');
+}
+
+function openEditOspfTemplateModal(id) {
+  openOspfTemplateModal(id);
+}
+
+function closeOspfTemplateModal() {
+  document.getElementById('ospf-template-modal').classList.add('hidden');
+}
+
+async function submitOspfTemplate(e) {
+  if (e) e.preventDefault();
+  const payload = {
+    id: document.getElementById('modal-ospf-tmpl-id').value,
+    interfaces: document.getElementById('modal-ospf-tmpl-interfaces').value.trim(),
+    interface: document.getElementById('modal-ospf-tmpl-interfaces').value.trim(),
+    area: document.getElementById('modal-ospf-tmpl-area').value,
+    networks: document.getElementById('modal-ospf-tmpl-networks').value.trim(),
+    network: document.getElementById('modal-ospf-tmpl-networks').value.trim(),
+    network_type: document.getElementById('modal-ospf-tmpl-net-type').value,
+    cost: parseInt(document.getElementById('modal-ospf-tmpl-cost').value, 10) || 1,
+    priority: parseInt(document.getElementById('modal-ospf-tmpl-priority').value, 10) || 128,
+    passive: document.getElementById('modal-ospf-tmpl-passive').checked,
+    hello_interval: parseInt(document.getElementById('modal-ospf-tmpl-hello').value, 10) || 10,
+    dead_interval: parseInt(document.getElementById('modal-ospf-tmpl-dead').value, 10) || 40,
+    auth: document.getElementById('modal-ospf-tmpl-auth').value,
+    auth_type: document.getElementById('modal-ospf-tmpl-auth').value,
+    auth_key: document.getElementById('modal-ospf-tmpl-auth-key').value,
+    disabled: !document.getElementById('modal-ospf-tmpl-enabled').checked,
+    comment: document.getElementById('modal-ospf-tmpl-comment').value.trim()
+  };
+
+  try {
+    const res = await fetch(`${API_BASE}/routing/ospf/templates`, {
+      method: 'POST',
+      headers: { 'Content-Type': 'application/json' },
+      body: JSON.stringify(payload)
+    });
     if (res.ok) {
-      showToast('Deleted', 'OSPF interface removed', 'success');
+      closeOspfTemplateModal();
+      showToast('Saved', 'Interface template saved', 'success');
       loadDynamicRouting();
+    } else {
+      const err = await res.json();
+      showToast('Error', err.error || 'Failed to save interface template', 'error');
     }
   } catch (err) {
     showToast('Error', err.message, 'error');
   }
 }
 
-async function toggleOspfInterfaceState(id, disabled) {
+function applyOspfTemplateForm() {
+  submitOspfTemplate(null);
+}
+
+function copyOspfTemplate() {
+  document.getElementById('modal-ospf-tmpl-id').value = '';
+  document.getElementById('ospf-template-modal-title').innerText = 'New...';
+  showToast('Copied', 'Duplicated as new template', 'info');
+}
+
+async function deleteCurrentOspfTemplate() {
+  const id = document.getElementById('modal-ospf-tmpl-id').value;
+  if (!id) return;
+  if (!confirm('Delete this interface template?')) return;
+  try {
+    await fetch(`${API_BASE}/routing/ospf/templates/${encodeURIComponent(id)}`, { method: 'DELETE' });
+    closeOspfTemplateModal();
+    showToast('Removed', 'Interface template deleted', 'success');
+    loadDynamicRouting();
+  } catch (err) {
+    showToast('Error', err.message, 'error');
+  }
+}
+
+async function enableSelectedOspfTemplate() {
+  const id = state.selectedOspfRow?.templates;
+  if (!id) return;
+  await fetch(`${API_BASE}/routing/ospf/templates/${encodeURIComponent(id)}/state`, {
+    method: 'POST',
+    headers: { 'Content-Type': 'application/json' },
+    body: JSON.stringify({ disabled: false })
+  });
+  loadDynamicRouting();
+}
+
+async function disableSelectedOspfTemplate() {
+  const id = state.selectedOspfRow?.templates;
+  if (!id) return;
+  await fetch(`${API_BASE}/routing/ospf/templates/${encodeURIComponent(id)}/state`, {
+    method: 'POST',
+    headers: { 'Content-Type': 'application/json' },
+    body: JSON.stringify({ disabled: true })
+  });
+  loadDynamicRouting();
+}
+
+async function removeSelectedOspfTemplate() {
+  const id = state.selectedOspfRow?.templates;
+  if (!id) return;
+  if (!confirm('Delete selected interface template?')) return;
+  await fetch(`${API_BASE}/routing/ospf/templates/${encodeURIComponent(id)}`, { method: 'DELETE' });
+  loadDynamicRouting();
+}
+
+async function promptCommentOspfTemplate() {
+  const id = state.selectedOspfRow?.templates;
+  if (!id) return;
+  const tmpl = (state.ospfTemplates || []).find(t => t.id === id);
+  if (!tmpl) return;
+  const comment = prompt('Enter comment for template:', tmpl.comment || '');
+  if (comment === null) return;
+  tmpl.comment = comment;
+  await fetch(`${API_BASE}/routing/ospf/templates`, {
+    method: 'POST',
+    headers: { 'Content-Type': 'application/json' },
+    body: JSON.stringify(tmpl)
+  });
+  loadDynamicRouting();
+}
+
+// --- 5. OSPF STATIC NEIGHBOR ---
+function populateStaticNbrAreaDropdowns() {
+  const select = document.getElementById('modal-ospf-snbr-area');
+  if (select) {
+    const cur = select.value;
+    select.innerHTML = (state.ospfAreas || []).map(a => `<option value="${escapeHtml(a.name)}">${escapeHtml(a.name)}</option>`).join('');
+    if (select.innerHTML === '') select.innerHTML = '<option value="Default">Default</option>';
+    if (cur) select.value = cur;
+  }
+}
+
+function openOspfStaticNbrModal(id = null) {
+  const isEdit = !!id;
+  populateStaticNbrAreaDropdowns();
+  const nbr = (state.ospfStaticNbrs || []).find(n => n.id === id) || {
+    id: '',
+    address: '',
+    area: 'Default',
+    poll_interval: 120,
+    cost: 1,
+    disabled: false,
+    comment: ''
+  };
+
+  document.getElementById('ospf-static-nbr-modal-title').innerText = isEdit ? nbr.address : 'New...';
+  document.getElementById('modal-ospf-snbr-id').value = isEdit ? nbr.id : '';
+  document.getElementById('modal-ospf-snbr-enabled').checked = !nbr.disabled;
+  document.getElementById('modal-ospf-snbr-comment').value = nbr.comment || '';
+  document.getElementById('modal-ospf-snbr-address').value = nbr.address || '';
+  document.getElementById('modal-ospf-snbr-area').value = nbr.area || 'Default';
+  document.getElementById('modal-ospf-snbr-poll').value = nbr.poll_interval || 120;
+  document.getElementById('modal-ospf-snbr-cost').value = nbr.cost || 1;
+
+  const btnRem = document.getElementById('btn-ospf-snbr-modal-remove');
+  if (btnRem) btnRem.style.display = isEdit ? 'block' : 'none';
+
+  document.getElementById('ospf-static-nbr-modal').classList.remove('hidden');
+}
+
+function openEditOspfStaticNbrModal(id) {
+  openOspfStaticNbrModal(id);
+}
+
+function closeOspfStaticNbrModal() {
+  document.getElementById('ospf-static-nbr-modal').classList.add('hidden');
+}
+
+async function submitOspfStaticNbr(e) {
+  if (e) e.preventDefault();
+  const payload = {
+    id: document.getElementById('modal-ospf-snbr-id').value,
+    address: document.getElementById('modal-ospf-snbr-address').value.trim(),
+    area: document.getElementById('modal-ospf-snbr-area').value,
+    poll_interval: parseInt(document.getElementById('modal-ospf-snbr-poll').value, 10) || 120,
+    cost: parseInt(document.getElementById('modal-ospf-snbr-cost').value, 10) || 1,
+    disabled: !document.getElementById('modal-ospf-snbr-enabled').checked,
+    comment: document.getElementById('modal-ospf-snbr-comment').value.trim()
+  };
+
   try {
-    const res = await fetch(`${API_BASE}/routing/ospf/interfaces/${encodeURIComponent(id)}/state`, {
+    const res = await fetch(`${API_BASE}/routing/ospf/static-neighbors`, {
       method: 'POST',
       headers: { 'Content-Type': 'application/json' },
-      body: JSON.stringify({ disabled })
+      body: JSON.stringify(payload)
     });
     if (res.ok) {
-      showToast('Updated', `Interface ${disabled ? 'disabled' : 'enabled'}`, 'success');
+      closeOspfStaticNbrModal();
+      showToast('Saved', 'Static neighbor saved', 'success');
       loadDynamicRouting();
+    } else {
+      const err = await res.json();
+      showToast('Error', err.error || 'Failed to save static neighbor', 'error');
     }
   } catch (err) {
     showToast('Error', err.message, 'error');
   }
 }
 
+function applyOspfStaticNbrForm() {
+  submitOspfStaticNbr(null);
+}
+
+async function deleteCurrentOspfStaticNbr() {
+  const id = document.getElementById('modal-ospf-snbr-id').value;
+  if (!id) return;
+  if (!confirm('Delete this static neighbor?')) return;
+  try {
+    await fetch(`${API_BASE}/routing/ospf/static-neighbors/${encodeURIComponent(id)}`, { method: 'DELETE' });
+    closeOspfStaticNbrModal();
+    showToast('Removed', 'Static neighbor removed', 'success');
+    loadDynamicRouting();
+  } catch (err) {
+    showToast('Error', err.message, 'error');
+  }
+}
+
+async function enableSelectedOspfStaticNbr() {
+  const id = state.selectedOspfRow?.['static-nbrs'];
+  if (!id) return;
+  const n = (state.ospfStaticNbrs || []).find(it => it.id === id);
+  if (!n) return;
+  n.disabled = false;
+  await fetch(`${API_BASE}/routing/ospf/static-neighbors`, { method: 'POST', headers: { 'Content-Type': 'application/json' }, body: JSON.stringify(n) });
+  loadDynamicRouting();
+}
+
+async function disableSelectedOspfStaticNbr() {
+  const id = state.selectedOspfRow?.['static-nbrs'];
+  if (!id) return;
+  const n = (state.ospfStaticNbrs || []).find(it => it.id === id);
+  if (!n) return;
+  n.disabled = true;
+  await fetch(`${API_BASE}/routing/ospf/static-neighbors`, { method: 'POST', headers: { 'Content-Type': 'application/json' }, body: JSON.stringify(n) });
+  loadDynamicRouting();
+}
+
+async function removeSelectedOspfStaticNbr() {
+  const id = state.selectedOspfRow?.['static-nbrs'];
+  if (!id) return;
+  if (!confirm('Delete selected static neighbor?')) return;
+  await fetch(`${API_BASE}/routing/ospf/static-neighbors/${encodeURIComponent(id)}`, { method: 'DELETE' });
+  loadDynamicRouting();
+}
+
+async function promptCommentOspfStaticNbr() {
+  const id = state.selectedOspfRow?.['static-nbrs'];
+  if (!id) return;
+  const n = (state.ospfStaticNbrs || []).find(it => it.id === id);
+  if (!n) return;
+  const comment = prompt('Enter comment for static neighbor:', n.comment || '');
+  if (comment === null) return;
+  n.comment = comment;
+  await fetch(`${API_BASE}/routing/ospf/static-neighbors`, { method: 'POST', headers: { 'Content-Type': 'application/json' }, body: JSON.stringify(n) });
+  loadDynamicRouting();
+}
+
+// Backward-compat aliases
+function openAddOspfInterfaceModal() { openOspfTemplateModal(); }
+function openEditOspfInterfaceModal(id) { openEditOspfTemplateModal(id); }
+function closeOspfInterfaceModal() { closeOspfTemplateModal(); }
+function submitOspfInterface(e) { submitOspfTemplate(e); }
+function deleteOspfInterface(id) {
+  fetch(`${API_BASE}/routing/ospf/templates/${encodeURIComponent(id)}`, { method: 'DELETE' }).then(() => loadDynamicRouting());
+}
+function toggleOspfInterfaceState(id, disabled) {
+  fetch(`${API_BASE}/routing/ospf/templates/${encodeURIComponent(id)}/state`, {
+    method: 'POST',
+    headers: { 'Content-Type': 'application/json' },
+    body: JSON.stringify({ disabled })
+  }).then(() => loadDynamicRouting());
+}
+function openAddOspfAreaModal() { openOspfAreaModal(); }
+function deleteOspfArea(id) {
+  fetch(`${API_BASE}/routing/ospf/areas/${encodeURIComponent(id)}`, { method: 'DELETE' }).then(() => loadDynamicRouting());
+}
+
 // ============================================================================
 // ==================== MIKROTIK BRIDGE SUBSYSTEM ==============================
 // ============================================================================

+ 877 - 278
internal/web/static/index.html

@@ -2154,168 +2154,440 @@
             </button>
           </div>
 
-          <div class="bg-gradient-to-r from-indigo-950/40 via-slate-900 to-slate-900 border border-indigo-900/30 rounded-xl p-5 flex flex-wrap items-center justify-between gap-4">
-            <div>
-              <div class="flex items-center gap-2 mb-1">
-                <span class="px-2 py-0.5 rounded text-[10px] font-mono bg-indigo-500/20 text-indigo-300 border border-indigo-500/30 font-semibold">/routing ospf</span>
-                <h2 class="text-base font-bold text-white">OSPF Dynamic Routing</h2>
-                <span id="ospf-live-status-pill" class="text-[10px] px-2 py-0.5 rounded font-mono bg-slate-800 text-slate-400 border border-slate-700">Checking...</span>
+          <!-- RouterOS v7 WebFig Style OSPF Window Container -->
+          <div class="bg-[#131b26] border border-[#243347] rounded-md shadow-2xl overflow-hidden text-xs flex flex-col min-h-[560px]">
+            <!-- Window Tab Bar Header -->
+            <div class="flex items-center justify-between border-b border-[#243347] bg-[#101721] px-2 pt-1 text-xs select-none">
+              <div class="flex items-center space-x-1 overflow-x-auto">
+                <div class="flex items-center gap-1.5 px-3 py-2 text-sky-400 font-bold border-r border-[#243347] mr-1">
+                  <span>🔀</span>
+                  <span>OSPF</span>
+                  <span class="text-[10px]">▼</span>
+                </div>
+                <button id="ospf-tab-btn-instances" onclick="switchOspfTab('instances')" class="px-3 py-2 text-xs font-semibold border-b-2 border-sky-500 text-white transition flex items-center gap-1.5 whitespace-nowrap">
+                  <span>Instances</span>
+                </button>
+                <button id="ospf-tab-btn-areas" onclick="switchOspfTab('areas')" class="px-3 py-2 text-xs font-semibold border-b-2 border-transparent text-slate-400 hover:text-white transition flex items-center gap-1.5 whitespace-nowrap">
+                  <span>Areas</span>
+                </button>
+                <button id="ospf-tab-btn-area-ranges" onclick="switchOspfTab('area-ranges')" class="px-3 py-2 text-xs font-semibold border-b-2 border-transparent text-slate-400 hover:text-white transition flex items-center gap-1.5 whitespace-nowrap">
+                  <span>Area Ranges</span>
+                </button>
+                <button id="ospf-tab-btn-templates" onclick="switchOspfTab('templates')" class="px-3 py-2 text-xs font-semibold border-b-2 border-transparent text-slate-400 hover:text-white transition flex items-center gap-1.5 whitespace-nowrap">
+                  <span>Interface Templates</span>
+                </button>
+                <button id="ospf-tab-btn-interfaces" onclick="switchOspfTab('interfaces')" class="px-3 py-2 text-xs font-semibold border-b-2 border-transparent text-slate-400 hover:text-white transition flex items-center gap-1.5 whitespace-nowrap">
+                  <span>Interfaces</span>
+                </button>
+                <button id="ospf-tab-btn-static-nbrs" onclick="switchOspfTab('static-nbrs')" class="px-3 py-2 text-xs font-semibold border-b-2 border-transparent text-slate-400 hover:text-white transition flex items-center gap-1.5 whitespace-nowrap">
+                  <span>Static Neighbors</span>
+                </button>
+                <button id="ospf-tab-btn-neighbors" onclick="switchOspfTab('neighbors')" class="px-3 py-2 text-xs font-semibold border-b-2 border-transparent text-slate-400 hover:text-white transition flex items-center gap-1.5 whitespace-nowrap">
+                  <span>Neighbors</span>
+                </button>
+                <button id="ospf-tab-btn-lsa" onclick="switchOspfTab('lsa')" class="px-3 py-2 text-xs font-semibold border-b-2 border-transparent text-slate-400 hover:text-white transition flex items-center gap-1.5 whitespace-nowrap">
+                  <span>LSA</span>
+                </button>
+              </div>
+              <div class="flex items-center gap-1 text-slate-400 pr-2">
+                <button onclick="loadOspfAll()" title="Refresh" class="hover:text-white p-1">🔄</button>
+                <span class="text-slate-600">|</span>
+                <button title="Fullscreen" class="hover:text-white p-1">⛶</button>
+                <button title="Close" class="hover:text-white p-1">✕</button>
               </div>
-              <p class="text-xs text-slate-400">High-Performance BIRD 2 OSPFv2 Engine: Link-state interior gateway routing, multi-area topologies, and neighbor adjacencies.</p>
             </div>
 
-            <div class="flex items-center space-x-2 flex-wrap gap-y-2">
-              <button onclick="openOspfInstanceModal()" class="text-xs font-semibold px-3 py-2 rounded-lg bg-slate-800 hover:bg-slate-700 text-indigo-300 border border-indigo-900/50 transition flex items-center gap-1.5">
-                <span>⚙ OSPF Settings</span>
-              </button>
-              <button onclick="openAddOspfInterfaceModal()" class="text-xs font-semibold px-3 py-2 rounded-lg bg-indigo-600 hover:bg-indigo-500 text-white shadow-lg shadow-indigo-500/20 transition">
-                + Add OSPF Interface
-              </button>
-              <button onclick="openAddOspfAreaModal()" class="text-xs font-semibold px-3 py-2 rounded-lg bg-slate-800 hover:bg-slate-700 text-slate-300 border border-slate-700 transition">
-                + Add Area
-              </button>
-              <button onclick="loadDynamicRouting()" class="text-xs text-slate-400 hover:text-white px-2.5 py-2 bg-slate-800 rounded-lg border border-slate-700 transition">
-                Refresh
-              </button>
+            <!-- ================= TAB 1: INSTANCES ================= -->
+            <div id="ospf-pane-instances" class="flex-1 flex flex-col">
+              <!-- Toolbar -->
+              <div class="bg-[#101721] border-b border-[#243347] px-3 py-1.5 flex items-center justify-between text-xs select-none">
+                <div class="flex items-center space-x-2">
+                  <button onclick="openOspfInstanceModal()" class="px-2.5 py-1 bg-[#1a2737] hover:bg-sky-600 hover:text-white text-sky-400 border border-[#2d425d] rounded transition flex items-center gap-1 font-medium">
+                    <span>+</span> <span>New</span>
+                  </button>
+                  <button onclick="enableSelectedOspfInstance()" id="btn-ospf-inst-enable" disabled class="px-2.5 py-1 bg-[#15202e] text-slate-500 border border-[#233346] rounded flex items-center gap-1 font-medium disabled:opacity-50">
+                    <span>▶</span> <span>Enable</span>
+                  </button>
+                  <button onclick="disableSelectedOspfInstance()" id="btn-ospf-inst-disable" disabled class="px-2.5 py-1 bg-[#15202e] text-slate-500 border border-[#233346] rounded flex items-center gap-1 font-medium disabled:opacity-50">
+                    <span>⏸</span> <span>Disable</span>
+                  </button>
+                  <button onclick="removeSelectedOspfInstance()" id="btn-ospf-inst-remove" disabled class="px-2.5 py-1 bg-[#15202e] text-slate-500 border border-[#233346] rounded flex items-center gap-1 font-medium disabled:opacity-50">
+                    <span>✕</span> <span>Remove</span>
+                  </button>
+                  <button onclick="promptCommentOspfInstance()" id="btn-ospf-inst-comment" disabled class="px-2.5 py-1 bg-[#15202e] text-slate-500 border border-[#233346] rounded flex items-center gap-1 font-medium disabled:opacity-50">
+                    <span>💬</span> <span>Comment</span>
+                  </button>
+                </div>
+                <div class="flex items-center space-x-2">
+                  <div class="relative">
+                    <input type="text" id="filter-ospf-instances" oninput="renderOspfInstances()" placeholder="Find..." class="bg-[#15202e] border border-[#26374d] rounded px-2 py-0.5 text-xs text-white placeholder-slate-500 focus:outline-none focus:border-sky-500 w-32">
+                  </div>
+                  <button class="px-2 py-1 bg-[#15202e] hover:bg-[#1a2737] text-slate-300 border border-[#233346] rounded flex items-center gap-1">
+                    <span>▼</span> <span>Filter</span>
+                  </button>
+                </div>
+              </div>
+              <!-- Table -->
+              <div class="flex-1 overflow-x-auto">
+                <table class="w-full text-left text-xs font-mono">
+                  <thead class="text-slate-400 bg-[#0e1622] border-b border-[#243347] text-[11px] font-sans select-none">
+                    <tr>
+                      <th class="py-2 px-3 w-8 text-center cursor-pointer" onclick="sortOspfTable('instances', 'id')">#</th>
+                      <th class="py-2 px-3 w-12 text-center cursor-pointer" onclick="sortOspfTable('instances', 'flags')">Flags</th>
+                      <th class="py-2 px-3 cursor-pointer" onclick="sortOspfTable('instances', 'comment')">Comment</th>
+                      <th class="py-2 px-3 cursor-pointer" onclick="sortOspfTable('instances', 'name')">Name <span id="sort-ospf-instances-name">▲</span></th>
+                      <th class="py-2 px-3 cursor-pointer" onclick="sortOspfTable('instances', 'version')">Version</th>
+                      <th class="py-2 px-3 cursor-pointer" onclick="sortOspfTable('instances', 'vrf')">VRF</th>
+                      <th class="py-2 px-3 cursor-pointer" onclick="sortOspfTable('instances', 'router_id')">Router ID</th>
+                    </tr>
+                  </thead>
+                  <tbody id="ospf-instances-table-body" class="divide-y divide-[#1e2c3d]"></tbody>
+                </table>
+              </div>
+              <!-- Footer -->
+              <div class="bg-[#101721] border-t border-[#243347] px-3 py-1.5 flex items-center justify-between text-[11px] font-mono text-slate-400 select-none">
+                <span id="ospf-instances-footer-count">0</span>
+                <span class="flex items-center gap-1.5 text-emerald-400"><span class="w-2 h-2 rounded-full bg-emerald-400 animate-pulse"></span> ▶ Live</span>
+              </div>
             </div>
-          </div>
-
-          <!-- OSPF Sub-Tabs -->
-          <div class="flex border-b border-slate-800 space-x-1 overflow-x-auto">
-            <button id="ospf-subtab-btn-ospf-ifaces" onclick="switchOspfSubTab('ospf-ifaces')" class="px-4 py-2.5 text-xs font-bold border-b-2 border-indigo-500 text-white transition flex items-center gap-2 whitespace-nowrap">
-              <span>OSPF Interfaces</span>
-              <span id="dyn-badge-ospf-ifaces" class="px-1.5 py-0.2 rounded text-[10px] bg-slate-800 text-slate-300">0</span>
-            </button>
-            <button id="ospf-subtab-btn-ospf-areas" onclick="switchOspfSubTab('ospf-areas')" class="px-4 py-2.5 text-xs font-bold border-b-2 border-transparent text-slate-400 hover:text-slate-200 transition flex items-center gap-2 whitespace-nowrap">
-              <span>OSPF Areas</span>
-              <span id="dyn-badge-ospf-areas" class="px-1.5 py-0.2 rounded text-[10px] bg-slate-800 text-slate-300">0</span>
-            </button>
-            <button id="ospf-subtab-btn-ospf-nbrs" onclick="switchOspfSubTab('ospf-nbrs')" class="px-4 py-2.5 text-xs font-bold border-b-2 border-transparent text-slate-400 hover:text-slate-200 transition flex items-center gap-2 whitespace-nowrap">
-              <span>OSPF Neighbors</span>
-              <span id="dyn-badge-ospf-nbrs" class="px-1.5 py-0.2 rounded text-[10px] bg-slate-800 text-slate-300">0</span>
-            </button>
-            <button id="ospf-subtab-btn-ospf-routes" onclick="switchOspfSubTab('ospf-routes')" class="px-4 py-2.5 text-xs font-bold border-b-2 border-transparent text-slate-400 hover:text-slate-200 transition flex items-center gap-2 whitespace-nowrap">
-              <span>OSPF Routes</span>
-              <span id="dyn-badge-ospf-routes" class="px-1.5 py-0.2 rounded text-[10px] bg-slate-800 text-slate-300">0</span>
-            </button>
-          </div>
 
-          <!-- Sub-Tab 4: OSPF Interfaces -->
-          <div id="dyn-subtab-ospf-ifaces" class="hidden bg-slate-900/90 border border-slate-800 rounded-xl overflow-hidden">
-            <div class="p-4 border-b border-slate-800 flex items-center justify-between flex-wrap gap-3">
-              <div class="flex items-center gap-3">
-                <h3 class="text-sm font-bold text-white">OSPF Interface Templates (/routing ospf interface-template)</h3>
-                <span id="ospf-ifaces-count-badge" class="text-xs font-mono text-slate-400">0 Interfaces</span>
-                <span id="ospf-live-status-pill" class="text-[10px] px-2 py-0.5 rounded font-mono bg-slate-800 text-slate-400 border border-slate-700">Checking...</span>
+            <!-- ================= TAB 2: AREAS ================= -->
+            <div id="ospf-pane-areas" class="hidden flex-1 flex flex-col">
+              <!-- Toolbar -->
+              <div class="bg-[#101721] border-b border-[#243347] px-3 py-1.5 flex items-center justify-between text-xs select-none">
+                <div class="flex items-center space-x-2">
+                  <button onclick="openOspfAreaModal()" class="px-2.5 py-1 bg-[#1a2737] hover:bg-sky-600 hover:text-white text-sky-400 border border-[#2d425d] rounded transition flex items-center gap-1 font-medium">
+                    <span>+</span> <span>New</span>
+                  </button>
+                  <button onclick="enableSelectedOspfArea()" id="btn-ospf-area-enable" disabled class="px-2.5 py-1 bg-[#15202e] text-slate-500 border border-[#233346] rounded flex items-center gap-1 font-medium disabled:opacity-50">
+                    <span>▶</span> <span>Enable</span>
+                  </button>
+                  <button onclick="disableSelectedOspfArea()" id="btn-ospf-area-disable" disabled class="px-2.5 py-1 bg-[#15202e] text-slate-500 border border-[#233346] rounded flex items-center gap-1 font-medium disabled:opacity-50">
+                    <span>⏸</span> <span>Disable</span>
+                  </button>
+                  <button onclick="removeSelectedOspfArea()" id="btn-ospf-area-remove" disabled class="px-2.5 py-1 bg-[#15202e] text-slate-500 border border-[#233346] rounded flex items-center gap-1 font-medium disabled:opacity-50">
+                    <span>✕</span> <span>Remove</span>
+                  </button>
+                  <button onclick="promptCommentOspfArea()" id="btn-ospf-area-comment" disabled class="px-2.5 py-1 bg-[#15202e] text-slate-500 border border-[#233346] rounded flex items-center gap-1 font-medium disabled:opacity-50">
+                    <span>💬</span> <span>Comment</span>
+                  </button>
+                </div>
+                <div class="flex items-center space-x-2">
+                  <div class="relative">
+                    <input type="text" id="filter-ospf-areas" oninput="renderOspfAreas()" placeholder="Find..." class="bg-[#15202e] border border-[#26374d] rounded px-2 py-0.5 text-xs text-white placeholder-slate-500 focus:outline-none focus:border-sky-500 w-32">
+                  </div>
+                  <button class="px-2 py-1 bg-[#15202e] hover:bg-[#1a2737] text-slate-300 border border-[#233346] rounded flex items-center gap-1">
+                    <span>▼</span> <span>Filter</span>
+                  </button>
+                </div>
+              </div>
+              <!-- Table -->
+              <div class="flex-1 overflow-x-auto">
+                <table class="w-full text-left text-xs font-mono">
+                  <thead class="text-slate-400 bg-[#0e1622] border-b border-[#243347] text-[11px] font-sans select-none">
+                    <tr>
+                      <th class="py-2 px-3 w-8 text-center cursor-pointer" onclick="sortOspfTable('areas', 'id')">#</th>
+                      <th class="py-2 px-3 w-12 text-center cursor-pointer" onclick="sortOspfTable('areas', 'flags')">Flags</th>
+                      <th class="py-2 px-3 cursor-pointer" onclick="sortOspfTable('areas', 'comment')">Comment</th>
+                      <th class="py-2 px-3 cursor-pointer" onclick="sortOspfTable('areas', 'name')">Name <span id="sort-ospf-areas-name">▲</span></th>
+                      <th class="py-2 px-3 cursor-pointer" onclick="sortOspfTable('areas', 'instance')">Instance</th>
+                      <th class="py-2 px-3 cursor-pointer" onclick="sortOspfTable('areas', 'area_id')">Area ID</th>
+                      <th class="py-2 px-3 cursor-pointer" onclick="sortOspfTable('areas', 'area_type')">Type</th>
+                    </tr>
+                  </thead>
+                  <tbody id="ospf-areas-table-body" class="divide-y divide-[#1e2c3d]"></tbody>
+                </table>
+              </div>
+              <!-- Footer -->
+              <div class="bg-[#101721] border-t border-[#243347] px-3 py-1.5 flex items-center justify-between text-[11px] font-mono text-slate-400 select-none">
+                <span id="ospf-areas-footer-count">0</span>
+                <span class="flex items-center gap-1.5 text-emerald-400"><span class="w-2 h-2 rounded-full bg-emerald-400 animate-pulse"></span> ▶ Live</span>
               </div>
-              <button onclick="openAddOspfInterfaceModal()" class="text-xs font-semibold px-3 py-1.5 rounded-lg bg-indigo-600 hover:bg-indigo-500 text-white transition">
-                + Add Interface
-              </button>
             </div>
 
-            <div class="overflow-x-auto">
-              <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-16 text-center">Flags</th>
-                    <th class="py-2.5 px-3">Network (CIDR)</th>
-                    <th class="py-2.5 px-3">Area</th>
-                    <th class="py-2.5 px-3">Interface</th>
-                    <th class="py-2.5 px-3">Cost</th>
-                    <th class="py-2.5 px-3">Priority</th>
-                    <th class="py-2.5 px-3">Timers (H/D)</th>
-                    <th class="py-2.5 px-3">Auth</th>
-                    <th class="py-2.5 px-3">Comment</th>
-                    <th class="py-2.5 px-3 text-right font-sans">Actions</th>
-                  </tr>
-                </thead>
-                <tbody id="ospf-ifaces-table-body" class="divide-y divide-slate-800/80"></tbody>
-              </table>
-            </div>
-            <div id="ospf-ifaces-empty" class="hidden p-10 text-center text-slate-500 text-xs font-sans">
-              No OSPF interfaces configured. Click "+ Add OSPF Interface" to enable OSPF on local subnets.
+            <!-- ================= TAB 3: AREA RANGES ================= -->
+            <div id="ospf-pane-area-ranges" class="hidden flex-1 flex flex-col">
+              <!-- Toolbar -->
+              <div class="bg-[#101721] border-b border-[#243347] px-3 py-1.5 flex items-center justify-between text-xs select-none">
+                <div class="flex items-center space-x-2">
+                  <button onclick="openOspfAreaRangeModal()" class="px-2.5 py-1 bg-[#1a2737] hover:bg-sky-600 hover:text-white text-sky-400 border border-[#2d425d] rounded transition flex items-center gap-1 font-medium">
+                    <span>+</span> <span>New</span>
+                  </button>
+                  <button onclick="enableSelectedOspfAreaRange()" id="btn-ospf-range-enable" disabled class="px-2.5 py-1 bg-[#15202e] text-slate-500 border border-[#233346] rounded flex items-center gap-1 font-medium disabled:opacity-50">
+                    <span>▶</span> <span>Enable</span>
+                  </button>
+                  <button onclick="disableSelectedOspfAreaRange()" id="btn-ospf-range-disable" disabled class="px-2.5 py-1 bg-[#15202e] text-slate-500 border border-[#233346] rounded flex items-center gap-1 font-medium disabled:opacity-50">
+                    <span>⏸</span> <span>Disable</span>
+                  </button>
+                  <button onclick="removeSelectedOspfAreaRange()" id="btn-ospf-range-remove" disabled class="px-2.5 py-1 bg-[#15202e] text-slate-500 border border-[#233346] rounded flex items-center gap-1 font-medium disabled:opacity-50">
+                    <span>✕</span> <span>Remove</span>
+                  </button>
+                  <button onclick="promptCommentOspfAreaRange()" id="btn-ospf-range-comment" disabled class="px-2.5 py-1 bg-[#15202e] text-slate-500 border border-[#233346] rounded flex items-center gap-1 font-medium disabled:opacity-50">
+                    <span>💬</span> <span>Comment</span>
+                  </button>
+                </div>
+                <div class="flex items-center space-x-2">
+                  <div class="relative">
+                    <input type="text" id="filter-ospf-area-ranges" oninput="renderOspfAreaRanges()" placeholder="Find..." class="bg-[#15202e] border border-[#26374d] rounded px-2 py-0.5 text-xs text-white placeholder-slate-500 focus:outline-none focus:border-sky-500 w-32">
+                  </div>
+                  <button class="px-2 py-1 bg-[#15202e] hover:bg-[#1a2737] text-slate-300 border border-[#233346] rounded flex items-center gap-1">
+                    <span>▼</span> <span>Filter</span>
+                  </button>
+                </div>
+              </div>
+              <!-- Table -->
+              <div class="flex-1 overflow-x-auto">
+                <table class="w-full text-left text-xs font-mono">
+                  <thead class="text-slate-400 bg-[#0e1622] border-b border-[#243347] text-[11px] font-sans select-none">
+                    <tr>
+                      <th class="py-2 px-3 w-8 text-center cursor-pointer" onclick="sortOspfTable('ranges', 'id')">#</th>
+                      <th class="py-2 px-3 w-12 text-center cursor-pointer" onclick="sortOspfTable('ranges', 'flags')">Flags</th>
+                      <th class="py-2 px-3 cursor-pointer" onclick="sortOspfTable('ranges', 'comment')">Comment</th>
+                      <th class="py-2 px-3 cursor-pointer" onclick="sortOspfTable('ranges', 'area')">Area <span id="sort-ospf-ranges-area">▲</span></th>
+                      <th class="py-2 px-3 cursor-pointer" onclick="sortOspfTable('ranges', 'prefix')">Prefix</th>
+                      <th class="py-2 px-3 cursor-pointer" onclick="sortOspfTable('ranges', 'cost')">Cost</th>
+                      <th class="py-2 px-3 cursor-pointer" onclick="sortOspfTable('ranges', 'advertise')">Advertise</th>
+                    </tr>
+                  </thead>
+                  <tbody id="ospf-ranges-table-body" class="divide-y divide-[#1e2c3d]"></tbody>
+                </table>
+              </div>
+              <!-- Footer -->
+              <div class="bg-[#101721] border-t border-[#243347] px-3 py-1.5 flex items-center justify-between text-[11px] font-mono text-slate-400 select-none">
+                <span id="ospf-ranges-footer-count">0</span>
+                <span class="flex items-center gap-1.5 text-emerald-400"><span class="w-2 h-2 rounded-full bg-emerald-400 animate-pulse"></span> ▶ Live</span>
+              </div>
             </div>
-          </div>
 
-          <!-- Sub-Tab 5: OSPF Areas -->
-          <div id="dyn-subtab-ospf-areas" class="hidden bg-slate-900/90 border border-slate-800 rounded-xl overflow-hidden">
-            <div class="p-4 border-b border-slate-800 flex items-center justify-between">
-              <div class="flex items-center gap-3">
-                <h3 class="text-sm font-bold text-white">OSPF Areas (/routing ospf area)</h3>
-                <span id="ospf-areas-count-badge" class="text-xs font-mono text-slate-400">0 Areas</span>
+            <!-- ================= TAB 4: INTERFACE TEMPLATES ================= -->
+            <div id="ospf-pane-templates" class="hidden flex-1 flex flex-col">
+              <!-- Toolbar -->
+              <div class="bg-[#101721] border-b border-[#243347] px-3 py-1.5 flex items-center justify-between text-xs select-none">
+                <div class="flex items-center space-x-2">
+                  <button onclick="openOspfTemplateModal()" class="px-2.5 py-1 bg-[#1a2737] hover:bg-sky-600 hover:text-white text-sky-400 border border-[#2d425d] rounded transition flex items-center gap-1 font-medium">
+                    <span>+</span> <span>New</span>
+                  </button>
+                  <button onclick="enableSelectedOspfTemplate()" id="btn-ospf-tmpl-enable" disabled class="px-2.5 py-1 bg-[#15202e] text-slate-500 border border-[#233346] rounded flex items-center gap-1 font-medium disabled:opacity-50">
+                    <span>▶</span> <span>Enable</span>
+                  </button>
+                  <button onclick="disableSelectedOspfTemplate()" id="btn-ospf-tmpl-disable" disabled class="px-2.5 py-1 bg-[#15202e] text-slate-500 border border-[#233346] rounded flex items-center gap-1 font-medium disabled:opacity-50">
+                    <span>⏸</span> <span>Disable</span>
+                  </button>
+                  <button onclick="removeSelectedOspfTemplate()" id="btn-ospf-tmpl-remove" disabled class="px-2.5 py-1 bg-[#15202e] text-slate-500 border border-[#233346] rounded flex items-center gap-1 font-medium disabled:opacity-50">
+                    <span>✕</span> <span>Remove</span>
+                  </button>
+                  <button onclick="promptCommentOspfTemplate()" id="btn-ospf-tmpl-comment" disabled class="px-2.5 py-1 bg-[#15202e] text-slate-500 border border-[#233346] rounded flex items-center gap-1 font-medium disabled:opacity-50">
+                    <span>💬</span> <span>Comment</span>
+                  </button>
+                </div>
+                <div class="flex items-center space-x-2">
+                  <div class="relative">
+                    <input type="text" id="filter-ospf-templates" oninput="renderOspfTemplates()" placeholder="Find..." class="bg-[#15202e] border border-[#26374d] rounded px-2 py-0.5 text-xs text-white placeholder-slate-500 focus:outline-none focus:border-sky-500 w-32">
+                  </div>
+                  <button class="px-2 py-1 bg-[#15202e] hover:bg-[#1a2737] text-slate-300 border border-[#233346] rounded flex items-center gap-1">
+                    <span>▼</span> <span>Filter</span>
+                  </button>
+                </div>
+              </div>
+              <!-- Table -->
+              <div class="flex-1 overflow-x-auto">
+                <table class="w-full text-left text-xs font-mono">
+                  <thead class="text-slate-400 bg-[#0e1622] border-b border-[#243347] text-[11px] font-sans select-none">
+                    <tr>
+                      <th class="py-2 px-3 w-8 text-center cursor-pointer" onclick="sortOspfTable('templates', 'index')">#</th>
+                      <th class="py-2 px-3 w-12 text-center cursor-pointer" onclick="sortOspfTable('templates', 'flags')">Flags</th>
+                      <th class="py-2 px-3 cursor-pointer" onclick="sortOspfTable('templates', 'comment')">Comment</th>
+                      <th class="py-2 px-3 cursor-pointer" onclick="sortOspfTable('templates', 'interfaces')">Interfaces <span id="sort-ospf-templates-interfaces">▲</span></th>
+                      <th class="py-2 px-3 cursor-pointer" onclick="sortOspfTable('templates', 'area')">Area</th>
+                      <th class="py-2 px-3 cursor-pointer" onclick="sortOspfTable('templates', 'networks')">Networks</th>
+                      <th class="py-2 px-3 cursor-pointer" onclick="sortOspfTable('templates', 'network_type')">Network Type</th>
+                      <th class="py-2 px-3 cursor-pointer" onclick="sortOspfTable('templates', 'cost')">Cost</th>
+                      <th class="py-2 px-3 cursor-pointer" onclick="sortOspfTable('templates', 'priority')">Priority</th>
+                      <th class="py-2 px-3 cursor-pointer" onclick="sortOspfTable('templates', 'auth')">Auth</th>
+                    </tr>
+                  </thead>
+                  <tbody id="ospf-templates-table-body" class="divide-y divide-[#1e2c3d]"></tbody>
+                </table>
+              </div>
+              <!-- Footer -->
+              <div class="bg-[#101721] border-t border-[#243347] px-3 py-1.5 flex items-center justify-between text-[11px] font-mono text-slate-400 select-none">
+                <span id="ospf-templates-footer-count">0</span>
+                <span class="flex items-center gap-1.5 text-emerald-400"><span class="w-2 h-2 rounded-full bg-emerald-400 animate-pulse"></span> ▶ Live</span>
               </div>
-              <button onclick="openAddOspfAreaModal()" class="text-xs font-semibold px-3 py-1.5 rounded-lg bg-indigo-600 hover:bg-indigo-500 text-white transition">
-                + Add Area
-              </button>
             </div>
 
-            <div class="overflow-x-auto">
-              <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">Name</th>
-                    <th class="py-2.5 px-3">Area ID</th>
-                    <th class="py-2.5 px-3">Type</th>
-                    <th class="py-2.5 px-3">Comment</th>
-                    <th class="py-2.5 px-3 text-right font-sans">Actions</th>
-                  </tr>
-                </thead>
-                <tbody id="ospf-areas-table-body" class="divide-y divide-slate-800/80"></tbody>
-              </table>
+            <!-- ================= TAB 5: INTERFACES (LIVE DYNAMIC) ================= -->
+            <div id="ospf-pane-interfaces" class="hidden flex-1 flex flex-col">
+              <!-- Toolbar -->
+              <div class="bg-[#101721] border-b border-[#243347] px-3 py-1.5 flex items-center justify-between text-xs select-none">
+                <div class="text-slate-400 italic font-sans text-[11px]">Dynamic live kernel interfaces registered in OSPF daemon</div>
+                <div class="flex items-center space-x-2">
+                  <div class="relative">
+                    <input type="text" id="filter-ospf-interfaces" oninput="renderOspfLiveInterfaces()" placeholder="Find..." class="bg-[#15202e] border border-[#26374d] rounded px-2 py-0.5 text-xs text-white placeholder-slate-500 focus:outline-none focus:border-sky-500 w-32">
+                  </div>
+                  <button class="px-2 py-1 bg-[#15202e] hover:bg-[#1a2737] text-slate-300 border border-[#233346] rounded flex items-center gap-1">
+                    <span>▼</span> <span>Filter</span>
+                  </button>
+                </div>
+              </div>
+              <!-- Table -->
+              <div class="flex-1 overflow-x-auto">
+                <table class="w-full text-left text-xs font-mono">
+                  <thead class="text-slate-400 bg-[#0e1622] border-b border-[#243347] text-[11px] font-sans select-none">
+                    <tr>
+                      <th class="py-2 px-3 w-12 text-center cursor-pointer" onclick="sortOspfTable('live_ifaces', 'flags')">Flags</th>
+                      <th class="py-2 px-3 cursor-pointer" onclick="sortOspfTable('live_ifaces', 'interface')">Interface <span id="sort-ospf-live_ifaces-interface">▲</span></th>
+                      <th class="py-2 px-3 cursor-pointer" onclick="sortOspfTable('live_ifaces', 'address')">Address</th>
+                      <th class="py-2 px-3 cursor-pointer" onclick="sortOspfTable('live_ifaces', 'area')">Area</th>
+                      <th class="py-2 px-3 cursor-pointer" onclick="sortOspfTable('live_ifaces', 'instance_id')">Instance ID</th>
+                      <th class="py-2 px-3 cursor-pointer" onclick="sortOspfTable('live_ifaces', 'state')">State</th>
+                      <th class="py-2 px-3 cursor-pointer" onclick="sortOspfTable('live_ifaces', 'cost')">Cost</th>
+                      <th class="py-2 px-3 cursor-pointer" onclick="sortOspfTable('live_ifaces', 'priority')">Priority</th>
+                    </tr>
+                  </thead>
+                  <tbody id="ospf-live-interfaces-table-body" class="divide-y divide-[#1e2c3d]"></tbody>
+                </table>
+              </div>
+              <!-- Footer -->
+              <div class="bg-[#101721] border-t border-[#243347] px-3 py-1.5 flex items-center justify-between text-[11px] font-mono text-slate-400 select-none">
+                <span id="ospf-live-interfaces-footer-count">0</span>
+                <span class="flex items-center gap-1.5 text-emerald-400"><span class="w-2 h-2 rounded-full bg-emerald-400 animate-pulse"></span> ▶ Live</span>
+              </div>
             </div>
-          </div>
 
-          <!-- Sub-Tab 6: OSPF Neighbors -->
-          <div id="dyn-subtab-ospf-nbrs" class="hidden bg-slate-900/90 border border-slate-800 rounded-xl overflow-hidden">
-            <div class="p-4 border-b border-slate-800 flex items-center justify-between">
-              <h3 class="text-sm font-bold text-white">Live OSPF Neighbors (show ip ospf neighbor)</h3>
-              <span id="ospf-nbrs-count-badge" class="text-xs font-mono text-slate-400">0 Neighbors</span>
+            <!-- ================= TAB 6: STATIC NEIGHBORS ================= -->
+            <div id="ospf-pane-static-nbrs" class="hidden flex-1 flex flex-col">
+              <!-- Toolbar -->
+              <div class="bg-[#101721] border-b border-[#243347] px-3 py-1.5 flex items-center justify-between text-xs select-none">
+                <div class="flex items-center space-x-2">
+                  <button onclick="openOspfStaticNbrModal()" class="px-2.5 py-1 bg-[#1a2737] hover:bg-sky-600 hover:text-white text-sky-400 border border-[#2d425d] rounded transition flex items-center gap-1 font-medium">
+                    <span>+</span> <span>New</span>
+                  </button>
+                  <button onclick="enableSelectedOspfStaticNbr()" id="btn-ospf-snbr-enable" disabled class="px-2.5 py-1 bg-[#15202e] text-slate-500 border border-[#233346] rounded flex items-center gap-1 font-medium disabled:opacity-50">
+                    <span>▶</span> <span>Enable</span>
+                  </button>
+                  <button onclick="disableSelectedOspfStaticNbr()" id="btn-ospf-snbr-disable" disabled class="px-2.5 py-1 bg-[#15202e] text-slate-500 border border-[#233346] rounded flex items-center gap-1 font-medium disabled:opacity-50">
+                    <span>⏸</span> <span>Disable</span>
+                  </button>
+                  <button onclick="removeSelectedOspfStaticNbr()" id="btn-ospf-snbr-remove" disabled class="px-2.5 py-1 bg-[#15202e] text-slate-500 border border-[#233346] rounded flex items-center gap-1 font-medium disabled:opacity-50">
+                    <span>✕</span> <span>Remove</span>
+                  </button>
+                  <button onclick="promptCommentOspfStaticNbr()" id="btn-ospf-snbr-comment" disabled class="px-2.5 py-1 bg-[#15202e] text-slate-500 border border-[#233346] rounded flex items-center gap-1 font-medium disabled:opacity-50">
+                    <span>💬</span> <span>Comment</span>
+                  </button>
+                </div>
+                <div class="flex items-center space-x-2">
+                  <div class="relative">
+                    <input type="text" id="filter-ospf-static-nbrs" oninput="renderOspfStaticNbrs()" placeholder="Find..." class="bg-[#15202e] border border-[#26374d] rounded px-2 py-0.5 text-xs text-white placeholder-slate-500 focus:outline-none focus:border-sky-500 w-32">
+                  </div>
+                  <button class="px-2 py-1 bg-[#15202e] hover:bg-[#1a2737] text-slate-300 border border-[#233346] rounded flex items-center gap-1">
+                    <span>▼</span> <span>Filter</span>
+                  </button>
+                </div>
+              </div>
+              <!-- Table -->
+              <div class="flex-1 overflow-x-auto">
+                <table class="w-full text-left text-xs font-mono">
+                  <thead class="text-slate-400 bg-[#0e1622] border-b border-[#243347] text-[11px] font-sans select-none">
+                    <tr>
+                      <th class="py-2 px-3 w-8 text-center cursor-pointer" onclick="sortOspfTable('snbrs', 'id')">#</th>
+                      <th class="py-2 px-3 w-12 text-center cursor-pointer" onclick="sortOspfTable('snbrs', 'flags')">Flags</th>
+                      <th class="py-2 px-3 cursor-pointer" onclick="sortOspfTable('snbrs', 'comment')">Comment</th>
+                      <th class="py-2 px-3 cursor-pointer" onclick="sortOspfTable('snbrs', 'address')">Address <span id="sort-ospf-snbrs-address">▲</span></th>
+                      <th class="py-2 px-3 cursor-pointer" onclick="sortOspfTable('snbrs', 'area')">Area</th>
+                      <th class="py-2 px-3 cursor-pointer" onclick="sortOspfTable('snbrs', 'poll_interval')">Poll Interval</th>
+                      <th class="py-2 px-3 cursor-pointer" onclick="sortOspfTable('snbrs', 'cost')">Cost</th>
+                    </tr>
+                  </thead>
+                  <tbody id="ospf-snbrs-table-body" class="divide-y divide-[#1e2c3d]"></tbody>
+                </table>
+              </div>
+              <!-- Footer -->
+              <div class="bg-[#101721] border-t border-[#243347] px-3 py-1.5 flex items-center justify-between text-[11px] font-mono text-slate-400 select-none">
+                <span id="ospf-snbrs-footer-count">0</span>
+                <span class="flex items-center gap-1.5 text-emerald-400"><span class="w-2 h-2 rounded-full bg-emerald-400 animate-pulse"></span> ▶ Live</span>
+              </div>
             </div>
 
-            <div class="overflow-x-auto">
-              <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">Neighbor ID</th>
-                    <th class="py-2.5 px-3">IP Address</th>
-                    <th class="py-2.5 px-3">Interface</th>
-                    <th class="py-2.5 px-3">Area</th>
-                    <th class="py-2.5 px-3">State</th>
-                    <th class="py-2.5 px-3">Dead Timer</th>
-                  </tr>
-                </thead>
-                <tbody id="ospf-nbrs-table-body" class="divide-y divide-slate-800/80"></tbody>
-              </table>
-            </div>
-            <div id="ospf-nbrs-empty" class="hidden p-10 text-center text-slate-500 text-xs font-sans">
-              No active OSPF neighbors discovered. Ensure neighbors are in the same subnet and hello/dead timers match.
+            <!-- ================= TAB 7: NEIGHBORS (LIVE DYNAMIC) ================= -->
+            <div id="ospf-pane-neighbors" class="hidden flex-1 flex flex-col">
+              <!-- Toolbar -->
+              <div class="bg-[#101721] border-b border-[#243347] px-3 py-1.5 flex items-center justify-between text-xs select-none">
+                <div class="text-slate-400 italic font-sans text-[11px]">Discovered OSPF neighbor adjacencies</div>
+                <div class="flex items-center space-x-2">
+                  <div class="relative">
+                    <input type="text" id="filter-ospf-neighbors" oninput="renderOspfNeighbors()" placeholder="Find..." class="bg-[#15202e] border border-[#26374d] rounded px-2 py-0.5 text-xs text-white placeholder-slate-500 focus:outline-none focus:border-sky-500 w-32">
+                  </div>
+                  <button class="px-2 py-1 bg-[#15202e] hover:bg-[#1a2737] text-slate-300 border border-[#233346] rounded flex items-center gap-1">
+                    <span>▼</span> <span>Filter</span>
+                  </button>
+                </div>
+              </div>
+              <!-- Table -->
+              <div class="flex-1 overflow-x-auto">
+                <table class="w-full text-left text-xs font-mono">
+                  <thead class="text-slate-400 bg-[#0e1622] border-b border-[#243347] text-[11px] font-sans select-none">
+                    <tr>
+                      <th class="py-2 px-3 w-12 text-center cursor-pointer" onclick="sortOspfTable('neighbors', 'flags')">Flags</th>
+                      <th class="py-2 px-3 cursor-pointer" onclick="sortOspfTable('neighbors', 'comment')">Comment</th>
+                      <th class="py-2 px-3 cursor-pointer" onclick="sortOspfTable('neighbors', 'instance')">Instance</th>
+                      <th class="py-2 px-3 cursor-pointer" onclick="sortOspfTable('neighbors', 'area')">Area</th>
+                      <th class="py-2 px-3 cursor-pointer" onclick="sortOspfTable('neighbors', 'address')">Address <span id="sort-ospf-neighbors-address">▲</span></th>
+                      <th class="py-2 px-3 cursor-pointer" onclick="sortOspfTable('neighbors', 'state')">State</th>
+                      <th class="py-2 px-3 cursor-pointer" onclick="sortOspfTable('neighbors', 'state_changes')">State Changes</th>
+                    </tr>
+                  </thead>
+                  <tbody id="ospf-neighbors-table-body" class="divide-y divide-[#1e2c3d]"></tbody>
+                </table>
+              </div>
+              <!-- Footer -->
+              <div class="bg-[#101721] border-t border-[#243347] px-3 py-1.5 flex items-center justify-between text-[11px] font-mono text-slate-400 select-none">
+                <span id="ospf-neighbors-footer-count">0</span>
+                <span class="flex items-center gap-1.5 text-emerald-400"><span class="w-2 h-2 rounded-full bg-emerald-400 animate-pulse"></span> ▶ Live</span>
+              </div>
             </div>
-          </div>
 
-          <!-- Sub-Tab 7: OSPF Routes -->
-          <div id="dyn-subtab-ospf-routes" class="hidden bg-slate-900/90 border border-slate-800 rounded-xl overflow-hidden">
-            <div class="p-4 border-b border-slate-800 flex items-center justify-between">
-              <h3 class="text-sm font-bold text-white">OSPF Routing Table (show ip ospf route)</h3>
-              <span id="ospf-routes-count-badge" class="text-xs font-mono text-slate-400">0 Routes</span>
+            <!-- ================= TAB 8: LSA (LINK-STATE DATABASE) ================= -->
+            <div id="ospf-pane-lsa" class="hidden flex-1 flex flex-col">
+              <!-- Toolbar -->
+              <div class="bg-[#101721] border-b border-[#243347] px-3 py-1.5 flex items-center justify-between text-xs select-none">
+                <div class="text-slate-400 italic font-sans text-[11px]">BIRD Link-State Database (LSADB) advertisements</div>
+                <div class="flex items-center space-x-2">
+                  <div class="relative">
+                    <input type="text" id="filter-ospf-lsa" oninput="renderOspfLSA()" placeholder="Find..." class="bg-[#15202e] border border-[#26374d] rounded px-2 py-0.5 text-xs text-white placeholder-slate-500 focus:outline-none focus:border-sky-500 w-32">
+                  </div>
+                  <button class="px-2 py-1 bg-[#15202e] hover:bg-[#1a2737] text-slate-300 border border-[#233346] rounded flex items-center gap-1">
+                    <span>▼</span> <span>Filter</span>
+                  </button>
+                </div>
+              </div>
+              <!-- Table -->
+              <div class="flex-1 overflow-x-auto">
+                <table class="w-full text-left text-xs font-mono">
+                  <thead class="text-slate-400 bg-[#0e1622] border-b border-[#243347] text-[11px] font-sans select-none">
+                    <tr>
+                      <th class="py-2 px-3 w-12 text-center cursor-pointer" onclick="sortOspfTable('lsa', 'flags')">Flags</th>
+                      <th class="py-2 px-3 cursor-pointer" onclick="sortOspfTable('lsa', 'instance')">Instance <span id="sort-ospf-lsa-instance">▲</span></th>
+                      <th class="py-2 px-3 cursor-pointer" onclick="sortOspfTable('lsa', 'area')">Area</th>
+                      <th class="py-2 px-3 cursor-pointer" onclick="sortOspfTable('lsa', 'type')">Type</th>
+                      <th class="py-2 px-3 cursor-pointer" onclick="sortOspfTable('lsa', 'originator')">Originator</th>
+                      <th class="py-2 px-3 cursor-pointer" onclick="sortOspfTable('lsa', 'id')">ID</th>
+                      <th class="py-2 px-3 cursor-pointer" onclick="sortOspfTable('lsa', 'link')">Link</th>
+                      <th class="py-2 px-3 cursor-pointer" onclick="sortOspfTable('lsa', 'link_index')">Link In...</th>
+                      <th class="py-2 px-3 cursor-pointer" onclick="sortOspfTable('lsa', 'sequence')">Sequence</th>
+                      <th class="py-2 px-3 cursor-pointer" onclick="sortOspfTable('lsa', 'age')">Age</th>
+                    </tr>
+                  </thead>
+                  <tbody id="ospf-lsa-table-body" class="divide-y divide-[#1e2c3d]"></tbody>
+                </table>
+              </div>
+              <!-- Footer -->
+              <div class="bg-[#101721] border-t border-[#243347] px-3 py-1.5 flex items-center justify-between text-[11px] font-mono text-slate-400 select-none">
+                <span id="ospf-lsa-footer-count">0</span>
+                <span class="flex items-center gap-1.5 text-emerald-400"><span class="w-2 h-2 rounded-full bg-emerald-400 animate-pulse"></span> ▶ Live</span>
+              </div>
             </div>
 
-            <div class="overflow-x-auto">
-              <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">Prefix</th>
-                    <th class="py-2.5 px-3">Type</th>
-                    <th class="py-2.5 px-3">Cost</th>
-                    <th class="py-2.5 px-3">Area</th>
-                    <th class="py-2.5 px-3">Next Hop / Dev</th>
-                  </tr>
-                </thead>
-                <tbody id="ospf-routes-table-body" class="divide-y divide-slate-800/80"></tbody>
-              </table>
-            </div>
-            <div id="ospf-routes-empty" class="hidden p-10 text-center text-slate-500 text-xs font-sans">
-              No OSPF routes calculated.
-            </div>
           </div>
         </section>
 
@@ -7369,186 +7641,513 @@
     </div>
   </div>
 
-  <!-- ================= MODAL: OSPF INSTANCE SETTINGS ================= -->
+  <!-- ================= MODAL: OSPF INSTANCE (RouterOS v7 Style) ================= -->
   <div id="ospf-instance-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">
-        <div>
-          <h3 class="text-sm font-bold text-white">OSPF Instance Settings (/routing ospf instance)</h3>
-          <p class="text-[11px] text-slate-400">Configure OSPFv2 link-state routing daemon and redistributions</p>
+    <div class="bg-slate-900 border border-slate-800 rounded-2xl w-full max-w-2xl shadow-2xl overflow-hidden animate-in flex flex-col max-h-[90vh]">
+      <div class="px-6 py-3.5 border-b border-slate-800 flex items-center justify-between bg-slate-950/60">
+        <div class="flex items-center gap-2.5">
+          <span class="text-sky-400 text-sm">🔀</span>
+          <span class="text-xs font-semibold text-slate-400">OSPF Instance &gt;</span>
+          <span id="ospf-instance-modal-title" class="text-xs font-bold text-sky-300 font-mono">New...</span>
         </div>
-        <button onclick="closeOspfInstanceModal()" class="text-slate-400 hover:text-white p-1 rounded-lg">✕</button>
+        <button type="button" onclick="closeOspfInstanceModal()" class="text-slate-400 hover:text-white p-1 rounded-lg">✕</button>
       </div>
-      <form id="ospf-instance-form" onsubmit="submitOspfInstance(event)" class="p-6 space-y-4 text-xs">
-        <div class="flex items-center justify-between p-3 rounded-xl bg-slate-800/60 border border-slate-700/60">
-          <div>
-            <div class="font-semibold text-white">Enable OSPF Routing</div>
-            <div class="text-[11px] text-slate-400">Run BIRD 2 daemon for dynamic interior gateway routing & link-state topology</div>
-          </div>
-          <input type="checkbox" id="modal-ospf-enabled" class="w-4 h-4 rounded text-indigo-600 focus:ring-indigo-500 bg-slate-800 border-slate-600">
-        </div>
 
-        <div class="grid grid-cols-2 gap-4">
-          <div>
-            <label class="block font-semibold text-slate-300 mb-1.5">Router ID</label>
-            <input type="text" id="modal-ospf-router-id" placeholder="192.168.131.56" class="w-full bg-slate-800 border border-slate-700 rounded-lg px-3 py-2 text-white font-mono focus:outline-none focus:border-indigo-500">
-          </div>
-          <div>
-            <label class="block font-semibold text-slate-300 mb-1.5">Default Metric</label>
-            <input type="number" id="modal-ospf-metric" value="10" min="1" max="65535" class="w-full bg-slate-800 border border-slate-700 rounded-lg px-3 py-2 text-white font-mono">
+      <div class="flex border-b border-slate-800 bg-slate-950/30 px-6 gap-2">
+        <button type="button" class="px-4 py-2 text-xs font-bold border-b-2 border-sky-500 text-white transition">General</button>
+      </div>
+
+      <form id="ospf-instance-form" onsubmit="submitOspfInstance(event)" class="flex-1 overflow-hidden flex flex-col">
+        <input type="hidden" id="modal-ospf-inst-id">
+        <div class="flex-1 overflow-y-auto p-6 flex gap-6">
+          <div class="flex-1 space-y-3.5 text-xs">
+            <div class="grid grid-cols-3 items-center gap-3">
+              <label class="text-slate-400 font-medium">Enabled</label>
+              <div class="col-span-2">
+                <input type="checkbox" id="modal-ospf-inst-enabled" checked class="w-4 h-4 rounded text-sky-500 bg-slate-800 border-slate-700">
+              </div>
+            </div>
+
+            <div class="grid grid-cols-3 items-center gap-3">
+              <label class="text-slate-400 font-medium">Comment</label>
+              <div class="col-span-2">
+                <input type="text" id="modal-ospf-inst-comment" placeholder="Optional comment" class="w-full bg-slate-800 border border-slate-700 rounded-lg px-3 py-1.5 text-white">
+              </div>
+            </div>
+
+            <div class="grid grid-cols-3 items-center gap-3">
+              <label class="text-slate-400 font-medium">Name *</label>
+              <div class="col-span-2">
+                <input type="text" id="modal-ospf-inst-name" required value="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">Version</label>
+              <div class="col-span-2">
+                <select id="modal-ospf-inst-version" class="w-full bg-slate-800 border border-slate-700 rounded-lg px-3 py-1.5 text-white font-mono">
+                  <option value="2">2 (OSPFv2 IPv4)</option>
+                  <option value="3">3 (OSPFv3 IPv6)</option>
+                </select>
+              </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-ospf-inst-vrf" value="main" 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">Router ID</label>
+              <div class="col-span-2">
+                <input type="text" id="modal-ospf-inst-router-id" placeholder="10.42.62.254" 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">Routing Table</label>
+              <div class="col-span-2">
+                <input type="text" id="modal-ospf-inst-routing-table" value="main" 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-start gap-3 pt-2 border-t border-slate-800">
+              <label class="text-slate-400 font-medium pt-1">Redistribute</label>
+              <div class="col-span-2 space-y-2">
+                <label class="flex items-center gap-2 cursor-pointer text-slate-300">
+                  <input type="checkbox" id="modal-ospf-inst-redist-conn" class="rounded text-sky-500 bg-slate-800 border-slate-700">
+                  <span>connected</span>
+                </label>
+                <label class="flex items-center gap-2 cursor-pointer text-slate-300">
+                  <input type="checkbox" id="modal-ospf-inst-redist-static" checked class="rounded text-sky-500 bg-slate-800 border-slate-700">
+                  <span>static</span>
+                </label>
+                <label class="flex items-center gap-2 cursor-pointer text-slate-300">
+                  <input type="checkbox" id="modal-ospf-inst-redist-bgp" class="rounded text-sky-500 bg-slate-800 border-slate-700">
+                  <span>bgp</span>
+                </label>
+              </div>
+            </div>
           </div>
-        </div>
 
-        <div class="border-t border-slate-800 pt-3">
-          <label class="block font-semibold text-slate-300 mb-2">Redistribute Routes into OSPF</label>
-          <div class="space-y-2">
-            <label class="flex items-center space-x-2 text-slate-300 cursor-pointer">
-              <input type="checkbox" id="modal-ospf-redist-conn" class="rounded text-indigo-600 bg-slate-800 border-slate-700">
-              <span>Redistribute Connected Subnets</span>
-            </label>
-            <label class="flex items-center space-x-2 text-slate-300 cursor-pointer">
-              <input type="checkbox" id="modal-ospf-redist-static" class="rounded text-indigo-600 bg-slate-800 border-slate-700">
-              <span>Redistribute Static Routes</span>
-            </label>
-            <label class="flex items-center space-x-2 text-slate-300 cursor-pointer">
-              <input type="checkbox" id="modal-ospf-redist-bgp" class="rounded text-indigo-600 bg-slate-800 border-slate-700">
-              <span>Redistribute BGP Routes</span>
-            </label>
+          <!-- Sidebar Actions -->
+          <div class="w-28 flex flex-col gap-2 border-l border-slate-800 pl-4 select-none">
+            <button type="button" onclick="copyOspfInstance()" class="text-xs text-left px-2 py-1.5 rounded hover:bg-slate-800 text-slate-300">📄 Copy</button>
+            <button type="button" onclick="deleteCurrentOspfInstance()" id="btn-ospf-inst-modal-remove" class="text-xs text-left px-2 py-1.5 rounded hover:bg-rose-950/40 text-rose-400">✕ Remove</button>
           </div>
         </div>
 
-        <div class="pt-4 border-t border-slate-800 flex justify-end space-x-3">
-          <button type="button" onclick="closeOspfInstanceModal()" class="px-4 py-2 rounded-lg text-slate-400 hover:bg-slate-800">Cancel</button>
-          <button type="submit" class="px-5 py-2 rounded-lg font-semibold bg-indigo-600 hover:bg-indigo-500 text-white shadow-lg shadow-indigo-500/20">Save OSPF Settings</button>
+        <!-- Footer -->
+        <div class="px-6 py-3 border-t border-slate-800 bg-slate-950/40 flex justify-end gap-2.5">
+          <button type="button" onclick="closeOspfInstanceModal()" class="px-4 py-1.5 rounded-lg text-xs font-semibold text-slate-400 hover:text-white hover:bg-slate-800 transition">Cancel</button>
+          <button type="button" onclick="applyOspfInstanceForm()" class="px-4 py-1.5 rounded-lg text-xs font-semibold bg-slate-800 hover:bg-slate-700 text-sky-300 border border-slate-700 transition">Apply</button>
+          <button type="submit" class="px-5 py-1.5 rounded-lg text-xs font-bold bg-sky-600 hover:bg-sky-500 text-white shadow-lg shadow-sky-600/30 transition">OK</button>
         </div>
       </form>
     </div>
   </div>
 
-  <!-- ================= MODAL: OSPF AREA ================= -->
+  <!-- ================= MODAL: OSPF AREA (RouterOS v7 Style) ================= -->
   <div id="ospf-area-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-md 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="ospf-area-modal-title" class="text-sm font-bold text-white">Add OSPF Area</h3>
-        <button onclick="closeOspfAreaModal()" class="text-slate-400 hover:text-white p-1 rounded-lg">✕</button>
+    <div class="bg-slate-900 border border-slate-800 rounded-2xl w-full max-w-2xl shadow-2xl overflow-hidden animate-in flex flex-col max-h-[90vh]">
+      <div class="px-6 py-3.5 border-b border-slate-800 flex items-center justify-between bg-slate-950/60">
+        <div class="flex items-center gap-2.5">
+          <span class="text-sky-400 text-sm">🔀</span>
+          <span class="text-xs font-semibold text-slate-400">OSPF Area &gt;</span>
+          <span id="ospf-area-modal-title" class="text-xs font-bold text-sky-300 font-mono">New...</span>
+        </div>
+        <button type="button" onclick="closeOspfAreaModal()" class="text-slate-400 hover:text-white p-1 rounded-lg">✕</button>
+      </div>
+
+      <div class="flex border-b border-slate-800 bg-slate-950/30 px-6 gap-2">
+        <button type="button" class="px-4 py-2 text-xs font-bold border-b-2 border-sky-500 text-white transition">General</button>
       </div>
-      <form id="ospf-area-form" onsubmit="submitOspfArea(event)" class="p-6 space-y-4 text-xs">
+
+      <form id="ospf-area-form" onsubmit="submitOspfArea(event)" class="flex-1 overflow-hidden flex flex-col">
         <input type="hidden" id="modal-ospf-area-id">
-        <div>
-          <label class="block font-semibold text-slate-300 mb-1.5">Area Name *</label>
-          <input type="text" id="modal-ospf-area-name" required placeholder="backbone" class="w-full bg-slate-800 border border-slate-700 rounded-lg px-3 py-2 text-white font-mono">
-        </div>
-        <div class="grid grid-cols-2 gap-4">
-          <div>
-            <label class="block font-semibold text-slate-300 mb-1.5">Area ID *</label>
-            <input type="text" id="modal-ospf-area-areaid" required placeholder="0.0.0.0" class="w-full bg-slate-800 border border-slate-700 rounded-lg px-3 py-2 text-white font-mono">
+        <div class="flex-1 overflow-y-auto p-6 flex gap-6">
+          <div class="flex-1 space-y-3.5 text-xs">
+            <div class="grid grid-cols-3 items-center gap-3">
+              <label class="text-slate-400 font-medium">Enabled</label>
+              <div class="col-span-2">
+                <input type="checkbox" id="modal-ospf-area-enabled" checked class="w-4 h-4 rounded text-sky-500 bg-slate-800 border-slate-700">
+              </div>
+            </div>
+
+            <div class="grid grid-cols-3 items-center gap-3">
+              <label class="text-slate-400 font-medium">Comment</label>
+              <div class="col-span-2">
+                <input type="text" id="modal-ospf-area-comment" placeholder="Optional comment" class="w-full bg-slate-800 border border-slate-700 rounded-lg px-3 py-1.5 text-white">
+              </div>
+            </div>
+
+            <div class="grid grid-cols-3 items-center gap-3">
+              <label class="text-slate-400 font-medium">Name *</label>
+              <div class="col-span-2">
+                <input type="text" id="modal-ospf-area-name" required value="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">Instance</label>
+              <div class="col-span-2">
+                <select id="modal-ospf-area-instance" class="w-full bg-slate-800 border border-slate-700 rounded-lg px-3 py-1.5 text-white font-mono">
+                  <option value="Default">Default</option>
+                </select>
+              </div>
+            </div>
+
+            <div class="grid grid-cols-3 items-center gap-3">
+              <label class="text-slate-400 font-medium">Area ID *</label>
+              <div class="col-span-2">
+                <input type="text" id="modal-ospf-area-areaid" required value="0.0.0.0" placeholder="0.0.0.0" 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">Type</label>
+              <div class="col-span-2">
+                <select id="modal-ospf-area-type" class="w-full bg-slate-800 border border-slate-700 rounded-lg px-3 py-1.5 text-white font-mono">
+                  <option value="default">default</option>
+                  <option value="stub">stub</option>
+                  <option value="nssa">nssa</option>
+                </select>
+              </div>
+            </div>
+
+            <div class="grid grid-cols-3 items-center gap-3">
+              <label class="text-slate-400 font-medium">Default Cost</label>
+              <div class="col-span-2">
+                <input type="number" id="modal-ospf-area-default-cost" min="1" max="65535" placeholder="1" 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">No Summaries</label>
+              <div class="col-span-2">
+                <input type="checkbox" id="modal-ospf-area-no-summaries" class="rounded text-sky-500 bg-slate-800 border-slate-700">
+              </div>
+            </div>
           </div>
-          <div>
-            <label class="block font-semibold text-slate-300 mb-1.5">Area Type</label>
-            <select id="modal-ospf-area-type" class="w-full bg-slate-800 border border-slate-700 rounded-lg px-3 py-2 text-white font-mono">
-              <option value="default">Default / Normal</option>
-              <option value="stub">Stub</option>
-              <option value="nssa">NSSA</option>
-            </select>
+
+          <!-- Sidebar Actions -->
+          <div class="w-28 flex flex-col gap-2 border-l border-slate-800 pl-4 select-none">
+            <button type="button" onclick="copyOspfArea()" class="text-xs text-left px-2 py-1.5 rounded hover:bg-slate-800 text-slate-300">📄 Copy</button>
+            <button type="button" onclick="deleteCurrentOspfArea()" id="btn-ospf-area-modal-remove" class="text-xs text-left px-2 py-1.5 rounded hover:bg-rose-950/40 text-rose-400">✕ Remove</button>
           </div>
         </div>
-        <div>
-          <label class="block font-semibold text-slate-300 mb-1.5">Comment</label>
-          <input type="text" id="modal-ospf-area-comment" placeholder="Area description" class="w-full bg-slate-800 border border-slate-700 rounded-lg px-3 py-2 text-white">
-        </div>
-        <div class="pt-4 border-t border-slate-800 flex justify-end space-x-3">
-          <button type="button" onclick="closeOspfAreaModal()" class="px-4 py-2 rounded-lg text-slate-400 hover:bg-slate-800">Cancel</button>
-          <button type="submit" class="px-5 py-2 rounded-lg font-semibold bg-indigo-600 hover:bg-indigo-500 text-white shadow-lg shadow-indigo-500/20">Save Area</button>
+
+        <!-- Footer -->
+        <div class="px-6 py-3 border-t border-slate-800 bg-slate-950/40 flex justify-end gap-2.5">
+          <button type="button" onclick="closeOspfAreaModal()" class="px-4 py-1.5 rounded-lg text-xs font-semibold text-slate-400 hover:text-white hover:bg-slate-800 transition">Cancel</button>
+          <button type="button" onclick="applyOspfAreaForm()" class="px-4 py-1.5 rounded-lg text-xs font-semibold bg-slate-800 hover:bg-slate-700 text-sky-300 border border-slate-700 transition">Apply</button>
+          <button type="submit" class="px-5 py-1.5 rounded-lg text-xs font-bold bg-sky-600 hover:bg-sky-500 text-white shadow-lg shadow-sky-600/30 transition">OK</button>
         </div>
       </form>
     </div>
   </div>
 
-  <!-- ================= MODAL: OSPF INTERFACE ================= -->
-  <div id="ospf-interface-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">
-        <div>
-          <h3 id="ospf-interface-modal-title" class="text-sm font-bold text-white">Add OSPF Interface</h3>
-          <p class="text-[11px] text-slate-400">Configure OSPF link parameters on a network subnet or interface</p>
+  <!-- ================= MODAL: OSPF AREA RANGE (RouterOS v7 Style) ================= -->
+  <div id="ospf-area-range-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-2xl shadow-2xl overflow-hidden animate-in flex flex-col max-h-[90vh]">
+      <div class="px-6 py-3.5 border-b border-slate-800 flex items-center justify-between bg-slate-950/60">
+        <div class="flex items-center gap-2.5">
+          <span class="text-sky-400 text-sm">🔀</span>
+          <span class="text-xs font-semibold text-slate-400">Area Range &gt;</span>
+          <span id="ospf-area-range-modal-title" class="text-xs font-bold text-sky-300 font-mono">New...</span>
         </div>
-        <button onclick="closeOspfInterfaceModal()" class="text-slate-400 hover:text-white p-1 rounded-lg">✕</button>
+        <button type="button" onclick="closeOspfAreaRangeModal()" class="text-slate-400 hover:text-white p-1 rounded-lg">✕</button>
       </div>
-      <form id="ospf-interface-form" onsubmit="submitOspfInterface(event)" class="p-6 space-y-4 text-xs">
-        <input type="hidden" id="modal-ospf-iface-id">
-        <div class="grid grid-cols-2 gap-4">
-          <div>
-            <label class="block font-semibold text-slate-300 mb-1.5">Subnet Network (CIDR) *</label>
-            <input type="text" id="modal-ospf-iface-net" required placeholder="192.168.131.0/24" class="w-full bg-slate-800 border border-slate-700 rounded-lg px-3 py-2 text-white font-mono">
+
+      <div class="flex border-b border-slate-800 bg-slate-950/30 px-6 gap-2">
+        <button type="button" class="px-4 py-2 text-xs font-bold border-b-2 border-sky-500 text-white transition">General</button>
+      </div>
+
+      <form id="ospf-area-range-form" onsubmit="submitOspfAreaRange(event)" class="flex-1 overflow-hidden flex flex-col">
+        <input type="hidden" id="modal-ospf-range-id">
+        <div class="flex-1 overflow-y-auto p-6 flex gap-6">
+          <div class="flex-1 space-y-3.5 text-xs">
+            <div class="grid grid-cols-3 items-center gap-3">
+              <label class="text-slate-400 font-medium">Enabled</label>
+              <div class="col-span-2">
+                <input type="checkbox" id="modal-ospf-range-enabled" checked class="w-4 h-4 rounded text-sky-500 bg-slate-800 border-slate-700">
+              </div>
+            </div>
+
+            <div class="grid grid-cols-3 items-center gap-3">
+              <label class="text-slate-400 font-medium">Comment</label>
+              <div class="col-span-2">
+                <input type="text" id="modal-ospf-range-comment" placeholder="Optional comment" class="w-full bg-slate-800 border border-slate-700 rounded-lg px-3 py-1.5 text-white">
+              </div>
+            </div>
+
+            <div class="grid grid-cols-3 items-center gap-3">
+              <label class="text-slate-400 font-medium">Area *</label>
+              <div class="col-span-2">
+                <select id="modal-ospf-range-area" class="w-full bg-slate-800 border border-slate-700 rounded-lg px-3 py-1.5 text-white font-mono">
+                  <option value="Default">Default</option>
+                </select>
+              </div>
+            </div>
+
+            <div class="grid grid-cols-3 items-center gap-3">
+              <label class="text-slate-400 font-medium">Prefix *</label>
+              <div class="col-span-2">
+                <input type="text" id="modal-ospf-range-prefix" required placeholder="10.42.0.0/16" 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">Cost</label>
+              <div class="col-span-2">
+                <input type="number" id="modal-ospf-range-cost" min="0" max="65535" placeholder="1" 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">Advertise</label>
+              <div class="col-span-2">
+                <input type="checkbox" id="modal-ospf-range-advertise" checked class="rounded text-sky-500 bg-slate-800 border-slate-700">
+              </div>
+            </div>
           </div>
-          <div>
-            <label class="block font-semibold text-slate-300 mb-1.5">OSPF Area *</label>
-            <input type="text" id="modal-ospf-iface-area" required value="0.0.0.0" placeholder="0.0.0.0" class="w-full bg-slate-800 border border-slate-700 rounded-lg px-3 py-2 text-white font-mono">
+
+          <!-- Sidebar Actions -->
+          <div class="w-28 flex flex-col gap-2 border-l border-slate-800 pl-4 select-none">
+            <button type="button" onclick="deleteCurrentOspfAreaRange()" id="btn-ospf-range-modal-remove" class="text-xs text-left px-2 py-1.5 rounded hover:bg-rose-950/40 text-rose-400">✕ Remove</button>
           </div>
         </div>
 
-        <div class="grid grid-cols-2 gap-4">
-          <div>
-            <label class="block font-semibold text-slate-300 mb-1.5">Interface (Optional Device)</label>
-            <select id="modal-ospf-iface-device" class="w-full bg-slate-800 border border-slate-700 rounded-lg px-3 py-2 text-white font-mono">
-              <option value="">Any matching interface</option>
-            </select>
-          </div>
-          <div>
-            <label class="block font-semibold text-slate-300 mb-1.5">Metric Cost</label>
-            <input type="number" id="modal-ospf-iface-cost" value="10" min="1" max="65535" class="w-full bg-slate-800 border border-slate-700 rounded-lg px-3 py-2 text-white font-mono">
-          </div>
+        <!-- Footer -->
+        <div class="px-6 py-3 border-t border-slate-800 bg-slate-950/40 flex justify-end gap-2.5">
+          <button type="button" onclick="closeOspfAreaRangeModal()" class="px-4 py-1.5 rounded-lg text-xs font-semibold text-slate-400 hover:text-white hover:bg-slate-800 transition">Cancel</button>
+          <button type="button" onclick="applyOspfAreaRangeForm()" class="px-4 py-1.5 rounded-lg text-xs font-semibold bg-slate-800 hover:bg-slate-700 text-sky-300 border border-slate-700 transition">Apply</button>
+          <button type="submit" class="px-5 py-1.5 rounded-lg text-xs font-bold bg-sky-600 hover:bg-sky-500 text-white shadow-lg shadow-sky-600/30 transition">OK</button>
         </div>
+      </form>
+    </div>
+  </div>
 
-        <div class="grid grid-cols-3 gap-3">
-          <div>
-            <label class="block font-semibold text-slate-300 mb-1.5">Priority</label>
-            <input type="number" id="modal-ospf-iface-priority" value="1" min="0" max="255" class="w-full bg-slate-800 border border-slate-700 rounded-lg px-3 py-2 text-white font-mono">
-          </div>
-          <div>
-            <label class="block font-semibold text-slate-300 mb-1.5">Hello (s)</label>
-            <input type="number" id="modal-ospf-iface-hello" value="10" min="1" max="65535" class="w-full bg-slate-800 border border-slate-700 rounded-lg px-3 py-2 text-white font-mono">
-          </div>
-          <div>
-            <label class="block font-semibold text-slate-300 mb-1.5">Dead (s)</label>
-            <input type="number" id="modal-ospf-iface-dead" value="40" min="1" max="65535" class="w-full bg-slate-800 border border-slate-700 rounded-lg px-3 py-2 text-white font-mono">
-          </div>
+  <!-- ================= MODAL: OSPF INTERFACE TEMPLATE (RouterOS v7 Style) ================= -->
+  <div id="ospf-template-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-3xl shadow-2xl overflow-hidden animate-in flex flex-col max-h-[90vh]">
+      <div class="px-6 py-3.5 border-b border-slate-800 flex items-center justify-between bg-slate-950/60">
+        <div class="flex items-center gap-2.5">
+          <span class="text-sky-400 text-sm">🔀</span>
+          <span class="text-xs font-semibold text-slate-400">Interface Template &gt;</span>
+          <span id="ospf-template-modal-title" class="text-xs font-bold text-sky-300 font-mono">New...</span>
         </div>
+        <button type="button" onclick="closeOspfTemplateModal()" class="text-slate-400 hover:text-white p-1 rounded-lg">✕</button>
+      </div>
+
+      <div class="flex border-b border-slate-800 bg-slate-950/30 px-6 gap-2">
+        <button type="button" id="ospf-tmpl-tab-btn-general" onclick="switchOspfTemplateTab('general')" class="px-4 py-2 text-xs font-bold border-b-2 border-sky-500 text-white transition">General</button>
+        <button type="button" id="ospf-tmpl-tab-btn-auth" onclick="switchOspfTemplateTab('auth')" class="px-4 py-2 text-xs font-bold border-b-2 border-transparent text-slate-400 hover:text-slate-200 transition">Authentication</button>
+      </div>
+
+      <form id="ospf-template-form" onsubmit="submitOspfTemplate(event)" class="flex-1 overflow-hidden flex flex-col">
+        <input type="hidden" id="modal-ospf-tmpl-id">
+        <div class="flex-1 overflow-y-auto p-6 flex gap-6">
+          <div class="flex-1 space-y-3.5 text-xs">
+            
+            <!-- Tab General -->
+            <div id="ospf-tmpl-tab-general" class="space-y-3.5">
+              <div class="grid grid-cols-3 items-center gap-3">
+                <label class="text-slate-400 font-medium">Enabled</label>
+                <div class="col-span-2">
+                  <input type="checkbox" id="modal-ospf-tmpl-enabled" checked class="w-4 h-4 rounded text-sky-500 bg-slate-800 border-slate-700">
+                </div>
+              </div>
+
+              <div class="grid grid-cols-3 items-center gap-3">
+                <label class="text-slate-400 font-medium">Comment</label>
+                <div class="col-span-2">
+                  <input type="text" id="modal-ospf-tmpl-comment" placeholder="Optional notes" class="w-full bg-slate-800 border border-slate-700 rounded-lg px-3 py-1.5 text-white">
+                </div>
+              </div>
+
+              <div class="grid grid-cols-3 items-center gap-3">
+                <label class="text-slate-400 font-medium">Interfaces</label>
+                <div class="col-span-2">
+                  <input type="text" id="modal-ospf-tmpl-interfaces" placeholder="e.g. ether1, wlan1 or all" 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">Physical/VLAN/tunnel interfaces or leave empty to match by networks</p>
+                </div>
+              </div>
+
+              <div class="grid grid-cols-3 items-center gap-3">
+                <label class="text-slate-400 font-medium">Area *</label>
+                <div class="col-span-2">
+                  <select id="modal-ospf-tmpl-area" class="w-full bg-slate-800 border border-slate-700 rounded-lg px-3 py-1.5 text-white font-mono">
+                    <option value="Default">Default</option>
+                  </select>
+                </div>
+              </div>
+
+              <div class="grid grid-cols-3 items-center gap-3">
+                <label class="text-slate-400 font-medium">Networks</label>
+                <div class="col-span-2">
+                  <input type="text" id="modal-ospf-tmpl-networks" placeholder="e.g. 10.42.36.244/30" 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">Subnet CIDR to match interfaces against</p>
+                </div>
+              </div>
+
+              <div class="grid grid-cols-3 items-center gap-3">
+                <label class="text-slate-400 font-medium">Network Type</label>
+                <div class="col-span-2">
+                  <select id="modal-ospf-tmpl-net-type" class="w-full bg-slate-800 border border-slate-700 rounded-lg px-3 py-1.5 text-white font-mono">
+                    <option value="broadcast">broadcast</option>
+                    <option value="ptp">ptp (point-to-point)</option>
+                    <option value="nbma">nbma</option>
+                    <option value="point-to-multipoint">point-to-multipoint</option>
+                  </select>
+                </div>
+              </div>
+
+              <div class="grid grid-cols-3 items-center gap-3">
+                <label class="text-slate-400 font-medium">Cost</label>
+                <div class="col-span-2">
+                  <input type="number" id="modal-ospf-tmpl-cost" value="1" min="1" max="65535" 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">Priority</label>
+                <div class="col-span-2">
+                  <input type="number" id="modal-ospf-tmpl-priority" value="128" min="0" max="255" 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">Passive</label>
+                <div class="col-span-2">
+                  <input type="checkbox" id="modal-ospf-tmpl-passive" class="rounded text-sky-500 bg-slate-800 border-slate-700">
+                  <span class="text-[11px] text-slate-400 ml-1.5">Do not send/listen for OSPF hello packets</span>
+                </div>
+              </div>
+
+              <div class="grid grid-cols-3 items-center gap-3">
+                <label class="text-slate-400 font-medium">Timers (Hello / Dead)</label>
+                <div class="col-span-2 grid grid-cols-2 gap-2">
+                  <input type="number" id="modal-ospf-tmpl-hello" value="10" min="1" max="65535" placeholder="Hello 10s" class="bg-slate-800 border border-slate-700 rounded-lg px-3 py-1.5 text-white font-mono">
+                  <input type="number" id="modal-ospf-tmpl-dead" value="40" min="1" max="65535" placeholder="Dead 40s" class="bg-slate-800 border border-slate-700 rounded-lg px-3 py-1.5 text-white font-mono">
+                </div>
+              </div>
+            </div>
+
+            <!-- Tab Authentication -->
+            <div id="ospf-tmpl-tab-auth" class="hidden space-y-3.5">
+              <div class="grid grid-cols-3 items-center gap-3">
+                <label class="text-slate-400 font-medium">Auth</label>
+                <div class="col-span-2">
+                  <select id="modal-ospf-tmpl-auth" class="w-full bg-slate-800 border border-slate-700 rounded-lg px-3 py-1.5 text-white font-mono">
+                    <option value="none">none</option>
+                    <option value="simple">simple</option>
+                    <option value="md5">md5</option>
+                  </select>
+                </div>
+              </div>
+
+              <div class="grid grid-cols-3 items-center gap-3">
+                <label class="text-slate-400 font-medium">Auth Key</label>
+                <div class="col-span-2">
+                  <input type="password" id="modal-ospf-tmpl-auth-key" placeholder="••••••••" class="w-full bg-slate-800 border border-slate-700 rounded-lg px-3 py-1.5 text-white font-mono">
+                </div>
+              </div>
+            </div>
 
-        <div class="grid grid-cols-2 gap-4">
-          <div>
-            <label class="block font-semibold text-slate-300 mb-1.5">Authentication Type</label>
-            <select id="modal-ospf-iface-auth-type" class="w-full bg-slate-800 border border-slate-700 rounded-lg px-3 py-2 text-white font-mono">
-              <option value="none">None</option>
-              <option value="simple">Simple Password</option>
-              <option value="md5">MD5 Cryptographic</option>
-            </select>
           </div>
-          <div>
-            <label class="block font-semibold text-slate-300 mb-1.5">Auth Secret Key</label>
-            <input type="password" id="modal-ospf-iface-auth-key" placeholder="••••••••" class="w-full bg-slate-800 border border-slate-700 rounded-lg px-3 py-2 text-white font-mono">
+
+          <!-- Sidebar Actions -->
+          <div class="w-28 flex flex-col gap-2 border-l border-slate-800 pl-4 select-none">
+            <button type="button" onclick="copyOspfTemplate()" class="text-xs text-left px-2 py-1.5 rounded hover:bg-slate-800 text-slate-300">📄 Copy</button>
+            <button type="button" onclick="deleteCurrentOspfTemplate()" id="btn-ospf-tmpl-modal-remove" class="text-xs text-left px-2 py-1.5 rounded hover:bg-rose-950/40 text-rose-400">✕ Remove</button>
           </div>
         </div>
 
-        <div class="grid grid-cols-2 gap-3 p-3 rounded-xl bg-slate-800/40 border border-slate-700/50">
-          <label class="flex items-center space-x-2 text-slate-300 cursor-pointer">
-            <input type="checkbox" id="modal-ospf-iface-passive" class="rounded text-indigo-600 bg-slate-800 border-slate-700">
-            <span>Passive (Do not form adjs)</span>
-          </label>
-          <label class="flex items-center space-x-2 text-slate-300 cursor-pointer">
-            <input type="checkbox" id="modal-ospf-iface-disabled" class="rounded text-indigo-600 bg-slate-800 border-slate-700">
-            <span>Disabled (X)</span>
-          </label>
+        <!-- Footer -->
+        <div class="px-6 py-3 border-t border-slate-800 bg-slate-950/40 flex justify-end gap-2.5">
+          <button type="button" onclick="closeOspfTemplateModal()" class="px-4 py-1.5 rounded-lg text-xs font-semibold text-slate-400 hover:text-white hover:bg-slate-800 transition">Cancel</button>
+          <button type="button" onclick="applyOspfTemplateForm()" class="px-4 py-1.5 rounded-lg text-xs font-semibold bg-slate-800 hover:bg-slate-700 text-sky-300 border border-slate-700 transition">Apply</button>
+          <button type="submit" class="px-5 py-1.5 rounded-lg text-xs font-bold bg-sky-600 hover:bg-sky-500 text-white shadow-lg shadow-sky-600/30 transition">OK</button>
         </div>
+      </form>
+    </div>
+  </div>
 
-        <div>
-          <label class="block font-semibold text-slate-300 mb-1.5">Comment</label>
-          <input type="text" id="modal-ospf-iface-comment" placeholder="LAN segment routing" class="w-full bg-slate-800 border border-slate-700 rounded-lg px-3 py-2 text-white">
+  <!-- ================= MODAL: OSPF STATIC NEIGHBOR ================= -->
+  <div id="ospf-static-nbr-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-xl shadow-2xl overflow-hidden animate-in flex flex-col max-h-[90vh]">
+      <div class="px-6 py-3.5 border-b border-slate-800 flex items-center justify-between bg-slate-950/60">
+        <div class="flex items-center gap-2.5">
+          <span class="text-sky-400 text-sm">🔀</span>
+          <span class="text-xs font-semibold text-slate-400">Static Neighbor &gt;</span>
+          <span id="ospf-static-nbr-modal-title" class="text-xs font-bold text-sky-300 font-mono">New...</span>
         </div>
+        <button type="button" onclick="closeOspfStaticNbrModal()" class="text-slate-400 hover:text-white p-1 rounded-lg">✕</button>
+      </div>
 
-        <div class="pt-4 border-t border-slate-800 flex justify-end space-x-3">
-          <button type="button" onclick="closeOspfInterfaceModal()" class="px-4 py-2 rounded-lg text-slate-400 hover:bg-slate-800">Cancel</button>
-          <button type="submit" class="px-5 py-2 rounded-lg font-semibold bg-indigo-600 hover:bg-indigo-500 text-white shadow-lg shadow-indigo-500/20">Save Interface</button>
+      <form id="ospf-static-nbr-form" onsubmit="submitOspfStaticNbr(event)" class="flex-1 overflow-hidden flex flex-col">
+        <input type="hidden" id="modal-ospf-snbr-id">
+        <div class="flex-1 overflow-y-auto p-6 flex gap-6">
+          <div class="flex-1 space-y-3.5 text-xs">
+            <div class="grid grid-cols-3 items-center gap-3">
+              <label class="text-slate-400 font-medium">Enabled</label>
+              <div class="col-span-2">
+                <input type="checkbox" id="modal-ospf-snbr-enabled" checked class="w-4 h-4 rounded text-sky-500 bg-slate-800 border-slate-700">
+              </div>
+            </div>
+
+            <div class="grid grid-cols-3 items-center gap-3">
+              <label class="text-slate-400 font-medium">Comment</label>
+              <div class="col-span-2">
+                <input type="text" id="modal-ospf-snbr-comment" placeholder="Optional comment" class="w-full bg-slate-800 border border-slate-700 rounded-lg px-3 py-1.5 text-white">
+              </div>
+            </div>
+
+            <div class="grid grid-cols-3 items-center gap-3">
+              <label class="text-slate-400 font-medium">Address *</label>
+              <div class="col-span-2">
+                <input type="text" id="modal-ospf-snbr-address" required placeholder="10.42.62.249" 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">Area</label>
+              <div class="col-span-2">
+                <select id="modal-ospf-snbr-area" class="w-full bg-slate-800 border border-slate-700 rounded-lg px-3 py-1.5 text-white font-mono">
+                  <option value="Default">Default</option>
+                </select>
+              </div>
+            </div>
+
+            <div class="grid grid-cols-3 items-center gap-3">
+              <label class="text-slate-400 font-medium">Poll Interval</label>
+              <div class="col-span-2">
+                <input type="number" id="modal-ospf-snbr-poll" min="1" max="65535" placeholder="120" 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">Cost</label>
+              <div class="col-span-2">
+                <input type="number" id="modal-ospf-snbr-cost" min="1" max="65535" placeholder="1" class="w-full bg-slate-800 border border-slate-700 rounded-lg px-3 py-1.5 text-white font-mono">
+              </div>
+            </div>
+          </div>
+
+          <!-- Sidebar Actions -->
+          <div class="w-28 flex flex-col gap-2 border-l border-slate-800 pl-4 select-none">
+            <button type="button" onclick="deleteCurrentOspfStaticNbr()" id="btn-ospf-snbr-modal-remove" class="text-xs text-left px-2 py-1.5 rounded hover:bg-rose-950/40 text-rose-400">✕ Remove</button>
+          </div>
+        </div>
+
+        <!-- Footer -->
+        <div class="px-6 py-3 border-t border-slate-800 bg-slate-950/40 flex justify-end gap-2.5">
+          <button type="button" onclick="closeOspfStaticNbrModal()" class="px-4 py-1.5 rounded-lg text-xs font-semibold text-slate-400 hover:text-white hover:bg-slate-800 transition">Cancel</button>
+          <button type="button" onclick="applyOspfStaticNbrForm()" class="px-4 py-1.5 rounded-lg text-xs font-semibold bg-slate-800 hover:bg-slate-700 text-sky-300 border border-slate-700 transition">Apply</button>
+          <button type="submit" class="px-5 py-1.5 rounded-lg text-xs font-bold bg-sky-600 hover:bg-sky-500 text-white shadow-lg shadow-sky-600/30 transition">OK</button>
         </div>
       </form>
     </div>