|
|
@@ -2602,6 +2602,81 @@ func (m *Manager) SetRoutingFilterState(id string, disabled bool) error {
|
|
|
return m.applyBirdConfig()
|
|
|
}
|
|
|
|
|
|
+func (m *Manager) MoveRoutingFilter(id string, direction string) error {
|
|
|
+ m.mu.Lock()
|
|
|
+ defer m.mu.Unlock()
|
|
|
+
|
|
|
+ var targetRule *RoutingFilterRule
|
|
|
+ for i := range m.config.RoutingFilters {
|
|
|
+ if m.config.RoutingFilters[i].ID == id {
|
|
|
+ targetRule = &m.config.RoutingFilters[i]
|
|
|
+ break
|
|
|
+ }
|
|
|
+ }
|
|
|
+ if targetRule == nil {
|
|
|
+ return fmt.Errorf("routing filter rule not found: %s", id)
|
|
|
+ }
|
|
|
+
|
|
|
+ chain := targetRule.Chain
|
|
|
+ dir := strings.ToLower(strings.TrimSpace(direction))
|
|
|
+ if dir != "up" && dir != "down" {
|
|
|
+ return fmt.Errorf("invalid direction %q: must be 'up' or 'down'", direction)
|
|
|
+ }
|
|
|
+
|
|
|
+ type chainItem struct {
|
|
|
+ globalIndex int
|
|
|
+ rule RoutingFilterRule
|
|
|
+ }
|
|
|
+ var items []chainItem
|
|
|
+ for i, r := range m.config.RoutingFilters {
|
|
|
+ if r.Chain == chain {
|
|
|
+ items = append(items, chainItem{globalIndex: i, rule: r})
|
|
|
+ }
|
|
|
+ }
|
|
|
+
|
|
|
+ sort.SliceStable(items, func(i, j int) bool {
|
|
|
+ return items[i].rule.RuleOrder < items[j].rule.RuleOrder
|
|
|
+ })
|
|
|
+
|
|
|
+ chainIdx := -1
|
|
|
+ for i, it := range items {
|
|
|
+ if it.rule.ID == id {
|
|
|
+ chainIdx = i
|
|
|
+ break
|
|
|
+ }
|
|
|
+ }
|
|
|
+ if chainIdx == -1 {
|
|
|
+ return fmt.Errorf("rule not found in chain")
|
|
|
+ }
|
|
|
+
|
|
|
+ targetIdx := chainIdx
|
|
|
+ if dir == "up" {
|
|
|
+ targetIdx = chainIdx - 1
|
|
|
+ } else if dir == "down" {
|
|
|
+ targetIdx = chainIdx + 1
|
|
|
+ }
|
|
|
+
|
|
|
+ if targetIdx < 0 || targetIdx >= len(items) {
|
|
|
+ // Already at boundary, normalize order and return
|
|
|
+ for i, it := range items {
|
|
|
+ m.config.RoutingFilters[it.globalIndex].RuleOrder = (i + 1) * 10
|
|
|
+ }
|
|
|
+ _ = m.save()
|
|
|
+ return nil
|
|
|
+ }
|
|
|
+
|
|
|
+ // Swap positions in the sorted chain list
|
|
|
+ items[chainIdx], items[targetIdx] = items[targetIdx], items[chainIdx]
|
|
|
+
|
|
|
+ // Reassign spaced rule_order values (10, 20, 30...)
|
|
|
+ for i, it := range items {
|
|
|
+ m.config.RoutingFilters[it.globalIndex].RuleOrder = (i + 1) * 10
|
|
|
+ }
|
|
|
+
|
|
|
+ _ = m.save()
|
|
|
+ return m.applyBirdConfig()
|
|
|
+}
|
|
|
+
|
|
|
// translateRos7RuleToBird converts a RouterOS v7 rule script (or fallback fields) to valid BIRD 2 syntax
|
|
|
func translateRos7RuleToBird(r *RoutingFilterRule) string {
|
|
|
script := strings.TrimSpace(r.Rule)
|
|
|
@@ -4185,13 +4260,19 @@ func (m *Manager) GetSummary() DynamicRoutingSummary {
|
|
|
// ==========================================
|
|
|
|
|
|
func generateFRRPrefixList(name string, rules []RoutingFilterRule) string {
|
|
|
+ sortedRules := make([]RoutingFilterRule, len(rules))
|
|
|
+ copy(sortedRules, rules)
|
|
|
+ sort.SliceStable(sortedRules, func(i, j int) bool {
|
|
|
+ return sortedRules[i].RuleOrder < sortedRules[j].RuleOrder
|
|
|
+ })
|
|
|
+
|
|
|
var sb strings.Builder
|
|
|
seq := 10
|
|
|
reDstRange := regexp.MustCompile(`dst\s+in\s+([0-9\.\/]+)\{([0-9]+),([0-9]+)\}`)
|
|
|
reDstIn := regexp.MustCompile(`dst\s+in\s+([0-9\.\/]+)`)
|
|
|
reDstEq := regexp.MustCompile(`\bdst\s*={1,2}\s*([0-9\.\/]+)`)
|
|
|
|
|
|
- for _, r := range rules {
|
|
|
+ for _, r := range sortedRules {
|
|
|
if r.Disabled {
|
|
|
continue
|
|
|
}
|