consul-templaterb 1.2.1 → 1.3.0
Sign up to get free protection for your applications and to get access to all the features.
- checksums.yaml +4 -4
- data/CHANGELOG.md +12 -0
- data/README.md +1 -1
- data/bin/consul-templaterb +43 -4
- data/lib/consul/async/consul_endpoint.rb +4 -34
- data/lib/consul/async/consul_template.rb +54 -13
- data/lib/consul/async/consul_template_engine.rb +2 -1
- data/lib/consul/async/endpoint.rb +137 -0
- data/lib/consul/async/stats.rb +40 -0
- data/lib/consul/async/vault_endpoint.rb +249 -0
- data/lib/consul/async/version.rb +1 -1
- data/null/ruby-type-inference/ruby-type-inference.mv.db +0 -0
- data/samples/consul-ui/common/header.html.erb +20 -1
- data/samples/consul-ui/consul-keys-ui.html.erb +39 -0
- data/samples/consul-ui/consul-nodes-ui.html.erb +35 -0
- data/samples/consul-ui/consul-services-ui.html.erb +9 -3
- data/samples/consul-ui/consul_keys.json.erb +12 -0
- data/samples/consul-ui/consul_nodes.json.erb +64 -0
- data/samples/consul-ui/{consul_template.json.erb → consul_services.json.erb} +0 -0
- data/samples/consul-ui/css/style.css +80 -4
- data/samples/consul-ui/js/keys.js +129 -0
- data/samples/consul-ui/js/nodes.js +120 -0
- data/samples/consul-ui/js/service.js +53 -179
- data/samples/consul-ui/js/utils.js +347 -0
- data/samples/consul-ui/vendors/highlight/atom-one-dark.css +96 -0
- data/samples/consul-ui/vendors/highlight/highlight.pack.js +2 -0
- data/samples/criteo/vault-test.erb +6 -0
- metadata +17 -3
@@ -0,0 +1,40 @@
|
|
1
|
+
require 'consul/async/utilities'
|
2
|
+
|
3
|
+
module Consul
|
4
|
+
module Async
|
5
|
+
class EndPointStats
|
6
|
+
attr_reader :successes, :errors, :start, :body_bytes
|
7
|
+
|
8
|
+
def initialize
|
9
|
+
@start = Time.now.utc
|
10
|
+
@successes = 0
|
11
|
+
@errors = 0
|
12
|
+
@body_bytes = 0
|
13
|
+
end
|
14
|
+
|
15
|
+
def on_response(res)
|
16
|
+
@successes += 1
|
17
|
+
@body_bytes = body_bytes + res.http.response.bytesize
|
18
|
+
end
|
19
|
+
|
20
|
+
def on_error(_http)
|
21
|
+
@errors += 1
|
22
|
+
end
|
23
|
+
|
24
|
+
def bytes_per_sec
|
25
|
+
diff = (Time.now.utc - start)
|
26
|
+
diff = 1 if diff < 1
|
27
|
+
(body_bytes / diff).round(0)
|
28
|
+
end
|
29
|
+
|
30
|
+
def bytes_per_sec_human
|
31
|
+
"#{Utilities.bytes_to_h(bytes_per_sec)}/s"
|
32
|
+
end
|
33
|
+
|
34
|
+
def body_bytes_human
|
35
|
+
Utilities.bytes_to_h(body_bytes)
|
36
|
+
end
|
37
|
+
end
|
38
|
+
end
|
39
|
+
end
|
40
|
+
|
@@ -0,0 +1,249 @@
|
|
1
|
+
require 'consul/async/utilities'
|
2
|
+
require 'consul/async/stats'
|
3
|
+
require 'em-http'
|
4
|
+
require 'net/http'
|
5
|
+
require 'thread'
|
6
|
+
require 'json'
|
7
|
+
|
8
|
+
module Consul
|
9
|
+
module Async
|
10
|
+
class VaultConfiguration
|
11
|
+
attr_reader :base_url, :token, :token_renew, :retry_duration, :min_duration, :wait_duration, :max_retry_duration, :retry_on_non_diff,
|
12
|
+
:lease_duration_factor, :debug
|
13
|
+
|
14
|
+
def initialize(base_url: 'http://localhost:8200',
|
15
|
+
debug: { network: false },
|
16
|
+
token: nil,
|
17
|
+
token_renew: true,
|
18
|
+
retry_duration: 10,
|
19
|
+
min_duration: 0.1,
|
20
|
+
lease_duration_factor: 0.5,
|
21
|
+
max_retry_duration: 600,
|
22
|
+
paths: {})
|
23
|
+
@base_url = base_url
|
24
|
+
@token_renew = token_renew
|
25
|
+
@debug = debug
|
26
|
+
@retry_duration = retry_duration
|
27
|
+
@min_duration = min_duration
|
28
|
+
@max_retry_duration = max_retry_duration
|
29
|
+
@lease_duration_factor = lease_duration_factor
|
30
|
+
@paths = paths
|
31
|
+
@token = token
|
32
|
+
|
33
|
+
end
|
34
|
+
|
35
|
+
def ch(path, symbol)
|
36
|
+
sub = @paths[path.to_sym]
|
37
|
+
if sub && sub[symbol]
|
38
|
+
STDERR.puts "[INFO] Overriding #{symbol}:=#{sub[symbol]} for #{path}"
|
39
|
+
sub[symbol]
|
40
|
+
else
|
41
|
+
method(symbol).call
|
42
|
+
end
|
43
|
+
end
|
44
|
+
|
45
|
+
def create(path)
|
46
|
+
return self unless @paths[path.to_sym]
|
47
|
+
VaultConfiguration.new(base_url: ch(path, :base_url),
|
48
|
+
debug: ch(path, :debug),
|
49
|
+
token: ch(path, :token),
|
50
|
+
retry_duration: ch(path, :retry_duration),
|
51
|
+
min_duration: ch(path, :min_duration),
|
52
|
+
max_retry_duration: ch(path, :max_retry_duration),
|
53
|
+
lease_duration_factor: ch(path, :lease_duration_factor),
|
54
|
+
paths: @paths)
|
55
|
+
end
|
56
|
+
end
|
57
|
+
class VaultResult
|
58
|
+
attr_reader :data, :http, :stats, :retry_in
|
59
|
+
|
60
|
+
def initialize(result, modified, stats, retry_in)
|
61
|
+
@data = result.response
|
62
|
+
@modified = modified
|
63
|
+
@http = result
|
64
|
+
@data_json = result.json
|
65
|
+
@last_update = Time.now.utc
|
66
|
+
@next_update = Time.now.utc + retry_in
|
67
|
+
@stats = stats
|
68
|
+
@retry_in = retry_in
|
69
|
+
end
|
70
|
+
|
71
|
+
def modified?
|
72
|
+
@modified
|
73
|
+
end
|
74
|
+
|
75
|
+
def mutate(new_data)
|
76
|
+
@data = new_data.dup
|
77
|
+
@data_json = nil
|
78
|
+
end
|
79
|
+
|
80
|
+
def [](path)
|
81
|
+
json[path]
|
82
|
+
end
|
83
|
+
|
84
|
+
def json
|
85
|
+
@data_json = JSON.parse(data) if @data_json.nil?
|
86
|
+
@data_json
|
87
|
+
end
|
88
|
+
|
89
|
+
end
|
90
|
+
class VaultHttpResponse
|
91
|
+
attr_reader :response_header, :response, :error, :json
|
92
|
+
|
93
|
+
def initialize(http, override_nil_response = nil)
|
94
|
+
if http.nil?
|
95
|
+
@response_header = nil
|
96
|
+
@response = override_nil_response
|
97
|
+
@error = 'Not initialized yet'
|
98
|
+
else
|
99
|
+
@response_header = http.response_header.nil? ? nil : http.response_header.dup.freeze
|
100
|
+
@response = http.response.nil? || http.response.empty? ? override_nil_response : http.response.dup.freeze
|
101
|
+
@error = http.error.nil? ? nil : http.error.dup.freeze
|
102
|
+
end
|
103
|
+
@json = JSON[response]
|
104
|
+
end
|
105
|
+
end
|
106
|
+
|
107
|
+
class VaultEndpoint
|
108
|
+
attr_reader :conf, :path, :http_method, :queue, :stats, :last_result, :enforce_json_200, :start_time, :default_value, :query_params
|
109
|
+
|
110
|
+
def initialize(conf, path, http_method = 'GET', enforce_json_200 = true, query_params = {}, default_value = '{}', post_data ={})
|
111
|
+
@conf = conf.create(path)
|
112
|
+
@default_value = default_value
|
113
|
+
@path = path
|
114
|
+
@http_method = http_method
|
115
|
+
@queue = EM::Queue.new
|
116
|
+
@x_consul_index = 0
|
117
|
+
@s_callbacks = []
|
118
|
+
@e_callbacks = []
|
119
|
+
@enforce_json_200 = enforce_json_200
|
120
|
+
@start_time = Time.now.utc
|
121
|
+
@consecutive_errors = 0
|
122
|
+
@query_params = query_params
|
123
|
+
@post_data = post_data
|
124
|
+
@stopping = false
|
125
|
+
@stats = EndPointStats.new
|
126
|
+
@last_result = VaultResult.new(VaultHttpResponse.new(nil, default_value), false, stats ,1)
|
127
|
+
on_response { |result| @stats.on_response result }
|
128
|
+
on_error { |http| @stats.on_error http }
|
129
|
+
_enable_network_debug if conf.debug && conf.debug[:network]
|
130
|
+
fetch
|
131
|
+
queue.push 0
|
132
|
+
end
|
133
|
+
|
134
|
+
def _enable_network_debug
|
135
|
+
on_response do |result|
|
136
|
+
state = result.x_consul_index.to_i < 1 ? '[WARN]' : '[ OK ]'
|
137
|
+
stats = result.stats
|
138
|
+
STDERR.puts "[DBUG]#{state}#{result.modified? ? '[MODFIED]' : '[NO DIFF]'}" \
|
139
|
+
"[s:#{stats.successes},err:#{stats.errors}]" \
|
140
|
+
"[#{stats.body_bytes_human.ljust(8)}][#{stats.bytes_per_sec_human.ljust(9)}]"\
|
141
|
+
" #{path.ljust(48)} idx:#{result.x_consul_index}, next in #{result.retry_in} s"
|
142
|
+
end
|
143
|
+
on_error { |http| STDERR.puts "[ERROR]: #{path}: #{http.error}" }
|
144
|
+
end
|
145
|
+
|
146
|
+
def on_response(&block)
|
147
|
+
@s_callbacks << block
|
148
|
+
end
|
149
|
+
|
150
|
+
def on_error(&block)
|
151
|
+
@e_callbacks << block
|
152
|
+
end
|
153
|
+
|
154
|
+
def ready?
|
155
|
+
@ready
|
156
|
+
end
|
157
|
+
|
158
|
+
def terminate
|
159
|
+
@stopping = true
|
160
|
+
end
|
161
|
+
|
162
|
+
private
|
163
|
+
|
164
|
+
def build_request()
|
165
|
+
res = {
|
166
|
+
head: {
|
167
|
+
'Accept' => 'application/json',
|
168
|
+
'X-Vault-Token' => conf.token
|
169
|
+
},
|
170
|
+
query: {},
|
171
|
+
path: path,
|
172
|
+
keepalive: true,
|
173
|
+
callback: method(:on_response)
|
174
|
+
}
|
175
|
+
# if @post_data
|
176
|
+
# res[:body] = JSON.generate(@post_data)
|
177
|
+
# end
|
178
|
+
@query_params.each_pair do |k, v|
|
179
|
+
res[:query][k] = v
|
180
|
+
end
|
181
|
+
res
|
182
|
+
end
|
183
|
+
|
184
|
+
def get_lease_duration(result)
|
185
|
+
result.json['lease_duration'] || conf.min_duration
|
186
|
+
end
|
187
|
+
|
188
|
+
def _get_errors(http)
|
189
|
+
return [http.error] if http.error
|
190
|
+
unless http.json.nil?
|
191
|
+
return http.json['errors'] if http.json.key?('errors')
|
192
|
+
end
|
193
|
+
['unknown error']
|
194
|
+
end
|
195
|
+
|
196
|
+
def _handle_error(http)
|
197
|
+
retry_in = [conf.max_retry_duration, conf.retry_duration + 2**@consecutive_errors].min
|
198
|
+
STDERR.puts "[ERROR][#{path}][#{http_method}] Code: #{http.response_header.status} #{_get_errors(http).join(' - ')} - Retry in #{retry_in}s #{stats.body_bytes_human}"
|
199
|
+
@consecutive_errors += 1
|
200
|
+
http_result = VaultHttpResponse.new(http, default_value)
|
201
|
+
EventMachine.add_timer(retry_in) do
|
202
|
+
yield
|
203
|
+
queue.push()
|
204
|
+
end
|
205
|
+
@e_callbacks.each { |c| c.call(http_result) }
|
206
|
+
end
|
207
|
+
|
208
|
+
def fetch
|
209
|
+
options = {
|
210
|
+
connect_timeout: 5, # default connection setup timeout
|
211
|
+
inactivity_timeout: 1, # default connection inactivity (post-setup) timeout
|
212
|
+
}
|
213
|
+
connection = EventMachine::HttpRequest.new(conf.base_url, options)
|
214
|
+
cb = proc do |_|
|
215
|
+
http = connection.send(http_method.downcase, build_request) # Under the hood: c.send('get', {stuff}) === c.get({stuff})
|
216
|
+
http.callback do
|
217
|
+
http_result = VaultHttpResponse.new(http.dup.freeze, default_value)
|
218
|
+
if enforce_json_200 && http.response_header.status != 200
|
219
|
+
_handle_error(http_result) { connection = EventMachine::HttpRequest.new(conf.base_url, options) }
|
220
|
+
else
|
221
|
+
@consecutive_errors = 0
|
222
|
+
modified = @last_result.nil? ? true : @last_result.data != http_result.response # Leaving it do to stats with this later
|
223
|
+
retry_in = get_lease_duration(http_result) * conf.lease_duration_factor
|
224
|
+
retry_in = [retry_in, conf.max_retry_duration].min
|
225
|
+
retry_in = [retry_in, conf.min_duration].max
|
226
|
+
result = VaultResult.new(http_result, modified, stats, retry_in)
|
227
|
+
unless @stopping
|
228
|
+
EventMachine.add_timer(retry_in) do
|
229
|
+
queue.push(0)
|
230
|
+
end
|
231
|
+
end
|
232
|
+
@last_result = result
|
233
|
+
@ready = true
|
234
|
+
@s_callbacks.each { |c| c.call(result) }
|
235
|
+
end
|
236
|
+
end
|
237
|
+
|
238
|
+
http.errback do
|
239
|
+
unless @stopping
|
240
|
+
_handle_error(http) { connection = EventMachine::HttpRequest.new(conf.base_url, options) }
|
241
|
+
end
|
242
|
+
end
|
243
|
+
queue.pop(&cb)
|
244
|
+
end
|
245
|
+
queue.pop(&cb)
|
246
|
+
end
|
247
|
+
end
|
248
|
+
end
|
249
|
+
end
|
data/lib/consul/async/version.rb
CHANGED
Binary file
|
@@ -3,13 +3,26 @@
|
|
3
3
|
# CONSUL_TOOLS_SUFFIX: suffix for the address of consul tools
|
4
4
|
# CONSUL_TOOLS_PREFIX: prefix for the address of consul tools
|
5
5
|
# CONSUL_TOOLS: comma sperated list of consul tools
|
6
|
-
tools = (ENV['CONSUL_TOOLS'] || 'services').split(",")
|
6
|
+
tools = (ENV['CONSUL_TOOLS'] || 'services,nodes,keys').split(",")
|
7
7
|
tools_suffix = ENV['CONSUL_TOOLS_PREFIX'] || '-ui.html'
|
8
8
|
tools_prefix = ENV['CONSUL_TOOLS_SUFFIX'] || 'consul-'
|
9
9
|
|
10
10
|
dc_suffix = ENV['CONSUL_DC_SUFFIX'] || ''
|
11
11
|
dc_prefix = ENV['CONSUL_DC_PREFIX'] || '#'
|
12
12
|
|
13
|
+
# CUSTOM_LINKS: A comma separated list of URLs to add to the header in the format: [linkname](actuallink)
|
14
|
+
# Example: [Consul template erb](https://github.com/criteo/consul-templaterb),[anotherlink](#)
|
15
|
+
custom_links = (ENV['CUSTOM_LINKS'] || '').split(",")
|
16
|
+
links_to_display = {}
|
17
|
+
if !custom_links.empty?
|
18
|
+
custom_links.each do |link|
|
19
|
+
if /\[.*\]\(.*\)/ =~ link
|
20
|
+
link_data = /\[(.*)\]\((.*)\)/.match(link)
|
21
|
+
links_to_display[link_data[1]] = link_data[2]
|
22
|
+
end
|
23
|
+
end
|
24
|
+
end
|
25
|
+
|
13
26
|
%><!DOCTYPE html>
|
14
27
|
<html lang="en">
|
15
28
|
<head>
|
@@ -21,6 +34,7 @@
|
|
21
34
|
<link rel="stylesheet" href="https://maxcdn.bootstrapcdn.com/bootstrap/4.0.0/css/bootstrap.min.css" integrity="sha384-Gn5384xqQ1aoWXA+058RXPxPg6fy4IWvTNh0E263XmFcJlSAwiGgFAW/dAiS6JXm" crossorigin="anonymous">
|
22
35
|
<link rel="stylesheet" href="https://use.fontawesome.com/releases/v5.0.11/css/all.css" integrity="sha384-p2jx59pefphTFIpeqCcISO9MdVfIm4pNnsL08A6v5vaQc4owkQqxMV8kg4Yvhaw/" crossorigin="anonymous">
|
23
36
|
<link rel="stylesheet" href="css/style.css">
|
37
|
+
<link rel="stylesheet" href="vendors/highlight/atom-one-dark.css">
|
24
38
|
<style id="css-states">
|
25
39
|
.service-tags { display: none; }
|
26
40
|
</style>
|
@@ -44,6 +58,11 @@
|
|
44
58
|
<a class="nav-link" href="<%= tools_prefix + tool + tools_suffix %>"><%= tool.gsub('_', ' ').capitalize %></a>
|
45
59
|
</li>
|
46
60
|
<% end %>
|
61
|
+
<% links_to_display.each do |linkname,link| %>
|
62
|
+
<li class="nav-item">
|
63
|
+
<a class="nav-link" target="_blank" href="<%= link %>"><%= linkname %></a>
|
64
|
+
</li>
|
65
|
+
<% end %>
|
47
66
|
</ul>
|
48
67
|
</div>
|
49
68
|
</nav>
|
@@ -0,0 +1,39 @@
|
|
1
|
+
<% datasource = ENV['SERVICE_DATASOURCE'] || 'consul_keys.json'
|
2
|
+
# Time to wait before reloading configuration again in seconds (0 = never)
|
3
|
+
refresh = ENV['REFRESH'] || '3600' %><%= render_file('common/header.html.erb', title: 'Keys') %>
|
4
|
+
<div class="main">
|
5
|
+
<div class="row mx-0">
|
6
|
+
<div id="filter-menu" class="col-4 col-m-3 px-4 pt-4">
|
7
|
+
<div class="form-group">
|
8
|
+
<div class="input-group">
|
9
|
+
<input id="keys-filter" type="text" placeholder="filter keys by name" class="form-control" />
|
10
|
+
<div class="input-group-append">
|
11
|
+
<span class="input-group-text" id="keys-counter"></span>
|
12
|
+
</div>
|
13
|
+
</div>
|
14
|
+
</div>
|
15
|
+
<div id="keys-wrapper" >
|
16
|
+
<ul id="keys-list" class="list-group">
|
17
|
+
</ul>
|
18
|
+
</div>
|
19
|
+
</div>
|
20
|
+
<div class="col-8 col-m-9">
|
21
|
+
<h2 class="text-center" id="kv-title"></h2>
|
22
|
+
<div id='data-wrapper'>
|
23
|
+
<pre><code id="kv-data">...</code></pre>
|
24
|
+
</div>
|
25
|
+
</div>
|
26
|
+
</div>
|
27
|
+
</div>
|
28
|
+
<!-- Optional JavaScript -->
|
29
|
+
<!-- JavaScript Dependencies: jQuery, Popper.js, Bootstrap JS, Shards JS -->
|
30
|
+
<script src="https://ajax.googleapis.com/ajax/libs/jquery/3.3.1/jquery.min.js"></script>
|
31
|
+
<script src="https://maxcdn.bootstrapcdn.com/bootstrap/4.0.0/js/bootstrap.min.js" integrity="sha384-JZR6Spejh4U02d8jOt6vLEHfe/JQGiRRSQQxSfFWpi1MquVdAyjUar5+76PVCmYl" crossorigin="anonymous"></script>
|
32
|
+
<script src="js/utils.js"></script>
|
33
|
+
<script src="js/keys.js"></script>
|
34
|
+
<script src="vendors/highlight/highlight.pack.js"></script>
|
35
|
+
<script type="text/javascript">
|
36
|
+
consulKeys = new ConsulKeys('<%= datasource %>','<%= refresh %>');
|
37
|
+
</script>
|
38
|
+
</body>
|
39
|
+
</html>
|
@@ -0,0 +1,35 @@
|
|
1
|
+
<% datasource = ENV['SERVICE_DATASOURCE'] || 'consul_nodes.json'
|
2
|
+
# Time to wait before reloading configuration again in seconds (0 = never)
|
3
|
+
refresh = ENV['REFRESH'] || '3600' %><%= render_file('common/header.html.erb', title: 'Nodes') %>
|
4
|
+
<div id='nodes' class="main">
|
5
|
+
<div class="row mx-0">
|
6
|
+
<div class="col-12 col-m-12">
|
7
|
+
<h2 class="text-center" id="service-title"></h2>
|
8
|
+
<div class="row mb-2">
|
9
|
+
<div class="input-group float-left col-10">
|
10
|
+
<input id="instance-filter" type="text" placeholder="filter nodes by name, service or tags" class="form-control" />
|
11
|
+
</div>
|
12
|
+
<div id="instance-statuses" class="col-2">
|
13
|
+
<span id="service-status-passing" status="passing" onclick="consulNodes.onClickFilter(this)" class="badge mx-1 badge-success passing service-status">0</span>
|
14
|
+
<span id="service-status-warning" status="warning" onclick="consulNodes.onClickFilter(this)" class="badge mx-1 badge-warning warning service-status">0</span>
|
15
|
+
<span id="service-status-critical" status="critical" onclick="consulNodes.onClickFilter(this)" class="badge mx-1 badge-danger critical service-status">0</span>
|
16
|
+
<span id="service-status-total" status="total" onclick="consulNodes.onClickFilter(this)" class="badge mx-1 badge-dark service-status">0</span>
|
17
|
+
</div>
|
18
|
+
</div>
|
19
|
+
<div id="instances-wrapper">
|
20
|
+
<div id="instances-list" class="list-group"></div>
|
21
|
+
</div>
|
22
|
+
</div>
|
23
|
+
</div>
|
24
|
+
</div>
|
25
|
+
<!-- Optional JavaScript -->
|
26
|
+
<!-- JavaScript Dependencies: jQuery, Popper.js, Bootstrap JS, Shards JS -->
|
27
|
+
<script src="https://ajax.googleapis.com/ajax/libs/jquery/3.3.1/jquery.min.js"></script>
|
28
|
+
<script src="https://maxcdn.bootstrapcdn.com/bootstrap/4.0.0/js/bootstrap.min.js" integrity="sha384-JZR6Spejh4U02d8jOt6vLEHfe/JQGiRRSQQxSfFWpi1MquVdAyjUar5+76PVCmYl" crossorigin="anonymous"></script>
|
29
|
+
<script src="js/utils.js"></script>
|
30
|
+
<script src="js/nodes.js"></script>
|
31
|
+
<script type="text/javascript">
|
32
|
+
consulNodes = new ConsulNodes('<%= datasource %>','<%= refresh %>');
|
33
|
+
</script>
|
34
|
+
</body>
|
35
|
+
</html>
|
@@ -1,6 +1,6 @@
|
|
1
|
-
<% datasource = ENV['SERVICE_DATASOURCE'] || '
|
1
|
+
<% datasource = ENV['SERVICE_DATASOURCE'] || 'consul_services.json'
|
2
2
|
# Time to wait before reloading configuration again in seconds (0 = never)
|
3
|
-
refresh = ENV['REFRESH'] || '
|
3
|
+
refresh = ENV['REFRESH'] || '3600' %><%= render_file('common/header.html.erb', title: 'Services') %>
|
4
4
|
<div class="main">
|
5
5
|
<div class="row mx-0">
|
6
6
|
<div id="filter-menu" class="col-4 col-m-3 px-4 pt-4">
|
@@ -12,7 +12,7 @@
|
|
12
12
|
</label>
|
13
13
|
</div>
|
14
14
|
<div class="input-group">
|
15
|
-
<input id="service-filter" type="text" placeholder="filter by name or tags" class="form-control" />
|
15
|
+
<input id="service-filter" type="text" placeholder="filter services by name or tags" class="form-control" />
|
16
16
|
<div class="input-group-append">
|
17
17
|
<span class="input-group-text" id="service-counter"></span>
|
18
18
|
</div>
|
@@ -25,6 +25,11 @@
|
|
25
25
|
</div>
|
26
26
|
<div class="col-8 col-m-9">
|
27
27
|
<h2 class="text-center" id="service-title"></h2>
|
28
|
+
<div class="row mb-2">
|
29
|
+
<div class="input-group float-left col-12">
|
30
|
+
<input id="instance-filter" type="text" placeholder="filter nodes by name or tags" class="form-control" />
|
31
|
+
</div>
|
32
|
+
</div>
|
28
33
|
<div class="progress">
|
29
34
|
<div id="service-progress-passing" status="passing" onclick="consulService.onClickFilter(this)" class="progress-status progress-bar bg-success" role="progressbar" style="width: 100%">passing</div>
|
30
35
|
<div id="service-progress-warning" status="warning" onclick="consulService.onClickFilter(this)" class="progress-status progress-bar bg-warning" role="progressbar" style="width: 0%">warning</div>
|
@@ -40,6 +45,7 @@
|
|
40
45
|
<!-- JavaScript Dependencies: jQuery, Popper.js, Bootstrap JS, Shards JS -->
|
41
46
|
<script src="https://ajax.googleapis.com/ajax/libs/jquery/3.3.1/jquery.min.js"></script>
|
42
47
|
<script src="https://maxcdn.bootstrapcdn.com/bootstrap/4.0.0/js/bootstrap.min.js" integrity="sha384-JZR6Spejh4U02d8jOt6vLEHfe/JQGiRRSQQxSfFWpi1MquVdAyjUar5+76PVCmYl" crossorigin="anonymous"></script>
|
48
|
+
<script src="js/utils.js"></script>
|
43
49
|
<script src="js/service.js"></script>
|
44
50
|
<script type="text/javascript">
|
45
51
|
consulService = new ConsulService('<%= datasource %>','<%= refresh %>');
|