consul-templaterb 1.0.9 → 1.0.10
Sign up to get free protection for your applications and to get access to all the features.
- checksums.yaml +4 -4
- data/CHANGELOG.md +8 -1
- data/TemplateAPI.md +108 -0
- data/lib/consul/async/consul_template.rb +1 -1
- data/lib/consul/async/version.rb +1 -1
- data/samples/consul-ui/common/header.html.erb +38 -0
- data/samples/consul-ui/consul-services-ui.html.erb +31 -0
- data/samples/consul-ui/consul_template.json.erb +89 -0
- data/samples/consul-ui/css/style.css +57 -0
- data/samples/consul-ui/js/service.js +206 -0
- data/samples/nodes.html.erb +36 -5
- metadata +7 -2
checksums.yaml
CHANGED
@@ -1,7 +1,7 @@
|
|
1
1
|
---
|
2
2
|
SHA256:
|
3
|
-
metadata.gz:
|
4
|
-
data.tar.gz:
|
3
|
+
metadata.gz: 03d2d0aa9f8c3d39ce100425d7b8a2907eea8e0362383a34e41917af4fc8e2b2
|
4
|
+
data.tar.gz: e1c0f7dd9bb50d8c10e5be073a00cc52e7de82b0541c8efb973929834b70c48a
|
5
5
|
SHA512:
|
6
|
-
metadata.gz:
|
7
|
-
data.tar.gz:
|
6
|
+
metadata.gz: 2bf4ff3ce92344d9e79ba00035072753ec95d860bb143f39b0c858038119adf89014e8b3f7e294b4095158840fe43198252da1626a561f49dce796cd94f8549c
|
7
|
+
data.tar.gz: d53f88ea95908908aced0200a2afb98d30a156bc4a3f9c0ce66412c1ae6fc4f22f591137bce031fb85e840d7dc7b0116702f891d5a027f44f818e24631b7f116
|
data/CHANGELOG.md
CHANGED
@@ -4,7 +4,14 @@
|
|
4
4
|
|
5
5
|
IMPROVEMENTS:
|
6
6
|
|
7
|
-
|
7
|
+
# 1.0.10
|
8
|
+
|
9
|
+
IMPROVEMENTS:
|
10
|
+
|
11
|
+
* [samples/nodes.html.erb](samples/nodes.html.erb) now also displays the services
|
12
|
+
* Added dynamic UI with JSON in directory [samples/consul-ui](samples/consul-ui)
|
13
|
+
|
14
|
+
## 1.0.9 (March 20, 2018)
|
8
15
|
|
9
16
|
IMPROVEMENTS:
|
10
17
|
|
data/TemplateAPI.md
CHANGED
@@ -14,27 +14,135 @@ thus the application will display a warning if the template is invalid and won't
|
|
14
14
|
|
15
15
|
Have a look to [samples/](samples/) directory to start writing your own templates.
|
16
16
|
|
17
|
+
## Common structure of returned objects
|
18
|
+
|
19
|
+
All objects returned by those functions described below all share the same structure:
|
20
|
+
|
21
|
+
* `.result` : handle the result
|
22
|
+
* `.endpoint` : get technical information about how data was retrieved and statistics
|
23
|
+
|
24
|
+
## Common re-implemented functions for all objects
|
25
|
+
|
26
|
+
Most objects returned by all those functions are contained within a `.result` object. However, in order
|
27
|
+
to avoid having to write .result in all templates, some shortcuts have been added:
|
28
|
+
* `[]` allow to either access values for map-based data or arrays
|
29
|
+
* for all objects: `.each`, `sort`, `.select`, `.each_value`, `.count`, `.empty?`
|
30
|
+
* additionnaly, for map based results, the following methods are available: `.keys`, `.values`, `.each_pair`,
|
31
|
+
`.each_value`
|
32
|
+
|
33
|
+
See [lib/consul/async/consul_template.rb:230](lib/consul/async/consul_template.rb#L230) and
|
34
|
+
[lib/consul/async/consul_template.rb:260](lib/consul/async/consul_template.rb#L260) for up to date list of
|
35
|
+
all those methods.
|
36
|
+
|
17
37
|
## datacenters()
|
18
38
|
|
19
39
|
[Get the list of datacenters as string array](https://www.consul.io/api/catalog.html#list-datacenters).
|
20
40
|
|
41
|
+
<details><summary>Examples</summary>
|
42
|
+
<div class="samples">
|
43
|
+
|
44
|
+
### List all datacenters in a text list and count services and nodes within
|
45
|
+
|
46
|
+
```erb
|
47
|
+
<% datacenters.each do |dc| %>
|
48
|
+
* <%= dc %> with <%= services(dc:dc).keys.count %> services, <%= nodes(dc:dc).count %> nodes
|
49
|
+
<% end %>
|
50
|
+
```
|
51
|
+
|
52
|
+
Full example: [samples/consul_template.txt.erb](samples/consul_template.txt.erb)
|
53
|
+
|
54
|
+
</div>
|
55
|
+
</details>
|
56
|
+
|
21
57
|
## services([dc: datacenter], [tag: tagToFilterWith])
|
22
58
|
|
23
59
|
[List the services matching the optional tag filter](https://www.consul.io/api/catalog.html#list-services),
|
24
60
|
if tag is not specified, will match all the services. Note that this endpoint performs client side tag
|
25
61
|
filtering for services to ease templates development since this feature is not available on Consul's endpoint.
|
26
62
|
|
63
|
+
<details><summary>Examples</summary>
|
64
|
+
<div class="samples">
|
65
|
+
|
66
|
+
### List all services in default datacenter and display its tags
|
67
|
+
|
68
|
+
```erb
|
69
|
+
<% services.each do |service_name, tags|
|
70
|
+
%> * <%= service_name %> [ <%= tags %> ]
|
71
|
+
<% end %>
|
72
|
+
```
|
73
|
+
|
74
|
+
Full example: [samples/consul_template.txt.erb](samples/consul_template.txt.erb)
|
75
|
+
|
76
|
+
### List all services in all datacenters having tag `http`
|
77
|
+
|
78
|
+
```erb
|
79
|
+
<%
|
80
|
+
datacenters.each do |dc| %>
|
81
|
+
* Datacenter <%= dc %>
|
82
|
+
<%
|
83
|
+
services(dc:dc, tag:'http').each do |service_name, tags|
|
84
|
+
%>
|
85
|
+
- service <%= service_name %> <%= tags.sort %><%
|
86
|
+
end
|
87
|
+
end
|
88
|
+
%>
|
89
|
+
```
|
90
|
+
|
91
|
+
</div>
|
92
|
+
</details>
|
93
|
+
|
27
94
|
## service(serviceName, [dc: datacenter], [tag: tagToFilterWith], [passing: true])
|
28
95
|
|
29
96
|
[List the instances](https://www.consul.io/api/health.html#list-nodes-for-service) of a service having the given
|
30
97
|
optional tag. If no tag is specified, will return all instances of service. By default, it will return all the
|
31
98
|
well services that are passing or not. An optional argument passing might be used to retrieve only passing instances.
|
32
99
|
|
100
|
+
<details><summary>Examples</summary>
|
101
|
+
<div class="samples">
|
102
|
+
|
103
|
+
### List all services instances with http tag on current DC, instances sorted by node name
|
104
|
+
|
105
|
+
```erb
|
106
|
+
<% services.each do |service_name, tags|
|
107
|
+
if tags.include? 'http'
|
108
|
+
%> ++ Service <%= service_name %>
|
109
|
+
<% service(service_name, tag:'http').sort {|a,b| a['Node']['Node'] <=> b['Node']['Node'] }.each do |snode|
|
110
|
+
%> * <%= service_name %> -> <%=
|
111
|
+
snode['Node']['Node'] %>:<%= snode['Service']['Port'] %> <%=
|
112
|
+
snode['Service']['Tags'] %> status: <%
|
113
|
+
snode['Checks'].each do |c| %> <%= c['Status']
|
114
|
+
%><% end if snode['Checks'] %>
|
115
|
+
<% end
|
116
|
+
end
|
117
|
+
end %>
|
118
|
+
```
|
119
|
+
|
120
|
+
Full example: [samples/consul_template.txt.erb](samples/consul_template.txt.erb)
|
121
|
+
|
122
|
+
</div>
|
123
|
+
</details>
|
124
|
+
|
33
125
|
## nodes([dc: datacenter])
|
34
126
|
|
35
127
|
[List all the nodes of selected datacenter](https://www.consul.io/api/catalog.html#list-nodes). No filtering is
|
36
128
|
applied.
|
37
129
|
|
130
|
+
<details><summary>Examples</summary>
|
131
|
+
<div class="samples">
|
132
|
+
|
133
|
+
### List all nodes for DC, sorted by name
|
134
|
+
|
135
|
+
```erb
|
136
|
+
<% nodes.sort {|a,b| a['Node'] <=> b['Node'] }.each do |snode|
|
137
|
+
%> * <%= snode['Address'].ljust(16) %> <%= snode['Node'] %>
|
138
|
+
<% end %>
|
139
|
+
```
|
140
|
+
|
141
|
+
Full example: [samples/consul_template.txt.erb](samples/consul_template.txt.erb)
|
142
|
+
|
143
|
+
</div>
|
144
|
+
</details>
|
145
|
+
|
38
146
|
## node(nodeNameOrId, [dc: datacenter])
|
39
147
|
|
40
148
|
[List all the services of a given Node](https://www.consul.io/api/catalog.html#list-services-for-node) using its
|
@@ -227,7 +227,7 @@ module Consul
|
|
227
227
|
|
228
228
|
class ConsulTemplateAbstract
|
229
229
|
extend Forwardable
|
230
|
-
def_delegators :result_delegate, :each, :[], :sort, :each_value, :count, :empty?
|
230
|
+
def_delegators :result_delegate, :each, :[], :sort, :select, :each_value, :count, :empty?
|
231
231
|
attr_reader :result, :endpoint, :seen_at
|
232
232
|
def initialize(consul_endpoint)
|
233
233
|
@endpoint = consul_endpoint
|
data/lib/consul/async/version.rb
CHANGED
@@ -0,0 +1,38 @@
|
|
1
|
+
<%
|
2
|
+
# This template can be configure the following way with environment variables
|
3
|
+
# CONSUL_TOOLS_SUFFIX: suffix for the address of consul tools
|
4
|
+
# CONSUL_TOOLS_PREFIX: prefix for the address of consul tools
|
5
|
+
# CONSUL_TOOLS: comma sperated list of consul tools
|
6
|
+
tools = (ENV['CONSUL_TOOLS'] || 'services').split(",")
|
7
|
+
suffix = ENV['CONSUL_TOOLS_PREFIX'] || '-ui.html'
|
8
|
+
prefix = ENV['CONSUL_TOOLS_SUFFIX'] || 'consul-'
|
9
|
+
%><!DOCTYPE html>
|
10
|
+
<html lang="en">
|
11
|
+
<head>
|
12
|
+
<meta charset="utf-8"/>
|
13
|
+
<meta name="viewport" content="width=device-width, initial-scale=1, shrink-to-fit=no"/>
|
14
|
+
<meta name="description" content="Display Consul information"/>
|
15
|
+
<meta name="author" content="Criteo"/>
|
16
|
+
<meta http-equiv="refresh" content="<%= param('refresh', ENV['REFRESH'] || '600') %>"/>
|
17
|
+
<title><%= param('title', 'Consul Real Time information') %></title>
|
18
|
+
<link rel="stylesheet" href="https://maxcdn.bootstrapcdn.com/bootstrap/4.0.0/css/bootstrap.min.css" integrity="sha384-Gn5384xqQ1aoWXA+058RXPxPg6fy4IWvTNh0E263XmFcJlSAwiGgFAW/dAiS6JXm" crossorigin="anonymous">
|
19
|
+
<!-- <link href="https://fonts.googleapis.com/icon?family=Material+Icons" rel="stylesheet"> -->
|
20
|
+
<link rel="stylesheet" href="https://use.fontawesome.com/releases/v5.0.11/css/all.css" integrity="sha384-p2jx59pefphTFIpeqCcISO9MdVfIm4pNnsL08A6v5vaQc4owkQqxMV8kg4Yvhaw/" crossorigin="anonymous">
|
21
|
+
<link rel="stylesheet" href="css/style.css">
|
22
|
+
</head>
|
23
|
+
<body>
|
24
|
+
<nav class="navbar navbar-expand-md navbar-dark bg-secondary">
|
25
|
+
<a class="navbar-brand" href="#">Consul</a>
|
26
|
+
<button class="navbar-toggler" type="button" data-toggle="collapse" data-target="#navbarsExampleDefault" aria-controls="navbarsExampleDefault" aria-expanded="false" aria-label="Toggle navigation">
|
27
|
+
<span class="navbar-brand">Consul</span>
|
28
|
+
</button>
|
29
|
+
<div class="collapse navbar-collapse" id="navbarsExampleDefault">
|
30
|
+
<ul class="navbar-nav mr-auto">
|
31
|
+
<% tools.each do |tool| %>
|
32
|
+
<li class="nav-item">
|
33
|
+
<a class="nav-link" href="<%= prefix + tool + suffix %>"><%= tool.gsub('_', ' ').capitalize %></a>
|
34
|
+
</li>
|
35
|
+
<% end %>
|
36
|
+
</ul>
|
37
|
+
</div>
|
38
|
+
</nav>
|
@@ -0,0 +1,31 @@
|
|
1
|
+
<% datasource = ENV['SERVICE_DATASOURCE'] || 'consul-template.json' %>
|
2
|
+
<%= render_file('common/header.html.erb', title: 'Services') %>
|
3
|
+
<div class="main">
|
4
|
+
<div class="row mx-0">
|
5
|
+
<div id="filter-menu" class="col-4 col-m-3 px-4 pt-4">
|
6
|
+
<div class="form-group">
|
7
|
+
<input id="service-filter" type="text" placeholder="filter" class="form-control" >
|
8
|
+
</div>
|
9
|
+
<div id="service-wrapper" >
|
10
|
+
<ul id="service-list" class="list-group">
|
11
|
+
</ul>
|
12
|
+
</div>
|
13
|
+
</div>
|
14
|
+
<div class="col-8 col-m-9">
|
15
|
+
<h2 class="text-center" id="service-title"></h2>
|
16
|
+
<div id="instances-wrapper">
|
17
|
+
<div id="instances-list" class="list-group"></div>
|
18
|
+
</div>
|
19
|
+
</div>
|
20
|
+
</div>
|
21
|
+
</div>
|
22
|
+
<!-- Optional JavaScript -->
|
23
|
+
<!-- JavaScript Dependencies: jQuery, Popper.js, Bootstrap JS, Shards JS -->
|
24
|
+
<script src="https://ajax.googleapis.com/ajax/libs/jquery/3.3.1/jquery.min.js"></script>
|
25
|
+
<script src="https://maxcdn.bootstrapcdn.com/bootstrap/4.0.0/js/bootstrap.min.js" integrity="sha384-JZR6Spejh4U02d8jOt6vLEHfe/JQGiRRSQQxSfFWpi1MquVdAyjUar5+76PVCmYl" crossorigin="anonymous"></script>
|
26
|
+
<script src="js/service.js"></script>
|
27
|
+
<script type="text/javascript">
|
28
|
+
consulService = new ConsulService('<%= datasource %>');
|
29
|
+
</script>
|
30
|
+
</body>
|
31
|
+
</html>
|
@@ -0,0 +1,89 @@
|
|
1
|
+
<%
|
2
|
+
# This template can be configure the following way with environment variables
|
3
|
+
# Environment variables to filter services/instances
|
4
|
+
# SERVICES_TAG_FILTER: basic tag filter for service (default HTTP)
|
5
|
+
# INSTANCE_MUST_TAG: Second level of filtering (optional, default to SERVICES_TAG_FILTER)
|
6
|
+
# INSTANCE_EXCLUDE_TAG: Exclude instances having the given tag (default: canary)
|
7
|
+
# EXCLUDE_SERVICES: comma-separated services to exclude (default: consul-agent-http,mesos-slave,mesos-agent-watcher)
|
8
|
+
|
9
|
+
service_tag_filter = ENV['SERVICES_TAG_FILTER'] || 'http'
|
10
|
+
instance_must_tag = ENV['INSTANCE_MUST_TAG'] || service_tag_filter
|
11
|
+
instance_exclude_tag = ENV['INSTANCE_EXCLUDE_TAG'] || 'canary'
|
12
|
+
|
13
|
+
# Services to hide
|
14
|
+
services_blacklist = (ENV['EXCLUDE_SERVICES'] || 'consul-agent-http,mesos-slave,mesos-agent-watcher,mesos-exporter-slave').split(',')
|
15
|
+
# Compute the health of a Service
|
16
|
+
def compute_state(snode)
|
17
|
+
states = ['passing', []]
|
18
|
+
snode['Checks'].each do |chk|
|
19
|
+
st = chk['Status']
|
20
|
+
states[1] << st
|
21
|
+
if st == 'critical'
|
22
|
+
states[0] = st
|
23
|
+
elsif st == 'warning' && states[0] == 'passing'
|
24
|
+
states[0] = st
|
25
|
+
end
|
26
|
+
end
|
27
|
+
states
|
28
|
+
end
|
29
|
+
def compute_attributes(snode)
|
30
|
+
w = 100
|
31
|
+
snode['Service']['Tags'].each do |tag|
|
32
|
+
match = /^weight-([1-9][0-9])*$/.match(tag)
|
33
|
+
w = match[1].to_i if match
|
34
|
+
end
|
35
|
+
attributes = ""
|
36
|
+
states = compute_state(snode)
|
37
|
+
attributes = "#{attributes} disabled" if states[0] == 'critical'
|
38
|
+
if states[0] == 'warning'
|
39
|
+
w = w / 8
|
40
|
+
end
|
41
|
+
attributes = "#{attributes} weight #{w}" if w.positive?
|
42
|
+
end
|
43
|
+
backends = {}
|
44
|
+
services(tag: service_tag_filter).each do |service_name, tags|
|
45
|
+
if !services_blacklist.include?(service_name) && tags.include?(instance_must_tag)
|
46
|
+
the_backends = []
|
47
|
+
service(service_name).sort {|a,b| a['Node']['Node'] <=> b['Node']['Node'] }.each do |snode|
|
48
|
+
tags_of_instance = snode['Service']['Tags']
|
49
|
+
if tags_of_instance.include?(instance_must_tag) && !tags_of_instance.include?(instance_exclude_tag)
|
50
|
+
the_backends << snode if snode['Service']['Port']
|
51
|
+
end
|
52
|
+
end
|
53
|
+
# We add the backend ONLY if at least one valid instance does exists
|
54
|
+
backends[service_name] = the_backends
|
55
|
+
end
|
56
|
+
end
|
57
|
+
%><%
|
58
|
+
json_backends = {}
|
59
|
+
backends.each_pair do |service_name, nodes|
|
60
|
+
service = {
|
61
|
+
name: service_name,
|
62
|
+
count: nodes.count,
|
63
|
+
instances: []
|
64
|
+
}
|
65
|
+
json_backends[service_name] = service
|
66
|
+
nodes.each do |snode|
|
67
|
+
checks = []
|
68
|
+
snode['Checks'].each do |ncheck|
|
69
|
+
check = {}
|
70
|
+
check['checkid'] = ncheck['CheckID']
|
71
|
+
check['name'] = ncheck['Name']
|
72
|
+
check['output'] = ncheck['Output']
|
73
|
+
check['status'] = ncheck['Status']
|
74
|
+
check['notes'] = ncheck['Notes']
|
75
|
+
checks.push(check)
|
76
|
+
end
|
77
|
+
server = { frontend_id: "backend_http__#{service_name}",
|
78
|
+
id: snode['Service']['ID'],
|
79
|
+
name: snode['Node']['Node'],
|
80
|
+
addr: snode['Node']['Address'],
|
81
|
+
port: snode['Service']['Port'],
|
82
|
+
tags: snode['Service']['Tags'],
|
83
|
+
checks: checks,
|
84
|
+
}
|
85
|
+
service[:instances] << server
|
86
|
+
end
|
87
|
+
end
|
88
|
+
json = { services: json_backends}
|
89
|
+
%><%= JSON.pretty_generate(json) %>
|
@@ -0,0 +1,57 @@
|
|
1
|
+
.btn:focus, .btn:active, button {
|
2
|
+
outline: none !important;
|
3
|
+
box-shadow: none;
|
4
|
+
}
|
5
|
+
|
6
|
+
#service-wrapper, #instances-wrapper {
|
7
|
+
overflow: scroll;
|
8
|
+
border-top-left-radius: 0px;
|
9
|
+
border-top-right-radius: 0px;
|
10
|
+
border-style: solid;
|
11
|
+
border-width: 1px 1px 1px 1px;
|
12
|
+
border-color: rgba(0,0,0,.125);
|
13
|
+
}
|
14
|
+
|
15
|
+
#service-wrapper .list-group-item:last-child, #instances-wrapper .list-group-item:last-child {
|
16
|
+
border-bottom-width: 0px;
|
17
|
+
border-bottom-left-radius: 0px;
|
18
|
+
border-bottom-right-radius: 0px;
|
19
|
+
}
|
20
|
+
|
21
|
+
#service-wrapper .list-group-item:first-child, #instances-wrapper .list-group-item:first-child {
|
22
|
+
border-top-width: 0px;
|
23
|
+
border-top-left-radius: 0px;
|
24
|
+
border-top-right-radius: 0px;
|
25
|
+
}
|
26
|
+
|
27
|
+
#service-wrapper .list-group-item, #instances-wrapper .list-group-item {
|
28
|
+
border-left: 0px;
|
29
|
+
border-right: 0px;
|
30
|
+
}
|
31
|
+
|
32
|
+
h2 {
|
33
|
+
margin-top: 1rem;
|
34
|
+
margin-bottom: 1rem;
|
35
|
+
}
|
36
|
+
|
37
|
+
h2 .fas {
|
38
|
+
cursor: pointer;
|
39
|
+
color: rgba( 0, 0, 0, 0.5);
|
40
|
+
font-size: 1.5rem;
|
41
|
+
transition: color .16s linear
|
42
|
+
}
|
43
|
+
|
44
|
+
h2 .fas:hover {
|
45
|
+
cursor: pointer;
|
46
|
+
color: #007bff;
|
47
|
+
opacity: 1;
|
48
|
+
font-size: 1.5rem;
|
49
|
+
}
|
50
|
+
|
51
|
+
.custom-links {
|
52
|
+
background-color: #EEEEEE;
|
53
|
+
color: #333333;
|
54
|
+
margin: 3px;
|
55
|
+
padding: 5px;
|
56
|
+
padding-left: 10px;
|
57
|
+
}
|
@@ -0,0 +1,206 @@
|
|
1
|
+
class ConsulService {
|
2
|
+
constructor(ressourceURL) {
|
3
|
+
this.ressourceURL = ressourceURL;
|
4
|
+
this.fetchRessource();
|
5
|
+
this.serviceList = $("#service-list");
|
6
|
+
this.serviceFilter = $("#service-filter");
|
7
|
+
this.serviceFilter.keyup(this.filterService)
|
8
|
+
}
|
9
|
+
|
10
|
+
fetchRessource() {
|
11
|
+
$.ajax({url: "consul_template.json", cache: false, dataType: "json", sourceObject: this, success: function(result){
|
12
|
+
if(this.sourceObject.data) {
|
13
|
+
// For autoupdate
|
14
|
+
} else {
|
15
|
+
this.sourceObject.initRessource(result);
|
16
|
+
}
|
17
|
+
}});
|
18
|
+
}
|
19
|
+
|
20
|
+
initRessource(data) {
|
21
|
+
this.data = data;
|
22
|
+
this.reloadServiceList();
|
23
|
+
var urlParam = new URL(location.href).searchParams.get('service')
|
24
|
+
if(urlParam) {
|
25
|
+
var nodes = document.getElementById('service-list').childNodes;
|
26
|
+
for(var i in nodes) {
|
27
|
+
if($(nodes[i]).html() == urlParam) {
|
28
|
+
this.selectService(nodes[i]);
|
29
|
+
break;
|
30
|
+
}
|
31
|
+
}
|
32
|
+
} else {
|
33
|
+
this.selectService(document.getElementById('service-list').firstElementChild);
|
34
|
+
}
|
35
|
+
}
|
36
|
+
|
37
|
+
reloadServiceList() {
|
38
|
+
for (var service in this.data.services) {
|
39
|
+
var listItem = '<button type="button" onclick="consulService.onClickServiceName(this)" class="list-group-item list-group-item-action">';
|
40
|
+
listItem += service;
|
41
|
+
listItem += '</button>';
|
42
|
+
this.serviceList.append(listItem);
|
43
|
+
}
|
44
|
+
resizeWrapper('service-wrapper', 'service-list');
|
45
|
+
}
|
46
|
+
|
47
|
+
filterService(e) {
|
48
|
+
var filter = new RegExp(e.target.value);
|
49
|
+
consulService.serviceList.children('button').each(function (){
|
50
|
+
if($(this).html().match(filter)) {
|
51
|
+
$(this).removeClass('d-none');
|
52
|
+
$(this).addClass('d-block');
|
53
|
+
} else {
|
54
|
+
$(this).removeClass('d-block');
|
55
|
+
$(this).addClass('d-none');
|
56
|
+
}
|
57
|
+
})
|
58
|
+
}
|
59
|
+
|
60
|
+
onClickServiceName(source) {
|
61
|
+
this.selectService(source);
|
62
|
+
this.updateURL();
|
63
|
+
}
|
64
|
+
|
65
|
+
updateURL() {
|
66
|
+
var newUrl = window.location.protocol + "//" + window.location.host + window.location.pathname;
|
67
|
+
newUrl += '?service=' + $(this.selectedService).html();
|
68
|
+
window.history.pushState({},"",newUrl);
|
69
|
+
}
|
70
|
+
|
71
|
+
selectService(source) {
|
72
|
+
if (this.selectedService) {
|
73
|
+
$(this.selectedService).removeClass('active');
|
74
|
+
}
|
75
|
+
this.selectedService = source;
|
76
|
+
$(this.selectedService).addClass('active');
|
77
|
+
|
78
|
+
var serviceName = $(source).html();
|
79
|
+
|
80
|
+
this.displayService(this.data.services[serviceName]);
|
81
|
+
}
|
82
|
+
|
83
|
+
displayService(service) {
|
84
|
+
$("#service-title").html(service['name']);
|
85
|
+
$("#instances-list").html("")
|
86
|
+
|
87
|
+
for (var key in service['instances']) {
|
88
|
+
var instance = service['instances'][key];
|
89
|
+
var serviceHtml = document.createElement('div');
|
90
|
+
serviceHtml.setAttribute('class','list-group-item');
|
91
|
+
|
92
|
+
serviceHtml.appendChild(serviceTitleGenerator(instance));
|
93
|
+
serviceHtml.appendChild(tagsGenerator(instance));
|
94
|
+
serviceHtml.appendChild(checksStatusGenerator(instance));
|
95
|
+
|
96
|
+
$("#instances-list").append(serviceHtml);
|
97
|
+
}
|
98
|
+
|
99
|
+
resizeWrapper('instances-wrapper', 'instances-list');
|
100
|
+
$('#instances-list .list-group-item').resize(resizeAll);
|
101
|
+
}
|
102
|
+
}
|
103
|
+
|
104
|
+
function serviceTitleGenerator(instance) {
|
105
|
+
var protocol = 'http://';
|
106
|
+
if(instance.tags.includes('https')) {
|
107
|
+
protocol = 'https://';
|
108
|
+
}
|
109
|
+
|
110
|
+
var htmlTitle = document.createElement('h5');
|
111
|
+
|
112
|
+
var instanceLink = document.createElement('a');
|
113
|
+
instanceLink.setAttribute('href', protocol + instance.name + ':' + instance.port);
|
114
|
+
instanceLink.setAttribute('target', '_blank');
|
115
|
+
instanceLink.appendChild(document.createTextNode(instance.name + ':' + instance.port));
|
116
|
+
htmlTitle.appendChild(instanceLink);
|
117
|
+
|
118
|
+
return htmlTitle;
|
119
|
+
}
|
120
|
+
|
121
|
+
function tagsGenerator(instance) {
|
122
|
+
var tags = document.createElement('div');
|
123
|
+
|
124
|
+
tags.className = 'tags';
|
125
|
+
tags.appendChild(document.createTextNode("Tags: "));
|
126
|
+
tags.appendChild(document.createElement('br'));
|
127
|
+
|
128
|
+
for (var tagKey in instance.tags) {
|
129
|
+
var tag = document.createElement('span');
|
130
|
+
tag.setAttribute('class', 'badge badge-secondary mx-1');
|
131
|
+
tag.appendChild(document.createTextNode(instance.tags[tagKey]));
|
132
|
+
tags.appendChild(tag);
|
133
|
+
}
|
134
|
+
return tags;
|
135
|
+
}
|
136
|
+
|
137
|
+
function checksStatusGenerator(instance) {
|
138
|
+
var checks = document.createElement('div');
|
139
|
+
checks.className = 'checks';
|
140
|
+
checks.appendChild(document.createTextNode("Checks: "));
|
141
|
+
checks.appendChild(document.createElement('br'));
|
142
|
+
|
143
|
+
for (var checkKey in instance.checks) {
|
144
|
+
checkId = Math.floor(Math.random()*10000);
|
145
|
+
switch(instance.checks[checkKey]['status']) {
|
146
|
+
case 'passing': var btn = 'btn-success'; break;
|
147
|
+
case 'warning': var btn = 'btn-warning'; break;
|
148
|
+
case 'critical': var btn = 'btn-danger'; break;
|
149
|
+
}
|
150
|
+
var check = document.createElement('div');
|
151
|
+
|
152
|
+
var btnCheck = document.createElement('button');
|
153
|
+
btnCheck.setAttribute('class','btn ' + btn + ' btn-sm m-1');
|
154
|
+
btnCheck.setAttribute('type', 'button');
|
155
|
+
btnCheck.setAttribute('data-toggle', 'collapse');
|
156
|
+
btnCheck.setAttribute('data-target', '#' + checkId);
|
157
|
+
btnCheck.setAttribute('aria-expanded', 'false');
|
158
|
+
|
159
|
+
btnCheck.appendChild(document.createTextNode(instance.checks[checkKey]['name']));
|
160
|
+
|
161
|
+
check.appendChild(btnCheck);
|
162
|
+
|
163
|
+
var collapseCheck = document.createElement('div');
|
164
|
+
collapseCheck.setAttribute('class', 'collapse')
|
165
|
+
collapseCheck.setAttribute('id', checkId)
|
166
|
+
|
167
|
+
var cardCheck = document.createElement('div');
|
168
|
+
cardCheck.setAttribute('class', 'card card-body p-3 m-1 mb-2');
|
169
|
+
|
170
|
+
var notes = document.createElement('table');
|
171
|
+
notes.setAttribute('class', 'table table-hover mb-0');
|
172
|
+
|
173
|
+
var dataToDisplay = ['notes', 'output'];
|
174
|
+
for (var index in dataToDisplay) {
|
175
|
+
var tr = document.createElement('tr');
|
176
|
+
var td1 = document.createElement('td');
|
177
|
+
var td2 = document.createElement('td');
|
178
|
+
var fieldName = dataToDisplay[index].replace(/\b\w/g, l => l.toUpperCase());
|
179
|
+
td1.appendChild(document.createTextNode(fieldName + ': '));
|
180
|
+
td2.appendChild(document.createTextNode(instance.checks[checkKey][dataToDisplay[index]]));
|
181
|
+
tr.appendChild(td1);
|
182
|
+
tr.appendChild(td2);
|
183
|
+
notes.appendChild(tr);
|
184
|
+
}
|
185
|
+
|
186
|
+
cardCheck.appendChild(notes);
|
187
|
+
collapseCheck.appendChild(cardCheck);
|
188
|
+
check.appendChild(collapseCheck);
|
189
|
+
checks.appendChild(check)
|
190
|
+
}
|
191
|
+
|
192
|
+
return checks;
|
193
|
+
}
|
194
|
+
|
195
|
+
function resizeAll() {
|
196
|
+
resizeWrapper('service-wrapper', 'service-list');
|
197
|
+
resizeWrapper('instances-wrapper', 'instances-list');
|
198
|
+
}
|
199
|
+
|
200
|
+
function resizeWrapper(wrapperId, wrapped) {
|
201
|
+
var size = $(window).height() - $('#' + wrapperId).offset()["top"] - 20;
|
202
|
+
$('#' + wrapperId).css("height", size);
|
203
|
+
}
|
204
|
+
|
205
|
+
$( window ).resize(resizeAll);
|
206
|
+
resizeAll();
|
data/samples/nodes.html.erb
CHANGED
@@ -1,14 +1,45 @@
|
|
1
|
-
<%= render_file('common/header.html.erb', title: 'Nodes')
|
1
|
+
<%= render_file('common/header.html.erb', title: 'Nodes') %><%
|
2
|
+
service_per_node = {}
|
3
|
+
services.each do |service_name, tags|
|
4
|
+
service(service_name, tag:'http').sort {|a,b| a['Node']['Node'] <=> b['Node']['Node'] }.each do |snode|
|
5
|
+
node_services = service_per_node[snode['Node']['Node']] || []
|
6
|
+
node_services.push(snode)
|
7
|
+
service_per_node[snode['Node']['Node']] = node_services
|
8
|
+
end
|
9
|
+
end %>
|
10
|
+
|
2
11
|
<h1 id="nodes">List all nodes for DC, sorted by name</h1>
|
3
|
-
<ul>
|
12
|
+
<ul class="list-group">
|
4
13
|
<% nodes.sort {|a,b| a['Node'] <=> b['Node'] }.each do |snode|
|
5
|
-
%><li id="node_<%= snode['ID'] %>"
|
14
|
+
%><li id="node_<%= snode['ID'] %>" class="list-group-item">
|
15
|
+
<a href="ssh://<%= snode['Address']%>"><%= snode['Address'] %></a> <%= snode['Node'] %><%
|
6
16
|
snode['Meta'].each do |k,v|
|
7
17
|
if v && !v.empty?
|
8
18
|
%><span class="badge badge-pill badge-primary float-right"><%= k %>: <%= v%></span><%
|
9
19
|
end
|
10
20
|
end if snode['Meta']
|
11
|
-
|
21
|
+
%><div><%
|
22
|
+
if service_per_node.key?(snode['Node'])
|
23
|
+
service_per_node[snode['Node']].each do |service|
|
24
|
+
tags = service['Service']['Tags'].sort
|
25
|
+
addr = service['Node']['Address']
|
26
|
+
port_num = service['Service']['Port'].to_i
|
27
|
+
port = port_num && port_num > 0 ? ":#{port_num}" : ''
|
28
|
+
url = if tags.include? 'https'
|
29
|
+
"https://#{addr}#{port}"
|
30
|
+
elsif tags.include? 'http'
|
31
|
+
"http://#{addr}#{port}"
|
32
|
+
elsif tags.include? 'ftp'
|
33
|
+
"ftp://#{addr}#{port}"
|
34
|
+
else
|
35
|
+
nil
|
36
|
+
end
|
37
|
+
%> <a class="badge badge-secondary" href='<%= url %>'>
|
38
|
+
<%= service['Service']['Service'] %>: <%= port_num %>
|
39
|
+
</a>
|
40
|
+
<% end
|
41
|
+
end
|
42
|
+
%></div></li>
|
12
43
|
<% end %>
|
13
44
|
</ul>
|
14
|
-
<%= render_file 'common/footer.html.erb' %>
|
45
|
+
<%= render_file 'common/footer.html.erb' %>
|
metadata
CHANGED
@@ -1,14 +1,14 @@
|
|
1
1
|
--- !ruby/object:Gem::Specification
|
2
2
|
name: consul-templaterb
|
3
3
|
version: !ruby/object:Gem::Version
|
4
|
-
version: 1.0.
|
4
|
+
version: 1.0.10
|
5
5
|
platform: ruby
|
6
6
|
authors:
|
7
7
|
- SRE Core Services
|
8
8
|
autorequire:
|
9
9
|
bindir: bin
|
10
10
|
cert_chain: []
|
11
|
-
date: 2018-
|
11
|
+
date: 2018-05-04 00:00:00.000000000 Z
|
12
12
|
dependencies:
|
13
13
|
- !ruby/object:Gem::Dependency
|
14
14
|
name: em-http-request
|
@@ -158,6 +158,11 @@ files:
|
|
158
158
|
- samples/checks.html.erb
|
159
159
|
- samples/common/footer.html.erb
|
160
160
|
- samples/common/header.html.erb
|
161
|
+
- samples/consul-ui/common/header.html.erb
|
162
|
+
- samples/consul-ui/consul-services-ui.html.erb
|
163
|
+
- samples/consul-ui/consul_template.json.erb
|
164
|
+
- samples/consul-ui/css/style.css
|
165
|
+
- samples/consul-ui/js/service.js
|
161
166
|
- samples/consul_template.html.erb
|
162
167
|
- samples/consul_template.json.erb
|
163
168
|
- samples/consul_template.txt.erb
|