free-range 0.1.2 → 0.1.3
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 +60 -7
- data/lib/{free-range.rb.~ → free-range.rb~} +129 -198
- metadata +2 -3
- data/bin/free-range.~ +0 -6
checksums.yaml
CHANGED
@@ -1,7 +1,7 @@
|
|
1
1
|
---
|
2
2
|
SHA256:
|
3
|
-
metadata.gz:
|
4
|
-
data.tar.gz:
|
3
|
+
metadata.gz: 8c353a0a3ea2e3a261a5cb6af72b3e5e4ac847be178a710e79927264e256393a
|
4
|
+
data.tar.gz: 771ead5640626c25b48b5639746246a08a44bb2f58db8cca3971581257de97e8
|
5
5
|
SHA512:
|
6
|
-
metadata.gz:
|
7
|
-
data.tar.gz:
|
6
|
+
metadata.gz: ef260cde29dfee53e9ab895c14a232c0e023052cb2c614e2ce3e7d34ffbdefebabbe856c2fbaa143179b7fa387468dd32d89718da69ab22d669230fd2a164a76
|
7
|
+
data.tar.gz: 2477e3ea176e80ceb40869bf1e2835ffd63b4a8e733fb87d3f4497bcc70d27d74814f94586e946b2c5b24f8db9f7359f4bb60c7a3087078927d608d03234915d
|
data/lib/free-range.rb
CHANGED
@@ -3,7 +3,6 @@ require 'rmagick'
|
|
3
3
|
require 'fileutils'
|
4
4
|
|
5
5
|
module FreeRange
|
6
|
-
|
7
6
|
# Перевірка наявності ImageMagick
|
8
7
|
begin
|
9
8
|
require 'rmagick'
|
@@ -20,16 +19,20 @@ module FreeRange
|
|
20
19
|
end
|
21
20
|
|
22
21
|
# Додаємо VLAN до списку (віртуальний метод, може бути перевизначений)
|
22
|
+
# @param vlan [Integer] VLAN ID to add
|
23
|
+
# @return [void]
|
23
24
|
def add_vlan(vlan)
|
24
25
|
@vlans << vlan
|
25
26
|
end
|
26
27
|
|
27
28
|
# Повертаємо масив VLAN
|
29
|
+
# @return [Array<Integer>] Sorted array of unique VLAN IDs
|
28
30
|
def vlans
|
29
31
|
@vlans.uniq.sort
|
30
32
|
end
|
31
33
|
|
32
34
|
# Створюємо хеш діапазонів із VLAN
|
35
|
+
# @return [Hash{Integer => Integer}] Hash mapping range start to range end
|
33
36
|
def ranges
|
34
37
|
return {} if @vlans.empty?
|
35
38
|
|
@@ -59,16 +62,23 @@ module FreeRange
|
|
59
62
|
end
|
60
63
|
|
61
64
|
# Додаємо діапазон до списку VLAN-ів
|
65
|
+
# @param start [Integer] Start of VLAN range
|
66
|
+
# @param finish [Integer] End of VLAN range
|
67
|
+
# @return [void]
|
62
68
|
def add_range(start, finish)
|
63
69
|
(start..finish).each { |vlan| @vlans << vlan }
|
64
70
|
end
|
65
71
|
|
66
72
|
# Додаємо діапазон до списку "інших" VLAN-ів
|
73
|
+
# @param start [Integer] Start of another VLAN range
|
74
|
+
# @param finish [Integer] End of another VLAN range
|
75
|
+
# @return [void]
|
67
76
|
def add_another_range(start, finish)
|
68
77
|
(start..finish).each { |vlan| @another_in_ranges << vlan }
|
69
78
|
end
|
70
79
|
|
71
80
|
# Повертаємо масив "інших" VLAN-ів
|
81
|
+
# @return [Array<Integer>] Sorted array of unique "another" VLAN IDs
|
72
82
|
def another_in_ranges
|
73
83
|
@another_in_ranges.uniq.sort
|
74
84
|
end
|
@@ -84,6 +94,9 @@ module FreeRange
|
|
84
94
|
|
85
95
|
# Клас для виводу даних
|
86
96
|
class Print
|
97
|
+
# Виводить хеш діапазонів VLAN у порядку зростання
|
98
|
+
# @param ranges [Ranges] Object containing VLAN ranges
|
99
|
+
# @return [void]
|
87
100
|
def self.ranged(ranges)
|
88
101
|
puts "\nСформований хеш діапазонів (в порядку зростання):"
|
89
102
|
if ranges.ranges.empty?
|
@@ -95,6 +108,9 @@ module FreeRange
|
|
95
108
|
end
|
96
109
|
end
|
97
110
|
|
111
|
+
# Виводить зайняті VLAN-и в межах діапазонів у порядку зростання
|
112
|
+
# @param vlans [Vlans] Object containing VLANs
|
113
|
+
# @return [void]
|
98
114
|
def self.vlans(vlans)
|
99
115
|
puts "\nЗайняті VLAN-и в межах діапазонів (в порядку зростання):"
|
100
116
|
if vlans.vlans.empty?
|
@@ -104,6 +120,9 @@ module FreeRange
|
|
104
120
|
end
|
105
121
|
end
|
106
122
|
|
123
|
+
# Виводить діапазони VLAN-ів у порядку зростання
|
124
|
+
# @param vlans [Vlans] Object containing VLANs
|
125
|
+
# @return [void]
|
107
126
|
def self.vlan_ranges(vlans)
|
108
127
|
puts "\nДіапазони VLAN-ів (в порядку зростання):"
|
109
128
|
vlan_ranges = vlans.vlan_ranges
|
@@ -116,6 +135,13 @@ module FreeRange
|
|
116
135
|
end
|
117
136
|
end
|
118
137
|
|
138
|
+
# Виводить комбіновані діапазони VLAN зі статусами
|
139
|
+
# @param ranges [Ranges] Object containing VLAN ranges
|
140
|
+
# @param vlans [Vlans] Object containing VLANs
|
141
|
+
# @param use_color [Boolean] Enable colored output
|
142
|
+
# @param target [String] Target device hostname
|
143
|
+
# @param interface [String, nil] Interface name or nil
|
144
|
+
# @return [void]
|
119
145
|
def self.combined_ranges(ranges, vlans, use_color, target, interface = nil)
|
120
146
|
if ranges.ranges.empty?
|
121
147
|
puts "Не знайдено діапазонів."
|
@@ -142,6 +168,13 @@ module FreeRange
|
|
142
168
|
puts result.join(',')
|
143
169
|
end
|
144
170
|
|
171
|
+
# Виводить таблицю розподілу VLAN
|
172
|
+
# @param ranges [Ranges] Object containing VLAN ranges
|
173
|
+
# @param vlans [Vlans] Object containing VLANs
|
174
|
+
# @param use_color [Boolean] Enable colored output
|
175
|
+
# @param target [String] Target device hostname
|
176
|
+
# @param interface [String, nil] Interface name or nil
|
177
|
+
# @return [void]
|
145
178
|
def self.table(ranges, vlans, use_color, target, interface = nil)
|
146
179
|
puts "VLAN Distribution for #{target}#{interface ? " (#{interface})" : ''}"
|
147
180
|
all_vlans, status_counts = build_vlan_statuses(ranges, vlans)
|
@@ -191,6 +224,13 @@ module FreeRange
|
|
191
224
|
puts summary_text
|
192
225
|
end
|
193
226
|
|
227
|
+
# Зберігає таблицю розподілу VLAN як PNG-зображення
|
228
|
+
# @param ranges [Ranges] Object containing VLAN ranges
|
229
|
+
# @param vlans [Vlans] Object containing VLANs
|
230
|
+
# @param path [String] Directory path to save the PNG
|
231
|
+
# @param target [String] Target device hostname
|
232
|
+
# @param interface [String, nil] Interface name or nil
|
233
|
+
# @return [void]
|
194
234
|
def self.table_png(ranges, vlans, path, target, interface = nil)
|
195
235
|
all_vlans, status_counts = build_vlan_statuses(ranges, vlans)
|
196
236
|
cell_width = 12
|
@@ -307,6 +347,10 @@ module FreeRange
|
|
307
347
|
|
308
348
|
private
|
309
349
|
|
350
|
+
# Будує хеш статусів VLAN і підраховує кількість кожного статусу
|
351
|
+
# @param ranges [Ranges] Object containing VLAN ranges
|
352
|
+
# @param vlans [Vlans] Object containing VLANs
|
353
|
+
# @return [Array<Hash, Hash>] Hash of VLAN statuses and status counts
|
310
354
|
def self.build_vlan_statuses(ranges, vlans)
|
311
355
|
all_vlans = {}
|
312
356
|
ranges.ranges.each do |start, finish|
|
@@ -321,6 +365,12 @@ module FreeRange
|
|
321
365
|
[all_vlans, status_counts]
|
322
366
|
end
|
323
367
|
|
368
|
+
# Форматує діапазон VLAN зі статусом для виводу
|
369
|
+
# @param start [Integer] Start of VLAN range
|
370
|
+
# @param finish [Integer] End of VLAN range
|
371
|
+
# @param status [String] VLAN status ('f', 'b', 'e', 'c', 'a', 'u')
|
372
|
+
# @param use_color [Boolean] Enable colored output
|
373
|
+
# @return [String] Formatted range string
|
324
374
|
def self.format_range(start, finish, status, use_color)
|
325
375
|
range_text = start == finish ? "#{start}" : "#{start}-#{finish}"
|
326
376
|
range_text_with_status = "#{range_text}(#{status})"
|
@@ -339,6 +389,10 @@ module FreeRange
|
|
339
389
|
end
|
340
390
|
end
|
341
391
|
|
392
|
+
# Форматує символ для таблиці VLAN
|
393
|
+
# @param status [String] VLAN status ('f', 'b', 'e', 'c', 'a', 'u', or ' ')
|
394
|
+
# @param use_color [Boolean] Enable colored output
|
395
|
+
# @return [String] Formatted character for table display
|
342
396
|
def self.format_table_char(status, use_color)
|
343
397
|
if use_color
|
344
398
|
case status
|
@@ -357,6 +411,10 @@ module FreeRange
|
|
357
411
|
end
|
358
412
|
|
359
413
|
# Метод для заповнення Ranges для одного інтерфейсу
|
414
|
+
# @param ssh_command [String] SSH command to execute
|
415
|
+
# @param interface [String, nil] Interface name or nil for all interfaces
|
416
|
+
# @param ranges [Ranges] Object to store VLAN ranges
|
417
|
+
# @return [void]
|
360
418
|
def self.process_interface(ssh_command, interface, ranges)
|
361
419
|
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
420
|
command_demux = interface ? "show configuration interfaces #{interface} | display set | match unnumbered-address" : 'show configuration interfaces | display set | match unnumbered-address'
|
@@ -404,6 +462,7 @@ module FreeRange
|
|
404
462
|
end
|
405
463
|
|
406
464
|
# Основна логіка виконання
|
465
|
+
# @return [void]
|
407
466
|
def self.run
|
408
467
|
options = {}
|
409
468
|
OptionParser.new do |opts|
|
@@ -437,12 +496,6 @@ module FreeRange
|
|
437
496
|
table_png_mode = options[:table_png]
|
438
497
|
interface = options[:interface]
|
439
498
|
|
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
499
|
login = { target: ARGV[0], username: username, password: password }
|
447
500
|
target = ARGV[0].split('.')[0]
|
448
501
|
puts "Connecting to device: #{login[:target]}"
|
@@ -1,68 +1,18 @@
|
|
1
|
-
|
2
|
-
|
3
|
-
|
4
|
-
require 'rmagick'
|
5
|
-
require 'fileutils'
|
6
|
-
|
7
|
-
# Парсинг аргументів командного рядка
|
8
|
-
options = {}
|
9
|
-
OptionParser.new do |opts|
|
10
|
-
opts.banner = "Використання: ruby free-range.rb <IP-адреса або hostname> [опції]"
|
11
|
-
|
12
|
-
opts.on("-u", "--username USERNAME", "Ім'я користувача для SSH") do |u|
|
13
|
-
options[:username] = u
|
14
|
-
end
|
15
|
-
|
16
|
-
opts.on("-p", "--password PASSWORD", "Пароль для SSH") do |p|
|
17
|
-
options[:password] = p
|
18
|
-
end
|
19
|
-
|
20
|
-
opts.on("-n", "--no-color", "Вимкнути кольоровий вивід") do
|
21
|
-
options[:no_color] = true
|
22
|
-
end
|
1
|
+
require 'optparse'
|
2
|
+
require 'rmagick'
|
3
|
+
require 'fileutils'
|
23
4
|
|
24
|
-
|
25
|
-
options[:debug] = true
|
26
|
-
end
|
27
|
-
|
28
|
-
opts.on("-t", "--table", "Вивести діаграму розподілу VLAN-ів") do
|
29
|
-
options[:table] = true
|
30
|
-
end
|
31
|
-
|
32
|
-
opts.on("-g", "--table-png PATH", "Зберегти діаграму розподілу VLAN-ів як PNG у вказаний каталог") do |path|
|
33
|
-
options[:table_png] = path
|
34
|
-
end
|
35
|
-
|
36
|
-
opts.on("-i", "--interface INTERFACE", "Назва інтерфейсу або 'all'") do |i|
|
37
|
-
options[:interface] = i
|
38
|
-
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']
|
5
|
+
module FreeRange
|
51
6
|
|
52
|
-
#
|
53
|
-
|
54
|
-
|
55
|
-
|
7
|
+
# Перевірка наявності ImageMagick
|
8
|
+
begin
|
9
|
+
require 'rmagick'
|
10
|
+
rescue LoadError
|
11
|
+
puts "Помилка: бібліотека rmagick не встановлена або ImageMagick недоступний."
|
12
|
+
puts "Встановіть ImageMagick і виконайте: gem install rmagick"
|
56
13
|
exit 1
|
57
14
|
end
|
58
15
|
|
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
16
|
# Абстрактний клас для роботи з VLAN
|
67
17
|
class VlanContainer
|
68
18
|
def initialize
|
@@ -173,8 +123,6 @@ module FreeRange
|
|
173
123
|
end
|
174
124
|
|
175
125
|
all_vlans, _status_counts = build_vlan_statuses(ranges, vlans)
|
176
|
-
|
177
|
-
# Формуємо діапазони з урахуванням статусів
|
178
126
|
result = []
|
179
127
|
sorted_vlans = all_vlans.keys.sort
|
180
128
|
start = sorted_vlans.first
|
@@ -183,14 +131,12 @@ module FreeRange
|
|
183
131
|
|
184
132
|
sorted_vlans[1..-1].each do |vlan|
|
185
133
|
unless vlan == prev + 1 && all_vlans[vlan] == status
|
186
|
-
# Завершуємо попередній діапазон
|
187
134
|
result << format_range(start, prev, status, use_color)
|
188
135
|
start = vlan
|
189
136
|
status = all_vlans[vlan]
|
190
137
|
end
|
191
138
|
prev = vlan
|
192
139
|
end
|
193
|
-
# Додаємо останній діапазон
|
194
140
|
result << format_range(start, prev, status, use_color)
|
195
141
|
|
196
142
|
puts result.join(',')
|
@@ -200,7 +146,6 @@ module FreeRange
|
|
200
146
|
puts "VLAN Distribution for #{target}#{interface ? " (#{interface})" : ''}"
|
201
147
|
all_vlans, status_counts = build_vlan_statuses(ranges, vlans)
|
202
148
|
|
203
|
-
# Виводимо заголовок
|
204
149
|
puts " 0 1 2 3 4 5 6 7 8 9 "
|
205
150
|
(0..40).each do |h|
|
206
151
|
start_vlan = h * 100
|
@@ -209,7 +154,6 @@ module FreeRange
|
|
209
154
|
puts "#{format("%4d", start_vlan)} #{row}"
|
210
155
|
end
|
211
156
|
|
212
|
-
# Виводимо легенду
|
213
157
|
legend_parts = [
|
214
158
|
["Legend: ", nil],
|
215
159
|
["f", 'f'], ["=free", nil], [", ", nil],
|
@@ -228,7 +172,6 @@ module FreeRange
|
|
228
172
|
end.join
|
229
173
|
puts "\n#{legend_text}"
|
230
174
|
|
231
|
-
# Виводимо підсумок
|
232
175
|
summary_parts = [
|
233
176
|
["Total: ", nil],
|
234
177
|
["f", 'f'], ["=#{status_counts['f']}", nil], [", ", nil],
|
@@ -250,8 +193,6 @@ module FreeRange
|
|
250
193
|
|
251
194
|
def self.table_png(ranges, vlans, path, target, interface = nil)
|
252
195
|
all_vlans, status_counts = build_vlan_statuses(ranges, vlans)
|
253
|
-
|
254
|
-
# Налаштування розмірів і стилів
|
255
196
|
cell_width = 12
|
256
197
|
cell_height = 20
|
257
198
|
rows = 41
|
@@ -259,42 +200,35 @@ module FreeRange
|
|
259
200
|
header_height = 60
|
260
201
|
label_width = 50
|
261
202
|
width = label_width + cols * cell_width + 10
|
262
|
-
height = header_height + rows * cell_height + 20 + 50
|
203
|
+
height = header_height + rows * cell_height + 20 + 50
|
263
204
|
font_size = 14
|
264
|
-
title_font_size = 18
|
205
|
+
title_font_size = 18
|
265
206
|
font = 'Courier'
|
266
207
|
|
267
|
-
# Створюємо полотно
|
268
208
|
canvas = Magick::Image.new(width, height) { |options| options.background_color = 'white' }
|
269
209
|
gc = Magick::Draw.new
|
270
210
|
gc.font = font
|
271
211
|
gc.pointsize = font_size
|
272
212
|
gc.text_antialias = true
|
273
213
|
|
274
|
-
# Малюємо заголовок із назвою пристрою
|
275
214
|
gc.fill('black')
|
276
215
|
gc.pointsize = title_font_size
|
277
216
|
gc.text(10, 25, "VLAN Distribution for #{target}#{interface ? " (#{interface})" : ''}")
|
278
|
-
gc.pointsize = font_size
|
217
|
+
gc.pointsize = font_size
|
279
218
|
|
280
|
-
# Малюємо заголовок (0 1 2 ... 9)
|
281
219
|
(0..9).each do |i|
|
282
220
|
x = label_width + i * 10 * cell_width - 3
|
283
221
|
gc.fill('black')
|
284
222
|
gc.text(x + 5, header_height - 5, i.to_s)
|
285
223
|
end
|
286
224
|
|
287
|
-
# Малюємо таблицю
|
288
225
|
(0..40).each do |h|
|
289
226
|
start_vlan = h * 100
|
290
227
|
end_vlan = [start_vlan + 99, 4094].min
|
291
228
|
y = header_height + h * cell_height
|
292
|
-
|
293
|
-
# Малюємо номер рядка
|
294
229
|
gc.fill('black')
|
295
230
|
gc.text(5, y + font_size, format("%4d", start_vlan))
|
296
231
|
|
297
|
-
# Малюємо клітинки
|
298
232
|
(start_vlan..end_vlan).each_with_index do |vlan, i|
|
299
233
|
status = all_vlans[vlan] || ' '
|
300
234
|
x = label_width + i * cell_width
|
@@ -314,7 +248,6 @@ module FreeRange
|
|
314
248
|
end
|
315
249
|
end
|
316
250
|
|
317
|
-
# Малюємо легенду з кольоровими фонами
|
318
251
|
legend_y = height - 50
|
319
252
|
x = 10
|
320
253
|
legend_parts = [
|
@@ -336,11 +269,10 @@ module FreeRange
|
|
336
269
|
else
|
337
270
|
gc.fill('black')
|
338
271
|
gc.text(x, legend_y, text)
|
339
|
-
x += text.length * 8
|
272
|
+
x += text.length * 8
|
340
273
|
end
|
341
274
|
end
|
342
275
|
|
343
|
-
# Малюємо підсумок VLAN-ів з кольоровими фонами
|
344
276
|
summary_y = height - 30
|
345
277
|
x = 10
|
346
278
|
summary_parts = [
|
@@ -362,11 +294,10 @@ module FreeRange
|
|
362
294
|
else
|
363
295
|
gc.fill('black')
|
364
296
|
gc.text(x, summary_y, text)
|
365
|
-
x += text.length * 8
|
297
|
+
x += text.length * 8
|
366
298
|
end
|
367
299
|
end
|
368
300
|
|
369
|
-
# Зберігаємо зображення
|
370
301
|
gc.draw(canvas)
|
371
302
|
FileUtils.mkdir_p(path) unless Dir.exist?(path)
|
372
303
|
filename = File.join(path, "free-range-#{target}#{interface ? "-#{interface.tr('/', '-')}" : ''}.png")
|
@@ -378,17 +309,12 @@ module FreeRange
|
|
378
309
|
|
379
310
|
def self.build_vlan_statuses(ranges, vlans)
|
380
311
|
all_vlans = {}
|
381
|
-
# Позначаємо VLAN із діапазонів як free
|
382
312
|
ranges.ranges.each do |start, finish|
|
383
313
|
(start..finish).each { |vlan| all_vlans[vlan] = 'f' }
|
384
314
|
end
|
385
|
-
# Оновлюємо статуси для зайнятих VLAN
|
386
315
|
vlans.vlans.uniq.each { |vlan| all_vlans[vlan] = all_vlans.key?(vlan) ? 'b' : 'e' }
|
387
|
-
# Позначаємо всі інші VLAN як unused
|
388
316
|
(1..4094).each { |vlan| all_vlans[vlan] = 'u' unless all_vlans.key?(vlan) }
|
389
|
-
# Оновлюємо статуси для "інших" VLAN
|
390
317
|
ranges.another_in_ranges.each { |vlan| all_vlans[vlan] = all_vlans.key?(vlan) && all_vlans[vlan] != 'u' ? 'c' : 'a' }
|
391
|
-
# Підраховуємо статуси
|
392
318
|
status_counts = { 'f' => 0, 'b' => 0, 'e' => 0, 'c' => 0, 'a' => 0, 'u' => 0 }
|
393
319
|
all_vlans.each_value { |status| status_counts[status] += 1 }
|
394
320
|
|
@@ -398,23 +324,15 @@ module FreeRange
|
|
398
324
|
def self.format_range(start, finish, status, use_color)
|
399
325
|
range_text = start == finish ? "#{start}" : "#{start}-#{finish}"
|
400
326
|
range_text_with_status = "#{range_text}(#{status})"
|
401
|
-
|
402
327
|
if use_color
|
403
328
|
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 # Без кольору для інших статусів
|
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 # Без кольору для інших статусів
|
418
336
|
end
|
419
337
|
else
|
420
338
|
range_text_with_status # Текстовий вивід зі статусами
|
@@ -424,20 +342,13 @@ module FreeRange
|
|
424
342
|
def self.format_table_char(status, use_color)
|
425
343
|
if use_color
|
426
344
|
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 # Без кольору для інших статусів
|
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 # Без кольору для інших статусів
|
441
352
|
end
|
442
353
|
else
|
443
354
|
status # Текстовий вивід без кольорів
|
@@ -445,25 +356,14 @@ module FreeRange
|
|
445
356
|
end
|
446
357
|
end
|
447
358
|
|
448
|
-
#
|
449
|
-
|
450
|
-
target = ARGV[0].split('.')[0] # Обрізаємо суфікс (наприклад, rhoh15-1.ukrhub.net → rhoh15-1)
|
451
|
-
|
452
|
-
puts "Connecting to device: #{login[:target]}"
|
453
|
-
|
454
|
-
ssh_command = "sshpass -p \"#{login[:password]}\" ssh -C -x -4 -o StrictHostKeyChecking=no #{login[:username]}@#{login[:target]}"
|
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)
|
359
|
+
# Метод для заповнення Ranges для одного інтерфейсу
|
360
|
+
def self.process_interface(ssh_command, interface, ranges)
|
459
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]+)?)"'
|
460
362
|
command_demux = interface ? "show configuration interfaces #{interface} | display set | match unnumbered-address" : 'show configuration interfaces | display set | match unnumbered-address'
|
461
363
|
command_another = interface ? "show configuration interfaces #{interface} | display set | match vlan-id" : 'show configuration interfaces | display set | match vlan-id'
|
462
364
|
|
463
|
-
# Виконуємо команду для отримання діапазонів
|
464
365
|
full_cmd = "#{ssh_command} '#{command_ranges}'"
|
465
366
|
result = `#{full_cmd}`.strip
|
466
|
-
|
467
367
|
unless result.empty?
|
468
368
|
result.each_line do |line|
|
469
369
|
if line =~ /ranges (\d+)(?:-(\d+))?/
|
@@ -474,10 +374,8 @@ module FreeRange
|
|
474
374
|
end
|
475
375
|
end
|
476
376
|
|
477
|
-
# Виконуємо команду для отримання unnumbered-address інтерфейсів (demux-source)
|
478
377
|
full_cmd = "#{ssh_command} '#{command_demux}'"
|
479
378
|
result = `#{full_cmd}`.strip
|
480
|
-
|
481
379
|
unless result.empty?
|
482
380
|
result.each_line do |line|
|
483
381
|
if line =~ /unit (\d+)/
|
@@ -490,10 +388,8 @@ module FreeRange
|
|
490
388
|
end
|
491
389
|
end
|
492
390
|
|
493
|
-
# Виконуємо команду для отримання vlan-ів усіх наявних інтерфейсів
|
494
391
|
full_cmd = "#{ssh_command} '#{command_another}'"
|
495
392
|
result = `#{full_cmd}`.strip
|
496
|
-
|
497
393
|
unless result.empty?
|
498
394
|
result.each_line do |line|
|
499
395
|
if line =~ /vlan-id (\d+)/
|
@@ -507,94 +403,129 @@ module FreeRange
|
|
507
403
|
end
|
508
404
|
end
|
509
405
|
|
510
|
-
#
|
511
|
-
|
512
|
-
|
513
|
-
|
514
|
-
|
515
|
-
|
516
|
-
|
517
|
-
|
518
|
-
|
519
|
-
|
520
|
-
|
521
|
-
|
522
|
-
|
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
|
523
425
|
|
524
|
-
|
525
|
-
|
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."
|
526
431
|
exit 1
|
527
432
|
end
|
528
433
|
|
529
|
-
|
530
|
-
|
531
|
-
|
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 порожній. Перевір шлях або доступ."
|
532
456
|
exit 1
|
533
457
|
end
|
534
458
|
|
535
|
-
|
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
|
536
501
|
ranges = Ranges.new
|
537
502
|
vlans = Vlans.new
|
538
|
-
|
539
|
-
# Заповнюємо VLAN-и, фільтруючи за інтерфейсом
|
540
503
|
subscribers_result.each_line do |line|
|
541
504
|
if line.split.first =~ /dhcp(?:_[0-9a-fA-F.]+)?_([^:]+):(\d+)@#{Regexp.escape(target)}$/
|
542
505
|
subscriber_interface, vlan = $1, $2.to_i
|
543
|
-
|
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
|
544
511
|
end
|
545
512
|
end
|
546
513
|
|
547
|
-
process_interface(ssh_command,
|
548
|
-
|
549
|
-
# Виводимо результати
|
514
|
+
process_interface(ssh_command, interface, ranges)
|
550
515
|
if debug
|
551
|
-
puts "\nІнтерфейс: #{
|
516
|
+
puts "\nІнтерфейс: #{interface}" if interface
|
552
517
|
Print.ranged(ranges)
|
553
518
|
Print.vlans(vlans)
|
554
519
|
Print.vlan_ranges(vlans)
|
555
520
|
puts
|
556
521
|
end
|
557
522
|
if table_png_mode
|
558
|
-
Print.table_png(ranges, vlans, table_png_mode, target,
|
523
|
+
Print.table_png(ranges, vlans, table_png_mode, target, interface)
|
559
524
|
elsif table_mode
|
560
|
-
Print.table(ranges, vlans, use_color, target,
|
525
|
+
Print.table(ranges, vlans, use_color, target, interface)
|
561
526
|
else
|
562
|
-
Print.combined_ranges(ranges, vlans, use_color, target,
|
527
|
+
Print.combined_ranges(ranges, vlans, use_color, target, interface)
|
563
528
|
end
|
564
529
|
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
530
|
end
|
599
|
-
|
600
531
|
end
|
metadata
CHANGED
@@ -1,7 +1,7 @@
|
|
1
1
|
--- !ruby/object:Gem::Specification
|
2
2
|
name: free-range
|
3
3
|
version: !ruby/object:Gem::Version
|
4
|
-
version: 0.1.
|
4
|
+
version: 0.1.3
|
5
5
|
platform: ruby
|
6
6
|
authors:
|
7
7
|
- Oleksandr Russkikh //aka Olden Gremlin
|
@@ -61,9 +61,8 @@ extensions: []
|
|
61
61
|
extra_rdoc_files: []
|
62
62
|
files:
|
63
63
|
- bin/free-range
|
64
|
-
- bin/free-range.~
|
65
64
|
- lib/free-range.rb
|
66
|
-
- lib/free-range.rb
|
65
|
+
- lib/free-range.rb~
|
67
66
|
homepage: https://github.com/oldengremlin/free-range
|
68
67
|
licenses:
|
69
68
|
- Apache-2.0
|