diff --git a/Gruntfile.js b/Gruntfile.js index 4cfdf04c50..5327fcbb70 100644 --- a/Gruntfile.js +++ b/Gruntfile.js @@ -27,6 +27,14 @@ module.exports = function(grunt) { } } }, + shell : { + generate_js_fixtures: { + options: { + stdout: true + }, + command : 'bin/developer phraseanet:generate-js-fixtures' + } + }, bower_postinst: { dist: { options: { @@ -338,6 +346,7 @@ module.exports = function(grunt) { }); grunt.loadNpmTasks("grunt-contrib"); + grunt.loadNpmTasks('grunt-shell'); grunt.loadNpmTasks("grunt-bower-task"); grunt.loadNpmTasks("grunt-bower-postinst"); grunt.loadNpmTasks('grunt-mocha-phantomjs'); @@ -348,6 +357,7 @@ module.exports = function(grunt) { grunt.registerTask("copy-deps", [ "copy:deps-when" ]); + grunt.registerTask("copy-assets", [ "copy:autobahnjs", "copy:backbone", @@ -384,5 +394,5 @@ module.exports = function(grunt) { "bower_postinst", "copy-assets" ]); - grunt.registerTask('test', ["qunit", "mocha_phantomjs"]); + grunt.registerTask('test', ["shell:generate_js_fixtures", "qunit", "mocha_phantomjs"]); }; diff --git a/bin/developer b/bin/developer index 98a92ff3a8..69a34048d2 100755 --- a/bin/developer +++ b/bin/developer @@ -16,6 +16,7 @@ use Alchemy\Phrasea\Command\Developer\Behat; use Alchemy\Phrasea\Command\Developer\BowerInstall; use Alchemy\Phrasea\Command\Developer\ComposerInstall; use Alchemy\Phrasea\Command\Developer\InstallAll; +use Alchemy\Phrasea\Command\Developer\JsFixtures; use Alchemy\Phrasea\Command\Developer\LessCompiler; use Alchemy\Phrasea\Command\Developer\RegenerateSqliteDb; use Alchemy\Phrasea\Command\Developer\RoutesDumper; @@ -87,6 +88,7 @@ $cli->command(new RoutesDumper()); $cli->command(new Behat()); $cli->command(new LessCompiler()); $cli->command(new Uninstaller()); +$cli->command(new JsFixtures()); $cli->command(new \module_console_systemTemplateGenerator('system:generate-templates')); diff --git a/lib/Alchemy/Phrasea/Application.php b/lib/Alchemy/Phrasea/Application.php index 8de2f9c0e6..396dfb6ea9 100644 --- a/lib/Alchemy/Phrasea/Application.php +++ b/lib/Alchemy/Phrasea/Application.php @@ -303,6 +303,7 @@ class Application extends SilexApplication $this->register(new SessionServiceProvider(), [ 'session.test' => $this->getEnvironment() === static::ENV_TEST ]); + $this['session.storage.handler'] = $this->share(function ($app) { return $this['session.storage.handler.factory']->create($app['conf']); }); diff --git a/lib/Alchemy/Phrasea/Command/Developer/JsFixtures.php b/lib/Alchemy/Phrasea/Command/Developer/JsFixtures.php new file mode 100644 index 0000000000..cc0f796689 --- /dev/null +++ b/lib/Alchemy/Phrasea/Command/Developer/JsFixtures.php @@ -0,0 +1,129 @@ +setDescription('Generate JS fixtures'); + } + + protected function doExecute(InputInterface $input, OutputInterface $output) + { + $dbRefPath = __DIR__ . '/../../../../../tests/db-ref.sqlite'; + if (!file_exists($dbRefPath)) { + throw new RuntimeException('You must generate sqlite db first, run "bin/console phraseanet:regenerate-sqlite" command.'); + } + + copy($dbRefPath, '/tmp/db.sqlite'); + + $user = $this->createUser($this->container); + $sbasId = current($this->container['phraseanet.appbox']->get_databoxes())->get_sbas_id(); + + try { + $this->writeResponse($output, 'GET', '/login/', '/home/login/index.html'); + $this->writeResponse($output, 'GET', '/admin/fields/'.$sbasId , '/admin/fields/index.html', $user); + $this->writeResponse($output, 'GET', '/admin/task-manager/tasks', '/admin/task-manager/index.html', $user); + } catch (RuntimeException $e) { + $user->delete(); + throw $e; + } + + $this->copy($output, [ + ['source' => 'login/common/templates.html.twig', 'target' => 'home/login/templates.html'], + ['source' => 'admin/fields/templates.html.twig', 'target' => 'admin/fields/templates.html'], + ['source' => 'admin/task-manager/templates.html.twig', 'target' => 'admin/task-manager/templates.html'], + ]); + + $user->delete(); + + return 0; + } + + private function copy(OutputInterface $output, $data) + { + foreach ($data as $paths) { + $output->writeln(sprintf("Generating %s", $this->container['root.path'] . '/www/scripts/tests/fixtures/'.$paths['target'])); + $this->container['filesystem']->copy( + $this->container['root.path'] . '/templates/web/'.$paths['source'], + $this->container['root.path'] . '/www/scripts/tests/fixtures/'.$paths['target'] + ); + } + } + + private function removeScriptTags($html) + { + return preg_replace('#(.*?)#is', '', $html); + } + + private function removeHeadTag($html) + { + return preg_replace('#(.*?)#is', '', $html); + } + + private function createUser(Application $app) + { + $user = \User_Adapter::create($app, uniqid('fixturejs'), uniqid('fixturejs'), uniqid('fixturejs') . '@js.js', true); + + $app['acl']->get($user)->set_admin(true); + $app['manipulator.acl']->resetAdminRights($user); + + return $user; + } + + private function loginUser(Application $app, \User_Adapter $user) + { + $app['authentication']->openAccount($user); + } + + private function logoutUser(Application $app) + { + $app['authentication']->closeAccount(); + } + + private function writeResponse(OutputInterface $output, $method, $path, $to, \User_Adapter $user = null) + { + $environment = Application::ENV_TEST; + $app = require __DIR__ . '/../../Application/Root.php'; + // force load of non cached template + $app['twig']->enableAutoReload(); + $client = new Client($app); + $fixturePath = 'www/scripts/tests/fixtures'; + $target = sprintf('%s/%s/%s', $app['root.path'],$fixturePath, $to); + $output->writeln(sprintf("Generating %s", $target)); + + if (null !== $user) { + $this->loginUser($app, $user); + } + $client->request($method, $path); + $response = $client->getResponse(); + if (null !== $user) { + $this->logoutUser($app); + } + if (false === $response->isOk()) { + throw new RuntimeException(sprintf('Request %s %s returns %d code error', $method, $path, $response->getStatusCode())); + } + + $this->container['filesystem']->mkdir(str_replace(basename($target), '', $target)); + $this->container['filesystem']->dumpFile($target, $this->removeHeadTag($this->removeScriptTags($response->getContent()))); + } +} diff --git a/lib/Alchemy/Phrasea/Controller/Admin/TaskManager.php b/lib/Alchemy/Phrasea/Controller/Admin/TaskManager.php index 9bd1e089cf..854c6b5b9a 100644 --- a/lib/Alchemy/Phrasea/Controller/Admin/TaskManager.php +++ b/lib/Alchemy/Phrasea/Controller/Admin/TaskManager.php @@ -42,6 +42,10 @@ class TaskManager implements ControllerProviderInterface ->get('/tasks', 'controller.admin.task:getTasks') ->bind('admin_tasks_list'); + $controllers + ->get('/scheduler', 'controller.admin.task:getScheduler') + ->bind('admin_scheduler'); + $controllers ->post('/tasks/create', 'controller.admin.task:postCreateTask') ->bind('admin_tasks_task_create'); @@ -120,11 +124,51 @@ class TaskManager implements ControllerProviderInterface return $app->redirectPath('admin_tasks_list'); } + 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(), [ + 'name' => $app->trans('Task Scheduler'), + '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) { - return $app['twig']->render('admin/task-manager/list.html.twig', [ - 'available_jobs' => $app['task-manager.available-jobs'], - 'tasks' => $app['manipulator.task']->getRepository()->findAll(), + $tasks = []; + + foreach ($app['manipulator.task']->getRepository()->findAll() as $task) { + $tasks[] = array_replace( + $app['task-manager.live-information']->getTask($task), [ + 'id' => $task->getId(), + 'name' => $task->getName() + ]); + } + + if ($request->getRequestFormat() === "json") { + foreach ($tasks as $k => $task) { + $tasks[$k]['urls'] = $this->getTaskResourceUrls($app, $task['id']); + } + + return $app->json($tasks); + } + + 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') + ]) ]); } @@ -239,6 +283,16 @@ class TaskManager implements ControllerProviderInterface public function getTask(Application $app, Request $request, Task $task) { + if ('json' === $request->getContentType()) { + return $app->json(array_replace([ + 'id' => $task->getId(), + 'name' => $task->getName(), + 'urls' => $this->getTaskResourceUrls($app, $task->getId()) + ], + $app['task-manager.live-information']->getTask($task) + )); + } + $editor = $app['task-manager.job-factory'] ->create($task->getJobId()) ->getEditor(); @@ -265,4 +319,15 @@ class TaskManager implements ControllerProviderInterface return (Boolean) @$dom->loadXML($string); } + + private function getTaskResourceUrls(Application $app, $taskId) + { + return [ + 'show' => $app->path('admin_tasks_task_show', ['task' => $taskId]), + 'start' => $app->path('admin_tasks_task_start', ['task' => $taskId]), + 'stop' => $app->path('admin_tasks_task_stop', ['task' => $taskId]), + 'delete' => $app->path('admin_tasks_task_delete', ['task' => $taskId]), + 'log' => $app->path('admin_tasks_task_log', ['task' => $taskId]), + ]; + } } diff --git a/package.json b/package.json index 5d2f403eb4..c43887d65c 100644 --- a/package.json +++ b/package.json @@ -9,6 +9,7 @@ "jake": "latest", "grunt-cli": "latest", "grunt": "~0.4.1", + "grunt-shell": "~0.6", "grunt-contrib": "~0.4.0", "grunt-bower-task": "~0.3.0", "grunt-bower-postinst": "~0.2.0", diff --git a/templates/web/admin/fields/index.html.twig b/templates/web/admin/fields/index.html.twig index e66f49a019..55b3b803c7 100644 --- a/templates/web/admin/fields/index.html.twig +++ b/templates/web/admin/fields/index.html.twig @@ -1,6 +1,3 @@ -{# include js templates #} -{% include 'admin/fields/templates.html.twig' %} -
{# sbas_id is saved in the dom and used to fetch right models and collections #} @@ -20,5 +17,8 @@
+{# include js templates #} +{% include 'admin/fields/templates.html.twig' %} + {# bootstrap admin field backbone application #} diff --git a/templates/web/admin/index.html.twig b/templates/web/admin/index.html.twig index 0dd0b73b6f..c79c969b76 100644 --- a/templates/web/admin/index.html.twig +++ b/templates/web/admin/index.html.twig @@ -107,24 +107,27 @@ } function enableLink(link) { - $(link).bind('click',function(event){ - var dest = link.attr('href'); + var method = link.attr("method"); if(dest && dest.indexOf('#') !== 0) { - loadRightAjax(dest); + loadRightAjax(dest, method || "GET"); return false; } - }); } - function loadRightAjax(url) + function loadRightAjax(url, method) { $('#right-ajax').empty().addClass('loading').parent().show(); - $.get(url, function(data) { - enableFormsCallback(data); + + $.ajax({ + type: method, + url: url, + success: function(data) { + enableFormsCallback(data); + } }); } diff --git a/templates/web/admin/task-manager/index.html.twig b/templates/web/admin/task-manager/index.html.twig new file mode 100644 index 0000000000..a4dfcb6bd8 --- /dev/null +++ b/templates/web/admin/task-manager/index.html.twig @@ -0,0 +1,129 @@ + + +
+ + + + + + + + + + + + + + + + + + + + + + + + + {% for task in tasks %} + + + + + + + + + + {% endfor %} + +
IDPID!{{ "actual status" | trans | upper }}{{ "scheduled status" | trans | upper }}{{ "name" | trans | upper }}
{{ scheduler["process-id"] }}{{ scheduler["actual"] }}{{ scheduler["configuration"] }}{{ scheduler["name"] }}
{% if task["id"] != "taskmanager" %}{{ task["id"] }}{% endif %}{{ task["process-id"] }}{{ task["actual"] }}{{ task["configuration"] }}{{ task["name"] }}
+
+ +
+
+ + + +{# include js templates #} +{% include 'admin/task-manager/templates.html.twig' %} + + diff --git a/templates/web/admin/task-manager/list.html.twig b/templates/web/admin/task-manager/list.html.twig deleted file mode 100644 index 24260f39ec..0000000000 --- a/templates/web/admin/task-manager/list.html.twig +++ /dev/null @@ -1,140 +0,0 @@ - - - - - - - - - - - - - - - - - - - - - - - - - {% for task in tasks %} - - - - - - - - - {% endfor %} - -
ID{{ 'Informations' | trans }}{{ 'admin::tasks: statut de la tache' | trans }}{{ 'admin::tasks: process_id de la tache' | trans }}{{ 'admin::tasks: nom de la tache' | trans }}
{{ 'admin::tasks: planificateur de taches' | trans }}
{{ task.getID() }}{{ task.getStatus() }}{{ task.getName() }} [{{ task.getName() }}]
- -
- -
- diff --git a/templates/web/admin/task-manager/templates.html.twig b/templates/web/admin/task-manager/templates.html.twig new file mode 100644 index 0000000000..e088e2428e --- /dev/null +++ b/templates/web/admin/task-manager/templates.html.twig @@ -0,0 +1,75 @@ + + + diff --git a/templates/web/login/index.html.twig b/templates/web/login/index.html.twig index 4114b624a0..2c368e1773 100644 --- a/templates/web/login/index.html.twig +++ b/templates/web/login/index.html.twig @@ -148,6 +148,7 @@ {% endif %} {% endblock %} diff --git a/tests/Alchemy/Tests/Phrasea/Controller/Admin/TaskManagerTest.php b/tests/Alchemy/Tests/Phrasea/Controller/Admin/TaskManagerTest.php index 10d400768a..fb122b13c7 100644 --- a/tests/Alchemy/Tests/Phrasea/Controller/Admin/TaskManagerTest.php +++ b/tests/Alchemy/Tests/Phrasea/Controller/Admin/TaskManagerTest.php @@ -19,6 +19,20 @@ class TaskManagerTest extends \PhraseanetAuthenticatedWebTestCase $this->assertTrue(self::$DI['client']->getResponse()->isOk()); } + public function testRootListTasksJson() + { + self::$DI['client']->request('GET', '/admin/task-manager/tasks', [], [], ["HTTP_CONTENT_TYPE" => "application/json", "HTTP_ACCEPT" => "application/json"]); + $this->assertEquals(200, self::$DI['client']->getResponse()->getStatusCode()); + $tasks = json_decode(self::$DI['client']->getResponse()->getContent()); + foreach ($tasks as $task) { + $this->assertObjectHasAttribute('id', $task); + $this->assertObjectHasAttribute('name', $task); + $this->assertObjectHasAttribute('configuration', $task); + $this->assertObjectHasAttribute('actual', $task); + $this->assertObjectHasAttribute('urls', $task); + } + } + public function testRootPostCreateTask() { $parameters = [ @@ -155,6 +169,35 @@ class TaskManagerTest extends \PhraseanetAuthenticatedWebTestCase $this->assertEquals(200, self::$DI['client']->getResponse()->getStatusCode()); } + public function testGetTaskJson() + { + self::$DI['client']->request('GET', '/admin/task-manager/task/1', [], [], ["HTTP_CONTENT_TYPE" => "application/json", "HTTP_ACCEPT" => "application/json"]); + $this->assertEquals(200, self::$DI['client']->getResponse()->getStatusCode()); + $json = json_decode(self::$DI['client']->getResponse()->getContent()); + $this->assertObjectHasAttribute('id', $json); + $this->assertObjectHasAttribute('name', $json); + $this->assertObjectHasAttribute('configuration', $json); + $this->assertObjectHasAttribute('actual', $json); + $this->assertObjectHasAttribute('urls', $json); + } + + public function testGetSchedulerJson() + { + self::$DI['client']->request('GET', '/admin/task-manager/scheduler', [], [], ["HTTP_CONTENT_TYPE" => "application/json", "HTTP_ACCEPT" => "application/json"]); + $this->assertEquals(200, self::$DI['client']->getResponse()->getStatusCode()); + $json = json_decode(self::$DI['client']->getResponse()->getContent()); + $this->assertObjectHasAttribute('name', $json); + $this->assertObjectHasAttribute('configuration', $json); + $this->assertObjectHasAttribute('actual', $json); + $this->assertObjectHasAttribute('urls', $json); + } + + public function testGetSchedulerJsonBadRequest() + { + self::$DI['client']->request('GET', '/admin/task-manager/scheduler'); + $this->assertEquals(406, self::$DI['client']->getResponse()->getStatusCode()); + } + public function testGetInvalidTask() { self::$DI['client']->request('GET', '/admin/task-manager/task/50'); diff --git a/www/scripts/apps/admin/tasks-manager/app.js b/www/scripts/apps/admin/tasks-manager/app.js new file mode 100644 index 0000000000..0190c729de --- /dev/null +++ b/www/scripts/apps/admin/tasks-manager/app.js @@ -0,0 +1,69 @@ +/* + * 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", + "models/scheduler", + "apps/admin/tasks-manager/views/scheduler", + "apps/admin/tasks-manager/views/tasks", + "apps/admin/tasks-manager/views/ping", + "apps/admin/tasks-manager/collections/tasks" +], function ($, _, Backbone, Scheduler, SchedulerView, TasksView, PingView, 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) + }; + + TaskManagerApp.tasksCollection = new TasksCollection(); + TaskManagerApp.Scheduler = new Scheduler(); + + TaskManagerApp.pingView = new PingView({ + el: TaskManagerApp.$pingView + }); + } + + var load = function() { + // 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 + }); + + // render views + TaskManagerApp.tasksView.render(); + TaskManagerApp.schedulerView.render(); + } + ); + }; + + var initialize = function () { + create(); + load(); + }; + + return { + create: create, + load: load, + initialize: initialize + }; +}); diff --git a/www/scripts/apps/admin/tasks-manager/collections/tasks.js b/www/scripts/apps/admin/tasks-manager/collections/tasks.js new file mode 100644 index 0000000000..9cac331e81 --- /dev/null +++ b/www/scripts/apps/admin/tasks-manager/collections/tasks.js @@ -0,0 +1,23 @@ +/* + * 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([ + "underscore", + "backbone", + "models/task" +], function (_, Backbone, TaskModel) { + var TaskCollection = Backbone.Collection.extend({ + model: TaskModel, + url: function () { + return "/admin/task-manager/tasks"; + } + }); + + return TaskCollection; +}); diff --git a/www/scripts/apps/admin/tasks-manager/main.js b/www/scripts/apps/admin/tasks-manager/main.js new file mode 100644 index 0000000000..2255dcf0da --- /dev/null +++ b/www/scripts/apps/admin/tasks-manager/main.js @@ -0,0 +1,32 @@ +/* + * 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. + */ + +// configure AMD loading +require.config({ + baseUrl: "/scripts", + paths: { + jquery: "../assets/jquery/jquery", + jqueryui: "../assets/jquery.ui/jquery-ui", + underscore: "../assets/underscore-amd/underscore", + backbone: "../assets/backbone-amd/backbone", + i18n: "../assets/i18next/i18next.amd-1.6.3", + bootstrap: "../assets/bootstrap/js/bootstrap.min" + }, + shim: { + bootstrap: ["jquery"], + jqueryui: { + deps: [ "jquery" ] + } + } +}); + +// launch application +require(["apps/admin/tasks-manager/app"], function (App) { + App.initialize(); +}); diff --git a/www/scripts/apps/admin/tasks-manager/views/ping.js b/www/scripts/apps/admin/tasks-manager/views/ping.js new file mode 100644 index 0000000000..2ad79ef608 --- /dev/null +++ b/www/scripts/apps/admin/tasks-manager/views/ping.js @@ -0,0 +1,26 @@ +/* + * 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 PingView = Backbone.View.extend({ + render: function () { + var date = new Date(); + + this.$el.html(date.toISOString()); + + return this; + } + }); + + return PingView; +}); diff --git a/www/scripts/apps/admin/tasks-manager/views/scheduler.js b/www/scripts/apps/admin/tasks-manager/views/scheduler.js new file mode 100644 index 0000000000..f6222e37df --- /dev/null +++ b/www/scripts/apps/admin/tasks-manager/views/scheduler.js @@ -0,0 +1,62 @@ +/* + * 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 SchedulerView = Backbone.View.extend({ + initialize: function () { + this.template = _.template($('#scheduler_template').html()); + // render only parts of the model + this.model.on('change:configuration', this.renderConfiguration, this); + this.model.on('change:actual', this.renderActual, this); + this.model.on('change:process-id', this.renderPid, this); + this.model.on('change:name', this.renderName, this); + }, + events: { + "click a": "clickAction" + }, + tagName: "tr", + render: function () { + this.$el.empty(); + this.$el.html(this.template({'scheduler':this.model.toJSON()})); + return this; + }, + renderConfiguration: function () { + $(".confScheduler", this.$el).html(this.model.get("configuration")); + return this; + }, + renderActual: function () { + $(".actualScheduler", this.$el).html(this.model.get("actual")); + return this; + }, + renderPid: function () { + $(".pidScheduler", this.$el).html(this.model.get("process-id")); + return this; + }, + renderName: function () { + $(".nameScheduler", this.$el).html(this.model.get("name")); + return this; + }, + clickAction: function(e) { + e.preventDefault(); + var link = $(e.target); + var url = link.attr('href'); + + if(url && url.indexOf('#') !== 0) { + // This is defined in admin/index.html.twig + window.loadRightAjax(url, link.attr("method") || "GET"); + } + } + }); + + return SchedulerView; +}); diff --git a/www/scripts/apps/admin/tasks-manager/views/task.js b/www/scripts/apps/admin/tasks-manager/views/task.js new file mode 100644 index 0000000000..7abe06dfbc --- /dev/null +++ b/www/scripts/apps/admin/tasks-manager/views/task.js @@ -0,0 +1,66 @@ +/* + * 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 TaskView = Backbone.View.extend({ + initialize: function () { + this.template = _.template($('#task_template').html()); + // render only parts of the model + this.model.on('change:id', this.renderId, this); + this.model.on('change:configuration', this.renderConfiguration, this); + this.model.on('change:actual', this.renderActual, this); + this.model.on('change:process-id', this.renderPid, this); + this.model.on('change:name', this.renderName, this); + }, + events: { + "click a": "clickAction" + }, + tagName: "tr", + render: function () { + this.$el.html(this.template({'task':this.model.toJSON()})); + return this; + }, + renderId: function () { + $(".idTask", this.$el).html(this.model.get("id")); + return this; + }, + renderConfiguration: function () { + $(".confTask", this.$el).html(this.model.get("configuration")); + return this; + }, + renderActual: function () { + $(".actualTask", this.$el).html(this.model.get("actual")); + return this; + }, + renderPid: function () { + $(".pidTask", this.$el).html(this.model.get("process-id")); + return this; + }, + renderName: function () { + $(".nameTask", this.$el).html(this.model.get("name")); + return this; + }, + clickAction: function(e) { + e.preventDefault(); + var link = $(e.target); + var url = link.attr('href'); + + if(url && url.indexOf('#') !== 0) { + // This is defined in admin/index.html.twig + window.loadRightAjax(url, link.attr("method") || "GET"); + } + } + }); + + return TaskView; +}); diff --git a/www/scripts/apps/admin/tasks-manager/views/tasks.js b/www/scripts/apps/admin/tasks-manager/views/tasks.js new file mode 100644 index 0000000000..8716671751 --- /dev/null +++ b/www/scripts/apps/admin/tasks-manager/views/tasks.js @@ -0,0 +1,70 @@ +/* + * 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", + "jqueryui", + "underscore", + "backbone", + "i18n", + "apps/admin/tasks-manager/views/task" +], function ($, jqueryui, _, Backbone, i18n, TaskView) { + var TasksView = Backbone.View.extend({ + initialize: function() { + this._taskViews = []; + this._rendered = false; + + this.collection.bind('add', this._addOne, this); + this.collection.bind('remove', this._removeOne, this); + }, + render: function () { + var $this = this; + $this.$el.empty(); + $this.collection.each(function(model) { + $this._appendDom($this._createView(model)); + }); + $this._rendered = true; + return $this; + }, + _addOne: function (task) { + var view = this._createView(task); + + if (this._rendered) { + this._appendDom(view); + } + }, + _createView: function (task) { + var view = new TaskView({ model: task }); + this._taskViews.push(view); + return view; + }, + _deleteView: function (task) { + var view = _(this._taskViews).select(function(taskView) { + return taskView.model === task; + })[0]; + this._taskViews = _(this._taskViews).without(view); + return view; + }, + _removeOne: function (task) { + var view = this._deleteView(task); + + if (this._rendered) { + this._removeDom(view); + } + }, + _appendDom: function(view) { + this.$el.append(view.render().el); + }, + _removeDom: function(view) { + view.$el.remove(); + } + }); + + return TasksView; +}); diff --git a/www/scripts/models/scheduler.js b/www/scripts/models/scheduler.js new file mode 100644 index 0000000000..d14da34f62 --- /dev/null +++ b/www/scripts/models/scheduler.js @@ -0,0 +1,34 @@ +/* + * 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([ + "underscore", + "backbone" +], function (_, Backbone) { + var TaskModel = Backbone.Model.extend({ + sync: function(method, model, options) { + var options = options || {}; + options.Accept = 'application/json'; + return Backbone.sync(method, model, options); + }, + urlRoot: function () { + return "/admin/task-manager/scheduler"; + }, + defaults: { + "name" : null, + "configuration": null, + "actual": null, + "process-id": null + } + }); + + // Return the model for the module + return TaskModel; +}); + diff --git a/www/scripts/models/task.js b/www/scripts/models/task.js new file mode 100644 index 0000000000..8bfc147023 --- /dev/null +++ b/www/scripts/models/task.js @@ -0,0 +1,34 @@ +/* + * 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([ + "underscore", + "backbone" +], function (_, Backbone) { + var TaskModel = Backbone.Model.extend({ + sync: function(method, model, options) { + var options = options || {}; + options.Accept = 'application/json'; + return Backbone.sync(method, model, options); + }, + urlRoot: function () { + return "/admin/task-manager/" + this.get("id"); + }, + defaults: { + "id" : null, + "name" : null, + "configuration": null, + "actual": null, + "process-id": null + } + }); + + // Return the model for the module + return TaskModel; +}); diff --git a/www/scripts/tests/fixtures/admin/fields/dom b/www/scripts/tests/fixtures/admin/fields/dom deleted file mode 100644 index f7f354628f..0000000000 --- a/www/scripts/tests/fixtures/admin/fields/dom +++ /dev/null @@ -1,18 +0,0 @@ -
- {# sbas_id is saved in the dom and used to fetch right models and collections #} - -
-
- {# set loading state, this will be removed once backbone application is fully loaded #} - - {% trans %}Loading database documentary fields ...{% endtrans %} -
-
-
-
-
- -
diff --git a/www/scripts/tests/fixtures/admin/fields/templates b/www/scripts/tests/fixtures/admin/fields/templates deleted file mode 100644 index bb607d9aec..0000000000 --- a/www/scripts/tests/fixtures/admin/fields/templates +++ /dev/null @@ -1,265 +0,0 @@ - - - - - - - - - - - - - - - - - diff --git a/www/scripts/tests/fixtures/home/login/form b/www/scripts/tests/fixtures/home/login/form deleted file mode 100644 index 67622a3a4d..0000000000 --- a/www/scripts/tests/fixtures/home/login/form +++ /dev/null @@ -1,55 +0,0 @@ -
-
-
- - - - - - -
- - - -
-
-
-
-
-
- - - - - - -
- - - -
-
-
-
- - -
-
- -
-
-
-
- -
-
-
- -
diff --git a/www/scripts/tests/fixtures/home/login/templates b/www/scripts/tests/fixtures/home/login/templates deleted file mode 100644 index 313b6189db..0000000000 --- a/www/scripts/tests/fixtures/home/login/templates +++ /dev/null @@ -1,23 +0,0 @@ - diff --git a/www/scripts/tests/index.html b/www/scripts/tests/index.html index e3dda53442..490c19debd 100644 --- a/www/scripts/tests/index.html +++ b/www/scripts/tests/index.html @@ -14,6 +14,7 @@ require([ 'tests/baseTest', 'specs/admin/fields', + 'specs/admin/taskmanager', 'specs/login/home', 'specs/models', 'specs/validator' diff --git a/www/scripts/tests/main.js b/www/scripts/tests/main.js index 3a3e8c341d..2d80deae14 100644 --- a/www/scripts/tests/main.js +++ b/www/scripts/tests/main.js @@ -4,7 +4,6 @@ require.config({ specs: "tests/specs", chai: "../assets/chai/chai", fixtures: "../assets/js-fixtures/fixtures", - app: "apps/admin/fields/app", jquery: "../assets/jquery/jquery", jqueryui: "../assets/jquery.ui/jquery-ui", underscore: "../assets/underscore-amd/underscore", diff --git a/www/scripts/tests/specs/admin/fields.js b/www/scripts/tests/specs/admin/fields.js index 9023f35902..3a2f272804 100644 --- a/www/scripts/tests/specs/admin/fields.js +++ b/www/scripts/tests/specs/admin/fields.js @@ -2,7 +2,7 @@ define([ 'chai', 'fixtures', 'jquery', - 'app', + 'apps/admin/fields/app', 'models/field', 'apps/admin/fields/collections/fields', 'apps/admin/fields/collections/dcFields', @@ -20,13 +20,16 @@ define([ var assert = chai.assert; var should = chai.should(); - // load fixtures in dom + // Note: fixture are loaded into scripts/tests/fixtures directory using + // bin/developer phraseanet:regenerate-js-fixtures fixtures.path = 'fixtures'; - $("body").append(fixtures.read('admin/fields/dom', 'admin/fields/templates')); + $("body").append(fixtures.read('admin/fields/index.html', 'admin/fields/templates.html')); var sbasId = 1; App.create(); + AdminFieldApp.languages = {"de":"Deutsch","en":"English","fr":"Francais","nl":"Dutch"}; + describe("Admin field", function () { describe("Initialization", function () { it("should create a global variable", function () { diff --git a/www/scripts/tests/specs/admin/taskmanager.js b/www/scripts/tests/specs/admin/taskmanager.js new file mode 100644 index 0000000000..db9f54f060 --- /dev/null +++ b/www/scripts/tests/specs/admin/taskmanager.js @@ -0,0 +1,103 @@ +define([ + 'chai', + 'fixtures', + 'jquery', + 'apps/admin/tasks-manager/app', + 'models/task', + 'apps/admin/tasks-manager/collections/tasks', + 'apps/admin/tasks-manager/views/ping', + 'apps/admin/tasks-manager/views/task', + 'apps/admin/tasks-manager/views/tasks' +], function (chai, fixtures, $, App, TaskModel, TaskCollection, PingView, TaskView, TasksView) { + var expect = chai.expect; + var assert = chai.assert; + var should = chai.should(); + + // Note: fixture are loaded into scripts/tests/fixtures directory using + // bin/developer phraseanet:regenerate-js-fixtures + fixtures.path = 'fixtures'; + $("body").append(fixtures.read('admin/task-manager/templates.html', 'admin/task-manager/index.html')); + + App.create(); + + describe("Admin task manager", function () { + describe("Initialization", function () { + it("should create a global variable", function () { + should.exist(TaskManagerApp); + }); + }); + + describe("Views", function () { + describe("TaskView", function () { + beforeEach(function () { + this.view = new TaskView({ + model: new TaskModel({ + "name":"Task", "configuration":"start", "actual": "stopped", "id":1, "urls" : [] + }) + }); + }); + + it("render() should return the view object", function () { + this.view.render().should.equal(this.view); + this.view.renderId().should.equal(this.view); + this.view.renderConfiguration().should.equal(this.view); + this.view.renderActual().should.equal(this.view); + this.view.renderPid().should.equal(this.view); + this.view.renderName().should.equal(this.view); + }); + + it("should render as a TR element", function () { + this.view.render().el.nodeName.should.equal("TR"); + }); + }); + + describe("Empty Tasks item views", function () { + beforeEach(function () { + this.collection = new TaskCollection([]); + this.view = new TasksView({ + collection: this.collection, + el: AdminFieldApp.$tasksListView + }); + }); + + it("should include list items for all models in collection", function () { + this.view.render(); + this.view.$el.find("tr").length.should.equal(0); + }); + }); + + describe("Tasks Item Views", function () { + beforeEach(function () { + this.collection = new TaskCollection([ + {"name" : "task", "actual":"stopped", "configuration": "start", "urls" : []}, + {"name" : "task2", "actual":"stopped", "configuration": "start", "urls" : []} + ]); + + this.view = new TasksView({ + collection: this.collection, + el: AdminFieldApp.$tasksListView + }); + }); + + it("render() should return the view object", function () { + this.view.render().should.equal(this.view); + }); + + it("should include list items for all models in collection", function () { + this.view.render(); + this.view.$el.find("tr").length.should.equal(2); + }); + }); + + describe("Ping View", function () { + beforeEach(function () { + this.view = new PingView(); + }); + + it("render() should return the view object", function () { + this.view.render().should.equal(this.view); + }); + }); + }); + }); +}); diff --git a/www/scripts/tests/specs/login/home.js b/www/scripts/tests/specs/login/home.js index e59315573c..fcf993cb05 100644 --- a/www/scripts/tests/specs/login/home.js +++ b/www/scripts/tests/specs/login/home.js @@ -9,9 +9,10 @@ define([ var expect = chai.expect; var assert = chai.assert; var should = chai.should(); - + // Note: fixture are loaded into scripts/tests/fixtures directory using + // bin/developer phraseanet:regenerate-js-fixtures fixtures.path = 'fixtures'; - $("body").append(fixtures.read('home/login/form', 'home/login/templates')); + $("body").append(fixtures.read('home/login/index.html', 'home/login/templates.html')); describe("Login Home", function () { describe("Form View", function () {