consul-templaterb 1.0.9 → 1.0.10
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/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
|