easel-dashboard 0.4.3 → 0.5
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/bin/easel +0 -1
- data/lib/easel/build_pages.rb +56 -5
- data/lib/easel/configuration.rb +54 -6
- data/lib/easel/controller.rb +21 -0
- data/lib/easel/data_gathering.rb +144 -0
- data/lib/easel/server.rb +21 -6
- data/lib/easel/websocket.rb +140 -34
- data/lib/html/app.css.erb +77 -43
- data/lib/html/app.html.erb +62 -147
- data/lib/html/app.js.erb +177 -0
- data/lib/html/controller.js.erb +136 -0
- metadata +38 -9
data/lib/html/app.css.erb
CHANGED
@@ -46,17 +46,16 @@ html, body {
|
|
46
46
|
span.stderr { color: var(--stderr-colour); }
|
47
47
|
span.stdout { color: var(--stdout-colour); }
|
48
48
|
|
49
|
-
h1
|
50
|
-
h2
|
51
|
-
h3
|
52
|
-
h4
|
49
|
+
h1 {font-size: var(--text-size-xlarge); font-weight: var(--text-weight-bold);}
|
50
|
+
h2 {font-size: var(--text-size-large); font-weight: var(--text-weight-bold);}
|
51
|
+
h3 {font-size: var(--text-size-large); font-weight: var(--text-weight-semi-bold);}
|
52
|
+
h4 {font-size: var(--text-size); font-weight: var(--text-weight-semi-bold);}
|
53
53
|
|
54
|
+
a { color: inherit; text-decoration: none; }
|
54
55
|
*{padding: 0; margin: 0;}
|
55
56
|
|
56
|
-
|
57
|
-
|
58
|
-
.screen {
|
59
|
-
height: 100vh;
|
57
|
+
.interface {
|
58
|
+
height: calc(100vh - var(--size-xlarge));
|
60
59
|
width: 100vw;
|
61
60
|
display: flex;
|
62
61
|
flex-direction: row;
|
@@ -64,24 +63,26 @@ h4, .h4 {font-size: var(--text-size); font-weight: var(--text-weight-semi-bold);
|
|
64
63
|
align-items: stretch;
|
65
64
|
}
|
66
65
|
|
67
|
-
.
|
66
|
+
.dashboard-selector {
|
68
67
|
width: 320px;
|
69
68
|
height: 100%;
|
70
|
-
overflow-y:
|
69
|
+
overflow-y: auto;
|
70
|
+
direction: rtl;
|
71
71
|
}
|
72
|
-
.cmd-picker .command:not(:last-child) {border-bottom: solid 1px var(--accent);}
|
73
72
|
|
74
|
-
.
|
73
|
+
.selector-card{
|
74
|
+
direction: ltr;
|
75
75
|
display: flex;
|
76
76
|
flex-direction: column;
|
77
77
|
background-color: var(--surface);
|
78
78
|
border-radius: var(--size-small);
|
79
79
|
align-items: stretch;
|
80
|
-
margin: var(--size-
|
80
|
+
margin: var(--size-large);
|
81
81
|
overflow: hidden;
|
82
|
+
box-shadow: 0 var(--size-medium) var(--size-large) var(--shadow);
|
82
83
|
}
|
83
84
|
|
84
|
-
.
|
85
|
+
.card-info {
|
85
86
|
background-color: var(--surface);
|
86
87
|
color: var(--on-surface);
|
87
88
|
padding: var(--size-medium);
|
@@ -94,9 +95,9 @@ h4, .h4 {font-size: var(--text-size); font-weight: var(--text-weight-semi-bold);
|
|
94
95
|
box-shadow: 0 0 var(--size-small) var(--shadow);
|
95
96
|
cursor: pointer;
|
96
97
|
}
|
97
|
-
.
|
98
|
+
.card-info:hover { color: var(--primary); }
|
98
99
|
|
99
|
-
.
|
100
|
+
.card-controls {
|
100
101
|
display: flex;
|
101
102
|
flex-direction: row;
|
102
103
|
justify-content: stretch;
|
@@ -104,14 +105,14 @@ h4, .h4 {font-size: var(--text-size); font-weight: var(--text-weight-semi-bold);
|
|
104
105
|
box-shadow: inset 0 var(--size-tiny) var(--size-small) #000000;
|
105
106
|
}
|
106
107
|
|
107
|
-
.
|
108
|
+
.card-icon {
|
108
109
|
flex-grow: 2;
|
109
110
|
text-align: center;
|
110
111
|
padding: var(--size-tiny);
|
111
112
|
background-color: var(--primary);
|
112
113
|
color: var(--on-primary);
|
113
114
|
}
|
114
|
-
.
|
115
|
+
.card-icon:hover {
|
115
116
|
background-color: var(--secondary);
|
116
117
|
color: var(--on-secondary);
|
117
118
|
cursor: pointer;
|
@@ -120,7 +121,7 @@ h4, .h4 {font-size: var(--text-size); font-weight: var(--text-weight-semi-bold);
|
|
120
121
|
::-webkit-scrollbar {
|
121
122
|
width: var(--size-small);
|
122
123
|
height: var(--size-small);
|
123
|
-
background-color:
|
124
|
+
background-color: none;
|
124
125
|
}
|
125
126
|
::-webkit-scrollbar-thumb {
|
126
127
|
background: var(--primary);
|
@@ -132,47 +133,78 @@ h4, .h4 {font-size: var(--text-size); font-weight: var(--text-weight-semi-bold);
|
|
132
133
|
|
133
134
|
|
134
135
|
|
136
|
+
.header {
|
137
|
+
width: 100vw;
|
138
|
+
height: var(--size-xlarge);
|
139
|
+
background-color: var(--surface);
|
140
|
+
z-index: 1;
|
141
|
+
box-shadow: 0 0 var(--size-medium) var(--shadow);
|
142
|
+
display: flex;
|
143
|
+
flex-direction: row;
|
144
|
+
justify-content: flex-start;
|
145
|
+
align-items: center;
|
146
|
+
}
|
147
|
+
.header-logo {
|
148
|
+
margin-left: var(--size-large);
|
149
|
+
color: var(--primary);
|
150
|
+
}
|
151
|
+
.on-hover-secondary:hover {
|
152
|
+
color: var(--secondary);
|
153
|
+
}
|
135
154
|
|
136
155
|
|
137
156
|
|
138
|
-
|
139
|
-
|
140
|
-
|
157
|
+
#dashboard-wrapper {
|
158
|
+
overflow: hidden;
|
159
|
+
border-radius: var(--size-small);
|
160
|
+
margin: var(--size-large);
|
161
|
+
width: calc(100vw - calc(320px + calc(3 * var(--size-small))));
|
162
|
+
box-shadow: 0 var(--size-medium) var(--size-large) var(--shadow);
|
163
|
+
}
|
164
|
+
.dashboard {
|
141
165
|
flex-grow: 2;
|
142
166
|
display: flex;
|
143
167
|
flex-direction: column;
|
144
168
|
justify-content: stretch;
|
145
169
|
align-items: stretch;
|
146
|
-
|
147
|
-
border-radius: var(--size-small);
|
148
|
-
padding: var(--size-medium);
|
149
|
-
margin: var(--size-small);
|
170
|
+
height: 100%;
|
150
171
|
}
|
151
|
-
|
152
|
-
|
172
|
+
|
173
|
+
.dashboard-title-wrapper {
|
174
|
+
margin: var(--size-medium);
|
175
|
+
display: flex;
|
176
|
+
flex-direction: row;
|
177
|
+
justify-content: space-between;
|
178
|
+
align-items: baseline;
|
153
179
|
}
|
154
|
-
.
|
180
|
+
.dashboard-main-text {
|
155
181
|
flex-grow: 2;
|
156
182
|
flex-basis: 0;
|
157
183
|
padding: var(--size-medium);
|
158
|
-
|
159
|
-
|
160
|
-
|
161
|
-
display: flex;
|
162
|
-
flex-direction: row;
|
163
|
-
justify-content: flex-end;
|
164
|
-
padding-top: var(--size-medium);
|
184
|
+
background-color: var(--background);
|
185
|
+
box-shadow: inset 0 0 var(--size-medium) var(--shadow);
|
186
|
+
margin: 0 var(--size-medium) 0;
|
165
187
|
}
|
166
|
-
.
|
167
|
-
|
188
|
+
.dashboard-main-graphics {
|
189
|
+
flex-grow: 2;
|
190
|
+
flex-basis: 0;
|
191
|
+
margin: 0 var(--size-medium) 0;
|
192
|
+
display: grid;
|
193
|
+
overflow: scroll;
|
194
|
+
grid-template-columns: 1fr 1fr;
|
195
|
+
grid-template-rows: 1fr 1fr;
|
196
|
+
}
|
197
|
+
.dashboard-footer {
|
198
|
+
margin-top: var(--size-medium);
|
199
|
+
width: 100%;
|
168
200
|
position: relative;
|
169
201
|
display: flex;
|
170
202
|
flex-direction: row;
|
171
203
|
justify-content: stretch;
|
172
|
-
border-radius: var(--size-small);
|
173
204
|
overflow: hidden;
|
205
|
+
border-top: solid 1px var(--primary);
|
174
206
|
}
|
175
|
-
.
|
207
|
+
.dashboard-button {
|
176
208
|
flex-grow: 2;
|
177
209
|
flex-basis: 0;
|
178
210
|
flex-shrink: 2;
|
@@ -182,7 +214,7 @@ h4, .h4 {font-size: var(--text-size); font-weight: var(--text-weight-semi-bold);
|
|
182
214
|
color: var(--on-primary);
|
183
215
|
overflow: hidden;
|
184
216
|
}
|
185
|
-
.
|
217
|
+
.dashboard-button:hover {
|
186
218
|
background-color: var(--secondary);
|
187
219
|
color: var(--on-secondary);
|
188
220
|
cursor: pointer;
|
@@ -190,15 +222,17 @@ h4, .h4 {font-size: var(--text-size); font-weight: var(--text-weight-semi-bold);
|
|
190
222
|
.top-shadow {
|
191
223
|
position: absolute;
|
192
224
|
width: 100%;
|
193
|
-
transform: translate(
|
225
|
+
transform: translate(0,calc( -1 * calc( var(--size-large))));
|
194
226
|
height: var(--size-large);
|
195
227
|
background-color: none;
|
196
228
|
z-index: 50;
|
197
229
|
box-shadow: 0 0 var(--size-small) var(--shadow);
|
198
230
|
}
|
231
|
+
.chart-wrapper {
|
232
|
+
position: relative;
|
233
|
+
}
|
199
234
|
|
200
235
|
|
201
236
|
.surface{ background-color: var(--surface); color: var(--on-surface); }
|
202
237
|
.background{ background-color: var(--background); color: var(--on-background); }
|
203
|
-
|
204
238
|
.scroll-content { min-height: min-content; overflow: auto; }
|
data/lib/html/app.html.erb
CHANGED
@@ -1,4 +1,18 @@
|
|
1
|
-
|
1
|
+
<%#
|
2
|
+
Notes on the refactor for HTML/CSS stuff:
|
3
|
+
- Think about how I want to build the CSS/JS/HTML stuff. Do I want to send
|
4
|
+
as one file? Multiple different ones?
|
5
|
+
- Fix the ugliness that is the HTML for the main interface. Build it initially
|
6
|
+
as an empty <div> that has the write surface/interface classes, and is
|
7
|
+
positioned properly. Then use JS to load the first dashboard (or the first)
|
8
|
+
command.
|
9
|
+
- Why is there a span.h2 instead of just an h2? the line break. Fix with
|
10
|
+
flex?
|
11
|
+
- Why do commands and dashboards need different html? Can't it just be a
|
12
|
+
different onclick (and I guess remove the 'Run' button)?
|
13
|
+
- Need to show state a little better - how to highlight the current
|
14
|
+
dashboard or command?
|
15
|
+
%><!DOCTYPE html>
|
2
16
|
<html lang="en" dir="ltr">
|
3
17
|
<head>
|
4
18
|
<meta charset="utf-8">
|
@@ -6,176 +20,77 @@
|
|
6
20
|
<%= $config[:title] %>
|
7
21
|
</title>
|
8
22
|
<link rel="stylesheet" href="/app.css">
|
9
|
-
<script
|
10
|
-
|
11
|
-
|
12
|
-
|
13
|
-
let ws_socket = new WebSocket("ws://" + location.hostname + ":" + location.port);
|
14
|
-
let current_cmd = null;
|
15
|
-
let keep_pane_contents = false; // Remove default contents when you run a command.
|
16
|
-
|
17
|
-
let streams = { <%$config[:commands].each do |command|%>
|
18
|
-
<%= command[:id] %>: { is_running: false, content: "", name: "<%= command[:name] %>" },<% end %>
|
19
|
-
};
|
20
|
-
|
21
|
-
// Receive message
|
22
|
-
ws_socket.onmessage = (event) => {
|
23
|
-
|
24
|
-
pane = document.getElementById('output-pane');
|
25
|
-
console.log("Received: " + event.data);
|
26
|
-
|
27
|
-
// Clear pane if appropriate
|
28
|
-
if (!keep_pane_contents) {
|
29
|
-
pane.innerHTML = "";
|
30
|
-
keep_pane_contents = true;
|
31
|
-
}
|
32
|
-
|
33
|
-
// Split message into [ID, CMD, MSG]
|
34
|
-
msg_frags = event.data.split(":");
|
35
|
-
id = msg_frags[0];
|
36
|
-
cmd_type = msg_frags[1];
|
37
|
-
msg = msg_frags.slice(2).join(":");
|
38
|
-
|
39
|
-
|
40
|
-
// Validate
|
41
|
-
if (!(id in streams) || !(["ERR", "OUT", "CLEAR", "FINISHED"].includes(cmd_type)) ) {
|
42
|
-
console.log("Error validating message: " + events.data);
|
43
|
-
return;
|
44
|
-
}
|
45
|
-
|
46
|
-
// Process message
|
47
|
-
switch (cmd_type) {
|
48
|
-
case "ERR":
|
49
|
-
append_stream_content(id, "<span class=\"stderr\">" + msg + "</span>");
|
50
|
-
break;
|
51
|
-
case "OUT":
|
52
|
-
append_stream_content(id, msg);
|
53
|
-
break;
|
54
|
-
case "CLEAR":
|
55
|
-
streams[id]['content'] = "";
|
56
|
-
update_pane();
|
57
|
-
break;
|
58
|
-
case "FINISHED":
|
59
|
-
document.getElementById('cmd-' + id + '-run-icon').innerHTML = "Run";
|
60
|
-
streams[id]['is_running'] = false;
|
61
|
-
update_pane();
|
62
|
-
break;
|
63
|
-
default:
|
64
|
-
console.log("Error: Message not understood. Id: " + id + ", cmd_type: " + cmd_type + ", msg:" + msg);
|
65
|
-
}
|
66
|
-
}
|
67
|
-
|
68
|
-
// append_stream_content
|
69
|
-
function append_stream_content(id, content){
|
70
|
-
console.log("Id: " + id + ", current_cmd: " + current_cmd + ", content: " + content);
|
71
|
-
streams[id]['content'] += content;
|
72
|
-
update_pane();
|
73
|
-
}
|
74
|
-
|
75
|
-
function toggle_run(id){
|
76
|
-
if (streams[id]['is_running']) { // STOP (already running)
|
77
|
-
ws_socket.send("STOP:" + id);
|
78
|
-
document.getElementById('cmd-' + id + '-run-icon').innerHTML = "Run";
|
79
|
-
} else { // RUN (currently not running)
|
80
|
-
streams[id]['content'] = "";
|
81
|
-
ws_socket.send("RUN:" + id);
|
82
|
-
document.getElementById('cmd-' + id + '-run-icon').innerHTML = "Stop";
|
83
|
-
current_cmd = id;
|
84
|
-
}
|
85
|
-
streams[id]['is_running'] = !streams[id]['is_running'];
|
86
|
-
update_pane();
|
87
|
-
}
|
88
|
-
|
89
|
-
function load_pane(id) {
|
90
|
-
current_cmd = id;
|
91
|
-
update_pane();
|
92
|
-
}
|
93
|
-
|
94
|
-
function update_pane() {
|
95
|
-
pane = document.getElementById('output-pane');
|
96
|
-
pane_state = document.getElementById('interface-state');
|
97
|
-
pane_state_details = document.getElementById('interface-state-details');
|
98
|
-
|
99
|
-
pane.innerHTML = streams[current_cmd]['content'];
|
100
|
-
if (streams[current_cmd]['is_running']) {
|
101
|
-
pane_state.innerHTML = "Running: ";
|
102
|
-
} else {
|
103
|
-
pane_state.innerHTML = "Output Of: ";
|
104
|
-
}
|
105
|
-
pane_state_details.innerHTML = streams[current_cmd]['name'];
|
106
|
-
}
|
107
|
-
|
108
|
-
function show_help_message() {
|
109
|
-
current_cmd = null;
|
110
|
-
pane = document.getElementById('output-pane');
|
111
|
-
pane_state = document.getElementById('interface-state');
|
112
|
-
pane_state_details = document.getElementById('interface-state-details');
|
113
|
-
|
114
|
-
|
115
|
-
pane.innerHTML =
|
116
|
-
"Welcome to the CDash Dashboard! Programs are set up via a YAML file on the server.\n\n" +
|
117
|
-
"You can run these programs by clicking 'Run' under a program's information.\n\n" +
|
118
|
-
"Programs' output will update even if you are looking at the output of another program.\n\n" +
|
119
|
-
"Use the 'Show' button to select which programs' output you are seeing.";
|
120
|
-
pane_state.innerHTML = "Showing: ";
|
121
|
-
pane_state_details = "Help Message";
|
122
|
-
}
|
123
|
-
|
124
|
-
</script>
|
23
|
+
<script src="https://cdn.jsdelivr.net/npm/chart.js@3.5.1/dist/chart.min.js"></script>
|
24
|
+
<script src="dashboardElements.js" charset="utf-8"></script>
|
25
|
+
<script src="createComponents.js" charset="utf-8"></script>
|
26
|
+
<script src="controller.js" charset="utf-8"></script>
|
125
27
|
</head>
|
126
28
|
<body>
|
127
|
-
<div class="
|
128
|
-
<div class="
|
29
|
+
<div class="header">
|
30
|
+
<div class="header-logo h2">
|
31
|
+
<%= $config[:header_logo] %>
|
32
|
+
<%= $config[:header_title] %>
|
33
|
+
</div>
|
34
|
+
</div>
|
35
|
+
<div class="interface">
|
36
|
+
<div class="dashboard-selector">
|
37
|
+
|
38
|
+
<%# GraphicDashboards %>
|
39
|
+
<% $config[:dashboards].each do |dashboard| %>
|
40
|
+
<div class="selector-card">
|
41
|
+
<div class="card-info"
|
42
|
+
onclick="load_dashboard('<%= dashboard[:id] %>')"
|
43
|
+
id="dash-<%=dashboard[:id]%>"
|
44
|
+
data-id="<%=dashboard[:id]%>">
|
45
|
+
<h2><%= dashboard[:name] %></h2>
|
46
|
+
<p><%= dashboard[:desc] %></p>
|
47
|
+
</div>
|
48
|
+
<div class="card-controls">
|
49
|
+
<div class="card-icon"
|
50
|
+
id="cmd-<%=dashboard[:id]%>-log-icon"
|
51
|
+
onclick="load_dashboard('<%= dashboard[:id] %>')">
|
52
|
+
Show
|
53
|
+
</div>
|
54
|
+
</div>
|
55
|
+
</div>
|
56
|
+
<% end %>
|
129
57
|
|
58
|
+
<%# Commands %>
|
130
59
|
<% $config[:commands].each do |command| %>
|
131
|
-
<div class="
|
132
|
-
<div class="
|
133
|
-
onclick="
|
60
|
+
<div class="selector-card">
|
61
|
+
<div class="card-info"
|
62
|
+
onclick="load_dashboard(<%=command[:id]%>)"
|
134
63
|
id="cmd-<%=command[:id]%>"
|
135
64
|
data-id="<%=command[:id]%>"
|
136
65
|
data-cmd="<%=command[:cmd]%>">
|
137
66
|
<h2><%= command[:name] %></h2>
|
138
67
|
<p><%= command[:desc] %></p>
|
139
68
|
</div>
|
140
|
-
<div class="
|
141
|
-
<div class="
|
69
|
+
<div class="card-controls">
|
70
|
+
<div class="card-icon"
|
142
71
|
id="cmd-<%=command[:id]%>-run-icon"
|
143
72
|
onclick="toggle_run(<%= command[:id] %>)">
|
144
73
|
Run
|
145
74
|
</div>
|
146
|
-
<div class="
|
75
|
+
<div class="card-icon"
|
147
76
|
id="cmd-<%=command[:id]%>-log-icon"
|
148
|
-
onclick="
|
77
|
+
onclick="load_dashboard(<%=command[:id]%>)">
|
149
78
|
Show
|
150
79
|
</div>
|
151
80
|
</div>
|
152
81
|
</div>
|
153
82
|
<% end %>
|
83
|
+
|
154
84
|
</div>
|
155
|
-
<div class="interface surface">
|
156
|
-
<div id="interface-title">
|
157
|
-
<span class="h2" id="interface-state">Waiting:</span>
|
158
|
-
<span id="interface-state-details"></span>
|
159
|
-
</div>
|
160
|
-
<div class="interface-output scroll-content">
|
161
|
-
<pre class="output-pane" id="output-pane">Click 'Run' to run a command and see it's output.
|
162
85
|
|
163
|
-
|
164
|
-
|
165
|
-
|
166
|
-
<div class="interface-commands">
|
167
|
-
<div class="top-shadow"></div>
|
168
|
-
<div class="interface-button"
|
169
|
-
onclick="show_help_message()">
|
170
|
-
Help
|
171
|
-
</div>
|
172
|
-
<div class="interface-button"
|
173
|
-
onclick="console.log('Save output button has not been implemented.')">
|
174
|
-
Save Output
|
175
|
-
</div>
|
176
|
-
</div>
|
86
|
+
<div class="surface" id="dashboard-wrapper">
|
87
|
+
<div class="dashboard-loading">
|
88
|
+
LOADING
|
177
89
|
</div>
|
178
90
|
</div>
|
179
91
|
</div>
|
180
92
|
</body>
|
93
|
+
<script type="text/javascript">
|
94
|
+
load_dashboard("<%=$config[:dashboards].nil? ? 0 : $config[:dashboards][0][:id]%>")
|
95
|
+
</script>
|
181
96
|
</html>
|
data/lib/html/app.js.erb
ADDED
@@ -0,0 +1,177 @@
|
|
1
|
+
/* app.js.erb
|
2
|
+
*
|
3
|
+
* Author: Eric Power
|
4
|
+
*
|
5
|
+
* Description:
|
6
|
+
* This ERB file generates the javascript that runs the client side of the Easel
|
7
|
+
* Dashboard.
|
8
|
+
*/
|
9
|
+
|
10
|
+
/* Plan for a refactor:
|
11
|
+
*
|
12
|
+
* This needs to be broken out into a few files (which could be merged into one
|
13
|
+
* before sending to the client). Basically, I want:
|
14
|
+
* - Controller: sets up state variables (eg. dashboards, comands,
|
15
|
+
* websocket), builds the websocket and initiates getting historical
|
16
|
+
* dashboard data.
|
17
|
+
* - Component Builder: builds components like a new
|
18
|
+
* - Messaging: functions that are used to send and receive messages via
|
19
|
+
* the WebSocket.
|
20
|
+
* - Chart: functions that build and update the various Chart JS elements
|
21
|
+
* in dashboards. Some ideological overlap with Component Builder, so need
|
22
|
+
* a better taxonomy.
|
23
|
+
*
|
24
|
+
* TODO: I want a 'plug and play' way to add in new element types. How this
|
25
|
+
* works needs to be designed. Basically, I need to drop in code on the client
|
26
|
+
* side that goes from knowing the type (eg. 'time-series') to building the
|
27
|
+
* canvas element, and handles updating it. I don't want to have to edit the
|
28
|
+
* html.erb stuff to set up the canvas specifically.
|
29
|
+
*
|
30
|
+
* On the server side, I might be able to get away with without anythnig if I
|
31
|
+
* can design the YAML structure well enough to not need it.
|
32
|
+
*/
|
33
|
+
"use strict";
|
34
|
+
|
35
|
+
|
36
|
+
|
37
|
+
// Key Variables
|
38
|
+
let ws_socket = new WebSocket("ws://" + location.hostname + ":" + location.port);
|
39
|
+
let dashboard = {
|
40
|
+
dashboard:
|
41
|
+
}
|
42
|
+
let current_cmd = null;
|
43
|
+
let keep_pane_contents = false; // Remove default contents when you run a command.
|
44
|
+
let commands = { <% $config[:commands].each do |command| %>
|
45
|
+
<%= command[:id] %>: {
|
46
|
+
is_running: false,
|
47
|
+
content: "[Click 'Run' to run the command.]",
|
48
|
+
name: "<%= command[:name] %>" },
|
49
|
+
<% end %>
|
50
|
+
};
|
51
|
+
let dashboards = { <% $config[:dashboards].each do |dashboard| %>
|
52
|
+
"<%= dashboard[:id] %>": {
|
53
|
+
name: "<%= dashboard[:name] %>",
|
54
|
+
elements: [
|
55
|
+
<% dashboard[:elements].each do |element| %>
|
56
|
+
{
|
57
|
+
name: "<%= element[:name] %>",
|
58
|
+
type: "<%= element[:type] %>",
|
59
|
+
canvas: document.createElement("CANVAS"),
|
60
|
+
config: {
|
61
|
+
type: 'line',
|
62
|
+
data: {
|
63
|
+
labels: [],
|
64
|
+
datasets: [
|
65
|
+
<% element[:data].each_with_index do |datum, index| %>
|
66
|
+
{
|
67
|
+
label: "<%= datum[:name] %>",
|
68
|
+
data: [],
|
69
|
+
borderColor: "<%= $config[:colours][:primary] %>",
|
70
|
+
},
|
71
|
+
<% end %>
|
72
|
+
]
|
73
|
+
}
|
74
|
+
},
|
75
|
+
data: [
|
76
|
+
<% element[:data].each do |datum| %>
|
77
|
+
{
|
78
|
+
name: "<%= datum[:name] %>",
|
79
|
+
store: []
|
80
|
+
},
|
81
|
+
<% end %> <%# end data loop %>
|
82
|
+
]
|
83
|
+
},
|
84
|
+
<% end %> <%# end element loop %>
|
85
|
+
]
|
86
|
+
},
|
87
|
+
<% end %> <%# end dashboard loop %>
|
88
|
+
};
|
89
|
+
|
90
|
+
// Turn cdashboards > elements > canvases into Charts
|
91
|
+
Object.keys(dashboards).forEach( (dashboard_id) => {
|
92
|
+
Object.keys(dashboards[dashboard_id]['elements']).forEach( (element_id) => {
|
93
|
+
let element = dashboards[dashboard_id]['elements'][element_id];
|
94
|
+
element['chart'] = new Chart(
|
95
|
+
element['canvas'],
|
96
|
+
element['config'])
|
97
|
+
})
|
98
|
+
});
|
99
|
+
|
100
|
+
|
101
|
+
|
102
|
+
|
103
|
+
/* toggle_run
|
104
|
+
*
|
105
|
+
* Sends a message to the server to stop or run the given id (dependent on its
|
106
|
+
* state), and updates the run/stop button accordingly.
|
107
|
+
*/
|
108
|
+
function toggle_run(id){
|
109
|
+
if (commands[id]['is_running']) { // STOP (already running)
|
110
|
+
ws_socket.send("STOP:" + id);
|
111
|
+
document.getElementById('cmd-' + id + '-run-icon').innerHTML = "Run";
|
112
|
+
} else { // RUN (currently not running)
|
113
|
+
commands[id]['content'] = "";
|
114
|
+
ws_socket.send("RUN:" + id);
|
115
|
+
document.getElementById('cmd-' + id + '-run-icon').innerHTML = "Stop";
|
116
|
+
current_cmd = id;
|
117
|
+
}
|
118
|
+
commands[id]['is_running'] = !commands[id]['is_running'];
|
119
|
+
update_dashboard();
|
120
|
+
}
|
121
|
+
|
122
|
+
/* load_pane
|
123
|
+
*
|
124
|
+
* Loads the output/dashboard associated with the id.
|
125
|
+
*/
|
126
|
+
function load_dashboard(id) {
|
127
|
+
current_cmd = id; // TODO: update the name of this to represent that it refers to a dashboard.
|
128
|
+
update_dashboard();
|
129
|
+
}
|
130
|
+
|
131
|
+
/* updates_pane
|
132
|
+
*
|
133
|
+
* Updates the contents of the pane. Typically called after a message has been
|
134
|
+
* received that wants to put new content onto the pane.
|
135
|
+
*/
|
136
|
+
function update_dashboard() {
|
137
|
+
|
138
|
+
let pane = document.getElementById('output-pane');
|
139
|
+
let pane_state = document.getElementById('interface-state');
|
140
|
+
let pane_state_details = document.getElementById('interface-state-details');
|
141
|
+
|
142
|
+
|
143
|
+
if (Number.isInteger(current_cmd)){
|
144
|
+
if (commands[current_cmd]['is_running']) {
|
145
|
+
pane_state.innerHTML = "Running: ";
|
146
|
+
} else {
|
147
|
+
pane_state.innerHTML = "Output Of: ";
|
148
|
+
}
|
149
|
+
pane_state_details.innerHTML = commands[current_cmd]['name'];
|
150
|
+
pane.innerHTML = commands[current_cmd]['content'];
|
151
|
+
} else { // is a dashboard
|
152
|
+
pane_state.innerHTML = dashboards[current_cmd]['name'];
|
153
|
+
pane_state_details.innerHTML = dashboards[current_cmd]['desc'];
|
154
|
+
document.getElementById('dash-elem').appendChild(
|
155
|
+
dashboards[current_cmd]['elements'][0]['canvas']
|
156
|
+
);
|
157
|
+
}
|
158
|
+
}
|
159
|
+
|
160
|
+
/* show_help_message
|
161
|
+
*
|
162
|
+
* Displays the help message on the pane. Called when the user clicks the 'help'
|
163
|
+
* button.
|
164
|
+
*/
|
165
|
+
function show_help_message() {
|
166
|
+
current_cmd = null;
|
167
|
+
let pane = document.getElementById('output-pane');
|
168
|
+
let pane_state = document.getElementById('interface-state');
|
169
|
+
let pane_state_details = document.getElementById('interface-state-details');
|
170
|
+
pane.innerHTML =
|
171
|
+
"Welcome to the CDash Dashboard! Programs are set up via a YAML file on the server.\n\n" +
|
172
|
+
"You can run these programs by clicking 'Run' under a program's information.\n\n" +
|
173
|
+
"Programs' output will update even if you are looking at the output of another program.\n\n" +
|
174
|
+
"Use the 'Show' button to select which programs' output you are seeing.";
|
175
|
+
pane_state.innerHTML = "Showing: ";
|
176
|
+
pane_state_details = "Help Message";
|
177
|
+
}
|