├── CHANGELOG.md ├── CONTRIBUTING.md ├── LICENSE ├── README.md ├── acl.php ├── composer.json ├── docs └── img │ └── automation.PNG ├── phpunit.xml ├── providers.php ├── public └── js │ ├── automation_date_range_filter.js │ ├── automation_logs_table.js │ ├── automation_status_filter.js │ └── filter.js ├── resources ├── config │ └── config.php ├── database │ ├── migrations │ │ └── 2015_07_24_082030_create_jobs_table.php │ └── seeds │ │ └── JobsCategorySeeder.php ├── lang │ └── en │ │ └── messages.php └── views │ └── admin │ ├── edit.twig │ ├── index │ ├── edit.twig │ ├── failed.twig │ ├── index.twig │ ├── logs.twig │ ├── show.twig │ └── success.twig │ ├── list.twig │ └── partials │ ├── _date_range_filter.twig │ ├── _deleted.twig │ ├── _filter_list.twig │ └── _left_pane.twig ├── src ├── AutomationServiceProvider.php ├── Console │ ├── QueueCommand.php │ └── SyncCommand.php ├── Contracts │ ├── IndexListener.php │ └── IndexPresenter.php ├── Http │ ├── Breadcrumb │ │ └── Breadcrumb.php │ ├── Controllers │ │ └── Admin │ │ │ ├── Api │ │ │ └── V1 │ │ │ │ └── IndexController.php │ │ │ └── IndexController.php │ ├── Datatables │ │ ├── Automation.php │ │ ├── AutomationDetails.php │ │ └── AutomationLogs.php │ ├── Filter │ │ ├── AutomationDateRangeFilter.php │ │ ├── AutomationLogsFilter.php │ │ └── AutomationStatusFilter.php │ ├── Handlers │ │ ├── AutomationLogsBreadcrumbMenu.php │ │ ├── AutomationLogsMenu.php │ │ └── Menu.php │ ├── Presenters │ │ └── IndexPresenter.php │ └── backend.php ├── Jobs │ ├── ManualLaunch.php │ ├── StartAutomation.php │ └── SyncAutomation.php ├── Model │ ├── JobErrors.php │ ├── JobResults.php │ ├── Jobs.php │ ├── JobsCategory.php │ └── JobsQueue.php ├── Processor │ └── IndexProcessor.php └── Repository │ └── Reports.php └── tests ├── AutomationServiceProviderTest.php ├── Console └── SyncCommandTest.php ├── Http ├── Controllers │ └── Admin │ │ └── IndexControllerTest.php ├── Handlers │ ├── AutomationPaneTest.php │ └── MenuTest.php └── Presenters │ └── IndexPresenterTest.php ├── Model ├── JobErrorsTest.php ├── JobResultsTest.php └── JobsTest.php └── Processor └── IndexProcessorTest.php /CHANGELOG.md: -------------------------------------------------------------------------------- 1 | # Changelog 2 | 3 | ### [0.9.2] 4 | 5 | ##### New 6 | 7 | * Laravel 5.4 integration. 8 | * Laravel 5.3 integration. 9 | * New composer.json structure. 10 | * New composer.json structure. 11 | * Session datatbales and ordering. 12 | 13 | ##### Changes 14 | 15 | * Readme.md structure change. 16 | * Change automation synchronization command. 17 | * Added homepage and friendly names to components. 18 | * Unit tests refactoring. 19 | * Unit tests refactoring. 20 | * Refactoring unit tests. 21 | * Unit tests refactoring after laravel 5.4 release. 22 | * Updated ACL file. 23 | * Old extension finder class. 24 | * Remove unused scripts. 25 | * Main menu title. 26 | 27 | ##### Fixes 28 | 29 | * Automation ordering. 30 | * Automation timeout queue process. 31 | 32 | ##### Other 33 | 34 | * Update composer.json. 35 | * Fix for composer handler. 36 | * Update composer.json. 37 | * Changed project name on composer. 38 | * INITIAL ANTARES COMMIT. 39 | * Initial commit. 40 | 41 | 42 | -------------------------------------------------------------------------------- /CONTRIBUTING.md: -------------------------------------------------------------------------------- 1 | # Contribution Guide 2 | 3 | This project adheres to the following standards and practices. 4 | 5 | ## Versioning 6 | 7 | This package is versioned under the [Semantic Versioning](http://semver.org/) guidelines as much as possible. 8 | 9 | Releases will be numbered with the following format: 10 | 11 | `..` 12 | 13 | And constructed with the following guidelines: 14 | 15 | * Breaking backward compatibility bumps the major and resets the minor and patch. 16 | * New additions without breaking backward compatibility bumps the minor and resets the patch. 17 | * Bug fixes and misc changes bumps the patch. 18 | 19 | ## Coding Standards 20 | 21 | This package is compliant with the [PSR-1](https://github.com/php-fig/fig-standards/blob/master/accepted/PSR-1-basic-coding-standard.md), [PSR-2](https://github.com/php-fig/fig-standards/blob/master/accepted/PSR-2-coding-style-guide.md) and [PSR-4](https://github.com/php-fig/fig-standards/blob/master/accepted/PSR-4-autoloader.md). If you notice any compliance oversights, please send a patch via pull request. 22 | 23 | ## Pull Requests 24 | 25 | The pull request process differs for new features and bugs. 26 | 27 | Pull requests for bugs may be sent without creating any proposal issue. If you believe that you know of a solution for a bug that has been filed, please leave a comment detailing your proposed fix or create a pull request with the fix mentioning that issue id. 28 | 29 | ### Proposal \ Feature Requests 30 | 31 | If you have a proposal or a feature request, you may create an issue with `[Proposal]` in the title. 32 | 33 | The proposal should also describe the new feature, as well as implementation ideas. The proposal will then be reviewed and either approved or denied. Once a proposal is approved, a pull request may be created implementing the new feature. 34 | 35 | ### Which Branch? 36 | 37 | **ALL** bug fixes should be made to the branch which they belong to. Bug fixes should never be sent to the `master` branch unless they fix features that exist only in the upcoming release. 38 | 39 | If a bug is found on a `patch` version `0.9.2` and it exists on the `minor` version `0.9.0`, the bug fix should be sent to the `0.9.0` branch which will be afterwards merged into the `0.9.2` branch. 40 | 41 | > **Note:** Pull requests which do not follow these guidelines will be closed without any further notice. 42 | -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | The BSD 3-Clause License 2 | Copyright (c) 2017, Antares 3 | All rights reserved. 4 | 5 | Redistribution and use in source and binary forms, with or without modification, are permitted provided that the following conditions are met: 6 | 7 | Redistributions of source code must retain the above copyright notice, this list of conditions and the following disclaimer. 8 | 9 | Redistributions in binary form must reproduce the above copyright notice, this list of conditions and the following disclaimer in the documentation and/or other materials provided with the distribution. 10 | 11 | Neither the name of Antares and its libraries nor the names of its contributors may be used to endorse or promote products derived from this software without specific prior written permission. 12 | 13 | THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. 14 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # Antares Automation Module 2 | 3 | [![Laravel 5.5](https://img.shields.io/badge/Laravel-5.5-orange.svg)](http://laravel.com) 4 | [![Coverage Status](https://coveralls.io/repos/github/antaresproject/project/badge.svg?branch=master)](https://coveralls.io/github/antaresproject/project?branch=master) 5 | [![Build Status](https://travis-ci.org/antaresproject/project.svg?branch=master)](https://travis-ci.org/antaresproject/project) 6 | [![Code Climate](https://codeclimate.com/github/antaresproject/project/badges/gpa.svg)](https://codeclimate.com/github/antaresproject/project) 7 | [![GitHub issues](https://img.shields.io/github/issues/antaresproject/project.svg)](https://github.com/antaresproject/project/issues) 8 | [![GitHub forks](https://img.shields.io/github/forks/antaresproject/project.svg)](https://github.com/antaresproject/project/network) 9 | [![GitHub stars](https://img.shields.io/github/stars/antaresproject/project.svg)](https://github.com/antaresproject/project/stargazers) 10 | [![GitHub license](https://img.shields.io/badge/license-New%20BSD-blue.svg)](https://raw.githubusercontent.com/antaresproject/project/master/LICENSE) 11 | 12 | 13 | Automation is a module used to executte cyclic operations based on [laravel task scheduler](https://laravel.com/docs/5.4/scheduling). It serves as a replacement for setting up your own cron jobs to make it easier to manage. It provides an intuitive control interface on the admin level. Automation can run tasks every few minutes, hourly, daily or weekly - depending how you set it up. Scheduled data is stored in the system database. 14 | 15 | ![automation](docs/img/automation.PNG) 16 | 17 | ## Documentation 18 | 19 | Antares Automation Module documentation can be found at [antaresproject.io/docs/core_modules/automation](http://antaresproject.io/docs/site/core_modules/automation/). 20 | 21 | Full Antares documentation can be found at [antaresproject.io/docs](http://antaresproject.io/docs). 22 | 23 | 24 | ## Changelog 25 | 26 | Antares Automation Module changelog can be found in release notes [antaresproject.io/docs/site/getting_started/changelog#automation](http://antaresproject.io/docs/site/getting_started/changelog/index.html#automation). 27 | 28 | You can find full Antares changelog in Antares Documentation [antaresproject.io/docs/site/getting_started/changelog](http://antaresproject.io/docs/site/getting_started/changelog/index.html). 29 | 30 | ## Issues 31 | 32 | The issue list of this repo is **exclusively** for bug reports and feature requests. 33 | 34 | Please follow [Issue Reporting Guide](http://antaresproject.io/docs/site/getting_started/issues_reporting_guide/index.html) before opening an issue. Issues not following the guide will be closed without further investigation. 35 | 36 | ## Contribution 37 | 38 | Please follow [Contribution Guide](http://antaresproject.io/docs/site/getting_started/contributing/index.html) before making a pull request. 39 | 40 | ## Community 41 | 42 | * Twitter: @antaresproject 43 | * Forum: (coming soon) 44 | * Blog: (coming soon) 45 | * Email: contact (at) antaresproject.io 46 | 47 | 48 | ## License 49 | 50 | This software is released under the BSD 3-Clause License. 51 | 52 | © 2017 INBS.Software. 53 | -------------------------------------------------------------------------------- /acl.php: -------------------------------------------------------------------------------- 1 | add(Role::admin()->name, $actions); 16 | 17 | return $permissions; -------------------------------------------------------------------------------- /composer.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "antaresproject/component-automation", 3 | "description": "Automation is a module used to executte cyclic operations based on laravel task scheduler. It serves as a replacement for setting up your own cron jobs to make it easier to manage. It provides an intuitive control interface on the admin level. Automation can run tasks every few minutes, hourly, daily or weekly - depending how you set it up. Scheduled data is stored in the system database.", 4 | "type": "antaresproject-component", 5 | "homepage": "https://github.com/antaresproject/automation", 6 | "version": "0.9.0", 7 | "authors": [ 8 | { 9 | "name": "Łukasz Cirut", 10 | "email": "contact@antaresproject.io" 11 | } 12 | ], 13 | "require": { 14 | "antaresproject/component-installer-plugin": "*", 15 | "php": ">=7.0.1" 16 | }, 17 | "autoload": { 18 | "psr-4": { 19 | "Antares\\Automation\\": "src/" 20 | } 21 | }, 22 | "extra": { 23 | "friendly-name": "Automation" 24 | } 25 | } 26 | -------------------------------------------------------------------------------- /docs/img/automation.PNG: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/antaresproject/automation/f60a8a12ca7f0f78687f17457cbdcbc2bdba61fa/docs/img/automation.PNG -------------------------------------------------------------------------------- /phpunit.xml: -------------------------------------------------------------------------------- 1 | 2 | 13 | 14 | 15 | ./tests/ 16 | 17 | 18 | 19 | 20 | ./src/ 21 | 22 | 23 | 24 | -------------------------------------------------------------------------------- /providers.php: -------------------------------------------------------------------------------- 1 | 0 && end.length > 0) { 26 | dateselector.daterangepicker("setRange", {start: moment(start, "YYYY-MM-DD").toDate(), 'end': moment(end, "YYYY-MM-DD").toDate()}); 27 | } 28 | 29 | var dateRangePicker = $('.card-filter').find('input.daterangepicker-filter'); 30 | if (dateRangePicker.length > 0) { 31 | dateRangePicker.daterangepicker({ 32 | change: function (event, data) { 33 | var values = $.parseJSON($(this).val()); 34 | $('.filter-container').find('input.daterangepicker-filter').daterangepicker("setRange", {start: moment(values.start, "YYYY-MM-DD").toDate(), end: moment(values.end, "YYYY-MM-DD").toDate()}); 35 | } 36 | }); 37 | } 38 | } 39 | }; 40 | DateRangeFilter.prototype.validator = { 41 | valid: function (value) { 42 | return value.length > 0; 43 | } 44 | }; 45 | DateRangeFilter.prototype.buttonsBinder = { 46 | bindButton: function (element) { 47 | element.on('click', function (e) { 48 | var tableContainer = $(this).closest('.tbl-c'); 49 | var table = tableContainer.find('[data-table-init]'); 50 | 51 | e.preventDefault(); 52 | 53 | var handler = $(this), input = handler.closest('.ddown__sgl--range').find('input.daterangepicker-filter'), value = input.val(); 54 | 55 | if (!DateRangeFilter.validator.valid(value)) { 56 | return false; 57 | } 58 | 59 | var filterContainer = null, classname = null, column = null; 60 | 61 | if ($(this).closest('.card-filter').length > 0) { 62 | classname = handler.closest('.card-filter').find('.datatables-card-filter').data('classname'); 63 | column = handler.closest('.card-filter').find('.datatables-card-filter').attr('column'); 64 | } else { 65 | filterContainer = $(this).closest('.filter-container'); 66 | classname = filterContainer.find('input.classname').attr('value'); 67 | column = filterContainer.find('.filter-group-column').val(); 68 | } 69 | if (classname === undefined || column === undefined) { 70 | return false; 71 | } 72 | tableContainer.LoadingOverlay('show'); 73 | $.ajax({ 74 | url: $('#filter-save-url').data('url'), 75 | type: 'POST', 76 | data: { 77 | classname: classname, 78 | params: { 79 | column: column, 80 | value: value 81 | } 82 | }, 83 | success: function (response) { 84 | $('.card-filter div[column=' + column + ']').parent().remove(); 85 | $('.card-filter').append(response); 86 | table.dataTable().api().draw(); 87 | var container = $('.card-filter div[column=' + column + ']').parent(); 88 | DateRangeFilter.dateRangeBinder.bindDateRangePicker(container.find('input:text')); 89 | DateRangeFilter.buttonsBinder.bindButton(container.find('a.add-daterange-button')); 90 | tableContainer.LoadingOverlay('hide'); 91 | } 92 | }); 93 | 94 | 95 | return false; 96 | }); 97 | } 98 | 99 | }; 100 | $(function () { 101 | window.DateRangeFilter = new DateRangeFilter(), DateRangeFilter.init(); 102 | }); 103 | -------------------------------------------------------------------------------- /public/js/automation_logs_table.js: -------------------------------------------------------------------------------- 1 | $(document).ready(function () { 2 | $('.tbl-c').on('click', '.show-full-log', function (e) { 3 | e.preventDefault(); 4 | e.stopPropagation(); 5 | var title = $(this).data('title'); 6 | 7 | APP.modal.init({ 8 | element: ".show-logs-modal", 9 | title: title, 10 | buttons: { 11 | close: { 12 | type: "default", 13 | action: function () { 14 | $.modal.close(); 15 | } 16 | } 17 | } 18 | }); 19 | var id = $(this).data('id'); 20 | $('.modal__content').html(''); 21 | $('.modal__content').html($('div[rel=' + id + ']').html()); 22 | return true; 23 | }); 24 | }); -------------------------------------------------------------------------------- /public/js/automation_status_filter.js: -------------------------------------------------------------------------------- 1 | $(document).ready(function () { 2 | $('.main-content').on('click', '.datatable-disable-selected-filter', function (e) { 3 | e.preventDefault(); 4 | var handler = $(this).parent(), column = handler.closest('.card-filter').find('.datatables-card-filter').attr('column'), select = $('.filter-container select[name=' + column + ']'); 5 | if (select.length > 0) { 6 | select.val(null).trigger("change"); 7 | } 8 | }); 9 | ready('.add-select-filter-button', function (element) { 10 | bindButton(element); 11 | }); 12 | ready('.card-filter select', function (element) { 13 | var column = $(element).closest('.card-filter').find('div[column]').attr('column'); 14 | $(element).on("change", function (evt) { 15 | $('.filter-container select[name=' + column + ']').val($(this).val()).trigger("change"); 16 | }); 17 | bindSelect($(element)); 18 | }); 19 | function bindButton(button) { 20 | $(button).on('click', function (e) { 21 | var element = $(this); 22 | e.preventDefault(); 23 | var overlay = $(this).closest('.grid-stack-item-content'), table = null; 24 | if (overlay.length <= 0) { 25 | overlay = $(this).closest('.tbl-c'); 26 | var table = overlay.find('[data-table-init]'); 27 | } 28 | 29 | var handler = $(this), filterContainer = null, classname = null, column = null, select = $(this).closest('.filter-container').find('select'); 30 | if (!select.length) { 31 | select = $(this).closest('.card-filter').find('select'); 32 | } 33 | var values = select.val(); 34 | 35 | if ($(this).closest('.card-filter').length > 0) { 36 | classname = handler.closest('.card-filter').find('.datatables-card-filter').data('classname'); 37 | column = handler.closest('.card-filter').find('.datatables-card-filter').attr('column'); 38 | } else { 39 | filterContainer = element.last().closest('.filter-container'); 40 | classname = filterContainer.find('input.classname').attr('value'); 41 | column = filterContainer.find('.filter-group-column').val(); 42 | } 43 | overlay.LoadingOverlay('show'); 44 | if (!$('#filter-save-url').length) { 45 | return false; 46 | } 47 | $.ajax({ 48 | url: $('#filter-save-url').data('url'), 49 | type: 'POST', 50 | data: { 51 | classname: classname, 52 | params: { 53 | column: column, 54 | value: values 55 | } 56 | }, 57 | success: function (response) { 58 | var logs = element.closest('.card--logs'); 59 | $('.card-filter div[column=' + column + ']').parent().remove(); 60 | $('.card-filter').append(response); 61 | bindSelect($('.card-filter div[column=' + column + ']').parent().find('select')); 62 | if ($('div[column=' + column + '] span').text().length <= 0) { 63 | $('div[column=' + column + '] i').trigger('click'); 64 | } 65 | if (table !== null) { 66 | table.dataTable().api().draw(); 67 | } 68 | overlay.LoadingOverlay('hide'); 69 | if (logs.length) { 70 | logs.LoadingOverlay('show'); 71 | var url = logs.find('.card-ctrls').data('url'); 72 | 73 | $.ajax({ 74 | url: url, 75 | success: function (response) { 76 | var childrens = $(response).closest('.widget-ajax-response').children().length; 77 | if (childrens > 1) { 78 | element.closest('.grid-stack-item-content').find('.card__content').html($(response).closest('.widget-ajax-response').html()); 79 | } else { 80 | var classname = $(response).attr('class').split(' ')[0], container = logs.closest('.' + classname); 81 | if (container.length > 0) { 82 | container.html($(response).html()); 83 | } 84 | } 85 | 86 | logs.LoadingOverlay('hide'); 87 | bindSelect($('.filter-container select[name=' + column + ']')); 88 | } 89 | }) 90 | } 91 | }, 92 | }); 93 | 94 | return false; 95 | }); 96 | } 97 | 98 | function bindSelect(element) { 99 | if (element.length > 0) { 100 | element.select2(); 101 | } 102 | } 103 | }); 104 | ready('.filter-multiple-select', function (element) { 105 | $(element).select2(); 106 | }); -------------------------------------------------------------------------------- /public/js/filter.js: -------------------------------------------------------------------------------- 1 | $(document).ready(function () { 2 | $('.main-content').on('click', '.filter-container .datatables-filter-item', function (e) { 3 | e.preventDefault(); 4 | var handler = $(this).find('a'), 5 | id = handler.attr('rel'), 6 | selected = handler.data('id'), 7 | overlayed = $('.ddown-multi__submenu'), 8 | value = $(this).find('.filter-value').val(), 9 | column = $(this).parents('.filter-container:first').find('.filter-group-column').val(), 10 | route = $('.filter-group-route').val(), 11 | classname = $(this).parents('.filter-container:first').find('input.classname').attr('value'), 12 | table = $(this).parents('.tbl-c').find('[data-table-init]'); 13 | 14 | if ($('.datatables-card-filter[rel=' + id + ']').length > 0) { 15 | return false; 16 | } 17 | 18 | overlayed.LoadingOverlay('show'); 19 | $.ajax({ 20 | url: $('input.datatables-filter-store').val(), 21 | data: { 22 | route: route, 23 | classname: classname, 24 | params: { 25 | column: column, 26 | value: value, 27 | selected: selected 28 | } 29 | }, 30 | type: 'POST', 31 | success: function (response) { 32 | $('.card-filter').append(response); 33 | overlayed.LoadingOverlay('hide'); 34 | table.dataTable().api().draw(); 35 | }, 36 | error: function (error) { 37 | overlayed.LoadingOverlay('hide'); 38 | } 39 | }); 40 | return false; 41 | }); 42 | bindSelect($('.filter-script-name')); 43 | var table = $('.tbl-c').find('[data-table-init]'); 44 | 45 | function lockMainFilter() { 46 | if ($('div[column=script_name] span').text().length > 0) { 47 | $('.filter-container select').attr('disabled', 'disabled'); 48 | } else { 49 | $('.filter-container select').removeAttr('disabled'); 50 | } 51 | } 52 | 53 | 54 | function bindSelect(handler) { 55 | handler.select2({ 56 | 'placeholder': 'Select script name...' 57 | }); 58 | lockMainFilter(); 59 | handler.on("change", function (evt) { 60 | var classname = $('.filter-script-name').parents('.filter-container:first').find('input.classname').attr('value'); 61 | column = $('.filter-script-name').parents('.filter-container:first').find('.filter-group-column').val(); 62 | var values = $(this).val(); 63 | $.ajax({ 64 | url: $('#filter-save-url').data('url'), 65 | type: 'POST', 66 | data: { 67 | classname: classname, 68 | params: { 69 | column: column, 70 | value: values 71 | } 72 | }, 73 | success: function (response) { 74 | $('.select2-container--open').removeClass('select2-container--open'); 75 | $('.card-filter div[column=' + column + ']').parent().remove(); 76 | $('.card-filter').append(response); 77 | lockMainFilter(); 78 | bindSelect($('.card-filter').find('select')); 79 | table.dataTable().api().draw(); 80 | 81 | }, 82 | complete: function () { 83 | $('.card-filter').LoadingOverlay('hide'); 84 | if ($('div[column=' + column + '] span').text().length <= 0) { 85 | $('div[column=' + column + '] i').trigger('click'); 86 | } 87 | $('.filter-container .ddown-multi__submenu').hide(); 88 | $('.filter-container select option').each(function (index, item) { 89 | $(item).removeAttr('selected'); 90 | }); 91 | $('.filter-container select').select2({ 92 | 'placeholder': 'Select script name...' 93 | }); 94 | } 95 | }); 96 | }); 97 | } 98 | }); 99 | -------------------------------------------------------------------------------- /resources/config/config.php: -------------------------------------------------------------------------------- 1 | [ 25 | 'model' => \Antares\Automation\Model\Jobs::class 26 | ], 27 | 'ignored' => [ 28 | 'automation:start', 29 | 'queue:work', 30 | 'queue:start', 31 | 'automation:sync' 32 | ] 33 | ]; 34 | -------------------------------------------------------------------------------- /resources/database/migrations/2015_07_24_082030_create_jobs_table.php: -------------------------------------------------------------------------------- 1 | down(); 37 | DB::transaction(function() { 38 | 39 | Schema::create('tbl_jobs_category', function (Blueprint $table) { 40 | $table->increments('id'); 41 | $table->string('name'); 42 | $table->string('title'); 43 | }); 44 | 45 | Schema::create('jobs', function (Blueprint $table) { 46 | $table->bigIncrements('id'); 47 | $table->string('queue'); 48 | $table->longText('payload'); 49 | $table->tinyInteger('attempts')->unsigned(); 50 | $table->tinyInteger('reserved')->unsigned(); 51 | $table->unsignedInteger('reserved_at')->nullable(); 52 | $table->unsignedInteger('available_at'); 53 | $table->unsignedInteger('created_at'); 54 | $table->index(['queue', 'reserved', 'reserved_at']); 55 | }); 56 | Schema::create('tbl_jobs', function(Blueprint $table) { 57 | $table->increments('id'); 58 | $table->integer('component_id')->unsigned()->nullable()->index('component_id_1'); 59 | $table->integer('category_id')->unsigned()->nullable()->index('catregory_id_1'); 60 | $table->boolean('active')->default(1); 61 | $table->string('name')->unique('name'); 62 | $table->text('value')->nullable(); 63 | $table->timestamps(); 64 | $table->index(['component_id'], 'component_id'); 65 | }); 66 | Schema::create('tbl_job_results', function(Blueprint $table) { 67 | $table->increments('id'); 68 | $table->integer('job_id')->unsigned()->nullable()->index('job_id_1'); 69 | $table->boolean('has_error')->default(0); 70 | $table->double('runtime', 8, 2); 71 | $table->text('return')->nullable(); 72 | $table->timestamp('created_at')->default(DB::raw('CURRENT_TIMESTAMP')); 73 | $table->index(['job_id'], 'job_id'); 74 | }); 75 | Schema::create('tbl_job_errors', function(Blueprint $table) { 76 | $table->increments('id'); 77 | $table->integer('result_job_id')->unsigned()->nullable()->index('result_job_id_id_1'); 78 | $table->integer('code'); 79 | $table->string('name'); 80 | $table->text('return')->nullable(); 81 | $table->timestamp('created_at')->default(DB::raw('CURRENT_TIMESTAMP')); 82 | $table->index(['result_job_id'], 'result_job_id'); 83 | }); 84 | }); 85 | Schema::table('tbl_jobs', function(Blueprint $table) { 86 | $table->foreign('component_id', 'fk_jobs_component_id')->references('id')->on('tbl_components')->onUpdate('NO ACTION')->onDelete('CASCADE'); 87 | $table->foreign('category_id', 'fk_jobs_category_id')->references('id')->on('tbl_jobs_category')->onUpdate('NO ACTION')->onDelete('CASCADE'); 88 | }); 89 | Schema::table('tbl_job_results', function(Blueprint $table) { 90 | $table->foreign('job_id', 'fk_jobs_job_id')->references('id')->on('tbl_jobs')->onUpdate('NO ACTION')->onDelete('CASCADE'); 91 | }); 92 | Schema::table('tbl_job_errors', function(Blueprint $table) { 93 | $table->foreign('result_job_id', 'fk_jobs_result_job_id')->references('id')->on('tbl_job_results')->onUpdate('NO ACTION')->onDelete('CASCADE'); 94 | }); 95 | } 96 | 97 | /** 98 | * Reverse the migrations. 99 | * @return void 100 | */ 101 | public function down() 102 | { 103 | DB::statement('SET FOREIGN_KEY_CHECKS=0;'); 104 | Schema::dropIfExists('jobs'); 105 | Schema::dropIfExists('tbl_job_errors'); 106 | Schema::dropIfExists('tbl_job_results'); 107 | Schema::dropIfExists('tbl_jobs'); 108 | Schema::dropIfExists('tbl_jobs_category'); 109 | DB::statement('SET FOREIGN_KEY_CHECKS=1;'); 110 | } 111 | 112 | } 113 | -------------------------------------------------------------------------------- /resources/database/seeds/JobsCategorySeeder.php: -------------------------------------------------------------------------------- 1 | down(); 36 | 37 | DB::table('tbl_jobs_category')->insert([ 38 | ['name' => 'system', 'title' => 'System'], 39 | ['name' => 'custom', 'title' => 'Custom'], 40 | ]); 41 | } 42 | 43 | public function down() 44 | { 45 | DB::table('tbl_jobs_category')->delete(); 46 | } 47 | 48 | } 49 | -------------------------------------------------------------------------------- /resources/lang/en/messages.php: -------------------------------------------------------------------------------- 1 | 'Disabled', 22 | 'enabled' => 'Enabled', 23 | 'select_status_placeholder' => 'select status...', 24 | 'executed_at' => 'Exectued at: [:start - :end]', 25 | 'select_date_range' => 'select date range...', 26 | 'confirm' => 'Confirm', 27 | 'datatable' => [ 28 | 'select_category' => 'Category:', 29 | 'select_all' => 'All', 30 | 'headers' => [ 31 | 'script_name' => 'Script name', 32 | 'category' => 'Category', 33 | 'status' => 'Status', 34 | 'description' => 'Description', 35 | 'interval' => 'Interval', 36 | 'last_run' => 'Last run', 37 | 'last_run_result' => 'Last run result' 38 | ] 39 | ], 40 | 'show_logs' => 'Show logs', 41 | 'show_full_log' => 'Show full log', 42 | 'cancel' => 'Cancel', 43 | 'full_log_title' => 'Details for :script_name', 44 | 'ask' => 'Are you sure?', 45 | 'running_job_message' => 'Running job :name', 46 | 'edit' => 'Edit', 47 | 'job' => [ 48 | 'failed' => 'Job failed for :name', 49 | 'success' => 'Job success for :name' 50 | ], 51 | 'intervals' => [ 52 | 'everyMinute' => 'Every 1 minute', 53 | 'everyFiveMinutes' => 'Every 5 minutes', 54 | 'everyTenMinutes' => 'Every 10 minutes', 55 | 'everyThirtyMinutes' => 'Every 30 minutes', 56 | 'hourly' => 'Hourly', 57 | 'daily' => 'Daily', 58 | 'twiceDaily' => 'Twice daily', 59 | 'twiceDaily' => 'Twice daily at :value', 60 | 'dailyAt' => 'Daily at :value', 61 | 'weekly' => 'Weekly', 62 | 'monthly' => 'Monthly', 63 | 'quarterly' => 'Quarterly', 64 | 'yearly' => 'Yearly' 65 | ], 66 | 'breadcrumb' => [ 67 | 'automation_log' => 'Automation Log', 68 | 'automation_logs_download' => 'Download', 69 | 'automation_logs_delete' => 'Delete' 70 | ], 71 | 'deleting_logs_modal_title' => 'Delete automation logs', 72 | 'select_range_to_delete_logs' => 'Select date range', 73 | 'delete_logs_cancel' => 'Cancel', 74 | 'delete_logs_delete' => 'Delete', 75 | 'deleting_logs_modal_title' => 'Deleting logs', 76 | 'delete_logs_ask' => 'Are you sure?', 77 | 'automation_delete_success' => 'Automation logs has been deleted.', 78 | 'automation_delete_error' => 'Automation logs has not been deleted.', 79 | 'automation_delete_no_logs' => 'Nothing to delete. There are no automation logs in specified date range.', 80 | 'automation_log' => 'Automation log' 81 | ]; 82 | -------------------------------------------------------------------------------- /resources/views/admin/edit.twig: -------------------------------------------------------------------------------- 1 | {% set ajax=app('request').ajax() %} 2 | {% extends ajax == false ? "antares/foundation::layouts.antares.index" : "antares/foundation::layouts.antares.clear" %} 3 | {% block content %} 4 | {{ form|raw }} 5 | {% endblock %} -------------------------------------------------------------------------------- /resources/views/admin/index/edit.twig: -------------------------------------------------------------------------------- 1 | {% extends "antares/foundation::layouts.antares.index" %} 2 | {% block content %} 3 | {{ form|raw }} 4 | {% endblock %} -------------------------------------------------------------------------------- /resources/views/admin/index/failed.twig: -------------------------------------------------------------------------------- 1 | {% extends "antares/foundation::layouts.antares.index" %} 2 | {% block content %} 3 |
4 |
5 |
6 | Automation job result 7 |
8 |
9 |
10 |
11 |
12 |
13 | 14 |
15 | 16 | {{ trans("antares/automation::messages.job.failed",{'name':params.title}) }} 17 | 18 |
19 | {{ params.description }} 20 |
21 |
22 |
23 |
24 |
25 | {{ trans('Command result') }} 26 |
27 |
28 |
29 |
{{ message }}
30 |
31 |
32 |
33 |
34 | {% endblock %} -------------------------------------------------------------------------------- /resources/views/admin/index/index.twig: -------------------------------------------------------------------------------- 1 | {% extends "antares/foundation::layouts.antares.index" %} 2 | {% block content %} 3 | {{ dataTable.scripts()|raw }} 4 | {{ dataTable.tableDeferred()|raw }} 5 | {% endblock %} -------------------------------------------------------------------------------- /resources/views/admin/index/logs.twig: -------------------------------------------------------------------------------- 1 | {% extends "antares/foundation::layouts.antares.index" %} 2 | {% block content %} 3 | {{ dataTable.scripts()|raw }} 4 | {{ dataTable.tableDeferred()|raw }} 5 | 33 | {% endblock %} -------------------------------------------------------------------------------- /resources/views/admin/index/show.twig: -------------------------------------------------------------------------------- 1 | {% extends "antares/foundation::layouts.antares.index" %} 2 | {% block content %} 3 | {{ dataTable.scripts()|raw }} 4 | {{ dataTable.tableDeferred()|raw }} 5 | {% endblock %} -------------------------------------------------------------------------------- /resources/views/admin/index/success.twig: -------------------------------------------------------------------------------- 1 | {% extends "antares/foundation::layouts.antares.index" %} 2 | {% block content %} 3 |
4 |
5 |
6 | Automation job result 7 |
8 |
9 |
10 |
11 |
12 |
13 | 14 |
15 | {{ trans("antares/automation::messages.job.success",{'name':params.title}) }} 16 |
17 | {{ params.description }} 18 |
19 |
20 |
21 |
22 |
23 | {{ trans('Command result') }} 24 |
25 |
26 |
27 |
{{ message }}
28 |
29 |
30 |
31 |
32 | 33 | 34 | {% endblock %} -------------------------------------------------------------------------------- /resources/views/admin/list.twig: -------------------------------------------------------------------------------- 1 | {% extends "antares/foundation::layouts.antares.index" %} 2 | {% block content %} 3 | {{ datatables.scripts()|raw }} 4 | {{ datatables.tableDeferred()|raw }} 5 | {% endblock %} -------------------------------------------------------------------------------- /resources/views/admin/partials/_date_range_filter.twig: -------------------------------------------------------------------------------- 1 |
  • 2 |
    3 |
    4 | 5 |
    6 |
    7 | 8 | {{ trans('antares/automation::messages.confirm') }} 9 | 10 |
  • 11 | -------------------------------------------------------------------------------- /resources/views/admin/partials/_deleted.twig: -------------------------------------------------------------------------------- 1 |
    2 |
    3 | {{ name|raw }} 4 | {% if name|length>0 %} 5 | 6 | {% endif %} 7 |
    8 |
    9 |
    10 |
      11 |
    • 12 | Filter By 13 |
    • 14 | {{ subview|raw }} 15 |
    16 |
    17 |
    -------------------------------------------------------------------------------- /resources/views/admin/partials/_filter_list.twig: -------------------------------------------------------------------------------- 1 |
  • 2 |
    3 |
    4 | 9 |
    10 |
    11 |
  • -------------------------------------------------------------------------------- /resources/views/admin/partials/_left_pane.twig: -------------------------------------------------------------------------------- 1 | {% set menu=app('antares.widget').make('menu.automation.pane') %} 2 | -------------------------------------------------------------------------------- /src/AutomationServiceProvider.php: -------------------------------------------------------------------------------- 1 | 'Antares\Automation\Http\Presenters\IndexPresenter' 56 | ]; 57 | 58 | /** 59 | * Boot the service provider. 60 | * 61 | * @return void 62 | */ 63 | public function bootExtensionComponents() 64 | { 65 | 66 | $this->listenEvents(); 67 | $this->attachMenu(AutomationLogsBreadcrumbMenu::class); 68 | } 69 | 70 | /** 71 | * Registers service provider 72 | */ 73 | public function register() 74 | { 75 | parent::register(); 76 | $this->commands([SyncCommand::class, QueueCommand::class]); 77 | } 78 | 79 | /** 80 | * component event listeners 81 | */ 82 | protected function listenEvents() 83 | { 84 | 85 | listen('after.activated.antaresproject/component-automation', function() { 86 | $watchDog = $this->app->make('antares.watchdog'); 87 | $watchDog->up('automation:start'); 88 | $job = $this->app->make(SyncAutomation::class)->onQueue('install'); 89 | return $this->dispatch($job); 90 | }); 91 | } 92 | 93 | } 94 | -------------------------------------------------------------------------------- /src/Console/QueueCommand.php: -------------------------------------------------------------------------------- 1 | schedule = $schedule; 107 | $this->reports = $reports; 108 | $this->ignored = config('antares/automation::ignored'); 109 | parent::__construct(); 110 | } 111 | 112 | /** 113 | * Execute the console command. 114 | * 115 | * @return void 116 | */ 117 | public function handle() 118 | { 119 | if ($this->isCli()) { 120 | while (true) { 121 | set_time_limit(0); 122 | ini_set('max_execution_time', 0); 123 | ignore_user_abort(); 124 | $this->runScheduledCommands(); 125 | } 126 | } else { 127 | Artisan::call('automation:run'); 128 | $this->info('Queue command finished.'); 129 | } 130 | } 131 | 132 | /** 133 | * Runs scheduled commands 134 | */ 135 | protected function runScheduledCommands() 136 | { 137 | $events = $this->schedule->dueEvents($this->laravel); 138 | $eventsRan = 0; 139 | if (empty($events)) { 140 | $this->info('Events list is empty.'); 141 | } 142 | $this->comment(sprintf('Start rejecting of %d commands.', count($events))); 143 | foreach ($events as $event) { 144 | 145 | if (!$event->filtersPass($this->laravel)) { 146 | $this->comment(sprintf('Command %s cannot pass by filter. Ignoring...', $event->command)); 147 | continue; 148 | } 149 | $this->runSingleCommand($event); 150 | ++$eventsRan; 151 | } 152 | 153 | if (count($events) === 0 || $eventsRan === 0) { 154 | $this->info('No scheduled commands are ready to run.'); 155 | } 156 | sleep(60); 157 | } 158 | 159 | /** 160 | * Runs single automation command 161 | * 162 | * @param Event $event 163 | */ 164 | protected function runSingleCommand(Event $event) 165 | { 166 | 167 | $commandName = $this->schedule->rejectCommand($event->command); 168 | SupportEvent::fire('antares.automation', 'before.' . $commandName); 169 | if (!$this->isIgnored($commandName)) { 170 | $this->info(sprintf('Command %s is ignored. Continue...', $commandName)); 171 | return false; 172 | } 173 | 174 | $this->line('Running scheduled command: ' . $event->getSummaryForDisplay()); 175 | $before = microtime(true); 176 | $event->run($this->laravel); 177 | $after = microtime(true); 178 | $process = $event->getProcess(); 179 | $runtime = $after - $before; 180 | ($process->isSuccessful()) ? $this->comment(sprintf('Command has been completed successfully in time %d and outputs %s.', $runtime, $process->getOutput())) : $this->comment('Command failed: ' . $process->getErrorOutput()); 181 | 182 | $this->reports->saveReport($commandName, $runtime, $process); 183 | $this->comment(sprintf('Report has been saved.')); 184 | SupportEvent::fire('antares.automation', 'after.' . $commandName); 185 | } 186 | 187 | /** 188 | * Whether command is valid 189 | * 190 | * @param String $command 191 | * @return boolean 192 | */ 193 | protected function isIgnored($command) 194 | { 195 | foreach ($this->ignored as $ignored) { 196 | if (str_contains($command, $ignored)) { 197 | return false; 198 | } 199 | } 200 | return true; 201 | } 202 | 203 | /** 204 | * whether command runs from cli 205 | * 206 | * @return boolean 207 | */ 208 | protected function isCli() 209 | { 210 | return php_sapi_name() == 'cli'; 211 | } 212 | 213 | } 214 | -------------------------------------------------------------------------------- /src/Console/SyncCommand.php: -------------------------------------------------------------------------------- 1 | filesystem = app(Filesystem::class); 114 | $this->finder = app(FilesystemFinder::class); 115 | $this->ignored = config('antares/automation::ignored'); 116 | } 117 | 118 | /** 119 | * fill components container 120 | * 121 | * @return SyncCommand 122 | */ 123 | protected function setComponents() 124 | { 125 | $components = app('Antares\Model\Component')->all(); 126 | foreach ($components as $component) { 127 | $this->components[$component->name] = $component->id; 128 | } 129 | return $this; 130 | } 131 | 132 | /** 133 | * Execute the console command. 134 | * 135 | * @return void 136 | */ 137 | public function handle() 138 | { 139 | $this->setComponents(); 140 | DB::beginTransaction(); 141 | try { 142 | $jobs = $this->scan(); 143 | $items = []; 144 | foreach ($jobs as $componentId => $commands) { 145 | if (empty($commands)) { 146 | continue; 147 | } 148 | $items = array_merge($items, $this->saveJobs($componentId, $commands)); 149 | } 150 | Jobs::query()->whereNotIn('name', $items)->delete(); 151 | } catch (Exception $ex) { 152 | DB::rollback(); 153 | Log::error($ex); 154 | $this->error('Sync error: ' . $ex->getMessage()); 155 | } 156 | DB::commit(); 157 | $this->line('Sync completed.'); 158 | } 159 | 160 | /** 161 | * Saves jobs as commands 162 | * 163 | * @param mixed $componentId 164 | * @param array $commands 165 | */ 166 | protected function saveJobs($componentId, $commands) 167 | { 168 | $return = []; 169 | foreach ($commands as $command) { 170 | $instance = app($command); 171 | $categoryId = $this->resolveCategoryId($instance->getCategory()); 172 | $name = $instance->getName(); 173 | if (in_array($name, $this->ignored)) { 174 | $this->info(sprintf('Command %s is ignored. Continue...', $name)); 175 | continue; 176 | } 177 | array_push($return, $name); 178 | $model = Jobs::query()->firstOrNew([ 179 | 'component_id' => $componentId, 180 | 'category_id' => $categoryId, 181 | 'name' => $name 182 | ]); 183 | $this->value($model, $instance); 184 | $model->save(); 185 | } 186 | return $return; 187 | } 188 | 189 | /** 190 | * Model value setter 191 | * 192 | * @param \Illuminate\Database\Eloquent\Model $model 193 | * @param mixed $command 194 | * @return \Illuminate\Database\Eloquent\Model 195 | */ 196 | protected function value($model, $command) 197 | { 198 | return $model->value = [ 199 | 'component_id' => $model->component_id, 200 | 'category_id' => $model->category_id, 201 | 'standalone' => $command->isStandalone(), 202 | 'description' => $command->getDescription(), 203 | 'title' => $command->getTitle(), 204 | 'cron' => $command->getCron(), 205 | 'launch' => $command->getLaunchTime(), 206 | 'launchTimes' => $command->getAvilableLanuchTimes(), 207 | 'classname' => get_class($command) 208 | ]; 209 | } 210 | 211 | /** 212 | * Resolving category id by category name 213 | * 214 | * @param String $name 215 | * @return mixed 216 | */ 217 | protected function resolveCategoryId($name) 218 | { 219 | $category = JobsCategory::firstOrNew(['name' => $name]); 220 | if ($category->exists) { 221 | return $category->id; 222 | } 223 | $category->title = Str::humanize($category->name); 224 | $category->save(); 225 | return $category->id; 226 | } 227 | 228 | /** 229 | * Scan application to find jobs 230 | * 231 | * @return array 232 | */ 233 | protected function scan() 234 | { 235 | $extensions = app('antares.extension')->getAvailableExtensions(); 236 | 237 | $commands = $this->findJobs(base_path('src/core')); 238 | $match = base_path('src/modules'); 239 | foreach ($extensions as $extension) { 240 | if (!starts_with($extension->getPath(), $match)) { 241 | continue; 242 | } 243 | if (!$extension->isActivated()) { 244 | continue; 245 | } 246 | $commands += $this->findJobs($extension->getPath(), ['testbench', 'tests', 'testing']); 247 | } 248 | 249 | if (empty($commands)) { 250 | $this->line('No jobs found.'); 251 | return []; 252 | } 253 | return $commands; 254 | } 255 | 256 | /** 257 | * find component id by manifest file 258 | * 259 | * @param array $directory 260 | * @return mixed 261 | */ 262 | protected function findComponentId($directory) 263 | { 264 | 265 | $componentId = null; 266 | $composerPath = $directory . DIRECTORY_SEPARATOR . 'composer.json'; 267 | 268 | if (!$this->filesystem->exists($composerPath)) { 269 | $componentId = $this->components['core']; 270 | } else { 271 | $name = json_decode($this->filesystem->get($composerPath))->name; 272 | $componentId = $this->components[str_replace('antaresproject/', '', $name)]; 273 | } 274 | return $componentId; 275 | } 276 | 277 | /** 278 | * finds job instances in directory 279 | * 280 | * @param String $directory 281 | * @param array $exclude 282 | * @return String 283 | */ 284 | protected function findJobs($directory, array $exclude = []) 285 | { 286 | $componentId = $this->findComponentId($directory); 287 | $commands = []; 288 | if (!is_dir($directory)) { 289 | return $commands; 290 | } 291 | $files = iterator_to_array(SymfonyFinder::create()->files()->ignoreDotFiles(true)->in($directory)->exclude('testbench')->exclude('testing')->exclude('tests'), false); 292 | foreach ($files as $file) { 293 | $extension = $file->getExtension(); 294 | if ($extension != 'php') { 295 | continue; 296 | } 297 | $namespace = $this->readNamespace($file->getContents()); 298 | if (is_null($namespace)) { 299 | continue; 300 | } 301 | 302 | try { 303 | $className = $namespace . '\\' . str_replace('.' . $extension, '', $file->getBasename()); 304 | if (!class_exists($className)) { 305 | continue; 306 | } 307 | $reflectionClass = new ReflectionClass($className); 308 | $parent = $reflectionClass->getParentClass(); 309 | 310 | 311 | if (is_bool($parent)) { 312 | continue; 313 | } 314 | if ($parent->getName() !== "Antares\View\Console\Command") { 315 | continue; 316 | } 317 | 318 | $commands[$componentId][] = $className; 319 | } catch (Exception $ex) { 320 | continue; 321 | } 322 | } 323 | return $commands; 324 | } 325 | 326 | /** 327 | * read namespace from file content 328 | * 329 | * @param String $src 330 | * @return String|null 331 | */ 332 | protected function readNamespace($src) 333 | { 334 | $tokens = token_get_all($src); 335 | $count = count($tokens); 336 | $i = 0; 337 | $namespace = ''; 338 | $namespace_ok = false; 339 | while ($i < $count) { 340 | $token = $tokens[$i]; 341 | if (is_array($token) && $token[0] === T_NAMESPACE) { 342 | // Found namespace declaration 343 | while (++$i < $count) { 344 | if ($tokens[$i] === ';') { 345 | $namespace_ok = true; 346 | $namespace = trim($namespace); 347 | break; 348 | } 349 | $namespace .= is_array($tokens[$i]) ? $tokens[$i][1] : $tokens[$i]; 350 | } 351 | break; 352 | } 353 | $i++; 354 | } 355 | if (!$namespace_ok) { 356 | return null; 357 | } else { 358 | return $namespace; 359 | } 360 | } 361 | 362 | } 363 | -------------------------------------------------------------------------------- /src/Contracts/IndexListener.php: -------------------------------------------------------------------------------- 1 | push(trans('antares/automation::messages.automation_log'), handles('antares::automation/index')); 37 | }); 38 | view()->share('breadcrumbs', Breadcrumbs::render('automations')); 39 | } 40 | } 41 | 42 | /** 43 | * on edit automation 44 | * 45 | * @param Model $model 46 | */ 47 | public function onEdit(Model $model) 48 | { 49 | $this->onInit(); 50 | $name = $model->exists ? $model->name : 'add'; 51 | 52 | Breadcrumbs::register('automation-' . $name, function($breadcrumbs) use($model) { 53 | $breadcrumbs->parent('automations'); 54 | $name = $model->exists ? 'Automation edit ' . $model->name : 'Automation create'; 55 | $breadcrumbs->push($name); 56 | }); 57 | view()->share('breadcrumbs', Breadcrumbs::render('automation-' . $name)); 58 | } 59 | 60 | /** 61 | * on list automations 62 | */ 63 | public function onList() 64 | { 65 | Breadcrumbs::register('automation', function($breadcrumbs) { 66 | $breadcrumbs->push('Automation', handles('antares::automation/index')); 67 | }); 68 | view()->share('breadcrumbs', Breadcrumbs::render('automation')); 69 | } 70 | 71 | /** 72 | * on shows automation details 73 | * 74 | * @param Model $model 75 | */ 76 | public function onShow(Model $model) 77 | { 78 | $this->onInit(); 79 | Breadcrumbs::register('automation-details-' . $model->id, function($breadcrumbs) use($model) { 80 | $breadcrumbs->parent('automations'); 81 | $breadcrumbs->push('Automation details ' . $model->name, handles('antares::automation/show/' . $model->id)); 82 | }); 83 | view()->share('breadcrumbs', Breadcrumbs::render('automation-details-' . $model->id)); 84 | } 85 | 86 | /** 87 | * when automation job completed successfully 88 | * 89 | * @param Model $model 90 | */ 91 | public function onRunSucceed(Model $model) 92 | { 93 | $this->onInit(); 94 | Breadcrumbs::register('automation-result-succeed', function($breadcrumbs) use($model) { 95 | $breadcrumbs->parent('automations'); 96 | $breadcrumbs->push('Automation job succeed for script ' . $model->name, handles('antares::automation/run/' . $model->id)); 97 | }); 98 | view()->share('breadcrumbs', Breadcrumbs::render('automation-result-succeed')); 99 | } 100 | 101 | /** 102 | * when automation job result failed 103 | * 104 | * @param Model $model 105 | */ 106 | public function onRunFailed(Model $model) 107 | { 108 | $this->onInit(); 109 | Breadcrumbs::register('automation-result-failed', function($breadcrumbs) use($model) { 110 | $breadcrumbs->parent('automations'); 111 | $breadcrumbs->push('Automation job failed for script ' . $model->name, handles('antares::automation/run/' . $model->id)); 112 | }); 113 | view()->share('breadcrumbs', Breadcrumbs::render('automation-result-failed')); 114 | } 115 | 116 | } 117 | -------------------------------------------------------------------------------- /src/Http/Controllers/Admin/Api/V1/IndexController.php: -------------------------------------------------------------------------------- 1 | processor = $processor; 42 | } 43 | 44 | /** 45 | * route acl access controlling 46 | */ 47 | public function setupMiddleware() 48 | { 49 | $this->middleware("antares.can:antares/automation::automation-list", ['only' => ['index']]); 50 | $this->middleware("antares.can:antares/automation::automation-run", ['only' => ['run']]); 51 | $this->middleware("antares.can:antares/automation::automation-details", ['only' => ['show']]); 52 | $this->middleware("antares.can:antares/automation::automation-edit", ['only' => ['edit', 'update']]); 53 | } 54 | 55 | /** 56 | * index default action 57 | * 58 | * @return \Illuminate\View\View 59 | */ 60 | public function index() 61 | { 62 | return $this->processor->index(); 63 | } 64 | 65 | /** 66 | * shows job details 67 | * 68 | * @param mixed $id 69 | * @return \Illuminate\View\View 70 | */ 71 | public function show($id) 72 | { 73 | return $this->processor->show($id, $this); 74 | } 75 | 76 | /** 77 | * when show job details failed 78 | * 79 | * @return \Illuminate\Http\RedirectResponse 80 | */ 81 | public function showFailed() 82 | { 83 | $message = trans('Automation job has not been found.'); 84 | app('antares.messages')->add('error', $message); 85 | return redirect()->back(); 86 | } 87 | 88 | /** 89 | * job edit form 90 | * 91 | * @param mixed $id 92 | * @return \Illuminate\Http\RedirectResponse 93 | */ 94 | public function edit($id) 95 | { 96 | return $this->processor->edit($id, $this); 97 | } 98 | 99 | /** 100 | * update single job 101 | * 102 | * @param mixed $id 103 | * @return \Illuminate\Http\RedirectResponse 104 | */ 105 | public function update() 106 | { 107 | return $this->processor->update($this); 108 | } 109 | 110 | /** 111 | * Response when storing command failed on validation. 112 | * @param \Illuminate\Support\MessageBag|array $errors 113 | * @return mixed 114 | */ 115 | public function updateValidationFailed($id, $errors) 116 | { 117 | return $this->redirectWithErrors(handles('antares::automation/edit/' . $id), $errors); 118 | } 119 | 120 | /** 121 | * when update job failed 122 | * 123 | * @return \Illuminate\Http\RedirectResponse 124 | */ 125 | public function updateFailed() 126 | { 127 | $message = trans('Automation job has not been updated.'); 128 | app('antares.messages')->add('error', $message); 129 | return redirect()->back(); 130 | } 131 | 132 | /** 133 | * when update job completed successfully 134 | * 135 | * @return \Illuminate\Http\RedirectResponse 136 | */ 137 | public function updateSuccess() 138 | { 139 | $message = trans('Automation job has been updated.'); 140 | app('antares.messages')->add('success', $message); 141 | return redirect()->to(handles('antares::automation/index')); 142 | } 143 | 144 | /** 145 | * runs single job 146 | * 147 | * @param mixed $id 148 | * @return \Illuminate\Http\RedirectResponse 149 | */ 150 | public function run($id) 151 | { 152 | return $this->processor->run($id, $this); 153 | } 154 | 155 | /** 156 | * response when run job failed 157 | * 158 | * @return \Illuminate\Http\RedirectResponse 159 | */ 160 | public function runFailed() 161 | { 162 | $message = trans('Automation job cannot be launched. Error appears while trying to run job handler.'); 163 | app('antares.messages')->add('error', $message); 164 | return redirect()->to(handles('antares::automation/index')); 165 | } 166 | 167 | /** 168 | * response when run job success 169 | * 170 | * @return \Illuminate\Http\RedirectResponse 171 | */ 172 | public function runSuccess() 173 | { 174 | $message = trans('Automation job has been added to queue.'); 175 | app('antares.messages')->add('success', $message); 176 | return redirect()->to(handles('antares::automation/index')); 177 | } 178 | 179 | /** 180 | * shows automation logs 181 | * 182 | * @return \Illuminate\View\View 183 | */ 184 | public function logs(AutomationLogs $datatable) 185 | { 186 | $count = app(JobResults::class)->count(); 187 | if (!$count) { 188 | app(Breadcrumb::class)->onInit(); 189 | } 190 | 191 | return $datatable->render('antares/automation::admin.index.logs'); 192 | } 193 | 194 | /** 195 | * Gets scripts for filter 196 | * 197 | * @return \Illuminate\Http\JsonResponse 198 | */ 199 | public function scripts() 200 | { 201 | return $this->processor->scripts(); 202 | } 203 | 204 | /** 205 | * Download automation logs 206 | * 207 | * @return \Symfony\Component\HttpFoundation\Response 208 | */ 209 | public function download() 210 | { 211 | return $this->processor->download(); 212 | } 213 | 214 | /** 215 | * Deletes automation logs 216 | * 217 | * @return \Symfony\Component\HttpFoundation\Response 218 | */ 219 | public function delete() 220 | { 221 | return $this->processor->delete($this); 222 | } 223 | 224 | /** 225 | * When delete of automation logs failed 226 | * 227 | * @return \Illuminate\Http\RedirectResponse 228 | */ 229 | public function deleteFailed() 230 | { 231 | app('antares.messages')->add('error', trans('antares/automation::messages.automation_delete_error')); 232 | return redirect()->to(handles('antares::automations/logs/index')); 233 | } 234 | 235 | /** 236 | * When date range of automation logs return empty collection 237 | * 238 | * @return \Illuminate\Http\RedirectResponse 239 | */ 240 | public function noLogsToDelete() 241 | { 242 | app('antares.messages')->add('error', trans('antares/automation::messages.automation_delete_no_logs')); 243 | return redirect()->to(handles('antares::automations/logs/index')); 244 | } 245 | 246 | /** 247 | * When automation log has been deleted 248 | * 249 | * @return \Illuminate\Http\RedirectResponse 250 | */ 251 | public function deleteSuccess() 252 | { 253 | app('antares.messages')->add('success', trans('antares/automation::messages.automation_delete_success')); 254 | return redirect()->to(handles('antares::automations/logs/index')); 255 | } 256 | 257 | } 258 | -------------------------------------------------------------------------------- /src/Http/Datatables/Automation.php: -------------------------------------------------------------------------------- 1 | select(['tbl_jobs.*'])->with('jobResults', 'component', 'category'); 53 | 54 | listen('datatables.order.title', function($query, $direction) { 55 | return $query->leftJoin('tbl_components', 'tbl_jobs.component_id', '=', 'tbl_components.id') 56 | ->orderBy('tbl_components.full_name', $direction); 57 | }); 58 | listen('datatables.order.last_run_result', function($query, $direction) { 59 | return $query->leftJoin('tbl_job_results', 'tbl_jobs.id', '=', 'tbl_job_results.job_id') 60 | ->orderBy('tbl_job_results.has_error', $direction); 61 | }); 62 | listen('datatables.order.last_run', function($query, $direction) { 63 | return $query->leftJoin('tbl_job_results', 'tbl_jobs.id', '=', 'tbl_job_results.job_id') 64 | ->orderBy('tbl_job_results.created_at', $direction); 65 | }); 66 | 67 | return $builder; 68 | } 69 | 70 | /** 71 | * Default search builder option 72 | * 73 | * @param \Illuminate\Database\Eloquent\Builder $builder 74 | * @return \Illuminate\Database\Eloquent\Builder 75 | */ 76 | protected function setDefaultBuilderOption($builder) 77 | { 78 | return $builder->whereHas('category', function($query) { 79 | $query->where('name', 'custom'); 80 | }); 81 | } 82 | 83 | /** 84 | * {@inheritdoc} 85 | */ 86 | public function ajax() 87 | { 88 | $acl = app('antares.acl')->make('antares/automation'); 89 | $canRun = $acl->can('automation-run'); 90 | $canView = $acl->can('automation-details'); 91 | $canUpdate = $acl->can('automation-edit'); 92 | return $this->prepare() 93 | ->filter(function($query) { 94 | $request = app('request'); 95 | $keyword = array_get($request->get('search'), 'value'); 96 | if (is_null($keyword) or ! strlen($keyword)) { 97 | return; 98 | } 99 | $keyword = str_contains($keyword, ': ') ? last(explode(': ', $keyword)) : $keyword; 100 | switch ($keyword) { 101 | case 'enabled': 102 | $query->where('tbl_jobs.active', 1); 103 | break; 104 | case 'disabled': 105 | $query->where('tbl_jobs.active', 0); 106 | break; 107 | default: 108 | $columns = request('columns', []); 109 | $categoryId = null; 110 | array_walk($columns, function($item, $index) use(&$categoryId) { 111 | 112 | if (array_get($item, 'data') == 'category_id') { 113 | 114 | $categoryId = array_get($item, 'search.value'); 115 | } 116 | }); 117 | if (!$categoryId) { 118 | $categoryId = $this->findCustomOptionId(); 119 | } 120 | $query->where('category_id', $categoryId); 121 | 122 | 123 | $query 124 | ->leftJoin('tbl_components', 'tbl_jobs.component_id', '=', 'tbl_components.id') 125 | ->leftJoin('tbl_jobs_category', 'tbl_jobs.category_id', '=', 'tbl_jobs_category.id') 126 | ->whereRaw("(tbl_jobs.name like '%$keyword%' or tbl_jobs.value like '%$keyword%' or tbl_components.full_name like '%$keyword%' or tbl_jobs_category.title like '%$keyword%')"); 127 | break; 128 | } 129 | }) 130 | ->filterColumn('category_id', function($query, $keyword) { 131 | if ($keyword !== 'all') { 132 | $query->where('category_id', $keyword); 133 | } 134 | }) 135 | ->editColumn('title', function ($model) { 136 | $name = $model->component->full_name; 137 | return ($name ? $name . ' : ' : '') . $model->value['title']; 138 | }) 139 | ->editColumn('description', function ($model) { 140 | return $model->value['description']; 141 | }) 142 | ->editColumn('category_id', function ($model) { 143 | if (is_null($model->category)) { 144 | return '---'; 145 | } 146 | return $model->category->title; 147 | }) 148 | ->editColumn('last_run_result', function ($model) { 149 | if ($model->jobResults->isEmpty()) { 150 | return '---'; 151 | } 152 | return ($model->jobResults->last()->has_error) ? 153 | '' . trans('Failure') . '' : 154 | '' . trans('Success') . ''; 155 | }) 156 | ->editColumn('last_run', function ($model) { 157 | if ($model->jobResults->isEmpty()) { 158 | return '---'; 159 | } 160 | return format_x_days($model->jobResults->last()->created_at); 161 | }) 162 | ->editColumn('interval', function ($model) { 163 | if (!isset($model->value['launch']) or $model->value['launch'] === false) { 164 | return '---'; 165 | } 166 | if (is_array($model->value['launch'])) { 167 | $return = ''; 168 | foreach ($model->value['launch'] as $when => $times) { 169 | $return .= trans('antares/automation::messages.intervals.' . $when, ['value' => implode(',', array_values($times))]); 170 | } 171 | return $return; 172 | } 173 | return trans('antares/automation::messages.intervals.' . $model->value['launch']); 174 | })->editColumn('active', function ($model) { 175 | return ((int) $model->active) ? 176 | '' . trans('Enabled') . '' : 177 | '' . trans('Disabled') . ''; 178 | })->editColumn('action', $this->getActionsColumn($canView, $canRun, $canUpdate)) 179 | ->make(true); 180 | } 181 | 182 | /** 183 | * {@inheritdoc} 184 | */ 185 | public function html() 186 | { 187 | return $this->setName('Automation List') 188 | ->addColumn(['data' => 'id', 'name' => 'id', 'title' => trans('Id')]) 189 | ->addColumn(['data' => 'title', 'name' => 'title', 'title' => trans('antares/automation::messages.datatable.headers.script_name'), 'className' => 'bolded']) 190 | ->addColumn(['data' => 'category_id', 'name' => 'category_id', 'title' => trans('antares/automation::messages.datatable.headers.category')]) 191 | ->addColumn(['data' => 'active', 'name' => 'active', 'title' => trans('antares/automation::messages.datatable.headers.status')]) 192 | ->addColumn(['data' => 'description', 'name' => 'description', 'title' => trans('antares/automation::messages.datatable.headers.description'), 'orderable' => false]) 193 | ->addColumn(['data' => 'interval', 'name' => 'active', 'title' => trans('antares/automation::messages.datatable.headers.interval')]) 194 | ->addColumn(['data' => 'last_run', 'name' => 'last_run', 'title' => trans('antares/automation::messages.datatable.headers.last_run')]) 195 | ->addColumn(['data' => 'last_run_result', 'name' => 'last_run_result', 'title' => trans('antares/automation::messages.datatable.headers.last_run_result')]) 196 | ->addAction(['name' => 'edit', 'title' => '', 'class' => 'mass-actions dt-actions', 'orderable' => false, 'searchable' => false]) 197 | ->setDeferedData() 198 | ->addGroupSelect($this->categories(), 2, 'all', ['data-prefix' => trans('antares/automation::messages.datatable.select_category')]); 199 | } 200 | 201 | /** 202 | * Creates options for automation table categories 203 | * 204 | * @return Collection 205 | */ 206 | protected function categories(): \Illuminate\Support\Collection 207 | { 208 | 209 | $options = JobsCategory::all(['id', 'title'])->pluck('title', 'id'); 210 | return $options->prepend(trans('antares/automation::messages.datatable.select_all'), 'all'); 211 | } 212 | 213 | /** 214 | * Finds custom option id 215 | * 216 | * @return mixes 217 | */ 218 | protected function findCustomOptionId() 219 | { 220 | return JobsCategory::where('name', 'custom')->first()->id; 221 | } 222 | 223 | /** 224 | * Get actions column for table builder. 225 | * @return callable 226 | */ 227 | protected function getActionsColumn($canView, $canRun, $canUpdate) 228 | { 229 | return function ($row) use($canView, $canRun, $canUpdate) { 230 | $btns = []; 231 | $html = app('html'); 232 | if ($canView) { 233 | $btns[] = $html->create('li', $html->link(handles("antares::automation/show/" . $row->id), trans('antares/automation::messages.show_logs'), ['data-icon' => 'desktop-windows'])); 234 | } 235 | if ($canRun) { 236 | $btns[] = $html->create('li', $html->link(handles("antares::automation/run/" . $row->id, ['csrf' => true]), trans('Run'), ['class' => "triggerable confirm", 'data-icon' => 'play-circle-outline', 'data-title' => trans('antares/automation::messages.ask'), 'data-description' => trans('antares/automation::messages.running_job_message', ['name' => $row->value['title']])])); 237 | } 238 | if ($canUpdate) { 239 | $btns[] = $html->create('li', $html->link(handles("antares::automation/edit/" . $row->id), trans('antares/automation::messages.edit'), ['data-icon' => 'edit'])); 240 | } 241 | if (empty($btns)) { 242 | return ''; 243 | } 244 | $section = $html->create('div', $html->create('section', $html->create('ul', $html->raw(implode('', $btns)))), ['class' => 'mass-actions-menu'])->get(); 245 | return '' . $html->raw($section)->get(); 246 | }; 247 | } 248 | 249 | } 250 | -------------------------------------------------------------------------------- /src/Http/Datatables/AutomationDetails.php: -------------------------------------------------------------------------------- 1 | get('order')) { 45 | $query->orderBy('created_at', 'desc'); 46 | } 47 | return $query; 48 | } 49 | 50 | /** 51 | * {@inheritdoc} 52 | */ 53 | public function ajax() 54 | { 55 | return $this->prepare() 56 | ->editColumn('has_error', function ($model = null) { 57 | return ($model->has_error) ? 58 | '' . trans('Failure') . '' : 59 | '' . trans('Success') . ''; 60 | }) 61 | ->editColumn('return', function ($model) { 62 | return '
    ' . $model->return . '
    '; 63 | })->editColumn('runtime', function ($model) { 64 | return $model->runtime . ' s'; 65 | }) 66 | ->editColumn('created_at', function ($model) { 67 | return format_x_days($model->created_at); 68 | }) 69 | ->make(true); 70 | } 71 | 72 | /** 73 | * {@inheritdoc} 74 | */ 75 | public function html() 76 | { 77 | return $this->setName('Automation Details List') 78 | ->addColumn(['data' => 'id', 'name' => 'id', 'title' => trans('Id')]) 79 | ->addColumn(['data' => 'has_error', 'name' => 'has_error', 'title' => trans('Status')]) 80 | ->addColumn(['data' => 'return', 'name' => 'return', 'title' => trans('Result')]) 81 | ->addColumn(['data' => 'runtime', 'name' => 'runtime', 'title' => trans('Runtime')]) 82 | ->addColumn(['data' => 'created_at', 'name' => 'created_at', 'title' => trans('Executed at')]) 83 | ->setDeferedData(); 84 | } 85 | 86 | /** 87 | * Get actions column for table builder. 88 | * @return callable 89 | */ 90 | protected function getActionsColumn($canView, $canRun, $canUpdate) 91 | { 92 | return function ($row) use($canView, $canRun, $canUpdate) { 93 | $btns = []; 94 | $html = app('html'); 95 | if ($canView) { 96 | $btns[] = $html->create('li', $html->link(handles("antares::automation/show/" . $row->id), trans('Show details'), ['data-icon' => 'desktop-windows'])); 97 | } 98 | if ($canRun) { 99 | $btns[] = $html->create('li', $html->link(handles("antares::automation/run/" . $row->id, ['csrf' => true]), trans('Run'), ['class' => "triggerable confirm", 'data-icon' => 'play-circle-outline', 'data-title' => trans("Are you sure?"), 'data-description' => trans('Running job') . ' #' . $row->id])); 100 | } 101 | if ($canUpdate) { 102 | $btns[] = $html->create('li', $html->link(handles("antares::automation/edit/" . $row->id), trans('Edit'), ['data-icon' => 'edit'])); 103 | } 104 | if (empty($btns)) { 105 | return ''; 106 | } 107 | $section = $html->create('div', $html->create('section', $html->create('ul', $html->raw(implode('', $btns)))), ['class' => 'mass-actions-menu'])->get(); 108 | return '' . $html->raw($section)->get(); 109 | }; 110 | } 111 | 112 | } 113 | -------------------------------------------------------------------------------- /src/Http/Datatables/AutomationLogs.php: -------------------------------------------------------------------------------- 1 | orderBy('has_error', $direction); 51 | }); 52 | return app(JobResults::class)->select(['tbl_job_results.id', 'tbl_job_results.has_error', 'tbl_job_results.job_id', 'tbl_job_results.return', 'tbl_job_results.runtime', 'tbl_job_results.created_at'])->with('job'); 53 | } 54 | 55 | /** 56 | * {@inheritdoc} 57 | */ 58 | public function ajax() 59 | { 60 | 61 | return $this->prepare() 62 | ->filter(function ($query) { 63 | $request = app('request'); 64 | if ($request->ajax() && $request->has('search.value')) { 65 | $key = $request->get('search')['value']; 66 | $query->leftJoin('tbl_jobs as tj', 'tbl_job_results.job_id', '=', 'tj.id') 67 | ->where('tj.name', 'like', "%{$key}%") 68 | ->orWhere('tj.value', 'like', "%{$key}%") 69 | ->orWhere('tbl_job_results.return', 'like', "%{$key}%"); 70 | } 71 | }) 72 | ->editColumn('job_id', $this->getJob_idValue()) 73 | ->editColumn('has_error', $this->getHas_errorValue()) 74 | ->editColumn('return', function ($model) { 75 | $string = $model->return; 76 | $class = (strlen($string) > 70) ? 'class="dots"' : ''; 77 | return '' . $model->return . ''; 78 | })->editColumn('runtime', $this->getRuntimeValue()) 79 | ->editColumn('created_at', function ($model) { 80 | return format_x_days($model->created_at); 81 | }) 82 | ->editColumn('action', $this->getActionsColumn()) 83 | ->make(true); 84 | } 85 | 86 | /** 87 | * Gets runtime column value 88 | * 89 | * @return Closure 90 | */ 91 | public function getRuntimeValue() 92 | { 93 | return function($row) { 94 | return $row->runtime . ' s'; 95 | }; 96 | } 97 | 98 | /** 99 | * Gets has_error column value 100 | * 101 | * @return Closure 102 | */ 103 | public function getHas_errorValue() 104 | { 105 | return function($row) { 106 | return ($row->has_error) ? 107 | '' . trans('Failure') . '' : 108 | '' . trans('Success') . ''; 109 | }; 110 | } 111 | 112 | /** 113 | * Gets job_id column value 114 | * 115 | * @return Closure 116 | */ 117 | public function getJob_idValue() 118 | { 119 | return function($row) { 120 | return isset($row->job) ? $row->job->value['title'] . (($row->job->name) ? ' [ ' . $row->job->name . ' ]' : '') : '---'; 121 | }; 122 | } 123 | 124 | /** 125 | * {@inheritdoc} 126 | */ 127 | public function html() 128 | { 129 | publish('automation', ['js/automation_logs_table.js']); 130 | return $this->setName('Automation Logs') 131 | ->addColumn(['data' => 'id', 'name' => 'id', 'title' => trans('Id')]) 132 | ->addColumn(['data' => 'job_id', 'name' => 'job_id', 'title' => trans('Script name'), 'className' => 'bolded']) 133 | ->addColumn(['data' => 'has_error', 'name' => 'has_error', 'title' => trans('Status')]) 134 | ->addColumn(['data' => 'return', 'name' => 'return', 'title' => trans('Result')]) 135 | ->addColumn(['data' => 'runtime', 'name' => 'runtime', 'title' => trans('Runtime')]) 136 | ->addColumn(['data' => 'created_at', 'name' => 'created_at', 'title' => trans('Executed at')]) 137 | ->addAction(['name' => 'edit', 'title' => '', 'class' => 'mass-actions dt-actions', 'orderable' => false, 'searchable' => false]) 138 | ->setDeferedData(); 139 | } 140 | 141 | /** 142 | * Get actions column for table builder. 143 | * 144 | * @return callable 145 | */ 146 | protected function getActionsColumn() 147 | { 148 | return function ($row) { 149 | $html = app('html'); 150 | $this->addTableAction('show_log', $row, $html->link(handles("antares::automation/show/" . $row->id), trans('antares/automation::messages.show_full_log'), [ 151 | 'data-icon' => 'desktop-windows', 152 | 'data-id' => $row->id, 153 | 'data-title' => trans('antares/automation::messages.full_log_title', ['script_name' => $row->job->value['title']]), 154 | 'class' => 'triggerable show-full-log'])); 155 | $section = $html->create('div', $html->create('section', $html->create('ul', $html->raw(implode('', $this->tableActions->toArray())))), ['class' => 'mass-actions-menu'])->get(); 156 | return '' . $html->raw($section)->get(); 157 | }; 158 | } 159 | 160 | } 161 | -------------------------------------------------------------------------------- /src/Http/Filter/AutomationDateRangeFilter.php: -------------------------------------------------------------------------------- 1 | 'antares/automation::messages.executed_at' 52 | ]; 53 | 54 | /** 55 | * filters data by parameters from memory 56 | * 57 | * @param mixed $builder 58 | */ 59 | public function apply($builder) 60 | { 61 | if (!empty($values = $this->getValues())) { 62 | $range = json_decode($values, true); 63 | if (!isset($range['start']) or ! isset($range['end'])) { 64 | return $builder; 65 | } 66 | } 67 | } 68 | 69 | } 70 | -------------------------------------------------------------------------------- /src/Http/Filter/AutomationLogsFilter.php: -------------------------------------------------------------------------------- 1 | id] = $job->value['title']; 65 | } 66 | return $return; 67 | } 68 | 69 | /** 70 | * filters data by parameters from memory 71 | * 72 | * @param mixed $builder 73 | */ 74 | public function apply($builder) 75 | { 76 | $params = $this->getParams(); 77 | 78 | if (is_null($ids = array_get($params, __CLASS__ . '.value'))) { 79 | return false; 80 | } 81 | if (!empty($ids)) { 82 | $builder->whereIn('job_id', $ids); 83 | } 84 | } 85 | 86 | } 87 | -------------------------------------------------------------------------------- /src/Http/Filter/AutomationStatusFilter.php: -------------------------------------------------------------------------------- 1 | trans('antares/automation::messages.disabled'), 61 | '1' => trans('antares/automation::messages.enabled') 62 | ]; 63 | } 64 | 65 | /** 66 | * renders filter 67 | * 68 | * @return String 69 | */ 70 | public function render() 71 | { 72 | publish('automation', ['js/automation_status_filter.js']); 73 | $selected = $this->getValues(); 74 | return view('datatables-helpers::partials._filter_select_multiple', [ 75 | 'options' => $this->options(), 76 | 'column' => $this->column, 77 | 'placeholder' => trans('antares/automation::messages.select_status_placeholder'), 78 | 'selected' => $selected 79 | ])->render(); 80 | } 81 | 82 | /** 83 | * filters data by parameters from memory 84 | * 85 | * @param mixed $builder 86 | */ 87 | public function apply($builder) 88 | { 89 | if (!empty($values = $this->getValues())) { 90 | $builder->whereIn('active', $values); 91 | } 92 | return $builder; 93 | } 94 | 95 | } 96 | -------------------------------------------------------------------------------- /src/Http/Handlers/AutomationLogsBreadcrumbMenu.php: -------------------------------------------------------------------------------- 1 | 'automation-log-breadcrumb', 37 | 'link' => 'antares::automations/logs/index', 38 | 'icon' => null, 39 | 'boot' => [ 40 | 'group' => 'menu.top.automation-logs', 41 | 'on' => 'antares/automation::admin.index.logs' 42 | ] 43 | ]; 44 | 45 | /** 46 | * Get the title. 47 | * @param string $value 48 | * @return string 49 | */ 50 | public function getTitleAttribute($value) 51 | { 52 | return trans('antares/automation::messages.breadcrumb.automation_log'); 53 | } 54 | 55 | /** 56 | * Check authorization to display the menu. 57 | * @param \Antares\Contracts\Authorization\Authorization $acl 58 | * @return bool 59 | */ 60 | public function authorize(Authorization $acl) 61 | { 62 | return true; 63 | return app('antares.acl')->make('antares/automation')->can('download-logs'); 64 | } 65 | 66 | /** 67 | * Create a handler. 68 | * @return void 69 | */ 70 | public function handle() 71 | { 72 | if (!$this->passesAuthorization()) { 73 | return; 74 | } 75 | $this->createMenu(); 76 | $count = app(JobResults::class)->count(); 77 | if ($count) { 78 | $this->handler 79 | ->add('automation-log-download', '^:automation-log-breadcrumb') 80 | ->title(trans('antares/automation::messages.breadcrumb.automation_logs_download')) 81 | ->icon('zmdi-download') 82 | ->link(handles('antares::automation/logs/download')) 83 | ->attributes(['class' => 'triggerable download-automation-log']); 84 | 85 | $this->handler 86 | ->add('automation-log-delete', '^:automation-log-breadcrumb') 87 | ->title(trans('antares/automation::messages.breadcrumb.automation_logs_delete')) 88 | ->icon('zmdi-delete') 89 | ->link(handles('antares::automation/logs/delete')) 90 | ->attributes(['class' => 'delete-logs-menu-breadcrumb']); 91 | } 92 | } 93 | 94 | } 95 | -------------------------------------------------------------------------------- /src/Http/Handlers/AutomationLogsMenu.php: -------------------------------------------------------------------------------- 1 | 'automation-logs', 36 | 'link' => 'antares::automations/logs/index' 37 | ]; 38 | 39 | /** 40 | * Gets title attribute 41 | * 42 | * @return String 43 | */ 44 | public function getTitleAttribute() 45 | { 46 | return trans('antares/automation::messages.automation_log'); 47 | } 48 | 49 | /** 50 | * Get position. 51 | * 52 | * @return string 53 | */ 54 | public function getPositionAttribute() 55 | { 56 | return $this->handler->has('logger.request-log') ? '>:logger.request-log' : '>:settings.general-config'; 57 | } 58 | 59 | /** 60 | * Check whether the menu should be displayed. 61 | * 62 | * @param \Antares\Contracts\Auth\Guard $auth 63 | * 64 | * @return bool 65 | */ 66 | public function authorize(Guard $auth) 67 | { 68 | return app('antares.acl')->make('antares/automation')->can('automation-list'); 69 | } 70 | 71 | } 72 | -------------------------------------------------------------------------------- /src/Http/Handlers/Menu.php: -------------------------------------------------------------------------------- 1 | 'automation', 38 | 'title' => 'Automation', 39 | 'link' => 'antares::automation/index', 40 | 'icon' => 'icon-support', 41 | ]; 42 | 43 | /** 44 | * Get position. 45 | * 46 | * @return string 47 | */ 48 | public function getPositionAttribute() 49 | { 50 | return $this->handler->has('settings.ban_management') ? '>:settings.ban_management' : '>:settings.general-config'; 51 | } 52 | 53 | /** 54 | * Check whether the menu should be displayed. 55 | * 56 | * @param \Antares\Contracts\Auth\Guard $auth 57 | * 58 | * @return bool 59 | */ 60 | public function authorize(Guard $auth) 61 | { 62 | return app('antares.acl')->make('antares/automation')->can('automation-list'); 63 | } 64 | 65 | } 66 | -------------------------------------------------------------------------------- /src/Http/Presenters/IndexPresenter.php: -------------------------------------------------------------------------------- 1 | container = $container; 82 | $this->config = config('antares/automation'); 83 | $this->breadcrumb = $breadcrumb; 84 | $this->automationDatatables = $automationDatatables; 85 | $this->automationDetailsDatatable = $automationDetailsDatatable; 86 | } 87 | 88 | /** 89 | * show report details 90 | * 91 | * @param Model $eloquent 92 | * @return View 93 | */ 94 | public function show($eloquent) 95 | { 96 | return $this->view('show', ['model' => $eloquent]); 97 | } 98 | 99 | /** 100 | * Table View Generator 101 | * 102 | * @param Model $model 103 | * @return View 104 | */ 105 | public function tableShow(Model $model) 106 | { 107 | $this->breadcrumb->onShow($model); 108 | return $this->automationDetailsDatatable->render('antares/automation::admin.index.show'); 109 | } 110 | 111 | public function table() 112 | { 113 | $this->breadcrumb->onList(); 114 | if (app('request')->ajax()) { 115 | return $this->automationDatatables->ajax(); 116 | } 117 | $dataTable = $this->automationDatatables->html(); 118 | return view('antares/automation::admin.index.index', compact('dataTable')); 119 | } 120 | 121 | /** 122 | * shows form edit job 123 | * 124 | * @param Model $eloquent 125 | * @return View 126 | */ 127 | public function edit($eloquent) 128 | { 129 | 130 | $this->breadcrumb->onEdit($eloquent); 131 | $configuration = $eloquent->value; 132 | if (!class_exists($configuration['classname'])) { 133 | throw new ModelNotFoundException(); 134 | } 135 | $command = $this->container->make($configuration['classname']); 136 | $form = $command->form($eloquent->toArray() + $configuration); 137 | return $this->view('edit', compact('form')); 138 | } 139 | 140 | /** 141 | * gets instance of command form 142 | * 143 | * @param Model $eloquent 144 | * @return FormBuilder 145 | */ 146 | public function form($eloquent) 147 | { 148 | $configuration = unserialize($eloquent->value); 149 | $command = $this->container->make($configuration['classname']); 150 | return $command->form($eloquent->toArray() + $configuration); 151 | } 152 | 153 | /** 154 | * Get the evaluated view contents for the given view. 155 | * 156 | * @param string $view 157 | * @param array $data 158 | * @param array $mergeData 159 | * 160 | * @return View 161 | */ 162 | public function view($view, $data = [], $mergeData = []) 163 | { 164 | return view('antares/automation::admin.index.' . $view, $data, $mergeData); 165 | } 166 | 167 | } 168 | -------------------------------------------------------------------------------- /src/Http/backend.php: -------------------------------------------------------------------------------- 1 | group(['prefix' => 'automation'], function (Router $router) { 26 | $router->match(['GET', 'POST'], 'index', 'IndexController@index'); 27 | $router->match(['GET', 'POST'], 'show/{id}', 'IndexController@show'); 28 | $router->get('edit/{id}', 'IndexController@edit'); 29 | $router->get('run/{id}', 'IndexController@run'); 30 | $router->post('update', 'IndexController@update'); 31 | $router->get('scripts', 'IndexController@scripts'); 32 | $router->get('logs/download', 'IndexController@download'); 33 | $router->post('logs/delete', 'IndexController@delete'); 34 | }); 35 | $router->match(['GET', 'POST'], 'automations/logs/index', 'IndexController@logs'); 36 | -------------------------------------------------------------------------------- /src/Jobs/ManualLaunch.php: -------------------------------------------------------------------------------- 1 | command = $command; 53 | return $this; 54 | } 55 | 56 | /** 57 | * Execute the job. 58 | * 59 | * @return void 60 | */ 61 | public function handle() 62 | { 63 | set_time_limit(0); 64 | $before = microtime(true); 65 | $artisan = base_path('artisan'); 66 | $command = $this->command; 67 | $process = new Process("php {$artisan} {$command}"); 68 | $process->setTimeout(4000); 69 | $process->run(); 70 | $after = microtime(true); 71 | $runtime = $after - $before; 72 | app(Reports::class)->saveReport($command, $runtime, $process); 73 | } 74 | 75 | /** 76 | * Command name getter 77 | * 78 | * @return String 79 | */ 80 | public function getCommand(): String 81 | { 82 | return $this->command; 83 | } 84 | 85 | } 86 | -------------------------------------------------------------------------------- /src/Jobs/StartAutomation.php: -------------------------------------------------------------------------------- 1 | start(); 48 | } 49 | 50 | } 51 | -------------------------------------------------------------------------------- /src/Jobs/SyncAutomation.php: -------------------------------------------------------------------------------- 1 | belongsTo(JobResults::class, 'result_job_id', 'id'); 78 | } 79 | 80 | } 81 | -------------------------------------------------------------------------------- /src/Model/JobResults.php: -------------------------------------------------------------------------------- 1 | belongsTo(Jobs::class, 'job_id', 'id'); 66 | } 67 | 68 | /** 69 | * relation to job errors table 70 | * 71 | * @return \Illuminate\Database\Eloquent\Relations\BelongsTo 72 | */ 73 | public function jobErrors() 74 | { 75 | return $this->belongsTo(JobErrors::class, 'result_job_id', 'id'); 76 | } 77 | 78 | } 79 | -------------------------------------------------------------------------------- /src/Model/Jobs.php: -------------------------------------------------------------------------------- 1 | 'array']; 79 | 80 | /** 81 | * Query scope for active jobs 82 | * 83 | * @param object $query 84 | * 85 | * @return void 86 | */ 87 | public function scopeActive($query) 88 | { 89 | $query->where('active', 1); 90 | } 91 | 92 | /** 93 | * relation to job results 94 | * 95 | * @return HasMany 96 | */ 97 | public function jobResults() 98 | { 99 | return $this->hasMany(JobResults::class, 'job_id', 'id'); 100 | } 101 | 102 | /** 103 | * relation to components 104 | * 105 | * @return HasOne 106 | */ 107 | public function component() 108 | { 109 | return $this->hasOne(Component::class, 'id', 'component_id'); 110 | } 111 | 112 | /** 113 | * Relations to category model 114 | * 115 | * @return \Illuminate\Database\Eloquent\Relations\BelongsTo 116 | */ 117 | public function category() 118 | { 119 | return $this->belongsTo(JobsCategory::class, 'category_id'); 120 | } 121 | 122 | /** 123 | * Gets patterned url for search engines 124 | * 125 | * @return String 126 | */ 127 | public static function getPatternUrl() 128 | { 129 | return handles('antares::automation/edit/{id}'); 130 | } 131 | 132 | } 133 | -------------------------------------------------------------------------------- /src/Model/JobsCategory.php: -------------------------------------------------------------------------------- 1 | hasMany(Jobs::class, 'category_id', 'id'); 72 | } 73 | 74 | /** 75 | * Gets patterned url for search engines 76 | * 77 | * @return String 78 | */ 79 | public static function getPatternUrl() 80 | { 81 | return handles('antares::automation'); 82 | } 83 | 84 | } 85 | -------------------------------------------------------------------------------- /src/Model/JobsQueue.php: -------------------------------------------------------------------------------- 1 | 'array']; 55 | 56 | } 57 | -------------------------------------------------------------------------------- /src/Processor/IndexProcessor.php: -------------------------------------------------------------------------------- 1 | presenter = $presenter; 67 | $this->kernelConsole = $kernelConsole; 68 | } 69 | 70 | /** 71 | * default index action 72 | * 73 | * @return View 74 | */ 75 | public function index() 76 | { 77 | return $this->presenter->table(); 78 | } 79 | 80 | /** 81 | * Shows job details 82 | * 83 | * @param mixed $id 84 | * @param IndexListener $listener 85 | * @return View 86 | */ 87 | public function show($id, IndexListener $listener) 88 | { 89 | $model = app('Antares\Automation\Model\Jobs')->whereId($id)->first(); 90 | return is_null($model) ? $listener->showFailed() : $this->presenter->tableShow($model); 91 | } 92 | 93 | /** 94 | * shows edit form 95 | * 96 | * @param mixed $id 97 | * @param IndexListener $listener 98 | * @return View 99 | */ 100 | public function edit($id, IndexListener $listener) 101 | { 102 | $model = app('Antares\Automation\Model\Jobs')->where('id', $id)->first(); 103 | if (is_null($model)) { 104 | return $listener->showFailed(); 105 | } 106 | return $this->presenter->edit($model); 107 | } 108 | 109 | /** 110 | * update job 111 | * 112 | * @param mixed $id 113 | * @param IndexListener $listener 114 | * @return RedirectResponse 115 | */ 116 | public function update(IndexListener $listener) 117 | { 118 | $id = Input::get('id'); 119 | $model = app('Antares\Automation\Model\Jobs')->where('id', $id)->firstOrFail(); 120 | $form = $this->presenter->form($model); 121 | if (!$form->isValid()) { 122 | return $listener->updateValidationFailed($id, $form->getMessageBag()); 123 | } 124 | if (is_null($model)) { 125 | return $listener->updateFailed(); 126 | } 127 | $data = $form->getData(); 128 | $values = unserialize($model->value); 129 | if (app()->make($values['classname'])->getDisablable()) { 130 | $model->active = isset($data['active']) ? $data['active'] : 0; 131 | } 132 | 133 | foreach ($data as $key => $value) { 134 | $values[$key] = $value; 135 | } 136 | $model->value = serialize($values); 137 | $model->save(); 138 | return $listener->updateSuccess(); 139 | } 140 | 141 | /** 142 | * runs single job 143 | * 144 | * @param mixed $id 145 | * @param IndexListener $listener 146 | * @return View 147 | */ 148 | public function run($id, IndexListener $listener) 149 | { 150 | $model = app('Antares\Automation\Model\Jobs')->where('id', $id)->first(); 151 | $params = $model->value + array_except($model->toArray(), 'value'); 152 | 153 | if (!is_null($classname = array_get($params, 'classname')) and class_exists($classname)) { 154 | $job = app(ManualLaunch::class)->setCommand($model->name)->onConnection('database')->onQueue('install'); 155 | $this->dispatch($job); 156 | return $listener->runSuccess(); 157 | } 158 | return $listener->runFailed(); 159 | } 160 | 161 | /** 162 | * Downloads automation logs 163 | * 164 | * @return \Symfony\Component\HttpFoundation\Response 165 | */ 166 | public function download() 167 | { 168 | $datatable = app(AutomationLogs::class); 169 | 170 | list($headers, $data) = $this->prepareHeadersAndKeys($datatable); 171 | $items = $this->prepareContent($datatable, $data); 172 | $top = implode(self::separator, $headers); 173 | $content = implode("\n", $items); 174 | $csv = implode("\n", [$top, $content]); 175 | $date = date('Y_m_d_H_i_s', time()); 176 | $filename = "automation_log_{$date}.csv"; 177 | 178 | return response($csv, 200, [ 179 | 'Content-Type' => 'text/csv', 180 | 'Content-Description' => 'File Transfer', 181 | 'Content-Disposition' => 'attachment; filename=' . $filename, 182 | 'Content-Transfer-Encoding' => 'binary', 183 | 'Connection' => 'Keep-Alive', 184 | 'Expires' => '0', 185 | 'Cache-Control' => 'must-revalidate, post-check=0, pre-check=0', 186 | 'Pragma' => 'public', 187 | 'Content-Length' => strlen($csv)]); 188 | } 189 | 190 | /** 191 | * Prepares content for csv 192 | * 193 | * @param AutomationLogs $datatable 194 | * @param array $data 195 | * @return array 196 | */ 197 | protected function prepareContent(AutomationLogs $datatable, array $data) 198 | { 199 | $collection = $datatable->query()->get(); 200 | $return = []; 201 | foreach ($collection as $log) { 202 | $element = []; 203 | foreach ($data as $key => $method) { 204 | if (method_exists($datatable, $method)) { 205 | $called = call_user_func($datatable->{$method}(), $log); 206 | $value = is_object($called) ? $called->__toString() : $called; 207 | } else { 208 | $value = $log->{$key}; 209 | } 210 | if (str_contains($value, "\n")) { 211 | $value = '"' . $value . '"'; 212 | } 213 | array_push($element, strip_tags($value)); 214 | } 215 | array_push($return, implode(self::separator, $element)); 216 | } 217 | return $return; 218 | } 219 | 220 | /** 221 | * Prepares headers and data keys for csv 222 | * 223 | * @param ActivityLogs $datatable 224 | * @return array 225 | */ 226 | protected function prepareHeadersAndKeys($datatable) 227 | { 228 | $headers = []; 229 | $data = []; 230 | $columns = $datatable->html()->getColumns(); 231 | foreach ($columns as $column) { 232 | if ($column->data == 'action') { 233 | continue; 234 | } 235 | array_push($headers, $column->title); 236 | array_set($data, $column->data, 'get' . ucfirst($column->data) . 'Value'); 237 | } 238 | return [$headers, $data]; 239 | } 240 | 241 | /** 242 | * Deletes automation logs 243 | * 244 | * @param IndexListener $listener 245 | * @return RedirectResponse 246 | */ 247 | public function delete(IndexListener $listener) 248 | { 249 | if (is_null($daterange = Input::get('daterange'))) { 250 | return $listener->deleteFailed(); 251 | } 252 | $start = null; 253 | $end = null; 254 | extract(json_decode($daterange, true)); 255 | if (is_null($start) or is_null($end)) { 256 | return $listener->deleteFailed(); 257 | } 258 | 259 | $logs = app(JobResults::class)->whereBetween('created_at', [$start . ' 00:00:00', $end . ' 00:00:00']); 260 | if (!$logs->count()) { 261 | return $listener->noLogsToDelete(); 262 | } 263 | DB::beginTransaction(); 264 | try { 265 | $logs->delete(); 266 | } catch (Exception $ex) { 267 | Log::alert($ex); 268 | DB::rollback(); 269 | return $listener->deleteFailed(); 270 | } 271 | DB::commit(); 272 | return $listener->deleteSuccess(); 273 | } 274 | 275 | /** 276 | * Gets scripts collection for filter 277 | * 278 | * @return JsonResponse 279 | */ 280 | public function scripts() 281 | { 282 | 283 | $input = Input::all(); 284 | $builder = Jobs::query(); 285 | if (!is_null($query = array_get($input, 'q'))) { 286 | $builder->where('name', 'like', '%' . e($query) . '%')->orWhere('value', 'like', '%' . e($query) . '%'); 287 | } 288 | $collection = $builder->get(); 289 | $return = new Collection(); 290 | foreach ($collection as $collected) { 291 | $return->push(['id' => $collected->id, 'script_name' => unserialize($collected->value)['title']]); 292 | } 293 | 294 | $paginate = $return->forPage(array_get($input, 'page', 0), array_get($input, 'per_page', 20)); 295 | return new JsonResponse(new Paginator($paginate, 20, 0), 200); 296 | } 297 | 298 | } 299 | -------------------------------------------------------------------------------- /src/Repository/Reports.php: -------------------------------------------------------------------------------- 1 | makeModel(); 54 | $model = $this->model->where('name', $commandName)->first(); 55 | $processFailed = !$process->isSuccessful(); 56 | if (is_null($model)) { 57 | return false; 58 | } 59 | $hasError = false; 60 | DB::beginTransaction(); 61 | try { 62 | $result = $model->jobResults()->getModel()->newInstance(); 63 | $result->fill([ 64 | 'job_id' => $model->id, 65 | 'has_error' => $processFailed, 66 | 'runtime' => $runtime, 67 | 'return' => $process->getOutput(), 68 | ]); 69 | $result->save(); 70 | if ($processFailed) { 71 | $error = $result->jobErrors()->getModel()->newInstance(); 72 | $error->insert([ 73 | 'result_job_id' => $result->id, 74 | 'code' => $process->getExitCode(), 75 | 'name' => $process->getExitCodeText(), 76 | 'return' => $process->getErrorOutput(), 77 | ]); 78 | } 79 | } catch (Exception $e) { 80 | $hasError = true; 81 | } 82 | if ($hasError) { 83 | DB::rollback(); 84 | return false; 85 | } 86 | DB::commit(); 87 | return true; 88 | } 89 | 90 | } 91 | -------------------------------------------------------------------------------- /tests/AutomationServiceProviderTest.php: -------------------------------------------------------------------------------- 1 | disableMiddlewareForAllTests(); 40 | } 41 | 42 | /** 43 | * Teardown the test environment. 44 | */ 45 | public function tearDown() 46 | { 47 | parent::tearDown(); 48 | unset($_SERVER['StubBaseController@setupFilters']); 49 | } 50 | 51 | /** 52 | * Tests Antares\Automation\AutomationServiceProvider::register 53 | */ 54 | public function testRegister() 55 | { 56 | $app = $this->app; 57 | $app['events'] = m::mock('\Illuminate\Contracts\Events\Dispatcher'); 58 | $app['files'] = m::mock('\Illuminate\Filesystem\Filesystem'); 59 | $stub = new AutomationServiceProvider($app); 60 | $this->assertNull($stub->register()); 61 | } 62 | 63 | /** 64 | * Tests Antares\Automation\AutomationServiceProvider::bootExtensionComponents 65 | */ 66 | public function testBootExtensionComponents() 67 | { 68 | $app = $this->app; 69 | $stub = new AutomationServiceProvider($app); 70 | $stub->register(); 71 | $this->assertNull($stub->bootExtensionComponents()); 72 | } 73 | 74 | } 75 | -------------------------------------------------------------------------------- /tests/Console/SyncCommandTest.php: -------------------------------------------------------------------------------- 1 | app[Component::class] = $componentMock = m::mock(Component::class); 59 | $mockObject = new stdClass(); 60 | $mockObject->name = 'acl_antares'; 61 | $mockObject->id = 1; 62 | $collection = new Collection([ 63 | $mockObject 64 | ]); 65 | $this->app[Provider::class] = $mProvider = m::mock(Provider::class); 66 | $this->app['antares.memory'] = $memoryManagerMock = m::mock(MemoryManager::class); 67 | $memoryManagerMock->shouldReceive('make')->with('component')->once()->andReturn($mProvider) 68 | ->shouldReceive('make')->with('jobs')->once()->andReturn($jobsMemory = m::mock(JobsMemory::class)); 69 | 70 | 71 | $mProvider->shouldReceive('get')->with('extensions.active')->once()->andReturn([ 72 | "components/automation" => [ 73 | "path" => "base::src/components/automation", 74 | "source-path" => "base::src/components/automation", 75 | "name" => "automation", 76 | "full_name" => "Automation Manager", 77 | "description" => "Automation Manager Antares", 78 | "author" => "Łukasz Cirut", 79 | "url" => "https://antares.com", 80 | "version" => "0.5", 81 | "config" => [], 82 | "autoload" => [], 83 | "provides" => [ 84 | "Antares\Automation\AutomationServiceProvider", 85 | "Antares\Automation\CommandServiceProvider", 86 | "Antares\Automation\ScheduleServiceProvider" 87 | ], 88 | ] 89 | ]); 90 | 91 | $componentMock->shouldReceive('all')->withNoArgs()->andReturn($collection); 92 | 93 | $command = new SyncCommand(); 94 | $command->setOutput($outputMock = m::mock(OutputStyle::class)); 95 | $info = 'No jobs found.'; 96 | $outputMock->shouldReceive('writeln')->withAnyArgs()->once()->andReturn($info); 97 | 98 | $this->assertNull($command->handle()); 99 | } 100 | 101 | /** 102 | * Tests Antares\Automation\Console\SyncCommand::setOutput 103 | */ 104 | public function testSetOutput() 105 | { 106 | $command = new SyncCommand(); 107 | $this->assertInstanceOf(SyncCommand::class, $command->setOutput($outputMock = m::mock(OutputStyle::class))); 108 | } 109 | 110 | } 111 | -------------------------------------------------------------------------------- /tests/Http/Controllers/Admin/IndexControllerTest.php: -------------------------------------------------------------------------------- 1 | addProvider(AutomationServiceProvider::class); 42 | parent::setUp(); 43 | 44 | $this->disableMiddlewareForAllTests(); 45 | } 46 | 47 | /** 48 | * Get processor mock. 49 | * 50 | * @return \Antares\Foundation\Processor\Account\ProfileDashboard 51 | */ 52 | protected function getProcessorMock() 53 | { 54 | $kernel = m::mock(\Illuminate\Contracts\Console\Kernel::class); 55 | $processor = m::mock(IndexProcessor::class, [m::mock(IndexPresenter::class), $kernel]); 56 | $processor->shouldReceive('update')->withAnyArgs()->andReturnNull(); 57 | $this->app->instance(IndexProcessor::class, $processor); 58 | return $processor; 59 | } 60 | 61 | /** 62 | * Tests Antares\Automation\Http\Controllers\Admin\IndexController::index 63 | */ 64 | public function testIndex() 65 | { 66 | $this->getProcessorMock()->shouldReceive('index')->once()->andReturn(View::class); 67 | $this->call('GET', 'antares/automation/index'); 68 | $this->assertResponseOk(); 69 | } 70 | 71 | /** 72 | * Tests Antares\Automation\Http\Controllers\Admin\IndexController::show 73 | */ 74 | public function testShow() 75 | { 76 | $this->getProcessorMock()->shouldReceive('show')->once()->andReturn(View::class); 77 | $this->call('GET', 'antares/automation/show/1'); 78 | $this->assertResponseOk(); 79 | } 80 | 81 | /** 82 | * Tests Antares\Automation\Http\Controllers\Admin\IndexController::showFailed 83 | */ 84 | public function testShowFailed() 85 | { 86 | $this->getProcessorMock()->shouldReceive('show')->once() 87 | ->andReturnUsing(function ($request, $listener) { 88 | return $listener->showFailed(); 89 | }); 90 | $this->call('GET', 'antares/automation/show/1'); 91 | $this->assertResponseStatus(302); 92 | } 93 | 94 | /** 95 | * Tests Antares\Automation\Http\Controllers\Admin\IndexController::edit 96 | */ 97 | public function testEdit() 98 | { 99 | $this->getProcessorMock()->shouldReceive('edit')->once()->andReturn(View::class); 100 | $this->call('GET', 'antares/automation/edit/1'); 101 | $this->assertResponseOk(); 102 | } 103 | 104 | /** 105 | * Tests Antares\Automation\Http\Controllers\Admin\IndexController::update 106 | */ 107 | public function testUpdate() 108 | { 109 | $this->getProcessorMock()->shouldReceive('edit')->once()->andReturnUsing(function ($id, $listener) { 110 | return $listener->updateSuccess(); 111 | }); 112 | $this->call('POST', 'antares/automation/update'); 113 | $this->assertResponseStatus(200); 114 | } 115 | 116 | /** 117 | * Tests Antares\Automation\Http\Controllers\Admin\IndexController::updateFailed 118 | */ 119 | public function testUpdateFailed() 120 | { 121 | $this->getProcessorMock()->shouldReceive('edit')->once()->andReturnUsing(function ($id, $listener) { 122 | return $listener->updateFailed(); 123 | }); 124 | $this->call('POST', 'antares/automation/update'); 125 | $this->assertResponseStatus(200); 126 | } 127 | 128 | /** 129 | * Tests Antares\Automation\Http\Controllers\Admin\IndexController::updateSuccess 130 | */ 131 | public function testUpdateSuccess() 132 | { 133 | $this->getProcessorMock()->shouldReceive('edit')->once()->andReturnUsing(function ($id, $listener) { 134 | return $listener->updateSuccess(); 135 | }); 136 | $this->call('POST', 'antares/automation/update'); 137 | $this->assertResponseStatus(200); 138 | } 139 | 140 | /** 141 | * Tests Antares\Automation\Http\Controllers\Admin\IndexController::run 142 | */ 143 | public function testRun() 144 | { 145 | $this->getProcessorMock()->shouldReceive('run')->once()->andReturnUsing(function ($id, $listener) { 146 | return $listener->updateSuccess(); 147 | }); 148 | $this->call('GET', 'antares/automation/run/1'); 149 | $this->assertResponseStatus(302); 150 | } 151 | 152 | /** 153 | * Tests Antares\Automation\Http\Controllers\Admin\IndexController::runSuccess 154 | */ 155 | public function testRunSuccess() 156 | { 157 | $this->getProcessorMock()->shouldReceive('run')->once()->andReturnUsing(function ($id, $listener) { 158 | return $listener->updateSuccess(); 159 | }); 160 | $this->call('GET', 'antares/automation/run/1'); 161 | $this->assertResponseStatus(302); 162 | } 163 | 164 | /** 165 | * Tests Antares\Automation\Http\Controllers\Admin\IndexController::runFailed 166 | */ 167 | public function testRunFailed() 168 | { 169 | $this->getProcessorMock()->shouldReceive('run')->once()->andReturnUsing(function ($id, $listener) { 170 | return $listener->updateFailed(); 171 | }); 172 | $this->call('GET', 'antares/automation/run/1'); 173 | $this->assertResponseStatus(302); 174 | } 175 | 176 | } 177 | -------------------------------------------------------------------------------- /tests/Http/Handlers/AutomationPaneTest.php: -------------------------------------------------------------------------------- 1 | assertInstanceOf(AutomationPane::class, AutomationPane::getInstance()); 56 | } 57 | 58 | /** 59 | * Tests \Antares\Automation\Http\Handlers\AutomationPane::compose 60 | */ 61 | public function testCompose() 62 | { 63 | $stub = AutomationPane::getInstance(); 64 | $this->app['antares.platform.memory'] = m::mock('Antares\Memory\Provider'); 65 | $acl = m::mock('Antares\Authorization\Factory') 66 | ->shouldReceive('make')->with("antares/automation")->andReturnSelf() 67 | ->shouldReceive("can")->with(m::type("String"))->andReturn(true) 68 | ->shouldReceive('attach')->with($this->app['antares.platform.memory'])->andReturnSelf() 69 | ->getMock(); 70 | 71 | $this->app['antares.widget'] = $widgetMock = m::mock(WidgetManager::class); 72 | $widgetMock->shouldReceive('make')->with('pane.left')->andReturn($pane = m::mock(Pane::class)); 73 | $pane->shouldReceive('add')->with('automation')->andReturnSelf() 74 | ->shouldReceive('content')->with('foo')->andReturnSelf(); 75 | 76 | 77 | 78 | $widgetMock->shouldReceive('make')->with('menu.automation.pane')->andReturn($menuMock = m::mock(Menu::class)); 79 | $menuMock->shouldReceive('add')->withAnyArgs()->andReturnSelf() 80 | ->shouldReceive('link')->withAnyArgs()->andReturnSelf() 81 | ->shouldReceive('title')->withAnyArgs()->andReturnSelf() 82 | ->shouldReceive('prepend')->withAnyArgs()->andReturnSelf(); 83 | $this->app['antares.acl'] = $acl; 84 | 85 | $foundation = m::mock('\Antares\Contracts\Foundation\Foundation'); 86 | $foundation->shouldReceive('handles')->withAnyArgs()->andReturn('#url'); 87 | $this->app['translator'] = $translator = m::mock('\Illuminate\Translator\Translator'); 88 | $translator->shouldReceive('trans')->withAnyArgs()->andReturn('foo'); 89 | $this->app['antares.app'] = $foundation; 90 | $this->app['view'] = $view = m::mock(Factory::class); 91 | $view->shouldReceive('make')->with(m::type('String'))->andReturnSelf() 92 | ->shouldReceive('render')->withNoArgs()->andReturn('foo'); 93 | 94 | $this->assertNull($stub->compose()); 95 | } 96 | 97 | } 98 | -------------------------------------------------------------------------------- /tests/Http/Handlers/MenuTest.php: -------------------------------------------------------------------------------- 1 | shouldReceive('make')->once()->with('antares.platform.menu')->andReturn($menu); 55 | $stub = new AutomationMenu($app); 56 | $this->assertInstanceOf(AutomationMenu::class, $stub); 57 | $this->assertInstanceOf(MenuHandler::class, $stub); 58 | } 59 | 60 | public function testItShouldBeChildOfExtensionGivenExtensionIsAvailable() 61 | { 62 | $app = m::mock('Illuminate\Container\Container'); 63 | $menu = m::mock(Menu::class); 64 | $app->shouldReceive('make')->once()->with('antares.platform.menu')->andReturn($menu); 65 | $menu->shouldReceive('has')->once()->with('extensions')->andReturn(true); 66 | $stub = new AutomationMenu($app); 67 | $this->assertEquals('^:settings', $stub->getPositionAttribute()); 68 | } 69 | 70 | public function testItShouldNextToHomeGivenExtensionIsntAvailable() 71 | { 72 | $app = m::mock('Illuminate\Container\Container'); 73 | $menu = m::mock(Menu::class); 74 | $app->shouldReceive('make')->once()->with('antares.platform.menu')->andReturn($menu); 75 | $menu->shouldReceive('has')->once()->with('extensions')->andReturn(false); 76 | $stub = new AutomationMenu($app); 77 | $this->assertEquals('>:home', $stub->getPositionAttribute()); 78 | } 79 | 80 | /** 81 | * Tests Antares\Automation\Http\Handlers\Menu::authorize 82 | */ 83 | public function testAuthorize() 84 | { 85 | $this->app['antares.platform.memory'] = m::mock('Antares\Memory\Provider'); 86 | $app = m::mock('Illuminate\Container\Container'); 87 | $menu = m::mock(Menu::class); 88 | $acl = m::mock('Antares\Authorization\Factory') 89 | ->shouldReceive('make')->with("antares/automation")->andReturnSelf() 90 | ->shouldReceive("can")->with(m::type("String"))->andReturn(true) 91 | ->shouldReceive('attach')->with($this->app['antares.platform.memory'])->andReturnSelf() 92 | ->getMock(); 93 | 94 | $this->app['antares.acl'] = $acl; 95 | $app->shouldReceive('make')->once()->with('antares.platform.menu')->andReturn($menu); 96 | $stub = new AutomationMenu($app); 97 | $guardMock = m::mock('\Antares\Contracts\Auth\Guard'); 98 | $guardMock->shouldReceive('guest')->andReturn(false); 99 | $this->assertTrue($stub->authorize($guardMock)); 100 | } 101 | 102 | } 103 | -------------------------------------------------------------------------------- /tests/Http/Presenters/IndexPresenterTest.php: -------------------------------------------------------------------------------- 1 | app->make('Collective\Html\HtmlBuilder'); 50 | $formBuilder = $this->app->make('Antares\Html\Support\FormBuilder'); 51 | $urlGenerator = url(); 52 | $builder = new Builder($this->app['config'], $this->app['view'], $htmlBuilder, $urlGenerator, $formBuilder); 53 | $this->stub = new IndexPresenter($this->app, $builder); 54 | $this->app['view']->addNamespace('antares/automation', realpath(base_path() . '../../../../components/automation/resources/views')); 55 | } 56 | 57 | protected function getModel() 58 | { 59 | return m::mock('Antares\Automation\Model\Jobs') 60 | ->shouldReceive('getTable')->withNoArgs()->andReturn('tbl_jobs') 61 | ->shouldReceive('getConnectionName')->withAnyArgs()->andReturn('mysql') 62 | ->shouldReceive('hydrate')->withAnyArgs()->andReturn(new Collection([1 => 2])) 63 | ->shouldReceive('where')->withAnyArgs()->andReturnSelf() 64 | ->shouldReceive('first')->withNoArgs()->andReturnSelf() 65 | ->shouldReceive('delete')->withNoArgs()->andReturnSelf() 66 | ->shouldReceive('newCollection')->withAnyArgs()->andReturn(new Collection()) 67 | ->getMock(); 68 | } 69 | 70 | protected function getBuilder() 71 | { 72 | $resolver = m::mock('Illuminate\Database\ConnectionInterface') 73 | ->shouldReceive('getTablePrefix')->withNoArgs()->andReturn('dupa') 74 | ->shouldReceive('getDriverName')->withNoArgs()->andReturn('mysql') 75 | ->shouldReceive('getQueryGrammar') 76 | ->andReturn($this->app['Illuminate\Database\Query\Grammars\Grammar']) 77 | ->shouldReceive('raw') 78 | ->andReturn($expression = m::mock('Illuminate\Database\Query\Expression')) 79 | ->shouldReceive('select') 80 | ->once() 81 | ->withAnyArgs() 82 | ->andReturn(null)->getMock(); 83 | 84 | $expression->shouldReceive('getValue')->andReturn('testowanie'); 85 | 86 | $queryBuilder = m::mock(Builder2::class); 87 | $queryBuilder->shouldReceive('getConnection')->withNoArgs()->andReturn($resolver) 88 | ->shouldReceive('toSql')->withNoArgs()->andReturn('') 89 | ->shouldReceive('select')->once()->withAnyArgs()->andReturn(null) 90 | ->shouldReceive('getBindings')->once()->withAnyArgs()->andReturn([]) 91 | ->shouldReceive('setBindings')->once()->withAnyArgs()->andReturnSelf() 92 | ->shouldReceive('count')->once()->withAnyArgs()->andReturn(0) 93 | ->shouldReceive('get')->once()->withAnyArgs()->andReturn([1 => 2]) 94 | ->shouldReceive('from')->once()->with('tbl_jobs')->andReturnSelf(); 95 | 96 | 97 | $resolver->shouldReceive('table') 98 | ->andReturn($queryBuilder); 99 | $builder = new Builder3($queryBuilder); 100 | $model = $this->getModel(); 101 | $model->shouldReceive('get')->withAnyArgs()->andReturn($builder); 102 | $builder->setModel($model); 103 | return $builder; 104 | } 105 | 106 | /** 107 | * Teardown the test environment. 108 | */ 109 | public function tearDown() 110 | { 111 | parent::tearDown(); 112 | } 113 | 114 | /** 115 | * Tests Antares\Automation\Http\Presenters\IndexPresenter::show 116 | */ 117 | public function testShow() 118 | { 119 | $this->assertInstanceOf(View::class, $this->stub->show($this->getModel())); 120 | } 121 | 122 | /** 123 | * Tests Antares\Automation\Http\Presenters\IndexPresenter::table 124 | */ 125 | public function testTable() 126 | { 127 | $this->assertInstanceOf(View::class, $this->stub->table($this->getBuilder())); 128 | } 129 | 130 | /** 131 | * Tests Antares\Automation\Http\Presenters\IndexPresenter::tableJson 132 | */ 133 | public function testTableJson() 134 | { 135 | $this->assertInstanceOf(JsonResponse::class, $this->stub->tableJson($this->getBuilder())); 136 | } 137 | 138 | /** 139 | * Tests Antares\Automation\Http\Presenters\IndexPresenter::view 140 | */ 141 | public function testView() 142 | { 143 | try { 144 | $this->stub->view('foo', [], []); 145 | } catch (Exception $e) { 146 | $this->assertSame("View [admin.index.foo] not found.", $e->getMessage()); 147 | } 148 | $this->assertInstanceOf(View::class, $this->stub->view('index', [])); 149 | } 150 | 151 | } 152 | -------------------------------------------------------------------------------- /tests/Model/JobErrorsTest.php: -------------------------------------------------------------------------------- 1 | addMockConnection($model); 48 | $this->model = $model; 49 | } 50 | 51 | /** 52 | * Teardown the test environment. 53 | */ 54 | public function tearDown() 55 | { 56 | parent::tearDown(); 57 | } 58 | 59 | /** 60 | * has timestamps 61 | */ 62 | public function testHasTimestamps() 63 | { 64 | $this->assertFalse($this->model->timestamps); 65 | } 66 | 67 | /** 68 | * has valid morph class 69 | */ 70 | public function testHasValidMorhClass() 71 | { 72 | $this->assertSame($this->model->getMorphClass(), 'Antares\Automation\Model\JobErrors'); 73 | } 74 | 75 | /** 76 | * has valid table name 77 | */ 78 | public function testHasValidTableName() 79 | { 80 | $this->assertSame('tbl_job_errors', $this->model->getTable()); 81 | } 82 | 83 | /** 84 | * has valid relation to job results table 85 | */ 86 | public function testJobResult() 87 | { 88 | $jobResult = $this->model->jobResult(); 89 | $this->assertInstanceOf(BelongsTo::class, $jobResult); 90 | $this->assertInstanceOf(JobResults::class, $jobResult->getModel()); 91 | } 92 | 93 | } 94 | -------------------------------------------------------------------------------- /tests/Model/JobResultsTest.php: -------------------------------------------------------------------------------- 1 | addMockConnection($model); 48 | $this->model = $model; 49 | } 50 | 51 | /** 52 | * Teardown the test environment. 53 | */ 54 | public function tearDown() 55 | { 56 | parent::tearDown(); 57 | } 58 | 59 | /** 60 | * has timestamps 61 | */ 62 | public function testHasTimestamps() 63 | { 64 | $this->assertFalse($this->model->timestamps); 65 | } 66 | 67 | /** 68 | * has valid morph class 69 | */ 70 | public function testHasValidMorhClass() 71 | { 72 | $this->assertSame($this->model->getMorphClass(), 'Antares\Automation\Model\JobResults'); 73 | } 74 | 75 | /** 76 | * has valid table name 77 | */ 78 | public function testHasValidTableName() 79 | { 80 | $this->assertSame('tbl_job_results', $this->model->getTable()); 81 | } 82 | 83 | /** 84 | * has valid relation to job errors table 85 | */ 86 | public function testJobErrors() 87 | { 88 | $jobErrors = $this->model->jobErrors(); 89 | $this->assertInstanceOf(BelongsTo::class, $jobErrors); 90 | $this->assertInstanceOf(JobErrors::class, $jobErrors->getModel()); 91 | } 92 | 93 | } 94 | -------------------------------------------------------------------------------- /tests/Model/JobsTest.php: -------------------------------------------------------------------------------- 1 | addMockConnection($model); 50 | $this->model = $model; 51 | } 52 | 53 | /** 54 | * Teardown the test environment. 55 | */ 56 | public function tearDown() 57 | { 58 | parent::tearDown(); 59 | } 60 | 61 | /** 62 | * has timestamps 63 | */ 64 | public function testHasTimestamps() 65 | { 66 | $this->assertTrue($this->model->timestamps); 67 | } 68 | 69 | /** 70 | * has valid morph class 71 | */ 72 | public function testHasValidMorhClass() 73 | { 74 | $this->assertSame($this->model->getMorphClass(), 'Antares\Automation\Model\Jobs'); 75 | } 76 | 77 | /** 78 | * has valid table name 79 | */ 80 | public function testHasValidTableName() 81 | { 82 | $this->assertSame('tbl_jobs', $this->model->getTable()); 83 | } 84 | 85 | /** 86 | * has valid relation to job results table 87 | */ 88 | public function testJobResults() 89 | { 90 | $jobResults = $this->model->jobResults(); 91 | $this->assertInstanceOf(HasMany::class, $jobResults); 92 | $this->assertInstanceOf(JobResults::class, $jobResults->getModel()); 93 | } 94 | 95 | /** 96 | * has valid relation to component table 97 | */ 98 | public function testComponent() 99 | { 100 | $component = $this->model->component(); 101 | $this->assertInstanceOf(HasOne::class, $component); 102 | $this->assertInstanceOf(Component::class, $component->getModel()); 103 | } 104 | 105 | } 106 | -------------------------------------------------------------------------------- /tests/Processor/IndexProcessorTest.php: -------------------------------------------------------------------------------- 1 | shouldReceive('getTablePrefix')->withNoArgs()->andReturn('dupa') 52 | // ->shouldReceive('getDriverName')->withNoArgs()->andReturn('mysql') 53 | // ->shouldReceive('getQueryGrammar') 54 | // ->andReturn($this->app['Illuminate\Database\Query\Grammars\Grammar']) 55 | // ->shouldReceive('raw') 56 | // ->andReturn($expression = m::mock('Illuminate\Database\Query\Expression')) 57 | // ->shouldReceive('select') 58 | // ->withAnyArgs() 59 | // ->andReturn(null)->getMock(); 60 | // 61 | // $expression->shouldReceive('getValue')->andReturn('testowanie'); 62 | // 63 | // $queryBuilder = m::mock(Builder::class); 64 | // $queryBuilder->shouldReceive('getConnection')->withNoArgs()->andReturn($resolver) 65 | // ->shouldReceive('toSql')->withNoArgs()->andReturn('') 66 | // ->shouldReceive('select')->withAnyArgs()->andReturn(null) 67 | // ->shouldReceive('getBindings')->withAnyArgs()->andReturn([]) 68 | // ->shouldReceive('setBindings')->withAnyArgs()->andReturnSelf() 69 | // ->shouldReceive('count')->withAnyArgs()->andReturn(0) 70 | // ->shouldReceive('get')->withAnyArgs()->andReturn([1 => 2]) 71 | // ->shouldReceive('from')->with('tbl_jobs')->andReturnSelf(); 72 | // 73 | // 74 | // $resolver->shouldReceive('table') 75 | // ->andReturn($queryBuilder); 76 | // 77 | // 78 | // $builder = new Builder3($queryBuilder); 79 | // $model->shouldReceive('getTable')->withNoArgs()->andReturn('tbl_jobs') 80 | // ->shouldReceive('get')->withAnyArgs()->andReturn($builder) 81 | // ->shouldReceive('getConnectionName')->withAnyArgs()->andReturn('mysql') 82 | // ->shouldReceive('hydrate')->withAnyArgs()->andReturn(new Collection([1 => 2])) 83 | // ->shouldReceive('where')->withAnyArgs()->andReturnSelf() 84 | // ->shouldReceive('first')->withNoArgs()->andReturnSelf() 85 | // ->shouldReceive('delete')->withNoArgs()->andReturnSelf() 86 | // ->shouldReceive('with')->withAnyArgs()->andReturnSelf() 87 | // ->shouldReceive('query')->withNoArgs()->andReturn($builder) 88 | // ->shouldReceive('getAttribute')->with('jobResults')->andReturn(new Collection()) 89 | // ->shouldReceive('getAttribute')->with('value')->andReturn(serialize(['foo' => 1, 'active' => 1, 'classname' => '\Antares\Logger\Console\ReportCommand', 'launch' => 'everyMinute', 'launchTimes' => ['everyMinute']])) 90 | // ->shouldReceive('with')->with(m::type('String'))->andReturnSelf() 91 | // ->shouldReceive('setAttribute')->withAnyArgs()->andReturnSelf() 92 | // ->shouldReceive('save')->withNoArgs()->andReturnSelf() 93 | // ->shouldReceive('toArray')->withNoArgs()->andReturn(['foo' => 1]) 94 | // ->shouldReceive('select')->withAnyArgs()->andReturnSelf() 95 | // ->shouldReceive('take')->withAnyArgs()->andReturnSelf() 96 | // ->shouldReceive('skip')->withAnyArgs()->andReturnSelf() 97 | // ->shouldReceive('toSql')->withNoArgs()->andReturn('select') 98 | // ->shouldReceive('getQuery')->withNoArgs()->andReturn($builder) 99 | // ->shouldReceive('newCollection')->withAnyArgs()->andReturn(new Collection()); 100 | // 101 | // $this->app['Antares\Automation\Model\Jobs'] = $model; 102 | // 103 | // $builder->setModel($model); 104 | // $model->shouldReceive('all')->withNoArgs()->andReturn($builder); 105 | // 106 | $this->app['view']->addNamespace('antares/automation', realpath(base_path() . '../../../../components/automation/resources/views')); 107 | } 108 | 109 | /** 110 | * Teardown the test environment. 111 | */ 112 | public function tearDown() 113 | { 114 | parent::tearDown(); 115 | m::close(); 116 | } 117 | 118 | /** 119 | * create presenter stub instance 120 | * 121 | * @return IndexPresenter 122 | */ 123 | protected function getPresenter() 124 | { 125 | 126 | $breadcrumb = m::mock(\Antares\Automation\Http\Breadcrumb\Breadcrumb::class); 127 | $breadcrumb->shouldReceive('onList')->withNoArgs()->andReturn(null) 128 | ->shouldReceive('onShow')->withAnyArgs()->andReturn(null) 129 | ->shouldReceive('onEdit')->withAnyArgs()->andReturn(null); 130 | 131 | $automationDatatables = $this->app->make(\Antares\Automation\Http\Datatables\Automation::class); 132 | $automationDetailsDatatables = $this->app->make(\Antares\Automation\Http\Datatables\AutomationDetails::class); 133 | return new IndexPresenter($this->app, $breadcrumb, $automationDatatables, $automationDetailsDatatables); 134 | } 135 | 136 | /** 137 | * gets stub instance 138 | * 139 | * @return IndexProcessor 140 | */ 141 | protected function getStub() 142 | { 143 | $kernel = m::mock(Kernel::class); 144 | return new IndexProcessor($this->getPresenter(), $kernel); 145 | } 146 | 147 | /** 148 | * Tests Antares\Automation\Processor\IndexProcessor::index 149 | */ 150 | public function testIndex() 151 | { 152 | $stub = $this->getStub(); 153 | $this->assertInstanceOf(View::class, $stub->index($this->app['request'])); 154 | } 155 | 156 | /** 157 | * Tests Antares\Automation\Processor\IndexProcessor::show 158 | */ 159 | public function testShow() 160 | { 161 | $stub = $this->getStub(); 162 | $this->assertInstanceOf(View::class, $stub->show(1)); 163 | } 164 | 165 | /** 166 | * Tests Antares\Automation\Processor\IndexProcessor::edit 167 | */ 168 | public function testEdit() 169 | { 170 | $stub = $this->getStub(); 171 | $indexListener = m::mock(IndexListener::class); 172 | $this->assertInstanceOf(View::class, $stub->edit(1, $indexListener)); 173 | } 174 | 175 | /** 176 | * Tests Antares\Automation\Processor\IndexProcessor::update 177 | * 178 | * @expectedException \Illuminate\Database\Eloquent\ModelNotFoundException 179 | */ 180 | public function testUpdate() 181 | { 182 | $stub = $this->getStub(); 183 | $indexListener = m::mock(IndexListener::class); 184 | $indexListener->shouldReceive('updateSuccess')->andReturn(new RedirectResponse('#')); 185 | $this->assertInstanceOf(RedirectResponse::class, $stub->update($indexListener)); 186 | } 187 | 188 | } 189 | --------------------------------------------------------------------------------