cyclid-ui 0.1.0

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.
Files changed (98) hide show
  1. checksums.yaml +7 -0
  2. data/LICENSE +178 -0
  3. data/README.md +20 -0
  4. data/app/cyclid_ui.rb +124 -0
  5. data/app/cyclid_ui/config.rb +64 -0
  6. data/app/cyclid_ui/controllers/auth.rb +102 -0
  7. data/app/cyclid_ui/controllers/base.rb +41 -0
  8. data/app/cyclid_ui/controllers/health.rb +96 -0
  9. data/app/cyclid_ui/controllers/organization.rb +64 -0
  10. data/app/cyclid_ui/controllers/user.rb +40 -0
  11. data/app/cyclid_ui/helpers.rb +55 -0
  12. data/app/cyclid_ui/memcache.rb +45 -0
  13. data/app/cyclid_ui/models/user.rb +91 -0
  14. data/app/cyclid_ui/templates/footer.mustache +8 -0
  15. data/app/cyclid_ui/templates/job.mustache +21 -0
  16. data/app/cyclid_ui/templates/job_info.mustache +40 -0
  17. data/app/cyclid_ui/templates/layout.mustache +102 -0
  18. data/app/cyclid_ui/templates/login.mustache +78 -0
  19. data/app/cyclid_ui/templates/organization.mustache +150 -0
  20. data/app/cyclid_ui/templates/user.mustache +97 -0
  21. data/app/cyclid_ui/views/job.rb +25 -0
  22. data/app/cyclid_ui/views/layout.rb +52 -0
  23. data/app/cyclid_ui/views/login.rb +25 -0
  24. data/app/cyclid_ui/views/organization.rb +25 -0
  25. data/app/cyclid_ui/views/user.rb +25 -0
  26. data/bin/cyclid-ui-assets +17 -0
  27. data/lib/cyclid_ui/app.rb +4 -0
  28. data/public/images/LICENSE +3 -0
  29. data/public/images/cyclid-logo-large.png +0 -0
  30. data/public/images/favicon16.png +0 -0
  31. data/public/images/favicon32.png +0 -0
  32. data/public/images/favicon48.png +0 -0
  33. data/public/images/favicon64.png +0 -0
  34. data/public/images/favicon96.png +0 -0
  35. data/public/js/api.js +34 -0
  36. data/public/js/cyclid.js +32 -0
  37. data/public/js/job.js +215 -0
  38. data/public/js/organization.js +345 -0
  39. data/public/js/user.js +145 -0
  40. data/public/vendor/bootstrap/css/bootstrap-theme.css +587 -0
  41. data/public/vendor/bootstrap/css/bootstrap-theme.css.map +1 -0
  42. data/public/vendor/bootstrap/css/bootstrap-theme.min.css +6 -0
  43. data/public/vendor/bootstrap/css/bootstrap-theme.min.css.map +1 -0
  44. data/public/vendor/bootstrap/css/bootstrap.css +6760 -0
  45. data/public/vendor/bootstrap/css/bootstrap.css.map +1 -0
  46. data/public/vendor/bootstrap/css/bootstrap.min.css +6 -0
  47. data/public/vendor/bootstrap/css/bootstrap.min.css.map +1 -0
  48. data/public/vendor/bootstrap/css/custom.css +193 -0
  49. data/public/vendor/bootstrap/fonts/glyphicons-halflings-regular.eot +0 -0
  50. data/public/vendor/bootstrap/fonts/glyphicons-halflings-regular.svg +288 -0
  51. data/public/vendor/bootstrap/fonts/glyphicons-halflings-regular.ttf +0 -0
  52. data/public/vendor/bootstrap/fonts/glyphicons-halflings-regular.woff +0 -0
  53. data/public/vendor/bootstrap/fonts/glyphicons-halflings-regular.woff2 +0 -0
  54. data/public/vendor/bootstrap/js/bootstrap.js +2363 -0
  55. data/public/vendor/bootstrap/js/bootstrap.min.js +7 -0
  56. data/public/vendor/bootstrap/js/npm.js +13 -0
  57. data/public/vendor/font-awesome/HELP-US-OUT.txt +7 -0
  58. data/public/vendor/font-awesome/css/font-awesome.css +2199 -0
  59. data/public/vendor/font-awesome/css/font-awesome.min.css +4 -0
  60. data/public/vendor/font-awesome/fonts/FontAwesome.otf +0 -0
  61. data/public/vendor/font-awesome/fonts/fontawesome-webfont.eot +0 -0
  62. data/public/vendor/font-awesome/fonts/fontawesome-webfont.svg +685 -0
  63. data/public/vendor/font-awesome/fonts/fontawesome-webfont.ttf +0 -0
  64. data/public/vendor/font-awesome/fonts/fontawesome-webfont.woff +0 -0
  65. data/public/vendor/font-awesome/fonts/fontawesome-webfont.woff2 +0 -0
  66. data/public/vendor/font-awesome/less/animated.less +34 -0
  67. data/public/vendor/font-awesome/less/bordered-pulled.less +25 -0
  68. data/public/vendor/font-awesome/less/core.less +12 -0
  69. data/public/vendor/font-awesome/less/fixed-width.less +6 -0
  70. data/public/vendor/font-awesome/less/font-awesome.less +18 -0
  71. data/public/vendor/font-awesome/less/icons.less +733 -0
  72. data/public/vendor/font-awesome/less/larger.less +13 -0
  73. data/public/vendor/font-awesome/less/list.less +19 -0
  74. data/public/vendor/font-awesome/less/mixins.less +60 -0
  75. data/public/vendor/font-awesome/less/path.less +15 -0
  76. data/public/vendor/font-awesome/less/rotated-flipped.less +20 -0
  77. data/public/vendor/font-awesome/less/screen-reader.less +5 -0
  78. data/public/vendor/font-awesome/less/stacked.less +20 -0
  79. data/public/vendor/font-awesome/less/variables.less +744 -0
  80. data/public/vendor/font-awesome/scss/_animated.scss +34 -0
  81. data/public/vendor/font-awesome/scss/_bordered-pulled.scss +25 -0
  82. data/public/vendor/font-awesome/scss/_core.scss +12 -0
  83. data/public/vendor/font-awesome/scss/_fixed-width.scss +6 -0
  84. data/public/vendor/font-awesome/scss/_icons.scss +733 -0
  85. data/public/vendor/font-awesome/scss/_larger.scss +13 -0
  86. data/public/vendor/font-awesome/scss/_list.scss +19 -0
  87. data/public/vendor/font-awesome/scss/_mixins.scss +60 -0
  88. data/public/vendor/font-awesome/scss/_path.scss +15 -0
  89. data/public/vendor/font-awesome/scss/_rotated-flipped.scss +20 -0
  90. data/public/vendor/font-awesome/scss/_screen-reader.scss +5 -0
  91. data/public/vendor/font-awesome/scss/_stacked.scss +20 -0
  92. data/public/vendor/font-awesome/scss/_variables.scss +744 -0
  93. data/public/vendor/font-awesome/scss/font-awesome.scss +18 -0
  94. data/public/vendor/jquery-2.2.4.min.js +4 -0
  95. data/public/vendor/jquery.md5.js +269 -0
  96. data/public/vendor/js.cookie.js +151 -0
  97. data/public/vendor/mustache.min.js +1 -0
  98. metadata +322 -0
@@ -0,0 +1,25 @@
1
+ # frozen_string_literal: true
2
+ # Copyright 2016 Liqwyd Ltd.
3
+ #
4
+ # Licensed under the Apache License, Version 2.0 (the "License");
5
+ # you may not use this file except in compliance with the License.
6
+ # You may obtain a copy of the License at
7
+ #
8
+ # http://www.apache.org/licenses/LICENSE-2.0
9
+ #
10
+ # Unless required by applicable law or agreed to in writing, software
11
+ # distributed under the License is distributed on an "AS IS" BASIS,
12
+ # WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13
+ # See the License for the specific language governing permissions and
14
+ # limitations under the License.
15
+
16
+ module Cyclid
17
+ module UI
18
+ module Views
19
+ # Job view
20
+ class Job < Layout
21
+ attr_reader :job_url, :job_id
22
+ end
23
+ end
24
+ end
25
+ end
@@ -0,0 +1,52 @@
1
+ # frozen_string_literal: true
2
+ # Copyright 2016 Liqwyd Ltd.
3
+ #
4
+ # Licensed under the Apache License, Version 2.0 (the "License");
5
+ # you may not use this file except in compliance with the License.
6
+ # You may obtain a copy of the License at
7
+ #
8
+ # http://www.apache.org/licenses/LICENSE-2.0
9
+ #
10
+ # Unless required by applicable law or agreed to in writing, software
11
+ # distributed under the License is distributed on an "AS IS" BASIS,
12
+ # WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13
+ # See the License for the specific language governing permissions and
14
+ # limitations under the License.
15
+
16
+ require 'digest/md5'
17
+
18
+ module Cyclid
19
+ module UI
20
+ module Views
21
+ # Main Layout view; everything common to the Views which use Layouts can
22
+ # be found here
23
+ class Layout < Mustache
24
+ attr_reader :organization, :api_url, :linkback_url
25
+
26
+ def username
27
+ @current_user.username || 'Nobody'
28
+ end
29
+
30
+ def organizations
31
+ @current_user.organizations
32
+ end
33
+
34
+ def title
35
+ @title || 'Cyclid'
36
+ end
37
+
38
+ # Return an array of elements to be inserted into the breadcrumb
39
+ def breadcrumbs
40
+ @crumbs.to_json
41
+ end
42
+
43
+ # Calculate the base Gravatar URL for the user
44
+ def gravatar_url
45
+ email = @current_user.email.downcase.strip
46
+ hash = Digest::MD5.hexdigest(email)
47
+ "https://www.gravatar.com/avatar/#{hash}?d=identicon&r=g"
48
+ end
49
+ end
50
+ end
51
+ end
52
+ end
@@ -0,0 +1,25 @@
1
+ # frozen_string_literal: true
2
+ # Copyright 2016 Liqwyd Ltd.
3
+ #
4
+ # Licensed under the Apache License, Version 2.0 (the "License");
5
+ # you may not use this file except in compliance with the License.
6
+ # You may obtain a copy of the License at
7
+ #
8
+ # http://www.apache.org/licenses/LICENSE-2.0
9
+ #
10
+ # Unless required by applicable law or agreed to in writing, software
11
+ # distributed under the License is distributed on an "AS IS" BASIS,
12
+ # WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13
+ # See the License for the specific language governing permissions and
14
+ # limitations under the License.
15
+
16
+ module Cyclid
17
+ module UI
18
+ module Views
19
+ # Login view. This is a non-Layout view.
20
+ class Login < Mustache
21
+ attr_reader :message
22
+ end
23
+ end
24
+ end
25
+ end
@@ -0,0 +1,25 @@
1
+ # frozen_string_literal: true
2
+ # Copyright 2016 Liqwyd Ltd.
3
+ #
4
+ # Licensed under the Apache License, Version 2.0 (the "License");
5
+ # you may not use this file except in compliance with the License.
6
+ # You may obtain a copy of the License at
7
+ #
8
+ # http://www.apache.org/licenses/LICENSE-2.0
9
+ #
10
+ # Unless required by applicable law or agreed to in writing, software
11
+ # distributed under the License is distributed on an "AS IS" BASIS,
12
+ # WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13
+ # See the License for the specific language governing permissions and
14
+ # limitations under the License.
15
+
16
+ module Cyclid
17
+ module UI
18
+ module Views
19
+ # Organization view
20
+ class Organization < Layout
21
+ attr_reader :organization_url
22
+ end
23
+ end
24
+ end
25
+ end
@@ -0,0 +1,25 @@
1
+ # frozen_string_literal: true
2
+ # Copyright 2016 Liqwyd Ltd.
3
+ #
4
+ # Licensed under the Apache License, Version 2.0 (the "License");
5
+ # you may not use this file except in compliance with the License.
6
+ # You may obtain a copy of the License at
7
+ #
8
+ # http://www.apache.org/licenses/LICENSE-2.0
9
+ #
10
+ # Unless required by applicable law or agreed to in writing, software
11
+ # distributed under the License is distributed on an "AS IS" BASIS,
12
+ # WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13
+ # See the License for the specific language governing permissions and
14
+ # limitations under the License.
15
+
16
+ module Cyclid
17
+ module UI
18
+ module Views
19
+ # User profile view
20
+ class User < Layout
21
+ attr_reader :user_url
22
+ end
23
+ end
24
+ end
25
+ end
@@ -0,0 +1,17 @@
1
+ #!/usr/bin/env ruby
2
+ # frozen_string_literal: true
3
+ # Copyright 2016 Liqwyd Ltd.
4
+ #
5
+ # Licensed under the Apache License, Version 2.0 (the "License");
6
+ # you may not use this file except in compliance with the License.
7
+ # You may obtain a copy of the License at
8
+ #
9
+ # http://www.apache.org/licenses/LICENSE-2.0
10
+ #
11
+ # Unless required by applicable law or agreed to in writing, software
12
+ # distributed under the License is distributed on an "AS IS" BASIS,
13
+ # WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
14
+ # See the License for the specific language governing permissions and
15
+ # limitations under the License.
16
+
17
+ puts File.expand_path('../../public', __FILE__)
@@ -0,0 +1,4 @@
1
+ # frozen_string_literal: true
2
+ $LOAD_PATH.push File.expand_path('../../../app', __FILE__)
3
+
4
+ require 'cyclid_ui'
@@ -0,0 +1,3 @@
1
+ The Cyclid Logo and its derivatives are licensed under the Creative Commons
2
+ Attribution-NonCommercial-NoDerivatives 4.0 International License. To view a
3
+ copy of this license, visit http://creativecommons.org/licenses/by-nc-nd/4.0/.
@@ -0,0 +1,34 @@
1
+ // Perform an authenticated GET to the API server.
2
+ function api_get(url, username, success, error) {
3
+ $.ajax({
4
+ type: 'GET',
5
+ url: encodeURI(url),
6
+ dataType: 'json',
7
+ crossDomain: true,
8
+ beforeSend: function(xhr) {
9
+ var token = Cookies.get('cyclid.token');
10
+ var authorization = `Token ${username}:${token}`
11
+ xhr.setRequestHeader('Authorization', authorization);
12
+ },
13
+ success: success,
14
+ error: error
15
+ });
16
+ }
17
+
18
+ // Perform an authenticated PUT to the API server.
19
+ function api_put(url, data, username, success, error) {
20
+ $.ajax({
21
+ type: 'PUT',
22
+ url: encodeURI(url),
23
+ data: JSON.stringify(data),
24
+ contentType: 'application/json',
25
+ crossDomain: true,
26
+ beforeSend: function(xhr) {
27
+ var token = Cookies.get('cyclid.token');
28
+ var authorization = `Token ${username}:${token}`
29
+ xhr.setRequestHeader('Authorization', authorization);
30
+ },
31
+ success: success,
32
+ error: error
33
+ });
34
+ }
@@ -0,0 +1,32 @@
1
+ // Create an interval and store the handle with a key
2
+ function addNamedInterval(name, func, interval) {
3
+ if( window.timers == undefined ){
4
+ console.log('initializing window.timers');
5
+ window.timers = {};
6
+ }
7
+
8
+ if( window.timers[name] == null ) {
9
+ var interval = setInterval(func, interval);
10
+ console.log(`setting interval ${interval} as ${name}`);
11
+ window.timers[name] = interval;
12
+ }
13
+ }
14
+
15
+ // Remove a named interval previously created with addNamedInterval
16
+ function removeNamedInterval(name) {
17
+ var interval = window.timers[name];
18
+
19
+ if( interval != undefined ) {
20
+ console.log(`clearing interval ${interval} for ${name}`);
21
+ clearInterval(interval);
22
+ }
23
+ window.timers[name] = null;
24
+ }
25
+
26
+ // Remove all named intervals previously created with addNamedInterval
27
+ function clearAllNamedIntervals() {
28
+ for(var timer in window.timers){
29
+ console.log(`removing ${timer}`);
30
+ removeNamedInterval(timer);
31
+ }
32
+ }
@@ -0,0 +1,215 @@
1
+ // Convert a Cyclid job status code to a human readable status
2
+ function ji_job_status_to_human(status_id) {
3
+ var statuses = {0: 'New',
4
+ 1: 'Waiting',
5
+ 2: 'Started',
6
+ 3: 'Failing',
7
+ 10: 'Succeeded',
8
+ 11: 'Failed'};
9
+ var status = statuses[status_id];
10
+
11
+ var glyphs = {0: 'glyphicon-share-alt',
12
+ 1: 'glyphicon-time',
13
+ 2: 'glyphicon-refresh',
14
+ 3: 'glyphicon-alert',
15
+ 10: 'glyphicon-ok',
16
+ 11: 'glyphicon-remove'};
17
+ var glyph = glyphs[status_id];
18
+
19
+ var labels = {0: 'label-primary',
20
+ 1: 'label-primary',
21
+ 2: 'label-info',
22
+ 3: 'label-warning',
23
+ 10: 'label-success',
24
+ 11: 'label-danger'};
25
+ var label = labels[status_id];
26
+
27
+ return `<span class="label ${label}">
28
+ <span class="glyphicon ${glyph}" aria-hidden="true"></span>&nbsp;${status}
29
+ </span>`
30
+ }
31
+
32
+ // Convert a Cyclid job status code to an indicator
33
+ function ji_job_status_to_indicator(status_id) {
34
+ var statuses = {0: 'New',
35
+ 1: 'Waiting',
36
+ 2: 'Started',
37
+ 3: 'Failing',
38
+ 10: 'Succeeded',
39
+ 11: 'Failed'};
40
+ var status = statuses[status_id];
41
+
42
+ var glyphs = {0: 'glyphicon-share-alt',
43
+ 1: 'glyphicon-time',
44
+ 2: 'glyphicon-refresh',
45
+ 3: 'glyphicon-alert',
46
+ 10: 'glyphicon-ok',
47
+ 11: 'glyphicon-remove'};
48
+ var glyph = glyphs[status_id];
49
+
50
+ var labels = {0: 'label-primary',
51
+ 1: 'label-primary',
52
+ 2: 'label-info',
53
+ 3: 'label-warning',
54
+ 10: 'label-success',
55
+ 11: 'label-danger'};
56
+ var label = labels[status_id];
57
+
58
+ return `<span class="label ${label}">
59
+ <span class="glyphicon ${glyph}" aria-hidden="true" title="${status}"></span>
60
+ </span>`
61
+ }
62
+
63
+ function ji_calculate_duration(started, ended) {
64
+ var date_started = new Date(started);
65
+ var date_ended = new Date(ended);
66
+
67
+ var duration = '';
68
+ if( date_ended > 0 ){
69
+ duration = new Date(date_ended.getTime() - date_started.getTime()).toISOString().substr(11, 8);
70
+ }
71
+
72
+ return duration;
73
+ }
74
+
75
+ // Set & show the job log element
76
+ function ji_update_log(log_text) {
77
+ // Show the log
78
+ var outer = $('#ji_log_outer');
79
+ outer.removeClass('hidden');
80
+
81
+ // Find the current position of the scrollable element, before we update it
82
+ var inner = $('#ji_log_inner')
83
+ var diff = inner.prop('scrollHeight') - (inner.scrollTop() + inner.outerHeight());
84
+
85
+ // Update the log
86
+ inner.html(log_text);
87
+
88
+ // If the user hasn't scrolled up, scroll to the bottom to show the new log data
89
+ if( diff <= 0 )
90
+ inner.scrollTop(inner.prop('scrollHeight'));
91
+ }
92
+
93
+ // Is the job in a "Failed" or "Succeeded" state?
94
+ function ji_job_finished(job_status) {
95
+ if( job_status == 10 || job_status == 11 ) {
96
+ return true;
97
+ } else {
98
+ return false;
99
+ }
100
+ }
101
+
102
+ // Is the job still active?
103
+ function ji_job_active(job_status) {
104
+ return !ji_job_finished(job_status);
105
+ }
106
+
107
+ function ji_update_status(job) {
108
+ var status = ji_job_status_to_human(job.status)
109
+ $('#ji_job_status').html(status);
110
+ $('#ji_job_status').data('status', job.status);
111
+
112
+ // Update the "Waiting" message appropriately
113
+ var waiting = '<h6>Unknown</h6>';
114
+ switch(job.status) {
115
+ case 1:
116
+ case 2:
117
+ waiting = '<h6><i class="fa fa-spinner fa-pulse"></i>&nbsp;Waiting for job to start...</h6>'
118
+ $('#ji_job_waiting').html(waiting);
119
+ $('#ji_job_waiting').removeClass('hidden');
120
+ break;
121
+ case 3:
122
+ case 4:
123
+ waiting = '<h6><i class="fa fa-cog fa-spin"></i>&nbsp;Waiting for job to complete...</h6>';
124
+ $('#ji_job_waiting').html(waiting);
125
+ $('#ji_job_waiting').removeClass('hidden');
126
+ break;
127
+ case 10:
128
+ case 11:
129
+ $('#ji_job_waiting').addClass('hidden');
130
+ break;
131
+ }
132
+
133
+ // Update the status indicator, if there is one
134
+ var indicator = ji_job_status_to_indicator(job.status);
135
+ $(`#row${job.job_id} > #status`).html(indicator);
136
+ }
137
+
138
+ // Set & show the job details
139
+ function ji_update_details(job) {
140
+ var title = `<a href="${gblLinkbackURL}/job/${job.id}">${job.job_name}&nbsp;<small>v${job.job_version}</small></a>`;
141
+ $('#ji_header').html(title);
142
+
143
+ $('#ji_job_id').text(job.id);
144
+
145
+ if (job.started) {
146
+ var started = new Date(job.started);
147
+ $('#ji_job_started').text(started.toUTCString());
148
+ }
149
+
150
+ if (job.ended) {
151
+ var ended = new Date(job.ended);
152
+ $('#ji_job_ended').text(ended.toUTCString());
153
+ }
154
+
155
+ var duration = ji_calculate_duration(job.started, job.ended);
156
+ $('#ji_job_duration').text(duration);
157
+
158
+ $('#ji_details').removeClass('hidden');
159
+ }
160
+
161
+ // Update everything: job details, status & log
162
+ function ji_update_all(job) {
163
+ ji_update_details(job);
164
+ ji_update_status(job);
165
+ ji_update_log(job.log);
166
+ }
167
+
168
+ function ji_get_failed(xhr) {
169
+ var failure_message = `<p>
170
+ <h2>Failed to retrieve job</h2><br>
171
+ <strong>${xhr.status}:</strong> ${xhr.responseText}
172
+ </p>`
173
+ $('#ji_failure > #error_message').html(failure_message);
174
+
175
+ $('#ji_failure').removeClass('hidden');
176
+ }
177
+
178
+ function ji_update_status_and_check_completion(url, job) {
179
+ var last_status = $('#ji_job_status').data('status');
180
+ if( job.status != last_status ) {
181
+ ji_update_status(job);
182
+ }
183
+
184
+ // Did the job end?
185
+ if( ji_job_finished(job.status) ){
186
+ console.log(`job #${job.job_id} ended`);
187
+
188
+ // Update the job details so that E.g. the "Ended" time is shown
189
+ api_get(url, gblUsername, ji_update_details, ji_get_failed);
190
+
191
+ // Find any timer associated with the job info & remove it
192
+ removeNamedInterval(`watcher${job.job_id}`);
193
+ }
194
+ }
195
+
196
+ function ji_watch_job(url) {
197
+ console.log(`ji_watch_job(${url})`);
198
+
199
+ // Check job status
200
+ var status_url = `${url}/status`;
201
+ console.log(`updating status from ${status_url}`);
202
+ api_get(status_url,
203
+ gblUsername,
204
+ function(job) {
205
+ ji_update_status_and_check_completion(url, job);
206
+ },
207
+ ji_get_failed);
208
+
209
+ // Update log
210
+ var log_url = `${url}/log`;
211
+ console.log(`updating log from ${log_url}`);
212
+ api_get(log_url, gblUsername, function(data) { ji_update_log(data.log); }, ji_get_failed);
213
+
214
+ console.log(`ji_watch_job(${url}) finished`);
215
+ }