free-range 0.1.2 → 0.2.0
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 +171 -31
- data/lib/free-range.rb.~ +237 -196
- data/lib/free-range.rb~ +531 -0
- metadata +16 -2
- data/bin/free-range.~ +0 -6
data/lib/free-range.rb.~
CHANGED
@@ -1,68 +1,60 @@
|
|
1
|
-
|
2
|
-
|
3
|
-
|
4
|
-
require 'rmagick'
|
5
|
-
require 'fileutils'
|
1
|
+
require 'optparse'
|
2
|
+
require 'rmagick'
|
3
|
+
require 'fileutils'
|
6
4
|
|
7
|
-
|
8
|
-
|
9
|
-
|
10
|
-
|
11
|
-
|
12
|
-
|
13
|
-
|
14
|
-
|
5
|
+
module FreeRange
|
6
|
+
# Перевірка наявності ImageMagick
|
7
|
+
begin
|
8
|
+
require 'rmagick'
|
9
|
+
rescue LoadError
|
10
|
+
puts "Помилка: бібліотека rmagick не встановлена або ImageMagick недоступний."
|
11
|
+
puts "Встановіть ImageMagick і виконайте: gem install rmagick"
|
12
|
+
exit 1
|
13
|
+
end
|
15
14
|
|
16
|
-
|
17
|
-
|
18
|
-
|
15
|
+
# Клас для зберігання конфігураційних команд
|
16
|
+
class Config
|
17
|
+
attr_accessor :ssh_command, :subscribers_command
|
19
18
|
|
20
|
-
|
21
|
-
|
19
|
+
# Ініціалізує об’єкт конфігурації з значеннями за замовчуванням
|
20
|
+
# @param login [Hash] Hash with target, username, and password
|
21
|
+
# @yield [self] Yields self for block-based configuration
|
22
|
+
def initialize(login)
|
23
|
+
@login = login
|
24
|
+
# Значення за замовчуванням
|
25
|
+
@ssh_command = "sshpass -p \"#{@login[:password]}\" ssh -C -x -4 -o StrictHostKeyChecking=no #{@login[:username]}@#{@login[:target]}"
|
26
|
+
@subscribers_command = "ssh -C -x roffice /usr/local/share/noc/bin/radius-subscribers"
|
27
|
+
yield self if block_given?
|
22
28
|
end
|
23
29
|
|
24
|
-
|
25
|
-
|
30
|
+
# Повертає команду для отримання списку інтерфейсів
|
31
|
+
# @return [String] Command to fetch interfaces
|
32
|
+
def command_interfaces
|
33
|
+
'show configuration interfaces | no-more | display set | match dynamic-profile | match "ranges ([0-9]+(-[0-9]+)?)"'
|
26
34
|
end
|
27
35
|
|
28
|
-
|
29
|
-
|
36
|
+
# Повертає команду для отримання діапазонів VLAN
|
37
|
+
# @param interface [String, nil] Interface name or nil for all interfaces
|
38
|
+
# @return [String] Command to fetch VLAN ranges
|
39
|
+
def command_ranges(interface)
|
40
|
+
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]+)?)"'
|
30
41
|
end
|
31
42
|
|
32
|
-
|
33
|
-
|
43
|
+
# Повертає команду для отримання демультиплексорних VLAN
|
44
|
+
# @param interface [String, nil] Interface name or nil for all interfaces
|
45
|
+
# @return [String] Command to fetch demux VLANs
|
46
|
+
def command_demux(interface)
|
47
|
+
interface ? "show configuration interfaces #{interface} | display set | match unnumbered-address" : 'show configuration interfaces | display set | match unnumbered-address'
|
34
48
|
end
|
35
49
|
|
36
|
-
|
37
|
-
|
50
|
+
# Повертає команду для отримання інших VLAN
|
51
|
+
# @param interface [String, nil] Interface name or nil for all interfaces
|
52
|
+
# @return [String] Command to fetch other VLANs
|
53
|
+
def command_another(interface)
|
54
|
+
interface ? "show configuration interfaces #{interface} | display set | match vlan-id" : 'show configuration interfaces | display set | match vlan-id'
|
38
55
|
end
|
39
|
-
end.parse!
|
40
|
-
|
41
|
-
# Перевіряємо, чи передано IP-адресу або hostname
|
42
|
-
if ARGV.empty?
|
43
|
-
puts "Помилка: потрібно вказати IP-адресу або hostname роутера як аргумент командного рядка."
|
44
|
-
puts "Використання: ruby free-range.rb <IP-адреса або hostname> [-u|--username USERNAME] [-p|--password PASSWORD] [-n|--no-color] [-d|--debug] [-t|--table] [--table-png PATH] [-i|--interface INTERFACE]"
|
45
|
-
exit 1
|
46
|
-
end
|
47
|
-
|
48
|
-
# Визначаємо облікові дані
|
49
|
-
username = options[:username] || ENV['WHOAMI']
|
50
|
-
password = options[:password] || ENV['WHATISMYPASSWD']
|
51
|
-
|
52
|
-
# Перевіряємо наявність облікових даних
|
53
|
-
if username.nil? || password.nil?
|
54
|
-
puts "Помилка: необхідно вказати ім'я користувача та пароль."
|
55
|
-
puts "Використовуйте опції -u|--username і -p|--password або змінні оточення WHOAMI і WHATISMYPASSWD."
|
56
|
-
exit 1
|
57
56
|
end
|
58
57
|
|
59
|
-
# Визначаємо, чи використовувати кольори та дебаг-режим
|
60
|
-
use_color = !options[:no_color] && ENV['TERM'] && ENV['TERM'] != 'dumb'
|
61
|
-
debug = options[:debug]
|
62
|
-
table_mode = options[:table]
|
63
|
-
table_png_mode = options[:table_png]
|
64
|
-
interface = options[:interface]
|
65
|
-
|
66
58
|
# Абстрактний клас для роботи з VLAN
|
67
59
|
class VlanContainer
|
68
60
|
def initialize
|
@@ -70,16 +62,20 @@ module FreeRange
|
|
70
62
|
end
|
71
63
|
|
72
64
|
# Додаємо VLAN до списку (віртуальний метод, може бути перевизначений)
|
65
|
+
# @param vlan [Integer] VLAN ID to add
|
66
|
+
# @return [void]
|
73
67
|
def add_vlan(vlan)
|
74
68
|
@vlans << vlan
|
75
69
|
end
|
76
70
|
|
77
71
|
# Повертаємо масив VLAN
|
72
|
+
# @return [Array<Integer>] Sorted array of unique VLAN IDs
|
78
73
|
def vlans
|
79
74
|
@vlans.uniq.sort
|
80
75
|
end
|
81
76
|
|
82
77
|
# Створюємо хеш діапазонів із VLAN
|
78
|
+
# @return [Hash{Integer => Integer}] Hash mapping range start to range end
|
83
79
|
def ranges
|
84
80
|
return {} if @vlans.empty?
|
85
81
|
|
@@ -109,16 +105,23 @@ module FreeRange
|
|
109
105
|
end
|
110
106
|
|
111
107
|
# Додаємо діапазон до списку VLAN-ів
|
108
|
+
# @param start [Integer] Start of VLAN range
|
109
|
+
# @param finish [Integer] End of VLAN range
|
110
|
+
# @return [void]
|
112
111
|
def add_range(start, finish)
|
113
112
|
(start..finish).each { |vlan| @vlans << vlan }
|
114
113
|
end
|
115
114
|
|
116
115
|
# Додаємо діапазон до списку "інших" VLAN-ів
|
116
|
+
# @param start [Integer] Start of another VLAN range
|
117
|
+
# @param finish [Integer] End of another VLAN range
|
118
|
+
# @return [void]
|
117
119
|
def add_another_range(start, finish)
|
118
120
|
(start..finish).each { |vlan| @another_in_ranges << vlan }
|
119
121
|
end
|
120
122
|
|
121
123
|
# Повертаємо масив "інших" VLAN-ів
|
124
|
+
# @return [Array<Integer>] Sorted array of unique "another" VLAN IDs
|
122
125
|
def another_in_ranges
|
123
126
|
@another_in_ranges.uniq.sort
|
124
127
|
end
|
@@ -134,6 +137,9 @@ module FreeRange
|
|
134
137
|
|
135
138
|
# Клас для виводу даних
|
136
139
|
class Print
|
140
|
+
# Виводить хеш діапазонів VLAN у порядку зростання
|
141
|
+
# @param ranges [Ranges] Object containing VLAN ranges
|
142
|
+
# @return [void]
|
137
143
|
def self.ranged(ranges)
|
138
144
|
puts "\nСформований хеш діапазонів (в порядку зростання):"
|
139
145
|
if ranges.ranges.empty?
|
@@ -145,6 +151,9 @@ module FreeRange
|
|
145
151
|
end
|
146
152
|
end
|
147
153
|
|
154
|
+
# Виводить зайняті VLAN-и в межах діапазонів у порядку зростання
|
155
|
+
# @param vlans [Vlans] Object containing VLANs
|
156
|
+
# @return [void]
|
148
157
|
def self.vlans(vlans)
|
149
158
|
puts "\nЗайняті VLAN-и в межах діапазонів (в порядку зростання):"
|
150
159
|
if vlans.vlans.empty?
|
@@ -154,6 +163,9 @@ module FreeRange
|
|
154
163
|
end
|
155
164
|
end
|
156
165
|
|
166
|
+
# Виводить діапазони VLAN-ів у порядку зростання
|
167
|
+
# @param vlans [Vlans] Object containing VLANs
|
168
|
+
# @return [void]
|
157
169
|
def self.vlan_ranges(vlans)
|
158
170
|
puts "\nДіапазони VLAN-ів (в порядку зростання):"
|
159
171
|
vlan_ranges = vlans.vlan_ranges
|
@@ -166,6 +178,13 @@ module FreeRange
|
|
166
178
|
end
|
167
179
|
end
|
168
180
|
|
181
|
+
# Виводить комбіновані діапазони VLAN зі статусами
|
182
|
+
# @param ranges [Ranges] Object containing VLAN ranges
|
183
|
+
# @param vlans [Vlans] Object containing VLANs
|
184
|
+
# @param use_color [Boolean] Enable colored output
|
185
|
+
# @param target [String] Target device hostname
|
186
|
+
# @param interface [String, nil] Interface name or nil
|
187
|
+
# @return [void]
|
169
188
|
def self.combined_ranges(ranges, vlans, use_color, target, interface = nil)
|
170
189
|
if ranges.ranges.empty?
|
171
190
|
puts "Не знайдено діапазонів."
|
@@ -173,8 +192,6 @@ module FreeRange
|
|
173
192
|
end
|
174
193
|
|
175
194
|
all_vlans, _status_counts = build_vlan_statuses(ranges, vlans)
|
176
|
-
|
177
|
-
# Формуємо діапазони з урахуванням статусів
|
178
195
|
result = []
|
179
196
|
sorted_vlans = all_vlans.keys.sort
|
180
197
|
start = sorted_vlans.first
|
@@ -183,24 +200,28 @@ module FreeRange
|
|
183
200
|
|
184
201
|
sorted_vlans[1..-1].each do |vlan|
|
185
202
|
unless vlan == prev + 1 && all_vlans[vlan] == status
|
186
|
-
# Завершуємо попередній діапазон
|
187
203
|
result << format_range(start, prev, status, use_color)
|
188
204
|
start = vlan
|
189
205
|
status = all_vlans[vlan]
|
190
206
|
end
|
191
207
|
prev = vlan
|
192
208
|
end
|
193
|
-
# Додаємо останній діапазон
|
194
209
|
result << format_range(start, prev, status, use_color)
|
195
210
|
|
196
211
|
puts result.join(',')
|
197
212
|
end
|
198
213
|
|
214
|
+
# Виводить таблицю розподілу VLAN
|
215
|
+
# @param ranges [Ranges] Object containing VLAN ranges
|
216
|
+
# @param vlans [Vlans] Object containing VLANs
|
217
|
+
# @param use_color [Boolean] Enable colored output
|
218
|
+
# @param target [String] Target device hostname
|
219
|
+
# @param interface [String, nil] Interface name or nil
|
220
|
+
# @return [void]
|
199
221
|
def self.table(ranges, vlans, use_color, target, interface = nil)
|
200
222
|
puts "VLAN Distribution for #{target}#{interface ? " (#{interface})" : ''}"
|
201
223
|
all_vlans, status_counts = build_vlan_statuses(ranges, vlans)
|
202
224
|
|
203
|
-
# Виводимо заголовок
|
204
225
|
puts " 0 1 2 3 4 5 6 7 8 9 "
|
205
226
|
(0..40).each do |h|
|
206
227
|
start_vlan = h * 100
|
@@ -209,7 +230,6 @@ module FreeRange
|
|
209
230
|
puts "#{format("%4d", start_vlan)} #{row}"
|
210
231
|
end
|
211
232
|
|
212
|
-
# Виводимо легенду
|
213
233
|
legend_parts = [
|
214
234
|
["Legend: ", nil],
|
215
235
|
["f", 'f'], ["=free", nil], [", ", nil],
|
@@ -228,7 +248,6 @@ module FreeRange
|
|
228
248
|
end.join
|
229
249
|
puts "\n#{legend_text}"
|
230
250
|
|
231
|
-
# Виводимо підсумок
|
232
251
|
summary_parts = [
|
233
252
|
["Total: ", nil],
|
234
253
|
["f", 'f'], ["=#{status_counts['f']}", nil], [", ", nil],
|
@@ -248,10 +267,15 @@ module FreeRange
|
|
248
267
|
puts summary_text
|
249
268
|
end
|
250
269
|
|
270
|
+
# Зберігає таблицю розподілу VLAN як PNG-зображення
|
271
|
+
# @param ranges [Ranges] Object containing VLAN ranges
|
272
|
+
# @param vlans [Vlans] Object containing VLANs
|
273
|
+
# @param path [String] Directory path to save the PNG
|
274
|
+
# @param target [String] Target device hostname
|
275
|
+
# @param interface [String, nil] Interface name or nil
|
276
|
+
# @return [void]
|
251
277
|
def self.table_png(ranges, vlans, path, target, interface = nil)
|
252
278
|
all_vlans, status_counts = build_vlan_statuses(ranges, vlans)
|
253
|
-
|
254
|
-
# Налаштування розмірів і стилів
|
255
279
|
cell_width = 12
|
256
280
|
cell_height = 20
|
257
281
|
rows = 41
|
@@ -259,42 +283,35 @@ module FreeRange
|
|
259
283
|
header_height = 60
|
260
284
|
label_width = 50
|
261
285
|
width = label_width + cols * cell_width + 10
|
262
|
-
height = header_height + rows * cell_height + 20 + 50
|
286
|
+
height = header_height + rows * cell_height + 20 + 50
|
263
287
|
font_size = 14
|
264
|
-
title_font_size = 18
|
288
|
+
title_font_size = 18
|
265
289
|
font = 'Courier'
|
266
290
|
|
267
|
-
# Створюємо полотно
|
268
291
|
canvas = Magick::Image.new(width, height) { |options| options.background_color = 'white' }
|
269
292
|
gc = Magick::Draw.new
|
270
293
|
gc.font = font
|
271
294
|
gc.pointsize = font_size
|
272
295
|
gc.text_antialias = true
|
273
296
|
|
274
|
-
# Малюємо заголовок із назвою пристрою
|
275
297
|
gc.fill('black')
|
276
298
|
gc.pointsize = title_font_size
|
277
299
|
gc.text(10, 25, "VLAN Distribution for #{target}#{interface ? " (#{interface})" : ''}")
|
278
|
-
gc.pointsize = font_size
|
300
|
+
gc.pointsize = font_size
|
279
301
|
|
280
|
-
# Малюємо заголовок (0 1 2 ... 9)
|
281
302
|
(0..9).each do |i|
|
282
303
|
x = label_width + i * 10 * cell_width - 3
|
283
304
|
gc.fill('black')
|
284
305
|
gc.text(x + 5, header_height - 5, i.to_s)
|
285
306
|
end
|
286
307
|
|
287
|
-
# Малюємо таблицю
|
288
308
|
(0..40).each do |h|
|
289
309
|
start_vlan = h * 100
|
290
310
|
end_vlan = [start_vlan + 99, 4094].min
|
291
311
|
y = header_height + h * cell_height
|
292
|
-
|
293
|
-
# Малюємо номер рядка
|
294
312
|
gc.fill('black')
|
295
313
|
gc.text(5, y + font_size, format("%4d", start_vlan))
|
296
314
|
|
297
|
-
# Малюємо клітинки
|
298
315
|
(start_vlan..end_vlan).each_with_index do |vlan, i|
|
299
316
|
status = all_vlans[vlan] || ' '
|
300
317
|
x = label_width + i * cell_width
|
@@ -314,7 +331,6 @@ module FreeRange
|
|
314
331
|
end
|
315
332
|
end
|
316
333
|
|
317
|
-
# Малюємо легенду з кольоровими фонами
|
318
334
|
legend_y = height - 50
|
319
335
|
x = 10
|
320
336
|
legend_parts = [
|
@@ -336,11 +352,10 @@ module FreeRange
|
|
336
352
|
else
|
337
353
|
gc.fill('black')
|
338
354
|
gc.text(x, legend_y, text)
|
339
|
-
x += text.length * 8
|
355
|
+
x += text.length * 8
|
340
356
|
end
|
341
357
|
end
|
342
358
|
|
343
|
-
# Малюємо підсумок VLAN-ів з кольоровими фонами
|
344
359
|
summary_y = height - 30
|
345
360
|
x = 10
|
346
361
|
summary_parts = [
|
@@ -362,11 +377,10 @@ module FreeRange
|
|
362
377
|
else
|
363
378
|
gc.fill('black')
|
364
379
|
gc.text(x, summary_y, text)
|
365
|
-
x += text.length * 8
|
380
|
+
x += text.length * 8
|
366
381
|
end
|
367
382
|
end
|
368
383
|
|
369
|
-
# Зберігаємо зображення
|
370
384
|
gc.draw(canvas)
|
371
385
|
FileUtils.mkdir_p(path) unless Dir.exist?(path)
|
372
386
|
filename = File.join(path, "free-range-#{target}#{interface ? "-#{interface.tr('/', '-')}" : ''}.png")
|
@@ -376,68 +390,62 @@ module FreeRange
|
|
376
390
|
|
377
391
|
private
|
378
392
|
|
393
|
+
# Будує хеш статусів VLAN і підраховує кількість кожного статусу
|
394
|
+
# @param ranges [Ranges] Object containing VLAN ranges
|
395
|
+
# @param vlans [Vlans] Object containing VLANs
|
396
|
+
# @return [Array<Hash, Hash>] Hash of VLAN statuses and status counts
|
379
397
|
def self.build_vlan_statuses(ranges, vlans)
|
380
398
|
all_vlans = {}
|
381
|
-
# Позначаємо VLAN із діапазонів як free
|
382
399
|
ranges.ranges.each do |start, finish|
|
383
400
|
(start..finish).each { |vlan| all_vlans[vlan] = 'f' }
|
384
401
|
end
|
385
|
-
# Оновлюємо статуси для зайнятих VLAN
|
386
402
|
vlans.vlans.uniq.each { |vlan| all_vlans[vlan] = all_vlans.key?(vlan) ? 'b' : 'e' }
|
387
|
-
# Позначаємо всі інші VLAN як unused
|
388
403
|
(1..4094).each { |vlan| all_vlans[vlan] = 'u' unless all_vlans.key?(vlan) }
|
389
|
-
# Оновлюємо статуси для "інших" VLAN
|
390
404
|
ranges.another_in_ranges.each { |vlan| all_vlans[vlan] = all_vlans.key?(vlan) && all_vlans[vlan] != 'u' ? 'c' : 'a' }
|
391
|
-
# Підраховуємо статуси
|
392
405
|
status_counts = { 'f' => 0, 'b' => 0, 'e' => 0, 'c' => 0, 'a' => 0, 'u' => 0 }
|
393
406
|
all_vlans.each_value { |status| status_counts[status] += 1 }
|
394
407
|
|
395
408
|
[all_vlans, status_counts]
|
396
409
|
end
|
397
410
|
|
411
|
+
# Форматує діапазон VLAN зі статусом для виводу
|
412
|
+
# @param start [Integer] Start of VLAN range
|
413
|
+
# @param finish [Integer] End of VLAN range
|
414
|
+
# @param status [String] VLAN status ('f', 'b', 'e', 'c', 'a', 'u')
|
415
|
+
# @param use_color [Boolean] Enable colored output
|
416
|
+
# @return [String] Formatted range string
|
398
417
|
def self.format_range(start, finish, status, use_color)
|
399
418
|
range_text = start == finish ? "#{start}" : "#{start}-#{finish}"
|
400
419
|
range_text_with_status = "#{range_text}(#{status})"
|
401
|
-
|
402
420
|
if use_color
|
403
421
|
case status
|
404
|
-
when 'f'
|
405
|
-
|
406
|
-
when '
|
407
|
-
|
408
|
-
when 'e
|
409
|
-
|
410
|
-
|
411
|
-
"\e[35m#{range_text}\e[0m" # Фіолетовий для configured
|
412
|
-
when 'a'
|
413
|
-
"\e[34m#{range_text}\e[0m" # Синій для another
|
414
|
-
when 'u'
|
415
|
-
"\e[90m#{range_text}\e[0m" # Темно-сірий для unused
|
416
|
-
else
|
417
|
-
range_text # Без кольору для інших статусів
|
422
|
+
when 'f' then "\e[32m#{range_text}\e[0m" # Зелений для free
|
423
|
+
when 'b' then "\e[33m#{range_text}\e[0m" # Жовтий для busy
|
424
|
+
when 'e' then "\e[31m#{range_text}\e[0m" # Червоний для error
|
425
|
+
when 'c' then "\e[35m#{range_text}\e[0m" # Фіолетовий для configured
|
426
|
+
when 'a' then "\e[34m#{range_text}\e[0m" # Синій для another
|
427
|
+
when 'u' then "\e[90m#{range_text}\e[0m" # Темно-сірий для unused
|
428
|
+
else range_text # Без кольору для інших статусів
|
418
429
|
end
|
419
430
|
else
|
420
431
|
range_text_with_status # Текстовий вивід зі статусами
|
421
432
|
end
|
422
433
|
end
|
423
434
|
|
435
|
+
# Форматує символ для таблиці VLAN
|
436
|
+
# @param status [String] VLAN status ('f', 'b', 'e', 'c', 'a', 'u', or ' ')
|
437
|
+
# @param use_color [Boolean] Enable colored output
|
438
|
+
# @return [String] Formatted character for table display
|
424
439
|
def self.format_table_char(status, use_color)
|
425
440
|
if use_color
|
426
441
|
case status
|
427
|
-
when 'f'
|
428
|
-
|
429
|
-
when '
|
430
|
-
|
431
|
-
when 'e
|
432
|
-
|
433
|
-
|
434
|
-
"\e[48;5;5m\e[30m#{status}\e[0m" # Фіолетовий фон, чорний текст
|
435
|
-
when 'a'
|
436
|
-
"\e[48;5;4m\e[30m#{status}\e[0m" # Синій фон, чорний текст
|
437
|
-
when 'u'
|
438
|
-
"\e[48;5;8m\e[30m#{status}\e[0m" # Темно-сірий фон, чорний текст
|
439
|
-
else
|
440
|
-
status # Без кольору для інших статусів
|
442
|
+
when 'f' then "\e[48;5;2m\e[30m#{status}\e[0m" # Зелений фон, чорний текст
|
443
|
+
when 'b' then "\e[48;5;3m\e[30m#{status}\e[0m" # Жовтий фон, чорний текст
|
444
|
+
when 'e' then "\e[48;5;1m\e[30m#{status}\e[0m" # Червоний фон, чорний текст
|
445
|
+
when 'c' then "\e[48;5;5m\e[30m#{status}\e[0m" # Фіолетовий фон, чорний текст
|
446
|
+
when 'a' then "\e[48;5;4m\e[30m#{status}\e[0m" # Синій фон, чорний текст
|
447
|
+
when 'u' then "\e[48;5;8m\e[30m#{status}\e[0m" # Темно-сірий фон, чорний текст
|
448
|
+
else status # Без кольору для інших статусів
|
441
449
|
end
|
442
450
|
else
|
443
451
|
status # Текстовий вивід без кольорів
|
@@ -445,25 +453,14 @@ module FreeRange
|
|
445
453
|
end
|
446
454
|
end
|
447
455
|
|
448
|
-
#
|
449
|
-
|
450
|
-
|
451
|
-
|
452
|
-
|
453
|
-
|
454
|
-
|
455
|
-
subscribers_command = "ssh -C -x roffice /usr/local/share/noc/bin/radius-subscribers"
|
456
|
-
|
457
|
-
# Функція для заповнення Ranges для одного інтерфейсу
|
458
|
-
def process_interface(ssh_command, interface, ranges)
|
459
|
-
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]+)?)"'
|
460
|
-
command_demux = interface ? "show configuration interfaces #{interface} | display set | match unnumbered-address" : 'show configuration interfaces | display set | match unnumbered-address'
|
461
|
-
command_another = interface ? "show configuration interfaces #{interface} | display set | match vlan-id" : 'show configuration interfaces | display set | match vlan-id'
|
462
|
-
|
463
|
-
# Виконуємо команду для отримання діапазонів
|
464
|
-
full_cmd = "#{ssh_command} '#{command_ranges}'"
|
456
|
+
# Метод для заповнення Ranges для одного інтерфейсу
|
457
|
+
# @param config [Config] Configuration object with commands
|
458
|
+
# @param interface [String, nil] Interface name or nil for all interfaces
|
459
|
+
# @param ranges [Ranges] Object to store VLAN ranges
|
460
|
+
# @return [void]
|
461
|
+
def self.process_interface(config, interface, ranges)
|
462
|
+
full_cmd = "#{config.ssh_command} '#{config.command_ranges(interface)}'"
|
465
463
|
result = `#{full_cmd}`.strip
|
466
|
-
|
467
464
|
unless result.empty?
|
468
465
|
result.each_line do |line|
|
469
466
|
if line =~ /ranges (\d+)(?:-(\d+))?/
|
@@ -474,10 +471,8 @@ module FreeRange
|
|
474
471
|
end
|
475
472
|
end
|
476
473
|
|
477
|
-
|
478
|
-
full_cmd = "#{ssh_command} '#{command_demux}'"
|
474
|
+
full_cmd = "#{config.ssh_command} '#{config.command_demux(interface)}'"
|
479
475
|
result = `#{full_cmd}`.strip
|
480
|
-
|
481
476
|
unless result.empty?
|
482
477
|
result.each_line do |line|
|
483
478
|
if line =~ /unit (\d+)/
|
@@ -490,10 +485,8 @@ module FreeRange
|
|
490
485
|
end
|
491
486
|
end
|
492
487
|
|
493
|
-
|
494
|
-
full_cmd = "#{ssh_command} '#{command_another}'"
|
488
|
+
full_cmd = "#{config.ssh_command} '#{config.command_another(interface)}'"
|
495
489
|
result = `#{full_cmd}`.strip
|
496
|
-
|
497
490
|
unless result.empty?
|
498
491
|
result.each_line do |line|
|
499
492
|
if line =~ /vlan-id (\d+)/
|
@@ -507,94 +500,142 @@ module FreeRange
|
|
507
500
|
end
|
508
501
|
end
|
509
502
|
|
510
|
-
#
|
511
|
-
|
512
|
-
|
513
|
-
|
514
|
-
|
515
|
-
|
516
|
-
|
517
|
-
|
518
|
-
|
519
|
-
|
520
|
-
|
521
|
-
|
522
|
-
|
503
|
+
# Основна логіка виконання
|
504
|
+
# @return [void]
|
505
|
+
def self.run
|
506
|
+
options = {}
|
507
|
+
OptionParser.new do |opts|
|
508
|
+
opts.banner = <<~BANNER
|
509
|
+
Використання: free-range <IP-адреса або hostname> [опції]
|
510
|
+
|
511
|
+
Аналізує розподіл VLAN на мережевих пристроях, генеруючи таблиці або PNG-зображення.
|
512
|
+
BANNER
|
513
|
+
opts.on("-h", "--help", "Показати цю довідку") { puts opts; exit 0 }
|
514
|
+
opts.on("-u", "--username USERNAME", "Ім'я користувача для SSH") { |u| options[:username] = u }
|
515
|
+
opts.on("-p", "--password PASSWORD", "Пароль для SSH") { |p| options[:password] = p }
|
516
|
+
opts.on("-n", "--no-color", "Вимкнути кольоровий вивід") { options[:no_color] = true }
|
517
|
+
opts.on("-d", "--debug", "Увімкнути дебаг-режим") { options[:debug] = true }
|
518
|
+
opts.on("-t", "--table", "Вивести діаграму розподілу VLAN-ів") { options[:table] = true }
|
519
|
+
opts.on("-g", "--table-png PATH", "Зберегти діаграму розподілу VLAN-ів як PNG") { |path| options[:table_png] = path }
|
520
|
+
opts.on("-i", "--interface INTERFACE", "Назва інтерфейсу або 'all'") { |i| options[:interface] = i }
|
521
|
+
opts.on("-c", "--config CONFIG_FILE", "Шлях до конфігураційного файлу") { |c| options[:config_file] = c }
|
522
|
+
end.parse!
|
523
|
+
|
524
|
+
if ARGV.empty?
|
525
|
+
puts "Помилка: потрібно вказати IP-адресу або hostname роутера."
|
526
|
+
puts "Використовуйте: free-range --help для довідки."
|
527
|
+
exit 1
|
528
|
+
end
|
523
529
|
|
524
|
-
|
525
|
-
|
530
|
+
username = options[:username] || ENV['WHOAMI']
|
531
|
+
password = options[:password] || ENV['WHATISMYPASSWD']
|
532
|
+
if username.nil? || password.nil?
|
533
|
+
puts "Помилка: необхідно вказати ім'я користувача та пароль."
|
534
|
+
puts "Використовуйте опції -u/--username і -p/--password або змінні оточення WHOAMI і WHATISMYPASSWD."
|
526
535
|
exit 1
|
527
536
|
end
|
528
537
|
|
529
|
-
|
530
|
-
|
531
|
-
|
538
|
+
use_color = !options[:no_color] && ENV['TERM'] && ENV['TERM'] != 'dumb'
|
539
|
+
debug = options[:debug]
|
540
|
+
table_mode = options[:table]
|
541
|
+
table_png_mode = options[:table_png]
|
542
|
+
interface = options[:interface]
|
543
|
+
config_file = options[:config_file]
|
544
|
+
|
545
|
+
login = { target: ARGV[0], username: username, password: password }
|
546
|
+
target = ARGV[0].split('.')[0]
|
547
|
+
puts "Connecting to device: #{login[:target]}"
|
548
|
+
|
549
|
+
# Ініціалізація конфігурації
|
550
|
+
config = Config.new(login)
|
551
|
+
if config_file
|
552
|
+
begin
|
553
|
+
load config_file
|
554
|
+
config = FreeRange::Config.new(login) { |c| eval(File.read(config_file), binding, config_file) }
|
555
|
+
rescue LoadError, Errno::ENOENT
|
556
|
+
puts "Помилка: неможливо завантажити конфігураційний файл '#{config_file}'."
|
557
|
+
exit 1
|
558
|
+
rescue StandardError => e
|
559
|
+
puts "Помилка в конфігураційному файлі '#{config_file}': #{e.message}"
|
560
|
+
exit 1
|
561
|
+
end
|
562
|
+
end
|
563
|
+
|
564
|
+
subscribers_result = `#{config.subscribers_command}`.strip
|
565
|
+
if subscribers_result.empty?
|
566
|
+
puts "Помилка: результат subscribers_command порожній. Перевір шлях або доступ."
|
532
567
|
exit 1
|
533
568
|
end
|
534
569
|
|
535
|
-
|
570
|
+
if interface == "all"
|
571
|
+
full_cmd = "#{config.ssh_command} '#{config.command_interfaces}'"
|
572
|
+
result = `#{full_cmd}`.strip
|
573
|
+
if result.empty?
|
574
|
+
puts "Помилка: результат команди порожній. Перевір підключення або команду."
|
575
|
+
exit 1
|
576
|
+
end
|
577
|
+
|
578
|
+
interfaces = result.each_line.map { |line| line.split[2] }.uniq
|
579
|
+
if interfaces.empty?
|
580
|
+
puts "Помилка: не знайдено інтерфейсів із діапазонами."
|
581
|
+
exit 1
|
582
|
+
end
|
583
|
+
|
584
|
+
interfaces.each do |intf|
|
585
|
+
ranges = Ranges.new
|
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
|
609
|
+
end
|
610
|
+
else
|
536
611
|
ranges = Ranges.new
|
537
612
|
vlans = Vlans.new
|
538
|
-
|
539
|
-
# Заповнюємо VLAN-и, фільтруючи за інтерфейсом
|
540
613
|
subscribers_result.each_line do |line|
|
541
614
|
if line.split.first =~ /dhcp(?:_[0-9a-fA-F.]+)?_([^:]+):(\d+)@#{Regexp.escape(target)}$/
|
542
615
|
subscriber_interface, vlan = $1, $2.to_i
|
543
|
-
|
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
|
544
621
|
end
|
545
622
|
end
|
546
623
|
|
547
|
-
process_interface(
|
548
|
-
|
549
|
-
# Виводимо результати
|
624
|
+
process_interface(config, interface, ranges)
|
550
625
|
if debug
|
551
|
-
puts "\nІнтерфейс: #{
|
626
|
+
puts "\nІнтерфейс: #{interface}" if interface
|
552
627
|
Print.ranged(ranges)
|
553
628
|
Print.vlans(vlans)
|
554
629
|
Print.vlan_ranges(vlans)
|
555
630
|
puts
|
556
631
|
end
|
557
632
|
if table_png_mode
|
558
|
-
Print.table_png(ranges, vlans, table_png_mode, target,
|
633
|
+
Print.table_png(ranges, vlans, table_png_mode, target, interface)
|
559
634
|
elsif table_mode
|
560
|
-
Print.table(ranges, vlans, use_color, target,
|
635
|
+
Print.table(ranges, vlans, use_color, target, interface)
|
561
636
|
else
|
562
|
-
Print.combined_ranges(ranges, vlans, use_color, target,
|
637
|
+
Print.combined_ranges(ranges, vlans, use_color, target, interface)
|
563
638
|
end
|
564
639
|
end
|
565
|
-
else
|
566
|
-
ranges = Ranges.new
|
567
|
-
vlans = Vlans.new
|
568
|
-
|
569
|
-
# Заповнюємо VLAN-и, фільтруючи за інтерфейсом, якщо задано
|
570
|
-
subscribers_result.each_line do |line|
|
571
|
-
if line.split.first =~ /dhcp(?:_[0-9a-fA-F.]+)?_([^:]+):(\d+)@#{Regexp.escape(target)}$/
|
572
|
-
subscriber_interface, vlan = $1, $2.to_i
|
573
|
-
if interface
|
574
|
-
vlans.add_vlan(vlan) if subscriber_interface == interface && vlan > 0
|
575
|
-
else
|
576
|
-
vlans.add_vlan(vlan) if vlan > 0
|
577
|
-
end
|
578
|
-
end
|
579
|
-
end
|
580
|
-
|
581
|
-
process_interface(ssh_command, interface, ranges)
|
582
|
-
|
583
|
-
# Виводимо результати
|
584
|
-
if debug
|
585
|
-
puts "\nІнтерфейс: #{interface}" if interface
|
586
|
-
Print.ranged(ranges)
|
587
|
-
Print.vlans(vlans)
|
588
|
-
Print.vlan_ranges(vlans)
|
589
|
-
puts
|
590
|
-
end
|
591
|
-
if table_png_mode
|
592
|
-
Print.table_png(ranges, vlans, table_png_mode, target, interface)
|
593
|
-
elsif table_mode
|
594
|
-
Print.table(ranges, vlans, use_color, target, interface)
|
595
|
-
else
|
596
|
-
Print.combined_ranges(ranges, vlans, use_color, target, interface)
|
597
|
-
end
|
598
640
|
end
|
599
|
-
|
600
641
|
end
|