free-range 0.2.0 → 0.2.2
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- checksums.yaml +4 -4
- data/lib/free-range.rb +97 -82
- data/lib/free-range.rb.~ +114 -76
- metadata +2 -3
- data/lib/free-range.rb~ +0 -531
checksums.yaml
CHANGED
@@ -1,7 +1,7 @@
|
|
1
1
|
---
|
2
2
|
SHA256:
|
3
|
-
metadata.gz:
|
4
|
-
data.tar.gz:
|
3
|
+
metadata.gz: c0f85398046bdc5ae6dcb287ee25528d3177e0e69d2f319df4beea41e7cdc211
|
4
|
+
data.tar.gz: 03b765801caed290b5768240273750520f2ec00a510b0bc4c569dc54b6c3f273
|
5
5
|
SHA512:
|
6
|
-
metadata.gz:
|
7
|
-
data.tar.gz:
|
6
|
+
metadata.gz: c9a326597e73b2b96b16a0dd9b0ee3d2bf5dd1d9078c5e864af0ba511699890bb70d93a6b8796574e682f2b983a9b3f97721f13ef2efb55f12806032bdd9ba9c
|
7
|
+
data.tar.gz: a67f1e6b3977d9c03f5f9802fd461f571c0229cd68cf2c87d2a3c93253a315ad00bd8fc867669a8cb3009a1c9eaf8132a8a092cfb017f3abde3e7ca20e2acff4
|
data/lib/free-range.rb
CHANGED
@@ -2,7 +2,10 @@ require 'optparse'
|
|
2
2
|
require 'rmagick'
|
3
3
|
require 'fileutils'
|
4
4
|
|
5
|
-
|
5
|
+
class FreeRange
|
6
|
+
# Змінні екземпляра для статичних параметрів
|
7
|
+
attr_reader :config, :subscribers_result, :target, :use_color, :debug, :table_mode, :table_png_mode
|
8
|
+
|
6
9
|
# Перевірка наявності ImageMagick
|
7
10
|
begin
|
8
11
|
require 'rmagick'
|
@@ -465,14 +468,12 @@ module FreeRange
|
|
465
468
|
end
|
466
469
|
|
467
470
|
# Метод для заповнення Ranges для одного інтерфейсу
|
468
|
-
# @param config [Config] Configuration object with commands
|
469
471
|
# @param interface [String, nil] Interface name or nil for all interfaces
|
470
472
|
# @param ranges [Ranges] Object to store VLAN ranges
|
471
|
-
# @param debug [Boolean] Enable debug output
|
472
473
|
# @return [void]
|
473
|
-
def
|
474
|
-
full_cmd = "#{config.ssh_command} '#{config.command_ranges(interface)}'"
|
475
|
-
puts "[DEBUG] Executing command: #{full_cmd}" if debug
|
474
|
+
def process_interface(interface, ranges)
|
475
|
+
full_cmd = "#{@config.ssh_command} '#{@config.command_ranges(interface)}'"
|
476
|
+
puts "[DEBUG] Executing command: #{full_cmd}" if @debug
|
476
477
|
result = `#{full_cmd}`.strip
|
477
478
|
unless result.empty?
|
478
479
|
result.each_line do |line|
|
@@ -484,8 +485,8 @@ module FreeRange
|
|
484
485
|
end
|
485
486
|
end
|
486
487
|
|
487
|
-
full_cmd = "#{config.ssh_command} '#{config.command_demux(interface)}'"
|
488
|
-
puts "[DEBUG] Executing command: #{full_cmd}" if debug
|
488
|
+
full_cmd = "#{@config.ssh_command} '#{@config.command_demux(interface)}'"
|
489
|
+
puts "[DEBUG] Executing command: #{full_cmd}" if @debug
|
489
490
|
result = `#{full_cmd}`.strip
|
490
491
|
unless result.empty?
|
491
492
|
result.each_line do |line|
|
@@ -499,8 +500,8 @@ module FreeRange
|
|
499
500
|
end
|
500
501
|
end
|
501
502
|
|
502
|
-
full_cmd = "#{config.ssh_command} '#{config.command_another(interface)}'"
|
503
|
-
puts "[DEBUG] Executing command: #{full_cmd}" if debug
|
503
|
+
full_cmd = "#{@config.ssh_command} '#{@config.command_another(interface)}'"
|
504
|
+
puts "[DEBUG] Executing command: #{full_cmd}" if @debug
|
504
505
|
result = `#{full_cmd}`.strip
|
505
506
|
unless result.empty?
|
506
507
|
result.each_line do |line|
|
@@ -515,9 +516,50 @@ module FreeRange
|
|
515
516
|
end
|
516
517
|
end
|
517
518
|
|
518
|
-
#
|
519
|
+
# Обробляє VLAN для інтерфейсу та виводить результати
|
520
|
+
# @param interface [String, nil] Interface name or nil for all interfaces
|
519
521
|
# @return [void]
|
520
|
-
def
|
522
|
+
def process_and_output(interface)
|
523
|
+
ranges = Ranges.new
|
524
|
+
vlans = Vlans.new
|
525
|
+
@subscribers_result.each_line do |line|
|
526
|
+
if line.split.first =~ /dhcp(?:_[0-9a-fA-F.]+)?_([^:]+):(\d+)@#{Regexp.escape(@target)}$/
|
527
|
+
subscriber_interface, vlan = $1, $2.to_i
|
528
|
+
if interface
|
529
|
+
vlans.add_vlan(vlan) if subscriber_interface == interface && vlan > 0
|
530
|
+
else
|
531
|
+
vlans.add_vlan(vlan) if vlan > 0
|
532
|
+
end
|
533
|
+
end
|
534
|
+
end
|
535
|
+
|
536
|
+
process_interface(interface, ranges)
|
537
|
+
if @debug
|
538
|
+
puts "\nІнтерфейс: #{interface}" if interface
|
539
|
+
Print.ranged(ranges)
|
540
|
+
Print.vlans(vlans)
|
541
|
+
Print.vlan_ranges(vlans)
|
542
|
+
puts
|
543
|
+
end
|
544
|
+
if @table_png_mode
|
545
|
+
Print.table_png(ranges, vlans, @table_png_mode, @target, interface)
|
546
|
+
elsif @table_mode
|
547
|
+
Print.table(ranges, vlans, @use_color, @target, interface)
|
548
|
+
else
|
549
|
+
Print.combined_ranges(ranges, vlans, @use_color, @target, interface)
|
550
|
+
end
|
551
|
+
end
|
552
|
+
|
553
|
+
# Ініціалізує об’єкт FreeRange із параметрами
|
554
|
+
def initialize
|
555
|
+
@config = nil
|
556
|
+
@subscribers_result = nil
|
557
|
+
@target = nil
|
558
|
+
@use_color = false
|
559
|
+
@debug = false
|
560
|
+
@table_mode = false
|
561
|
+
@table_png_mode = nil
|
562
|
+
|
521
563
|
options = {}
|
522
564
|
OptionParser.new do |opts|
|
523
565
|
opts.banner = <<~BANNER
|
@@ -542,27 +584,27 @@ module FreeRange
|
|
542
584
|
exit 1
|
543
585
|
end
|
544
586
|
|
545
|
-
#
|
546
|
-
use_color = !options[:no_color] && ENV['TERM'] && ENV['TERM'] != 'dumb'
|
547
|
-
debug = options[:debug]
|
548
|
-
table_mode = options[:table]
|
549
|
-
table_png_mode = options[:table_png]
|
587
|
+
# Ініціалізуємо змінні екземпляра
|
588
|
+
@use_color = !options[:no_color] && ENV['TERM'] && ENV['TERM'] != 'dumb'
|
589
|
+
@debug = options[:debug]
|
590
|
+
@table_mode = options[:table]
|
591
|
+
@table_png_mode = options[:table_png]
|
550
592
|
interface = options[:interface]
|
551
593
|
config_file = options[:config_file]
|
552
594
|
|
553
595
|
# Ініціалізуємо config з порожнім login
|
554
|
-
config = Config.new({ target: ARGV[0], username: nil, password: nil })
|
596
|
+
@config = Config.new({ target: ARGV[0], username: nil, password: nil })
|
555
597
|
|
556
598
|
# Завантажуємо конфігураційний файл, якщо він вказаний
|
557
599
|
if config_file
|
558
600
|
begin
|
559
601
|
# Виконуємо конфігураційний файл у контексті існуючого об’єкта config
|
560
|
-
config.instance_eval(File.read(config_file), config_file)
|
602
|
+
@config.instance_eval(File.read(config_file), config_file)
|
561
603
|
rescue LoadError, Errno::ENOENT
|
562
604
|
puts "Помилка: неможливо завантажити конфігураційний файл '#{config_file}'."
|
563
605
|
exit 1
|
564
606
|
rescue ArgumentError => e
|
565
|
-
puts "Помилка в
|
607
|
+
puts "Помилка в аргументах конфігураційного файлу '#{config_file}': #{e.message}"
|
566
608
|
exit 1
|
567
609
|
rescue StandardError => e
|
568
610
|
puts "Помилка в конфігураційному файлі '#{config_file}': #{e.message}"
|
@@ -571,8 +613,8 @@ module FreeRange
|
|
571
613
|
end
|
572
614
|
|
573
615
|
# Визначаємо username і password з пріоритетом: аргументи > config > ENV
|
574
|
-
username = options[:username] || config.username || ENV['WHOAMI']
|
575
|
-
password = options[:password] || config.password || ENV['WHATISMYPASSWD']
|
616
|
+
username = options[:username] || @config.username || ENV['WHOAMI']
|
617
|
+
password = options[:password] || @config.password || ENV['WHATISMYPASSWD']
|
576
618
|
|
577
619
|
if username.nil? || password.nil?
|
578
620
|
puts "Помилка: необхідно вказати ім'я користувача та пароль."
|
@@ -581,24 +623,41 @@ module FreeRange
|
|
581
623
|
end
|
582
624
|
|
583
625
|
login = { target: ARGV[0], username: username, password: password }
|
584
|
-
target = ARGV[0].split('.')[0]
|
626
|
+
@target = ARGV[0].split('.')[0]
|
585
627
|
puts "Connecting to device: #{login[:target]}"
|
586
628
|
|
587
629
|
# Оновлюємо config з актуальними login даними
|
588
|
-
config = Config.new(login) { |c|
|
589
|
-
c.username = config.username if config.username
|
590
|
-
c.password = config.password if config.password
|
630
|
+
@config = Config.new(login) { |c|
|
631
|
+
c.username = @config.username if @config.username
|
632
|
+
c.password = @config.password if @config.password
|
591
633
|
}
|
592
634
|
|
593
|
-
|
594
|
-
|
635
|
+
if @debug
|
636
|
+
puts "[DEBUG] Values:"
|
637
|
+
puts "[DEBUG] use_color: #{@use_color}"
|
638
|
+
puts "[DEBUG] table_mode: #{@table_mode}"
|
639
|
+
puts "[DEBUG] table_png_mode: #{@table_png_mode}"
|
640
|
+
puts "[DEBUG] interface: #{interface}"
|
641
|
+
puts "[DEBUG] ARGV[0]: #{ARGV[0]}"
|
642
|
+
puts "[DEBUG] target: #{@target}"
|
643
|
+
puts "[DEBUG] login: #{login}"
|
644
|
+
puts "[DEBUG] config_file: #{config_file}"
|
645
|
+
puts "[DEBUG] config.username: #{@config.username}"
|
646
|
+
puts "[DEBUG] config.password: #{@config.password}"
|
647
|
+
puts "[DEBUG] config.ssh_command: #{@config.ssh_command}"
|
648
|
+
puts "[DEBUG] config.subscribers_command: #{@config.subscribers_command}"
|
649
|
+
puts "[DEBUG] config.command_interfaces: #{@config.command_interfaces}"
|
650
|
+
end
|
651
|
+
|
652
|
+
@subscribers_result = `#{@config.subscribers_command}`.strip
|
653
|
+
if @subscribers_result.empty?
|
595
654
|
puts "Помилка: результат subscribers_command порожній. Перевір шлях або доступ."
|
596
655
|
exit 1
|
597
656
|
end
|
598
657
|
|
599
658
|
if interface == "all"
|
600
|
-
full_cmd = "#{config.ssh_command} '#{config.command_interfaces}'"
|
601
|
-
puts "[DEBUG] Executing command: #{full_cmd}" if debug
|
659
|
+
full_cmd = "#{@config.ssh_command} '#{@config.command_interfaces}'"
|
660
|
+
puts "[DEBUG] Executing command: #{full_cmd}" if @debug
|
602
661
|
result = `#{full_cmd}`.strip
|
603
662
|
if result.empty?
|
604
663
|
puts "Помилка: результат команди порожній. Перевір підключення або команду."
|
@@ -612,60 +671,16 @@ module FreeRange
|
|
612
671
|
end
|
613
672
|
|
614
673
|
interfaces.each do |intf|
|
615
|
-
|
616
|
-
vlans = Vlans.new
|
617
|
-
subscribers_result.each_line do |line|
|
618
|
-
if line.split.first =~ /dhcp(?:_[0-9a-fA-F.]+)?_([^:]+):(\d+)@#{Regexp.escape(target)}$/
|
619
|
-
subscriber_interface, vlan = $1, $2.to_i
|
620
|
-
vlans.add_vlan(vlan) if subscriber_interface == intf && vlan > 0
|
621
|
-
end
|
622
|
-
end
|
623
|
-
|
624
|
-
process_interface(config, intf, ranges, debug)
|
625
|
-
if debug
|
626
|
-
puts "\nІнтерфейс: #{intf}"
|
627
|
-
Print.ranged(ranges)
|
628
|
-
Print.vlans(vlans)
|
629
|
-
Print.vlan_ranges(vlans)
|
630
|
-
puts
|
631
|
-
end
|
632
|
-
if table_png_mode
|
633
|
-
Print.table_png(ranges, vlans, table_png_mode, target, intf)
|
634
|
-
elsif table_mode
|
635
|
-
Print.table(ranges, vlans, use_color, target, intf)
|
636
|
-
else
|
637
|
-
Print.combined_ranges(ranges, vlans, use_color, target, intf)
|
638
|
-
end
|
674
|
+
process_and_output(intf)
|
639
675
|
end
|
640
676
|
else
|
641
|
-
|
642
|
-
vlans = Vlans.new
|
643
|
-
subscribers_result.each_line do |line|
|
644
|
-
if line.split.first =~ /dhcp(?:_[0-9a-fA-F.]+)?_([^:]+):(\d+)@#{Regexp.escape(target)}$/
|
645
|
-
subscriber_interface, vlan = $1, $2.to_i
|
646
|
-
if interface
|
647
|
-
vlans.add_vlan(vlan) if subscriber_interface == interface && vlan > 0
|
648
|
-
else
|
649
|
-
vlans.add_vlan(vlan) if vlan > 0
|
650
|
-
end
|
651
|
-
end
|
652
|
-
end
|
653
|
-
|
654
|
-
process_interface(config, interface, ranges, debug)
|
655
|
-
if debug
|
656
|
-
puts "\nІнтерфейс: #{interface}" if interface
|
657
|
-
Print.ranged(ranges)
|
658
|
-
Print.vlans(vlans)
|
659
|
-
Print.vlan_ranges(vlans)
|
660
|
-
puts
|
661
|
-
end
|
662
|
-
if table_png_mode
|
663
|
-
Print.table_png(ranges, vlans, table_png_mode, target, interface)
|
664
|
-
elsif table_mode
|
665
|
-
Print.table(ranges, vlans, use_color, target, interface)
|
666
|
-
else
|
667
|
-
Print.combined_ranges(ranges, vlans, use_color, target, interface)
|
668
|
-
end
|
677
|
+
process_and_output(interface)
|
669
678
|
end
|
670
679
|
end
|
680
|
+
|
681
|
+
# Основна логіка виконання
|
682
|
+
# @return [void]
|
683
|
+
def self.run
|
684
|
+
new.run
|
685
|
+
end
|
671
686
|
end
|
data/lib/free-range.rb.~
CHANGED
@@ -14,19 +14,30 @@ module FreeRange
|
|
14
14
|
|
15
15
|
# Клас для зберігання конфігураційних команд
|
16
16
|
class Config
|
17
|
-
attr_accessor :
|
17
|
+
attr_accessor :username, :password
|
18
18
|
|
19
|
-
# Ініціалізує об’єкт конфігурації
|
19
|
+
# Ініціалізує об’єкт конфігурації
|
20
20
|
# @param login [Hash] Hash with target, username, and password
|
21
21
|
# @yield [self] Yields self for block-based configuration
|
22
22
|
def initialize(login)
|
23
23
|
@login = login
|
24
|
-
|
25
|
-
@
|
26
|
-
@subscribers_command = "ssh -C -x roffice /usr/local/share/noc/bin/radius-subscribers"
|
24
|
+
@username = nil
|
25
|
+
@password = nil
|
27
26
|
yield self if block_given?
|
28
27
|
end
|
29
28
|
|
29
|
+
# Повертає команду SSH
|
30
|
+
# @return [String] SSH command string
|
31
|
+
def ssh_command
|
32
|
+
"sshpass -p \"#{@login[:password]}\" ssh -C -x -4 -o StrictHostKeyChecking=no #{@login[:username]}@#{@login[:target]}"
|
33
|
+
end
|
34
|
+
|
35
|
+
# Повертає команду для отримання даних про передплатників
|
36
|
+
# @return [String] Subscribers command string
|
37
|
+
def subscribers_command
|
38
|
+
"ssh -C -x roffice /usr/local/share/noc/bin/radius-subscribers"
|
39
|
+
end
|
40
|
+
|
30
41
|
# Повертає команду для отримання списку інтерфейсів
|
31
42
|
# @return [String] Command to fetch interfaces
|
32
43
|
def command_interfaces
|
@@ -36,21 +47,21 @@ module FreeRange
|
|
36
47
|
# Повертає команду для отримання діапазонів VLAN
|
37
48
|
# @param interface [String, nil] Interface name or nil for all interfaces
|
38
49
|
# @return [String] Command to fetch VLAN ranges
|
39
|
-
def command_ranges(interface)
|
50
|
+
def command_ranges(interface = nil)
|
40
51
|
interface ? "show configuration interfaces #{interface} | no-more | display set | match dynamic-profile | match \"ranges ([0-9]+(-[0-9]+)?)\"" : 'show configuration interfaces | no-more | display set | match dynamic-profile | match "ranges ([0-9]+(-[0-9]+)?)"'
|
41
52
|
end
|
42
53
|
|
43
54
|
# Повертає команду для отримання демультиплексорних VLAN
|
44
55
|
# @param interface [String, nil] Interface name or nil for all interfaces
|
45
56
|
# @return [String] Command to fetch demux VLANs
|
46
|
-
def command_demux(interface)
|
57
|
+
def command_demux(interface = nil)
|
47
58
|
interface ? "show configuration interfaces #{interface} | display set | match unnumbered-address" : 'show configuration interfaces | display set | match unnumbered-address'
|
48
59
|
end
|
49
60
|
|
50
61
|
# Повертає команду для отримання інших VLAN
|
51
62
|
# @param interface [String, nil] Interface name or nil for all interfaces
|
52
63
|
# @return [String] Command to fetch other VLANs
|
53
|
-
def command_another(interface)
|
64
|
+
def command_another(interface = nil)
|
54
65
|
interface ? "show configuration interfaces #{interface} | display set | match vlan-id" : 'show configuration interfaces | display set | match vlan-id'
|
55
66
|
end
|
56
67
|
end
|
@@ -457,9 +468,11 @@ module FreeRange
|
|
457
468
|
# @param config [Config] Configuration object with commands
|
458
469
|
# @param interface [String, nil] Interface name or nil for all interfaces
|
459
470
|
# @param ranges [Ranges] Object to store VLAN ranges
|
471
|
+
# @param debug [Boolean] Enable debug output
|
460
472
|
# @return [void]
|
461
|
-
def self.process_interface(config, interface, ranges)
|
473
|
+
def self.process_interface(config, interface, ranges, debug = false)
|
462
474
|
full_cmd = "#{config.ssh_command} '#{config.command_ranges(interface)}'"
|
475
|
+
puts "[DEBUG] Executing command: #{full_cmd}" if debug
|
463
476
|
result = `#{full_cmd}`.strip
|
464
477
|
unless result.empty?
|
465
478
|
result.each_line do |line|
|
@@ -472,6 +485,7 @@ module FreeRange
|
|
472
485
|
end
|
473
486
|
|
474
487
|
full_cmd = "#{config.ssh_command} '#{config.command_demux(interface)}'"
|
488
|
+
puts "[DEBUG] Executing command: #{full_cmd}" if debug
|
475
489
|
result = `#{full_cmd}`.strip
|
476
490
|
unless result.empty?
|
477
491
|
result.each_line do |line|
|
@@ -486,6 +500,7 @@ module FreeRange
|
|
486
500
|
end
|
487
501
|
|
488
502
|
full_cmd = "#{config.ssh_command} '#{config.command_another(interface)}'"
|
503
|
+
puts "[DEBUG] Executing command: #{full_cmd}" if debug
|
489
504
|
result = `#{full_cmd}`.strip
|
490
505
|
unless result.empty?
|
491
506
|
result.each_line do |line|
|
@@ -500,6 +515,47 @@ module FreeRange
|
|
500
515
|
end
|
501
516
|
end
|
502
517
|
|
518
|
+
# Обробляє VLAN для інтерфейсу та виводить результати
|
519
|
+
# @param config [Config] Configuration object with commands
|
520
|
+
# @param interface [String, nil] Interface name or nil for all interfaces
|
521
|
+
# @param subscribers_result [String] Result of subscribers command
|
522
|
+
# @param target [String] Target device hostname
|
523
|
+
# @param use_color [Boolean] Enable colored output
|
524
|
+
# @param debug [Boolean] Enable debug output
|
525
|
+
# @param table_mode [Boolean] Display VLAN distribution table
|
526
|
+
# @param table_png_mode [String, nil] Path to save PNG or nil
|
527
|
+
# @return [void]
|
528
|
+
def self.process_and_output(config, interface, subscribers_result, target, use_color, debug, table_mode, table_png_mode)
|
529
|
+
ranges = Ranges.new
|
530
|
+
vlans = Vlans.new
|
531
|
+
subscribers_result.each_line do |line|
|
532
|
+
if line.split.first =~ /dhcp(?:_[0-9a-fA-F.]+)?_([^:]+):(\d+)@#{Regexp.escape(target)}$/
|
533
|
+
subscriber_interface, vlan = $1, $2.to_i
|
534
|
+
if interface
|
535
|
+
vlans.add_vlan(vlan) if subscriber_interface == interface && vlan > 0
|
536
|
+
else
|
537
|
+
vlans.add_vlan(vlan) if vlan > 0
|
538
|
+
end
|
539
|
+
end
|
540
|
+
end
|
541
|
+
|
542
|
+
process_interface(config, interface, ranges, debug)
|
543
|
+
if debug
|
544
|
+
puts "\nІнтерфейс: #{interface}" if interface
|
545
|
+
Print.ranged(ranges)
|
546
|
+
Print.vlans(vlans)
|
547
|
+
Print.vlan_ranges(vlans)
|
548
|
+
puts
|
549
|
+
end
|
550
|
+
if table_png_mode
|
551
|
+
Print.table_png(ranges, vlans, table_png_mode, target, interface)
|
552
|
+
elsif table_mode
|
553
|
+
Print.table(ranges, vlans, use_color, target, interface)
|
554
|
+
else
|
555
|
+
Print.combined_ranges(ranges, vlans, use_color, target, interface)
|
556
|
+
end
|
557
|
+
end
|
558
|
+
|
503
559
|
# Основна логіка виконання
|
504
560
|
# @return [void]
|
505
561
|
def self.run
|
@@ -527,14 +583,7 @@ module FreeRange
|
|
527
583
|
exit 1
|
528
584
|
end
|
529
585
|
|
530
|
-
|
531
|
-
password = options[:password] || ENV['WHATISMYPASSWD']
|
532
|
-
if username.nil? || password.nil?
|
533
|
-
puts "Помилка: необхідно вказати ім'я користувача та пароль."
|
534
|
-
puts "Використовуйте опції -u/--username і -p/--password або змінні оточення WHOAMI і WHATISMYPASSWD."
|
535
|
-
exit 1
|
536
|
-
end
|
537
|
-
|
586
|
+
# Визначаємо змінні з опцій
|
538
587
|
use_color = !options[:no_color] && ENV['TERM'] && ENV['TERM'] != 'dumb'
|
539
588
|
debug = options[:debug]
|
540
589
|
table_mode = options[:table]
|
@@ -542,25 +591,63 @@ module FreeRange
|
|
542
591
|
interface = options[:interface]
|
543
592
|
config_file = options[:config_file]
|
544
593
|
|
545
|
-
|
546
|
-
|
547
|
-
puts "Connecting to device: #{login[:target]}"
|
594
|
+
# Ініціалізуємо config з порожнім login
|
595
|
+
config = Config.new({ target: ARGV[0], username: nil, password: nil })
|
548
596
|
|
549
|
-
#
|
550
|
-
config = Config.new(login)
|
597
|
+
# Завантажуємо конфігураційний файл, якщо він вказаний
|
551
598
|
if config_file
|
552
599
|
begin
|
553
|
-
|
554
|
-
config
|
600
|
+
# Виконуємо конфігураційний файл у контексті існуючого об’єкта config
|
601
|
+
config.instance_eval(File.read(config_file), config_file)
|
555
602
|
rescue LoadError, Errno::ENOENT
|
556
603
|
puts "Помилка: неможливо завантажити конфігураційний файл '#{config_file}'."
|
557
604
|
exit 1
|
605
|
+
rescue ArgumentError => e
|
606
|
+
puts "Помилка в аргументах конфігураційного файлу '#{config_file}': #{e.message}"
|
607
|
+
exit 1
|
558
608
|
rescue StandardError => e
|
559
609
|
puts "Помилка в конфігураційному файлі '#{config_file}': #{e.message}"
|
560
610
|
exit 1
|
561
611
|
end
|
562
612
|
end
|
563
613
|
|
614
|
+
# Визначаємо username і password з пріоритетом: аргументи > config > ENV
|
615
|
+
username = options[:username] || config.username || ENV['WHOAMI']
|
616
|
+
password = options[:password] || config.password || ENV['WHATISMYPASSWD']
|
617
|
+
|
618
|
+
if username.nil? || password.nil?
|
619
|
+
puts "Помилка: необхідно вказати ім'я користувача та пароль."
|
620
|
+
puts "Використовуйте опції -u/--username і -p/--password, конфігураційний файл або змінні оточення WHOAMI і WHATISMYPASSWD."
|
621
|
+
exit 1
|
622
|
+
end
|
623
|
+
|
624
|
+
login = { target: ARGV[0], username: username, password: password }
|
625
|
+
target = ARGV[0].split('.')[0]
|
626
|
+
puts "Connecting to device: #{login[:target]}"
|
627
|
+
|
628
|
+
# Оновлюємо config з актуальними login даними
|
629
|
+
config = Config.new(login) { |c|
|
630
|
+
c.username = config.username if config.username
|
631
|
+
c.password = config.password if config.password
|
632
|
+
}
|
633
|
+
|
634
|
+
if debug
|
635
|
+
puts "[DEBUG] Values:"
|
636
|
+
puts "[DEBUG] use_color: #{use_color}"
|
637
|
+
puts "[DEBUG] table_mode: #{table_mode}"
|
638
|
+
puts "[DEBUG] table_png_mode: #{table_png_mode}"
|
639
|
+
puts "[DEBUG] interface: #{interface}"
|
640
|
+
puts "[DEBUG] ARGV[0]: #{ARGV[0]}"
|
641
|
+
puts "[DEBUG] target: #{target}"
|
642
|
+
puts "[DEBUG] login: #{login}"
|
643
|
+
puts "[DEBUG] config_file: #{config_file}"
|
644
|
+
puts "[DEBUG] config.username: #{config.username}"
|
645
|
+
puts "[DEBUG] config.password: #{config.password}"
|
646
|
+
puts "[DEBUG] config.ssh_command: #{config.ssh_command}"
|
647
|
+
puts "[DEBUG] config.subscribers_command: #{config.subscribers_command}"
|
648
|
+
puts "[DEBUG] config.command_interfaces: #{config.command_interfaces}"
|
649
|
+
end
|
650
|
+
|
564
651
|
subscribers_result = `#{config.subscribers_command}`.strip
|
565
652
|
if subscribers_result.empty?
|
566
653
|
puts "Помилка: результат subscribers_command порожній. Перевір шлях або доступ."
|
@@ -569,6 +656,7 @@ module FreeRange
|
|
569
656
|
|
570
657
|
if interface == "all"
|
571
658
|
full_cmd = "#{config.ssh_command} '#{config.command_interfaces}'"
|
659
|
+
puts "[DEBUG] Executing command: #{full_cmd}" if debug
|
572
660
|
result = `#{full_cmd}`.strip
|
573
661
|
if result.empty?
|
574
662
|
puts "Помилка: результат команди порожній. Перевір підключення або команду."
|
@@ -582,60 +670,10 @@ module FreeRange
|
|
582
670
|
end
|
583
671
|
|
584
672
|
interfaces.each do |intf|
|
585
|
-
|
586
|
-
vlans = Vlans.new
|
587
|
-
subscribers_result.each_line do |line|
|
588
|
-
if line.split.first =~ /dhcp(?:_[0-9a-fA-F.]+)?_([^:]+):(\d+)@#{Regexp.escape(target)}$/
|
589
|
-
subscriber_interface, vlan = $1, $2.to_i
|
590
|
-
vlans.add_vlan(vlan) if subscriber_interface == intf && vlan > 0
|
591
|
-
end
|
592
|
-
end
|
593
|
-
|
594
|
-
process_interface(config, intf, ranges)
|
595
|
-
if debug
|
596
|
-
puts "\nІнтерфейс: #{intf}"
|
597
|
-
Print.ranged(ranges)
|
598
|
-
Print.vlans(vlans)
|
599
|
-
Print.vlan_ranges(vlans)
|
600
|
-
puts
|
601
|
-
end
|
602
|
-
if table_png_mode
|
603
|
-
Print.table_png(ranges, vlans, table_png_mode, target, intf)
|
604
|
-
elsif table_mode
|
605
|
-
Print.table(ranges, vlans, use_color, target, intf)
|
606
|
-
else
|
607
|
-
Print.combined_ranges(ranges, vlans, use_color, target, intf)
|
608
|
-
end
|
673
|
+
process_and_output(config, intf, subscribers_result, target, use_color, debug, table_mode, table_png_mode)
|
609
674
|
end
|
610
675
|
else
|
611
|
-
|
612
|
-
vlans = Vlans.new
|
613
|
-
subscribers_result.each_line do |line|
|
614
|
-
if line.split.first =~ /dhcp(?:_[0-9a-fA-F.]+)?_([^:]+):(\d+)@#{Regexp.escape(target)}$/
|
615
|
-
subscriber_interface, vlan = $1, $2.to_i
|
616
|
-
if interface
|
617
|
-
vlans.add_vlan(vlan) if subscriber_interface == interface && vlan > 0
|
618
|
-
else
|
619
|
-
vlans.add_vlan(vlan) if vlan > 0
|
620
|
-
end
|
621
|
-
end
|
622
|
-
end
|
623
|
-
|
624
|
-
process_interface(config, interface, ranges)
|
625
|
-
if debug
|
626
|
-
puts "\nІнтерфейс: #{interface}" if interface
|
627
|
-
Print.ranged(ranges)
|
628
|
-
Print.vlans(vlans)
|
629
|
-
Print.vlan_ranges(vlans)
|
630
|
-
puts
|
631
|
-
end
|
632
|
-
if table_png_mode
|
633
|
-
Print.table_png(ranges, vlans, table_png_mode, target, interface)
|
634
|
-
elsif table_mode
|
635
|
-
Print.table(ranges, vlans, use_color, target, interface)
|
636
|
-
else
|
637
|
-
Print.combined_ranges(ranges, vlans, use_color, target, interface)
|
638
|
-
end
|
676
|
+
process_and_output(config, interface, subscribers_result, target, use_color, debug, table_mode, table_png_mode)
|
639
677
|
end
|
640
678
|
end
|
641
679
|
end
|
metadata
CHANGED
@@ -1,14 +1,14 @@
|
|
1
1
|
--- !ruby/object:Gem::Specification
|
2
2
|
name: free-range
|
3
3
|
version: !ruby/object:Gem::Version
|
4
|
-
version: 0.2.
|
4
|
+
version: 0.2.2
|
5
5
|
platform: ruby
|
6
6
|
authors:
|
7
7
|
- Oleksandr Russkikh //aka Olden Gremlin
|
8
8
|
autorequire:
|
9
9
|
bindir: bin
|
10
10
|
cert_chain: []
|
11
|
-
date: 2025-09-
|
11
|
+
date: 2025-09-30 00:00:00.000000000 Z
|
12
12
|
dependencies:
|
13
13
|
- !ruby/object:Gem::Dependency
|
14
14
|
name: rmagick
|
@@ -77,7 +77,6 @@ files:
|
|
77
77
|
- bin/free-range
|
78
78
|
- lib/free-range.rb
|
79
79
|
- lib/free-range.rb.~
|
80
|
-
- lib/free-range.rb~
|
81
80
|
homepage: https://github.com/oldengremlin/free-range
|
82
81
|
licenses:
|
83
82
|
- Apache-2.0
|
data/lib/free-range.rb~
DELETED
@@ -1,531 +0,0 @@
|
|
1
|
-
require 'optparse'
|
2
|
-
require 'rmagick'
|
3
|
-
require 'fileutils'
|
4
|
-
|
5
|
-
module FreeRange
|
6
|
-
|
7
|
-
# Перевірка наявності ImageMagick
|
8
|
-
begin
|
9
|
-
require 'rmagick'
|
10
|
-
rescue LoadError
|
11
|
-
puts "Помилка: бібліотека rmagick не встановлена або ImageMagick недоступний."
|
12
|
-
puts "Встановіть ImageMagick і виконайте: gem install rmagick"
|
13
|
-
exit 1
|
14
|
-
end
|
15
|
-
|
16
|
-
# Абстрактний клас для роботи з VLAN
|
17
|
-
class VlanContainer
|
18
|
-
def initialize
|
19
|
-
@vlans = []
|
20
|
-
end
|
21
|
-
|
22
|
-
# Додаємо VLAN до списку (віртуальний метод, може бути перевизначений)
|
23
|
-
def add_vlan(vlan)
|
24
|
-
@vlans << vlan
|
25
|
-
end
|
26
|
-
|
27
|
-
# Повертаємо масив VLAN
|
28
|
-
def vlans
|
29
|
-
@vlans.uniq.sort
|
30
|
-
end
|
31
|
-
|
32
|
-
# Створюємо хеш діапазонів із VLAN
|
33
|
-
def ranges
|
34
|
-
return {} if @vlans.empty?
|
35
|
-
|
36
|
-
vlan_ranges_hash = {}
|
37
|
-
sorted_vlans = vlans
|
38
|
-
start = sorted_vlans.first
|
39
|
-
prev = start
|
40
|
-
|
41
|
-
sorted_vlans[1..-1].each do |vlan|
|
42
|
-
unless vlan == prev + 1
|
43
|
-
vlan_ranges_hash[start] = prev
|
44
|
-
start = vlan
|
45
|
-
end
|
46
|
-
prev = vlan
|
47
|
-
end
|
48
|
-
vlan_ranges_hash[start] = prev
|
49
|
-
|
50
|
-
vlan_ranges_hash
|
51
|
-
end
|
52
|
-
end
|
53
|
-
|
54
|
-
# Клас для зберігання та обробки діапазонів
|
55
|
-
class Ranges < VlanContainer
|
56
|
-
def initialize
|
57
|
-
super
|
58
|
-
@another_in_ranges = []
|
59
|
-
end
|
60
|
-
|
61
|
-
# Додаємо діапазон до списку VLAN-ів
|
62
|
-
def add_range(start, finish)
|
63
|
-
(start..finish).each { |vlan| @vlans << vlan }
|
64
|
-
end
|
65
|
-
|
66
|
-
# Додаємо діапазон до списку "інших" VLAN-ів
|
67
|
-
def add_another_range(start, finish)
|
68
|
-
(start..finish).each { |vlan| @another_in_ranges << vlan }
|
69
|
-
end
|
70
|
-
|
71
|
-
# Повертаємо масив "інших" VLAN-ів
|
72
|
-
def another_in_ranges
|
73
|
-
@another_in_ranges.uniq.sort
|
74
|
-
end
|
75
|
-
end
|
76
|
-
|
77
|
-
# Клас для зберігання та обробки VLAN-ів
|
78
|
-
class Vlans < VlanContainer
|
79
|
-
# Метод add_vlan уже успадковано від VlanContainer
|
80
|
-
# Метод vlans уже успадковано
|
81
|
-
# Метод ranges уже успадковано, але перейменуємо для зрозумілості
|
82
|
-
alias vlan_ranges ranges
|
83
|
-
end
|
84
|
-
|
85
|
-
# Клас для виводу даних
|
86
|
-
class Print
|
87
|
-
def self.ranged(ranges)
|
88
|
-
puts "\nСформований хеш діапазонів (в порядку зростання):"
|
89
|
-
if ranges.ranges.empty?
|
90
|
-
puts "Не знайдено діапазонів."
|
91
|
-
else
|
92
|
-
ranges.ranges.sort_by { |k, _| k }.each do |start, end_val|
|
93
|
-
puts "range[#{start}]=#{end_val}"
|
94
|
-
end
|
95
|
-
end
|
96
|
-
end
|
97
|
-
|
98
|
-
def self.vlans(vlans)
|
99
|
-
puts "\nЗайняті VLAN-и в межах діапазонів (в порядку зростання):"
|
100
|
-
if vlans.vlans.empty?
|
101
|
-
puts "Не знайдено VLAN-ів у межах діапазонів."
|
102
|
-
else
|
103
|
-
puts vlans.vlans.uniq.sort.join(", ")
|
104
|
-
end
|
105
|
-
end
|
106
|
-
|
107
|
-
def self.vlan_ranges(vlans)
|
108
|
-
puts "\nДіапазони VLAN-ів (в порядку зростання):"
|
109
|
-
vlan_ranges = vlans.vlan_ranges
|
110
|
-
if vlan_ranges.empty?
|
111
|
-
puts "Не знайдено діапазонів VLAN-ів."
|
112
|
-
else
|
113
|
-
vlan_ranges.sort_by { |k, _| k }.each do |start, end_val|
|
114
|
-
puts "range[#{start}]=#{end_val}"
|
115
|
-
end
|
116
|
-
end
|
117
|
-
end
|
118
|
-
|
119
|
-
def self.combined_ranges(ranges, vlans, use_color, target, interface = nil)
|
120
|
-
if ranges.ranges.empty?
|
121
|
-
puts "Не знайдено діапазонів."
|
122
|
-
return
|
123
|
-
end
|
124
|
-
|
125
|
-
all_vlans, _status_counts = build_vlan_statuses(ranges, vlans)
|
126
|
-
result = []
|
127
|
-
sorted_vlans = all_vlans.keys.sort
|
128
|
-
start = sorted_vlans.first
|
129
|
-
prev = start
|
130
|
-
status = all_vlans[start]
|
131
|
-
|
132
|
-
sorted_vlans[1..-1].each do |vlan|
|
133
|
-
unless vlan == prev + 1 && all_vlans[vlan] == status
|
134
|
-
result << format_range(start, prev, status, use_color)
|
135
|
-
start = vlan
|
136
|
-
status = all_vlans[vlan]
|
137
|
-
end
|
138
|
-
prev = vlan
|
139
|
-
end
|
140
|
-
result << format_range(start, prev, status, use_color)
|
141
|
-
|
142
|
-
puts result.join(',')
|
143
|
-
end
|
144
|
-
|
145
|
-
def self.table(ranges, vlans, use_color, target, interface = nil)
|
146
|
-
puts "VLAN Distribution for #{target}#{interface ? " (#{interface})" : ''}"
|
147
|
-
all_vlans, status_counts = build_vlan_statuses(ranges, vlans)
|
148
|
-
|
149
|
-
puts " 0 1 2 3 4 5 6 7 8 9 "
|
150
|
-
(0..40).each do |h|
|
151
|
-
start_vlan = h * 100
|
152
|
-
end_vlan = [start_vlan + 99, 4094].min
|
153
|
-
row = (start_vlan..end_vlan).map { |vlan| format_table_char(all_vlans[vlan] || ' ', use_color) }.join
|
154
|
-
puts "#{format("%4d", start_vlan)} #{row}"
|
155
|
-
end
|
156
|
-
|
157
|
-
legend_parts = [
|
158
|
-
["Legend: ", nil],
|
159
|
-
["f", 'f'], ["=free", nil], [", ", nil],
|
160
|
-
["b", 'b'], ["=busy", nil], [", ", nil],
|
161
|
-
["e", 'e'], ["=error", nil], [", ", nil],
|
162
|
-
["c", 'c'], ["=configured", nil], [", ", nil],
|
163
|
-
["a", 'a'], ["=another", nil], [", ", nil],
|
164
|
-
["u", 'u'], ["=unused", nil]
|
165
|
-
]
|
166
|
-
legend_text = legend_parts.map do |text, status|
|
167
|
-
if status && use_color
|
168
|
-
format_table_char(status, use_color)
|
169
|
-
else
|
170
|
-
text
|
171
|
-
end
|
172
|
-
end.join
|
173
|
-
puts "\n#{legend_text}"
|
174
|
-
|
175
|
-
summary_parts = [
|
176
|
-
["Total: ", nil],
|
177
|
-
["f", 'f'], ["=#{status_counts['f']}", nil], [", ", nil],
|
178
|
-
["b", 'b'], ["=#{status_counts['b']}", nil], [", ", nil],
|
179
|
-
["e", 'e'], ["=#{status_counts['e']}", nil], [", ", nil],
|
180
|
-
["c", 'c'], ["=#{status_counts['c']}", nil], [", ", nil],
|
181
|
-
["a", 'a'], ["=#{status_counts['a']}", nil], [", ", nil],
|
182
|
-
["u", 'u'], ["=#{status_counts['u']}", nil]
|
183
|
-
]
|
184
|
-
summary_text = summary_parts.map do |text, status|
|
185
|
-
if status && use_color
|
186
|
-
format_table_char(status, use_color)
|
187
|
-
else
|
188
|
-
text
|
189
|
-
end
|
190
|
-
end.join
|
191
|
-
puts summary_text
|
192
|
-
end
|
193
|
-
|
194
|
-
def self.table_png(ranges, vlans, path, target, interface = nil)
|
195
|
-
all_vlans, status_counts = build_vlan_statuses(ranges, vlans)
|
196
|
-
cell_width = 12
|
197
|
-
cell_height = 20
|
198
|
-
rows = 41
|
199
|
-
cols = 100
|
200
|
-
header_height = 60
|
201
|
-
label_width = 50
|
202
|
-
width = label_width + cols * cell_width + 10
|
203
|
-
height = header_height + rows * cell_height + 20 + 50
|
204
|
-
font_size = 14
|
205
|
-
title_font_size = 18
|
206
|
-
font = 'Courier'
|
207
|
-
|
208
|
-
canvas = Magick::Image.new(width, height) { |options| options.background_color = 'white' }
|
209
|
-
gc = Magick::Draw.new
|
210
|
-
gc.font = font
|
211
|
-
gc.pointsize = font_size
|
212
|
-
gc.text_antialias = true
|
213
|
-
|
214
|
-
gc.fill('black')
|
215
|
-
gc.pointsize = title_font_size
|
216
|
-
gc.text(10, 25, "VLAN Distribution for #{target}#{interface ? " (#{interface})" : ''}")
|
217
|
-
gc.pointsize = font_size
|
218
|
-
|
219
|
-
(0..9).each do |i|
|
220
|
-
x = label_width + i * 10 * cell_width - 3
|
221
|
-
gc.fill('black')
|
222
|
-
gc.text(x + 5, header_height - 5, i.to_s)
|
223
|
-
end
|
224
|
-
|
225
|
-
(0..40).each do |h|
|
226
|
-
start_vlan = h * 100
|
227
|
-
end_vlan = [start_vlan + 99, 4094].min
|
228
|
-
y = header_height + h * cell_height
|
229
|
-
gc.fill('black')
|
230
|
-
gc.text(5, y + font_size, format("%4d", start_vlan))
|
231
|
-
|
232
|
-
(start_vlan..end_vlan).each_with_index do |vlan, i|
|
233
|
-
status = all_vlans[vlan] || ' '
|
234
|
-
x = label_width + i * cell_width
|
235
|
-
color = case status
|
236
|
-
when 'f' then '#00FF00' # Зелений
|
237
|
-
when 'b' then '#FFFF00' # Жовтий
|
238
|
-
when 'e' then '#FF0000' # Червоний
|
239
|
-
when 'c' then '#FF00FF' # Фіолетовий
|
240
|
-
when 'a' then '#0000FF' # Синій
|
241
|
-
when 'u' then '#555555' # Темно-сірий
|
242
|
-
else 'white' # Пробіл
|
243
|
-
end
|
244
|
-
gc.fill(color)
|
245
|
-
gc.rectangle(x, y, x + cell_width - 1, y + cell_height - 1)
|
246
|
-
gc.fill('black')
|
247
|
-
gc.text(x + 2, y + font_size, status) unless status == ' '
|
248
|
-
end
|
249
|
-
end
|
250
|
-
|
251
|
-
legend_y = height - 50
|
252
|
-
x = 10
|
253
|
-
legend_parts = [
|
254
|
-
["Legend: ", nil],
|
255
|
-
["f", '#00FF00'], ["=free", nil], [", ", nil],
|
256
|
-
["b", '#FFFF00'], ["=busy", nil], [", ", nil],
|
257
|
-
["e", '#FF0000'], ["=error", nil], [", ", nil],
|
258
|
-
["c", '#FF00FF'], ["=configured", nil], [", ", nil],
|
259
|
-
["a", '#0000FF'], ["=another", nil], [", ", nil],
|
260
|
-
["u", '#555555'], ["=unused", nil]
|
261
|
-
]
|
262
|
-
legend_parts.each do |text, color|
|
263
|
-
if color
|
264
|
-
gc.fill(color)
|
265
|
-
gc.rectangle(x, legend_y - font_size + 2, x + 10, legend_y + 2)
|
266
|
-
gc.fill('black')
|
267
|
-
gc.text(x + 2, legend_y, text)
|
268
|
-
x += 12
|
269
|
-
else
|
270
|
-
gc.fill('black')
|
271
|
-
gc.text(x, legend_y, text)
|
272
|
-
x += text.length * 8
|
273
|
-
end
|
274
|
-
end
|
275
|
-
|
276
|
-
summary_y = height - 30
|
277
|
-
x = 10
|
278
|
-
summary_parts = [
|
279
|
-
["Total: ", nil],
|
280
|
-
["f", '#00FF00'], ["=#{status_counts['f']}", nil], [", ", nil],
|
281
|
-
["b", '#FFFF00'], ["=#{status_counts['b']}", nil], [", ", nil],
|
282
|
-
["e", '#FF0000'], ["=#{status_counts['e']}", nil], [", ", nil],
|
283
|
-
["c", '#FF00FF'], ["=#{status_counts['c']}", nil], [", ", nil],
|
284
|
-
["a", '#0000FF'], ["=#{status_counts['a']}", nil], [", ", nil],
|
285
|
-
["u", '#555555'], ["=#{status_counts['u']}", nil]
|
286
|
-
]
|
287
|
-
summary_parts.each do |text, color|
|
288
|
-
if color
|
289
|
-
gc.fill(color)
|
290
|
-
gc.rectangle(x, summary_y - font_size + 2, x + 10, summary_y + 2)
|
291
|
-
gc.fill('black')
|
292
|
-
gc.text(x + 2, summary_y, text)
|
293
|
-
x += 12
|
294
|
-
else
|
295
|
-
gc.fill('black')
|
296
|
-
gc.text(x, summary_y, text)
|
297
|
-
x += text.length * 8
|
298
|
-
end
|
299
|
-
end
|
300
|
-
|
301
|
-
gc.draw(canvas)
|
302
|
-
FileUtils.mkdir_p(path) unless Dir.exist?(path)
|
303
|
-
filename = File.join(path, "free-range-#{target}#{interface ? "-#{interface.tr('/', '-')}" : ''}.png")
|
304
|
-
canvas.write(filename)
|
305
|
-
puts "Зображення збережено: #{filename}"
|
306
|
-
end
|
307
|
-
|
308
|
-
private
|
309
|
-
|
310
|
-
def self.build_vlan_statuses(ranges, vlans)
|
311
|
-
all_vlans = {}
|
312
|
-
ranges.ranges.each do |start, finish|
|
313
|
-
(start..finish).each { |vlan| all_vlans[vlan] = 'f' }
|
314
|
-
end
|
315
|
-
vlans.vlans.uniq.each { |vlan| all_vlans[vlan] = all_vlans.key?(vlan) ? 'b' : 'e' }
|
316
|
-
(1..4094).each { |vlan| all_vlans[vlan] = 'u' unless all_vlans.key?(vlan) }
|
317
|
-
ranges.another_in_ranges.each { |vlan| all_vlans[vlan] = all_vlans.key?(vlan) && all_vlans[vlan] != 'u' ? 'c' : 'a' }
|
318
|
-
status_counts = { 'f' => 0, 'b' => 0, 'e' => 0, 'c' => 0, 'a' => 0, 'u' => 0 }
|
319
|
-
all_vlans.each_value { |status| status_counts[status] += 1 }
|
320
|
-
|
321
|
-
[all_vlans, status_counts]
|
322
|
-
end
|
323
|
-
|
324
|
-
def self.format_range(start, finish, status, use_color)
|
325
|
-
range_text = start == finish ? "#{start}" : "#{start}-#{finish}"
|
326
|
-
range_text_with_status = "#{range_text}(#{status})"
|
327
|
-
if use_color
|
328
|
-
case status
|
329
|
-
when 'f' then "\e[32m#{range_text}\e[0m" # Зелений для free
|
330
|
-
when 'b' then "\e[33m#{range_text}\e[0m" # Жовтий для busy
|
331
|
-
when 'e' then "\e[31m#{range_text}\e[0m" # Червоний для error
|
332
|
-
when 'c' then "\e[35m#{range_text}\e[0m" # Фіолетовий для configured
|
333
|
-
when 'a' then "\e[34m#{range_text}\e[0m" # Синій для another
|
334
|
-
when 'u' then "\e[90m#{range_text}\e[0m" # Темно-сірий для unused
|
335
|
-
else range_text # Без кольору для інших статусів
|
336
|
-
end
|
337
|
-
else
|
338
|
-
range_text_with_status # Текстовий вивід зі статусами
|
339
|
-
end
|
340
|
-
end
|
341
|
-
|
342
|
-
def self.format_table_char(status, use_color)
|
343
|
-
if use_color
|
344
|
-
case status
|
345
|
-
when 'f' then "\e[48;5;2m\e[30m#{status}\e[0m" # Зелений фон, чорний текст
|
346
|
-
when 'b' then "\e[48;5;3m\e[30m#{status}\e[0m" # Жовтий фон, чорний текст
|
347
|
-
when 'e' then "\e[48;5;1m\e[30m#{status}\e[0m" # Червоний фон, чорний текст
|
348
|
-
when 'c' then "\e[48;5;5m\e[30m#{status}\e[0m" # Фіолетовий фон, чорний текст
|
349
|
-
when 'a' then "\e[48;5;4m\e[30m#{status}\e[0m" # Синій фон, чорний текст
|
350
|
-
when 'u' then "\e[48;5;8m\e[30m#{status}\e[0m" # Темно-сірий фон, чорний текст
|
351
|
-
else status # Без кольору для інших статусів
|
352
|
-
end
|
353
|
-
else
|
354
|
-
status # Текстовий вивід без кольорів
|
355
|
-
end
|
356
|
-
end
|
357
|
-
end
|
358
|
-
|
359
|
-
# Метод для заповнення Ranges для одного інтерфейсу
|
360
|
-
def self.process_interface(ssh_command, interface, ranges)
|
361
|
-
command_ranges = interface ? "show configuration interfaces #{interface} | no-more | display set | match dynamic-profile | match \"ranges ([0-9]+(-[0-9]+)?)\"" : 'show configuration interfaces | no-more | display set | match dynamic-profile | match "ranges ([0-9]+(-[0-9]+)?)"'
|
362
|
-
command_demux = interface ? "show configuration interfaces #{interface} | display set | match unnumbered-address" : 'show configuration interfaces | display set | match unnumbered-address'
|
363
|
-
command_another = interface ? "show configuration interfaces #{interface} | display set | match vlan-id" : 'show configuration interfaces | display set | match vlan-id'
|
364
|
-
|
365
|
-
full_cmd = "#{ssh_command} '#{command_ranges}'"
|
366
|
-
result = `#{full_cmd}`.strip
|
367
|
-
unless result.empty?
|
368
|
-
result.each_line do |line|
|
369
|
-
if line =~ /ranges (\d+)(?:-(\d+))?/
|
370
|
-
start_range = $1.to_i
|
371
|
-
end_range = $2 ? $2.to_i : $1.to_i
|
372
|
-
ranges.add_range(start_range, end_range)
|
373
|
-
end
|
374
|
-
end
|
375
|
-
end
|
376
|
-
|
377
|
-
full_cmd = "#{ssh_command} '#{command_demux}'"
|
378
|
-
result = `#{full_cmd}`.strip
|
379
|
-
unless result.empty?
|
380
|
-
result.each_line do |line|
|
381
|
-
if line =~ /unit (\d+)/
|
382
|
-
start_range = $1.to_i
|
383
|
-
if start_range > 0
|
384
|
-
end_range = start_range
|
385
|
-
ranges.add_range(start_range, end_range)
|
386
|
-
end
|
387
|
-
end
|
388
|
-
end
|
389
|
-
end
|
390
|
-
|
391
|
-
full_cmd = "#{ssh_command} '#{command_another}'"
|
392
|
-
result = `#{full_cmd}`.strip
|
393
|
-
unless result.empty?
|
394
|
-
result.each_line do |line|
|
395
|
-
if line =~ /vlan-id (\d+)/
|
396
|
-
start_range = $1.to_i
|
397
|
-
if start_range > 0
|
398
|
-
end_range = start_range
|
399
|
-
ranges.add_another_range(start_range, end_range)
|
400
|
-
end
|
401
|
-
end
|
402
|
-
end
|
403
|
-
end
|
404
|
-
end
|
405
|
-
|
406
|
-
# Основна логіка виконання
|
407
|
-
def self.run
|
408
|
-
options = {}
|
409
|
-
OptionParser.new do |opts|
|
410
|
-
opts.banner = "Використання: free-range <IP-адреса або hostname> [опції]"
|
411
|
-
opts.on("-u", "--username USERNAME", "Ім'я користувача для SSH") { |u| options[:username] = u }
|
412
|
-
opts.on("-p", "--password PASSWORD", "Пароль для SSH") { |p| options[:password] = p }
|
413
|
-
opts.on("-n", "--no-color", "Вимкнути кольоровий вивід") { options[:no_color] = true }
|
414
|
-
opts.on("-d", "--debug", "Увімкнути дебаг-режим") { options[:debug] = true }
|
415
|
-
opts.on("-t", "--table", "Вивести діаграму розподілу VLAN-ів") { options[:table] = true }
|
416
|
-
opts.on("-g", "--table-png PATH", "Зберегти діаграму розподілу VLAN-ів як PNG") { |path| options[:table_png] = path }
|
417
|
-
opts.on("-i", "--interface INTERFACE", "Назва інтерфейсу або 'all'") { |i| options[:interface] = i }
|
418
|
-
end.parse!
|
419
|
-
|
420
|
-
if ARGV.empty?
|
421
|
-
puts "Помилка: потрібно вказати IP-адресу або hostname роутера."
|
422
|
-
puts "Використання: free-range <IP-адреса або hostname> [-u|--username USERNAME] [-p|--password PASSWORD] [-n|--no-color] [-d|--debug] [-t|--table] [--table-png PATH] [-i|--interface INTERFACE]"
|
423
|
-
exit 1
|
424
|
-
end
|
425
|
-
|
426
|
-
username = options[:username] || ENV['WHOAMI']
|
427
|
-
password = options[:password] || ENV['WHATISMYPASSWD']
|
428
|
-
if username.nil? || password.nil?
|
429
|
-
puts "Помилка: необхідно вказати ім'я користувача та пароль."
|
430
|
-
puts "Використовуйте опції -u|--username і -p|--password або змінні оточення WHOAMI і WHATISMYPASSWD."
|
431
|
-
exit 1
|
432
|
-
end
|
433
|
-
|
434
|
-
use_color = !options[:no_color] && ENV['TERM'] && ENV['TERM'] != 'dumb'
|
435
|
-
debug = options[:debug]
|
436
|
-
table_mode = options[:table]
|
437
|
-
table_png_mode = options[:table_png]
|
438
|
-
interface = options[:interface]
|
439
|
-
|
440
|
-
# # Перевірка формату інтерфейсу
|
441
|
-
# if interface && interface != "all" && interface !~ /^[a-z]+-\d+\/\d+\/\d+$/
|
442
|
-
# puts "Помилка: некоректна назва інтерфейсу. Використовуйте формат 'xe-0/0/2' або 'all'."
|
443
|
-
# exit 1
|
444
|
-
# end
|
445
|
-
|
446
|
-
login = { target: ARGV[0], username: username, password: password }
|
447
|
-
target = ARGV[0].split('.')[0]
|
448
|
-
puts "Connecting to device: #{login[:target]}"
|
449
|
-
|
450
|
-
ssh_command = "sshpass -p \"#{login[:password]}\" ssh -C -x -4 -o StrictHostKeyChecking=no #{login[:username]}@#{login[:target]}"
|
451
|
-
subscribers_command = "ssh -C -x roffice /usr/local/share/noc/bin/radius-subscribers"
|
452
|
-
|
453
|
-
subscribers_result = `#{subscribers_command}`.strip
|
454
|
-
if subscribers_result.empty?
|
455
|
-
puts "Помилка: результат subscribers_command порожній. Перевір шлях або доступ."
|
456
|
-
exit 1
|
457
|
-
end
|
458
|
-
|
459
|
-
if interface == "all"
|
460
|
-
command_interfaces = 'show configuration interfaces | no-more | display set | match dynamic-profile | match "ranges ([0-9]+(-[0-9]+)?)"'
|
461
|
-
full_cmd = "#{ssh_command} '#{command_interfaces}'"
|
462
|
-
result = `#{full_cmd}`.strip
|
463
|
-
if result.empty?
|
464
|
-
puts "Помилка: результат команди порожній. Перевір підключення або команду."
|
465
|
-
exit 1
|
466
|
-
end
|
467
|
-
|
468
|
-
interfaces = result.each_line.map { |line| line.split[2] }.uniq
|
469
|
-
if interfaces.empty?
|
470
|
-
puts "Помилка: не знайдено інтерфейсів із діапазонами."
|
471
|
-
exit 1
|
472
|
-
end
|
473
|
-
|
474
|
-
interfaces.each do |intf|
|
475
|
-
ranges = Ranges.new
|
476
|
-
vlans = Vlans.new
|
477
|
-
subscribers_result.each_line do |line|
|
478
|
-
if line.split.first =~ /dhcp(?:_[0-9a-fA-F.]+)?_([^:]+):(\d+)@#{Regexp.escape(target)}$/
|
479
|
-
subscriber_interface, vlan = $1, $2.to_i
|
480
|
-
vlans.add_vlan(vlan) if subscriber_interface == intf && vlan > 0
|
481
|
-
end
|
482
|
-
end
|
483
|
-
|
484
|
-
process_interface(ssh_command, intf, ranges)
|
485
|
-
if debug
|
486
|
-
puts "\nІнтерфейс: #{intf}"
|
487
|
-
Print.ranged(ranges)
|
488
|
-
Print.vlans(vlans)
|
489
|
-
Print.vlan_ranges(vlans)
|
490
|
-
puts
|
491
|
-
end
|
492
|
-
if table_png_mode
|
493
|
-
Print.table_png(ranges, vlans, table_png_mode, target, intf)
|
494
|
-
elsif table_mode
|
495
|
-
Print.table(ranges, vlans, use_color, target, intf)
|
496
|
-
else
|
497
|
-
Print.combined_ranges(ranges, vlans, use_color, target, intf)
|
498
|
-
end
|
499
|
-
end
|
500
|
-
else
|
501
|
-
ranges = Ranges.new
|
502
|
-
vlans = Vlans.new
|
503
|
-
subscribers_result.each_line do |line|
|
504
|
-
if line.split.first =~ /dhcp(?:_[0-9a-fA-F.]+)?_([^:]+):(\d+)@#{Regexp.escape(target)}$/
|
505
|
-
subscriber_interface, vlan = $1, $2.to_i
|
506
|
-
if interface
|
507
|
-
vlans.add_vlan(vlan) if subscriber_interface == interface && vlan > 0
|
508
|
-
else
|
509
|
-
vlans.add_vlan(vlan) if vlan > 0
|
510
|
-
end
|
511
|
-
end
|
512
|
-
end
|
513
|
-
|
514
|
-
process_interface(ssh_command, interface, ranges)
|
515
|
-
if debug
|
516
|
-
puts "\nІнтерфейс: #{interface}" if interface
|
517
|
-
Print.ranged(ranges)
|
518
|
-
Print.vlans(vlans)
|
519
|
-
Print.vlan_ranges(vlans)
|
520
|
-
puts
|
521
|
-
end
|
522
|
-
if table_png_mode
|
523
|
-
Print.table_png(ranges, vlans, table_png_mode, target, interface)
|
524
|
-
elsif table_mode
|
525
|
-
Print.table(ranges, vlans, use_color, target, interface)
|
526
|
-
else
|
527
|
-
Print.combined_ranges(ranges, vlans, use_color, target, interface)
|
528
|
-
end
|
529
|
-
end
|
530
|
-
end
|
531
|
-
end
|