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~
ADDED
@@ -0,0 +1,531 @@
|
|
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
|
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.
|
4
|
+
version: 0.2.0
|
5
5
|
platform: ruby
|
6
6
|
authors:
|
7
7
|
- Oleksandr Russkikh //aka Olden Gremlin
|
@@ -52,6 +52,20 @@ dependencies:
|
|
52
52
|
- - "~>"
|
53
53
|
- !ruby/object:Gem::Version
|
54
54
|
version: '13.0'
|
55
|
+
- !ruby/object:Gem::Dependency
|
56
|
+
name: yard
|
57
|
+
requirement: !ruby/object:Gem::Requirement
|
58
|
+
requirements:
|
59
|
+
- - "~>"
|
60
|
+
- !ruby/object:Gem::Version
|
61
|
+
version: '0.9'
|
62
|
+
type: :development
|
63
|
+
prerelease: false
|
64
|
+
version_requirements: !ruby/object:Gem::Requirement
|
65
|
+
requirements:
|
66
|
+
- - "~>"
|
67
|
+
- !ruby/object:Gem::Version
|
68
|
+
version: '0.9'
|
55
69
|
description: A Ruby script to analyze VLAN distribution on network devices, generating
|
56
70
|
tables or PNG images.
|
57
71
|
email: olden@ukr-com.net
|
@@ -61,9 +75,9 @@ extensions: []
|
|
61
75
|
extra_rdoc_files: []
|
62
76
|
files:
|
63
77
|
- bin/free-range
|
64
|
-
- bin/free-range.~
|
65
78
|
- lib/free-range.rb
|
66
79
|
- lib/free-range.rb.~
|
80
|
+
- lib/free-range.rb~
|
67
81
|
homepage: https://github.com/oldengremlin/free-range
|
68
82
|
licenses:
|
69
83
|
- Apache-2.0
|