Bind task manager backbone app to websocket using autobahn

This commit is contained in:
Nicolas Le Goff
2014-02-26 16:05:41 +01:00
parent d3bf3f747e
commit 0a2d4fe04d
7 changed files with 222 additions and 50 deletions

View File

@@ -52,7 +52,7 @@ module.exports = function(grunt) {
"autobahnjs": { "autobahnjs": {
"expand": true, "expand": true,
"src": [ "src": [
"<%= path.bower %>/autobahnjs/build/autobahn.min.js", "<%= path.bower %>/autobahnjs/build/autobahn.js",
"<%= path.bower %>/autobahnjs/LICENSE" "<%= path.bower %>/autobahnjs/LICENSE"
], ],
"dest": "<%= path.asset %>/autobahnjs/", "dest": "<%= path.asset %>/autobahnjs/",

View File

@@ -14,6 +14,7 @@ namespace Alchemy\Phrasea\Controller\Admin;
use Alchemy\Phrasea\Exception\InvalidArgumentException; use Alchemy\Phrasea\Exception\InvalidArgumentException;
use Alchemy\Phrasea\Form\TaskForm; use Alchemy\Phrasea\Form\TaskForm;
use Alchemy\Phrasea\Model\Entities\Task; use Alchemy\Phrasea\Model\Entities\Task;
use Alchemy\Phrasea\TaskManager\TaskManagerStatus;
use Silex\Application; use Silex\Application;
use Silex\ControllerProviderInterface; use Silex\ControllerProviderInterface;
use Symfony\Component\HttpFoundation\Request; use Symfony\Component\HttpFoundation\Request;
@@ -46,6 +47,10 @@ class TaskManager implements ControllerProviderInterface
->get('/scheduler', 'controller.admin.task:getScheduler') ->get('/scheduler', 'controller.admin.task:getScheduler')
->bind('admin_scheduler'); ->bind('admin_scheduler');
$controllers
->get('/live', 'controller.admin.task:getLiveInformation')
->bind('admin_tasks_live_info');
$controllers $controllers
->post('/tasks/create', 'controller.admin.task:postCreateTask') ->post('/tasks/create', 'controller.admin.task:postCreateTask')
->bind('admin_tasks_task_create'); ->bind('admin_tasks_task_create');
@@ -124,22 +129,38 @@ class TaskManager implements ControllerProviderInterface
return $app->redirectPath('admin_tasks_list'); return $app->redirectPath('admin_tasks_list');
} }
public function getLiveInformation(Application $app, Request $request)
{
if ($request->getRequestFormat() !== "json") {
$app->abort(406, 'Only JSON format is accepted.');
}
foreach ($app['manipulator.task']->getRepository()->findAll() as $task) {
$tasks[$task->getId()] = $app['task-manager.live-information']->getTask($task);
}
return $app->json([
'manager' => $app['task-manager.live-information']->getManager(),
'tasks' => $tasks
]);
}
public function getScheduler(Application $app, Request $request) public function getScheduler(Application $app, Request $request)
{ {
if ($request->getRequestFormat() !== "json") { if ($request->getRequestFormat() !== "json") {
$app->abort(406, 'Only JSON format is accepted.'); $app->abort(406, 'Only JSON format is accepted.');
} }
$scheduler = array_replace($app['task-manager.live-information']->getManager(), [ return $app->json([
'name' => $app->trans('Task Scheduler'), 'name' => $app->trans('Task Scheduler'),
'configuration' => $app['task-manager.status']->getStatus(),
'urls' => [ 'urls' => [
'start' => $app->path('admin_tasks_scheduler_start'), 'start' => $app->path('admin_tasks_scheduler_start'),
'stop' => $app->path('admin_tasks_scheduler_stop'), 'stop' => $app->path('admin_tasks_scheduler_stop'),
'log' => $app->path('admin_tasks_scheduler_log'), 'log' => $app->path('admin_tasks_scheduler_log'),
] ]
]); ]);
return $app->json($scheduler);
} }
public function getTasks(Application $app, Request $request) public function getTasks(Application $app, Request $request)
@@ -147,11 +168,11 @@ class TaskManager implements ControllerProviderInterface
$tasks = []; $tasks = [];
foreach ($app['repo.tasks']->findAll() as $task) { foreach ($app['repo.tasks']->findAll() as $task) {
$tasks[] = array_replace( $tasks[] = [
$app['task-manager.live-information']->getTask($task), [
'id' => $task->getId(), 'id' => $task->getId(),
'name' => $task->getName() 'name' => $task->getName(),
]); 'configuration' => $task->getStatus()
];
} }
if ($request->getRequestFormat() === "json") { if ($request->getRequestFormat() === "json") {
@@ -165,10 +186,11 @@ class TaskManager implements ControllerProviderInterface
return $app['twig']->render('admin/task-manager/index.html.twig', [ return $app['twig']->render('admin/task-manager/index.html.twig', [
'available_jobs' => $app['task-manager.available-jobs'], 'available_jobs' => $app['task-manager.available-jobs'],
'tasks' => $tasks, 'tasks' => $tasks,
'scheduler' => array_replace( 'scheduler' => [
$app['task-manager.live-information']->getManager(), [ 'id' => '',
'name' => $app->trans('Task Scheduler') 'name' => $app->trans('Task Scheduler'),
]) 'configuration' => $app['task-manager.status']->getStatus(),
]
]); ]);
} }

View File

@@ -51,6 +51,7 @@ $groups = [
, '//assets/blueimp-load-image/load-image.js' , '//assets/blueimp-load-image/load-image.js'
, '//assets/jquery-file-upload/jquery.iframe-transport.js' , '//assets/jquery-file-upload/jquery.iframe-transport.js'
, '//assets/jquery-file-upload/jquery.fileupload.js' , '//assets/jquery-file-upload/jquery.fileupload.js'
, '//assets/autobahnjs/autobahn.js'
], ],
'report' => [ 'report' => [
'//assets/jquery.ui/i18n/jquery-ui-i18n.js' '//assets/jquery.ui/i18n/jquery-ui-i18n.js'

View File

@@ -1,19 +1,25 @@
<div class="page-header">
<h1>{{ 'Task Scheduler' | trans }}
<small style="font-size:16px;">
{% set updateTime %}
<span id="pingTime">{{ "now"|date(constant("DateTime::ISO8601")) }}</span>
{% endset %}
{% trans with {'%updateTime%' : updateTime} %}Last update on %updateTime%{% endtrans %}
</small>
</h1>
</div>
<div id="task-manager-app"> <div id="task-manager-app">
<div class="page-header">
<h1>{{ 'Task Scheduler' | trans }}
<small style="font-size:16px;">
{% set updateTime %}
<span id="pingTime" class="ping-view">{{ "now"|date(constant("DateTime::ISO8601")) }}</span>
{% endset %}
{% trans with {'%updateTime%' : updateTime} %}Last update on %updateTime%{% endtrans %}
</small>
</h1>
</div>
<table class="admintable"> <table class="admintable">
<thead> <thead>
<tr> <tr>
<th></th> <th class="refresh-view" style="width:80px;" data-refresh-url="{{ path('admin_tasks_live_info') }}">
<button class="btn btn-refresh">
<i class="icon icon-refresh"/>
</button>
<i id="spinner" style="font-size:16px" class='icon-spinner icon-spin'>
</th>
<th>ID</th> <th>ID</th>
<th>PID</th> <th>PID</th>
<th>!</th> <th>!</th>
@@ -22,7 +28,7 @@
<th>{{ "name" | trans | upper }}</th> <th>{{ "name" | trans | upper }}</th>
</tr> </tr>
</thead> </thead>
<tbody id="scheduler-view"> <tbody class="scheduler-view">
<tr> <tr>
<td class="menu"> <td class="menu">
<div class="btn-group"> <div class="btn-group">
@@ -49,14 +55,14 @@
</div> </div>
</td> </td>
<td></td> <td></td>
<td>{{ scheduler["process-id"] }}</td>
<td></td> <td></td>
<td>{{ scheduler["actual"] }}</td> <td></td>
<td></td>
<td>{{ scheduler["configuration"] }}</td> <td>{{ scheduler["configuration"] }}</td>
<td>{{ scheduler["name"] }}</td> <td>{{ scheduler["name"] }}</td>
</tr> </tr>
</tbody> </tbody>
<tbody id="tasks-list-view"> <tbody class="tasks-list-view">
{% for task in tasks %} {% for task in tasks %}
<tr> <tr>
<td class="menu"> <td class="menu">
@@ -93,10 +99,10 @@
</ul> </ul>
</div> </div>
</td> </td>
<td>{% if task["id"] != "taskmanager" %}{{ task["id"] }}{% endif %}</td> <td>{{ task["id"] }}</td>
<td>{{ task["process-id"] }}</td> <td></td>
<td></td>
<td></td> <td></td>
<td>{{ task["actual"] }}</td>
<td>{{ task["configuration"] }}</td> <td>{{ task["configuration"] }}</td>
<td>{{ task["name"] }}</td> <td>{{ task["name"] }}</td>
</tr> </tr>
@@ -115,6 +121,8 @@
</form> </form>
</div> </div>
{#<script src="/assets/autobahnjs/autobahn.js"></script>#}
<script type="text/javascript"> <script type="text/javascript">
$(document).ready(function(){ $(document).ready(function(){
$("form[name='form-create-task'] select").bind("change", function() { $("form[name='form-create-task'] select").bind("change", function() {
@@ -126,4 +134,5 @@
{# include js templates #} {# include js templates #}
{% include 'admin/task-manager/templates.html.twig' %} {% include 'admin/task-manager/templates.html.twig' %}
<script type="text/javascript" src="{{ path('minifier', { 'f' : 'assets/requirejs/require.js,/scripts/apps/admin/tasks-manager/main.js' }) }}"></script> <script type="text/javascript" src="{{ path('minifier', { 'f' : 'assets/requirejs/require.js' }) }}"></script>
<script type="text/javascript" src="{{ path('minifier', { 'f' : 'scripts/apps/admin/tasks-manager/main.js' }) }}"></script>

View File

@@ -12,58 +12,90 @@ define([
"underscore", "underscore",
"backbone", "backbone",
"models/scheduler", "models/scheduler",
"common/websockets/connection",
"apps/admin/tasks-manager/views/scheduler", "apps/admin/tasks-manager/views/scheduler",
"apps/admin/tasks-manager/views/tasks", "apps/admin/tasks-manager/views/tasks",
"apps/admin/tasks-manager/views/ping", "apps/admin/tasks-manager/views/ping",
"apps/admin/tasks-manager/views/refresh",
"apps/admin/tasks-manager/collections/tasks" "apps/admin/tasks-manager/collections/tasks"
], function ($, _, Backbone, Scheduler, SchedulerView, TasksView, PingView, TasksCollection) { ], function ($, _, Backbone, Scheduler, WSConnection, SchedulerView, TasksView, PingView, RefreshView, TasksCollection) {
var create = function() { var create = function() {
window.TaskManagerApp = { window.TaskManagerApp = {
$scope: $("#task-manager-app"), $scope: $("#task-manager-app"),
$tasksListView : $("#tasks-list-view", this.$scope), $tasksListView : $(".tasks-list-view", this.$scope),
$schedulerView : $("#scheduler-view", this.$scope), $schedulerView : $(".scheduler-view", this.$scope),
$pingView : $("#pingTime", this.$scope) $pingView : $(".ping-view", this.$scope),
$refreshView : $(".refresh-view", this.$scope),
eventAggregator: _.extend({}, Backbone.Events),
wsuri: "ws://dev.phrasea.net:9090/websockets",
wstopic: "http://phraseanet.com/topics/admin/task-manager"
}; };
TaskManagerApp.tasksCollection = new TasksCollection(); TaskManagerApp.tasksCollection = new TasksCollection();
TaskManagerApp.Scheduler = new Scheduler(); TaskManagerApp.Scheduler = new Scheduler();
TaskManagerApp.pingView = new PingView({el: TaskManagerApp.$pingView});
TaskManagerApp.pingView = new PingView({ TaskManagerApp.refreshView = new RefreshView({
el: TaskManagerApp.$pingView el: TaskManagerApp.$refreshView,
pingView: TaskManagerApp.pingView,
tasksCollection: TaskManagerApp.tasksCollection,
scheduler: TaskManagerApp.Scheduler
}); });
} }
var load = function() { var load = function() {
TaskManagerApp.refreshView.refreshAction();
// fetch objects // fetch objects
$.when.apply($, [ $.when.apply($, [
TaskManagerApp.tasksCollection.fetch(), TaskManagerApp.tasksCollection.fetch(),
TaskManagerApp.Scheduler.fetch() TaskManagerApp.Scheduler.fetch()
]).done( ]).done(
function () { function () {
TaskManagerApp.schedulerView = new SchedulerView({ // Init & render views
model: TaskManagerApp.Scheduler, TaskManagerApp.schedulerView = new SchedulerView({model: TaskManagerApp.Scheduler, el: TaskManagerApp.$schedulerView});
el: TaskManagerApp.$schedulerView TaskManagerApp.tasksView = new TasksView({collection: TaskManagerApp.tasksCollection, el: TaskManagerApp.$tasksListView});
});
TaskManagerApp.tasksView = new TasksView({
collection: TaskManagerApp.tasksCollection,
el: TaskManagerApp.$tasksListView
});
// render views
TaskManagerApp.tasksView.render(); TaskManagerApp.tasksView.render();
TaskManagerApp.schedulerView.render(); TaskManagerApp.schedulerView.render();
// Sets connection to the web socket
var ws = new WSConnection({url:TaskManagerApp.wsuri, topic: TaskManagerApp.wstopic, eventAggregator: TaskManagerApp.eventAggregator});
ws.run();
// On ticks re-render ping view, update tasks & scheduler model
TaskManagerApp.eventAggregator.on("ws:manager-tick", function(response) {
var $this = this;
$this.pingView.render();
$this.Scheduler.set({"actual": "started", "process-id": response.message.manager["process-id"]});
_.each(response.message.jobs, function(data, id) {
var jobModel = $this.tasksCollection.get(id);
if ("undefined" !== jobModel) {
jobModel.set({"actual": data["status"], "process-id": data["process-id"]});
}
});
});
} }
); );
}; };
var initialize = function () { var initialize = function () {
create(); create();
var regexp = /task-manager/;
$(document).ajaxComplete(function(event, request, settings) {
if ("undefined" !== typeof settings && regexp.test(settings.url)) {
TaskManagerApp.refreshView.loadState(false);
}
});
$(document).ajaxStart(function(event, request, settings) {
if ("undefined" !== typeof settings && regexp.test(settings.url)) {
TaskManagerApp.refreshView.loadState(true);
}
});
load(); load();
}; };
return { return {
create: create,
load: load,
initialize: initialize initialize: initialize
}; };
}); });

View File

@@ -0,0 +1,72 @@
/*
* This file is part of Phraseanet
*
* (c) 2005-2014 Alchemy
*
* For the full copyright and license information, please view the LICENSE
* file that was distributed with this source code.
*/
define([
"jquery",
"underscore",
"backbone",
""
], function ($, _, Backbone) {
var RefreshView = Backbone.View.extend({
initialize: function(options) {
if (!"pingView" in options) {
throw "You must set the ping view"
}
this.pingView = options.pingView;
if (!"scheduler" in options) {
throw "You must set the scheduler model"
}
this.scheduler = options.scheduler;
if (!"tasksCollection" in options) {
throw "You must set the tasks collection model"
}
this.tasksCollection = options.tasksCollection;
this.refreshUrl = this.$el.data('refresh-url');
},
events: {
"click .btn-refresh": "refreshAction"
},
refreshAction: function(event) {
var $this = this;
$.ajax({
dataType: "json",
url: $this.refreshUrl,
data: {},
success: function(response) {
$this.pingView.render();
$this.scheduler.set({
"actual": response.manager["actual"],
"process-id": response.manager["process-id"],
"configuration": response.manager["configuration"]
});
_.each(response.tasks, function(data, id) {
var jobModel = $this.tasksCollection.get(id);
if ("undefined" !== jobModel) {
jobModel.set({
"actual": data["actual"],
"process-id": data["process-id"],
"configuration": data["configuration"]
});
}
});
}
});
},
loadState: function(state) {
if (state) {
$("#spinner", this.$el).addClass('icon-spinner icon-spin');
} else {
$("#spinner", this.$el).removeClass('icon-spinner icon-spin');
}
}
});
return RefreshView;
});

View File

@@ -0,0 +1,36 @@
define([
"underscore"
], function (_) {
return function (options) {
if (!"url" in options) {
throw "You must set the websocket 'url'"
}
if (!"topic" in options) {
throw "You must set the websocket 'topic'"
}
if (!"eventAggregator" in options) {
throw "You must set an event aggregator"
}
var eventAggregator = options.eventAggregator;
return {
run: function() {
// autobahn js is defined as a global object there is no way to load
// it as a UMD module
ab.connect(options.url, function (session) {
eventAggregator.trigger("ws:connect", session);
session.subscribe(options.topic, function (topic, msg) {
// double encoded string
var msg = JSON.parse(JSON.parse(msg));
eventAggregator.trigger("ws:"+msg.event, msg, session);
}
);
},
function (code, reason) {
eventAggregator.trigger("ws:session-gone", code,reason);
});
}
}
}
});