diff --git a/deb.mk b/deb.mk
index f3875b16..1b4b2453 100644
--- a/deb.mk
+++ b/deb.mk
@@ -3,7 +3,7 @@ Source: $(NAME)
Section: unknown
Priority: optional
Maintainer: $(VENDOR)
-Build-Depends: debhelper (>= 9), python-virtualenv, python-pip, python-dev, gcc, libffi-dev, libssl-dev, libyaml-dev, libkrb5-dev
+Build-Depends: debhelper (>= 9), python-virtualenv, python-pip, python-dev, gcc, libffi-dev, libssl-dev, libyaml-dev, libkrb5-dev, libssl-dev, libsasl2-dev, libldap2-dev
Standards-Version: 3.9.5
Homepage: https://gitlab.com/vstconsulting/polemarch
Vcs-Git: git@gitlab.com:vstconsulting/polemarch.git
@@ -11,7 +11,7 @@ Vcs-Browser: https://gitlab.com/vstconsulting/polemarch.git
Package: $(NAME)
Architecture: amd64
-Depends: $${shlibs:Depends}, $${misc:Depends}, python-virtualenv, libffi6, libssl-dev, sshpass, libpython2.7, git, libyaml-dev, libkrb5-dev
+Depends: $${shlibs:Depends}, $${misc:Depends}, python-virtualenv, libffi6, libssl-dev, sshpass, libpython2.7, git, libyaml-dev, libkrb5-dev, libssl-dev, libsasl2-dev, libldap2-dev
Description: $(SUMMARY)
$(DESCRIPTION)
endef
diff --git a/doc/config.rst b/doc/config.rst
index 04857da4..c379daa3 100644
--- a/doc/config.rst
+++ b/doc/config.rst
@@ -158,6 +158,36 @@ PostgreSQL. Configuration details you can look at
If you run at Polemarch software at multiple nodes (clusterization), you should
use some of client-server database (SQLite not suitable) shared for all nodes.
+If you use MySQL there are a list of required settings, that you should make for correct
+database work.
+
+Firstly, if you use MySQL and you have set timezone different from "UTC" you should make
+next command:
+
+.. sourcecode:: bash
+
+ mysql_tzinfo_to_sql /usr/share/zoneinfo | mysql -u root -p mysql
+
+Secondly, for correct MySQL work you should set next options in ``settings.ini`` file:
+
+.. sourcecode:: bash
+
+ [database.options]
+ init_command = SET sql_mode='STRICT_TRANS_TABLES', default_storage_engine=INNODB, NAMES 'utf8', CHARACTER SET 'utf8', SESSION collation_connection = 'utf8_unicode_ci'
+
+Finally, you should add some options to MySQL configuration:
+
+.. sourcecode:: bash
+
+ [client]
+ default-character-set=utf8
+ init_command = SET collation_connection = @@collation_database
+
+ [mysqld]
+ character-set-server=utf8
+ collation-server=utf8_unicode_ci
+
+
.. _cache:
Cache settings
diff --git a/doc/gui.rst b/doc/gui.rst
index 61969242..4e91ef7f 100644
--- a/doc/gui.rst
+++ b/doc/gui.rst
@@ -204,6 +204,7 @@ As you can see, the form of new GIT project creation consist of 5 fields:
After project creation you will the next page:
.. image:: gui_png/test-project.png
+.. image:: gui_png/test-project2.png
As you can see at image above for GIT project
it is possible to choose a branch to what user want to sync. In this example user will sync
@@ -222,6 +223,11 @@ Also there are 2 new fields:
ERROR - synchronization failed,
OK - project is synchronized.
+There is new section on this page:
+
+* **Reame.md** - if project has “readme.md” or “readme.rst” file in it’s project directory,
+ Polemarch will add content of this file to this section.
+
Also there are several buttons on this page:
* **save** - this button saves all changes you have made on this page.
@@ -438,7 +444,9 @@ But also there are some new buttons here:
* **save and execute** - this button saves all changes you have made on this page and executes this template.
-* **create new option** - this button opens the "Create new option" page.
+* **options** - this button opens the page with this template options list.
+
+* **periodic tasks** - this button opens the page with the list of periodic tasks based on this template.
* **history** - this button opens history list of template executions.
@@ -453,6 +461,10 @@ Sometimes your need to keep some similar templates, which are different by only
In this case template options will be extremly useful for you. In every template you can create
a lot of options which can modify this template by some parameters. Let's look at the example:
+.. image:: gui_png/empty-options-list.png
+
+As you can see, now there are no options for this template. Let's create the first one.
+
.. image:: gui_png/create-new-option.png
As you can see there are 2 section on this page: "New option" and "Adding new argument".
@@ -513,12 +525,6 @@ As you can see there are 2 sections on this page: "New task" and "Adding new arg
* **save in history** - if value is true, the fact of task execution will be saved in history records.
Otherwise, no history records about this periodic task execution will be saved.
-* **inventory source** - source of inventory. It can be either "From database" or "From file in project dir".
-
-* **inventory from project / inventory file** - name of inventory.
-
-* **group** - name of group to which this periodic task will be executed.
-
* **kind** - kind of task: module or playbook.
* **playbook** - name of playbook. This field is available for kind=playbook only.
@@ -527,8 +533,19 @@ As you can see there are 2 sections on this page: "New task" and "Adding new arg
the ansible module name and Polemarch will suggest you appropriate name values.
This field is available for kind=module only.
+* **template from project** - name of template from this project. This field has autocomplete, so you can just start typing
+ the template name and Polemarch will suggest you appropriate name values. Also it is possible to choose template with some option.
+ Options' name will be shown in square brackets, for example, "template_name [template_option_name]".
+ This field is available for kind=template only.
+
+* **group** - name of group to which this periodic task will be executed.
+
* **args** - arguments for ansible module. This field is available for kind=module only.
+* **inventory source** - source of inventory. It can be either "From database" or "From file in project dir".
+
+* **inventory from project / inventory file** - name of inventory.
+
* **type** - type of schedule. It can be either "Interval schedule" or "Cron style schedule".
* **interval schedule / cron style schedule** - value for schedule.
diff --git a/doc/gui_png/create-periodic.png b/doc/gui_png/create-periodic.png
index 0c821e48..ea89047e 100644
Binary files a/doc/gui_png/create-periodic.png and b/doc/gui_png/create-periodic.png differ
diff --git a/doc/gui_png/empty-options-list.png b/doc/gui_png/empty-options-list.png
new file mode 100644
index 00000000..3d98d1d2
Binary files /dev/null and b/doc/gui_png/empty-options-list.png differ
diff --git a/doc/gui_png/module-template-page.png b/doc/gui_png/module-template-page.png
index 50b02237..a9826e02 100644
Binary files a/doc/gui_png/module-template-page.png and b/doc/gui_png/module-template-page.png differ
diff --git a/doc/gui_png/test-periodic.png b/doc/gui_png/test-periodic.png
index 397a2854..d5325cef 100644
Binary files a/doc/gui_png/test-periodic.png and b/doc/gui_png/test-periodic.png differ
diff --git a/doc/gui_png/test-project.png b/doc/gui_png/test-project.png
index 9b8586ee..907a2ab9 100644
Binary files a/doc/gui_png/test-project.png and b/doc/gui_png/test-project.png differ
diff --git a/doc/gui_png/test-project2.png b/doc/gui_png/test-project2.png
new file mode 100644
index 00000000..91b61603
Binary files /dev/null and b/doc/gui_png/test-project2.png differ
diff --git a/doc/polemarch-sphinx-theme/layout.html b/doc/polemarch-sphinx-theme/layout.html
index fa51c1ef..338d48f1 100644
--- a/doc/polemarch-sphinx-theme/layout.html
+++ b/doc/polemarch-sphinx-theme/layout.html
@@ -101,6 +101,11 @@
+
+
+
+
+
diff --git a/doc/polemarch-sphinx-theme/static/polemarch-style.css b/doc/polemarch-sphinx-theme/static/polemarch-style.css
index a77e3a32..d10a1fa4 100644
--- a/doc/polemarch-sphinx-theme/static/polemarch-style.css
+++ b/doc/polemarch-sphinx-theme/static/polemarch-style.css
@@ -203,9 +203,9 @@ form.search {
background-position: center center;
background-repeat: no-repeat;
-webkit-background-size:150px 40px;
- -o-background-size: 150px 40px;
- -moz-background-size:150px 40px;
- background-size: 150px 40px;
+ -o-background-size: 150px 40px;
+ -moz-background-size:150px 40px;
+ background-size: 150px 40px;
display: block;
width:150px;
height: 40px;
@@ -221,9 +221,9 @@ form.search {
background-position: center center;
background-repeat: no-repeat;
-webkit-background-size:40px 40px;
- -o-background-size: 40px 40px;
- -moz-background-size:40px 40px;
- background-size: 40px 40px;
+ -o-background-size: 40px 40px;
+ -moz-background-size:40px 40px;
+ background-size: 40px 40px;
display: block;
width: 40px;
height: 40px;
@@ -258,7 +258,54 @@ li.toctree-l1.current>a {
padding: 12px 5px 12px 10px!important;
}
+/* beginning of styles for badge(label) with versions on Read The Docs */
+.rst-versions {
+ z-index: 3000!important;
+}
+
+.rst-current-version > span:first-child {
+ margin-top: 10px!important;
+}
+
+
+/* end of styles for badge(label) with versions on Read The Docs */
+
+
+/* beginning of styles for Notification field about current version */
+.body {
+ padding-top: 1px;
+}
+
+.body > .warning {
+ background-color: #FCC;
+ border: 1px solid #FAA;
+}
+
+.body > .admonition {
+ padding: 7px;
+ margin-left: 15px;
+ margin-right: 15px;
+ border-radius: 3px;
+ margin-top: 19px;
+}
+
+.body > .admonition {
+
+}
+
+.body > .admonition p.admonition-title {
+ font-weight: normal;
+ font-size: 24px;
+ margin: 0 0 10px 0;
+ padding: 0;
+ line-height: 1;
+}
+
+.body > .admonition p.last {
+ margin-bottom: 0;
+}
+/* end of styles for Notification field about current version */
@media (max-width: 767px) {
.bg-logo-doc {
@@ -268,8 +315,8 @@ li.toctree-l1.current>a {
background-image: url(img/logo/logo-bw.png);
}
.fork-me img {
- width:112px;
- height:112px;
+ width:112px;
+ height:112px;
}
#big-search-input-pm {
diff --git a/doc/restapi.rst b/doc/restapi.rst
index bf33290f..cda29673 100644
--- a/doc/restapi.rst
+++ b/doc/restapi.rst
@@ -873,6 +873,8 @@ Projects
},
"revision": "5471aeb916ee7f8754d55f159e532592b995b0ec",
"branch": "master",
+ "readme_content": null,
+ "readme_ext": null,
"url":"http://localhost:8080/api/v1/projects/7/"
}
@@ -891,6 +893,8 @@ Projects
:>json object owner: |project_owner_details|
:>json string revision: ``GIT`` revision
:>json string branch: current branch of project, to which project has been synced last time.
+ :>json string readme_content: |project_readme_content_ref|
+ :>json string readme_ext: |project_readme_ext_ref|
:>json string url: url to this specific inventory.
.. |project_notes_def| replace:: not required field for some user's notes, for example,
@@ -914,7 +918,10 @@ Projects
could be seen in :http:get:`/api/v1/users/{id}/`.
.. |project_details_ref| replace:: **Response JSON Object:** response json
fields are the same as in :http:get:`/api/v1/projects/{id}/`.
-
+.. |project_readme_content_ref| replace:: if project has "readme.md" or "readme.rst" file
+ in it's project directory, this field will contain content of readme file parsed to html
+.. |project_readme_ext_ref| replace:: if project has "readme.md" or "readme.rst" file
+ in it's project directory, this field will contain extension of readme file
.. http:get:: /api/v1/projects/
Gets list of projects. |pagination_def|
@@ -1029,6 +1036,8 @@ Projects
},
"revision": "NO VCS",
"branch": "NO VCS",
+ "readme_content": null,
+ "readme_ext": null,
"url":"http://localhost:8080/api/v1/projects/9/"
}
@@ -1088,6 +1097,8 @@ Projects
},
"revision": "NO VCS",
"branch": "NO VCS",
+ "readme_content": null,
+ "readme_ext": null,
"url":"http://localhost:8080/api/v1/projects/9/"
}
@@ -1359,6 +1370,8 @@ Periodic tasks
"project":7,
"inventory":8,
"save_result": true,
+ "template": null,
+ "template_opt": null,
"enabled": true,
"vars":{
@@ -1372,11 +1385,12 @@ Periodic tasks
:>json string type: |ptask_type_details|
:>json string schedule: |ptask_schedule_details|
:>json string mode: playbook or module to run periodically.
- :>json string kind: either this task is playbook running (``PLAYBOOK``) or
- module running (``MODULE``).
+ :>json string kind: |ptask_kind_def|
:>json number project: id of project, which this task belongs to.
:>json number inventory: id of inventory for which must execute_playbook playbook.
:>json boolean save_result: if ``save_result`` is true, the result will be saved.
+ :>json number template: |ptask_template_def|
+ :>json string template_opt: |ptask_template_opt_def|
:>json boolean enabled: if ``enabled`` is true, the periodic task will be enabled.
:>json object vars: |ptask_vars_def|
:>json string url: url to this specific periodic task.
@@ -1384,6 +1398,14 @@ Periodic tasks
.. |ptask_notes_def| replace:: not required field for some user's notes, for example,
for what purpose this periodic task was created or something like this.
+.. |ptask_kind_def| replace:: ``PLAYBOOK`` (if this task runs playbook), ``MODULE``
+ (if this task runs module) or ``TEMPLATE`` (if this task runs template).
+
+.. |ptask_template_def| replace:: id of template (if kind is ``PLAYBOOK`` or ``MODULE``,
+ this field will be equal to null).
+
+.. |ptask_template_opt_def| replace:: name of template option (if kind is ``PLAYBOOK``
+ or ``MODULE`` of if this periodic task executes template without option, this field will be equal to null).
.. |ptask_details_ref| replace:: **Response JSON Object:** response json
fields are the same as in :http:get:`/api/v1/periodic-tasks/{id}/`.
@@ -1441,34 +1463,32 @@ Periodic tasks
{
"id":10,
"name":"periodic-test",
- "type":"INTERVAL",
- "schedule":"60",
+ "type":"CRONTAB",
+ "schedule":"60* */2 sun,fri 1-15 *",
"mode":"collect_data.yml",
"kind":"PLAYBOOK",
- "project": 12,
+ "project":7,
"inventory":8,
"save_result": true,
+ "template": null,
+ "template_opt": null,
"enabled": true,
- "vars":{
-
- },
- "url":"http://127.0.0.1:8080/api/v1/periodic-tasks/10/?format=json"
+ "url":"http://127.0.0.1:8080/api/v1/periodic-tasks/10/"
},
{
"id":11,
"name":"periodic-test2",
- "type":"CRONTAB",
- "schedule":"* */2 sun,fri 1-15 *",
- "mode":"do_greatest_evil.yml",
- "kind":"PLAYBOOK",
- "project": 12,
+ "type":"INTERVAL",
+ "schedule":"20",
+ "mode":"",
+ "kind":"TEMPLATE",
+ "project":7,
"inventory":8,
"save_result": true,
+ "template": 1,
+ "template_opt": "some-vars",
"enabled": true,
- "vars":{
-
- },
- "url":"http://127.0.0.1:8080/api/v1/periodic-tasks/11/?format=json"
+ "url":"http://127.0.0.1:8080/api/v1/periodic-tasks/11/"
}
]
}
@@ -1502,7 +1522,11 @@ Periodic tasks
"mode": "touch_the_clouds.yml",
"kind": "PLAYBOOK",
"project": 7,
- "inventory": 8
+ "inventory": 8,
+ "save_result": true,
+ "template": null,
+ "template_opt": null,
+ "enabled": true,
"vars":{
},
@@ -1523,6 +1547,8 @@ Periodic tasks
"project": 7,
"inventory": 8,
"save_result": true,
+ "template": null,
+ "template_opt": null,
"enabled": true,
"vars":{
@@ -1551,10 +1577,7 @@ Periodic tasks
{
"type": "INTERVAL",
- "schedule": "25",
- "mode": "touch_the_clouds.yml",
- "project": 7,
- "inventory": 8
+ "schedule": "60"
}
Results:
@@ -1566,13 +1589,18 @@ Periodic tasks
"name":"new-periodic-test",
"notes":"",
"type": "INTERVAL",
- "schedule": "25",
+ "schedule": "60",
"mode": "touch_the_clouds.yml",
"kind": "PLAYBOOK",
"project": 7,
"inventory": 8,
"save_result": true,
+ "template": null,
+ "template_opt": null,
"enabled": true,
+ "vars":{
+
+ },
"url": "http://127.0.0.1:8080/api/v1/periodic-tasks/14/?format=api"
}
@@ -1594,16 +1622,20 @@ Periodic tasks
{
"name":"new-periodic-test",
+ "notes":"",
"type": "INTERVAL",
- "schedule": "25",
+ "schedule": "60",
"mode": "touch_the_clouds.yml",
"kind": "PLAYBOOK",
"project": 7,
"inventory": 8,
"save_result": true,
+ "template": null,
+ "template_opt": null,
"enabled": true,
- "vars": {}
+ "vars":{
+ }
}
Results:
@@ -3255,57 +3287,79 @@ Users
Host: example.com
Accept: application/json, text/javascript
- {
- "pmwTasksTemplatesWidget": {
- "active": true,
- "sortNum": 8,
- "collapse": true
- },
- "pmwUsersCounter": {
- "active": true,
- "sortNum": 5,
- "collapse": false
- },
- "pmwProjectsCounter": {
- "active": true,
- "sortNum": 4,
- "collapse": false
- },
- "pmwHostsCounter": {
- "active": true,
- "sortNum": 0,
- "collapse": false
- },
- "pmwInventoriesCounter": {
- "active": true,
- "sortNum": 2,
- "collapse": false
- },
- "pmwGroupsCounter": {
- "active": true,
- "sortNum": 1,
- "collapse": false
- },
- "pmwChartWidget": {
- "active": true,
- "sortNum": 6,
- "collapse": false
- },
- "pmwModulesTemplatesWidget": {
- "active": true,
- "sortNum": 9,
- "collapse": true
- },
- "pmwTemplatesCounter": {
- "active": true,
- "sortNum": 3,
- "collapse": false
- },
- "pmwAnsibleModuleWidget": {
- "active": true,
- "sortNum": 7,
- "collapse": true
- }
+ {
+ "chartLineSettings": {
+ "ok": {
+ "active": true
+ },
+ "all_tasks": {
+ "active": true
+ },
+ "interrupted": {
+ "active": true
+ },
+ "delay": {
+ "active": true
+ },
+ "error": {
+ "active": true
+ },
+ "offline": {
+ "active": true
+ }
+ },
+ "widgetSettings": {
+ "pmwTasksTemplatesWidget": {
+ "active": false,
+ "sortNum": 8,
+ "collapse": false
+ },
+ "pmwUsersCounter": {
+ "active": true,
+ "sortNum": 5,
+ "collapse": false
+ },
+ "pmwProjectsCounter": {
+ "active": true,
+ "sortNum": 3,
+ "collapse": false
+ },
+ "pmwHostsCounter": {
+ "active": true,
+ "sortNum": 0,
+ "collapse": false
+ },
+ "pmwInventoriesCounter": {
+ "active": true,
+ "sortNum": 2,
+ "collapse": false
+ },
+ "pmwGroupsCounter": {
+ "active": true,
+ "sortNum": 1,
+ "collapse": false
+ },
+ "pmwChartWidget": {
+ "active": true,
+ "sortNum": 6,
+ "collapse": false
+ },
+ "pmwModulesTemplatesWidget": {
+ "active": false,
+ "sortNum": 9,
+ "collapse": false
+ },
+ "pmwTemplatesCounter": {
+ "active": true,
+ "sortNum": 4,
+ "collapse": false
+ },
+ "pmwAnsibleModuleWidget": {
+ "active": true,
+ "sortNum": 7,
+ "collapse": true
+ }
+ }
}
Results:
@@ -3313,63 +3367,90 @@ Users
.. sourcecode:: js
{
- "pmwTasksTemplatesWidget": {
- "active": true,
- "sortNum": 8,
- "collapse": true
- },
- "pmwUsersCounter": {
- "active": true,
- "sortNum": 5,
- "collapse": false
- },
- "pmwProjectsCounter": {
- "active": true,
- "sortNum": 4,
- "collapse": false
- },
- "pmwHostsCounter": {
- "active": true,
- "sortNum": 0,
- "collapse": false
- },
- "pmwInventoriesCounter": {
- "active": true,
- "sortNum": 2,
- "collapse": false
- },
- "pmwGroupsCounter": {
- "active": true,
- "sortNum": 1,
- "collapse": false
- },
- "pmwChartWidget": {
- "active": true,
- "sortNum": 6,
- "collapse": false
- },
- "pmwModulesTemplatesWidget": {
- "active": true,
- "sortNum": 9,
- "collapse": true
- },
- "pmwTemplatesCounter": {
- "active": true,
- "sortNum": 3,
- "collapse": false
- },
- "pmwAnsibleModuleWidget": {
- "active": true,
- "sortNum": 7,
- "collapse": true
- }
- }
+ "chartLineSettings": {
+ "ok": {
+ "active": true
+ },
+ "all_tasks": {
+ "active": true
+ },
+ "interrupted": {
+ "active": true
+ },
+ "delay": {
+ "active": true
+ },
+ "error": {
+ "active": true
+ },
+ "offline": {
+ "active": true
+ }
+ },
+ "widgetSettings": {
+ "pmwTasksTemplatesWidget": {
+ "active": false,
+ "sortNum": 8,
+ "collapse": false
+ },
+ "pmwUsersCounter": {
+ "active": true,
+ "sortNum": 5,
+ "collapse": false
+ },
+ "pmwProjectsCounter": {
+ "active": true,
+ "sortNum": 3,
+ "collapse": false
+ },
+ "pmwHostsCounter": {
+ "active": true,
+ "sortNum": 0,
+ "collapse": false
+ },
+ "pmwInventoriesCounter": {
+ "active": true,
+ "sortNum": 2,
+ "collapse": false
+ },
+ "pmwGroupsCounter": {
+ "active": true,
+ "sortNum": 1,
+ "collapse": false
+ },
+ "pmwChartWidget": {
+ "active": true,
+ "sortNum": 6,
+ "collapse": false
+ },
+ "pmwModulesTemplatesWidget": {
+ "active": false,
+ "sortNum": 9,
+ "collapse": false
+ },
+ "pmwTemplatesCounter": {
+ "active": true,
+ "sortNum": 4,
+ "collapse": false
+ },
+ "pmwAnsibleModuleWidget": {
+ "active": true,
+ "sortNum": 7,
+ "collapse": true
+ }
+ }
+ }
+ :>json object chartLineSettings: object with Dashboard chart line settings.
+ :>json boolean active: |users_settings_lines_active|
+ :>json object widgetSettings: object with Dashboard widgets settings.
:>json string pmw{widget_Name}: widget name.
:>json boolean active: |users_settings_active|
:>json number sortNum: |users_settings_sortNum|
:>json boolean collapse: |users_settings_collapse|
+.. |users_settings_lines_active| replace:: one of Dashboard chart line settings, if ``active`` is ``true``, this line will be visible on Dashboard.
+
.. |users_settings_active| replace:: one of widget's settings, if ``active`` is ``true``, this widget will be visible on Dashboard.
.. |users_settings_sortNum| replace:: one of widget's settings, it means order number of widget on Dashboard.
@@ -3402,58 +3483,83 @@ Users
.. sourcecode:: js
{
- "pmwTasksTemplatesWidget": {
- "active": true,
- "sortNum": 8,
- "collapse": true
- },
- "pmwUsersCounter": {
- "active": true,
- "sortNum": 5,
- "collapse": false
- },
- "pmwProjectsCounter": {
- "active": true,
- "sortNum": 4,
- "collapse": false
- },
- "pmwHostsCounter": {
- "active": true,
- "sortNum": 0,
- "collapse": false
- },
- "pmwInventoriesCounter": {
- "active": true,
- "sortNum": 2,
- "collapse": false
- },
- "pmwGroupsCounter": {
- "active": true,
- "sortNum": 1,
- "collapse": false
- },
- "pmwChartWidget": {
- "active": true,
- "sortNum": 6,
- "collapse": false
- },
- "pmwModulesTemplatesWidget": {
- "active": true,
- "sortNum": 9,
- "collapse": true
- },
- "pmwTemplatesCounter": {
- "active": true,
- "sortNum": 3,
- "collapse": false
- },
- "pmwAnsibleModuleWidget": {
- "active": true,
- "sortNum": 7,
- "collapse": true
- }
- }
+ "chartLineSettings": {
+ "ok": {
+ "active": true
+ },
+ "all_tasks": {
+ "active": true
+ },
+ "interrupted": {
+ "active": true
+ },
+ "delay": {
+ "active": true
+ },
+ "error": {
+ "active": true
+ },
+ "offline": {
+ "active": true
+ }
+ },
+ "widgetSettings": {
+ "pmwTasksTemplatesWidget": {
+ "active": false,
+ "sortNum": 8,
+ "collapse": false
+ },
+ "pmwUsersCounter": {
+ "active": true,
+ "sortNum": 5,
+ "collapse": false
+ },
+ "pmwProjectsCounter": {
+ "active": true,
+ "sortNum": 3,
+ "collapse": false
+ },
+ "pmwHostsCounter": {
+ "active": true,
+ "sortNum": 0,
+ "collapse": false
+ },
+ "pmwInventoriesCounter": {
+ "active": true,
+ "sortNum": 2,
+ "collapse": false
+ },
+ "pmwGroupsCounter": {
+ "active": true,
+ "sortNum": 1,
+ "collapse": false
+ },
+ "pmwChartWidget": {
+ "active": true,
+ "sortNum": 6,
+ "collapse": false
+ },
+ "pmwModulesTemplatesWidget": {
+ "active": false,
+ "sortNum": 9,
+ "collapse": false
+ },
+ "pmwTemplatesCounter": {
+ "active": true,
+ "sortNum": 4,
+ "collapse": false
+ },
+ "pmwAnsibleModuleWidget": {
+ "active": true,
+ "sortNum": 7,
+ "collapse": true
+ }
+ }
+ }
+ :>json object chartLineSettings: object with Dashboard chart line settings.
+ :>json boolean active: |users_settings_lines_active|
+ :>json object widgetSettings: object with Dashboard widgets settings.
:>json string pmw{widget_Name}: widget name.
:>json boolean active: |users_settings_active|
:>json number sortNum: |users_settings_sortNum|
diff --git a/doc/screencast.gif b/doc/screencast.gif
index cb51ea4b..0d26cd5c 100644
Binary files a/doc/screencast.gif and b/doc/screencast.gif differ
diff --git a/polemarch/api/v1/filters.py b/polemarch/api/v1/filters.py
index f71781c5..8f02524b 100644
--- a/polemarch/api/v1/filters.py
+++ b/polemarch/api/v1/filters.py
@@ -168,7 +168,8 @@ class Meta:
'mode',
'kind',
'type',
- 'project')
+ 'project',
+ 'template')
class HistoryLinesFilter(filters.FilterSet):
diff --git a/polemarch/api/v1/serializers.py b/polemarch/api/v1/serializers.py
index f7224c9c..b5549da4 100644
--- a/polemarch/api/v1/serializers.py
+++ b/polemarch/api/v1/serializers.py
@@ -764,6 +764,8 @@ class Meta:
'owner',
'revision',
'branch',
+ 'readme_content',
+ 'readme_ext',
'url',)
def inventories_operations(self, method, data):
diff --git a/polemarch/api/v1/views.py b/polemarch/api/v1/views.py
index 31f96a44..d5c193e3 100644
--- a/polemarch/api/v1/views.py
+++ b/polemarch/api/v1/views.py
@@ -265,9 +265,9 @@ class BulkViewSet(rest_views.APIView):
'periodictask': _op_types.keys(),
'template': _op_types.keys(),
'history': ['del', "get"],
- 'hooks': _op_types.keys(),
- 'users': _op_types.keys(),
- 'teams': _op_types.keys(),
+ 'hook': _op_types.keys(),
+ 'user': _op_types.keys(),
+ 'team': _op_types.keys(),
}
type_to_bulk = {
"host": "hosts",
diff --git a/polemarch/main/models/projects.py b/polemarch/main/models/projects.py
index 503613e1..5cf5a068 100644
--- a/polemarch/main/models/projects.py
+++ b/polemarch/main/models/projects.py
@@ -4,6 +4,8 @@
import os
import logging
import six
+from docutils.core import publish_parts
+from markdown2 import Markdown
from django.conf import settings
from django.utils import timezone
from django.core.validators import ValidationError
@@ -51,6 +53,34 @@ class Meta:
class SyncError(Exception):
pass
+ class ReadMe(object):
+
+ def __init__(self, project):
+ self.project = project
+ self.content = None
+ self.ext = None
+ self.set_readme()
+
+ def set_readme(self):
+ if os.path.exists(self.project.path):
+ md = None
+ rst = None
+ for file in os.listdir(self.project.path):
+ if file.lower() == 'readme.md':
+ md = file
+ if file.lower() == 'readme.rst':
+ rst = file
+ if rst is not None:
+ file = open(self.project.path + '/' + rst)
+ self.content = publish_parts(file.read(),
+ writer_name='html')['html_body']
+ self.ext = os.path.splitext(rst)[1]
+ elif md is not None:
+ file = open(self.project.path + '/' + md)
+ markdowner = Markdown()
+ self.content = markdowner.convert(file.read())
+ self.ext = os.path.splitext(md)[1]
+
HIDDEN_VARS = [
'repo_password',
]
@@ -179,3 +209,18 @@ def revision(self):
@property
def branch(self):
return self.repo_class.get_branch_name()
+
+ def __get_readme(self):
+ readme = getattr(self, 'readme', None)
+ if readme is None:
+ self.readme = self.ReadMe(self)
+ return self.readme
+ return readme
+
+ @property
+ def readme_content(self):
+ return self.__get_readme().content
+
+ @property
+ def readme_ext(self):
+ return self.__get_readme().ext
diff --git a/polemarch/main/models/tasks.py b/polemarch/main/models/tasks.py
index e38f252f..03399b67 100644
--- a/polemarch/main/models/tasks.py
+++ b/polemarch/main/models/tasks.py
@@ -316,9 +316,10 @@ def execute(self, sync=True):
else:
data = self.template.get_data_with_options(self.template_opt)
data.pop('inventory', None)
+ kind = self.template._exec_types[self.template.kind]
args = [
- self.template.kind.upper(),
- data.pop(self.template.kind.lower()),
+ kind.upper(),
+ data.pop(kind),
self.template.inventory_object
]
kwargs.update(data)
diff --git a/polemarch/main/tests/bulk.py b/polemarch/main/tests/bulk.py
index ad877ef7..75f00dab 100644
--- a/polemarch/main/tests/bulk.py
+++ b/polemarch/main/tests/bulk.py
@@ -179,7 +179,7 @@ def test_bulk_history(self):
def test_bulk_unsupported(self):
data = dict(username="some_user", password="some_password")
bulk_data = [
- {'type': "add", 'item': "user", 'data': data}
+ {'type': "add", 'item': "users", 'data': data}
]
self.get_result("post", "/api/v1/_bulk/", 415,
data=json.dumps(bulk_data))
diff --git a/polemarch/main/tests/project.py b/polemarch/main/tests/project.py
index cf4a9f80..6762e837 100644
--- a/polemarch/main/tests/project.py
+++ b/polemarch/main/tests/project.py
@@ -150,3 +150,39 @@ def check_tasks(count):
check_tasks(1)
self.get_result("delete", project_url)
+
+ def test_project_read_me(self):
+ url = "/api/v1/projects/"
+ project_url = "/api/v1/projects/{}/"
+
+ data = dict(name="Manual project", repository="manual",
+ vars=dict(repo_type="MANUAL"))
+ prj_id = self.mass_create(url, [data], "name", "repository")[0]
+ project = self.get_model_class('Project').objects.get(pk=prj_id)
+ self.assertEqual(project.vars["repo_type"], "MANUAL")
+ self.assertEqual(project.status, "OK")
+ project_url = project_url.format(project.id)
+
+ self.assertEqual(project.readme_content, None)
+ self.assertEqual(project.readme_ext, None)
+ delattr(project, 'readme')
+
+ with open(project.path+"/readme.md", "w") as f:
+ f.write("# test README.md \n **bold** \n *italic* \n")
+ self.get_result("post", project_url + "sync/", 200)
+ self.assertEqual(project.readme_content,
+ "
test README.md
\n\n
bold" +
+ " \n italic
\n")
+ self.assertEqual(project.readme_ext, ".md")
+ delattr(project, 'readme')
+
+ with open(project.path+"/readme.rst", "w") as f:
+ f.write("test README.rst \n **bold** \n *italic* \n")
+ self.get_result("post", project_url + "sync/", 200)
+ self.assertEqual(project.readme_content,
+ '
\n
\n- ' +
+ 'test README.rst
\n- bold\n' +
+ 'italic
\n
\n
\n')
+ self.assertEqual(project.readme_ext, ".rst")
+
+ self.get_result("delete", project_url)
diff --git a/polemarch/static/css/gui.css b/polemarch/static/css/gui.css
index cf97c3c0..ba80cb9c 100644
--- a/polemarch/static/css/gui.css
+++ b/polemarch/static/css/gui.css
@@ -672,7 +672,7 @@ table{
}
}
-.user-status, .group-children, .all-only, .new_subitem {
+.user-status, .group-children, .all-only, .new_subitem, .pt-enabled {
font-weight: bold;
text-align: center;
vertical-align: middle;
@@ -925,7 +925,7 @@ table{
list-style: none;
}
-.recipients_list ul {
+.recipients_list ul, .option-vars {
margin-left:15px;
padding-left: 0px;
}
@@ -940,6 +940,32 @@ table{
}
+.readme_content {
+ word-wrap: break-word;
+}
+
+.readme_content h1 {
+ font-size: 24px;
+}
+
+.readme_content h2 {
+ font-size: 20px;
+}
+
+.readme_content h3 {
+ font-size: 18px;
+}
+
+@media (min-width: 1200px) {
+ .readme_content img {
+ max-width: 60%;
+ width: auto;
+ }
+}
+
+
+
+
@media (max-width: 1200px) {
.hidden-1200 {
display: none;
diff --git a/polemarch/static/js/pmDashboard.js b/polemarch/static/js/pmDashboard.js
index fc085b56..1e247826 100644
--- a/polemarch/static/js/pmDashboard.js
+++ b/polemarch/static/js/pmDashboard.js
@@ -155,6 +155,88 @@ pmDashboard.model.defaultWidgets = [
],
]
+/*
+ * Массив, хранящий в себе настройки линий графика на странице Dashboard'а.
+ */
+pmDashboard.model.ChartLineSettings = [
+
+]
+
+/*
+ * Массив, хранящий в себе настройки по умолчанию линий графика на странице Dashboard'а.
+ */
+pmDashboard.model.defaultChartLineSettings = [
+ {
+ name: "all_tasks",
+ title: "All tasks",
+ color: "#1f77b4",
+ active: true
+ },
+ {
+ name: "ok",
+ title: "OK",
+ color: "#276900",
+ active: true
+ },
+ {
+ name: "error",
+ title: "ERROR",
+ color: "#333333",
+ active: true
+ },
+ {
+ name: "interrupted",
+ title: "INTERRUPTED",
+ color: "#9b97e4",
+ active: true
+ },
+ {
+ name: "delay",
+ title: "DELAY",
+ color: "#808419",
+ active: true
+ },
+ {
+ name: "offline",
+ title: "OFFLINE",
+ color: "#9e9e9e",
+ active: true
+ }
+]
+
+/**
+ * Функция полностью копирует настройки для линий графика.
+ * Подразумевается, что данная функция вызывается, когда пришел из API пустой JSON.
+ */
+pmDashboard.cloneChartLineSettingsTotally = function(){
+ pmDashboard.model.ChartLineSettings = JSON.parse(JSON.stringify(pmDashboard.model.defaultChartLineSettings));
+ return pmDashboard.model.ChartLineSettings;
+}
+
+/**
+ * Функция обновляет часть настроек линий графика, данные по которым пришли из API.
+ * Подразумевается, что данная функция вызывается, когда пришел из API непустой JSON.
+ */
+pmDashboard.cloneChartLineSettingsFromApi = function(data){
+ pmDashboard.model.ChartLineSettings = JSON.parse(JSON.stringify(pmDashboard.model.defaultChartLineSettings));
+ for(var i in pmDashboard.model.ChartLineSettings)
+ {
+ for(var j in data)
+ {
+ if(pmDashboard.model.ChartLineSettings[i].name == j)
+ {
+ for(var k in data[j])
+ {
+ if(pmDashboard.model.ChartLineSettings[i].hasOwnProperty(k))
+ {
+ pmDashboard.model.ChartLineSettings[i][k] = data[j][k];
+ }
+ }
+ }
+ }
+ }
+ return pmDashboard.model.ChartLineSettings;
+}
/**
* Функция полностью копирует настройки по умолчанию для виджетов.
@@ -223,22 +305,27 @@ pmDashboard.clonetWidgetsSettingsFromApiAndVerify = function(data){
}
/**
- * Функция проверяет необходимо ли посылать запрос к API для загрузки пользовательских настроек виджетов.
- * Если в модели отсутствует какой-либо виджет, либо у виджета отсутсвует какое-нибудь свойство,
+ * Функция проверяет необходимо ли посылать запрос к API для загрузки
+ * пользовательских настроек Dashboard'a (настройки виджетов, настройки линий графика).
+ * Например, если в модели отсутствует какой-либо виджет,
+ * либо у виджета отсутсвует какое-нибудь свойство,
* то запрос к API будет отправлен.
+ * @param {Object} defaultObj - объект с настройками по умолчанию
+ * @param {Object} currentObj - объект с текущими настройками
+ *
*/
-pmDashboard.checkWidgetSettings = function()
+pmDashboard.checkNecessityToLoadDashboardSettingsFromApi = function(defaultObj, currentObj)
{
- var bool1=false, bool2=false;
- for (var i in pmDashboard.model.defaultWidgets[0]){
- for (var j in pmDashboard.model.widgets[0])
+ var bool1 = false, bool2 = false;
+ for (var i in defaultObj){
+ for (var j in currentObj)
{
- if(pmDashboard.model.defaultWidgets[0][i].name==pmDashboard.model.widgets[0][j].name)
+ if(defaultObj[i].name == currentObj[j].name)
{
- for(var k in pmDashboard.model.defaultWidgets[0][i])
+ for(var k in defaultObj[i])
{
- if(!(k in pmDashboard.model.widgets[0][j])){
- bool1=true;
+ if(!(k in currentObj[j])){
+ bool1 = true;
}
}
@@ -246,9 +333,9 @@ pmDashboard.checkWidgetSettings = function()
}
}
- if(pmDashboard.model.defaultWidgets[0].length!=pmDashboard.model.widgets[0].length)
+ if(defaultObj.length != currentObj.length)
{
- bool2=true;
+ bool2 = true;
}
if(bool1 || bool2)
@@ -279,13 +366,15 @@ pmDashboard.getNewWidgetSettings = function(localObj)
}
/**
- *Функция заправшивает у API пользовательские настройки виджетов.
- *Если они есть(пришел не пустой объект), то данные настройки добавляются в local storage.
+ *Функция заправшивает у API пользовательские настройки Dashboard'a
+ *(настройки виджетов, настройки линий графика).
+ *Если они есть(пришел не пустой объект), то данные настройки добавляются в pmDashboard.model.
*/
-pmDashboard.getUserWidgetSettingsFromAPI = function()
+pmDashboard.getUserDashboardSettingsFromAPI = function()
{
var userId=window.my_user_id;
- if(pmDashboard.checkWidgetSettings())
+ if(pmDashboard.checkNecessityToLoadDashboardSettingsFromApi(pmDashboard.model.defaultWidgets[0], pmDashboard.model.widgets[0]) ||
+ pmDashboard.checkNecessityToLoadDashboardSettingsFromApi(pmDashboard.model.defaultChartLineSettings, pmDashboard.model.ChartLineSettings))
{
return spajs.ajax.Call({
url: hostname + "/api/v1/users/" + userId + "/settings/",
@@ -293,17 +382,24 @@ pmDashboard.getUserWidgetSettingsFromAPI = function()
contentType: 'application/json',
success: function (data)
{
- if ($.isEmptyObject(data))
+ if ($.isEmptyObject(data.widgetSettings))
{
- console.log("empty object");
pmDashboard.cloneDefaultWidgetsTotally();
}
else
{
- console.log("not empty object");
- pmDashboard.clonetWidgetsSettingsFromApiAndVerify(data);
+ pmDashboard.clonetWidgetsSettingsFromApiAndVerify(data.widgetSettings);
pmDashboard.model.widgets[0].sort(pmDashboard.sortCountWidget);
}
+
+ if ($.isEmptyObject(data.chartLineSettings))
+ {
+ pmDashboard.cloneChartLineSettingsTotally();
+ }
+ else
+ {
+ pmDashboard.cloneChartLineSettingsFromApi(data.chartLineSettings);
+ }
},
error: function (e)
{
@@ -316,26 +412,30 @@ pmDashboard.getUserWidgetSettingsFromAPI = function()
{
return false;
}
-
-
}
/**
- *Функция сохраняет в API пользовательские настройки виджетов.
+ *Функция сохраняет в API пользовательские настройки Dashboard'a
+ *(настройки виджетов, настройки линий графика).
*/
-pmDashboard.putUserWidgetSettingsToAPI = function()
+pmDashboard.putUserDashboardSettingsToAPI = function()
{
var userId=window.my_user_id;
- var dataToPut= {};
+ var widgetSettings= {};
for(var i in pmDashboard.model.widgets[0]){
var objName=pmDashboard.model.widgets[0][i].name;
- dataToPut[objName]=pmDashboard.getNewWidgetSettings(pmDashboard.model.widgets[0][i]);
+ widgetSettings[objName]=pmDashboard.getNewWidgetSettings(pmDashboard.model.widgets[0][i]);
+ }
+ var chartLineSettings = {};
+ for(var i in pmDashboard.model.ChartLineSettings){
+ var objName=pmDashboard.model.ChartLineSettings[i].name;
+ chartLineSettings[objName]={active: pmDashboard.model.ChartLineSettings[i].active};
}
return spajs.ajax.Call({
url: hostname + "/api/v1/users/" + userId + "/settings/",
type: "POST",
contentType: 'application/json',
- data: JSON.stringify(dataToPut),
+ data: JSON.stringify({widgetSettings:widgetSettings, chartLineSettings:chartLineSettings}),
success: function (data)
{
//console.log("Data was posted");
@@ -371,7 +471,7 @@ pmDashboard.setNewWidgetActiveValue = function(thisButton)
pmDashboard.model.widgets[0][i].active=false;
}
}
- pmDashboard.putUserWidgetSettingsToAPI();
+ pmDashboard.putUserDashboardSettingsToAPI();
}
/**
@@ -400,19 +500,20 @@ pmDashboard.setNewWidgetCollapseValue = function(thisButton)
}
}
}
- pmDashboard.putUserWidgetSettingsToAPI();
+ pmDashboard.putUserDashboardSettingsToAPI();
}
/**
- *Функция, сохраняющая настройки виджетов, внесенные в форму настроек виджетов Dashboard'a.
+ *Функция, сохраняющая настройки виджетов/линий графика,
+ *внесенные в таблицу редактирования настроек Dashboard'a.
*/
-pmDashboard.saveWigdetsOptions = function()
+pmDashboard.getOptionsFromTable = function(table_id, pmDashboard_obj)
{
- var modalTable=document.getElementById("modal-table");
+ var modalTable=document.getElementById(table_id);
var modalTableTr=modalTable.getElementsByTagName("tr");
for(var i=1; i
$.notify("Error in field ansible_ssh_private_key_file invalid value", "error");
- //pmInventories.showHostVarsModal({group: 'all', name: val.name});
- var scroll_el = "#imported_hosts";
- if ($(scroll_el).length != 0) {
- $('html, body').animate({ scrollTop: $(scroll_el).offset().top }, 700);
- }
+ pmInventories.showHostVarsModal(i);
def2.reject()
return def2.promise();
}
@@ -1676,11 +1680,7 @@ pmInventories.importInventory = function(inventory)
{
//
$.notify("Error in field ansible_ssh_private_key_file invalid value", "error");
- //pmInventories.showGroupVarsModal({name:i});
- var scroll_el = "#imported_groups";
- if ($(scroll_el).length != 0) {
- $('html, body').animate({ scrollTop: $(scroll_el).offset().top }, 700);
- }
+ pmInventories.showGroupVarsModal(i);
def2.reject()
return def2.promise();
}
@@ -2196,6 +2196,18 @@ pmInventories.copyItem = function(item_id)
}
+pmInventories.importedHostsIsEmpty = function(hosts_arr)
+{
+ for (var i in hosts_arr)
+ {
+ if(hosts_arr[i] !== undefined)
+ {
+ return false;
+ }
+ }
+ return true;
+}
+
pmInventories.model.page_list = {
@@ -2705,14 +2717,10 @@ pmInventories.filed.inventoriesAutocomplete.getValue = function(pmObj, filed)
var inventory = $("#inventories-autocomplete").val()
if($("#inventory-source").val() != 'db')
{
- inventory = $("#inventories-file").val()
- if(!/^\.\//.test(inventory))
- {
- inventory = trim("./"+inventory)
- }
+ inventory = $("#inventories-file").val();
+ inventory = trim(inventory);
}
-
return inventory;
}
diff --git a/polemarch/static/js/pmItems.js b/polemarch/static/js/pmItems.js
index 8d23240b..a6c546c8 100644
--- a/polemarch/static/js/pmItems.js
+++ b/polemarch/static/js/pmItems.js
@@ -709,7 +709,7 @@ pmItems.deleteRows = function (elements)
*/
pmItems.deleteSelected = function ()
{
- if ($.inArray(this.model.bulk_name, ['history', 'host', 'group', 'inventory', 'project', 'periodictask', 'template']) != -1)
+ if ($.inArray(this.model.bulk_name, ['history', 'host', 'group', 'inventory', 'project', 'periodictask', 'template', 'user', 'team', 'hook']) != -1)
{
var thisObj = this;
var deleteBulk = []
@@ -724,7 +724,6 @@ pmItems.deleteSelected = function ()
})
}
}
-
return $.when(spajs.ajax.Call({
url: hostname + "/api/v1/_bulk/",
type: "POST",
diff --git a/polemarch/static/js/pmModuleTemplates.js b/polemarch/static/js/pmModuleTemplates.js
index 82f55da8..bd5ff514 100644
--- a/polemarch/static/js/pmModuleTemplates.js
+++ b/polemarch/static/js/pmModuleTemplates.js
@@ -85,11 +85,20 @@ pmModuleTemplates.model.page_item = {
{
class:'btn btn-info',
function:function(item_id){
- return "spajs.showLoader("+this.model.className+".setNewOption("+item_id+")); return false;"
+ return "spajs.open({ menuId:'template/"+this.model.kind+"/"+item_id+"/options'}); return false;"
},
- title:'Create new option',
+ title:'Options',
link:function(){ return '#'},
- help:'Create new option'
+ help:'Options of this template'
+ },
+ {
+ class:'btn btn-info',
+ function:function(item_id){
+ return "spajs.open({ menuId:'template/"+this.model.kind+"/"+item_id+"/periodic-tasks'}); return false;"
+ },
+ title:'Periodic tasks',
+ link:function(){ return '#'},
+ help:'Periodic tasks linked to this template'
},
{
class:'btn btn-info',
@@ -115,9 +124,6 @@ pmModuleTemplates.model.page_item = {
sections:[
function(section, item_id){
return jsonEditor.editor(pmModuleTemplates.model.items[item_id].data.vars, {block:'module', title1:'Arguments', title2:'Adding new argument', select2:true});
- },
- function(section, item_id){
- return spajs.just.render("options_section", {item_id:item_id})
}
],
title: function(item_id){
@@ -237,7 +243,7 @@ pmModuleTemplates.model.page_item_option = {
{
class:'btn btn-danger danger-right',
function:function(item_id){
- return 'spajs.showLoader(pmModuleTemplates.removeOption('+item_id+')); return false;'
+ return 'spajs.showLoader(pmModuleTemplates.removeOption('+item_id+', "'+pmModuleTemplates.model.items[item_id].option_name+'")); return false;'
},
title:' Remove',
link:function(){ return '#'},
@@ -301,7 +307,7 @@ pmModuleTemplates.saveAndExecute = function(item_id)
*/
pmModuleTemplates.setNewOption = function(item_id)
{
- return spajs.openURL(window.location.href+"/new-option");
+ return spajs.open({ menuId:"template/"+this.model.kind+"/"+item_id+"/new-option"});
}
/**
@@ -534,10 +540,10 @@ pmModuleTemplates.showOptionPage = function(holder, menuInfo, data)
/**
*Функция удаляет опцию.
*/
-pmModuleTemplates.removeOption = function(item_id)
+pmModuleTemplates.removeOption = function(item_id, option_name)
{
var def = new $.Deferred();
- var optionName=pmModuleTemplates.model.items[item_id].option_name;
+ var optionName=option_name;
delete pmModuleTemplates.model.items[item_id].options[optionName];
var dataToAdd1={options:{}};
dataToAdd1['options']=pmModuleTemplates.model.items[item_id].options;
@@ -551,88 +557,24 @@ pmModuleTemplates.removeOption = function(item_id)
{
thisObj.model.items[item_id] = data
$.notify('Option "'+optionName+'" was successfully deleted', "success");
- $.when(spajs.open({ menuId:"template/"+thisObj.model.kind+"/"+data.id})).always(function(){
- def.resolve();
- });
-
- },
- error: function (e)
- {
- def.reject(e)
- polemarch.showErrors(e.responseJSON)
- }
- });
- return def.promise();
-}
-
-/**
- *Функция добавляет на страницу секцию для удаления/добавления опций на страницу шаблона.
- */
-pmModuleTemplates.showExistingOptionsToEdit = function (item_id) {
- if(!item_id)
- {
- throw "Error in pmInventories.showExistingOptionsToEdit with item_id = `" + item_id + "`"
- }
-
- $("#add_existing_options_to_module_template").remove();
- $(".content").appendTpl(spajs.just.render('add_existing_options_to_module_template', {item_id:item_id}))
- var scroll_el = "#add_existing_options_to_module_template";
- if ($(scroll_el).length != 0) {
- $('html, body').animate({ scrollTop: $(scroll_el).offset().top }, 1000);
- }
- $("#polemarch-model-items-select").select2({ width: '100%' });
-
-}
-
-/**
- *Функция сохраняет изменения внесенные в секции для удаления/добавления опций на странице шаблона.
- */
-pmModuleTemplates.setOptionList = function(item_id, option_list)
-{
- var thisObj=this;
-
- if(!item_id)
- {
- throw "Error in pmModuleTemplates.setOptionList with item_id = `" + item_id + "`"
- }
-
- if(!option_list)
- {
- var options={};
- }
- else
- {
- var options=pmModuleTemplates.model.items[item_id].options;
- for(var i in options)
- {
- var bool=false;
- for(var j in option_list)
+ if(/options/.test(window.location.href) == false)
{
- if(i==option_list[j])
- {
- bool=true;
- }
+ $.when(spajs.open({ menuId:"template/"+thisObj.model.kind+"/"+data.id+"/options"})).always(function(){
+ def.resolve();
+ });
}
- if(bool==false)
+ else
{
- delete options[i];
+ def.resolve();
}
- }
- }
- return spajs.ajax.Call({
- url: "/api/v1/templates/"+item_id+"/",
- type: "PATCH",
- contentType:'application/json',
- data:JSON.stringify({options:options}),
- success: function(data)
- {
- spajs.openURL(window.location.href);
},
- error:function(e)
+ error: function (e)
{
+ def.reject(e)
polemarch.showErrors(e.responseJSON)
}
});
+ return def.promise();
}
@@ -642,19 +584,27 @@ pmModuleTemplates.showItem = function(holder, menuInfo, data)
var item_id = data.reg[1];
var def = new $.Deferred();
var thisObj = this;
- $.when(pmInventories.loadAllItems(), pmProjects.loadAllItems(), pmModuleTemplates.loadItem(item_id)).done(function()
+ $.when(pmInventories.loadAllItems(), pmProjects.loadAllItems(),
+ pmModuleTemplates.loadItem(item_id)).done(function()
{
- $.when(pmModuleTemplates.selectInventory(pmModuleTemplates.model.items[item_id].data.inventory)).always(function()
+ $.when(pmProjects.loadItem(thisObj.model.items[item_id].data.project)).done(function ()
{
- var tpl = thisObj.model.name+'_module_page'
- if(!spajs.just.isTplExists(tpl))
+ thisObj.model.selectedProject = thisObj.model.items[item_id].data.project;
+ $.when(pmModuleTemplates.selectInventory(pmModuleTemplates.model.items[item_id].data.inventory)).always(function()
{
- tpl = 'items_page'
- }
+ var tpl = thisObj.model.name+'_module_page'
+ if(!spajs.just.isTplExists(tpl))
+ {
+ tpl = 'items_page'
+ }
- $(holder).insertTpl(spajs.just.render(tpl, {item_id:item_id, pmObj:thisObj, opt:{}}))
- def.resolve();
+ $(holder).insertTpl(spajs.just.render(tpl, {item_id:item_id, pmObj:thisObj, opt:{}}))
+ def.resolve();
+ });
+ }).fail(function () {
+ $.notify("Error with loading of project data");
});
+
}).fail(function(e)
{
def.reject(e);
@@ -670,12 +620,33 @@ pmModuleTemplates.showNewItemPage = function(holder, menuInfo, data)
var thisObj = this;
$.when(pmInventories.loadAllItems(), pmProjects.loadAllItems()).done(function()
{
- $(holder).insertTpl(spajs.just.render(thisObj.model.name+'_new_module_page', {}))
+ if(pmProjects.model.itemslist.results.length != 0)
+ {
+ $.when(pmProjects.loadItem(pmProjects.model.itemslist.results[0].id)).done(function()
+ {
+ //for P+
+ thisObj.model.selectedProject = pmProjects.model.itemslist.results[0].id;
+ //
+ $(holder).insertTpl(spajs.just.render(thisObj.model.name+'_new_module_page', {}))
- $("#inventories-autocomplete").select2({ width: '100%' });
- $("#projects-autocomplete").select2({ width: '100%' });
+ $("#inventories-autocomplete").select2({ width: '100%' });
+ $("#projects-autocomplete").select2({ width: '100%' });
+
+ def.resolve();
+ }).fail(function(){
+ $.notify("Error with loading of project data");
+ });
+ }
+ else
+ {
+ $(holder).insertTpl(spajs.just.render(thisObj.model.name+'_new_module_page', {}))
+
+ $("#inventories-autocomplete").select2({ width: '100%' });
+ $("#projects-autocomplete").select2({ width: '100%' });
+
+ def.resolve();
+ }
- def.resolve();
}).fail(function(e)
{
def.reject(e);
@@ -759,6 +730,280 @@ pmModuleTemplates.addItem = function()
return def.promise();
}
+/**
+ * Функция предназначена для загрузки всех периодических тасок,
+ * ссылающихся на данный шаблон.
+ * @param number template_id - id of template
+ */
+pmModuleTemplates.loadLinkedPeriodicTasks = function(template_id)
+{
+ var thisObj = this;
+ return spajs.ajax.Call({
+ url: hostname + "/api/v1/periodic-tasks/",
+ type: "GET",
+ contentType: 'application/json',
+ data: "template="+template_id,
+ success: function (data)
+ {
+ // thisObj.model.linkedPeriodicTask = [];
+ // thisObj.model.linkedPeriodicTasks = data.results;
+ pmPeriodicTasks.model.itemslist = data
+ pmPeriodicTasks.model.items = {}
+
+ for (var i in data.results)
+ {
+ var val = pmPeriodicTasks.afterItemLoad(data.results[i])
+ pmPeriodicTasks.model.items.justWatch(val.id);
+ pmPeriodicTasks.model.items[val.id] = mergeDeep(thisObj.model.items[val.id], val)
+ }
+ },
+ error: function (e)
+ {
+ console.warn(e)
+ polemarch.showErrors(e)
+ }
+ });
+}
+
+/**
+ * Функция открывает страницу со списком опций шаблона
+ */
+pmModuleTemplates.showOptionsList = function (holder, menuInfo, data)
+{
+ setActiveMenuLi();
+ var thisObj = this;
+ var offset = 0
+ var limit = thisObj.pageSize;
+ if (data.reg && data.reg[1] > 0)
+ {
+ offset = thisObj.pageSize * (data.reg[1] - 1);
+ }
+ return $.when(thisObj.loadItem(data.reg[1])).done(function ()
+ {
+ var tpl = 'template_options_list'
+
+ $(holder).insertTpl(spajs.just.render(tpl, {query: "", pmObj: thisObj, item_id:data.reg[1], opt: {}}))
+ }).fail(function ()
+ {
+ $.notify("", "error");
+ })
+}
+
+/**
+ * Функция выделяет/снимает выделение с опций в таблице списка опций.
+ * @param {array} elements - массив выделенных элементов
+ * @param {boolean} mode - true - добавить выделение, false - снять выделение
+ * @param {string} div_id - id блока, в котором находятся данные элементы
+ */
+pmModuleTemplates.toggleSelectAllOptions = function (elements, mode, div_id)
+{
+ for (var i = 0; i < elements.length; i++)
+ {
+ if($(elements[i]).hasClass('item-row'))
+ {
+ $(elements[i]).toggleClass('selected', mode);
+ }
+ }
+ pmModuleTemplates.countSelectedOptions(div_id);
+}
+
+/**
+ * Функция выделяет/снимает выделение с одного конкретного элемента в определенной таблице.
+ * В данном случае в таблице со списком опций.
+ * @param {object} thisEl - конкретный элемент
+ * @param {string} div_id - id блока, в котором находится данный элемент
+ */
+pmModuleTemplates.toggleSelectOption = function (thisEl, div_id)
+{
+ $(thisEl).parent().toggleClass('selected');
+ pmModuleTemplates.countSelectedOptions(div_id);
+}
+
+/**
+ * Функция подсчитывает количество выделенных элементов в определенной таблице элементов.
+ * И запоминает данное число в pmModuleTemplates.model.selectedOptionsCount,
+ * а сами элементы в pmModuleTemplates.model.selectedOptions.
+ * В зависимости от нового значения pmModuleTemplates.model.selectedOptionsCount
+ * часть кнопок отображается либо скрывается.
+ * @param {string} div_id - id блока, в котором находятся данные элементы
+ */
+pmModuleTemplates.countSelectedOptions = function (div_id)
+{
+ var elements=$("#"+div_id+"_table tr");
+ var count=0;
+ pmModuleTemplates.model.selectedOptions = [];
+ for (var i = 0; i < elements.length; i++)
+ {
+ if($(elements[i]).hasClass('item-row') && $(elements[i]).hasClass('selected'))
+ {
+ count+=1;
+ pmModuleTemplates.model.selectedOptions.push($(elements[i]).attr('data-id'));
+ }
+ }
+
+ if(count==0)
+ {
+ $($("#"+div_id+" .actions_button")[0]).addClass("hide");
+ }
+ else
+ {
+ $($("#"+div_id+" .actions_button")[0]).removeClass("hide");
+ }
+ pmModuleTemplates.model.selectedOptionsCount=count;
+}
+
+/**
+ *Функция удаляет все выделенные опции.
+ */
+pmModuleTemplates.removeSelectedOptions = function(item_id, option_names)
+{
+ var def = new $.Deferred();
+ for(var i in option_names)
+ {
+ var optionName=option_names[i];
+ delete pmModuleTemplates.model.items[item_id].options[optionName];
+ }
+ var dataToAdd1={options:{}};
+ dataToAdd1['options']=pmModuleTemplates.model.items[item_id].options;
+ var thisObj = this;
+ spajs.ajax.Call({
+ url: hostname + "/api/v1/" + this.model.name + "/" + item_id + "/",
+ type: "PATCH",
+ contentType: 'application/json',
+ data: JSON.stringify(dataToAdd1),
+ success: function (data)
+ {
+ thisObj.model.items[item_id] = data
+ $.notify('Options were successfully deleted', "success");
+ def.resolve();
+ },
+ error: function (e)
+ {
+ def.reject(e)
+ polemarch.showErrors(e.responseJSON)
+ }
+ });
+ return def.promise();
+}
+
+/**
+ *Функция открывает список периодических тасок, созданных на основе данного шаблона.
+ */
+pmModuleTemplates.showPeriodicTasksList = function (holder, menuInfo, data)
+{
+ setActiveMenuLi();
+ var thisObj = this;
+ var offset = 0
+ var limit = thisObj.pageSize;
+ if (data.reg && data.reg[1] > 0)
+ {
+ offset = thisObj.pageSize * (data.reg[1] - 1);
+ }
+ var template_id = data.reg[1];
+ return $.when(thisObj.loadItem(template_id), thisObj.loadLinkedPeriodicTasks(template_id)).done(function ()
+ {
+ var tpl = 'linked-to-template-periodic-tasks_list';
+ var project_id = thisObj.model.items[template_id].data.project;
+
+ $(holder).insertTpl(spajs.just.render(tpl, {query: "", pmObj: thisObj, project_id:project_id, item_id:template_id, opt: {}}))
+ }).fail(function ()
+ {
+ $.notify("", "error");
+ })
+}
+
+/**
+ *Функция открывает страницу создания новой периодической таски для шаблона.
+ */
+pmModuleTemplates.showNewPeriodicTaskFromTemplate = function (holder, menuInfo, data)
+{
+ var def = new $.Deferred();
+ var thisObj = this;
+ var item_id = data.reg[1]
+ $.when(thisObj.loadItem(item_id), pmInventories.loadAllItems()).done(function()
+ {
+ var project_id = thisObj.model.items[item_id].data.project
+ pmPeriodicTasks.model.newitem = {type:'INTERVAL', kind:'TEMPLATE'}
+ var tpl = 'from-template-periodic-tasks_new_page'
+ if(!spajs.just.isTplExists(tpl))
+ {
+ tpl = 'items_page'
+ }
+
+ $(holder).insertTpl(spajs.just.render(tpl, {item_id:item_id, project_id:project_id, pmObj:thisObj, opt:{}}))
+ def.resolve();
+ }).fail(function(e)
+ {
+ def.reject(e);
+ })
+
+ return def.promise()
+
+}
+
+
+/**
+ * Функция рендерит шаблон для поля поиска на странице списка опций шаблона.
+ */
+pmModuleTemplates.searchFiledForTemplateOptions = function (options)
+{
+ options.className = this.model.className;
+ this.model.searchAdditionalData = options
+ return spajs.just.render('searchFiledForTemplateOptions', {opt: options});
+}
+
+/**
+ * Функция для поиска опций на странице списка опций шаблона.
+ */
+pmModuleTemplates.searchTemplateOptions = function (query, options)
+{
+ if (this.isEmptySearchQuery(query))
+ {
+ return spajs.open({menuId: 'template/Module/' + options.template_id +'/options', reopen: true});
+ }
+
+ return spajs.open({menuId: 'template/Module/' + options.template_id +'/options' + '/search/' + this.searchObjectToString(trim(query)), reopen: true});
+}
+
+/**
+ * Функция показывает результаты поиска опций шаблона.
+ */
+pmModuleTemplates.showOptionsSearchResult = function (holder, menuInfo, data)
+{
+
+ setActiveMenuLi();
+ var thisObj = this;
+ var template_id = data.reg[1];
+ var search = this.searchStringToObject(decodeURIComponent(data.reg[2]))
+
+ return $.when(thisObj.loadItem(data.reg[1])).done(function ()
+ {
+ var unvalidSearchOptions = [];
+ for (var i in thisObj.model.items[template_id].options_list)
+ {
+ var option_name = thisObj.model.items[template_id].options_list[i];
+ if(option_name.match(search.name) == null)
+ {
+ unvalidSearchOptions.push(option_name);
+ delete thisObj.model.items[template_id].options_list[i];
+ }
+ }
+
+ for(var i in unvalidSearchOptions)
+ {
+ delete thisObj.model.items[template_id].options[unvalidSearchOptions[i]];
+ }
+
+ var tpl = 'template_options_list';
+
+ $(holder).insertTpl(spajs.just.render(tpl, {query:decodeURIComponent(search.name), pmObj: thisObj, item_id:template_id, opt: {}}))
+ }).fail(function ()
+ {
+ $.notify("", "error");
+ })
+}
+
+
tabSignal.connect("polemarch.start", function()
{
spajs.addMenu({
@@ -773,6 +1018,21 @@ tabSignal.connect("polemarch.start", function()
onOpen:function(holder, menuInfo, data){return pmModuleTemplates.showOptionPage(holder, menuInfo, data);},
})
+ spajs.addMenu({
+ id:"Module-options",
+ urlregexp:[/^template\/Module\/([0-9]+)\/options$/, /^templates\/Module\/([0-9]+)\/options$/],
+ onOpen:function(holder, menuInfo, data){return pmModuleTemplates.showOptionsList(holder, menuInfo, data);}
+ })
+
+ spajs.addMenu({
+ id:"Module-options-search",
+ urlregexp:[/^template\/Module\/([0-9]+)\/options\/search\/([A-z0-9 %\-.:,=]+)$/,
+ /^templates\/Module\/([0-9]+)\/options\/search\/([A-z0-9 %\-.:,=]+)$/,
+ /^template\/Module\/([0-9]+)\/options\/search\/([A-z0-9 %\-.:,=]+)$/,
+ /^templates\/Module\/([0-9]+)\/options\/search\/([A-z0-9 %\-.:,=]+)$/],
+ onOpen:function(holder, menuInfo, data){return pmModuleTemplates.showOptionsSearchResult(holder, menuInfo, data);}
+ })
+
spajs.addMenu({
id:"Module-item",
urlregexp:[/^template\/Module\/([0-9]+)$/, /^templates\/Module\/([0-9]+)$/],
@@ -785,4 +1045,28 @@ tabSignal.connect("polemarch.start", function()
onOpen:function(holder, menuInfo, data){return pmModuleTemplates.showNewItemPage(holder, menuInfo, data);}
})
+ spajs.addMenu({
+ id:"Module-periodic-tasks",
+ urlregexp:[/^template\/Module\/([0-9]+)\/periodic-tasks/, /^templates\/Module\/([0-9]+)\/periodic-tasks/],
+ onOpen:function(holder, menuInfo, data){return pmModuleTemplates.showPeriodicTasksList(holder, menuInfo, data);}
+ })
+
+ spajs.addMenu({
+ id:"Module-new-periodic-task",
+ urlregexp:[/^template\/Module\/([0-9]+)\/new-periodic-task/, /^templates\/Module\/([0-9]+)\/new-periodic-task/],
+ onOpen:function(holder, menuInfo, data){return pmModuleTemplates.showNewPeriodicTaskFromTemplate(holder, menuInfo, data);}
+ })
+
+ spajs.addMenu({
+ id:"Module-periodic-task",
+ urlregexp:[/^template\/Module\/([0-9]+)\/periodic-task\/([0-9]+)/, /^templates\/Module\/([0-9]+)\/periodic-task\/([0-9]+)/],
+ onOpen:function(holder, menuInfo, data){return pmPeriodicTasks.showPeriodicTaskPageFromTemplate(holder, menuInfo, data);}
+ })
+
+ spajs.addMenu({
+ id:"Module-periodic-tasks-search",
+ urlregexp:[/^template\/Module\/([0-9]+)\/periodic-tasks\/search\/([A-z0-9 %\-.:,=]+)$/, /^template\/Module\/([0-9]+)\/periodic-tasks\/search\/([A-z0-9 %\-.:,=]+)\/page\/([0-9]+)$/],
+ onOpen:function(holder, menuInfo, data){return pmPeriodicTasks.showSearchResultsFromTemplate(holder, menuInfo, data);}
+ })
+
})
diff --git a/polemarch/static/js/pmPeriodicTasks.js b/polemarch/static/js/pmPeriodicTasks.js
index 75af2327..19b0b2d1 100644
--- a/polemarch/static/js/pmPeriodicTasks.js
+++ b/polemarch/static/js/pmPeriodicTasks.js
@@ -9,6 +9,50 @@ pmPeriodicTasks.model.className = "pmPeriodicTasks"
pmPeriodicTasks.inventoriesAutocompletefiled = new pmInventories.filed.inventoriesAutocomplete()
+/**
+ * Для ввода шаблона
+ * @type Object
+ */
+pmPeriodicTasks.filed.projectTemplatesAutocomplete = inheritance(filedsLib.filed.simpleText)
+pmPeriodicTasks.filed.projectTemplatesAutocomplete.type = 'projectTemplatesAutocomplete'
+pmPeriodicTasks.filed.projectTemplatesAutocomplete.getValue = function(pmObj, filed)
+{
+ var template = $("#templates-autocomplete").val()
+
+ return template;
+}
+
+/**
+ * Функция для рендера поля
+ * @type Object
+ */
+pmPeriodicTasks.filed.projectTemplatesAutocomplete.render = function(pmObj, filed, item_id, opt)
+{
+ var html = spajs.just.render('filed_type_'+this.type, {pmObj:pmObj, filed:filed, item_id:item_id, filedObj:this, opt:opt})
+ return spajs.just.onInsert(html, function()
+ {
+ $("#templates-autocomplete").select2({ width: '100%' });
+
+ if(filed.onchange && item_id)
+ {
+ filed.onchange({value:filed.getFiledValue.apply(pmObj, [item_id])})
+ }
+ else if(filed.onchange)
+ {
+ if(pmTasksTemplates.model.itemslist.results[0])
+ {
+ filed.onchange({value:pmTasksTemplates.model.itemslist.results[0].id})
+ }
+ else
+ {
+ filed.onchange({value:""})
+ }
+ }
+ });
+}
+
+pmPeriodicTasks.projectTemplatesAutocompletefiled = new pmPeriodicTasks.filed.projectTemplatesAutocomplete();
+
pmPeriodicTasks.copyAndEdit = function(item_id)
{
@@ -64,7 +108,7 @@ pmPeriodicTasks.copyItem = function(item_id)
type: "POST",
contentType:'application/json',
data: JSON.stringify(data),
- success: function(data)
+ success: function(data)
{
thisObj.model.items[data.id] = data
def.resolve(data.id)
@@ -128,15 +172,37 @@ pmPeriodicTasks.deleteItem = function(item_id, force)
$.when(this.loadItem(item_id)).done(function()
{
var project_id = pmPeriodicTasks.model.items[item_id].project;
+ var template_id = pmPeriodicTasks.model.items[item_id].template;
$.when(thisObj.deleteItemQuery(item_id)).done(function(data)
{
- $.when(spajs.open({ menuId: "project/"+project_id+"/periodic-tasks"})).done(function()
+ if(/template/.test(window.location.href) || /templates/.test(window.location.href))
{
- def.resolve()
- }).fail(function(e){
- def.reject(e);
- polemarch.showErrors(e.responseJSON)
- })
+ if(/Task/.test(window.location.href))
+ {
+ var template_kind = "Task";
+ }
+ else
+ {
+ var template_kind = "Module";
+ }
+ $.when(spajs.open({ menuId:"template/"+template_kind + "/" + template_id + "/periodic-tasks"})).done(function(){
+ def.resolve()
+ }).fail(function(e){
+ def.reject(e);
+ polemarch.showErrors(e.responseJSON)
+ })
+ }
+ else
+ {
+ $.when(spajs.open({ menuId:"project/"+project_id+"/periodic-tasks"})).done(function(){
+ def.resolve()
+ }).fail(function(e){
+ def.reject(e);
+ polemarch.showErrors(e.responseJSON)
+ })
+ }
+
+
}).fail(function(e){
def.reject(e);
polemarch.showErrors(e.responseJSON)
@@ -157,7 +223,7 @@ pmPeriodicTasks.execute = function(project_id, item_id)
type: "POST",
data:JSON.stringify({}),
contentType:'application/json',
- success: function(data)
+ success: function(data)
{
$.notify("Started", "success");
if(data && data.history_id)
@@ -234,7 +300,7 @@ pmPeriodicTasks.showList = function(holder, menuInfo, data)
}
var project_id = data.reg[1];
- return $.when(this.searchItems(project_id, 'project'), pmProjects.loadItem(project_id)).done(function()
+ return $.when(this.searchItems(project_id, 'project'), pmProjects.loadItem(project_id), pmTasksTemplates.loadAllItemsFromProject(project_id)).done(function()
{
$(holder).insertTpl(spajs.just.render(thisObj.model.name+'_list', {query:"", project_id:project_id}))
}).fail(function()
@@ -247,10 +313,27 @@ pmPeriodicTasks.search = function(query, options)
{
if(this.isEmptySearchQuery(query))
{
- return spajs.open({ menuId:'project/' + options.project_id +"/" + this.model.name, reopen:true});
+ if(options.template_kind === undefined)
+ {
+ return spajs.open({ menuId:'project/' + options.project_id +"/" + this.model.name, reopen:true});
+ }
+ else
+ {
+ return spajs.open({ menuId:'template/'+ options.template_kind+ "/" + options.template_id +"/" + this.model.name, reopen:true});
+ }
+
+ }
+
+ if(options.template_kind === undefined)
+ {
+ return spajs.open({ menuId:'project/' + options.project_id +"/" + this.model.name+"/search/"+this.searchObjectToString(trim(query)), reopen:true});
+
+ }
+ else
+ {
+ return spajs.open({ menuId:'template/'+ options.template_kind+ "/" + options.template_id +"/" + this.model.name + "/search/"+this.searchObjectToString(trim(query)), reopen:true});
}
- return spajs.open({ menuId:'project/' + options.project_id +"/" + this.model.name+"/search/"+this.searchObjectToString(trim(query)), reopen:true});
}
pmPeriodicTasks.showSearchResults = function(holder, menuInfo, data)
@@ -276,7 +359,7 @@ pmPeriodicTasks.showNewItemPage = function(holder, menuInfo, data)
setActiveMenuLi();
var project_id = data.reg[1];
var thisObj = this;
- return $.when(pmTasks.searchItems(project_id, "project"), pmProjects.loadItem(project_id), pmInventories.loadAllItems()).done(function()
+ return $.when(pmTasks.searchItems(project_id, "project"), pmProjects.loadItem(project_id), pmInventories.loadAllItems(), pmTasksTemplates.loadAllItemsFromProject(project_id)).done(function()
{
thisObj.model.newitem = {type:'INTERVAL', kind:'PLAYBOOK'}
$(holder).insertTpl(spajs.just.render(thisObj.model.name+'_new_page', {project_id:project_id}))
@@ -330,6 +413,12 @@ pmPeriodicTasks.filed.selectInventoryKindPlaybookGroupModuleAndTime.getValue = f
return '';
}
+pmPeriodicTasks.filed.selectTemplateFromProjectToPT = inheritance(filedsLib.filed.simpleText)
+pmPeriodicTasks.filed.selectTemplateFromProjectToPT.type = 'selectTemplateFromProjectToPT'
+pmPeriodicTasks.filed.selectTemplateFromProjectToPT.getValue = function(pmObj, filed){
+ return '';
+}
+
pmPeriodicTasks.model.page_list = {
short_title: 'Periodic tasks',
}
@@ -439,16 +528,171 @@ pmPeriodicTasks.model.page_item = {
data.project = opt.project_id
data.type = $("#periodic-tasks_"+item_id+"_type").val()
- data.inventory = pmPeriodicTasks.inventoriesAutocompletefiled.getValue()
data.kind = $("#periodic-tasks_"+item_id+"_kind").val()
- if(!data.inventory)
+ if(data.kind == "MODULE")
{
- $.notify("Invalid field `inventory` ", "error");
- return false;
+ data.mode = moduleArgsEditor.getSelectedModuleName()
+ if(!data.mode)
+ {
+ $.notify("Module name is empty", "error");
+ return false;
+ }
+ }
+ else if(data.kind == "PLAYBOOK")
+ {
+ data.mode = $("#periodic-tasks_"+item_id+"_playbook").val()
+ if(!data.mode)
+ {
+ $.notify("Playbook name is empty", "error");
+ return false;
+ }
+ }
+ else
+ {
+ var template_val = pmPeriodicTasks.projectTemplatesAutocompletefiled.getValue();
+ var template_val_arr = template_val.split("/");
+ data.template = template_val_arr[0];
+ if(template_val_arr[1] !== undefined)
+ {
+ data.template_opt = template_val_arr[1];
+ }
+ else
+ {
+ data.template_opt = null;
+ }
+
+ }
+
+ if(data.kind != "TEMPLATE")
+ {
+ data.inventory = pmPeriodicTasks.inventoriesAutocompletefiled.getValue()
+
+ if(!data.inventory)
+ {
+ $.notify("Invalid field `inventory` ", "error");
+ return false;
+ }
}
+ if(data.type == "CRONTAB")
+ {
+ data.schedule = crontabEditor.getCronString()
+ }
+ else
+ {
+ data.schedule = $("#periodic-tasks_"+item_id+"_schedule_INTERVAL").val()
+ if(!data.schedule)
+ {
+ $.notify("Invalid field `Interval schedule` ", "error");
+ return;
+ }
+ }
+
+ data.vars = jsonEditor.jsonEditorGetValues(data.kind)
+
+ if(data.kind == "MODULE")
+ {
+ data.vars.group = pmGroups.getGroupsAutocompleteValue()
+ data.vars.args = moduleArgsEditor.getModuleArgs();
+ }
+ return data;
+ },
+}
+
+pmPeriodicTasks.model.page_item_from_template = {
+ buttons:[
+ {
+ class:'btn btn-primary',
+ function:function(item_id, opt){ return 'spajs.showLoader('+this.model.className+'.updateItem('+item_id+', {project_id:'+opt.project_id+'})); return false;'},
+ title:'Save',
+ link:function(){ return '#'},
+ },
+ {
+ class:'btn btn-warning',
+ function:function(item_id, opt){
+ return "spajs.showLoader(pmPeriodicTasks.execute("+opt.project_id+", "+item_id+")); return false;"
+ },
+ title:'Execute',
+ link:function(){ return '#'},
+ help:'Execute'
+ },
+ {
+ class:'btn btn-danger danger-right',
+ function:function(item_id){ return 'spajs.showLoader('+this.model.className+'.deleteItem('+item_id+')); return false;'},
+ title:' Remove',
+ link:function(){ return '#'},
+ },
+ ],
+ sections:[
+
+ ],
+ title: function(item_id){
+ return "Periodic task "+this.model.items[item_id].justText('name')
+ },
+ back_link: function(item_id, opt){
+ return polemarch.opt.host + "/?template/" + opt.template_kind + "/" + opt.template_id + "/" + this.model.name;
+ },
+ short_title: function(item_id){
+ return this.model.items[item_id].justText('name', function(v){return v.slice(0, 20)})
+ },
+ fileds:[
+ [
+ {
+ filed: new filedsLib.filed.text(),
+ title:'Name',
+ name:'name',
+ placeholder:'Enter template name',
+ validator:function(value){
+ return filedsLib.validator.notEmpty(value, 'Name')
+ },
+ fast_validator:function(value){ return value != '' && value}
+ },
+ {
+ filed: new filedsLib.filed.boolean(),
+ title:'Save in history',
+ name:'save_result',
+ help:'Save result of task in history',
+ },
+ {
+ filed: new filedsLib.filed.boolean(),
+ title:'Enabled',
+ name:'enabled',
+ help:'',
+ },
+ ],
+ [
+ {
+ filed: new pmPeriodicTasks.filed.selectTemplateFromProjectToPT(),
+ name:'template',
+ },
+ ],
+ [
+ {
+ filed: new filedsLib.filed.textarea(),
+ title:'Notes',
+ name:'notes',
+ placeholder:'Not required field, just for your notes'
+ },
+ ]
+ ],
+ onUpdate:function(result)
+ {
+ return true;
+ },
+ onBeforeSave:function(data, item_id, opt)
+ {
+ if(!opt || !opt.project_id)
+ {
+ throw "Error in pmPeriodicTasks.onBeforeSave with opt.project_id is null"
+ }
+
+ data.project = opt.project_id
+
+ data.type = $("#periodic-tasks_"+item_id+"_type").val()
+
+ data.kind = $("#periodic-tasks_"+item_id+"_kind").val()
if(data.kind == "MODULE")
{
@@ -459,7 +703,7 @@ pmPeriodicTasks.model.page_item = {
return false;
}
}
- else
+ else if(data.kind == "PLAYBOOK")
{
data.mode = $("#periodic-tasks_"+item_id+"_playbook").val()
if(!data.mode)
@@ -468,6 +712,32 @@ pmPeriodicTasks.model.page_item = {
return false;
}
}
+ else
+ {
+ var template_val = pmPeriodicTasks.projectTemplatesAutocompletefiled.getValue();
+ var template_val_arr = template_val.split("/");
+ data.template = template_val_arr[0];
+ if(template_val_arr[1] !== undefined)
+ {
+ data.template_opt = template_val_arr[1];
+ }
+ else
+ {
+ data.template_opt = null;
+ }
+
+ }
+
+ if(data.kind != "TEMPLATE")
+ {
+ data.inventory = pmPeriodicTasks.inventoriesAutocompletefiled.getValue()
+
+ if(!data.inventory)
+ {
+ $.notify("Invalid field `inventory` ", "error");
+ return false;
+ }
+ }
if(data.type == "CRONTAB")
{
@@ -502,7 +772,8 @@ pmPeriodicTasks.showItem = function(holder, menuInfo, data)
var item_id = data.reg[2];
var project_id = data.reg[1];
- $.when(pmPeriodicTasks.loadItem(item_id), pmTasks.loadAllItems(), pmInventories.loadAllItems(), pmProjects.loadItem(project_id)).done(function()
+ $.when(pmPeriodicTasks.loadItem(item_id), pmTasks.loadAllItems(), pmInventories.loadAllItems(),
+ pmProjects.loadItem(project_id), pmTasksTemplates.loadAllItemsFromProject(project_id)).done(function()
{
var tpl = thisObj.model.name+'_page'
if(!spajs.just.isTplExists(tpl))
@@ -558,6 +829,80 @@ pmPeriodicTasks.showItem = function(holder, menuInfo, data)
return def.promise()
}
+/**
+ * Функция открывет страницу для отображения периодической таски,
+ * созданной на основе шаблона, и на которую перешли со страницы этого шаблона.
+ */
+pmPeriodicTasks.showPeriodicTaskPageFromTemplate = function(holder, menuInfo, data)
+{
+ setActiveMenuLi();
+ var def = new $.Deferred();
+ var thisObj = this;
+ var item_id = data.reg[2];
+ var template_id = data.reg[1];
+
+ $.when(pmPeriodicTasks.loadItem(item_id),
+ pmInventories.loadAllItems(), pmTasksTemplates.loadItem(template_id)).done(function()
+ {
+
+ var tpl = 'periodic-task-from-template-page'
+ var project_id = pmTasksTemplates.model.items[template_id].data.project;
+ var template_kind = pmTasksTemplates.model.items[template_id].kind;
+
+ $(holder).insertTpl(spajs.just.render(tpl, {item_id:item_id, pmObj:thisObj, opt:{project_id:project_id, template_id:template_id, template_kind:template_kind}}))
+ pmPeriodicTasks.selectInventory(pmPeriodicTasks.model.items[item_id].inventory)
+
+ def.resolve();
+
+ }).fail(function(e)
+ {
+ $.notify("", "error");
+ def.reject(e);
+ })
+
+ return def.promise()
+}
+
+/**
+ * Функция открывет страницу для отображения результатов поиска периодических тасок,
+ * созданной на основе шаблона, и на которую перешли со страницы этого шаблона.
+ */
+pmPeriodicTasks.showSearchResultsFromTemplate = function(holder, menuInfo, data)
+{
+ setActiveMenuLi();
+ var thisObj = this;
+ var template_id = data.reg[1];
+
+
+ var search = this.searchStringToObject(decodeURIComponent(data.reg[2]))
+ search['template'] = template_id
+
+ return $.when(pmTasksTemplates.loadItem(template_id), pmModuleTemplates.loadItem(template_id)).done(function(){
+ var project_id = pmTasksTemplates.model.items[template_id].data.project;
+ var pmObj = undefined;
+ if(pmTasksTemplates.model.items[template_id].kind == "Task")
+ {
+ pmObj = pmTasksTemplates;
+ }
+ else
+ {
+ pmObj = pmModuleTemplates;
+ }
+
+ return $.when(thisObj.sendSearchQuery(search), pmProjects.loadItem(project_id)).done(function()
+ {
+ $(holder).insertTpl(spajs.just.render('linked-to-template-periodic-tasks_list', {query:decodeURIComponent(data.reg[2]), project_id:project_id, item_id:template_id, pmObj:pmObj}))
+ }).fail(function()
+ {
+ $.notify("", "error");
+ }).promise();
+
+ }).fail(function(){
+ $.notify("Error with loading project data", "error");
+ }).promise();
+
+
+}
/**
* @return $.Deferred
@@ -577,7 +922,6 @@ pmPeriodicTasks.addItem = function(project_id)
data.name = $("#new_periodic-tasks_name").val()
data.type = $("#new_periodic-tasks_type").val()
- data.inventory = pmPeriodicTasks.inventoriesAutocompletefiled.getValue()
if(!data.name)
{
@@ -586,14 +930,6 @@ pmPeriodicTasks.addItem = function(project_id)
return def.promise();
}
- if(!data.inventory)
- {
- $.notify("Invalid field `inventory` ", "error");
- def.reject();
- return def.promise();
- }
-
-
data.kind = $("#new_periodic-tasks_kind").val()
if(data.kind == "MODULE")
@@ -606,7 +942,7 @@ pmPeriodicTasks.addItem = function(project_id)
return def.promise();
}
}
- else
+ else if(data.kind == "PLAYBOOK")
{
data.mode = $("#new_periodic-tasks_playbook").val()
if(!data.mode)
@@ -616,6 +952,32 @@ pmPeriodicTasks.addItem = function(project_id)
return def.promise();
}
}
+ else
+ {
+ var template_val = pmPeriodicTasks.projectTemplatesAutocompletefiled.getValue();
+ var template_val_arr = template_val.split("/");
+ data.template = template_val_arr[0];
+ if(template_val_arr[1] !== undefined)
+ {
+ data.template_opt = template_val_arr[1];
+ }
+ else
+ {
+ data.template_opt = null;
+ }
+
+ }
+
+ if(data.kind != "TEMPLATE")
+ {
+ data.inventory = pmPeriodicTasks.inventoriesAutocompletefiled.getValue()
+ if(!data.inventory)
+ {
+ $.notify("Invalid field `inventory` ", "error");
+ def.reject();
+ return def.promise();
+ }
+ }
if(data.type == "CRONTAB")
{
@@ -649,13 +1011,31 @@ pmPeriodicTasks.addItem = function(project_id)
type: "POST",
contentType:'application/json',
data: JSON.stringify(data),
- success: function(data)
+ success: function(data)
{
$.notify("periodic task created", "success");
- $.when(spajs.open({ menuId:"project/"+project_id+"/periodic-task/"+data.id})).always(function(){
- def.resolve()
- })
+ if(/template/.test(window.location.href) || /templates/.test(window.location.href))
+ {
+ if(/Task/.test(window.location.href))
+ {
+ var template_kind = "Task";
+ }
+ else
+ {
+ var template_kind = "Module";
+ }
+ $.when(spajs.open({ menuId:"template/"+template_kind+ "/" + data.template +"/periodic-task/"+data.id})).always(function(){
+ def.resolve()
+ })
+ }
+ else
+ {
+ $.when(spajs.open({ menuId:"project/"+project_id+"/periodic-task/"+data.id})).always(function(){
+ def.resolve()
+ })
+ }
+
},
error:function(e)
{
@@ -674,7 +1054,7 @@ pmPeriodicTasks.loadItem = function(item_id)
type: "GET",
contentType:'application/json',
data: "",
- success: function(data)
+ success: function(data)
{
if(data.kind == "MODULE")
{
@@ -701,6 +1081,95 @@ pmPeriodicTasks.loadItem = function(item_id)
});
}
+/**
+ *Данная функция в зависимости от текущего значения свойства enabled
+ *вызывает функцию активации/деактивации periodic task соответственно.
+ */
+pmPeriodicTasks.changeItemActivation = function(item_id) {
+ if(pmPeriodicTasks.model.items[item_id].enabled==true)
+ {
+ return pmPeriodicTasks.deactivateItem(item_id);
+ }
+ else
+ {
+ return pmPeriodicTasks.activateItem(item_id);
+ }
+}
+
+/**
+ *Функция деактивирует periodic task, переводя значение поля enabled из true в false.
+ */
+pmPeriodicTasks.deactivateItem = function(item_id) {
+ var thisObj = this;
+ var def = new $.Deferred();
+ var data = JSON.parse(JSON.stringify(pmPeriodicTasks.model.items[item_id]));
+ data.enabled = false;
+ if(data.kind == "TEMPLATE")
+ {
+ delete data.inventory;
+ delete data.mode;
+ }
+ spajs.ajax.Call({
+ url: hostname + "/api/v1/"+thisObj.model.name+"/"+item_id+"/",
+ type: "PATCH",
+ contentType:'application/json',
+ data: JSON.stringify(data),
+ success: function(data)
+ {
+ $.notify("Periodic task was deactivated", "success");
+ pmPeriodicTasks.model.items[item_id].enabled=!pmPeriodicTasks.model.items[item_id].enabled;
+ var t=$(".change-activation-"+item_id)[0];
+ $(t).html("Activate");
+ var s=$(".pt-enabled-"+item_id)[0];
+ $(s).html("");
+ def.resolve()
+ },
+ error:function(e)
+ {
+ def.reject(e)
+ }
+ });
+
+ return def.promise();
+}
+
+/**
+ *Функция активирует periodic task, переводя значение поля enabled из false в true.
+ */
+pmPeriodicTasks.activateItem = function(item_id) {
+ var thisObj = this;
+ var def = new $.Deferred();
+ var data = JSON.parse(JSON.stringify(pmPeriodicTasks.model.items[item_id]));
+ data.enabled = true;
+ if(data.kind == "TEMPLATE")
+ {
+ delete data.inventory;
+ delete data.mode;
+ }
+ spajs.ajax.Call({
+ url: hostname + "/api/v1/"+thisObj.model.name+"/"+item_id+"/",
+ type: "PATCH",
+ contentType:'application/json',
+ data: JSON.stringify(data),
+ success: function(data)
+ {
+ $.notify("Periodic task was activated", "success");
+ pmPeriodicTasks.model.items[item_id].enabled=!pmPeriodicTasks.model.items[item_id].enabled;
+ var t=$(".change-activation-"+item_id)[0];
+ $(t).html("Deactivate");
+ var s=$(".pt-enabled-"+item_id)[0];
+ $(s).html('');
+ def.resolve()
+ },
+ error:function(e)
+ {
+ def.reject(e)
+ }
+ });
+
+ return def.promise();
+}
+
tabSignal.connect("polemarch.start", function()
{
// tasks
diff --git a/polemarch/static/js/pmProjects.js b/polemarch/static/js/pmProjects.js
index 632818bb..3975f870 100644
--- a/polemarch/static/js/pmProjects.js
+++ b/polemarch/static/js/pmProjects.js
@@ -164,6 +164,13 @@ pmProjects.model.page_list = {
title:'Delete',
link:function(){ return '#'}
},
+ ],
+ actionsOnSelected:[
+ {
+ function:function(item){ return 'spajs.showLoader('+this.model.className+'.syncSelectedProjects()); return false;'},
+ title:'Sync all selected',
+ link:function(){ return '#'}
+ }
]
}
@@ -360,7 +367,11 @@ pmProjects.model.page_item = {
link:function(){ return '#'},
},
],
- sections:[],
+ sections:[
+ function(section, item_id){
+ return spajs.just.render("project_readme", { item_id: item_id, pmObj: pmProjects})
+ }
+ ],
/**
* @param {Integer} item_id Идентификатор редактируемого элемента
* @returns {String} То что подставится в шаблон на title
@@ -722,6 +733,47 @@ pmProjects.renderBranchInput = function(item_id)
return html;
}
+pmProjects.syncSelectedProjects = function()
+{
+ var syncBulk = [];
+ var thisObj = this;
+
+ for (var i in thisObj.model.selectedItems)
+ {
+ if (thisObj.model.selectedItems[i])
+ {
+ syncBulk.push({
+ type: "mod",
+ method: 'post',
+ data_type: 'sync',
+ item: thisObj.model.bulk_name,
+ pk: i
+ })
+ }
+ }
+
+ return $.when(spajs.ajax.Call
+ (
+ {
+ url: hostname + "/api/v1/_bulk/",
+ type: "POST",
+ contentType: 'application/json',
+ data: JSON.stringify(syncBulk)
+ })).done(function (data)
+ {
+ for (var i in syncBulk)
+ {
+ $(".item-" + syncBulk[i].pk).removeClass("selected");
+ thisObj.toggleSelect(syncBulk[i].pk, false);
+ thisObj.model.items[syncBulk[i].pk].status = "WAIT_SYNC";
+ }
+ $.notify("Synchronization of selected projects was started", "success");
+ }).fail(function(){
+ $.notify("Error with synchronization of selected projects", "error");
+ }).promise();
+
+}
+
tabSignal.connect("polemarch.start", function()
{
// projects
diff --git a/polemarch/static/js/pmTasksTemplates.js b/polemarch/static/js/pmTasksTemplates.js
index 3426fbe4..da83776d 100644
--- a/polemarch/static/js/pmTasksTemplates.js
+++ b/polemarch/static/js/pmTasksTemplates.js
@@ -69,46 +69,46 @@ pmTasksTemplates.filed.selectProjectInventoryAndPlaybookForOption.render = funct
})
}
- // Export all selected templates
+// Export all selected templates
pmTasksTemplates.newAutoCompletePlaybook = function()
{
var thisObj=this;
return new autoComplete({
- selector: '#playbook-autocomplete',
- minChars: 0,
- cache:false,
- showByClick:false,
- menuClass:'playbook-autocomplete',
- renderItem: function(item, search)
- {
- return '' + item.playbook + '
';
- },
- onSelect: function(event, term, item)
- {
- $("#playbook-autocomplete").val($(item).text());
- //console.log('onSelect', term, item);
- //var value = $(item).attr('data-value');
- },
- source: function(term, response)
- {
- term = term.toLowerCase();
+ selector: '#playbook-autocomplete',
+ minChars: 0,
+ cache:false,
+ showByClick:false,
+ menuClass:'playbook-autocomplete',
+ renderItem: function(item, search)
+ {
+ return '' + item.playbook + '
';
+ },
+ onSelect: function(event, term, item)
+ {
+ $("#playbook-autocomplete").val($(item).text());
+ //console.log('onSelect', term, item);
+ //var value = $(item).attr('data-value');
+ },
+ source: function(term, response)
+ {
+ term = term.toLowerCase();
- var matches = []
- for(var i in pmTasks.model.itemslist.results)
- {
- var val=pmTasks.model.itemslist.results[i];
- if(val.name.toLowerCase().indexOf(term) != -1 && thisObj.model.selectedProject == val.project)
- {
- matches.push(val)
- }
- }
- if(matches.length)
+ var matches = []
+ for(var i in pmTasks.model.itemslist.results)
+ {
+ var val=pmTasks.model.itemslist.results[i];
+ if(val.name.toLowerCase().indexOf(term) != -1 && thisObj.model.selectedProject == val.project)
{
- response(matches);
+ matches.push(val)
}
}
- });
+ if(matches.length)
+ {
+ response(matches);
+ }
+ }
+ });
}
pmTasksTemplates.model.page_list = {
@@ -186,15 +186,23 @@ pmTasksTemplates.model.page_item = {
link:function(){ return '#'},
help:'Save and execute'
},
-
{
class:'btn btn-info',
function:function(item_id){
- return "spajs.showLoader("+this.model.className+".setNewOption("+item_id+")); return false;"
+ return "spajs.open({ menuId:'template/"+this.model.kind+"/"+item_id+"/options'}); return false;"
},
- title:'Create new option',
+ title:'Options',
link:function(){ return '#'},
- help:'Create new option'
+ help:'Options of this template'
+ },
+ {
+ class:'btn btn-info',
+ function:function(item_id){
+ return "spajs.open({ menuId:'template/"+this.model.kind+"/"+item_id+"/periodic-tasks'}); return false;"
+ },
+ title:'Periodic tasks',
+ link:function(){ return '#'},
+ help:'Periodic tasks linked to this template'
},
{
class:'btn btn-info',
@@ -220,9 +228,6 @@ pmTasksTemplates.model.page_item = {
sections:[
function(section, item_id){
return jsonEditor.editor(pmTasksTemplates.model.items[item_id].data.vars, {block:'playbook', title1:'Arguments', title2:'Adding new argument', select2:true});
- },
- function(section, item_id){
- return spajs.just.render("options_section_task_page", {item_id:item_id})
}
],
title: function(item_id){
@@ -345,7 +350,7 @@ pmTasksTemplates.model.page_item_option = {
{
class:'btn btn-danger danger-right',
function:function(item_id){
- return 'spajs.showLoader(pmTasksTemplates.removeOption('+item_id+')); return false;'
+ return 'spajs.showLoader(pmTasksTemplates.removeOption('+item_id+', "'+pmTasksTemplates.model.items[item_id].option_name+'")); return false;'
},
title:' Remove',
link:function(){ return '#'},
@@ -409,7 +414,7 @@ pmTasksTemplates.saveAndExecute = function(item_id)
*/
pmTasksTemplates.setNewOption = function(item_id)
{
- return spajs.openURL(window.location.href+"/new-option");
+ return spajs.open({ menuId:"template/"+this.model.kind+"/"+item_id+"/new-option"});
}
/**
@@ -553,10 +558,10 @@ pmTasksTemplates.saveAndExecuteOption = function(item_id)
/**
*Функция удаляет опцию.
*/
-pmTasksTemplates.removeOption = function(item_id)
+pmTasksTemplates.removeOption = function(item_id, option_name)
{
var def = new $.Deferred();
- var optionName=pmTasksTemplates.model.items[item_id].option_name;
+ var optionName=option_name;
delete pmTasksTemplates.model.items[item_id].options[optionName];
var dataToAdd1={options:{}};
dataToAdd1['options']=pmTasksTemplates.model.items[item_id].options;
@@ -570,87 +575,24 @@ pmTasksTemplates.removeOption = function(item_id)
{
thisObj.model.items[item_id] = data
$.notify('Option "'+optionName+'" was successfully deleted', "success");
- $.when(spajs.open({ menuId:"template/"+thisObj.model.kind+"/"+data.id})).always(function(){
- def.resolve();
- });
-
- },
- error: function (e)
- {
- def.reject(e)
- polemarch.showErrors(e.responseJSON)
- }
- });
- return def.promise();
-}
-
-/**
- *Функция добавляет на страницу секцию для удаления/добавления опций на страницу шаблона.
- */
-pmTasksTemplates.showExistingOptionsToEdit = function (item_id) {
- if(!item_id)
- {
- throw "Error in pmTasksTemplates.showExistingOptionsToEdit with item_id = `" + item_id + "`"
- }
-
- $("#add_existing_options_to_task_template").remove();
- $(".content").appendTpl(spajs.just.render('add_existing_options_to_task_template', {item_id:item_id}))
- var scroll_el = "#add_existing_options_to_task_template";
- if ($(scroll_el).length != 0) {
- $('html, body').animate({ scrollTop: $(scroll_el).offset().top }, 1000);
- }
- $("#polemarch-model-items-select").select2({ width: '100%' });
-
-}
-
-/**
- *Функция сохраняет изменения внесенные в секции для удаления/добавления опций на странице шаблона.
- */
-pmTasksTemplates.setOptionList = function(item_id, option_list)
-{
- var thisObj=this;
- if(!item_id)
- {
- throw "Error in pmTasksTemplates.setOptionList with item_id = `" + item_id + "`"
- }
-
- if(!option_list)
- {
- var options={};
- }
- else
- {
- var options=pmTasksTemplates.model.items[item_id].options;
- for(var i in options)
- {
- var bool=false;
- for(var j in option_list)
+ if(/options/.test(window.location.href) == false)
{
- if(i==option_list[j])
- {
- bool=true;
- }
+ $.when(spajs.open({ menuId:"template/"+thisObj.model.kind+"/"+data.id+"/options"})).always(function(){
+ def.resolve();
+ });
}
- if(bool==false)
+ else
{
- delete options[i];
+ def.resolve();
}
- }
- }
- return spajs.ajax.Call({
- url: "/api/v1/templates/"+item_id+"/",
- type: "PATCH",
- contentType:'application/json',
- data:JSON.stringify({options:options}),
- success: function(data)
- {
- spajs.openURL(window.location.href);
},
- error:function(e)
+ error: function (e)
{
+ def.reject(e)
polemarch.showErrors(e.responseJSON)
}
});
+ return def.promise();
}
/**
@@ -663,7 +605,7 @@ pmTasksTemplates.showNewOptionPage = function(holder, menuInfo, data)
var item_id = data.reg[1]
$.when(pmProjects.loadAllItems(), pmTasksTemplates.loadItem(item_id), pmInventories.loadAllItems(), pmTasks.loadAllItems()).done(function()
{
- thisObj.model.selectedProject == pmTasksTemplates.model.items[item_id].project
+ thisObj.model.selectedProject = thisObj.model.items[item_id].data.project
var tpl = 'new_option_page'
if(!spajs.just.isTplExists(tpl))
@@ -692,33 +634,33 @@ pmTasksTemplates.showOptionPage = function(holder, menuInfo, data)
var option_name=data.reg[2];
$.when(pmProjects.loadAllItems(), pmTasksTemplates.loadItem(item_id), pmInventories.loadAllItems(), pmTasks.loadAllItems()).done(function()
{
- thisObj.model.selectedProject == pmTasksTemplates.model.items[item_id].project;
+ thisObj.model.selectedProject = thisObj.model.items[item_id].project;
pmTasksTemplates.model.items[item_id].option_name=option_name;
- pmTasksTemplates.model.items[item_id].dataForOption={};
- for(var i in pmTasksTemplates.model.items[item_id].data)
+ pmTasksTemplates.model.items[item_id].dataForOption={};
+ for(var i in pmTasksTemplates.model.items[item_id].data)
+ {
+ if(i!='vars')
{
- if(i!='vars')
- {
- pmTasksTemplates.model.items[item_id].dataForOption[i]=pmTasksTemplates.model.items[item_id].data[i];
- }
+ pmTasksTemplates.model.items[item_id].dataForOption[i]=pmTasksTemplates.model.items[item_id].data[i];
}
- var optionAPI=pmTasksTemplates.model.items[item_id].options[pmTasksTemplates.model.items[item_id].option_name];
- for(var i in optionAPI)
+ }
+ var optionAPI=pmTasksTemplates.model.items[item_id].options[pmTasksTemplates.model.items[item_id].option_name];
+ for(var i in optionAPI)
+ {
+ if(pmTasksTemplates.model.items[item_id].dataForOption.hasOwnProperty(i))
{
- if(pmTasksTemplates.model.items[item_id].dataForOption.hasOwnProperty(i))
- {
- pmTasksTemplates.model.items[item_id].dataForOption[i]=optionAPI[i];
- }
+ pmTasksTemplates.model.items[item_id].dataForOption[i]=optionAPI[i];
}
- if(optionAPI.hasOwnProperty('vars'))
+ }
+ if(optionAPI.hasOwnProperty('vars'))
+ {
+ pmTasksTemplates.model.items[item_id].dataForOption['vars']={};
+ for(var i in optionAPI['vars'])
{
- pmTasksTemplates.model.items[item_id].dataForOption['vars']={};
- for(var i in optionAPI['vars'])
- {
- pmTasksTemplates.model.items[item_id].dataForOption['vars'][i]=optionAPI['vars'][i];
- }
+ pmTasksTemplates.model.items[item_id].dataForOption['vars'][i]=optionAPI['vars'][i];
}
+ }
var tpl = 'module_option_page'
if(!spajs.just.isTplExists(tpl))
@@ -768,19 +710,25 @@ pmTasksTemplates.showItem = function(holder, menuInfo, data)
var def = new $.Deferred();
var thisObj = this;
var item_id = data.reg[1]
- $.when(pmProjects.loadAllItems(), pmTasksTemplates.loadItem(item_id), pmInventories.loadAllItems(), pmTasks.loadAllItems()).done(function()
+ $.when(pmProjects.loadAllItems(), pmTasksTemplates.loadItem(item_id),
+ pmInventories.loadAllItems(), pmTasks.loadAllItems()).done(function()
{
- thisObj.model.selectedProject == pmTasksTemplates.model.items[item_id].project
-
- var tpl = thisObj.model.name+'_page'
- if(!spajs.just.isTplExists(tpl))
+ $.when(pmProjects.loadItem(thisObj.model.items[item_id].data.project)).done(function ()
{
- tpl = 'items_page'
- }
+ thisObj.model.selectedProject = pmTasksTemplates.model.items[item_id].data.project
- $(holder).insertTpl(spajs.just.render(tpl, {item_id:item_id, pmObj:thisObj, opt:{}}))
- pmTasksTemplates.selectProject($("#projects-autocomplete").val());
- def.resolve();
+ var tpl = thisObj.model.name+'_page'
+ if(!spajs.just.isTplExists(tpl))
+ {
+ tpl = 'items_page'
+ }
+
+ $(holder).insertTpl(spajs.just.render(tpl, {item_id:item_id, pmObj:thisObj, opt:{}}))
+ pmTasksTemplates.selectProject($("#projects-autocomplete").val());
+ def.resolve();
+ }).fail(function () {
+ $.notify("Error with loading of project data");
+ });
}).fail(function(e)
{
def.reject(e);
@@ -803,54 +751,112 @@ pmTasksTemplates.showNewItemPage = function(holder, menuInfo, data)
var thisObj = this;
$.when(pmProjects.loadAllItems(), pmInventories.loadAllItems(), pmTasks.loadAllItems()).done(function()
{
- $(holder).insertTpl(spajs.just.render(thisObj.model.name+'_new_page', {}))
-
- $("#inventories-autocomplete").select2({ width: '100%' });
- //$("#projects-autocomplete").select2({ width: '100%' });
-
- new autoComplete({
- selector: '#playbook-autocomplete',
- minChars: 0,
- cache:false,
- showByClick:false,
- menuClass:'playbook-autocomplete',
- renderItem: function(item, search)
- {
- var style = "";
- if(thisObj.model.selectedProject != item.project)
- {
- style = "style='display:none'"
- }
- return '' + item.playbook + '
';
- },
- onSelect: function(event, term, item)
- {
- $("#playbook-autocomplete").val($(item).text());
- //console.log('onSelect', term, item);
- //var value = $(item).attr('data-value');
- },
- source: function(term, response)
+ if(pmProjects.model.itemslist.results.length != 0)
+ {
+ $.when(pmProjects.loadItem(pmProjects.model.itemslist.results[0].id)).done(function()
{
- term = term.toLowerCase();
-
- var matches = []
- for(var i in pmTasks.model.itemslist.results)
+ $(holder).insertTpl(spajs.just.render(thisObj.model.name+'_new_page', {}))
+
+ $("#inventories-autocomplete").select2({ width: '100%' });
+ //$("#projects-autocomplete").select2({ width: '100%' });
+
+ new autoComplete({
+ selector: '#playbook-autocomplete',
+ minChars: 0,
+ cache:false,
+ showByClick:false,
+ menuClass:'playbook-autocomplete',
+ renderItem: function(item, search)
+ {
+ var style = "";
+ if(thisObj.model.selectedProject != item.project)
+ {
+ style = "style='display:none'"
+ }
+ return '' + item.playbook + '
';
+ },
+ onSelect: function(event, term, item)
+ {
+ $("#playbook-autocomplete").val($(item).text());
+ //console.log('onSelect', term, item);
+ //var value = $(item).attr('data-value');
+ },
+ source: function(term, response)
+ {
+ term = term.toLowerCase();
+
+ var matches = []
+ for(var i in pmTasks.model.itemslist.results)
+ {
+ var val=pmTasks.model.itemslist.results[i];
+ if(val.name.toLowerCase().indexOf(term) != -1 && thisObj.model.selectedProject == val.project)
+ {
+ matches.push(val)
+ }
+ }
+ if(matches.length)
+ {
+ response(matches);
+ }
+ }
+ });
+ pmTasksTemplates.selectProject($("#projects-autocomplete").val());
+ def.resolve();
+ }).fail(function(){
+ $.notify("Error with loading of project data");
+ });
+ }
+ else
+ {
+ $(holder).insertTpl(spajs.just.render(thisObj.model.name+'_new_page', {}))
+
+ $("#inventories-autocomplete").select2({ width: '100%' });
+ //$("#projects-autocomplete").select2({ width: '100%' });
+
+ new autoComplete({
+ selector: '#playbook-autocomplete',
+ minChars: 0,
+ cache:false,
+ showByClick:false,
+ menuClass:'playbook-autocomplete',
+ renderItem: function(item, search)
{
- var val=pmTasks.model.itemslist.results[i];
- if(val.name.toLowerCase().indexOf(term) != -1 && thisObj.model.selectedProject == val.project)
+ var style = "";
+ if(thisObj.model.selectedProject != item.project)
{
- matches.push(val)
+ style = "style='display:none'"
}
- }
- if(matches.length)
+ return '' + item.playbook + '
';
+ },
+ onSelect: function(event, term, item)
{
- response(matches);
+ $("#playbook-autocomplete").val($(item).text());
+ //console.log('onSelect', term, item);
+ //var value = $(item).attr('data-value');
+ },
+ source: function(term, response)
+ {
+ term = term.toLowerCase();
+
+ var matches = []
+ for(var i in pmTasks.model.itemslist.results)
+ {
+ var val=pmTasks.model.itemslist.results[i];
+ if(val.name.toLowerCase().indexOf(term) != -1 && thisObj.model.selectedProject == val.project)
+ {
+ matches.push(val)
+ }
+ }
+ if(matches.length)
+ {
+ response(matches);
+ }
}
- }
- });
- pmTasksTemplates.selectProject($("#projects-autocomplete").val());
+ });
+ pmTasksTemplates.selectProject($("#projects-autocomplete").val());
+ def.resolve();
+ }
- def.resolve();
}).fail(function(e)
{
def.reject(e);
@@ -896,7 +902,7 @@ pmTasksTemplates.addItem = function()
type: "POST",
contentType:'application/json',
data:JSON.stringify(data),
- success: function(data)
+ success: function(data)
{
$.notify("template created", "success");
$.when(spajs.open({ menuId:"template/"+thisObj.model.kind+"/"+data.id})).always(function(){
@@ -914,6 +920,312 @@ pmTasksTemplates.addItem = function()
}
+/**
+ * Функция предназначена для загрузки всех шаблонов(task, module),
+ * привязанных к определенному проекту.
+ * @param number project_id - id of project
+ */
+pmTasksTemplates.loadAllItemsFromProject = function(project_id)
+{
+ var thisObj = this;
+ return spajs.ajax.Call({
+ url: hostname + "/api/v1/" + this.model.name + "/",
+ type: "GET",
+ contentType: 'application/json',
+ data: "project="+project_id,
+ success: function (data)
+ {
+ thisObj.model.itemslist = data
+
+ for (var i in data.results)
+ {
+ var val = thisObj.afterItemLoad(data.results[i])
+ thisObj.model.items.justWatch(val.id);
+ thisObj.model.items[val.id] = mergeDeep(thisObj.model.items[val.id], val)
+ }
+ },
+ error: function (e)
+ {
+ console.warn(e)
+ polemarch.showErrors(e)
+ }
+ });
+}
+
+/**
+ * Функция предназначена для загрузки всех периодических тасок,
+ * ссылающихся на данный шаблон.
+ * @param number template_id - id of template
+ */
+pmTasksTemplates.loadLinkedPeriodicTasks = function(template_id)
+{
+ var thisObj = this;
+ return spajs.ajax.Call({
+ url: hostname + "/api/v1/periodic-tasks/",
+ type: "GET",
+ contentType: 'application/json',
+ data: "template="+template_id,
+ success: function (data)
+ {
+ // thisObj.model.linkedPeriodicTask = [];
+ // thisObj.model.linkedPeriodicTasks = data.results;
+ pmPeriodicTasks.model.itemslist = data
+ pmPeriodicTasks.model.items = {}
+
+ for (var i in data.results)
+ {
+ var val = pmPeriodicTasks.afterItemLoad(data.results[i])
+ pmPeriodicTasks.model.items.justWatch(val.id);
+ pmPeriodicTasks.model.items[val.id] = mergeDeep(thisObj.model.items[val.id], val)
+ }
+ },
+ error: function (e)
+ {
+ console.warn(e)
+ polemarch.showErrors(e)
+ }
+ });
+}
+
+/**
+ * Функция открывает страницу со списком опций шаблона
+ */
+pmTasksTemplates.showOptionsList = function (holder, menuInfo, data)
+{
+ setActiveMenuLi();
+ var thisObj = this;
+ var offset = 0
+ var limit = thisObj.pageSize;
+ if (data.reg && data.reg[1] > 0)
+ {
+ offset = thisObj.pageSize * (data.reg[1] - 1);
+ }
+ return $.when(thisObj.loadItem(data.reg[1])).done(function ()
+ {
+ var tpl = 'template_options_list'
+
+ $(holder).insertTpl(spajs.just.render(tpl, {query: "", pmObj: thisObj, item_id:data.reg[1], opt: {}}))
+ }).fail(function ()
+ {
+ $.notify("", "error");
+ })
+}
+
+/**
+ * Функция выделяет/снимает выделение с опций в таблице списка опций.
+ * @param {array} elements - массив выделенных элементов
+ * @param {boolean} mode - true - добавить выделение, false - снять выделение
+ * @param {string} div_id - id блока, в котором находятся данные элементы
+ */
+pmTasksTemplates.toggleSelectAllOptions = function (elements, mode, div_id)
+{
+ for (var i = 0; i < elements.length; i++)
+ {
+ if($(elements[i]).hasClass('item-row'))
+ {
+ $(elements[i]).toggleClass('selected', mode);
+ }
+ }
+ pmTasksTemplates.countSelectedOptions(div_id);
+}
+
+/**
+ * Функция выделяет/снимает выделение с одного конкретного элемента в определенной таблице.
+ * В данном случае в таблице со списком опций.
+ * @param {object} thisEl - конкретный элемент
+ * @param {string} div_id - id блока, в котором находится данный элемент
+ */
+pmTasksTemplates.toggleSelectOption = function (thisEl, div_id)
+{
+ $(thisEl).parent().toggleClass('selected');
+ pmTasksTemplates.countSelectedOptions(div_id);
+}
+
+/**
+ * Функция подсчитывает количество выделенных элементов в определенной таблице элементов.
+ * И запоминает данное число в pmModuleTemplates.model.selectedOptionsCount,
+ * а сами элементы в pmModuleTemplates.model.selectedOptions.
+ * В зависимости от нового значения pmModuleTemplates.model.selectedOptionsCount
+ * часть кнопок отображается либо скрывается.
+ * @param {string} div_id - id блока, в котором находятся данные элементы
+ */
+pmTasksTemplates.countSelectedOptions = function (div_id)
+{
+ var elements=$("#"+div_id+"_table tr");
+ var count=0;
+ pmTasksTemplates.model.selectedOptions = [];
+ for (var i = 0; i < elements.length; i++)
+ {
+ if($(elements[i]).hasClass('item-row') && $(elements[i]).hasClass('selected'))
+ {
+ count+=1;
+ pmTasksTemplates.model.selectedOptions.push($(elements[i]).attr('data-id'));
+ }
+ }
+
+ if(count==0)
+ {
+ $($("#"+div_id+" .actions_button")[0]).addClass("hide");
+ }
+ else
+ {
+ $($("#"+div_id+" .actions_button")[0]).removeClass("hide");
+ }
+ pmTasksTemplates.model.selectedOptionsCount=count;
+}
+
+/**
+ *Функция удаляет все выделенные опции.
+ */
+pmTasksTemplates.removeSelectedOptions = function(item_id, option_names)
+{
+ var def = new $.Deferred();
+ for(var i in option_names)
+ {
+ var optionName=option_names[i];
+ delete pmTasksTemplates.model.items[item_id].options[optionName];
+ }
+ var dataToAdd1={options:{}};
+ dataToAdd1['options']=pmTasksTemplates.model.items[item_id].options;
+ var thisObj = this;
+ spajs.ajax.Call({
+ url: hostname + "/api/v1/" + this.model.name + "/" + item_id + "/",
+ type: "PATCH",
+ contentType: 'application/json',
+ data: JSON.stringify(dataToAdd1),
+ success: function (data)
+ {
+ thisObj.model.items[item_id] = data
+ $.notify('Options were successfully deleted', "success");
+ def.resolve();
+ },
+ error: function (e)
+ {
+ def.reject(e)
+ polemarch.showErrors(e.responseJSON)
+ }
+ });
+ return def.promise();
+}
+
+/**
+ *Функция открывает список периодических тасок, созданных на основе данного шаблона.
+ */
+pmTasksTemplates.showPeriodicTasksList = function (holder, menuInfo, data)
+{
+ setActiveMenuLi();
+ var thisObj = this;
+ var offset = 0
+ var limit = thisObj.pageSize;
+ if (data.reg && data.reg[1] > 0)
+ {
+ offset = thisObj.pageSize * (data.reg[1] - 1);
+ }
+ var template_id = data.reg[1];
+ return $.when(thisObj.loadItem(template_id), thisObj.loadLinkedPeriodicTasks(template_id)).done(function ()
+ {
+ var tpl = 'linked-to-template-periodic-tasks_list';
+ var project_id = thisObj.model.items[template_id].data.project;
+
+ $(holder).insertTpl(spajs.just.render(tpl, {query: "", pmObj: thisObj, project_id:project_id, item_id:template_id, opt: {}}))
+ }).fail(function ()
+ {
+ $.notify("", "error");
+ })
+}
+
+/**
+ *Функция открывает страницу создания новой периодической таски для шаблона.
+ */
+pmTasksTemplates.showNewPeriodicTaskFromTemplate = function (holder, menuInfo, data)
+{
+ var def = new $.Deferred();
+ var thisObj = this;
+ var item_id = data.reg[1]
+ $.when(pmTasksTemplates.loadItem(item_id), pmInventories.loadAllItems()).done(function()
+ {
+ var project_id = thisObj.model.items[item_id].data.project
+ pmPeriodicTasks.model.newitem = {type:'INTERVAL', kind:'TEMPLATE'}
+ var tpl = 'from-template-periodic-tasks_new_page'
+ if(!spajs.just.isTplExists(tpl))
+ {
+ tpl = 'items_page'
+ }
+
+ $(holder).insertTpl(spajs.just.render(tpl, {item_id:item_id, project_id:project_id, pmObj:thisObj, opt:{}}))
+ def.resolve();
+ }).fail(function(e)
+ {
+ def.reject(e);
+ })
+
+ return def.promise()
+
+}
+
+/**
+ * Функция рендерит шаблон для поля поиска на странице списка опций шаблона.
+ */
+pmTasksTemplates.searchFiledForTemplateOptions = function (options)
+{
+ options.className = this.model.className;
+ this.model.searchAdditionalData = options
+ return spajs.just.render('searchFiledForTemplateOptions', {opt: options});
+}
+
+/**
+ * Функция для поиска опций на странице списка опций шаблона.
+ */
+pmTasksTemplates.searchTemplateOptions = function (query, options)
+{
+ if (this.isEmptySearchQuery(query))
+ {
+ return spajs.open({menuId: 'template/Task/' + options.template_id +'/options', reopen: true});
+ }
+
+ return spajs.open({menuId: 'template/Task/' + options.template_id +'/options' + '/search/' + this.searchObjectToString(trim(query)), reopen: true});
+}
+
+/**
+ * Функция показывает результаты поиска опций шаблона.
+ */
+pmTasksTemplates.showOptionsSearchResult = function (holder, menuInfo, data)
+{
+ setActiveMenuLi();
+ var thisObj = this;
+ var template_id = data.reg[1];
+ var search = this.searchStringToObject(decodeURIComponent(data.reg[2]))
+
+ return $.when(thisObj.loadItem(data.reg[1])).done(function ()
+ {
+ var unvalidSearchOptions = [];
+ for (var i in thisObj.model.items[template_id].options_list)
+ {
+ var option_name = thisObj.model.items[template_id].options_list[i];
+ if(option_name.match(search.name) == null)
+ {
+ unvalidSearchOptions.push(option_name);
+ delete thisObj.model.items[template_id].options_list[i];
+ }
+
+ }
+
+ for(var i in unvalidSearchOptions)
+ {
+ delete thisObj.model.items[template_id].options[unvalidSearchOptions[i]];
+ }
+
+ var tpl = 'template_options_list';
+
+ $(holder).insertTpl(spajs.just.render(tpl, {query:decodeURIComponent(search.name), pmObj: thisObj, item_id:template_id, opt: {}}))
+ }).fail(function ()
+ {
+ $.notify("", "error");
+ })
+}
+
+
+
tabSignal.connect("polemarch.start", function()
{
// Tasks Templates
@@ -953,4 +1265,43 @@ tabSignal.connect("polemarch.start", function()
onOpen:function(holder, menuInfo, data){return pmTasksTemplates.showOptionPage(holder, menuInfo, data);}
})
+ spajs.addMenu({
+ id:"task-options",
+ urlregexp:[/^template\/Task\/([0-9]+)\/options$/, /^templates\/Task\/([0-9]+)\/options$/],
+ onOpen:function(holder, menuInfo, data){return pmTasksTemplates.showOptionsList(holder, menuInfo, data);}
+ })
+
+ spajs.addMenu({
+ id:"task-options-search",
+ urlregexp:[/^template\/Task\/([0-9]+)\/options\/search\/([A-z0-9 %\-.:,=]+)$/,
+ /^templates\/Task\/([0-9]+)\/options\/search\/([A-z0-9 %\-.:,=]+)$/,
+ /^template\/Task\/([0-9]+)\/options\/search\/([A-z0-9 %\-.:,=]+)$/,
+ /^templates\/Task\/([0-9]+)\/options\/search\/([A-z0-9 %\-.:,=]+)$/],
+ onOpen:function(holder, menuInfo, data){return pmTasksTemplates.showOptionsSearchResult(holder, menuInfo, data);}
+ })
+
+ spajs.addMenu({
+ id:"task-periodic-tasks",
+ urlregexp:[/^template\/Task\/([0-9]+)\/periodic-tasks/, /^templates\/Task\/([0-9]+)\/periodic-tasks/],
+ onOpen:function(holder, menuInfo, data){return pmTasksTemplates.showPeriodicTasksList(holder, menuInfo, data);}
+ })
+
+ spajs.addMenu({
+ id:"task-new-periodic-task",
+ urlregexp:[/^template\/Task\/([0-9]+)\/new-periodic-task/, /^templates\/Task\/([0-9]+)\/new-periodic-task/],
+ onOpen:function(holder, menuInfo, data){return pmTasksTemplates.showNewPeriodicTaskFromTemplate(holder, menuInfo, data);}
+ })
+
+ spajs.addMenu({
+ id:"task-periodic-task",
+ urlregexp:[/^template\/Task\/([0-9]+)\/periodic-task\/([0-9]+)/, /^templates\/Task\/([0-9]+)\/periodic-task\/([0-9]+)/],
+ onOpen:function(holder, menuInfo, data){return pmPeriodicTasks.showPeriodicTaskPageFromTemplate(holder, menuInfo, data);}
+ })
+
+ spajs.addMenu({
+ id:"task-periodic-tasks-search",
+ urlregexp:[/^template\/Task\/([0-9]+)\/periodic-tasks\/search\/([A-z0-9 %\-.:,=]+)$/, /^template\/Task\/([0-9]+)\/periodic-tasks\/search\/([A-z0-9 %\-.:,=]+)\/page\/([0-9]+)$/],
+ onOpen:function(holder, menuInfo, data){return pmPeriodicTasks.showSearchResultsFromTemplate(holder, menuInfo, data);}
+ })
+
})
diff --git a/polemarch/static/js/pmUsers.js b/polemarch/static/js/pmUsers.js
index ab4418df..ccdcf6bf 100644
--- a/polemarch/static/js/pmUsers.js
+++ b/polemarch/static/js/pmUsers.js
@@ -4,6 +4,7 @@ var pmUsers = inheritance(pmItems)
pmUsers.model.name = "users"
pmUsers.model.page_name = "user"
pmUsers.model.className = "pmUsers"
+pmUsers.model.bulk_name = "user"
pmUsers.model.page_list = {
buttons:[
@@ -367,6 +368,9 @@ pmUsers.model.profile_page = {
sections:[
function(){
return spajs.just.render("WidgetsSettingsFromProfile");
+ },
+ function(){
+ return spajs.just.render("chart_line_settings");
}
],
onUpdate:function(result)
@@ -621,7 +625,7 @@ pmUsers.showProfile = function (holder, menuInfo, data)
var thisObj = this;
//console.log(menuInfo, data)
- return $.when(pmDashboard.getUserWidgetSettingsFromAPI(), this.loadItem(data.reg[1])).done(function ()
+ return $.when(pmDashboard.getUserDashboardSettingsFromAPI(), this.loadItem(data.reg[1])).done(function ()
{
var tpl = "profile_page"
if (!spajs.just.isTplExists(tpl))
@@ -640,13 +644,23 @@ pmUsers.showProfile = function (holder, menuInfo, data)
*Функция, сохраняющая все настройки профиля пользоваетля.
*/
pmUsers.updateProfile = function (item_id) {
- return $.when(pmUsers.updateItem(item_id), pmDashboard.saveWigdetsOptionsFromProfile()).done(function ()
+ var def = new $.Deferred();
+ $.when(pmUsers.updateItem(item_id)).done(function ()
{
- $.notify("Profile was successfully updated", "success");
+ $.when(pmDashboard.saveAllDashboardSettingsFromProfile()).done(function()
+ {
+ $.notify("Profile was successfully updated", "success");
+ def.resolve();
+ }).fail(function(){
+ $.notify("Dashboard settings were not updated", "error");
+ def.reject();
+ })
}).fail(function ()
{
$.notify("Profile was not updated", "error");
- }).promise()
+ def.reject();
+ })
+ return def.promise();
}
diff --git a/polemarch/static/js/tests/qUnitTest.js b/polemarch/static/js/tests/qUnitTest.js
index 6a6d7702..7e898385 100644
--- a/polemarch/static/js/tests/qUnitTest.js
+++ b/polemarch/static/js/tests/qUnitTest.js
@@ -706,6 +706,38 @@ window.qunitTestsArray.push({
render(done)
})
});
+
+ syncQUnit.addTest('Открытие profile текущего пользователя', function ( assert )
+ {
+ var done = assert.async();
+
+ $.when(spajs.open({ menuId:"profile/"+my_user_id})).done(function()
+ {
+ assert.ok(true, 'Успешно открыто меню profile');
+ render(done)
+ }).fail(function()
+ {
+ debugger;
+ assert.ok(false, 'Ошибка при открытиии меню profile');
+ render(done)
+ })
+ });
+
+ syncQUnit.addTest('Сохранение profile текущего пользователя', function ( assert )
+ {
+ var done = assert.async();
+
+ $.when(pmUsers.updateProfile(my_user_id)).done(function()
+ {
+ assert.ok(true, 'Успешно сохранен profile');
+ render(done)
+ }).fail(function()
+ {
+ debugger;
+ assert.ok(false, 'Ошибка при сохранении profile');
+ render(done)
+ })
+ });
}})
@@ -2749,6 +2781,24 @@ window.qunitTestsArray.push({
//тестируем опции шаблона
var itemIdForOptionTest=undefined;
+ syncQUnit.addTest('Открытие страницы списка опций шаблона задачи', function ( assert )
+ {
+ var done = assert.async();
+ itemIdForOptionTest = /template\/Task\/([0-9]+)/.exec(window.location.href)[1];
+
+ $.when(spajs.open({ menuId:"template/Task/"+itemIdForOptionTest+"/options"})).done(function()
+ {
+ assert.ok(true, 'Успешно открыто меню templates/Task/item_id/options');
+ render(done)
+ }).fail(function()
+ {
+ debugger;
+ assert.ok(false, 'Ошибка при открытиии меню templates/Task/item_id/options');
+ render(done)
+ })
+ });
+
+
syncQUnit.addTest('Открытие страницы создания новой опции шаблона задачи', function ( assert )
{
// Предполагается что мы от прошлого теста попали на страницу создания project
@@ -2771,7 +2821,7 @@ window.qunitTestsArray.push({
{
var done = assert.async();
- //пытаемся сохранить пустую опцию, даже не забав имя
+ //пытаемся сохранить пустую опцию, даже не задав имя
$.when(pmTasksTemplates.saveNewOption(itemIdForOptionTest)).done(function()
{
debugger;
@@ -2929,6 +2979,119 @@ window.qunitTestsArray.push({
//конец тестирования опции шаблона
+ //начинаем тестирование переодических тасок через шаблоны
+ var itemIdForPT = undefined;
+ syncQUnit.addTest('Открытие страницы списка периодических тасок шаблона задачи', function ( assert )
+ {
+ var done = assert.async();
+ itemIdForPT = /template\/Task\/([0-9]+)/.exec(window.location.href)[1];
+
+ $.when(spajs.open({ menuId:"template/Task/"+itemIdForPT+"/periodic-tasks"})).done(function()
+ {
+ assert.ok(true, 'Успешно открыто меню templates/Task/item_id/periodic-tasks');
+ render(done)
+ }).fail(function()
+ {
+ debugger;
+ assert.ok(false, 'Ошибка при открытиии меню templates/Task/item_id/periodic-tasks');
+ render(done)
+ })
+ });
+
+ syncQUnit.addTest('Открытие страницы создания новой периодической таски шаблона задачи', function ( assert )
+ {
+ var done = assert.async();
+
+ $.when(spajs.open({ menuId:"template/Task/"+itemIdForPT+"/new-periodic-task"})).done(function()
+ {
+ assert.ok(true, 'Успешно открыто меню templates/Task/item_id/new-periodic-task');
+ render(done)
+ }).fail(function()
+ {
+ debugger;
+ assert.ok(false, 'Ошибка при открытиии меню templates/Task/item_id/new-periodic-task');
+ render(done)
+ })
+ });
+
+
+ syncQUnit.addTest('Создание новой периодической таски шаблона задачи', function ( assert )
+ {
+ var done = assert.async();
+
+ $("#new_periodic-tasks_name").val("new-pt");
+ $("#new_periodic-tasks_schedule_INTERVAL").val(60);
+
+ var project_id = pmTasksTemplates.model.items[itemIdForPT].data.project;
+
+ $.when(pmPeriodicTasks.addItem(project_id)).done(function()
+ {
+ assert.ok(true, 'Periodic task for template была успешно создана');
+ render(done)
+ }).fail(function()
+ {
+ debugger;
+ assert.ok(false, 'Ошибка при создании periodic task for template');
+ render(done)
+ })
+ });
+
+
+ syncQUnit.addTest('Изменение периодической таски шаблона задачи', function ( assert )
+ {
+ var done = assert.async();
+
+ var project_id = pmTasksTemplates.model.items[itemIdForPT].data.project;
+ var pt_id = /template\/Task\/([0-9]+)\/periodic-task\/([0-9]+)/.exec(window.location.href)[2];
+ $("#periodic-tasks_"+pt_id+"_schedule_INTERVAL").val(3600);
+
+ $.when(pmPeriodicTasks.updateItem(pt_id, {project_id:project_id})).done(function()
+ {
+ assert.ok(true, 'Periodic task for template была успешно изменена');
+ render(done)
+ }).fail(function()
+ {
+ debugger;
+ assert.ok(false, 'Ошибка при изменении periodic task for template');
+ render(done)
+ })
+ });
+
+
+ syncQUnit.addTest('Удаление периодической таски шаблона задачи', function ( assert )
+ {
+ var done = assert.async();
+
+ var pt_id = /template\/Task\/([0-9]+)\/periodic-task\/([0-9]+)/.exec(window.location.href)[2];
+
+ $.when(pmPeriodicTasks.deleteItem(pt_id, true)).done(function()
+ {
+ assert.ok(true, 'Periodic task for template была успешно удалена');
+ render(done)
+ }).fail(function()
+ {
+ debugger;
+ assert.ok(false, 'Ошибка при удалении periodic task for template');
+ render(done)
+ })
+ });
+ //конец тестирования переодических тасок через шаблоны
+
+ syncQUnit.addTest('Открытие страницы шаблона шаблона', function ( assert )
+ {
+ var done = assert.async();
+
+ $.when(spajs.open({ menuId:"template/Task/"+itemIdForPT})).done(function()
+ {
+ assert.ok(true, 'Успешно открыт шаблон');
+ render(done)
+ }).fail(function(){
+ debugger;
+ assert.ok(false, 'Ошибка при открытии шаблона');
+ render(done)
+ })
+ });
+
syncQUnit.addTest('Изменение не валидного шаблона', function ( assert )
{
var done = assert.async();
@@ -3191,11 +3354,28 @@ window.qunitTestsArray.push({
//начало тестирования опций шаблона
var itemIdForOptionTest1=undefined;
+
+ syncQUnit.addTest('Открытие страницы списка опций шаблона модуля', function ( assert )
+ {
+ var done = assert.async();
+ itemIdForOptionTest1 = /template\/Module\/([0-9]+)/.exec(window.location.href)[1];
+
+ $.when(spajs.open({ menuId:"template/Module/"+itemIdForOptionTest1+"/options"})).done(function()
+ {
+ assert.ok(true, 'Успешно открыто меню templates/Module/item_id/options');
+ render(done)
+ }).fail(function()
+ {
+ debugger;
+ assert.ok(false, 'Ошибка при открытиии меню templates/Module/item_id/options');
+ render(done)
+ })
+ });
+
syncQUnit.addTest('Открытие страницы создания новой опции шаблона модуля', function ( assert )
{
// Предполагается что мы от прошлого теста попали на страницу создания project
var done = assert.async();
- itemIdForOptionTest1 = /template\/Module\/([0-9]+)/.exec(window.location.href)[1];
$.when(spajs.open({ menuId:"template/Module/"+itemIdForOptionTest1+"/new-option"})).done(function()
{
@@ -3371,6 +3551,119 @@ window.qunitTestsArray.push({
});
//конец тестирования опции шаблона
+ //начинаем тестирование переодических тасок через шаблоны
+ var itemIdForPT1 = undefined;
+ syncQUnit.addTest('Открытие страницы списка периодических тасок шаблона модуля', function ( assert )
+ {
+ var done = assert.async();
+ itemIdForPT1 = /template\/Module\/([0-9]+)/.exec(window.location.href)[1];
+
+ $.when(spajs.open({ menuId:"template/Module/"+itemIdForPT1+"/periodic-tasks"})).done(function()
+ {
+ assert.ok(true, 'Успешно открыто меню templates/Module/item_id/periodic-tasks');
+ render(done)
+ }).fail(function()
+ {
+ debugger;
+ assert.ok(false, 'Ошибка при открытиии меню templates/Module/item_id/periodic-tasks');
+ render(done)
+ })
+ });
+
+ syncQUnit.addTest('Открытие страницы создания новой периодической таски шаблона Module', function ( assert )
+ {
+ var done = assert.async();
+
+ $.when(spajs.open({ menuId:"template/Module/"+itemIdForPT1+"/new-periodic-task"})).done(function()
+ {
+ assert.ok(true, 'Успешно открыто меню templates/Module/item_id/new-periodic-task');
+ render(done)
+ }).fail(function()
+ {
+ debugger;
+ assert.ok(false, 'Ошибка при открытиии меню templates/Module/item_id/new-periodic-task');
+ render(done)
+ })
+ });
+
+
+ syncQUnit.addTest('Создание новой периодической таски шаблона Module', function ( assert )
+ {
+ var done = assert.async();
+
+ $("#new_periodic-tasks_name").val("new-pt");
+ $("#new_periodic-tasks_schedule_INTERVAL").val(60);
+
+ var project_id = pmModuleTemplates.model.items[itemIdForPT1].data.project;
+
+ $.when(pmPeriodicTasks.addItem(project_id)).done(function()
+ {
+ assert.ok(true, 'Periodic task for template была успешно создана');
+ render(done)
+ }).fail(function()
+ {
+ debugger;
+ assert.ok(false, 'Ошибка при создании periodic task for template');
+ render(done)
+ })
+ });
+
+
+ syncQUnit.addTest('Изменение периодической таски шаблона задачи', function ( assert )
+ {
+ var done = assert.async();
+
+ var project_id = pmModuleTemplates.model.items[itemIdForPT1].data.project;
+ var pt_id = /template\/Module\/([0-9]+)\/periodic-task\/([0-9]+)/.exec(window.location.href)[2];
+ $("#periodic-tasks_"+pt_id+"_schedule_INTERVAL").val(3600);
+
+ $.when(pmPeriodicTasks.updateItem(pt_id, {project_id:project_id})).done(function()
+ {
+ assert.ok(true, 'Periodic task for template была успешно изменена');
+ render(done)
+ }).fail(function()
+ {
+ debugger;
+ assert.ok(false, 'Ошибка при изменении periodic task for template');
+ render(done)
+ })
+ });
+
+
+ syncQUnit.addTest('Удаление периодической таски шаблона задачи', function ( assert )
+ {
+ var done = assert.async();
+
+ var pt_id = /template\/Module\/([0-9]+)\/periodic-task\/([0-9]+)/.exec(window.location.href)[2];
+
+ $.when(pmPeriodicTasks.deleteItem(pt_id, true)).done(function()
+ {
+ assert.ok(true, 'Periodic task for template была успешно удалена');
+ render(done)
+ }).fail(function()
+ {
+ debugger;
+ assert.ok(false, 'Ошибка при удалении periodic task for template');
+ render(done)
+ })
+ });
+ //конец тестирования переодических тасок через шаблоны
+
+ syncQUnit.addTest('Открытие страницы шаблона модуля', function ( assert )
+ {
+ var done = assert.async();
+
+ $.when(spajs.open({ menuId:"template/Module/"+itemIdForPT1})).done(function()
+ {
+ assert.ok(true, 'Успешно открыт шаблон');
+ render(done)
+ }).fail(function(){
+ debugger;
+ assert.ok(false, 'Ошибка при открытии шаблона');
+ render(done)
+ })
+ });
+
syncQUnit.addTest('Изменение не валидного шаблона модуля', function ( assert )
{
var done = assert.async();
@@ -3729,6 +4022,74 @@ window.qunitTestsArray.push({
})
});
+ syncQUnit.addTest('Поиск task template options', function ( assert )
+ {
+ var done = assert.async();
+
+ $.when(spajs.open({ menuId:"template/Task/9999999999/options/search/name"})).done(function()
+ {
+ debugger;
+ assert.ok(false, 'Успешно открыто меню template/Task/9999999999/options/search/name');
+ render(done)
+ }).fail(function()
+ {
+
+ assert.ok(true, 'Ошибка при открытиии меню template/Task/9999999999/options/search/name');
+ render(done)
+ })
+ });
+
+ syncQUnit.addTest('Поиск task template periodic tasks', function ( assert )
+ {
+ var done = assert.async();
+
+ $.when(spajs.open({ menuId:"template/Task/9999999999/periodic-tasks/search/name"})).done(function()
+ {
+ debugger;
+ assert.ok(false, 'Успешно открыто меню template/Task/9999999999/periodic-tasks/search/name');
+ render(done)
+ }).fail(function()
+ {
+
+ assert.ok(true, 'Ошибка при открытиии меню template/Task/9999999999/periodic-tasks/search/name');
+ render(done)
+ })
+ });
+
+ syncQUnit.addTest('Поиск module template options', function ( assert )
+ {
+ var done = assert.async();
+
+ $.when(spajs.open({ menuId:"template/Module/9999999999/options/search/name"})).done(function()
+ {
+ debugger;
+ assert.ok(false, 'Успешно открыто меню template/Module/9999999999/options/search/name');
+ render(done)
+ }).fail(function()
+ {
+
+ assert.ok(true, 'Ошибка при открытиии меню template/Module/9999999999/options/search/name');
+ render(done)
+ })
+ });
+
+ syncQUnit.addTest('Поиск module template periodic tasks', function ( assert )
+ {
+ var done = assert.async();
+
+ $.when(spajs.open({ menuId:"template/Module/9999999999/periodic-tasks/search/name"})).done(function()
+ {
+ debugger;
+ assert.ok(false, 'Успешно открыто меню template/Module/9999999999/periodic-tasks/search/name');
+ render(done)
+ }).fail(function()
+ {
+
+ assert.ok(true, 'Ошибка при открытиии меню template/Module/9999999999/periodic-tasks/search/name');
+ render(done)
+ })
+ });
+
syncQUnit.addTest('Страница ошибки 400 в project history', function ( assert )
{
var done = assert.async();
diff --git a/polemarch/static/templates/pmHistory.html b/polemarch/static/templates/pmHistory.html
index 6f73fcd8..3fd9bb92 100644
--- a/polemarch/static/templates/pmHistory.html
+++ b/polemarch/static/templates/pmHistory.html
@@ -141,6 +141,9 @@
Delete all selected elements
+
+ Clear output of selected elements
+
<% if(!query){ %>
@@ -308,7 +311,10 @@