diff --git a/Gruntfile.js b/Gruntfile.js
index f79d06706f..ce4fa59190 100644
--- a/Gruntfile.js
+++ b/Gruntfile.js
@@ -52,7 +52,7 @@ module.exports = function(grunt) {
"autobahnjs": {
"expand": true,
"src": [
- "<%= path.bower %>/autobahnjs/build/autobahn.min.js",
+ "<%= path.bower %>/autobahnjs/build/autobahn.js",
"<%= path.bower %>/autobahnjs/LICENSE"
],
"dest": "<%= path.asset %>/autobahnjs/",
diff --git a/lib/Alchemy/Phrasea/Controller/Admin/TaskManager.php b/lib/Alchemy/Phrasea/Controller/Admin/TaskManager.php
index 74fb6923aa..0de5cc7b83 100644
--- a/lib/Alchemy/Phrasea/Controller/Admin/TaskManager.php
+++ b/lib/Alchemy/Phrasea/Controller/Admin/TaskManager.php
@@ -14,6 +14,7 @@ namespace Alchemy\Phrasea\Controller\Admin;
use Alchemy\Phrasea\Exception\InvalidArgumentException;
use Alchemy\Phrasea\Form\TaskForm;
use Alchemy\Phrasea\Model\Entities\Task;
+use Alchemy\Phrasea\TaskManager\TaskManagerStatus;
use Silex\Application;
use Silex\ControllerProviderInterface;
use Symfony\Component\HttpFoundation\Request;
@@ -46,6 +47,10 @@ class TaskManager implements ControllerProviderInterface
->get('/scheduler', 'controller.admin.task:getScheduler')
->bind('admin_scheduler');
+ $controllers
+ ->get('/live', 'controller.admin.task:getLiveInformation')
+ ->bind('admin_tasks_live_info');
+
$controllers
->post('/tasks/create', 'controller.admin.task:postCreateTask')
->bind('admin_tasks_task_create');
@@ -124,22 +129,38 @@ class TaskManager implements ControllerProviderInterface
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)
{
if ($request->getRequestFormat() !== "json") {
$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'),
+ 'configuration' => $app['task-manager.status']->getStatus(),
'urls' => [
'start' => $app->path('admin_tasks_scheduler_start'),
'stop' => $app->path('admin_tasks_scheduler_stop'),
'log' => $app->path('admin_tasks_scheduler_log'),
]
]);
-
- return $app->json($scheduler);
}
public function getTasks(Application $app, Request $request)
@@ -147,11 +168,11 @@ class TaskManager implements ControllerProviderInterface
$tasks = [];
foreach ($app['repo.tasks']->findAll() as $task) {
- $tasks[] = array_replace(
- $app['task-manager.live-information']->getTask($task), [
+ $tasks[] = [
'id' => $task->getId(),
- 'name' => $task->getName()
- ]);
+ 'name' => $task->getName(),
+ 'configuration' => $task->getStatus()
+ ];
}
if ($request->getRequestFormat() === "json") {
@@ -165,10 +186,11 @@ class TaskManager implements ControllerProviderInterface
return $app['twig']->render('admin/task-manager/index.html.twig', [
'available_jobs' => $app['task-manager.available-jobs'],
'tasks' => $tasks,
- 'scheduler' => array_replace(
- $app['task-manager.live-information']->getManager(), [
- 'name' => $app->trans('Task Scheduler')
- ])
+ 'scheduler' => [
+ 'id' => '',
+ 'name' => $app->trans('Task Scheduler'),
+ 'configuration' => $app['task-manager.status']->getStatus(),
+ ]
]);
}
diff --git a/lib/conf.d/minifyGroupsConfig.php b/lib/conf.d/minifyGroupsConfig.php
index 882630cd52..a2ec8486e9 100644
--- a/lib/conf.d/minifyGroupsConfig.php
+++ b/lib/conf.d/minifyGroupsConfig.php
@@ -51,6 +51,7 @@ $groups = [
, '//assets/blueimp-load-image/load-image.js'
, '//assets/jquery-file-upload/jquery.iframe-transport.js'
, '//assets/jquery-file-upload/jquery.fileupload.js'
+ , '//assets/autobahnjs/autobahn.js'
],
'report' => [
'//assets/jquery.ui/i18n/jquery-ui-i18n.js'
diff --git a/templates/web/admin/task-manager/index.html.twig b/templates/web/admin/task-manager/index.html.twig
index a4dfcb6bd8..fb479c949c 100644
--- a/templates/web/admin/task-manager/index.html.twig
+++ b/templates/web/admin/task-manager/index.html.twig
@@ -1,19 +1,25 @@
-
-
+
+
+
- |
+
+
+
+ |
ID |
PID |
! |
@@ -22,7 +28,7 @@
{{ "name" | trans | upper }} |
-
+
|
- {{ scheduler["process-id"] }} |
|
- {{ scheduler["actual"] }} |
+ |
+ |
{{ scheduler["configuration"] }} |
{{ scheduler["name"] }} |
-
+
{% for task in tasks %}
- {% if task["id"] != "taskmanager" %}{{ task["id"] }}{% endif %} |
- {{ task["process-id"] }} |
+ {{ task["id"] }} |
+ |
+ |
|
- {{ task["actual"] }} |
{{ task["configuration"] }} |
{{ task["name"] }} |
@@ -115,6 +121,8 @@
+{##}
+
+
+
diff --git a/www/scripts/apps/admin/tasks-manager/app.js b/www/scripts/apps/admin/tasks-manager/app.js
index 0190c729de..dcde165f95 100644
--- a/www/scripts/apps/admin/tasks-manager/app.js
+++ b/www/scripts/apps/admin/tasks-manager/app.js
@@ -12,58 +12,90 @@ define([
"underscore",
"backbone",
"models/scheduler",
+ "common/websockets/connection",
"apps/admin/tasks-manager/views/scheduler",
"apps/admin/tasks-manager/views/tasks",
"apps/admin/tasks-manager/views/ping",
+ "apps/admin/tasks-manager/views/refresh",
"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() {
window.TaskManagerApp = {
$scope: $("#task-manager-app"),
- $tasksListView : $("#tasks-list-view", this.$scope),
- $schedulerView : $("#scheduler-view", this.$scope),
- $pingView : $("#pingTime", this.$scope)
+ $tasksListView : $(".tasks-list-view", this.$scope),
+ $schedulerView : $(".scheduler-view", 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.Scheduler = new Scheduler();
-
- TaskManagerApp.pingView = new PingView({
- el: TaskManagerApp.$pingView
+ TaskManagerApp.pingView = new PingView({el: TaskManagerApp.$pingView});
+ TaskManagerApp.refreshView = new RefreshView({
+ el: TaskManagerApp.$refreshView,
+ pingView: TaskManagerApp.pingView,
+ tasksCollection: TaskManagerApp.tasksCollection,
+ scheduler: TaskManagerApp.Scheduler
});
}
var load = function() {
+ TaskManagerApp.refreshView.refreshAction();
// fetch objects
$.when.apply($, [
TaskManagerApp.tasksCollection.fetch(),
TaskManagerApp.Scheduler.fetch()
]).done(
function () {
- TaskManagerApp.schedulerView = new SchedulerView({
- model: TaskManagerApp.Scheduler,
- el: TaskManagerApp.$schedulerView
- });
- TaskManagerApp.tasksView = new TasksView({
- collection: TaskManagerApp.tasksCollection,
- el: TaskManagerApp.$tasksListView
- });
+ // Init & render views
+ TaskManagerApp.schedulerView = new SchedulerView({model: TaskManagerApp.Scheduler, el: TaskManagerApp.$schedulerView});
+ TaskManagerApp.tasksView = new TasksView({collection: TaskManagerApp.tasksCollection, el: TaskManagerApp.$tasksListView});
- // render views
TaskManagerApp.tasksView.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 () {
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();
};
return {
- create: create,
- load: load,
initialize: initialize
};
});
diff --git a/www/scripts/apps/admin/tasks-manager/views/refresh.js b/www/scripts/apps/admin/tasks-manager/views/refresh.js
new file mode 100644
index 0000000000..039548706a
--- /dev/null
+++ b/www/scripts/apps/admin/tasks-manager/views/refresh.js
@@ -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;
+});
diff --git a/www/scripts/common/websockets/connection.js b/www/scripts/common/websockets/connection.js
new file mode 100644
index 0000000000..4a6364f3af
--- /dev/null
+++ b/www/scripts/common/websockets/connection.js
@@ -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);
+ });
+ }
+ }
+ }
+});