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.
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, .h1 {font-size: var(--text-size-xlarge); font-weight: var(--text-weight-bold);}
50
- h2, .h2 {font-size: var(--text-size-large); font-weight: var(--text-weight-bold);}
51
- h3, .h3 {font-size: var(--text-size-large); font-weight: var(--text-weight-semi-bold);}
52
- h4, .h4 {font-size: var(--text-size); font-weight: var(--text-weight-semi-bold);}
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
- .cmd-picker {
66
+ .dashboard-selector {
68
67
  width: 320px;
69
68
  height: 100%;
70
- overflow-y: scroll;
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
- .command-box{
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-small);
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
- .command-info {
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
- .command-info:hover { color: var(--primary); }
98
+ .card-info:hover { color: var(--primary); }
98
99
 
99
- .command-controls {
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
- .command-icon {
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
- .command-icon:hover {
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: var(--background);
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
- .interface {
140
- /* margin: var(--size-large); */
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
- overflow: hidden;
147
- border-radius: var(--size-small);
148
- padding: var(--size-medium);
149
- margin: var(--size-small);
170
+ height: 100%;
150
171
  }
151
- #interface-title {
152
- padding-bottom: var(--size-medium);
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
- .interface-output {
180
+ .dashboard-main-text {
155
181
  flex-grow: 2;
156
182
  flex-basis: 0;
157
183
  padding: var(--size-medium);
158
- box-shadow: inset 0 0 var(--size-medium) var(--background);
159
- }
160
- .interface-footer{
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
- .interface-commands {
167
- width: 33%;
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
- .interface-button {
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
- .interface-button:hover {
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(calc( -1 * var(--size-small)),calc( -1 * calc( var(--size-large))));
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; }
@@ -1,4 +1,18 @@
1
- <!DOCTYPE html>
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 type="text/javascript">
10
-
11
- // TODO: Add a RUN and STOP variable, that point to a div as a play button
12
- // or a square.
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="screen">
128
- <div class="cmd-picker">
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="command-box">
132
- <div class="command-info"
133
- onclick="load_pane(<%=command[:id]%>)"
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="command-controls">
141
- <div class="command-icon"
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="command-icon"
75
+ <div class="card-icon"
147
76
  id="cmd-<%=command[:id]%>-log-icon"
148
- onclick="load_pane(<%=command[:id]%>)">
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
- Click 'Help' (below) to see more.</pre>
164
- </div>
165
- <div class="interface-footer">
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>
@@ -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
+ }