Browse Source

Nouvelle interface basée sur Mithril

develop
William Petit 3 years ago
parent
commit
07b51daf2b
9 changed files with 520 additions and 932 deletions
  1. 11
    158
      client/index.html
  2. 0
    32
      client/index2.html
  3. 477
    219
      client/js/app.js
  4. 0
    475
      client/js/app2.js
  5. 13
    5
      handlers/handler-example
  6. 6
    5
      lib/hook-server.js
  7. 3
    2
      lib/job-queue.js
  8. 1
    0
      package.json
  9. 9
    36
      payload.json

+ 11
- 158
client/index.html View File

@@ -6,174 +6,27 @@
6 6
     <link rel="stylesheet" href="css/style.css">
7 7
   </head>
8 8
   <body>
9
+    <nav class="navbar navbar-default">
10
+      <div class="container">
11
+        <div class="navbar-header">
12
+          <a class="navbar-brand" href="#">
13
+            <img alt="Brand" src="img/logo.svg" class="brand-logo" /> Marang
14
+          </a>
15
+        </div>
16
+      </div>
17
+    </nav>
9 18
     <div class="container">
10 19
       <div class="row">
11 20
         <div class="col-md-12">
12
-          <h1><img class="logo" src="img/logo.svg" /> Marang</h1>
13
-
14
-          <ul class="nav nav-tabs" role="tablist">
15
-            <li role="presentation" class="active"><a href="#jobs" aria-controls="jobs" role="tab" data-toggle="tab">Jobs</a></li>
16
-            <li role="presentation"><a href="#slots" aria-controls="slots" role="tab" data-toggle="tab">Slots</a></li>
17
-          </ul>
18
-
19
-          <div class="tab-content">
20
-            <div role="tabpanel" class="tab-pane active" id="jobs">
21
-              <ul class="list-group spaced-top" id="jobs-list"></ul>
22
-            </div>
23
-            <div role="tabpanel" class="tab-pane" id="slots">
24
-              <div class="clearfix" style="margin-top:15px;">
25
-                <button id="addSlotButton"
26
-                  type="button" class="btn btn-success pull-right">
27
-                  Add slot  <span class="glyphicon glyphicon-plus"></span>
28
-                </button>
29
-              </div>
30
-              <ul class="list-group" style="margin-top:15px;" id="slots-list"></ul>
31
-            </div>
32
-          </div>
33
-      </div>
34
-    </div>
35
-
36
-    <div class="modal fade" id="editSlotModal">
37
-      <div class="modal-dialog">
38
-        <div class="modal-content">
39
-          <div class="modal-header">
40
-            <button type="button" class="close" data-dismiss="modal" aria-label="Close">
41
-              <span aria-hidden="true">&times;</span>
42
-            </button>
43
-            <h4 class="modal-title">Edit slot</h4>
44
-          </div>
45
-          <div class="modal-body">
46
-            <form class="form" id="editSlotForm" action>
47
-              <div class="form-group">
48
-                <label for="slotLabelInput">Label</label>
49
-                <input name="label" required type="text" class="form-control" id="slotLabelInput" placeholder="Slot's label">
50
-              </div>
51
-              <div class="form-group">
52
-                <label for="slotTokenInput">Token</label>
53
-                <input name="token" type="text" class="form-control" readonly required id="slotTokenInput">
54
-              </div>
55
-              <div class="form-group">
56
-                <label for="slotHandlerInput">Handler</label>
57
-                <select name="handler" class="form-control" required id="slotHandlerInput"></select>
58
-              </div>
59
-              <div class="form-group">
60
-                <label for="slotHandlerArgs">Handler's Arguments</label>
61
-                <input type="text" name="handlerArgs" class="form-control" id="slotHandlerArgs" placeholder="'foo' -bar">
62
-              </div>
63
-              <div class="form-group">
64
-                <label for="slotHandlerUidInput">Handler UID</label>
65
-                <input name="handlerUid" type="number" min="0" class="form-control" id="slotHandlerUidInput" placholder="Handler's user numerical ID">
66
-              </div>
67
-              <div class="form-group">
68
-                <label for="slotHandlerGidInput">Handler GID</label>
69
-                <input name="handlerGid" type="number" min="0" class="form-control" id="slotHandlerGidInput" placholder="Handler's group numerical ID">
70
-              </div>
71
-            </form>
72
-          </div>
73
-          <div class="modal-footer">
74
-            <button type="button" class="btn btn-default" data-dismiss="modal">Close</button>
75
-            <button type="button" class="btn btn-primary" id="editSlotSaveButton">Save</button>
76
-          </div>
21
+          <div id="app"></div>
77 22
         </div>
78 23
       </div>
79 24
     </div>
80
-
81
-    <script type="text/x-template-handlebars" id="slotItemTmpl">
82
-      <li class="list-group-item clearfix">
83
-        <div class="btn-group pull-right" role="group">
84
-          <button data-slot-token="{{ token }}"
85
-            class="btn btn-primary btn-xs edit-slot">
86
-            Edit <span class="glyphicon glyphicon-pencil"></span>
87
-          </button>
88
-          <button data-slot-token="{{ token }}"
89
-            class="btn btn-danger btn-xs delete-slot">
90
-            Delete <span class="glyphicon glyphicon-trash"></span>
91
-          </button>
92
-        </div>
93
-        <h4 class="list-group-item-heading">{{ label }}</h4>
94
-        <form class="form">
95
-          <div class="form-group form-group-sm">
96
-            <label class="col-sm-2 control-label">Trigger URL</label>
97
-            <div class="col-sm-10">
98
-              <p class="form-control-static">{{ url }}</p>
99
-            </div>
100
-          </div>
101
-          <div class="form-group form-group-sm">
102
-            <label class="col-sm-2 control-label">Token</label>
103
-            <div class="col-sm-10">
104
-              <p class="form-control-static">{{ token }}</p>
105
-            </div>
106
-          </div>
107
-          <div class="form-group form-group-sm">
108
-            <label class="col-sm-2 control-label">Handler</label>
109
-            <div class="col-sm-10">
110
-              <p class="form-control-static">{{ handler }} {{ handlerArgs }}</p>
111
-            </div>
112
-          </div>
113
-          <div class="form-group form-group-sm">
114
-            <label class="col-sm-2 control-label">User:Group</label>
115
-            <div class="col-sm-10">
116
-              <p class="form-control-static">{{ handlerUid }}:{{ handlerGid }}</p>
117
-            </div>
118
-          </div>
119
-        </form>
120
-      </li>
121
-    </script>
122
-
123
-    <script type="text/x-template-handlebars" id="jobItemTmpl">
124
-      <li class="list-group-item">
125
-        {{#unless slot}}<span class="badge">manual</span>{{/unless}}
126
-        {{#if hasTerminated }}
127
-          {{#if hasError}}
128
-            <span class="badge badge-danger">Error</span>
129
-          {{else}}
130
-            <span class="badge badge-success">Done</span>
131
-          {{/if}}
132
-        {{else}}
133
-          <span class="badge badge-primary">Running</span>
134
-        {{/if}}
135
-        <h4 class="list-group-item-heading">Job {{ handler }}/{{timestamp}}</h4>
136
-        <p class="list-group-item-text">
137
-          <strong>Job Key</strong> {{ key }}<br />
138
-          <strong>Date</strong> {{ dateCreation }}<br />
139
-          <strong>RemoteAddress</strong> {{ remoteAddress }}<br />
140
-          {{#if slot}}<strong>Slot</strong> {{ slot }}<br />{{/if}}
141
-        </p>
142
-        <div class="btn-group spaced-top" role="group">
143
-          <button class="btn btn-default btn-sm"
144
-            data-toggle="collapse" data-target="#job-log-{{key}}">
145
-            Show log
146
-          </button>
147
-          <button class="btn btn-default btn-sm"
148
-            data-toggle="collapse" data-target="#job-payload-{{key}}">
149
-            Show payload
150
-          </button>
151
-        </div>
152
-        <div class="btn-group pull-right" role="group">
153
-          <button data-job-key="{{ key }}"
154
-            class="btn btn-primary btn-xs replay-job">
155
-            Replay <span class="glyphicon glyphicon-repeat"></span>
156
-          </button>
157
-          <button data-job-key="{{ key }}"
158
-            class="btn btn-danger btn-xs delete-job">
159
-            Delete <span class="glyphicon glyphicon-trash"></span>
160
-          </button>
161
-        </div>
162
-        <div class="collapse" id="job-payload-{{key}}">
163
-          <pre>{{stringify payload}}</pre>
164
-        </div>
165
-        <div class="collapse job-log" id="job-log-{{key}}" data-job-key="{{key}}">
166
-          <i>Loading...</i>log
167
-        </div>
168
-      </li>
169
-    </script>
170
-
171 25
     <script src="vendor/node-uuid/uuid.js"></script>
172
-    <script src="vendor/moment/min/moment-with-locales.min.js"></script>
173
-    <script src="vendor/handlebars/dist/handlebars.min.js"></script>
174 26
     <script src="vendor/jquery/dist/jquery.min.js"></script>
175 27
     <script src="vendor/bootstrap/dist/js/bootstrap.min.js"></script>
176 28
     <script src="vendor/ansi_up/ansi_up.js"></script>
29
+    <script src="vendor/mithril/mithril.min.js"></script>
177 30
     <script src="js/app.js"></script>
178 31
   </body>
179 32
 </html>

+ 0
- 32
client/index2.html View File

@@ -1,32 +0,0 @@
1
-<html>
2
-  <head>
3
-    <meta charset="utf-8">
4
-    <title>Marang</title>
5
-    <link rel="stylesheet" href="vendor/bootstrap/dist/css/bootstrap.min.css">
6
-    <link rel="stylesheet" href="css/style.css">
7
-  </head>
8
-  <body>
9
-    <nav class="navbar navbar-default">
10
-      <div class="container">
11
-        <div class="navbar-header">
12
-          <a class="navbar-brand" href="#">
13
-            <img alt="Brand" src="img/logo.svg" class="brand-logo" /> Marang
14
-          </a>
15
-        </div>
16
-      </div>
17
-    </nav>
18
-    <div class="container">
19
-      <div class="row">
20
-        <div class="col-md-12">
21
-          <div id="app"></div>
22
-        </div>
23
-      </div>
24
-    </div>
25
-    <script src="vendor/node-uuid/uuid.js"></script>
26
-    <script src="vendor/jquery/dist/jquery.min.js"></script>
27
-    <script src="vendor/bootstrap/dist/js/bootstrap.min.js"></script>
28
-    <script src="vendor/ansi_up/ansi_up.js"></script>
29
-    <script src="vendor/mithril/mithril.min.js"></script>
30
-    <script src="js/app2.js"></script>
31
-  </body>
32
-</html>

+ 477
- 219
client/js/app.js View File

@@ -1,235 +1,493 @@
1
-$(function() {
1
+(function() {
2 2
 
3
-  var $slotsList = $('#slots-list');
4
-  var $jobsList = $('#jobs-list');
5
-  var $slotHandlerInput = $('#slotHandlerInput');
6
-  var $slotTokenInput = $('#slotTokenInput');
7
-  var $addSlotForm = $('#addSlotForm');
8
-  var $editSlotModal = $('#editSlotModal');
9
-  var $editSlotForm = $('#editSlotForm');
3
+  var Marang = {
4
+    Models: {},
5
+    Modules: {},
6
+    Components: {}
7
+  };
10 8
 
11
-  var slotItemTmpl = Handlebars.compile($('#slotItemTmpl').text());
12
-  var jobItemTmpl  = Handlebars.compile($('#jobItemTmpl').text());
9
+  /*************************
10
+   *         Models
11
+   *************************/
13 12
 
14
-  moment.locale('en');
13
+  Marang.Models.JobsList = m.prop([]);
14
+  Marang.Models.SlotsList = m.prop([]);
15
+  Marang.Models.ServerInfo = m.prop({});
15 16
 
16
-  refresh();
17
-  $('a[data-toggle="tab"]').on('show.bs.tab', refresh);
18
-
19
-  $('#addSlotButton').click(function (e) {
20
-    updateSlotForm();
21
-    refreshHandlers();
22
-    generateSlotToken();
23
-    $editSlotModal.modal('show');
24
-  });
25
-
26
-  $('#editSlotSaveButton').click(function() {
27
-
28
-    var formData = $editSlotForm.serializeArray();
29
-    var data = {};
30
-
31
-    formData.forEach(function(field) {
32
-      if(field.value !== '' && field.value !== null) {
33
-        data[field.name] = field.value;
34
-      }
35
-    });
36
-
37
-    $.ajax({
38
-      url:'/api/slots/'+data.token,
39
-      type:"POST",
40
-      data: JSON.stringify(data),
41
-      contentType:"application/json; charset=utf-8",
42
-      success: function(){
43
-        updateSlotForm();
44
-        refreshSlots();
45
-        $editSlotModal.modal('hide');
46
-      }
47
-    });
48
-
49
-  });
50
-
51
-  $slotsList.on('click', '.edit-slot', function() {
52
-
53
-    refreshHandlers();
54
-
55
-    var $el = $(this);
56
-    var slotToken = $el.data('slotToken');
57
-
58
-    $.ajax({
59
-      url:'/api/slots/'+slotToken,
60
-      contentType:"application/json; charset=utf-8",
61
-      type:"GET",
62
-      success: function(slot){
63
-        updateSlotForm(slot);
64
-        $editSlotModal.modal('show');
65
-      }
66
-    });
67
-
68
-  });
69
-
70
-  $slotsList.on('click', '.delete-slot', function() {
71
-    var $el = $(this);
72
-    var slotToken = $el.data('slotToken');
73
-    if( !confirm('Êtes vous sûr de vouloir supprimer le slot '+slotToken+' ?') ) return;
74
-    $.ajax({
75
-      url:'/api/slots/'+slotToken,
76
-      type:"DELETE",
77
-      success: function(){
78
-        refreshSlots();
79
-      }
80
-    });
81
-  });
82
-
83
-  $jobsList.on('click', '.delete-job', function() {
84
-    var $el = $(this);
85
-    var jobKey = $el.data('jobKey');
86
-    $.ajax({
87
-      url:'/api/jobs/'+jobKey,
88
-      type:"DELETE",
89
-      success: function(){
90
-        refreshJobs();
17
+  Marang.Models.loadJobs = function() {
18
+    m.request({method: 'GET', url: '/api/jobs'}).then(function(jobs) {
19
+      return Object.keys(jobs)
20
+        .map(function(jobKey) {
21
+          var job = jobs[jobKey];
22
+          job.key = jobKey;
23
+          return new Marang.Models.Job(job);
24
+        })
25
+        .sort(function(j1, j2) {
26
+          return new Date(j2.timestamp()) - new Date(j1.timestamp());
27
+        })
28
+      ;
29
+    }).then(Marang.Models.JobsList);
30
+  };
31
+
32
+  Marang.Models.loadSlots = function() {
33
+    m.request({method: 'GET', url: '/api/slots'}).then(function(slots) {
34
+      return Object.keys(slots)
35
+        .map(function(slotKey) {
36
+          var slot = slots[slotKey];
37
+          return new Marang.Models.Slot(slot);
38
+        })
39
+      ;
40
+    }).then(Marang.Models.SlotsList);
41
+  };
42
+
43
+  Marang.Models.loadServerInfo = function() {
44
+    m.request({method: 'GET', url: '/api/info'}).then(Marang.Models.ServerInfo);
45
+  };
46
+
47
+  Marang.Models.Job = function(data) {
48
+
49
+    data = data || {};
50
+
51
+    this.slot = m.prop(data.slot);
52
+    this.timestamp = m.prop(data.timestamp);
53
+    this.payload = m.prop(data.payload);
54
+    this.remoteAddress = m.prop(data.remoteAddress);
55
+    this.status = m.prop(data.status);
56
+    this.key = m.prop(data.key);
57
+    this.exitCode = m.prop(data.exitCode);
58
+    this.logs = m.prop(data.logs || '');
59
+    this.payload = m.prop(data.payload);
60
+
61
+    this.statusLabel = function() {
62
+      switch(this.status()) {
63
+        case 0:
64
+          return {label: 'Queued', cssClass: 'label-default', icon: 'glyphicon glyphicon-hourglass'};
65
+        case 1:
66
+          return {label: 'Running', cssClass: 'label-info', icon: 'glyphicon glyphicon-cog'};
67
+        case 2:
68
+          if(this.exitCode() !== 0) {
69
+            return {label: 'Failed', cssClass: 'label-danger', icon: 'glyphicon glyphicon-warning-sign'};
70
+          } else {
71
+            return {label: 'Done', cssClass: 'label-success', icon: 'glyphicon glyphicon-ok'};
72
+          }
73
+          break;
74
+        default:
75
+          return {label: 'Unknown', cssClass: 'label-warning', icon: 'glyphicon glyphicon-question-sign'};
91 76
       }
92
-    });
93
-  });
94
-
95
-  $jobsList.on('click', '.replay-job', function() {
96
-
97
-    var $el = $(this);
98
-    var jobKey = $el.data('jobKey');
99
-
100
-    var job;
77
+    };
78
+
79
+    this.refreshLogs = function() {
80
+      return m.request({
81
+        method: 'GET',
82
+        url: '/api/jobs/'+this.key()+'/log',
83
+        deserialize: function(value) {return value;}
84
+      }).then(this.logs);
85
+    };
86
+
87
+    this.delete = function() {
88
+      return m.request({
89
+        method: 'DELETE',
90
+        url: '/api/jobs/'+this.key()
91
+      });
92
+    };
93
+
94
+  };
95
+
96
+  Marang.Models.Slot = function(data) {
97
+    data = data || {};
98
+    this.label = m.prop(data.label || '');
99
+    this.token = m.prop(data.token || uuid.v4());
100
+    this.handler = m.prop(data.handler || '');
101
+    this.handlerArgs = m.prop(data.handlerArgs || '');
102
+    this.handlerUid = m.prop(data.handlerUid || null);
103
+    this.handlerGid = m.prop(data.handlerGid || null);
104
+  };
105
+
106
+  /*************************
107
+   *         Components
108
+   *************************/
109
+
110
+  Marang.Components.TabsNav = {
111
+
112
+    controller: function(args) {
113
+      args = args || {};
114
+      this.activeTab = m.prop(args.activeTab);
115
+    },
116
+
117
+    view: function(ctrl) {
118
+      return m('ul', {class: 'nav nav-tabs', role: 'tablist'}, [
119
+        m('li', {role: 'presentation', class: ctrl.activeTab() === 'jobs' ? 'active' : ''}, [
120
+          m('a', {href: '#/jobs', role: 'tab'}, [
121
+            'Jobs ',
122
+            m('span.glyphicon.glyphicon-tasks')
123
+          ])
124
+        ]),
125
+        m('li', {role: 'presentation', class: ctrl.activeTab() === 'slots' ? 'active' : ''}, [
126
+          m('a', {href: '#/slots', role: 'tab'}, [
127
+            'Slots ',
128
+            m('span.glyphicon.glyphicon-link')
129
+          ])
130
+        ])
131
+      ]);
132
+    }
133
+
134
+  };
135
+
136
+  Marang.Components.Modal = {
137
+
138
+    config: function(attrs) {
139
+      attrs = attrs || {};
140
+      return function(el, isInitialized) {
141
+        var $el = $(el);
142
+        if(!isInitialized) {
143
+          var opts = {};
144
+          opts.backdrop = 'backdrop' in attrs ? attrs.backdrop : 'static';
145
+          opts.keyboard = 'keyboard' in attrs ? attrs.keyboard : false;
146
+          opts.show = 'show' in attrs ? attrs.show : false;
147
+          $el.modal(opts);
148
+        }
149
+        $el.modal(attrs.state);
150
+      };
151
+    },
152
+
153
+    view: function(ctrl, args) {
154
+      var onSuccess = args && typeof args.onSuccess === 'function' ? args.onSuccess : function(){};
155
+      var onClose = args && typeof args.onClose === 'function' ? args.onClose : function(){};
156
+      return m('div', {class:'modal fade', config: Marang.Components.Modal.config(args)}, [
157
+        m('div', {class: 'modal-dialog'}, [
158
+          m('div', {class:'modal-content'}, [
159
+            m('div', {class: 'modal-header'}, [
160
+              m('button', {class:'close', onclick: onClose}, [
161
+                m('span', {ariaHidden: true}, m.trust('&times;'))
162
+              ]),
163
+              m('h4', {class: 'modal-title'}, args.title)
164
+            ]),
165
+            m('div', {class: 'modal-body'}, [args.content]),
166
+            m('div', {class: 'modal-footer'}, [
167
+              m('button', {class: 'btn btn-default', onclick: onClose}, 'Close'),
168
+              m('button', {class: 'btn btn-primary', onclick: onSuccess}, 'Save')
169
+            ])
170
+          ])
171
+        ])
172
+      ]);
173
+    }
174
+
175
+  };
176
+
177
+  Marang.Components.SlotForm = {
178
+
179
+    controller: function(args) {
180
+      this.handlers = m.prop([]);
181
+      m.request({method: 'GET', url: '/api/handlers'}).then(this.handlers);
182
+    },
183
+
184
+    view: function(ctrl, attrs) {
185
+      var slot = attrs.slot;
186
+      return m('form.form', [
187
+        m('.form-group', [
188
+          m('label[for="slotLabelInput"]', 'Label'),
189
+          m('input.form-control[id="slotLabelInput"][placeholder="Slot\'s Label"][required]', {
190
+            value: slot.label(),
191
+            onchange: m.withAttr('value', slot.label)
192
+          })
193
+        ]),
194
+        m('.form-group', [
195
+          m('label[for="slotTokenInput"]', 'Token'),
196
+          m('input.form-control[id="slotTokenInput"][readonly][required]', {value: slot.token()})
197
+        ]),
198
+        m('.form-group', [
199
+          m('label[for="slotHandlerInput"]', 'Handler'),
200
+          m('select.form-control[id="slotHandlerInput"]',
201
+            { value: slot.handler(), onchange: m.withAttr('value', slot.handler)},
202
+            ctrl.handlers().map(function(handler) {
203
+              return m('option', {value: handler}, handler);
204
+            })
205
+          )
206
+        ]),
207
+        m('.form-group', [
208
+          m('label[for="slotHandlerArgsInput"]', 'Handler\'s Arguments'),
209
+          m('input.form-control[id="slotHandlerArgsInput"][placeholder="\'foo\' -bar"]',
210
+            { value: slot.handlerArgs(), onchange: m.withAttr('value', slot.handlerArgs)}
211
+          )
212
+        ]),
213
+        m('.form-group', [
214
+          m('label[for="slotHandlerUidInput"]', 'Handler UID'),
215
+          m('input.form-control[type="number"][min=0][id="slotHandlerUidInput"][placeholder="Handler\'s user numerical ID"]',
216
+            { value: slot.handlerUid(), onchange: m.withAttr('value', slot.handlerUid)}
217
+          )
218
+        ]),
219
+        m('.form-group', [
220
+          m('label[for="slotHandlerGidInput"]', 'Handler GID'),
221
+          m('input.form-control[type="number"][min=0][id="slotHandlerGidInput"][placeholder="Handler\'s group numerical ID"]',
222
+            { value: slot.handlerGid(), onchange: m.withAttr('value', slot.handlerGid)}
223
+          )
224
+        ])
225
+      ]);
226
+    }
227
+  };
228
+
229
+  Marang.Components.SlotEntry = {
230
+
231
+    view: function(ctrl, args) {
232
+      var slot = args.slot;
233
+      var userGroup = (slot.handlerUid() === null ? '--' : slot.handlerUid()) +
234
+        ':' +
235
+        (slot.handlerGid() === null ? '--' : slot.handlerGid())
236
+      ;
237
+      var slotUrl = args.serverInfo.hookServer + '/' + slot.token();
238
+      return m('li.list-group-item.clearfix', [
239
+        m('div.btn-group.pull-right', [
240
+          m('button.btn.btn-primary.btn-xs', {onclick: args.onEditClick.bind(null, slot)}, [
241
+            'Edit ',
242
+            m('span.glyphicon.glyphicon-pencil')
243
+          ]),
244
+          m('button.btn.btn-danger.btn-xs', {onclick: args.onDeleteClick.bind(null, slot)}, [
245
+            'Delete ',
246
+            m('span.glyphicon.glyphicon-trash')
247
+          ])
248
+        ]),
249
+        m('h4.list-group-item-heading', slot.label()),
250
+        m('form.form-horizontal', [
251
+          m('div.form-group.form-group-sm', [
252
+            m('label.col-sm-2.control-label', 'URL'),
253
+            m('div.col-sm-10', [
254
+              m('p.form-control-static', slotUrl)
255
+            ])
256
+          ]),
257
+          m('div.form-group.form-group-sm', [
258
+            m('label.col-sm-2.control-label', 'Handler'),
259
+            m('div.col-sm-10', [
260
+              m('p.form-control-static', slot.handler())
261
+            ])
262
+          ]),
263
+          m('div.form-group.form-group-sm', [
264
+            m('label.col-sm-2.control-label', 'User:Group'),
265
+            m('div.col-sm-10', [
266
+              m('p.form-control-static', userGroup)
267
+            ])
268
+          ])
269
+        ])
270
+      ]);
271
+    }
272
+
273
+  };
274
+
275
+  Marang.Components.JobEntry = {
276
+
277
+    controller: function(args) {
278
+
279
+      var isOverviewVisible = m.prop(false);
280
+      var overviewData = m.prop('');
281
+
282
+      return {
283
+
284
+        isOverviewVisible: isOverviewVisible,
285
+        overviewData: overviewData,
286
+
287
+        onReplayClick: function(job) {
288
+          var serverInfo = Marang.Models.ServerInfo();
289
+          m.request({
290
+            method: 'POST',
291
+            url: serverInfo.hookServer+'/'+job.slot(),
292
+            data: job.payload()
293
+          }).then(function() {
294
+            Marang.Models.loadJobs();
295
+          });
296
+        },
297
+
298
+        onDeleteClick: function(job) {
299
+          job.delete().then(function() {
300
+            Marang.Models.loadJobs();
301
+          });
302
+        },
303
+
304
+        onShowPayloadClick: function(job) {
305
+          isOverviewVisible(true);
306
+          overviewData(JSON.stringify(job.payload(), null, 2));
307
+        },
308
+
309
+        onShowLogsClick: function(job) {
310
+          isOverviewVisible(true);
311
+          overviewData('Loading...');
312
+          job.refreshLogs().then(function() {
313
+            overviewData(m.trust(ansi_up.ansi_to_html(job.logs())));
314
+          });
315
+        },
316
+
317
+        onCloseOverviewClick: function() {
318
+          isOverviewVisible(false);
319
+        }
101 320
 
102
-    $.ajax({
103
-        url:'/api/jobs/'+jobKey,
104
-        type:"GET"
105
-      })
106
-      .then(function(_job) {
107
-        job = _job;
108
-        return $.ajax({
109
-          url:'/api/info',
110
-          type:"GET"
111
-        });
112
-      })
113
-      .then(function(info) {
114
-        var endpoint = info.hookServer+'/'+job.slot;
115
-        return $.ajax({
116
-          url: endpoint,
117
-          data: JSON.stringify(job.payload),
118
-          contentType:"application/json; charset=utf-8",
119
-          type:"POST"
321
+      };
322
+
323
+    },
324
+
325
+    view: function(ctrl, args) {
326
+
327
+      var job = args.job;
328
+      var statusLabel = job.statusLabel();
329
+
330
+      return m('li.list-group-item.clearfix', [
331
+        m('span.label.label-default.pull-right', {class: statusLabel.cssClass}, [
332
+          statusLabel.label+' ',
333
+          m('span', {class: statusLabel.icon})
334
+      ]),
335
+        m('h4.list-group-item-heading', 'Job #'+job.key()),
336
+        m('p.list-group-item-text.spaced-top', [
337
+          m('strong', 'Date '), m('span', new Date(job.timestamp()), m('br')),
338
+          m('strong', 'Remote Address '), m('span', job.remoteAddress(), m('br')),
339
+          m('strong', 'Slot '), m('span', job.slot(), m('br')),
340
+        ]),
341
+        m('div.spaced-top.clearfix', [
342
+          m('div.btn-group.pull-left', [
343
+            m('button.btn.btn-default.btn-xs', { onclick: ctrl.onShowLogsClick.bind(null, job) }, ['Show logs ', m('span.glyphicon.glyphicon-console')]),
344
+            m('button.btn.btn-default.btn-xs', { onclick: ctrl.onShowPayloadClick.bind(null, job) }, ['Show payload ', m('span.glyphicon.glyphicon-info-sign')]),
345
+            m('button.btn.btn-default.btn-xs',
346
+              {onclick: ctrl.onCloseOverviewClick, class: ctrl.isOverviewVisible() ? '' : 'hidden'},
347
+              ['Close ', m('span.glyphicon.glyphicon-remove')]
348
+            )
349
+          ]),
350
+          m('div.btn-group.pull-right', [
351
+            m('button.btn.btn-primary.btn-xs', { onclick: ctrl.onReplayClick.bind(null, job) }, ['Replay ', m('span.glyphicon.glyphicon-repeat')]),
352
+            m('button.btn.btn-danger.btn-xs', { onclick: ctrl.onDeleteClick.bind(null, job) }, ['Delete ', m('span.glyphicon.glyphicon-trash')])
353
+          ])
354
+        ]),
355
+        m('pre.overview', {class: ctrl.isOverviewVisible() ? '' : 'hidden'}, ctrl.overviewData())
356
+      ]);
357
+    }
358
+
359
+  };
360
+
361
+  /*************************
362
+   *         Modules
363
+   *************************/
364
+
365
+  Marang.Modules.Jobs = {
366
+
367
+    controller: function() {
368
+      Marang.Models.loadJobs();
369
+      Marang.Models.loadServerInfo();
370
+    },
371
+
372
+    view: function(ctrl) {
373
+
374
+      var jobsList = Marang.Models.JobsList();
375
+      var jobItems;
376
+
377
+      if(jobsList.length > 0) {
378
+        jobItems = jobsList.map(function(job) {
379
+          return m(Marang.Components.JobEntry, {
380
+            job: job
381
+          });
120 382
         });
121
-      })
122
-      .then(function() {
123
-        refresh();
124
-      })
125
-    ;
126
-
127
-  });
128
-
129
-  // Hide all collapsable
130
-  $jobsList.on('show.bs.collapse', '.collapse', function () {
131
-    $jobsList.find('.collapse').collapse('hide');
132
-  });
133
-
134
-  $jobsList.on('show.bs.collapse', '.collapse.job-log', function () {
135
-    var $el = $(this);
136
-    var jobKey = $el.data('jobKey');
137
-    $.get('/api/jobs/'+jobKey+'/log', function(log) {
138
-      if(!log) {
139
-        $el.empty().html('<i>No data.</i>');
140 383
       } else {
141
-        var $pre = $('<pre>'+ansi_up.ansi_to_html(log)+'</pre>');
142
-        $el.empty().html($pre);
143
-        $pre.scrollTop($pre[0].scrollHeight);
384
+        jobItems = [m('li.list-group-item', {style:'text-align:center'}, 'No job yet.')];
144 385
       }
145
-    });
146
-  });
147
-
148
-  function refreshInfo(cb) {
149
-    $.getJSON('/api/info?t='+Date.now(), cb);
150
-  }
151
-
152
-  function refreshSlots() {
153
-
154
-    refreshInfo(function(info) {
155
-
156
-      $.getJSON('/api/slots?t='+Date.now(), function(data) {
157 386
 
158
-        var $items = Object.keys(data).map(function(slotKey) {
159
-          var slot = data[slotKey];
160
-          slot.handlerGid = 'handlerGid' in slot ? slot.handlerGid : '--';
161
-          slot.handlerUid = 'handlerUid' in slot ? slot.handlerUid : '--';
162
-          slot.url = info.hookServer + '/' + slot.token;
163
-          return slotItemTmpl(slot);
387
+      return m('div', [
388
+        m.component(Marang.Components.TabsNav, {activeTab: 'jobs'}),
389
+        m('ul.list-group.clearfix', {style: 'margin-top:15px'}, jobItems)
390
+      ]);
391
+    }
392
+
393
+  };
394
+
395
+  Marang.Modules.Slots = {
396
+
397
+    controller: function() {
398
+
399
+      var slotModalState = this.slotModalState = m.prop('hide');
400
+      var slotModalTitle = this.slotModalTitle = m.prop('Add slot');
401
+      var editedSlot = this.editedSlot = m.prop(new Marang.Models.Slot());
402
+
403
+      Marang.Models.loadServerInfo();
404
+      Marang.Models.loadSlots();
405
+
406
+      this.addSlotClickHandler = function() {
407
+        editedSlot(new Marang.Models.Slot());
408
+        slotModalTitle('Add slot');
409
+        slotModalState('show');
410
+      };
411
+
412
+      this.onModalClose = function() {
413
+        slotModalState('hide');
414
+      };
415
+
416
+      this.saveSlot = function() {
417
+        slotModalState('hide');
418
+        var slot = editedSlot();
419
+        m.request({method: 'POST', url: '/api/slots/'+slot.token(), data: slot});
420
+        Marang.Models.loadSlots();
421
+      };
422
+
423
+      this.editSlot = function(slot) {
424
+        editedSlot(slot);
425
+        slotModalTitle('Edit slot');
426
+        slotModalState('show');
427
+      };
428
+
429
+      this.deleteSlot = function(slot) {
430
+        var result = confirm('Vous allez supprimer le slot "'+slot.label()+'". Êtes vous sûr ?');
431
+        if(!result) return;
432
+        m.request({method: 'DELETE', url: '/api/slots/'+slot.token()});
433
+        Marang.Models.loadSlots();
434
+      };
435
+
436
+    },
437
+
438
+    view: function(ctrl) {
439
+
440
+      var slotItems;
441
+      var slotsList = Marang.Models.SlotsList();
442
+      var serverInfo = Marang.Models.ServerInfo();
443
+
444
+      if(slotsList.length > 0) {
445
+        slotItems = slotsList.map(function(slot) {
446
+          return m(Marang.Components.SlotEntry, {
447
+            key: slot.token(),
448
+            slot: slot,
449
+            serverInfo: serverInfo,
450
+            onDeleteClick: ctrl.deleteSlot,
451
+            onEditClick: ctrl.editSlot
452
+          });
164 453
         });
165
-
166
-        if($items.length === 0) {
167
-          $slotsList.html('<li class="list-group-item text-center">No slots yet.</li>');
168
-        } else {
169
-          $slotsList.empty().append($items);
170
-        }
171
-
172
-      });
173
-
174
-
175
-    });
176
-  }
177
-
178
-  function refreshJobs() {
179
-    $.getJSON('/api/jobs?t='+Date.now(), function(data) {
180
-      var $items = Object.keys(data)
181
-        .map(function(jobKey) {
182
-          var job = data[jobKey];
183
-          job.key = jobKey;
184
-          job.hasTerminated = job.exitCode !== undefined;
185
-          job.hasError = job.exitCode !== 0;
186
-          job.dateCreation = moment(new Date(job.timestamp)).fromNow();
187
-          return job;
188
-        })
189
-        .sort(function(j1, j2) {
190
-          return new Date(j2.timestamp) - new Date(j1.timestamp);
191
-        })
192
-        .map(jobItemTmpl)
193
-      ;
194
-      if($items.length === 0) {
195
-        $jobsList.html('<li class="list-group-item text-center">No jobs yet.</li>');
196 454
       } else {
197
-        $jobsList.empty().append($items);
455
+        slotItems = [m('li.list-group-item', {style:'text-align:center'}, 'No slot yet.')];
198 456
       }
199
-    });
200
-  }
201 457
 
202
-  function refreshHandlers() {
203
-    $.getJSON('/api/handlers?t='+Date.now(), function(data) {
204
-      var options = data.map(function(handlerKey) {
205
-        return '<option value="'+handlerKey+'">'+handlerKey+'</option>';
206
-      });
207
-      $slotHandlerInput.html(options.join(''));
208
-    });
209
-  }
210
-
211
-  function updateSlotForm(slot) {
212
-    $editSlotModal.find('.modal-title').html(slot ? 'Edit slot' : 'New slot');
213
-    $editSlotForm.find('#slotLabelInput').val(slot ? slot.label : '');
214
-    $editSlotForm.find('#slotTokenInput').val(slot ? slot.token : '');
215
-    $editSlotForm.find('#slotHandlerInput').val(slot ? slot.handler : '');
216
-    $editSlotForm.find('#slotHandlerArgs').val(slot ? slot.handlerArgs : '');
217
-    $editSlotForm.find('#slotHandlerUidInput').val(slot ? slot.handlerUid : '');
218
-    $editSlotForm.find('#slotHandlerGidInput').val(slot ? slot.handlerGid : '');
219
-  }
220
-
221
-  function refresh() {
222
-    refreshSlots();
223
-    refreshJobs();
224
-  }
225
-
226
-  function generateSlotToken() {
227
-    $slotTokenInput.val(uuid.v4());
228
-  }
229
-
230
-  Handlebars.registerHelper('stringify', function(input) {
231
-    return JSON.stringify(input, null, 2);
458
+      return [
459
+        m('div', [
460
+          m(Marang.Components.TabsNav, {activeTab: 'slots'}),
461
+          m('div', {class: 'clearfix', style:'margin-top:15px'}, [
462
+            m('button', {class: 'btn btn-success pull-right', onclick: ctrl.addSlotClickHandler}, [
463
+              'Add slot ',
464
+              m('span.glyphicon.glyphicon-plus')
465
+            ])
466
+          ])
467
+        ]),
468
+        m(Marang.Components.Modal, {
469
+          state: ctrl.slotModalState(),
470
+          title: ctrl.slotModalTitle(),
471
+          content: m(Marang.Components.SlotForm, {slot: ctrl.editedSlot()}),
472
+          onSuccess: ctrl.saveSlot,
473
+          onClose: ctrl.onModalClose
474
+        }),
475
+        m('ul.list-group.clearfix', {style: 'margin-top:15px'}, slotItems)
476
+      ];
477
+    }
478
+
479
+  };
480
+
481
+  /*************************
482
+   *         Routes
483
+   *************************/
484
+
485
+  m.route.mode = "hash";
486
+
487
+  m.route(document.getElementById('app'), '/', {
488
+    '': Marang.Modules.Jobs,
489
+    '/jobs': Marang.Modules.Jobs,
490
+    '/slots': Marang.Modules.Slots
232 491
   });
233 492
 
234
-
235
-});
493
+}());

+ 0
- 475
client/js/app2.js View File

@@ -1,475 +0,0 @@
1
-(function() {
2
-
3
-  var Marang = {
4
-    Models: {},
5
-    Modules: {},
6
-    Components: {}
7
-  };
8
-
9
-  /*************************
10
-   *         Models
11
-   *************************/
12
-
13
-  Marang.Models.JobsList = m.prop([]);
14
-  Marang.Models.SlotsList = m.prop([]);
15
-  Marang.Models.ServerInfo = m.prop({});
16
-
17
-  Marang.Models.loadJobs = function() {
18
-    m.request({method: 'GET', url: '/api/jobs'}).then(function(jobs) {
19
-      return Object.keys(jobs)
20
-        .map(function(jobKey) {
21
-          var job = jobs[jobKey];
22
-          job.key = jobKey;
23
-          return new Marang.Models.Job(job);
24
-        })
25
-      ;
26
-    }).then(Marang.Models.JobsList);
27
-  };
28
-
29
-  Marang.Models.loadSlots = function() {
30
-    m.request({method: 'GET', url: '/api/slots'}).then(function(slots) {
31
-      return Object.keys(slots)
32
-        .map(function(slotKey) {
33
-          var slot = slots[slotKey];
34
-          return new Marang.Models.Slot(slot);
35
-        })
36
-      ;
37
-    }).then(Marang.Models.SlotsList);
38
-  };
39
-
40
-  Marang.Models.loadServerInfo = function() {
41
-    m.request({method: 'GET', url: '/api/info'}).then(Marang.Models.ServerInfo);
42
-  };
43
-
44
-  Marang.Models.Job = function(data) {
45
-
46
-    data = data || {};
47
-
48
-    this.slot = m.prop(data.slot);
49
-    this.timestamp = m.prop(data.timestamp);
50
-    this.payload = m.prop(data.payload);
51
-    this.remoteAddress = m.prop(data.remoteAddress);
52
-    this.status = m.prop(data.status);
53
-    this.key = m.prop(data.key);
54
-    this.exitCode = m.prop(data.exitCode);
55
-    this.logs = m.prop(data.logs || '');
56
-    this.payload = m.prop(data.payload);
57
-
58
-    this.statusLabel = function() {
59
-      switch(this.status()) {
60
-        case 0:
61
-          return {label: 'Queued', cssClass: 'label-default', icon: 'glyphicon glyphicon-hourglass'};
62
-        case 1:
63
-          return {label: 'Running', cssClass: 'label-info', icon: 'glyphicon glyphicon-cog'};
64
-        case 2:
65
-          if(this.exitCode() !== 0) {
66
-            return {label: 'Failed', cssClass: 'label-danger', icon: 'glyphicon glyphicon-warning-sign'};
67
-          } else {
68
-            return {label: 'Done', cssClass: 'label-success', icon: 'glyphicon glyphicon-ok'};
69
-          }
70
-          break;
71
-        default:
72
-          return {label: 'Unknown', cssClass: 'label-warning', icon: 'glyphicon glyphicon-question-sign'};
73
-      }
74
-    };
75
-
76
-    this.refreshLogs = function() {
77
-      return m.request({
78
-        method: 'GET',
79
-        url: '/api/jobs/'+this.key()+'/log',
80
-        deserialize: function(value) {return value;}
81
-      }).then(this.logs);
82
-    };
83
-
84
-    this.delete = function() {
85
-      return m.request({
86
-        method: 'DELETE',
87
-        url: '/api/jobs/'+this.key()
88
-      });
89
-    };
90
-
91
-  };
92
-
93
-  Marang.Models.Slot = function(data) {
94
-    data = data || {};
95
-    this.label = m.prop(data.label || '');
96
-    this.token = m.prop(data.token || uuid.v4());
97
-    this.handler = m.prop(data.handler || '');
98
-    this.handlerArgs = m.prop(data.handlerArgs || '');
99
-    this.handlerUid = m.prop(data.handlerUid || null);
100
-    this.handlerGid = m.prop(data.handlerGid || null);
101
-  };
102
-
103
-  /*************************
104
-   *         Components
105
-   *************************/
106
-
107
-  Marang.Components.TabsNav = {
108
-
109
-    controller: function(args) {
110
-      args = args || {};
111
-      this.activeTab = m.prop(args.activeTab);
112
-    },
113
-
114
-    view: function(ctrl) {
115
-      return m('ul', {class: 'nav nav-tabs', role: 'tablist'}, [
116
-        m('li', {role: 'presentation', class: ctrl.activeTab() === 'jobs' ? 'active' : ''}, [
117
-          m('a', {href: '#/jobs', role: 'tab'}, [
118
-            'Jobs ',
119
-            m('span.glyphicon.glyphicon-tasks')
120
-          ])
121
-        ]),
122
-        m('li', {role: 'presentation', class: ctrl.activeTab() === 'slots' ? 'active' : ''}, [
123
-          m('a', {href: '#/slots', role: 'tab'}, [
124
-            'Slots ',
125
-            m('span.glyphicon.glyphicon-link')
126
-          ])
127
-        ])
128
-      ]);
129
-    }
130
-
131
-  };
132
-
133
-  Marang.Components.Modal = {
134
-
135
-    config: function(attrs) {
136
-      attrs = attrs || {};
137
-      return function(el, isInitialized) {
138
-        var $el = $(el);
139
-        if(!isInitialized) {
140
-          var opts = {};
141
-          opts.backdrop = 'backdrop' in attrs ? attrs.backdrop : 'static';
142
-          opts.keyboard = 'keyboard' in attrs ? attrs.keyboard : false;
143
-          opts.show = 'show' in attrs ? attrs.show : false;
144
-          $el.modal(opts);
145
-        }
146
-        $el.modal(attrs.state);
147
-      };
148
-    },
149
-
150
-    view: function(ctrl, args) {
151
-      var onSuccess = args && typeof args.onSuccess === 'function' ? args.onSuccess : function(){};
152
-      var onClose = args && typeof args.onClose === 'function' ? args.onClose : function(){};
153
-      return m('div', {class:'modal fade', config: Marang.Components.Modal.config(args)}, [
154
-        m('div', {class: 'modal-dialog'}, [
155
-          m('div', {class:'modal-content'}, [
156
-            m('div', {class: 'modal-header'}, [
157
-              m('button', {class:'close', onclick: onClose}, [
158
-                m('span', {ariaHidden: true}, m.trust('&times;'))
159
-              ]),
160
-              m('h4', {class: 'modal-title'}, args.title)
161
-            ]),
162
-            m('div', {class: 'modal-body'}, [args.content]),
163
-            m('div', {class: 'modal-footer'}, [
164
-              m('button', {class: 'btn btn-default', onclick: onClose}, 'Close'),
165
-              m('button', {class: 'btn btn-primary', onclick: onSuccess}, 'Save')
166
-            ])
167
-          ])
168
-        ])
169
-      ]);
170
-    }
171
-
172
-  };
173
-
174
-  Marang.Components.SlotForm = {
175
-
176
-    controller: function(args) {
177
-      this.handlers = m.prop([]);
178
-      m.request({method: 'GET', url: '/api/handlers'}).then(this.handlers);
179
-    },
180
-
181
-    view: function(ctrl, attrs) {
182
-      var slot = attrs.slot;
183
-      return m('form.form', [
184
-        m('.form-group', [
185
-          m('label[for="slotLabelInput"]', 'Label'),
186
-          m('input.form-control[id="slotLabelInput"][placeholder="Slot\'s Label"][required]', {
187
-            value: slot.label(),
188
-            onchange: m.withAttr('value', slot.label)
189
-          })
190
-        ]),
191
-        m('.form-group', [
192
-          m('label[for="slotTokenInput"]', 'Token'),
193
-          m('input.form-control[id="slotTokenInput"][readonly][required]', {value: slot.token()})
194
-        ]),
195
-        m('.form-group', [
196
-          m('label[for="slotHandlerInput"]', 'Handler'),
197
-          m('select.form-control[id="slotHandlerInput"]',
198
-            { value: slot.handler(), onchange: m.withAttr('value', slot.handler)},
199
-            ctrl.handlers().map(function(handler) {
200
-              return m('option', {value: handler}, handler);
201
-            })
202
-          )
203
-        ]),
204
-        m('.form-group', [
205
-          m('label[for="slotHandlerArgsInput"]', 'Handler\'s Arguments'),
206
-          m('input.form-control[id="slotHandlerArgsInput"][placeholder="\'foo\' -bar"]',
207
-            { value: slot.handlerArgs(), onchange: m.withAttr('value', slot.handlerArgs)}
208
-          )
209
-        ]),
210
-        m('.form-group', [
211
-          m('label[for="slotHandlerUidInput"]', 'Handler UID'),
212
-          m('input.form-control[type="number"][min=0][id="slotHandlerUidInput"][placeholder="Handler\'s user numerical ID"]',
213
-            { value: slot.handlerUid(), onchange: m.withAttr('value', slot.handlerUid)}
214
-          )
215
-        ]),
216
-        m('.form-group', [
217
-          m('label[for="slotHandlerGidInput"]', 'Handler GID'),
218
-          m('input.form-control[type="number"][min=0][id="slotHandlerGidInput"][placeholder="Handler\'s group numerical ID"]',
219
-            { value: slot.handlerGid(), onchange: m.withAttr('value', slot.handlerGid)}
220
-          )
221
-        ])
222
-      ]);
223
-    }
224
-  };
225
-
226
-  Marang.Components.SlotEntry = {
227
-
228
-    view: function(ctrl, args) {
229
-      var slot = args.slot;
230
-      var userGroup = (slot.handlerUid() === null ? '--' : slot.handlerUid()) +
231
-        ':' +
232
-        (slot.handlerGid() === null ? '--' : slot.handlerGid())
233
-      ;
234
-      var slotUrl = args.serverInfo.hookServer + '/' + slot.token();
235
-      return m('li.list-group-item.clearfix', [
236
-        m('div.btn-group.pull-right', [
237
-          m('button.btn.btn-primary.btn-xs', {onclick: args.onEditClick.bind(null, slot)}, [
238
-            'Edit ',
239
-            m('span.glyphicon.glyphicon-pencil')
240
-          ]),
241
-          m('button.btn.btn-danger.btn-xs', {onclick: args.onDeleteClick.bind(null, slot)}, [
242
-            'Delete ',
243
-            m('span.glyphicon.glyphicon-trash')
244
-          ])
245
-        ]),
246
-        m('h4.list-group-item-heading', slot.label()),
247
-        m('form.form', [
248
-          m('div.form-group.form-group-sm', [
249
-            m('label.col-sm-2.control-label', 'URL'),
250
-            m('div.col-sm-10', [
251
-              m('p.form-control-static', slotUrl)
252
-            ])
253
-          ]),
254
-          m('div.form-group.form-group-sm', [
255
-            m('label.col-sm-2.control-label', 'Handler'),
256
-            m('div.col-sm-10', [
257
-              m('p.form-control-static', slot.handler())
258
-            ])
259
-          ]),
260
-          m('div.form-group.form-group-sm', [
261
-            m('label.col-sm-2.control-label', 'User:Group'),
262
-            m('div.col-sm-10', [
263
-              m('p.form-control-static', userGroup)
264
-            ])
265
-          ])
266
-        ])
267
-      ]);
268
-    }
269
-
270
-  };
271
-
272
-  Marang.Components.JobEntry = {
273
-
274
-    controller: function(args) {
275
-
276
-      var isOverviewVisible = m.prop(false);
277
-      var overviewData = m.prop('');
278
-
279
-      return {
280
-
281
-        isOverviewVisible: isOverviewVisible,
282
-        overviewData: overviewData,
283
-
284
-        onReplayClick: function(job) {
285
-
286
-        },
287
-
288
-        onDeleteClick: function(job) {
289
-          job.delete().then(function() {
290
-            Marang.Models.loadJobs();
291
-          });
292
-        },
293
-
294
-        onShowPayloadClick: function(job) {
295
-          isOverviewVisible(true);
296
-          overviewData(JSON.stringify(job.payload(), null, 2));
297
-        },
298
-
299
-        onShowLogsClick: function(job) {
300
-          isOverviewVisible(true);
301
-          overviewData('Loading...');
302
-          job.refreshLogs().then(function() {
303
-            overviewData(m.trust(ansi_up.ansi_to_html(job.logs())));
304
-          });
305
-        }
306
-
307
-      };
308
-
309
-    },
310
-
311
-    view: function(ctrl, args) {
312
-
313
-      var job = args.job;
314
-      var statusLabel = job.statusLabel();
315
-
316
-      return m('li.list-group-item.clearfix', [
317
-        m('span.label.label-default.pull-right', {class: statusLabel.cssClass}, [
318
-          statusLabel.label+' ',
319
-          m('span', {class: statusLabel.icon})
320
-      ]),
321
-        m('h4.list-group-item-heading', 'Job #'+job.key()),
322
-        m('p.list-group-item-text.spaced-top', [
323
-          m('strong', 'Job Key '), m('span', job.key(), m('br')),
324
-          m('strong', 'Date '), m('span', new Date(job.timestamp()), m('br')),
325
-          m('strong', 'Remote Address '), m('span', job.remoteAddress(), m('br')),
326
-          m('strong', 'Slot '), m('span', job.slot(), m('br')),
327
-        ]),
328
-        m('div.spaced-top.clearfix', [
329
-          m('div.btn-group.pull-left', [
330
-            m('button.btn.btn-default.btn-xs', { onclick: ctrl.onShowLogsClick.bind(null, job) }, ['Show logs ', m('span.glyphicon.glyphicon-console')]),
331
-            m('button.btn.btn-default.btn-xs', { onclick: ctrl.onShowPayloadClick.bind(null, job) }, ['Show payload ', m('span.glyphicon.glyphicon-info-sign')])
332
-          ]),
333
-          m('div.btn-group.pull-right', [
334
-            m('button.btn.btn-primary.btn-xs', { onclick: ctrl.onReplayClick.bind(null, job) }, ['Replay ', m('span.glyphicon.glyphicon-repeat')]),
335
-            m('button.btn.btn-danger.btn-xs', { onclick: ctrl.onDeleteClick.bind(null, job) }, ['Delete ', m('span.glyphicon.glyphicon-trash')])
336
-          ])
337
-        ]),
338
-        m('pre.overview', {class: ctrl.isOverviewVisible() ? '' : 'hidden'}, ctrl.overviewData())
339
-      ]);
340
-    }
341
-
342
-  };
343
-
344
-  /*************************
345
-   *         Modules
346
-   *************************/
347
-
348
-  Marang.Modules.Jobs = {
349
-
350
-    controller: function() {
351
-      Marang.Models.loadJobs();
352
-    },
353
-
354
-    view: function(ctrl) {
355
-
356
-      var jobsList = Marang.Models.JobsList();
357
-      var jobItems;
358
-
359
-      if(jobsList.length > 0) {
360
-        jobItems = jobsList.map(function(job) {
361
-          return m(Marang.Components.JobEntry, {
362
-            job: job
363
-          });
364
-        });
365
-      } else {
366
-        jobItems = [m('li.list-group-item', {style:'text-align:center'}, 'No job yet.')];
367
-      }
368
-
369
-      return m('div', [
370
-        m.component(Marang.Components.TabsNav, {activeTab: 'jobs'}),
371
-        m('ul.list-group.clearfix', {style: 'margin-top:15px'}, jobItems)
372
-      ]);
373
-    }
374
-
375
-  };
376
-
377
-  Marang.Modules.Slots = {
378
-
379
-    controller: function() {
380
-
381
-      var slotModalState = this.slotModalState = m.prop('hide');
382
-      var slotModalTitle = this.slotModalTitle = m.prop('Add slot');
383
-      var editedSlot = this.editedSlot = m.prop(new Marang.Models.Slot());
384
-
385
-      Marang.Models.loadServerInfo();
386
-      Marang.Models.loadSlots();
387
-
388
-      this.addSlotClickHandler = function() {
389
-        editedSlot(new Marang.Models.Slot());
390
-        slotModalTitle('Add slot');
391
-        slotModalState('show');
392
-      };
393
-
394
-      this.onModalClose = function() {
395
-        slotModalState('hide');
396
-      };
397
-
398
-      this.saveSlot = function() {
399
-        slotModalState('hide');
400
-        var slot = editedSlot();
401
-        m.request({method: 'POST', url: '/api/slots/'+slot.token(), data: slot});
402
-        Marang.Models.loadSlots();
403
-      };
404
-
405
-      this.editSlot = function(slot) {
406
-        editedSlot(slot);
407
-        slotModalTitle('Edit slot');
408
-        slotModalState('show');
409
-      };
410
-
411
-      this.deleteSlot = function(slot) {
412
-        var result = confirm('Vous allez supprimer le slot "'+slot.label()+'". Êtes vous sûr ?');
413
-        if(!result) return;
414
-        m.request({method: 'DELETE', url: '/api/slots/'+slot.token()});
415
-        Marang.Models.loadSlots();
416
-      };
417
-
418
-    },
419
-
420
-    view: function(ctrl) {
421
-
422
-      var slotItems;
423
-      var slotsList = Marang.Models.SlotsList();
424
-      var serverInfo = Marang.Models.ServerInfo();
425
-
426
-      if(slotsList.length > 0) {
427
-        slotItems = slotsList.map(function(slot) {
428
-          return m(Marang.Components.SlotEntry, {
429
-            key: slot.token(),
430
-            slot: slot,
431
-            serverInfo: serverInfo,
432
-            onDeleteClick: ctrl.deleteSlot,
433
-            onEditClick: ctrl.editSlot
434
-          });
435
-        });
436
-      } else {
437
-        slotItems = [m('li.list-group-item', {style:'text-align:center'}, 'No slot yet.')];
438
-      }
439
-
440
-      return [
441
-        m('div', [
442
-          m(Marang.Components.TabsNav, {activeTab: 'slots'}),
443
-          m('div', {class: 'clearfix', style:'margin-top:15px'}, [
444
-            m('button', {class: 'btn btn-success pull-right', onclick: ctrl.addSlotClickHandler}, [
445
-              'Add slot ',
446
-              m('span.glyphicon.glyphicon-plus')
447
-            ])
448
-          ])
449
-        ]),
450
-        m(Marang.Components.Modal, {
451
-          state: ctrl.slotModalState(),
452
-          title: ctrl.slotModalTitle(),
453
-          content: m(Marang.Components.SlotForm, {slot: ctrl.editedSlot()}),
454
-          onSuccess: ctrl.saveSlot,
455
-          onClose: ctrl.onModalClose
456
-        }),
457
-        m('ul.list-group.clearfix', {style: 'margin-top:15px'}, slotItems)
458
-      ];
459
-    }
460
-
461
-  };
462
-
463
-  /*************************
464
-   *         Routes
465
-   *************************/
466
-
467
-  m.route.mode = "hash";
468
-
469
-  m.route(document.getElementById('app'), '/', {
470
-    '': Marang.Modules.Jobs,
471
-    '/jobs': Marang.Modules.Jobs,
472
-    '/slots': Marang.Modules.Slots
473
-  });
474
-
475
-}());

+ 13
- 5
handlers/handler-example View File

@@ -1,24 +1,32 @@
1 1
 #!/bin/bash
2 2
 
3
+normal=$(printf '\e[0m')
4
+red=$(printf '\e[31m')
5
+cyan=$(printf '\e[36m')
6
+green=$(printf '\e[32m')
7
+yellow=$(printf '\e[33m')
8
+
3 9
 cat <<EOF
4 10
 
5 11
 ==== Marang handler example ====
6 12
 
7
-Handler: $(readlink -f $0)
13
+Handler: ${cyan}$(readlink -f $0)${normal}
8 14
 
9 15
 Executed as:
10 16
 
11
-  User: $(whoami)
12
-  Groups: $(groups)
17
+  User: ${green}$(whoami)${normal}
18
+  Groups: ${green}$(groups)${normal}
13 19
 
14 20
 Arguments:
15 21
 
16
-  ${@}
22
+  ${yellow}${@}${normal}
17 23
 
18 24
 Payload:
19 25
 
20
-  ${MARANG_PAYLOAD}
26
+  ${red}${MARANG_PAYLOAD}${normal}
21 27
 
22 28
 ================================
23 29
 
24 30
 EOF
31
+
32
+sleep 5

+ 6
- 5
lib/hook-server.js View File

@@ -43,17 +43,18 @@ p._configureRoutes = function() {
43 43
 
44 44
   var app = this._app;
45 45
 
46
-  var defaultWhitelist = [
47
-    { port: config.webApp.port, host: config.webApp.host },
48
-    { port: config.webApp.publicPort, host: config.webApp.publicHost }
49
-  ];
46
+  var defaultWhitelist = [{ port: config.webApp.port, host: config.webApp.host }];
47
+
48
+  if(config.webApp.publicPort && config.webApp.publicHost) {
49
+    defaultWhitelist.push({ port: config.webApp.publicPort, host: config.webApp.publicHost });
50
+  }
50 51
 
51 52
   var whitelist = defaultWhitelist.concat(config.hookServer.corsWhitelist);
52 53
 
53 54
   app.use(morgan('combined'));
54 55
   app.use(cors({
55 56
     origin: function(origin, cb){
56
-      if(!origin) return cb(null, true);
57
+      if(!origin) return cb(null, false);
57 58
       var parsedOrigin = url.parse(origin);
58 59
       var ok = whitelist.reduce(function(ok, entry) {
59 60
         if(ok) return true;

+ 3
- 2
lib/job-queue.js View File

@@ -5,6 +5,7 @@ const EventEmitter = require('events').EventEmitter;
5 5
 const path = require('path');
6 6
 const spawn = require('child_process').spawn;
7 7
 const SHELL_REGEX = /('(\\'|[^'])*'|"(\\"|[^"])*"|\/(\\\/|[^\/])*\/|(\\ |[^ ])+|[\w-]+)/g;
8
+const _ = require('lodash');
8 9
 
9 10
 class JobQueue extends EventEmitter {
10 11
 
@@ -49,8 +50,8 @@ class JobQueue extends EventEmitter {
49 50
       jobProcess = spawn(handlerPath, handlerArgs, {
50 51
         cwd: this._handlersDir,
51 52
         env: handlerEnv,
52
-        uid: job.slot.handlerUid !== undefined ? +job.slot.handlerUid : undefined,
53
-        gid: job.slot.handlerGid !== undefined ? +job.slot.handlerGid : undefined,
53
+        uid: _.isInteger(job.slot.handlerUid) ? +job.slot.handlerUid : undefined,
54
+        gid: _.isInteger(job.slot.handlerGid) ? +job.slot.handlerGid : undefined,
54 55
       });
55 56
     } catch(err) {
56 57
       this._currentJob = null;

+ 1
- 0
package.json View File

@@ -22,6 +22,7 @@
22 22
     "level": "^1.2.0",
23 23
     "level-party": "^2.1.2",
24 24
     "level-sublevel": "^6.4.6",
25
+    "lodash": "^4.11.1",
25 26
     "mithril": "^0.2.3",
26 27
     "moment": "^2.10.3",
27 28
     "morgan": "^1.6.1",

+ 9
- 36
payload.json View File

@@ -1,38 +1,11 @@
1 1
 {
2
-  "secret": "",
3
-  "ref": "refs/heads/master",
4
-  "commits": [
5
-    {
6
-      "id": "8d64f7593db24846edb71283c1dfb3f0e6cfd009",
7
-      "message": "Test\n",
8
-      "url": "https://forge.cadoles.com/wpetit/marang-tamarin-test/commit/8d64f7593db24846edb71283c1dfb3f0e6cfd009",
9
-      "author": {
10
-        "name": "William Petit",
11
-        "email": "wpetit@cadoles.com",
12
-        "username": "wpetit"
13
-      }
14
-    }
15
-  ],
16
-  "repository": {
17
-    "id": 44,
18
-    "name": "marang-tamarin-test",
19
-    "url": "https://forge.cadoles.com/wpetit/marang-tamarin-test",
20
-    "description": "Test project for Marang/Tamarin",
21
-    "website": "",
22
-    "watchers": 1,
23
-    "owner": {
24
-      "name": "William Petit",
25
-      "email": "wpetit@cadoles.com",
26
-      "username": "wpetit"
27
-    },
28
-    "private": false
29
-  },
30
-  "pusher": {
31
-    "name": "William Petit",
32
-    "email": "wpetit@cadoles.com",
33
-    "username": "wpetit"
34
-  },
35
-  "before": "279b18dd87063514fa33a1d12efea46c71788725",
36
-  "after": "8d64f7593db24846edb71283c1dfb3f0e6cfd009",
37
-  "compare_url": "https://forge.cadoles.com/wpetit/marang-tamarin-test/compare/279b18dd87063514fa33a1d12efea46c71788725...8d64f7593db24846edb71283c1dfb3f0e6cfd009"
2
+  "hello": "world",
3
+  "test": [
4
+    "foo",
5
+    "bar",
6
+    1,
7
+    2,
8
+    4,
9
+    null
10
+  ]
38 11
 }

Loading…
Cancel
Save