mayu 0.1.0.beta1
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 +7 -0
- data/.gitignore +11 -0
- data/.rspec +3 -0
- data/.travis.yml +5 -0
- data/Gemfile +9 -0
- data/Gemfile.lock +69 -0
- data/LICENSE.txt +21 -0
- data/README.md +206 -0
- data/Rakefile +6 -0
- data/bin/mayu-association-collector +84 -0
- data/config.ru +41 -0
- data/lib/mayu.rb +8 -0
- data/lib/mayu/ap.rb +43 -0
- data/lib/mayu/app.rb +246 -0
- data/lib/mayu/association.rb +34 -0
- data/lib/mayu/cisco_wlc_collector.rb +102 -0
- data/lib/mayu/device.rb +23 -0
- data/lib/mayu/loader.rb +168 -0
- data/lib/mayu/map.rb +42 -0
- data/lib/mayu/periodic_loader.rb +55 -0
- data/lib/mayu/relation.rb +50 -0
- data/lib/mayu/renderer.rb +43 -0
- data/lib/mayu/stores/base.rb +44 -0
- data/lib/mayu/stores/concat.rb +41 -0
- data/lib/mayu/stores/file.rb +25 -0
- data/lib/mayu/stores/memory.rb +21 -0
- data/lib/mayu/stores/s3.rb +40 -0
- data/lib/mayu/user.rb +41 -0
- data/lib/mayu/user_completer.rb +30 -0
- data/lib/mayu/version.rb +3 -0
- data/mayu.gemspec +34 -0
- data/script/console +14 -0
- data/script/setup +8 -0
- metadata +176 -0
data/lib/mayu/loader.rb
ADDED
@@ -0,0 +1,168 @@
|
|
1
|
+
require 'mayu/association'
|
2
|
+
require 'mayu/user'
|
3
|
+
require 'mayu/device'
|
4
|
+
require 'mayu/map'
|
5
|
+
require 'mayu/ap'
|
6
|
+
|
7
|
+
require 'mayu/user_completer'
|
8
|
+
|
9
|
+
module Mayu
|
10
|
+
class Loader
|
11
|
+
def initialize(store:, user_completer: nil)
|
12
|
+
@store = store
|
13
|
+
@cached_user_completer = user_completer
|
14
|
+
end
|
15
|
+
|
16
|
+
attr_reader :store
|
17
|
+
|
18
|
+
def load
|
19
|
+
objects
|
20
|
+
self
|
21
|
+
end
|
22
|
+
|
23
|
+
def objects
|
24
|
+
@objects ||= store.get
|
25
|
+
end
|
26
|
+
|
27
|
+
###
|
28
|
+
|
29
|
+
def associations
|
30
|
+
@associations ||= objects.fetch(:associations).map do |_|
|
31
|
+
Association.load(_).tap do |association|
|
32
|
+
association._ap_finder = method(:find_ap)
|
33
|
+
association._user_finder = method(:find_user)
|
34
|
+
association._device_finder = method(:find_device_by_mac)
|
35
|
+
association._ensure_relation_finders_satisfied!
|
36
|
+
end
|
37
|
+
end
|
38
|
+
end
|
39
|
+
|
40
|
+
def association_by_mac
|
41
|
+
@association_by_mac = associations.map do |_|
|
42
|
+
[_.mac, _]
|
43
|
+
end.to_h
|
44
|
+
end
|
45
|
+
|
46
|
+
def association_by_ip
|
47
|
+
@association_by_ip = associations.map do |_|
|
48
|
+
[_.ip, _]
|
49
|
+
end.to_h
|
50
|
+
end
|
51
|
+
|
52
|
+
def associations_by_ap
|
53
|
+
@association_by_ap = associations.group_by do |_|
|
54
|
+
_.ap_key.to_s
|
55
|
+
end.to_h
|
56
|
+
end
|
57
|
+
|
58
|
+
def find_association_by_mac(mac)
|
59
|
+
association_by_mac[mac]
|
60
|
+
end
|
61
|
+
|
62
|
+
def find_association_by_ip(ip)
|
63
|
+
association_by_ip[ip]
|
64
|
+
end
|
65
|
+
|
66
|
+
def find_associations_by_ap(k)
|
67
|
+
associations_by_ap.fetch(k.to_s, [])
|
68
|
+
end
|
69
|
+
|
70
|
+
###
|
71
|
+
|
72
|
+
def users
|
73
|
+
@users ||= objects.fetch(:users).map do |_|
|
74
|
+
User.load(_).tap do |user|
|
75
|
+
user._devices_finder = method(:find_devices_by_user)
|
76
|
+
user._ensure_relation_finders_satisfied!
|
77
|
+
end
|
78
|
+
end
|
79
|
+
end
|
80
|
+
|
81
|
+
def user_by_key
|
82
|
+
@user_by_key ||= users.map do |_|
|
83
|
+
[_.key.to_sym, _]
|
84
|
+
end.to_h
|
85
|
+
end
|
86
|
+
|
87
|
+
def find_user(k)
|
88
|
+
user_by_key[k.to_sym]
|
89
|
+
end
|
90
|
+
|
91
|
+
def user_completer
|
92
|
+
@user_completer ||= @cached_user_completer ? @cached_user_completer.update(users) : UserCompleter.new(users)
|
93
|
+
end
|
94
|
+
|
95
|
+
def suggest_users(query)
|
96
|
+
user_completer.query(query)
|
97
|
+
end
|
98
|
+
|
99
|
+
###
|
100
|
+
|
101
|
+
def devices
|
102
|
+
@devices ||= objects.fetch(:devices).map do |_|
|
103
|
+
Device.load(_).tap do |device|
|
104
|
+
device._user_finder = method(:find_user)
|
105
|
+
device._association_finder = proc { find_association_by_mac(device.mac) }
|
106
|
+
device._ensure_relation_finders_satisfied!
|
107
|
+
end
|
108
|
+
end
|
109
|
+
end
|
110
|
+
|
111
|
+
def device_by_mac
|
112
|
+
@device_by_mac ||= devices.reverse_each.map do |device|
|
113
|
+
[device.mac, device]
|
114
|
+
end.to_h
|
115
|
+
end
|
116
|
+
|
117
|
+
def devices_by_user
|
118
|
+
@devices_by_user ||= devices.reverse_each.group_by do |device|
|
119
|
+
device.user_key
|
120
|
+
end
|
121
|
+
end
|
122
|
+
|
123
|
+
def find_device_by_mac(k)
|
124
|
+
device_by_mac[k]
|
125
|
+
end
|
126
|
+
|
127
|
+
def find_devices_by_user(k)
|
128
|
+
devices_by_user.fetch(k, [])
|
129
|
+
end
|
130
|
+
|
131
|
+
###
|
132
|
+
|
133
|
+
def maps
|
134
|
+
@maps ||= objects.fetch(:maps).map do |k, v|
|
135
|
+
[k, Map.load(v).tap do |map|
|
136
|
+
map.key = k
|
137
|
+
map._aps_finder = proc { find_aps_by_map(k).values }
|
138
|
+
map._ensure_relation_finders_satisfied!
|
139
|
+
end]
|
140
|
+
end.to_h
|
141
|
+
end
|
142
|
+
|
143
|
+
def find_map(k)
|
144
|
+
maps[k.to_sym]
|
145
|
+
end
|
146
|
+
|
147
|
+
###
|
148
|
+
|
149
|
+
def aps
|
150
|
+
@aps ||= objects.fetch(:aps).map do |k,v|
|
151
|
+
[k, Ap.load(v).tap do |ap|
|
152
|
+
ap.key = k
|
153
|
+
ap._map_finder = method(:find_map)
|
154
|
+
ap._associations_finder = proc { find_associations_by_ap(k) }
|
155
|
+
ap._ensure_relation_finders_satisfied!
|
156
|
+
end]
|
157
|
+
end.to_h
|
158
|
+
end
|
159
|
+
|
160
|
+
def find_ap(k)
|
161
|
+
aps[k.to_sym]
|
162
|
+
end
|
163
|
+
|
164
|
+
def find_aps_by_map(key)
|
165
|
+
aps.select { |k,v| v.map_key.to_s == key.to_s }
|
166
|
+
end
|
167
|
+
end
|
168
|
+
end
|
data/lib/mayu/map.rb
ADDED
@@ -0,0 +1,42 @@
|
|
1
|
+
module Mayu
|
2
|
+
Map = Struct.new(:key, :name, :url, :fg_color, :bg_color, :highlight_color, keyword_init: true) do
|
3
|
+
include Mayu::Relation
|
4
|
+
|
5
|
+
def self.load(obj)
|
6
|
+
new(**obj)
|
7
|
+
end
|
8
|
+
|
9
|
+
relates :aps
|
10
|
+
|
11
|
+
def associations
|
12
|
+
@associations ||= aps.flat_map(&:associations)
|
13
|
+
end
|
14
|
+
def devices
|
15
|
+
@devices ||= aps.flat_map(&:devices)
|
16
|
+
end
|
17
|
+
def users
|
18
|
+
@users ||= aps.flat_map(&:users)
|
19
|
+
end
|
20
|
+
|
21
|
+
def associations_count
|
22
|
+
associations.size
|
23
|
+
end
|
24
|
+
def devices_count
|
25
|
+
devices.size
|
26
|
+
end
|
27
|
+
def users_count
|
28
|
+
users.size
|
29
|
+
end
|
30
|
+
|
31
|
+
def as_json
|
32
|
+
{
|
33
|
+
key: key,
|
34
|
+
name: name,
|
35
|
+
url: url,
|
36
|
+
fg_color: fg_color,
|
37
|
+
bg_color: bg_color,
|
38
|
+
highlight_color: highlight_color,
|
39
|
+
}
|
40
|
+
end
|
41
|
+
end
|
42
|
+
end
|
@@ -0,0 +1,55 @@
|
|
1
|
+
require 'mayu/loader'
|
2
|
+
require 'thread'
|
3
|
+
|
4
|
+
module Mayu
|
5
|
+
class PeriodicLoader
|
6
|
+
SHUTDOWN_KEY = :mayu_periodic_loader_shutdown
|
7
|
+
def initialize(interval:, **options)
|
8
|
+
@loaded_at = nil
|
9
|
+
@loader = nil
|
10
|
+
@interval = interval
|
11
|
+
@options = options
|
12
|
+
@lock = Mutex.new
|
13
|
+
@thread = nil
|
14
|
+
end
|
15
|
+
|
16
|
+
attr_reader :loaded_at, :loader
|
17
|
+
|
18
|
+
def start
|
19
|
+
return if @thread
|
20
|
+
@lock.synchronize do
|
21
|
+
return if @thread
|
22
|
+
reload
|
23
|
+
@thread = Thread.new(&method(:thread))
|
24
|
+
end
|
25
|
+
end
|
26
|
+
|
27
|
+
def shutdown
|
28
|
+
@lock.synchronize do
|
29
|
+
if @thread
|
30
|
+
@thread[SHUTDOWN_KEY] = true
|
31
|
+
end
|
32
|
+
@thread = nil
|
33
|
+
end
|
34
|
+
end
|
35
|
+
|
36
|
+
def reload
|
37
|
+
@loaded_at = Time.now
|
38
|
+
new_loader = Loader.new(user_completer: @loader&.user_completer, **@options).load
|
39
|
+
@loader = new_loader
|
40
|
+
end
|
41
|
+
|
42
|
+
def thread
|
43
|
+
loop do
|
44
|
+
return if @thread[SHUTDOWN_KEY]
|
45
|
+
@lock.synchronize do
|
46
|
+
reload
|
47
|
+
end
|
48
|
+
sleep @interval
|
49
|
+
rescue Exception => e
|
50
|
+
$stderr.puts e.full_message
|
51
|
+
sleep @interval
|
52
|
+
end
|
53
|
+
end
|
54
|
+
end
|
55
|
+
end
|
@@ -0,0 +1,50 @@
|
|
1
|
+
module Mayu
|
2
|
+
module Relation
|
3
|
+
def self.included(klass)
|
4
|
+
klass.extend ClassMethods
|
5
|
+
end
|
6
|
+
|
7
|
+
def _ensure_relation_finders_satisfied!
|
8
|
+
self.class.relations.map do |k|
|
9
|
+
unless instance_variable_get(:"@_#{k}_finder")
|
10
|
+
raise "#{k.class}: #{k.inspect} not satisfied"
|
11
|
+
end
|
12
|
+
end
|
13
|
+
end
|
14
|
+
|
15
|
+
module ClassMethods
|
16
|
+
def relations
|
17
|
+
@_relations ||= []
|
18
|
+
end
|
19
|
+
def relates(name)
|
20
|
+
self.relations << name
|
21
|
+
|
22
|
+
key_exist = self.instance_methods.include?(:"#{name}_key")
|
23
|
+
self.class_eval(<<-EOF, __FILE__, __LINE__ + 1)
|
24
|
+
def _#{name}_finder=(x)
|
25
|
+
@_#{name}_finder = x
|
26
|
+
end
|
27
|
+
|
28
|
+
def _#{name}
|
29
|
+
@_#{name}
|
30
|
+
end
|
31
|
+
|
32
|
+
def _#{name}=(x)
|
33
|
+
key = #{key_exist ? "#{name}_key" : 'nil'}
|
34
|
+
@_#{name}_key = key
|
35
|
+
@_#{name} = x
|
36
|
+
end
|
37
|
+
|
38
|
+
def #{name}
|
39
|
+
raise "#{name} finder not set" unless @_#{name}_finder
|
40
|
+
key = #{key_exist ? "#{name}_key" : 'nil'}
|
41
|
+
if @_#{name}_key && @_#{name}_key != key
|
42
|
+
@_#{name} = nil
|
43
|
+
end
|
44
|
+
self._#{name} ||= @_#{name}_finder[key]
|
45
|
+
end
|
46
|
+
EOF
|
47
|
+
end
|
48
|
+
end
|
49
|
+
end
|
50
|
+
end
|
@@ -0,0 +1,43 @@
|
|
1
|
+
require 'json'
|
2
|
+
|
3
|
+
module Mayu
|
4
|
+
|
5
|
+
class Renderer
|
6
|
+
def initialize(spec)
|
7
|
+
@spec = spec
|
8
|
+
end
|
9
|
+
|
10
|
+
def render(obj, spec: @spec)
|
11
|
+
r = nil
|
12
|
+
case obj
|
13
|
+
when nil
|
14
|
+
return nil
|
15
|
+
when Array
|
16
|
+
return obj.map { |_| render(_, spec: spec) }
|
17
|
+
when Hash
|
18
|
+
r = obj
|
19
|
+
else
|
20
|
+
case
|
21
|
+
when obj.respond_to?(:as_json)
|
22
|
+
r = obj.as_json
|
23
|
+
when !spec.empty?
|
24
|
+
r = {}
|
25
|
+
else
|
26
|
+
return obj
|
27
|
+
end
|
28
|
+
end
|
29
|
+
next_specs, keys = [spec].flatten.partition { |_| Hash === _ }
|
30
|
+
aref = Hash === obj
|
31
|
+
r2 = r.transform_values do |v|
|
32
|
+
render(v, spec: {})
|
33
|
+
end
|
34
|
+
keys.each do |k|
|
35
|
+
r[k] = render(r.fetch(k) { aref ? obj.fetch(k) { obj.send(k) } : obj.send(k) }, spec: {})
|
36
|
+
end
|
37
|
+
(next_specs.inject(&:merge) || {}).each do |k, ns|
|
38
|
+
r[k] = render(r.fetch(k) { aref ? obj.fetch(k) { obj.send(k) } : obj.send(k) }, spec: ns)
|
39
|
+
end
|
40
|
+
r2.merge(r)
|
41
|
+
end
|
42
|
+
end
|
43
|
+
end
|
@@ -0,0 +1,44 @@
|
|
1
|
+
require 'time'
|
2
|
+
module Mayu
|
3
|
+
module Stores
|
4
|
+
class Base
|
5
|
+
def initialize(**options)
|
6
|
+
@options = options
|
7
|
+
end
|
8
|
+
|
9
|
+
def put(obj)
|
10
|
+
raise NotImplementedError
|
11
|
+
end
|
12
|
+
|
13
|
+
def get
|
14
|
+
raise NotImplementedError
|
15
|
+
end
|
16
|
+
|
17
|
+
def for_json(obj)
|
18
|
+
case obj
|
19
|
+
when Array
|
20
|
+
obj.map{ |_| for_json(_) }
|
21
|
+
when Hash
|
22
|
+
obj.transform_values { |v| v.is_a?(Time) ? v.xmlschema : for_json(v) }
|
23
|
+
else
|
24
|
+
obj
|
25
|
+
end
|
26
|
+
end
|
27
|
+
|
28
|
+
def from_json(obj)
|
29
|
+
case obj
|
30
|
+
when Array
|
31
|
+
obj.map do |v|
|
32
|
+
from_json(v)
|
33
|
+
end
|
34
|
+
when Hash
|
35
|
+
obj.map do |k,v|
|
36
|
+
[k, k.to_s.end_with?('_at') ? (v && Time.xmlschema(v)) : from_json(v)]
|
37
|
+
end.to_h
|
38
|
+
else
|
39
|
+
obj
|
40
|
+
end
|
41
|
+
end
|
42
|
+
end
|
43
|
+
end
|
44
|
+
end
|
@@ -0,0 +1,41 @@
|
|
1
|
+
require 'mayu/stores/base'
|
2
|
+
|
3
|
+
module Mayu
|
4
|
+
module Stores
|
5
|
+
class Concat < Base
|
6
|
+
def initialize(stores:)
|
7
|
+
@stores = stores
|
8
|
+
end
|
9
|
+
|
10
|
+
attr_reader :stores
|
11
|
+
|
12
|
+
def put(obj)
|
13
|
+
raise NotImplementedError, "this store is read only"
|
14
|
+
end
|
15
|
+
|
16
|
+
def get
|
17
|
+
objs = @stores.map(&:get)
|
18
|
+
keys = objs.flat_map(&:keys).uniq
|
19
|
+
|
20
|
+
r = {}
|
21
|
+
objs.each do |i|
|
22
|
+
keys.each do |k|
|
23
|
+
next unless i[k]
|
24
|
+
case r[k]
|
25
|
+
when Array
|
26
|
+
r[k] ||= []
|
27
|
+
r[k] += i[k]
|
28
|
+
when Hash
|
29
|
+
r[k] ||= {}
|
30
|
+
r[k] = r[k].merge(i[k])
|
31
|
+
else
|
32
|
+
r[k] = i[k]
|
33
|
+
end
|
34
|
+
end
|
35
|
+
end
|
36
|
+
r
|
37
|
+
end
|
38
|
+
end
|
39
|
+
end
|
40
|
+
end
|
41
|
+
|