cyclid-ui 0.1.0

Sign up to get free protection for your applications and to get access to all the features.
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
+ }