diff --git a/.gitlab-ci.yml b/.gitlab-ci.yml index 6bf1ab08..9c777d87 100644 --- a/.gitlab-ci.yml +++ b/.gitlab-ci.yml @@ -32,11 +32,17 @@ stages: - GUI - tags - issue_building - variables: - - $CI_COMMIT_MESSAGE =~ /Merge branch/ + only: changes: - - polemarch/static/* - - polemarch/templates/* + - polemarch/**/*.{py,pyx} + - tests.py + - setup.{py,cfg} + - MANIFEST.in + - requirements.txt + - requirements-test.txt + - Makefile + - tox.ini + - .coveragerc retry: 2 .pack_tamplate: &packing-test @@ -77,11 +83,14 @@ code_style: refs: - tags - issue_building - variables: - - $CI_COMMIT_MESSAGE =~ /Merge branch/ + only: changes: - - polemarch/static/* - - polemarch/templates/* + - polemarch/**/*.{py,pyx} + - requirements.txt + - Makefile + - tox.ini + - .pylintrc + - .pep8 retry: 2 py27-install: @@ -96,7 +105,7 @@ py36-install: #################################################### # DEPRECATED -default_rpm_tests: +.rpm_tests_tamplate: &packing-test-rpm_tests <<: *packing-test script: - cat /etc/hosts @@ -107,7 +116,8 @@ default_rpm_tests: - sudo -H -u polemarch /opt/polemarch/bin/pip install -r requirements-test.txt - sudo -H -u polemarch /opt/polemarch/bin/polemarchctl test -v2 polemarch.main.tests --failfast -default_deb_tests: + +.deb_tests_tamplate: &packing-test-deb_tests <<: *packing-test image: onegreyonewhite/tox:ubuntu script: @@ -121,6 +131,33 @@ default_deb_tests: - sudo -H -u polemarch /opt/polemarch/bin/pip install -r requirements-test.txt - sudo -H -u polemarch /opt/polemarch/bin/polemarchctl test -v2 polemarch.main.tests --failfast + +default_rpm_tests: + <<: *packing-test-rpm_tests + + +default_deb_tests: + <<: *packing-test-deb_tests + + +developer_rpm_tests: + <<: *packing-test-rpm_tests + stage: release + only: + refs: + - developer + when: manual + allow_failure: true + +developer_deb_tests: + <<: *packing-test-deb_tests + stage: release + only: + refs: + - developer + when: manual + allow_failure: true + # Realese ########################################### pages: @@ -134,6 +171,7 @@ pages: only: refs: - developer + when: always release_pypi: stage: release @@ -187,7 +225,7 @@ release_deb: - $PYPI_UPLOAD_PASSWORD image: onegreyonewhite/tox:ubuntu script: - - make test ENVS=deb + - make test ENVS=deb INSTALL_PY=python3 allow_failure: false artifacts: name: "release-deb-${CI_BUILD_REF_NAME}.${CI_BUILD_ID}" @@ -203,5 +241,9 @@ publish_release: - tags variables: - $PYPI_UPLOAD_PASSWORD + before_script: + - git config --global user.name "${GITLAB_USER_NAME}" + - git config --global user.email "${GITLAB_USER_EMAIL}" + - git push https://${GITHUB_USER}:${GITHUB_TOKEN}@github.com/vstconsulting/polemarch.git "${CI_COMMIT_TAG}" script: - make test ENVS=release diff --git a/Makefile b/Makefile index ff30696f..1d90685a 100644 --- a/Makefile +++ b/Makefile @@ -99,14 +99,14 @@ compile: build-clean print_vars prebuild: print_vars # Create virtualenv - $(PY) -m virtualenv -p $(INSTALL_PY) --no-site-packages $(PREBUILD_DIR) + $(PY) -m virtualenv -p $(INSTALL_PY) --download --no-site-packages $(PREBUILD_DIR) # Install required packages $(PREBUILD_BINDIR)/pip install -U pip $(PREBUILD_BINDIR)/pip install -U dist/$(NAME)-$(VER).tar.gz $(REQUIREMENTS) $(PREBUILD_BINDIR)/pip install -U -r requirements-git.txt # RECORD files are used by wheels for checksum. They contain path names which # match the buildroot and must be removed or the package will fail to build. - find $(PREBUILD_DIR) -name "RECORD" -exec rm -rf {} \; + # find $(PREBUILD_DIR) -name "RECORD" -exec rm -rf {} \; # Change the virtualenv path to the target installation direcotry. $(RELOCATE_BIN) --source=$(PREBUILD_DIR) --destination=$(INSTALL_DIR) # Remove sources for Clang diff --git a/README.rst b/README.rst index 72ce67cb..b31f32a3 100644 --- a/README.rst +++ b/README.rst @@ -1,6 +1,14 @@ Polemarch ========= +.. image:: https://gitlab.com/vstconsulting/polemarch/badges/master/pipeline.svg + :target: https://gitlab.com/vstconsulting/polemarch/commits/master + :alt: Tests status + +.. image:: https://gitlab.com/vstconsulting/polemarch/badges/master/coverage.svg + :target: https://gitlab.com/vstconsulting/polemarch/pipelines + :alt: Code coverage + .. image:: https://readthedocs.org/projects/polemarch/badge/?version=stable :target: http://polemarch.readthedocs.io/en/stable/?badge=stable :alt: Documentation Status @@ -8,11 +16,11 @@ Polemarch .. image:: https://badge.fury.io/py/polemarch.svg :target: https://badge.fury.io/py/polemarch -**Polemarch** is service for orchestration infrastructure by ansible. -Simply WEB gui for orchestration infrastructure by ansible playbooks. +**Polemarch** is a service for infrastructure management based on ansible. +Simple WEB gui for infrastructure management using ansible playbooks or ansible modules. Official site: -https://gitlab.com/vstconsulting/polemarch +http://about.polemarch.org For any questions you could use issues tracker: https://gitlab.com/vstconsulting/polemarch/issues @@ -24,96 +32,31 @@ https://gitlab.com/vstconsulting/polemarch/issues Features -------- +* execution templates; * scheduled tasks execution; -* share hosts, groups between projects; +* sharing of hosts, groups, inventories between projects; * history of tasks execution with all details; * easy configurable clustering for reliability and scalability: -* import Ansible projects from Git repository or tar archive; +* import of Ansible projects from Git repository or tar archive; +* import of `inventory file `_; +* support of quick project deployment; * documentation: http://polemarch.readthedocs.io/en/latest/ ; -* groups of hosts and groups hierarchy; -* multiuser; -* responsive design; - -Red Hat/CentOS installation ---------------------------- - -1. Download rpm from official site: - https://github.com/vstconsulting/polemarch/releases - -2. Install it with command - - .. sourcecode:: bash - - sudo yum localinstall polemarch-X.X.X-0.x86_64.rpm. - -3. Run services with commands - - .. sourcecode:: bash - - sudo service polemarchweb start - sudo service polemarchworker start - -That's it. Polemarch web panel on 8080 port. Default administrative account is -admin/admin. - -If you have any problems with `.deb` package, you can install it from PyPi: -https://polemarch.readthedocs.io/en/stable/quickstart.html#install-from-pypi - -Note: If you using authentication by password at some of your machines -managed by Polemarch, you also must install ``sshpass`` package because it -required for ansible to autheticate via ssh by password. It available in -EPEL for Red Hat/CentOS. Also you can use specify ``connection`` command line -argument during playbook run as ``paramiko``. When ansible uses paramiko to -make ssh connection, ``sshpass`` not necessary. - -Ubuntu/Debian installation --------------------------- - -1. Download deb from official site: - https://github.com/vstconsulting/polemarch/releases - -2. Install it with command - - .. sourcecode:: bash - - sudo dpkg -i polemarch_X.X.X-0_amd64.deb || sudo apt-get install -f - -3. Run services with commands - - .. sourcecode:: bash - - sudo service polemarchweb start - sudo service polemarchworker start - -Note for Debian 9 users: Polemarch currently built with libssl1.0.0, so you -need to install it for your distro: - - .. sourcecode:: bash - - wget http://ftp.us.debian.org/debian/pool/main/o/openssl/libssl1.0.0_1.0.2l-1~bpo8+1_amd64.deb - sudo dpkg -i libssl1.0.0_1.0.2l-1~bpo8+1_amd64.deb - -That's it. Polemarch web panel on 8080 port. Default administrative account is -admin/admin. - -If you have any problems with `.deb` package, you can install it from PyPi: -https://polemarch.readthedocs.io/en/stable/quickstart.html#install-from-pypi +* support of hosts groups and groups hierarchy; +* support of multi user connection; +* support of `hooks `_; +* user friendly interface. Quickstart ---------- -After you install Polemarch by instructions above you can use it without any -further configurations. Interface is pretty intuitive and common for any web -application. - -Default installation is suitable for most simple and common cases, but +`Default installation `_ +is suitable for most simple and common cases, but Polemarch is highly configurable system. If you need something more advanced (scalability, dedicated DB, custom cache, logging or directories) you can -always configure Polemarch like said in documentation at -http://polemarch.readthedocs.io/en/latest/ +always configure Polemarch like it is said in `documentation `_. + How to contribute ----------------- -Refer to documentation page about that: -http://polemarch.readthedocs.io/en/stable/contribute.html \ No newline at end of file +Refer to the documentation page about `contribution `_. diff --git a/deb.mk b/deb.mk index 617d27d5..51a0fbef 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, libssl-dev, libsasl2-dev, libldap2-dev +Build-Depends: debhelper (>= 9), python3-virtualenv, python3-pip, python3-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, libssl-dev, libsasl2-dev, libldap2-dev +Depends: $${shlibs:Depends}, $${misc:Depends}, python3-virtualenv, libffi6, libssl-dev, sshpass, libpython3.5, git, libyaml-dev, libkrb5-dev, libssl-dev, libsasl2-dev, libldap2-dev Description: $(SUMMARY) $(DESCRIPTION) endef diff --git a/doc/api_schema.yaml b/doc/api_schema.yaml index 77a0f47f..63a93098 100755 --- a/doc/api_schema.yaml +++ b/doc/api_schema.yaml @@ -9,9 +9,9 @@ info: name: System Administrator x-links: Request: - - url: https://gitlab.com/vstconsulting/polemarch/issues/new?issuable_template%5D=Ask&issue%5Btitle%5D=Ask%20about%20version%200.2.2 + - url: https://gitlab.com/vstconsulting/polemarch/issues/new?issuable_template%5D=Ask&issue%5Btitle%5D=Ask%20about%20version%200.2.3 name: Question - - url: https://gitlab.com/vstconsulting/polemarch/issues/new?issuable_template%5D=Bug&issue%5Btitle%5D=Bug%20in%20version%200.2.2 + - url: https://gitlab.com/vstconsulting/polemarch/issues/new?issuable_template%5D=Bug&issue%5Btitle%5D=Bug%20in%20version%200.2.3 name: Bug report - url: https://gitlab.com/vstconsulting/polemarch/issues/new?issuable_template%5D=Feature%20request&issue%5Btitle%5D= name: Feature request @@ -22,9 +22,9 @@ info: url: https://gitlab.com/vstconsulting/polemarch.git name: Official repository x-versions: - application: 0.2.2 - library: 0.2.2 - vstutils: 1.2.1 + application: 0.2.3 + library: 0.2.3 + vstutils: 1.3.0 django: 1.11.15 ansible: 2.6.4 version: v2 @@ -11899,7 +11899,6 @@ definitions: History: required: - mode - - status type: object properties: id: @@ -11944,8 +11943,13 @@ definitions: status: title: Status type: string - maxLength: 50 - minLength: 1 + enum: + - DELAY + - RUN + - OK + - ERROR + - OFFLINE + - INTERRUPTED stop_time: title: Stop time type: string @@ -12154,8 +12158,13 @@ definitions: status: title: Status type: string + enum: + - NEW + - WAIT_SYNC + - SYNC + - ERROR + - OK readOnly: true - minLength: 1 ProjectCreateMaster: required: - name @@ -12228,8 +12237,13 @@ definitions: status: title: Status type: string + enum: + - NEW + - WAIT_SYNC + - SYNC + - ERROR + - OK readOnly: true - minLength: 1 revision: title: Revision type: string @@ -12265,12 +12279,54 @@ definitions: $ref: '#/definitions/Module' value_field: name view_field: path - verbose: - title: Verbose - description: verbose mode (-vvv for more, -vvvv to enable connection debugging) + args: + title: Args + description: module arguments + type: string + background: + title: Background + description: run asynchronously, failing after X seconds (default=N/A) + type: integer + become: + title: Become + description: run operations with become (does not imply password prompting) + type: boolean + default: false + become_method: + title: Become method + description: 'privilege escalation method to use (default=sudo), valid choices: + [ sudo | su | pbrun | pfexec | doas | dzdo | ksu | runas | pmrun | enable + | machinectl ]' + type: string + become_user: + title: Become user + description: run operations as this user (default=root) + type: string + check: + title: Check + description: don't make any changes; instead, try to predict some of the changes + that may occur + type: boolean + default: false + connection: + title: Connection + description: connection type to use (default=smart) + type: string + diff: + title: Diff + description: when changing (small) files and templates, show the differences + in those files; works great with --check + type: boolean + default: false + extra_vars: + title: Extra vars + description: set additional variables as key=value or YAML/JSON, if filename + prepend with @ + type: string + forks: + title: Forks + description: specify number of parallel processes to use (default=5) type: integer - default: 0 - maximum: 4 inventory: title: Inventory description: specify inventory host path or comma separated host list. --inventory-file @@ -12282,129 +12338,61 @@ definitions: $ref: '#/definitions/Inventory' value_field: id view_field: name + key_file: + title: Key file + description: use this file to authenticate the connection + type: string + format: secretfile + limit: + title: Limit + description: further limit selected hosts to an additional pattern + type: string list_hosts: title: List hosts description: outputs a list of matching hosts; does not execute anything else type: boolean default: false - limit: - title: Limit - description: further limit selected hosts to an additional pattern - type: string module_path: title: Module path description: prepend colon-separated path(s) to module library (default=['/home/grey/.ansible/plugins/modules', '/usr/share/ansible/plugins/modules']) type: string - extra_vars: - title: Extra vars - description: set additional variables as key=value or YAML/JSON, if filename - prepend with @ - type: string - forks: - title: Forks - description: specify number of parallel processes to use (default=5) - type: integer - vault_password_file: - title: Vault password file - description: vault password file - type: string - format: secretfile - vault_id: - title: Vault id - description: the vault identity to use - type: string one_line: title: One line description: condense output type: boolean default: false - tree: - title: Tree - description: log output to this directory - type: string - poll: - title: Poll - description: set the poll interval if using -B (default=15) - type: integer - background: - title: Background - description: run asynchronously, failing after X seconds (default=N/A) - type: integer - check: - title: Check - description: don't make any changes; instead, try to predict some of the changes - that may occur - type: boolean - default: false - syntax_check: - title: Syntax check - description: perform a syntax check on the playbook, but do not execute it - type: boolean - default: false - diff: - title: Diff - description: when changing (small) files and templates, show the differences - in those files; works great with --check - type: boolean - default: false playbook_dir: title: Playbook dir description: Since this tool does not use playbooks, use this as a subsitute playbook directory.This sets the relative path for many features including roles/ group_vars/ etc. type: string - args: - title: Args - description: module arguments - type: string + poll: + title: Poll + description: set the poll interval if using -B (default=15) + type: integer private_key: title: Private key description: use this file to authenticate the connection type: string format: secretfile - key_file: - title: Key file - description: use this file to authenticate the connection - type: string - format: secretfile - user: - title: User - description: connect as this user (default=None) - type: string - connection: - title: Connection - description: connection type to use (default=smart) - type: string - timeout: - title: Timeout - description: override the connection timeout in seconds (default=10) - type: integer - ssh_common_args: - title: Ssh common args - description: specify common arguments to pass to sftp/scp/ssh (e.g. ProxyCommand) + scp_extra_args: + title: Scp extra args + description: specify extra arguments to pass to scp only (e.g. -l) type: string sftp_extra_args: title: Sftp extra args description: specify extra arguments to pass to sftp only (e.g. -f, -l) type: string - scp_extra_args: - title: Scp extra args - description: specify extra arguments to pass to scp only (e.g. -l) + ssh_common_args: + title: Ssh common args + description: specify common arguments to pass to sftp/scp/ssh (e.g. ProxyCommand) type: string ssh_extra_args: title: Ssh extra args description: specify extra arguments to pass to ssh only (e.g. -R) type: string - sudo: - title: Sudo - description: run operations with sudo (nopasswd) (deprecated, use become) - type: boolean - default: false - sudo_user: - title: Sudo user - description: desired sudo user (default=root) (deprecated, use become) - type: string su: title: Su description: run operations with su (deprecated, use become) @@ -12415,21 +12403,47 @@ definitions: description: run operations with su as this user (default=None) (deprecated, use become) type: string - become: - title: Become - description: run operations with become (does not imply password prompting) + sudo: + title: Sudo + description: run operations with sudo (nopasswd) (deprecated, use become) type: boolean default: false - become_method: - title: Become method - description: 'privilege escalation method to use (default=sudo), valid choices: - [ sudo | su | pbrun | pfexec | doas | dzdo | ksu | runas | pmrun | enable - | machinectl ]' + sudo_user: + title: Sudo user + description: desired sudo user (default=root) (deprecated, use become) type: string - become_user: - title: Become user - description: run operations as this user (default=root) + syntax_check: + title: Syntax check + description: perform a syntax check on the playbook, but do not execute it + type: boolean + default: false + timeout: + title: Timeout + description: override the connection timeout in seconds (default=10) + type: integer + tree: + title: Tree + description: log output to this directory + type: string + user: + title: User + description: connect as this user (default=None) + type: string + vault_id: + title: Vault id + description: the vault identity to use type: string + vault_password_file: + title: Vault password file + description: vault password file + type: string + format: secretfile + verbose: + title: Verbose + description: verbose mode (-vvv for more, -vvvv to enable connection debugging) + type: integer + default: 0 + maximum: 4 group: title: Group type: string @@ -12465,62 +12479,20 @@ definitions: $ref: '#/definitions/Playbook' value_field: playbook view_field: name - verbose: - title: Verbose - description: verbose mode (-vvv for more, -vvvv to enable connection debugging) - type: integer - default: 0 - maximum: 4 - inventory: - title: Inventory - description: specify inventory host path or comma separated host list. --inventory-file - is deprecated - type: string - format: autocomplete - additionalProperties: - model: - $ref: '#/definitions/Inventory' - value_field: id - view_field: name - list_hosts: - title: List hosts - description: outputs a list of matching hosts; does not execute anything else + become: + title: Become + description: run operations with become (does not imply password prompting) type: boolean default: false - limit: - title: Limit - description: further limit selected hosts to an additional pattern - type: string - module_path: - title: Module path - description: prepend colon-separated path(s) to module library (default=['/home/grey/.ansible/plugins/modules', - '/usr/share/ansible/plugins/modules']) - type: string - extra_vars: - title: Extra vars - description: set additional variables as key=value or YAML/JSON, if filename - prepend with @ - type: string - forks: - title: Forks - description: specify number of parallel processes to use (default=5) - type: integer - vault_password_file: - title: Vault password file - description: vault password file - type: string - format: secretfile - vault_id: - title: Vault id - description: the vault identity to use - type: string - tags: - title: Tags - description: only run plays and tasks tagged with these values + become_method: + title: Become method + description: 'privilege escalation method to use (default=sudo), valid choices: + [ sudo | su | pbrun | pfexec | doas | dzdo | ksu | runas | pmrun | enable + | machinectl ]' type: string - skip_tags: - title: Skip tags - description: only run plays and tasks whose tags do not match these values + become_user: + title: Become user + description: run operations as this user (default=root) type: string check: title: Check @@ -12528,30 +12500,58 @@ definitions: that may occur type: boolean default: false - syntax_check: - title: Syntax check - description: perform a syntax check on the playbook, but do not execute it - type: boolean - default: false + connection: + title: Connection + description: connection type to use (default=smart) + type: string diff: title: Diff description: when changing (small) files and templates, show the differences in those files; works great with --check type: boolean default: false - force_handlers: - title: Force handlers - description: run handlers even if a task fails - type: boolean - default: false + extra_vars: + title: Extra vars + description: set additional variables as key=value or YAML/JSON, if filename + prepend with @ + type: string flush_cache: title: Flush cache description: clear the fact cache for every host in inventory type: boolean default: false - list_tasks: - title: List tasks - description: list all tasks that would be executed + force_handlers: + title: Force handlers + description: run handlers even if a task fails + type: boolean + default: false + forks: + title: Forks + description: specify number of parallel processes to use (default=5) + type: integer + inventory: + title: Inventory + description: specify inventory host path or comma separated host list. --inventory-file + is deprecated + type: string + format: autocomplete + additionalProperties: + model: + $ref: '#/definitions/Inventory' + value_field: id + view_field: name + key_file: + title: Key file + description: use this file to authenticate the connection + type: string + format: secretfile + limit: + title: Limit + description: further limit selected hosts to an additional pattern + type: string + list_hosts: + title: List hosts + description: outputs a list of matching hosts; does not execute anything else type: boolean default: false list_tags: @@ -12559,62 +12559,50 @@ definitions: description: list all available tags type: boolean default: false - step: - title: Step - description: 'one-step-at-a-time: confirm each task before running' + list_tasks: + title: List tasks + description: list all tasks that would be executed type: boolean default: false - start_at_task: - title: Start at task - description: start the playbook at the task matching this name + module_path: + title: Module path + description: prepend colon-separated path(s) to module library (default=['/home/grey/.ansible/plugins/modules', + '/usr/share/ansible/plugins/modules']) type: string private_key: title: Private key description: use this file to authenticate the connection type: string format: secretfile - key_file: - title: Key file - description: use this file to authenticate the connection + scp_extra_args: + title: Scp extra args + description: specify extra arguments to pass to scp only (e.g. -l) type: string - format: secretfile - user: - title: User - description: connect as this user (default=None) + sftp_extra_args: + title: Sftp extra args + description: specify extra arguments to pass to sftp only (e.g. -f, -l) type: string - connection: - title: Connection - description: connection type to use (default=smart) + skip_tags: + title: Skip tags + description: only run plays and tasks whose tags do not match these values type: string - timeout: - title: Timeout - description: override the connection timeout in seconds (default=10) - type: integer ssh_common_args: title: Ssh common args description: specify common arguments to pass to sftp/scp/ssh (e.g. ProxyCommand) type: string - sftp_extra_args: - title: Sftp extra args - description: specify extra arguments to pass to sftp only (e.g. -f, -l) - type: string - scp_extra_args: - title: Scp extra args - description: specify extra arguments to pass to scp only (e.g. -l) - type: string ssh_extra_args: title: Ssh extra args description: specify extra arguments to pass to ssh only (e.g. -R) type: string - sudo: - title: Sudo - description: run operations with sudo (nopasswd) (deprecated, use become) + start_at_task: + title: Start at task + description: start the playbook at the task matching this name + type: string + step: + title: Step + description: 'one-step-at-a-time: confirm each task before running' type: boolean default: false - sudo_user: - title: Sudo user - description: desired sudo user (default=root) (deprecated, use become) - type: string su: title: Su description: run operations with su (deprecated, use become) @@ -12625,25 +12613,50 @@ definitions: description: run operations with su as this user (default=None) (deprecated, use become) type: string - become: - title: Become - description: run operations with become (does not imply password prompting) + sudo: + title: Sudo + description: run operations with sudo (nopasswd) (deprecated, use become) type: boolean default: false - become_method: - title: Become method - description: 'privilege escalation method to use (default=sudo), valid choices: - [ sudo | su | pbrun | pfexec | doas | dzdo | ksu | runas | pmrun | enable - | machinectl ]' + sudo_user: + title: Sudo user + description: desired sudo user (default=root) (deprecated, use become) type: string - become_user: - title: Become user - description: run operations as this user (default=root) + syntax_check: + title: Syntax check + description: perform a syntax check on the playbook, but do not execute it + type: boolean + default: false + tags: + title: Tags + description: only run plays and tasks tagged with these values type: string + timeout: + title: Timeout + description: override the connection timeout in seconds (default=10) + type: integer + user: + title: User + description: connect as this user (default=None) + type: string + vault_id: + title: Vault id + description: the vault identity to use + type: string + vault_password_file: + title: Vault password file + description: vault password file + type: string + format: secretfile + verbose: + title: Verbose + description: verbose mode (-vvv for more, -vvvv to enable connection debugging) + type: integer + default: 0 + maximum: 4 ProjectHistory: required: - mode - - status type: object properties: id: @@ -12689,8 +12702,13 @@ definitions: status: title: Status type: string - maxLength: 50 - minLength: 1 + enum: + - DELAY + - RUN + - OK + - ERROR + - OFFLINE + - INTERRUPTED stop_time: title: Stop time type: string diff --git a/doc/config.rst b/doc/config.rst index cc87db1d..0e7a4854 100644 --- a/doc/config.rst +++ b/doc/config.rst @@ -50,7 +50,7 @@ example than general howto) you must do such steps: pip install polemarch -#. Setup DB-server. MySQL for example. For all nodes edit :ref:`database` +#. Setup DB-server. MariaDB for example. For all nodes edit :ref:`database` and provide credential for you database. Like this: .. sourcecode:: ini @@ -70,11 +70,11 @@ example than general howto) you must do such steps: [cache] backend = django_redis.cache.RedisCache - location = redis://redis-server:6379/1 + location = redis://redis_hostname_or_ip:6379/1 [locks] backend = django_redis.cache.RedisCache - location = redis://redis-server:6379/2 + location = redis://redis_hostname_or_ip:6379/2 #. Setup some network filesystem. NFS, for example. Mount it in the same directory of all worker-intended nodes. Write this directory in :ref:`main`. @@ -179,6 +179,18 @@ So in this case authorization logic will be the following: server creates session for him. +* **debug** - Enable debug mode. Default: false. +* **allowed_hosts** - Comma separated list of domains, which allowed to serve. Default: ``*``. +* **ldap-server** - LDAP server connection. +* **ldap-default-domain** - Default domain for auth. +* **timezone** - Timezone of web-application. Default: UTC. +* **log_level** - Logging level. Default: WARNING. +* **enable_admin_panel** - Enable or disable Django Admin panel. Defaul: false. +* **projects_dir** - Path where projects will be stored. +* **hooks_dir** - Path where hook scripts stored. +* **executor_path** - Path for polemarch-ansible wrapper binary. + + .. _database: Database settings @@ -198,21 +210,22 @@ use some of client-server database (SQLite not suitable) shared for all nodes. If you use MySQL there is a list of required settings, that you should create for correct database work. -Firstly, if you use MySQL and you have set timezone different from "UTC" you should run +Firstly, if you use MariaDB and you have set timezone different from "UTC" you should run 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: +Secondly, for correct MariaDB work you should set next options in ``settings.ini`` file: .. sourcecode:: bash [database.options] + connect_timeout = 10 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: +Finally, you should add some options to MariaDB configuration: .. sourcecode:: bash @@ -240,6 +253,7 @@ additional plugins. You can find details about cache configuration at `_. In clusterization scenario we advice to share cache between nodes to speedup their work using client-server cache realizations. +We recommend to use Redis in production environments. .. _locks: @@ -258,6 +272,20 @@ example, is not suitable. In case of clusterization we strongly recommend to use Redis or Memcached as backend for that purpose. Cache and locks backend can be same, but don't forget about requirement we said above. + +.. _session: + +Session cache settings +---------------------- + +Section ``[session]``. + +Polemarch store sessions in :ref:`database`, but for better performance, +we use a cache-based session backend. It is based on Django cache, so there is +another bunch of same settings as :ref:`cache`. By default, +settings getted from :ref:`cache`. + + .. _rpc: Rpc settings @@ -273,6 +301,31 @@ and Celery itself. Those kinds of settings: broker backend, number of worker-processes per node and some settings used for troubleshoot server-broker-worker interaction problems. + +* **connection** - Celery broker connection. Read more: http://docs.celeryproject.org/en/latest/userguide/configuration.html#conf-broker-settings Default: ``filesystem:///var/tmp``. +* **concurrency** - Count of celery worker threads. Default: 4. +* **heartbeat** - Interval between sending heartbeat packages, which says that connection still alive. Default: 10. +* **enable_worker** - Enable or disable worker with webserver. Default: true. +* **clone_retry_count** - Count of retrys on project sync operation. + + +.. _worker: + +Worker settings +--------------- + +Section ``[worker]``. + +Celery worker options for start. Useful settings: + +* **loglevel** - Celery worker logging level. Default: from main section ``log_level``. +* **pidfile** - Celery worker pidfile. Default: ``/run/polemarch_worker.pid`` +* **autoscale** - Options for autoscaling. Two comma separated numbers: max,min. +* **beat** - Enable or disable celery beat scheduler. Default: true. + +Other settings can be getted from command ``celery worker --help``. + + .. _web: Web settings @@ -280,8 +333,23 @@ Web settings Section ``[web]``. -Here placed settings related to web-server. Those settings like: allowed hosts, -static files directory or pagination limit. +Here placed settings related to web-server. Those settings like: +session_timeout, static_files_url or pagination limit. + +* **session_timeout** - Session life-cycle time. Default: 2w (two weeks). +* **rest_page_limit** - Default limit of objects in API list. Default: 1000. +* **public_openapi** - Allow to have access to OpenAPI schema from public. Default: ``false``. + + +.. _git: + +Git settings +------------ + +Sections ``[git.fetch]`` and ``[git.clone]``. + +Options for git commands. See options in ``git fetch --help`` or ``git clone --help``. + Production web settings ----------------------- diff --git a/doc/gui.rst b/doc/gui.rst index 20f95819..d7c2939f 100644 --- a/doc/gui.rst +++ b/doc/gui.rst @@ -72,7 +72,6 @@ As you can see, the form of new project creation consist of 5 fields: After project creation you will see the next page: .. image:: gui_screenshots/test_project_1.png -.. image:: gui_screenshots/test_project_2.png As you can see there are some new fields on this page: @@ -104,7 +103,7 @@ As you can see there are some new fields on this page: Also there are some new buttons: -Sublist buttons: +Sublinks buttons: * **group** - this button opens project's group list. * **history** - this button opens project executions' history list. @@ -139,13 +138,14 @@ Polemarch for pulling your changes. As you can see, now project's status is "NEW", so we need to click on "sync" button to get all needed data from your GIT project. +.. image:: gui_screenshots/test_project_2.png .. image:: gui_screenshots/test_project_3.png After your project's status has been changed into "OK" you can confidently start working with Polemarch. Module execution ---------------- +---------------- The simplest way to start using Polemarch is to execute module. To make this action click on 'execute module' button on project page. @@ -155,7 +155,7 @@ To make this action click on 'execute module' button on project page. As you can see, there are 2 fields on this page: * **module** - autocomplete field with the list of project's modules. -* **add field** - select field, that provides user with new variables field for module execution. +* **add field** - select field, that provides user with new variables fields for module execution. Also there is only one button on this page: @@ -181,14 +181,13 @@ When status of your module execution changes to "OK" you will see the next page: .. image:: gui_screenshots/execute_module_3.png .. image:: gui_screenshots/execute_module_4.png -.. image:: gui_screenshots/execute_module_5.png Template -------- In previous abstract to execute module we needed to fill several fields. -To do it before every module/playbook is rather inconvenient. +To do it before every module/playbook execution is rather inconvenient. In this case Polemarch templates save our time and nerves. Polemarch template is an object, that saves all options that user used during task execution. @@ -228,13 +227,13 @@ After template creation you will see the next page: .. image:: gui_screenshots/create_template_4.png -As you can see there is only one new fields on this page: +As you can see there is only one new field on this page: * **id** - |id_field_def| Also there are several buttons here: -Sublist buttons: +Sublinks buttons: * **option** - this button opens template's option list. * **variables** - this button opens project's variables list. @@ -296,7 +295,6 @@ When status of your template execution changes to "OK" you will see the next pag .. image:: gui_screenshots/execute_template_2.png .. image:: gui_screenshots/execute_template_3.png -.. image:: gui_screenshots/execute_template_4.png Periodic tasks @@ -314,7 +312,7 @@ which Polemarch makes by himself with some interval. Let's create periodic task, based on our "test-task-template". To do it open project page: -.. image:: gui_screenshots/test_project_3.png +.. image:: gui_screenshots/test_project_2.png And click on "periodic task" button: @@ -372,10 +370,6 @@ As you can see there is only one new fields on this page: Also there are several buttons here: -Sublist buttons: - -* **variables** - this button opens periodic task's variables list. - Action buttons: * **save** - |save_button_def| @@ -432,7 +426,7 @@ As you can see there are 2 new fields on this page: Also there are some new buttons here: -Sublist buttons: +Sublinks buttons: * **all groups** - this button opens inventory's all groups list (list of groups, which includes also groups that are nested into inventory groups). @@ -464,7 +458,7 @@ There are 2 buttons here: * **create** - |create_button_def| group. * **add** - this button opens the all group list from database, -from which you can choose group for this inventory. + from which you can choose group for this inventory. We need to create group. To do it click on "create" button. @@ -491,7 +485,7 @@ As you can see there are 2 new fields on this page: Also there are some new buttons here: -Sublist buttons: +Sublinks buttons: * **host** - this button opens group's host list. * **variables** - this button opens group's variables list. @@ -518,7 +512,7 @@ There are 2 buttons here: * **create** - |create_button_def| host. * **add** - this button opens the all host list from database, -from which you can choose host for this group. + from which you can choose host for this group. We need to create host. To do it click on "create" button. @@ -532,6 +526,7 @@ As you can see, the form of new host creation consist of following fields: for what purpose this host was created or something like this. * **type** - type of host (RANGE, HOST). + * RANGE - range of IPs or hosts. * HOST - single host. @@ -547,7 +542,7 @@ As you can see there are 2 new fields on this page: Also there are some new buttons here: -Sublist buttons: +Sublinks buttons: * **variables** - this button opens host's variables list. @@ -627,7 +622,7 @@ And click on "Import inventory" button. Then you will see the next page: As you can see, the form of "Import inventory" action consist of 2 fields: * **name** - name of your inventory. -* **inventory file** - value of your inventory file. +* **inventory file** - content of your inventory file. After filling of all fields you should click on "Exec" button and then you will see page of your imported inventory: @@ -783,7 +778,7 @@ Hooks Polemarch has his own system of hooks. Polemarch hooks are synchronous and you can appoint them on different events -like “after end task”, “before start task” and so on. +like “after task end”, “before task start” and so on. WARNING: You should be accurate with hooks appointment, because the more hooks you have, the more time they need for execution and, @@ -862,9 +857,9 @@ As you can see there is only one new fields on this page: Also there are several buttons here: -Sublist buttons: +Sublinks buttons: -* **settings** - this button opens dashboard settings of current user. +* **settings** - this button opens interface settings of current user. Action buttons: diff --git a/doc/gui_screenshots/change_password.png b/doc/gui_screenshots/change_password.png index e959b736..727d0c9f 100644 Binary files a/doc/gui_screenshots/change_password.png and b/doc/gui_screenshots/change_password.png differ diff --git a/doc/gui_screenshots/create_group.png b/doc/gui_screenshots/create_group.png index 77224a7e..46df8d70 100644 Binary files a/doc/gui_screenshots/create_group.png and b/doc/gui_screenshots/create_group.png differ diff --git a/doc/gui_screenshots/create_hook.png b/doc/gui_screenshots/create_hook.png index ab9c3283..701c0ebe 100644 Binary files a/doc/gui_screenshots/create_hook.png and b/doc/gui_screenshots/create_hook.png differ diff --git a/doc/gui_screenshots/create_host.png b/doc/gui_screenshots/create_host.png index 2ca6fe8f..6b02e1a8 100644 Binary files a/doc/gui_screenshots/create_host.png and b/doc/gui_screenshots/create_host.png differ diff --git a/doc/gui_screenshots/create_inventory.png b/doc/gui_screenshots/create_inventory.png index ed190161..da411585 100644 Binary files a/doc/gui_screenshots/create_inventory.png and b/doc/gui_screenshots/create_inventory.png differ diff --git a/doc/gui_screenshots/create_periodic_task_1.png b/doc/gui_screenshots/create_periodic_task_1.png index e9d07f17..465b0cfa 100644 Binary files a/doc/gui_screenshots/create_periodic_task_1.png and b/doc/gui_screenshots/create_periodic_task_1.png differ diff --git a/doc/gui_screenshots/create_periodic_task_2.png b/doc/gui_screenshots/create_periodic_task_2.png index 852386f6..a03e9e88 100644 Binary files a/doc/gui_screenshots/create_periodic_task_2.png and b/doc/gui_screenshots/create_periodic_task_2.png differ diff --git a/doc/gui_screenshots/create_project.png b/doc/gui_screenshots/create_project.png index 80dbd1aa..6e8db260 100644 Binary files a/doc/gui_screenshots/create_project.png and b/doc/gui_screenshots/create_project.png differ diff --git a/doc/gui_screenshots/create_project_with_polemarch_yaml.png b/doc/gui_screenshots/create_project_with_polemarch_yaml.png index 375191c2..c1aa01b0 100644 Binary files a/doc/gui_screenshots/create_project_with_polemarch_yaml.png and b/doc/gui_screenshots/create_project_with_polemarch_yaml.png differ diff --git a/doc/gui_screenshots/create_project_with_polemarch_yaml_2.png b/doc/gui_screenshots/create_project_with_polemarch_yaml_2.png index c0b15833..4c76c125 100644 Binary files a/doc/gui_screenshots/create_project_with_polemarch_yaml_2.png and b/doc/gui_screenshots/create_project_with_polemarch_yaml_2.png differ diff --git a/doc/gui_screenshots/create_project_with_polemarch_yaml_3.png b/doc/gui_screenshots/create_project_with_polemarch_yaml_3.png index a0ff1709..2efeb779 100644 Binary files a/doc/gui_screenshots/create_project_with_polemarch_yaml_3.png and b/doc/gui_screenshots/create_project_with_polemarch_yaml_3.png differ diff --git a/doc/gui_screenshots/create_project_with_polemarch_yaml_4.png b/doc/gui_screenshots/create_project_with_polemarch_yaml_4.png index 5ee086f2..6451fbe4 100644 Binary files a/doc/gui_screenshots/create_project_with_polemarch_yaml_4.png and b/doc/gui_screenshots/create_project_with_polemarch_yaml_4.png differ diff --git a/doc/gui_screenshots/create_project_with_polemarch_yaml_5.png b/doc/gui_screenshots/create_project_with_polemarch_yaml_5.png index d4c7fc4b..6d8de2c0 100644 Binary files a/doc/gui_screenshots/create_project_with_polemarch_yaml_5.png and b/doc/gui_screenshots/create_project_with_polemarch_yaml_5.png differ diff --git a/doc/gui_screenshots/create_template.png b/doc/gui_screenshots/create_template.png index f3bce141..a010e744 100644 Binary files a/doc/gui_screenshots/create_template.png and b/doc/gui_screenshots/create_template.png differ diff --git a/doc/gui_screenshots/create_template_2.png b/doc/gui_screenshots/create_template_2.png index 4b58f1e4..8d505765 100644 Binary files a/doc/gui_screenshots/create_template_2.png and b/doc/gui_screenshots/create_template_2.png differ diff --git a/doc/gui_screenshots/create_template_3.png b/doc/gui_screenshots/create_template_3.png index 7b546fef..f4b6b688 100644 Binary files a/doc/gui_screenshots/create_template_3.png and b/doc/gui_screenshots/create_template_3.png differ diff --git a/doc/gui_screenshots/create_template_4.png b/doc/gui_screenshots/create_template_4.png index 82221893..934dfcaa 100644 Binary files a/doc/gui_screenshots/create_template_4.png and b/doc/gui_screenshots/create_template_4.png differ diff --git a/doc/gui_screenshots/create_template_variable.png b/doc/gui_screenshots/create_template_variable.png index 6f11f09d..60d74f70 100644 Binary files a/doc/gui_screenshots/create_template_variable.png and b/doc/gui_screenshots/create_template_variable.png differ diff --git a/doc/gui_screenshots/create_template_variable_2.png b/doc/gui_screenshots/create_template_variable_2.png index 2e26390c..34cba53e 100644 Binary files a/doc/gui_screenshots/create_template_variable_2.png and b/doc/gui_screenshots/create_template_variable_2.png differ diff --git a/doc/gui_screenshots/create_user.png b/doc/gui_screenshots/create_user.png index 572388dd..f3dc5838 100644 Binary files a/doc/gui_screenshots/create_user.png and b/doc/gui_screenshots/create_user.png differ diff --git a/doc/gui_screenshots/dashboard.png b/doc/gui_screenshots/dashboard.png index 9779214c..dd55cceb 100644 Binary files a/doc/gui_screenshots/dashboard.png and b/doc/gui_screenshots/dashboard.png differ diff --git a/doc/gui_screenshots/execute_module_1.png b/doc/gui_screenshots/execute_module_1.png index 9ab00582..da2cbffc 100644 Binary files a/doc/gui_screenshots/execute_module_1.png and b/doc/gui_screenshots/execute_module_1.png differ diff --git a/doc/gui_screenshots/execute_module_2.png b/doc/gui_screenshots/execute_module_2.png index c099061b..e98ef7b8 100644 Binary files a/doc/gui_screenshots/execute_module_2.png and b/doc/gui_screenshots/execute_module_2.png differ diff --git a/doc/gui_screenshots/execute_module_3.png b/doc/gui_screenshots/execute_module_3.png index a2a6a03c..64b2329e 100644 Binary files a/doc/gui_screenshots/execute_module_3.png and b/doc/gui_screenshots/execute_module_3.png differ diff --git a/doc/gui_screenshots/execute_module_4.png b/doc/gui_screenshots/execute_module_4.png index 1a47def9..b7ccb0b9 100644 Binary files a/doc/gui_screenshots/execute_module_4.png and b/doc/gui_screenshots/execute_module_4.png differ diff --git a/doc/gui_screenshots/execute_module_5.png b/doc/gui_screenshots/execute_module_5.png deleted file mode 100644 index f3b42cee..00000000 Binary files a/doc/gui_screenshots/execute_module_5.png and /dev/null differ diff --git a/doc/gui_screenshots/execute_template_1.png b/doc/gui_screenshots/execute_template_1.png index 5cb961fd..7d3ce2db 100644 Binary files a/doc/gui_screenshots/execute_template_1.png and b/doc/gui_screenshots/execute_template_1.png differ diff --git a/doc/gui_screenshots/execute_template_2.png b/doc/gui_screenshots/execute_template_2.png index 8afee4ee..c00f2517 100644 Binary files a/doc/gui_screenshots/execute_template_2.png and b/doc/gui_screenshots/execute_template_2.png differ diff --git a/doc/gui_screenshots/execute_template_3.png b/doc/gui_screenshots/execute_template_3.png index 3bb4c124..18d68dd8 100644 Binary files a/doc/gui_screenshots/execute_template_3.png and b/doc/gui_screenshots/execute_template_3.png differ diff --git a/doc/gui_screenshots/execute_template_4.png b/doc/gui_screenshots/execute_template_4.png deleted file mode 100644 index cf990638..00000000 Binary files a/doc/gui_screenshots/execute_template_4.png and /dev/null differ diff --git a/doc/gui_screenshots/hooks_empty_list.png b/doc/gui_screenshots/hooks_empty_list.png index 7dd5dd7c..08cee526 100644 Binary files a/doc/gui_screenshots/hooks_empty_list.png and b/doc/gui_screenshots/hooks_empty_list.png differ diff --git a/doc/gui_screenshots/import_inventory.png b/doc/gui_screenshots/import_inventory.png index 659560c0..9cff3c33 100644 Binary files a/doc/gui_screenshots/import_inventory.png and b/doc/gui_screenshots/import_inventory.png differ diff --git a/doc/gui_screenshots/import_inventory_2.png b/doc/gui_screenshots/import_inventory_2.png index dbe8c677..336e6d54 100644 Binary files a/doc/gui_screenshots/import_inventory_2.png and b/doc/gui_screenshots/import_inventory_2.png differ diff --git a/doc/gui_screenshots/import_inventory_3.png b/doc/gui_screenshots/import_inventory_3.png index 580c009b..9c5b91eb 100644 Binary files a/doc/gui_screenshots/import_inventory_3.png and b/doc/gui_screenshots/import_inventory_3.png differ diff --git a/doc/gui_screenshots/import_inventory_4.png b/doc/gui_screenshots/import_inventory_4.png index 62e8ef15..957a4379 100644 Binary files a/doc/gui_screenshots/import_inventory_4.png and b/doc/gui_screenshots/import_inventory_4.png differ diff --git a/doc/gui_screenshots/import_inventory_5.png b/doc/gui_screenshots/import_inventory_5.png index 83d720bd..b668e62d 100644 Binary files a/doc/gui_screenshots/import_inventory_5.png and b/doc/gui_screenshots/import_inventory_5.png differ diff --git a/doc/gui_screenshots/import_inventory_6.png b/doc/gui_screenshots/import_inventory_6.png index 5c2d759c..418fd18b 100644 Binary files a/doc/gui_screenshots/import_inventory_6.png and b/doc/gui_screenshots/import_inventory_6.png differ diff --git a/doc/gui_screenshots/periodic_task_empty_list.png b/doc/gui_screenshots/periodic_task_empty_list.png index ce67a3e5..a77eff86 100644 Binary files a/doc/gui_screenshots/periodic_task_empty_list.png and b/doc/gui_screenshots/periodic_task_empty_list.png differ diff --git a/doc/gui_screenshots/periodic_task_execution_history.png b/doc/gui_screenshots/periodic_task_execution_history.png index d0eb4ba7..3d10ab98 100644 Binary files a/doc/gui_screenshots/periodic_task_execution_history.png and b/doc/gui_screenshots/periodic_task_execution_history.png differ diff --git a/doc/gui_screenshots/test_group.png b/doc/gui_screenshots/test_group.png index 40ad3e67..8a9a8770 100644 Binary files a/doc/gui_screenshots/test_group.png and b/doc/gui_screenshots/test_group.png differ diff --git a/doc/gui_screenshots/test_host.png b/doc/gui_screenshots/test_host.png index cc574f1a..24acf4cf 100644 Binary files a/doc/gui_screenshots/test_host.png and b/doc/gui_screenshots/test_host.png differ diff --git a/doc/gui_screenshots/test_host_variables.png b/doc/gui_screenshots/test_host_variables.png index 0366a7b1..9019f895 100644 Binary files a/doc/gui_screenshots/test_host_variables.png and b/doc/gui_screenshots/test_host_variables.png differ diff --git a/doc/gui_screenshots/test_host_variables_1.png b/doc/gui_screenshots/test_host_variables_1.png index a5660ffc..3b6b151b 100644 Binary files a/doc/gui_screenshots/test_host_variables_1.png and b/doc/gui_screenshots/test_host_variables_1.png differ diff --git a/doc/gui_screenshots/test_host_variables_2.png b/doc/gui_screenshots/test_host_variables_2.png index 01460c7d..54280fe8 100644 Binary files a/doc/gui_screenshots/test_host_variables_2.png and b/doc/gui_screenshots/test_host_variables_2.png differ diff --git a/doc/gui_screenshots/test_inventory.png b/doc/gui_screenshots/test_inventory.png index 751230d4..22510b61 100644 Binary files a/doc/gui_screenshots/test_inventory.png and b/doc/gui_screenshots/test_inventory.png differ diff --git a/doc/gui_screenshots/test_inventory_group.png b/doc/gui_screenshots/test_inventory_group.png index 95e4d71f..9edfeaee 100644 Binary files a/doc/gui_screenshots/test_inventory_group.png and b/doc/gui_screenshots/test_inventory_group.png differ diff --git a/doc/gui_screenshots/test_inventory_group_host.png b/doc/gui_screenshots/test_inventory_group_host.png index 29d26c96..82ef0387 100644 Binary files a/doc/gui_screenshots/test_inventory_group_host.png and b/doc/gui_screenshots/test_inventory_group_host.png differ diff --git a/doc/gui_screenshots/test_periodic_task.png b/doc/gui_screenshots/test_periodic_task.png index 92a12670..550ef6fb 100644 Binary files a/doc/gui_screenshots/test_periodic_task.png and b/doc/gui_screenshots/test_periodic_task.png differ diff --git a/doc/gui_screenshots/test_project_1.png b/doc/gui_screenshots/test_project_1.png index 56c40fff..06ea0d58 100644 Binary files a/doc/gui_screenshots/test_project_1.png and b/doc/gui_screenshots/test_project_1.png differ diff --git a/doc/gui_screenshots/test_project_2.png b/doc/gui_screenshots/test_project_2.png index 01ae616a..079c9c69 100644 Binary files a/doc/gui_screenshots/test_project_2.png and b/doc/gui_screenshots/test_project_2.png differ diff --git a/doc/gui_screenshots/test_project_3.png b/doc/gui_screenshots/test_project_3.png index 5659563f..ac09e7a8 100644 Binary files a/doc/gui_screenshots/test_project_3.png and b/doc/gui_screenshots/test_project_3.png differ diff --git a/doc/gui_screenshots/test_user.png b/doc/gui_screenshots/test_user.png index 08fa0ae1..bf0bde54 100644 Binary files a/doc/gui_screenshots/test_user.png and b/doc/gui_screenshots/test_user.png differ diff --git a/doc/gui_screenshots/user_list.png b/doc/gui_screenshots/user_list.png index 975fa846..cd715219 100644 Binary files a/doc/gui_screenshots/user_list.png and b/doc/gui_screenshots/user_list.png differ diff --git a/doc/gui_screenshots/user_settings.png b/doc/gui_screenshots/user_settings.png index 1198ae32..745dbaf6 100644 Binary files a/doc/gui_screenshots/user_settings.png and b/doc/gui_screenshots/user_settings.png differ diff --git a/doc/index.rst b/doc/index.rst index 514e650e..7df9cf37 100644 --- a/doc/index.rst +++ b/doc/index.rst @@ -15,6 +15,21 @@ Contact us on `any questions or `report bugs `_. +We also help you via: + +`Twitter +`_ - we post information about new releases and new features + +`Slack +`_ - support channel + +`Stack Overflow +`_ - tag for questions about our application on Stack Overflow + +.. toctree:: + :hidden: + + Home .. toctree:: :maxdepth: 2 diff --git a/doc/polemarch-sphinx-theme/layout.html b/doc/polemarch-sphinx-theme/layout.html index 797a08ae..81f4a5b9 100644 --- a/doc/polemarch-sphinx-theme/layout.html +++ b/doc/polemarch-sphinx-theme/layout.html @@ -1,10 +1,28 @@ {% extends "vst-sphinx-theme/layout.html" %} +{%- block css %} + {{ super() }} + +{%- endblock %} + {% block logo %} - +{% endblock %} + +{% block additional_information %} + + + +| + + + +| + + + {% endblock %} \ No newline at end of file diff --git a/doc/quickstart.rst b/doc/quickstart.rst index 1e6dc5aa..3a2d8880 100644 --- a/doc/quickstart.rst +++ b/doc/quickstart.rst @@ -59,7 +59,7 @@ Install from PyPI .. sourcecode:: bash - virualenv polemarch + virtualenv polemarch cd polemarch source bin/activate @@ -67,13 +67,13 @@ Install from PyPI .. sourcecode:: bash - pip install polemarch + pip install -U polemarch #. Edit config file: #. Open `/etc/polemarch/settings.ini`, if it does not exist, create it. Polemarch uses config from this directory. - #. The default database is SQLite3, but MySQL is recommended. Settings needed for correct work database: + #. The default database is SQLite3, but MariaDB is recommended. Settings needed for correct work MariaDB database: .. code-block:: ini @@ -82,8 +82,25 @@ Install from PyPI name = db_name user = db_user password = db_password - host = db_host - port = db_port + + [database.options] + connect_timeout = 20 + init_command = SET sql_mode='STRICT_TRANS_TABLES', default_storage_engine=INNODB, NAMES 'utf8', CHARACTER SET 'utf8', SESSION collation_connection = 'utf8_unicode_ci' + + #. Create database in MariaDB use this commands: + + .. code-block:: mysql + + SET @@global.innodb_large_prefix = 1; + create user db_user; + create database db_name default CHARACTER set utf8 default COLLATE utf8_general_ci; + grant all on db_name.* to 'db_user'@'localhost' identified by 'db_password'; + + #. Then, if you use MariaDB and you have set timezone different from "UTC" you should run next command: + + .. sourcecode:: bash + + mysql_tzinfo_to_sql /usr/share/zoneinfo | mysql -u root -p mysql #. The default cache system is file based cache, but RedisCache is recommended. Settings needed for correct RedisCache work: @@ -91,18 +108,18 @@ Install from PyPI [cache] backend = django_redis.cache.RedisCache - location = redis://redis-server:6379/1 + location = redis://127.0.0.1:6379/1 [locks] backend = django_redis.cache.RedisCache - location = redis://redis-server:6379/2 + location = redis://127.0.0.1:6379/2 #. The default celery broker is file Celery broker, but Redis is recommended. Settings needed for correct Redis work: .. code-block:: ini [rpc] - connection = redis://redis-server:6379/3 + connection = redis://127.0.0.1:6379/3 heartbeat = 5 concurrency = 8 enable_worker = true @@ -116,15 +133,21 @@ Install from PyPI threads = 4 harakiri = 120 vacuum = True + pidfile = /run/polemarch.pid + log_file = /var/log/{PROG_NAME}_web.log [worker] - logfile = /tmp/{PROG_NAME}_worker.log # output will be /tmp/polemarch_worker.log - pidfile = /tmp/{PROG_NAME}_worker.pid # output will be /tmp/polemarch_worker.pid + # output will be /run/polemarch_worker.log + logfile = /var/log/{PROG_NAME}_worker.log + # output will be /run/polemarch_worker.pid + pidfile = /run/{PROG_NAME}_worker.pid loglevel = INFO Also if you need to set your own path for logfile or pidfile, different from the path from example, you can do it, but make sure, that user, which starts Polemarch has write-permissions for these directory and file. + If you run it as root, we recommend to add in ```[uwsig]``` params ```uid``` and ```gid``` + (`read more `_). #. Make migrations: @@ -147,18 +170,16 @@ If you need to restart Polemarch use following command: polemarchctl webserver reload=/var/run/polemarch/web.pid -If you use another directory for storing Polemarch pid file, use path to this file -instead of default ``/var/run/polemarch/web.pid``. +If you use another directory for storing Polemarch pid file, use path to this file. If you need to stop Polemarch use following command: .. sourcecode:: bash - polemarchctl webserver stop=/var/run/polemarch/web.pid + polemarchctl webserver stop=/run/polemarch.pid -If you use another directory for storing Polemarch pid file, use path to this file -instead of default ``/var/run/polemarch/web.pid``. +If you use another directory for storing Polemarch pid file, use path to this file. Quickstart @@ -183,13 +204,13 @@ To upload the data, use the command: .. sourcecode:: bash - sudo -u polemarch /opt/polemarch/bin/polemarchctl dumpdata --natural-foreign --natural-primary -a --indent 4 -o /home/polemarch/backup.json + polemarchctl dumpdata --natural-foreign --natural-primary -a --indent 4 -o /home/polemarch/backup.json To load the saved data, use: .. sourcecode:: bash - sudo -u polemarch /opt/polemarch/bin/polemarchctl loaddata /home/polemarch/backup.json + polemarchctl loaddata /home/polemarch/backup.json The second way is to use SQL backup or to copy you database manually. We strongly recommend to use this way of making a backup, because @@ -253,7 +274,7 @@ To run a ``migrate`` command you should run follow code: .. sourcecode:: python - sudo -u polemarch /opt/polemarch/bin/polemarchctl migrate + polemarchctl migrate Create superuser ---------------- @@ -264,7 +285,7 @@ To create a superuser account use the follow command: .. sourcecode:: python - sudo -u polemarch /opt/polemarch/bin/polemarchctl createsuperuser + polemarchctl createsuperuser This command prompts for all required user's options. @@ -275,7 +296,7 @@ To change password use the follow command: .. sourcecode:: python - sudo -u polemarch /opt/polemarch/bin/polemarchctl changepassword [] + polemarchctl changepassword [] It prompts you to enter a new password twice for the given user. If the entries are identical, this immediately becomes the new password. diff --git a/doc/screencast.gif b/doc/screencast.gif index 626bdd8f..65559ca2 100644 Binary files a/doc/screencast.gif and b/doc/screencast.gif differ diff --git a/polemarch/__init__.py b/polemarch/__init__.py index 687fb39d..e7347936 100644 --- a/polemarch/__init__.py +++ b/polemarch/__init__.py @@ -31,6 +31,6 @@ "VST_ROOT_URLCONF": os.getenv("VST_ROOT_URLCONF", 'vstutils.urls'), } -__version__ = "0.2.2" +__version__ = "0.2.3" prepare_environment(**default_settings) diff --git a/polemarch/api/v2/serializers.py b/polemarch/api/v2/serializers.py index c59ab244..bcbc48a7 100644 --- a/polemarch/api/v2/serializers.py +++ b/polemarch/api/v2/serializers.py @@ -184,13 +184,14 @@ class _WithPermissionsSerializer(_SignalSerializer): def is_valid(self, *args, **kwargs): result = super(_WithPermissionsSerializer, self).is_valid(*args, **kwargs) - self.validated_data['owner'] = self.validated_data.get( - 'owner', self.current_user() - ) + if not hasattr(self, 'instance'): # nocv + self.validated_data['owner'] = self.validated_data.get( + 'owner', self.current_user() + ) return result def current_user(self): - return self.context['request'].user + return self.context['request'].user # nocv class UserSerializer(vst_serializers.UserSerializer): @@ -261,7 +262,6 @@ class WidgetSettingsSerializer(vst_serializers.JsonObjectSerializer): pmwGroupsCounter = CounterWidgetSettingSerializer() pmwHostsCounter = CounterWidgetSettingSerializer() pmwChartWidget = WidgetSettingSerializer() - pmwAnsibleModuleWidget = WidgetSettingSerializer() class UserSettingsSerializer(vst_serializers.JsonObjectSerializer): @@ -295,6 +295,8 @@ class Meta: class HistorySerializer(_SignalSerializer): + status = serializers.ChoiceField(choices=models.History.statuses, required=False) + class Meta: model = models.History fields = ( @@ -332,6 +334,7 @@ class Meta(HistorySerializer.Meta): class OneHistorySerializer(_SignalSerializer): + status = serializers.ChoiceField(choices=models.History.statuses, required=False) raw_stdout = serializers.SerializerMethodField(read_only=True) execution_time = vst_fields.UptimeField() @@ -850,7 +853,7 @@ def create(self, validated_data): class ProjectSerializer(_InventoryOperations): - status = vst_fields.VSTCharField(read_only=True) + status = serializers.ChoiceField(read_only=True, choices=models.Project.STATUSES) type = vst_fields.VSTCharField(read_only=True) class Meta: diff --git a/polemarch/main/migrations/0001_v2.py b/polemarch/main/migrations/0001_v2.py index df1670b0..a307cbae 100644 --- a/polemarch/main/migrations/0001_v2.py +++ b/polemarch/main/migrations/0001_v2.py @@ -155,7 +155,7 @@ class Migration(migrations.Migration): ('type', models.CharField(max_length=32)), ('when', models.CharField(default=None, max_length=32, null=True)), ('enable', models.BooleanField(default=True)), - ('recipients', models.CharField(max_length=16383)), + ('recipients', models.CharField(max_length=4096)), ], options={ 'abstract': False, @@ -451,21 +451,23 @@ class Migration(migrations.Migration): on_delete=django.db.models.deletion.CASCADE, to=settings.AUTH_USER_MODEL), ), - migrations.AlterIndexTogether( - name='template', - index_together=set([('id', 'name', 'kind', 'inventory', 'project')]), - ), - migrations.AlterIndexTogether( - name='historylines', - index_together=set( - [('history', 'line_number'), ('history',), ('line_number',)]), - ), - migrations.AlterIndexTogether( - name='history', - index_together=set([('id', 'project', 'mode', 'status', 'inventory', - 'start_time', 'stop_time', 'initiator', - 'initiator_type')]), - ), + # migrations.AlterIndexTogether( + # name='template', + # index_together=set([('id', 'name', 'kind', 'inventory', 'project')]), + # ), + # Fix bug with Django migrations + # Please, remove indexes manually. + # migrations.AlterIndexTogether( + # name='historylines', + # index_together=set( + # [('history', 'line_number'), ('history',), ('line_number',)]), + # ), + # migrations.AlterIndexTogether( + # name='history', + # index_together=set([('id', 'project', 'mode', 'status', 'inventory', + # 'start_time', 'stop_time', 'initiator', + # 'initiator_type')]), + # ), migrations.AlterIndexTogether( name='group', index_together=set([('children', 'id'), ('children',)]), diff --git a/polemarch/main/migrations/0003_models_optimization.py b/polemarch/main/migrations/0003_models_optimization.py new file mode 100644 index 00000000..8b14c2f0 --- /dev/null +++ b/polemarch/main/migrations/0003_models_optimization.py @@ -0,0 +1,84 @@ +# -*- coding: utf-8 -*- +# Generated by Django 1.11.15 on 2018-11-19 01:31 +from __future__ import unicode_literals + +from django.db import migrations, models +import django.db.models.deletion +import uuid + + +class Migration(migrations.Migration): + + dependencies = [ + ('main', '0002_modules_and_rename'), + ] + + operations = [ + migrations.AlterField( + model_name='history', + name='inventory', + field=models.ForeignKey(blank=True, default=None, null=True, on_delete=django.db.models.deletion.SET_NULL, related_name='history', related_query_name='history', to='main.Inventory'), + ), + migrations.AlterField( + model_name='history', + name='kind', + field=models.CharField(db_index=True, default='PLAYBOOK', max_length=50), + ), + migrations.AlterField( + model_name='history', + name='status', + field=models.CharField(db_index=True, max_length=50), + ), + migrations.AlterField( + model_name='hook', + name='enable', + field=models.BooleanField(db_index=True, default=True), + ), + migrations.AlterField( + model_name='hook', + name='recipients', + field=models.TextField(), + ), + migrations.AlterField( + model_name='hook', + name='type', + field=models.CharField(db_index=True, max_length=32), + ), + migrations.AlterField( + model_name='hook', + name='when', + field=models.CharField(db_index=True, default=None, max_length=32, null=True), + ), + migrations.AlterField( + model_name='periodictask', + name='inventory_file', + field=models.CharField(blank=True, max_length=2048, null=True), + ), + migrations.AlterField( + model_name='periodictask', + name='schedule', + field=models.CharField(max_length=768), + ), + migrations.AlterField( + model_name='task', + name='name', + field=models.CharField(default=uuid.uuid1, max_length=251), + ), + migrations.AlterField( + model_name='variable', + name='key', + field=models.CharField(max_length=512), + ), + migrations.AlterIndexTogether( + name='history', + index_together=set([]), + ), + migrations.AlterIndexTogether( + name='historylines', + index_together=set([]), + ), + migrations.AlterIndexTogether( + name='template', + index_together=set([]), + ), + ] diff --git a/polemarch/main/models/hooks.py b/polemarch/main/models/hooks.py index 3f85095d..88b0d819 100644 --- a/polemarch/main/models/hooks.py +++ b/polemarch/main/models/hooks.py @@ -51,10 +51,10 @@ class Hook(BModel): objects = HooksQuerySet.as_manager() handlers = HookHandlers("HOOKS", "'type' needed!") name = models.CharField(max_length=512, default=uuid.uuid1) - type = models.CharField(max_length=32, null=False) - when = models.CharField(max_length=32, null=True, default=None) - enable = models.BooleanField(default=True) - recipients = models.CharField(max_length=16383) + type = models.CharField(max_length=32, null=False, db_index=True) + when = models.CharField(max_length=32, null=True, default=None, db_index=True) + enable = models.BooleanField(default=True, db_index=True) + recipients = models.TextField() @property def reps(self): diff --git a/polemarch/main/models/projects.py b/polemarch/main/models/projects.py index f9b0dbab..05b8c693 100644 --- a/polemarch/main/models/projects.py +++ b/polemarch/main/models/projects.py @@ -109,6 +109,14 @@ def set_readme(self): 'boolean': bool, } + STATUSES = [ + 'NEW', + 'WAIT_SYNC', + 'SYNC', + 'ERROR', + 'OK', + ] + def __unicode__(self): return str(self.name) # pragma: no cover @@ -305,7 +313,7 @@ class Task(BModel): objects = TaskFilterQuerySet.as_manager() project = models.ForeignKey(Project, on_delete=models.CASCADE, related_query_name="playbook") - name = models.CharField(max_length=256, default=uuid.uuid1) + name = models.CharField(max_length=251, default=uuid.uuid1) playbook = models.CharField(max_length=256) class Meta: diff --git a/polemarch/main/models/tasks.py b/polemarch/main/models/tasks.py index 10bd1411..fbfb629a 100644 --- a/polemarch/main/models/tasks.py +++ b/polemarch/main/models/tasks.py @@ -42,9 +42,6 @@ class Template(ACLModel): class Meta: default_related_name = 'template' - index_together = [ - ["id", "name", "kind", "inventory", "project"] - ] template_fields = OrderedDict() template_fields["Task"] = ["playbook", "vars", "inventory"] @@ -199,7 +196,7 @@ class PeriodicTask(AbstractModel): related_query_name="periodic_task", null=True, blank=True) inventory_file = models.CharField(max_length=2*1024, null=True, blank=True) - schedule = models.CharField(max_length=4*1024) + schedule = models.CharField(max_length=768) type = models.CharField(max_length=10) save_result = models.BooleanField(default=True) enabled = models.BooleanField(default=True) @@ -363,18 +360,18 @@ class History(BModel): objects = HistoryQuerySet.as_manager() project = models.ForeignKey(Project, on_delete=models.CASCADE, related_query_name="history", null=True) - inventory = models.ForeignKey(Inventory, on_delete=models.CASCADE, + inventory = models.ForeignKey(Inventory, on_delete=models.SET_NULL, related_query_name="history", blank=True, null=True, default=None) mode = models.CharField(max_length=256) revision = models.CharField(max_length=256, blank=True, null=True) - kind = models.CharField(max_length=50, default="PLAYBOOK") + kind = models.CharField(max_length=50, default="PLAYBOOK", db_index=True) start_time = models.DateTimeField(default=timezone.now) stop_time = models.DateTimeField(blank=True, null=True) raw_args = models.TextField(default="") json_args = models.TextField(default="{}") raw_inventory = models.TextField(default="") - status = models.CharField(max_length=50) + status = models.CharField(max_length=50, db_index=True) initiator = models.IntegerField(default=0) # Initiator type should be always as in urls for api initiator_type = models.CharField(max_length=50, default="project") @@ -399,10 +396,6 @@ def __init__(self): class Meta: default_related_name = "history" ordering = ["-start_time"] - index_together = [ - ["id", "project", "mode", "status", "inventory", - "start_time", "stop_time", "initiator", "initiator_type"] - ] @property def working(self): @@ -550,7 +543,3 @@ class HistoryLines(BModel): class Meta: default_related_name = "raw_history_line" ordering = ['-line_gnumber', '-line_number'] - index_together = [ - ["history"], ["line_number"], - ["history", "line_number"] - ] diff --git a/polemarch/main/models/users.py b/polemarch/main/models/users.py index 1d0b5a3f..5836379c 100644 --- a/polemarch/main/models/users.py +++ b/polemarch/main/models/users.py @@ -6,7 +6,8 @@ from django.contrib.auth.models import Group as BaseGroup from django.contrib.auth.models import User as BaseUser -from .base import settings, models, BModel, ACLModel, BQuerySet +from django.contrib.auth import get_user_model +from .base import models, BModel, ACLModel, BQuerySet logger = logging.getLogger("polemarch") @@ -15,7 +16,7 @@ class ACLPermission(BModel): role = models.CharField(max_length=10) uagroup = models.ForeignKey('main.UserGroup', on_delete=None, blank=True, null=True) - user = models.ForeignKey(settings.AUTH_USER_MODEL, on_delete=None, + user = models.ForeignKey(get_user_model(), on_delete=None, blank=True, null=True) @property diff --git a/polemarch/main/models/vars.py b/polemarch/main/models/vars.py index d415228b..ac797f2a 100644 --- a/polemarch/main/models/vars.py +++ b/polemarch/main/models/vars.py @@ -51,7 +51,7 @@ class Variable(BModel): content_type = models.ForeignKey(ContentType, on_delete=models.CASCADE) object_id = models.PositiveIntegerField() content_object = GenericForeignKey('content_type', 'object_id') - key = models.CharField(max_length=128) + key = models.CharField(max_length=512) value = models.CharField(max_length=2*1024, null=True) variables_keys = [ diff --git a/polemarch/main/settings.ini b/polemarch/main/settings.ini index b2f71954..73ad109d 100644 --- a/polemarch/main/settings.ini +++ b/polemarch/main/settings.ini @@ -27,7 +27,7 @@ # name = {HOME}/db.sqlite3 [database.options] -# Database options settings +# Database options settings. Change to `connect_timeout` for MySQL ############################################################## # timeout = 10 @@ -107,14 +107,6 @@ ############################################################## # threads = 2 -# Folder where Polemarch should look for its static files (js, css and so on). -# Attention: change only filesystem path (after last '=' sign). Example: -# static-map = /static=/my/filesystem/path -# Attention: If you changing it, don't forget to change `static_files_url` in -# [web] section. -############################################################## -# static-map = /static=/opt/polemarch/lib/python2.7/site-packages/polemarch/static - # Where Polemarch web-server should place his PID-file ############################################################## # pidfile = /var/run/polemarch/web.pid diff --git a/polemarch/main/settings.py b/polemarch/main/settings.py index f8e7d527..40c3238a 100644 --- a/polemarch/main/settings.py +++ b/polemarch/main/settings.py @@ -56,7 +56,7 @@ { 'name': 'Projects', 'url': '/project', - 'span_class': 'glyphicon glyphicon-blackboard', + 'span_class': 'fa fa-fort-awesome', }, { 'name': 'Inventories', @@ -66,33 +66,33 @@ { 'name': 'Groups', 'url': '/group', - 'span_class': 'glyphicon glyphicon-tasks', + 'span_class': 'fa fa-tasks', }, { 'name': 'Hosts', 'url': '/host', - 'span_class': 'glyphicon glyphicon-hdd', + 'span_class': 'fa fa-codepen', }, ] }, { 'name': 'History', 'url': '/history', - 'span_class': 'glyphicon glyphicon-calendar', + 'span_class': 'fa fa-calendar', }, { 'name': 'System', - 'span_class': 'glyphicon glyphicon-cog', + 'span_class': 'fa fa-cog', 'sublinks': [ { 'name': 'Users', 'url': '/user', - 'span_class': 'glyphicon glyphicon-user', + 'span_class': 'fa fa-user', }, { 'name': 'Hooks', 'url': '/hook', - 'span_class': 'glyphicon glyphicon-console' + 'span_class': 'fa fa-plug' }, ] }, diff --git a/polemarch/main/tests/executions.py b/polemarch/main/tests/executions.py index dc960817..c2729e39 100644 --- a/polemarch/main/tests/executions.py +++ b/polemarch/main/tests/executions.py @@ -309,11 +309,11 @@ def get_complex_data(self, with_subs=False): # Execute actions _exec = dict( connection="local", inventory="<9[data][id]>", - module="ping", group="all", args="", forks=1 + module="ping", group="127.0.1.1", args="", forks=1, verbose=4 ) bulk_data += [ self.get_mod_bulk( - 'project', "<10[data][id]>", _exec, 'sync', + 'project', "<10[data][id]>", {}, 'sync', ), self.get_mod_bulk( 'project', "<10[data][id]>", _exec, 'execute_module', @@ -356,7 +356,10 @@ def generate_subs(self, bulk_results=None): self.assertEqual(history['mode'], 'ping') self.assertEqual(history['kind'], 'MODULE') self.assertEqual(history['inventory'], objects['inventory'][0]['id']) - self.assertEqual(history['status'], "OK") + self.assertEqual( + history['status'], "OK", + self.get_result('get', self.get_url('history', history['id'], 'raw')) + ) etalon = self._get_string_from_file('exemplary_complex_inventory') etalon = etalon.replace('PATH', '[~~ENCRYPTED~~]') etalon = etalon.replace('mypass', '[~~ENCRYPTED~~]') diff --git a/polemarch/main/tests/openapi.py b/polemarch/main/tests/openapi.py index 6983826f..37afb1b1 100644 --- a/polemarch/main/tests/openapi.py +++ b/polemarch/main/tests/openapi.py @@ -183,11 +183,11 @@ def test_openapi_schema(self): history = definitions['History'] objName = 'History' - self.check_fields(objName, history['required'], 'status', 'mode') + self.check_fields(objName, history['required'], 'mode') self.check_fields(objName, history['properties']['id'], **id_value) self.check_fields( objName, history['properties']['status'], - type='string', maxLength=50, minLength=1 + type='string', enum=self.get_model_class('History').statuses ) self.check_fields(objName, history['properties']['executor'], type='integer') self.check_fields(objName, history['properties']['project'], type='integer') @@ -220,12 +220,12 @@ def test_openapi_schema(self): objName = 'OneHistory' self.check_fields( - objName, oneHistory['required'], 'status', 'mode', 'execution_time' + objName, oneHistory['required'], 'mode', 'execution_time' ) self.check_fields(objName, oneHistory['properties']['id'], **id_value) self.check_fields( objName, oneHistory['properties']['status'], - type='string', maxLength=50, minLength=1 + type='string', enum=self.get_model_class('History').statuses ) self.check_fields(objName, oneHistory['properties']['executor'], type='integer') self.check_fields(objName, oneHistory['properties']['project'], type='integer') @@ -310,7 +310,7 @@ def test_openapi_schema(self): self.check_fields(objName, hook['properties']['enable'], type='boolean') self.check_fields( objName, hook['properties']['recipients'], - type='string', maxLength=16383, minLength=1 + type='string', minLength=1 ) del hook @@ -342,7 +342,7 @@ def test_openapi_schema(self): ) self.check_fields( objName, project['properties']['status'], - type='string', readOnly=True, minLength=1 + type='string', readOnly=True, enum=self.get_model_class('Project').STATUSES ) del project @@ -394,7 +394,7 @@ def test_openapi_schema(self): ) self.check_fields( objName, oneProject['properties']['status'], - type='string', readOnly=True, minLength=1 + type='string', readOnly=True, enum=self.get_model_class('Project').STATUSES ) self.check_fields( objName, oneProject['properties']['revision'], @@ -760,11 +760,11 @@ def test_openapi_schema(self): projectHistory = definitions['ProjectHistory'] objName = 'ProjectHistory' - self.check_fields(objName, projectHistory['required'], 'status', 'mode') + self.check_fields(objName, projectHistory['required'], 'mode') self.check_fields(objName, projectHistory['properties']['id'], **id_value) self.check_fields( objName, projectHistory['properties']['status'], - type='string', minLength=1, maxLength=50 + type='string', enum=self.get_model_class('History').statuses ) self.check_fields( objName, projectHistory['properties']['revision'], @@ -963,7 +963,7 @@ def test_openapi_schema(self): self.check_fields(objName, periodicTaskVariable['properties']['id'], **id_value) self.check_fields( objName, periodicTaskVariable['properties']['key'], - type='string', minLength=1, maxLength=128 + type='string', minLength=1, maxLength=512 ) self.check_fields( objName, periodicTaskVariable['properties']['value'], @@ -978,7 +978,7 @@ def test_openapi_schema(self): self.check_fields(objName, playbook['properties']['id'], **id_value) self.check_fields( objName, playbook['properties']['name'], - type='string', maxLength=256, minLength=1 + type='string', maxLength=251, minLength=1 ) self.check_fields( objName, playbook['properties']['playbook'], @@ -992,7 +992,7 @@ def test_openapi_schema(self): self.check_fields(objName, onePlaybook['properties']['id'], **id_value) self.check_fields( objName, onePlaybook['properties']['name'], - type='string', maxLength=256, minLength=1 + type='string', maxLength=251, minLength=1 ) self.check_fields( objName, onePlaybook['properties']['playbook'], @@ -1252,7 +1252,7 @@ def test_openapi_schema(self): widgetList = ['pmwUsersCounter', 'pmwProjectsCounter', 'pmwTemplatesCounter', 'pmwInventoriesCounter', 'pmwGroupsCounter', 'pmwHostsCounter', - 'pmwChartWidget', 'pmwAnsibleModuleWidget'] + 'pmwChartWidget'] self.check_fields(objName, widgetSettings['required'], *widgetList) ref = '#/definitions/CounterWidgetSetting' self.check_fields( @@ -1275,10 +1275,6 @@ def test_openapi_schema(self): self.check_fields( objName, widgetSettings['properties']['pmwChartWidget'], **{'$ref': ref} ) - self.check_fields( - objName, widgetSettings['properties']['pmwAnsibleModuleWidget'], - **{'$ref': ref} - ) del widgetSettings userSettings = definitions['UserSettings'] diff --git a/polemarch/static/css/polemarch-gui.css b/polemarch/static/css/polemarch-gui.css index 72323b49..f7dcf0ee 100644 --- a/polemarch/static/css/polemarch-gui.css +++ b/polemarch/static/css/polemarch-gui.css @@ -32,32 +32,29 @@ .td_history_start_time, .td_history_stop_time, .td_project-history_start_time, .td_project-history_stop_time{ - width:150px; + width:160px; } .td_history_status, -.td_project-history_status { - font-weight: bold; - vertical-align: middle; - text-align: center; - max-width: 110px; +.td_project-history_status{ + width: 125px; } -@media (max-width: 1200px) { +@media (max-width: 1400px) { .td_history_stop_time, .td_project-history_stop_time { display: none; } } -@media (max-width: 1100px) { - .td_history_mode, - .td_project-history_mode { +@media (max-width: 1300px) { + .td_history_kind, + .td_project-history_kind { display: none; } } -@media (max-width: 1050px) { +@media (max-width: 1200px) { .td_history_initiator, .td_project-history_initiator, .td_history_revision, @@ -66,69 +63,146 @@ } } -@media (max-width: 1000px) { +@media (max-width: 1100px) { .td_history_inventory, .td_project-history_inventory { display: none; } } -@media (max-width: 850px) { +@media (max-width: 1000px) { .td_history_executor, - .td_project-history_executor { + .td_project-history_executor, + .td_hook_recipients { display: none; } } -@media (max-width: 660px) { +@media (max-width: 780px) { .td_history_project, - .td_project-history_project { + .td_project-history_project, + .td_hook_when { display: none; } } -@media (max-width: 550px) { - .td_history_kind, - .td_project-history_kind { - display: none; +@media (max-width: 700px) { + .td_history_start_time, + .td_project-history_start_time { + width: 100px; } } -@media (max-width: 480px) { +@media (max-width: 600px) { .td_history_start_time, .td_project-history_start_time { - width: 70px; + display: none; } } -.tr_status_OFFLINE .td_history_status, -.tr_status_OFFLINE .td_project-history_status { - color: #9e9e9e; +@media (max-width: 480px) { + .td_user_is_active, + + .td_hook_type, + + .td_group_children, + .td_inventory-group_children, + .td_project-group_children, + .td_project-inventory-group_children, + + .td_host_type, + .td_group-host_type, + .td_inventory-host_type, + .td_project-host_type, + .td_project-group-host_type, + .td_project-inventory-host_type, + .td_project-inventory-group-host_type{ + display: none; + } } -.tr_status_INTERRUPTED .td_history_status, -.tr_status_INTERRUPTED .td_project-history_status { - color: #9b97e4; +@media (max-width: 480px) { + .column_history_actions, + .column_project-history_actions, + .td_hook_enable { + display: none; + } +} + +body { + --history-status-ok: #276900; + --history-status-error: #dc3545; + --history-status-interrupted: #9b97e4; + --history-status-delay: #808419; + --history-status-offline: #9e9e9e; + --history-status-run: #0085ff; + + --project-status-new: #8c8c8c; + --project-status-error: #dc3545; + --project-status-ok: #276900; + --project-status-wait-sync: #0085ff; + --project-status-sync: #ff8c00; + + --chart-legend-text-color: #666666; + --chart-axes-text-color: #666666; + --chart-axes-lines-color: efefef; } -.tr_status_RUN .td_history_status, -.tr_status_RUN .td_project-history_status { - color: #0085ff; + +.gui-skin-dark { + --history-status-ok: #56E401; + --history-status-error: #F61328; + --history-status-interrupted: #B68CF3; + --history-status-delay: #F1FF00; + --history-status-offline: #cccccc; + --history-status-run: #00D7FF; + + --project-status-new: #D2D2D0; + --project-status-error: #F61328; + --project-status-ok: #56E401; + --project-status-wait-sync: #00D7FF; + --project-status-sync: #FF9600; + + --chart-legend-text-color: #cccccc; + --chart-axes-text-color: #cccccc; + --chart-axes-lines-color: #bababa; } -.tr_status_ERROR .td_history_status, -.tr_status_ERROR .td_project-history_status { +.tr_status_offline .td_history_status, +.tr_status_offline .td_project-history_status, +.field_status_offline { + color: #var(--history-status-offline); } -.tr_status_DELAY .td_history_status, -.tr_status_DELAY .td_project-history_status { - color: #808419; +.tr_status_interrupted .td_history_status, +.tr_status_interrupted .td_project-history_status, +.field_status_interrupted { + color: var(--history-status-interrupted); } -.tr_status_OK .td_history_status, -.tr_status_OK .td_project-history_status { - color: #276900; +.tr_status_run .td_history_status, +.tr_status_run .td_project-history_status, +.field_status_run { + color: var(--history-status-run); +} + +.tr_status_error .td_history_status, +.tr_status_error .td_project-history_status, +.field_status_error { + color: var(--history-status-error); +} + +.tr_status_delay .td_history_status, +.tr_status_delay .td_project-history_status, +.field_status_delay { + color: var(--history-status-delay); +} + +.tr_status_ok .td_history_status, +.tr_status_ok .td_project-history_status, +.field_status_ok { + color: var(--history-status-ok); } .project-status{ @@ -137,25 +211,59 @@ vertical-align: middle; } -.project-status-SYNC{ - color: #f40; + +.tr_status_sync .td_project_status, +.field_status_sync { + color: var(--project-status-sync); } -.project-status-WAIT_SYNC{ - color: #0085ff; +.tr_status_wait_sync .td_project_status, +.field_status_wait_sync { + color: var(--project-status-wait-sync); } -.project-status-OK{ - color: #276900; +.tr_status_new .td_project_status, +.field_status_new { + color: var(--project-status-new); +} + +.tr_status_error .td_project_status, +.field_status_error { + color: var(--project-status-error); } +.tr_status_ok .td_project_status, +.field_status_ok { + color: var(--project-status-ok); +} + +.td_project_status { + display: none; + width: 15%; +} + +.td_project_type { + display: none; + width: 18%; +} + +@media (min-width: 767px) { + .td_project_type { + display: table-cell; + } +} +@media (min-width: 600px) { + .td_project_status { + display: table-cell; + } +} -.stdout-line .S{color:red}/* Строки красные */ -.stdout-line .func{color:blue}/* Юзер-функции синие */ -.stdout-line .C{color:orange}/* Комменты оранжевые */ -.stdout-line .kwrd{font-weight:bold}/* Ключевые слова полужирные */ -.stdout-line .R{color:gray} /*Серые регвыражения */ +.stdout-line .S{color:red}/* red - lines */ +.stdout-line .func{color:blue}/* blue - user-functions */ +.stdout-line .C{color:orange}/* comments - orange */ +.stdout-line .kwrd{font-weight:bold}/* bold - keywords */ +.stdout-line .R{color:gray} /* grey - regexps */ .stdout-line.fatal{ background: #ffe7e7; } @@ -166,6 +274,8 @@ min-height: 400px; line-height: 1; background: #000; + padding-left: 15px; + padding-right: 15px; } @media (min-width: 767px) { @@ -183,21 +293,21 @@ display: none; } -.history-mode .history-LeftInfo .btn-history-resize .glyphicon-resize-full{ +.history-mode .history-LeftInfo .btn-history-resize .fa-expand{ display: block; } -.history-mode.history-fullsize .history-LeftInfo .btn-history-resize .glyphicon-resize-full{ +.history-mode.history-fullsize .history-LeftInfo .btn-history-resize .fa-expand{ display: none; } -.history-mode .history-LeftInfo .btn-history-resize .glyphicon-resize-small{ +.history-mode .history-LeftInfo .btn-history-resize .fa-compress{ display: none; } -.history-mode.history-fullsize .history-LeftInfo .btn-history-resize .glyphicon-resize-small{ +.history-mode.history-fullsize .history-LeftInfo .btn-history-resize .fa-compress{ display: block; } @@ -224,7 +334,23 @@ display: block; width:160px; } - + +.text-logo { + margin-left: auto; + width: 165px; + float: inherit; +} + +.sidebar-collapse .text-logo { + display: none; +} + +.sidebar-collapse .main-sidebar:hover .text-logo { + display: inline-block; +} + + + #menu-system-hooks { display: none; } @@ -234,3 +360,10 @@ display: block; } +@media (max-width: 452px) { + #period-list { + width: 100%!important; + margin-top: 10px; + } + +} \ No newline at end of file diff --git a/polemarch/static/img/logo/horizontal.png b/polemarch/static/img/logo/horizontal.png deleted file mode 100644 index 7177effe..00000000 Binary files a/polemarch/static/img/logo/horizontal.png and /dev/null differ diff --git a/polemarch/static/img/logo/horizontal.svg b/polemarch/static/img/logo/horizontal.svg new file mode 100644 index 00000000..02054426 --- /dev/null +++ b/polemarch/static/img/logo/horizontal.svg @@ -0,0 +1,361 @@ + + + +image/svg+xml \ No newline at end of file diff --git a/polemarch/static/js/common.js b/polemarch/static/js/common.js index e946ec82..e0f7b6df 100644 --- a/polemarch/static/js/common.js +++ b/polemarch/static/js/common.js @@ -10,136 +10,9 @@ if(guiLocalSettings.get('hideMenu')) } } -function setActiveMenuLiBase() -{ - if(/\#\/project/.test(window.location.href)) - { - $("#Projects").addClass("active active-li active-bold"); - } - else if(/\#\/host/.test(window.location.href)) - { - $("#menu-inventories").addClass("menu-treeview-active active active-li"); - $("#menu-inventories-hosts").addClass("active-bold"); - $("#menu-inventories").removeClass("menu-treeview"); - } - else if(/\#\/group/.test(window.location.href)) - { - $("#menu-inventories").addClass("menu-treeview-active active active-li"); - $("#menu-inventories-groups").addClass("active-bold"); - $("#menu-inventories").removeClass("menu-treeview"); - } - else if(/\#\/inventory/.test(window.location.href)) - { - $("#menu-inventories").addClass("menu-treeview-active active active-li active-bold"); - $("#menu-inventories-inventories").addClass("active-bold"); - $("#menu-inventories").removeClass("menu-treeview"); - } - else if(/\#\/history/.test(window.location.href)){ - - $("#History").addClass("active active-li active-bold"); - } - else if(/\#\/hook/.test(window.location.href)) - { - $("#menu-system").addClass("menu-treeview-active active active-li"); - $("#menu-system-hooks").addClass("active-bold"); - $("#menu-system").removeClass("menu-treeview"); - } - else if(/\#\/team/.test(window.location.href)) - { - $("#menu-system").addClass("menu-treeview-active active active-li"); - $("#menu-system-teams").addClass("active-bold"); - $("#menu-system").removeClass("menu-treeview"); - } - else if(/\#\/user/.test(window.location.href) || /\#profile/.test(window.location.href)) - { - $("#menu-system").addClass("menu-treeview-active active active-li"); - $("#menu-system-users").addClass("active-bold"); - $("#menu-system").removeClass("menu-treeview"); - } - else - { - $("#menu-home").addClass("active active-li active-bold"); - } -} - - -function setActiveMenuLi() -{ - if($('li').is('.menu-treeview-active')) - { - var t=$(".menu-treeview-active"); - $(t).addClass("menu-treeview"); - $(t).removeClass("menu-treeview-active"); - } - - if($('li').is('.active-li')) - { - var t=$(".active-li"); - $(t).removeClass("active"); - $(t).removeClass("active-li"); - } - - if($('li').is('.active-bold')) - { - var g=$(".active-bold"); - $(g).removeClass("active-bold"); - } - - return setActiveMenuLiBase(); -} - -tabSignal.connect("spajs.open", setActiveMenuLi); - -/* - * Функция добавляет элементу меню (при наведении на него) - * css-класс hover-li, который добавляет необходимые стили. - * Добавление класса происходит не сразу, а после небольшой паузы. - * Это необходимо для того, чтобы выпавшее подменю быстро не пропадало - * при попытке навести курсор на него. - */ -$(".sidebar-menu > li").mouseenter(function () { - var thisEl = this; - setTimeout(function () { - var menuTreeviewMenues = $(".menu-treeview-menu"); - var bool = false; - for(var i=0; i { + obj.guiSkin.applySettings() + }, + }; + + form['history_status_error'] = { + color_var:"--history-status-error", + title:'History status error', + format:'color', + type: "string", + default:"#dc3545", + priority: 42, + value:obj.guiSkin.value.history_status_error, + onchange:() => { + obj.guiSkin.applySettings() + }, + }; + + form['history_status_interrupted'] = { + color_var:"--history-status-interrupted", + title:'History status interrupted', + format:'color', + type: "string", + default:"#9b97e4", + priority: 43, + value:obj.guiSkin.value.history_status_interrupted, + onchange:() => { + obj.guiSkin.applySettings() + }, + }; + + form['history_status_delay'] = { + color_var:"--history-status-delay", + title:'History status delay', + format:'color', + type: "string", + default:"#808419", + priority: 44, + value:obj.guiSkin.value.history_status_delay, + onchange:() => { + obj.guiSkin.applySettings() + }, + }; + + form['history_status_offline'] = { + color_var:"--history-status-offline", + title:'History status offline', + format:'color', + type: "string", + default:"#9e9e9e", + priority: 45, + value:obj.guiSkin.value.history_status_offline, + onchange:() => { + obj.guiSkin.applySettings() + }, + }; + + form['history_status_run'] = { + color_var:"--history-status-run", + title:'History status run', + format:'color', + type: "string", + default:"#0085ff", + priority: 46, + value:obj.guiSkin.value.history_status_run, + onchange:() => { + obj.guiSkin.applySettings() + }, + }; + + + form['project_status_new'] = { + color_var:"--project-status-new", + title:'Project status new', + format:'color', + type: "string", + default:"#bf71b7", + priority: 47, + value:obj.guiSkin.value.project_status_new, + onchange:() => { + obj.guiSkin.applySettings() + }, + }; + + form['project_status_error'] = { + color_var:"--project-status-error", + title:'Project status error', + format:'color', + type: "string", + default:"#dc3545", + priority: 48, + value:obj.guiSkin.value.project_status_error, + onchange:() => { + obj.guiSkin.applySettings() + }, + }; + + form['project_status_ok'] = { + color_var:"--project-status-ok", + title:'Project status ok', + format:'color', + type: "string", + default:"#276900", + priority: 49, + value:obj.guiSkin.value.project_status_ok, + onchange:() => { + obj.guiSkin.applySettings() + }, + }; + + form['project_status_wait_sync'] = { + color_var:"--project-status-wait-sync", + title:'Project status wait_sync', + format:'color', + type: "string", + default:"#0085ff", + priority: 50, + value:obj.guiSkin.value.project_status_wait_sync, + onchange:() => { + obj.guiSkin.applySettings() + }, + }; + + form['project_status_sync'] = { + color_var:"--project-status-sync", + title:'Project status sync', + format:'color', + type: "string", + default:"#ff8c00", + priority: 51, + value:obj.guiSkin.value.project_status_sync, + onchange:() => { + obj.guiSkin.applySettings() + }, + }; + + form['chart_legend_text_color'] = { + color_var:"--chart-legend-text-color", + title:'Chart legend text color', + format:'color', + type: "string", + default:"#666666", + priority: 52, + value:obj.guiSkin.value.chart_legend_text_color, + onchange:() => { + obj.guiSkin.applySettings() + }, + }; + + form['chart_axes_text_color'] = { + color_var:"--chart-axes-text-color", + title:'Chart legend text color', + format:'color', + type: "string", + default:"#666666", + priority: 53, + value:obj.guiSkin.value.chart_axes_text_color, + onchange:() => { + obj.guiSkin.applySettings() + }, + }; + + form['chart_axes_lines_color'] = { + color_var:"--chart-axes-lines-color", + title:'Chart axes lines color', + format:'color', + type: "string", + default:"#efefef", + priority: 54, + value:obj.guiSkin.value.chart_axes_lines_color, + onchange:() => { + obj.guiSkin.applySettings() + }, + }; +}); + + +tabSignal.connect('guiSkin.dark.init', function(obj) +{ + let form = obj.guiSkin.options.form; + + form['history_status_ok'] = { + color_var:"--history-status-ok", + title:'History status ok', + format:'color', + type: "string", + default:"#56E401", + priority: 41, + value:obj.guiSkin.value.history_status_ok, + onchange:() => { + obj.guiSkin.applySettings() + }, + }; + + form['history_status_error'] = { + color_var:"--history-status-error", + title:'History status error', + format:'color', + type: "string", + default:"#F61328", + priority: 42, + value:obj.guiSkin.value.history_status_error, + onchange:() => { + obj.guiSkin.applySettings() + }, + }; + + form['history_status_interrupted'] = { + color_var:"--history-status-interrupted", + title:'History status interrupted', + format:'color', + type: "string", + default:"#B68CF3", + priority: 43, + value:obj.guiSkin.value.history_status_interrupted, + onchange:() => { + obj.guiSkin.applySettings() + }, + }; + + form['history_status_delay'] = { + color_var:"--history-status-delay", + title:'History status delay', + format:'color', + type: "string", + default:"#F1FF00", + priority: 44, + value:obj.guiSkin.value.history_status_delay, + onchange:() => { + obj.guiSkin.applySettings() + }, + }; + + form['history_status_offline'] = { + color_var:"--history-status-offline", + title:'History status offline', + format:'color', + type: "string", + default:"#cccccc", + priority: 45, + value:obj.guiSkin.value.history_status_offline, + onchange:() => { + obj.guiSkin.applySettings() + }, + }; + + form['history_status_run'] = { + color_var:"--history-status-run", + title:'History status run', + format:'color', + type: "string", + default:"#00D7FF", + priority: 46, + value:obj.guiSkin.value.history_status_run, + onchange:() => { + obj.guiSkin.applySettings() + }, + }; + + + form['project_status_new'] = { + color_var:"--project-status-new", + title:'Project status new', + format:'color', + type: "string", + default:"#D2D2D0", + priority: 47, + value:obj.guiSkin.value.project_status_new, + onchange:() => { + obj.guiSkin.applySettings() + }, + }; + + form['project_status_error'] = { + color_var:"--project-status-error", + title:'Project status error', + format:'color', + type: "string", + default:"#F61328", + priority: 48, + value:obj.guiSkin.value.project_status_error, + onchange:() => { + obj.guiSkin.applySettings() + }, + }; + + form['project_status_ok'] = { + color_var:"--project-status-ok", + title:'Project status ok', + format:'color', + type: "string", + default:"#56E401", + priority: 49, + value:obj.guiSkin.value.project_status_ok, + onchange:() => { + obj.guiSkin.applySettings() + }, + }; + + form['project_status_wait_sync'] = { + color_var:"--project-status-wait-sync", + title:'Project status wait_sync', + format:'color', + type: "string", + default:"#00D7FF", + priority: 50, + value:obj.guiSkin.value.project_status_wait_sync, + onchange:() => { + obj.guiSkin.applySettings() + }, + }; + + form['project_status_sync'] = { + color_var:"--project-status-sync", + title:'Project status sync', + format:'color', + type: "string", + default:"#FF9600", + priority: 51, + value:obj.guiSkin.value.project_status_sync, + onchange:() => { + obj.guiSkin.applySettings() + }, + }; + + form['chart_legend_text_color'] = { + color_var:"--chart-legend-text-color", + title:'Chart legend text color', + format:'color', + type: "string", + default:"#cccccc", + priority: 52, + value:obj.guiSkin.value.chart_legend_text_color, + onchange:() => { + obj.guiSkin.applySettings() + }, + }; + + form['chart_axes_text_color'] = { + color_var:"--chart-axes-text-color", + title:'Chart axes lines color', + format:'color', + type: "string", + default:"#cccccc", + priority: 53, + value:obj.guiSkin.value.chart_axes_text_color, + onchange:() => { + obj.guiSkin.applySettings() + }, + }; + + form['chart_axes_lines_color'] = { + color_var:"--chart-axes-lines-color", + title:'Chart axes lines color', + format:'color', + type: "string", + default:"#bababa", + priority: 54, + value:obj.guiSkin.value.chart_axes_lines_color, + onchange:() => { + obj.guiSkin.applySettings() + }, + }; +}); \ No newline at end of file diff --git a/polemarch/static/js/pmDashboard.js b/polemarch/static/js/pmDashboard.js index 41b19cc5..7cd79957 100644 --- a/polemarch/static/js/pmDashboard.js +++ b/polemarch/static/js/pmDashboard.js @@ -28,7 +28,7 @@ guiDashboard.statsDataMomentType='day'; /** - * Двумерный массив с описанием списка отображаемых виджетов в каждой строке + * Two-dimensional array with structure of Dashboard widgets. * * @example * [ @@ -46,20 +46,20 @@ guiDashboard.model.widgets = [ ], ] -/* -*Двумерный массив, хранящий в себе настройки виджетов по умолчанию. +/** + * Two-dimensional array with default structure of Dashboard widgets. */ guiDashboard.model.defaultWidgets = [ [ - /**/{ - name:'pmwTemplatesCounter', - title:'Templates Counter', - sort:1, - active:true, - opt:{}, - type:1, - collapse:false, - }, + { + name:'pmwTemplatesCounter', + title:'Templates Counter', + sort:1, + active:true, + opt:{}, + type:1, + collapse:false, + }, { name:'pmwProjectsCounter', title:'Projects Counter', @@ -105,19 +105,19 @@ guiDashboard.model.defaultWidgets = [ type:1, collapse:false, }, - { - name:'pmwAnsibleModuleWidget', - title:'Run shell command', - sort:7, - active:true, - opt:{}, - type:0, - collapse:false, - }, + // { + // name:'pmwAnsibleModuleWidget', + // title:'Run shell command', + // sort:7, + // active:true, + // opt:{}, + // type:0, + // collapse:false, + // }, { name:'pmwChartWidget', title:'Tasks history', - sort:8, + sort:7, active:true, opt:{}, type:0, @@ -144,60 +144,70 @@ guiDashboard.model.defaultWidgets = [ ], ] -/* - * Массив, хранящий в себе настройки линий графика на странице Dashboard'а. +/** + * Array with Dashboard's Chart line settings. */ guiDashboard.model.ChartLineSettings = [ ] -/* - * Массив, хранящий в себе настройки по умолчанию линий графика на странице Dashboard'а. +/** + * Array with default Dashboard's Chart line settings. */ guiDashboard.model.defaultChartLineSettings = [ { name: "all_tasks", title: "All tasks", color: "#1f77b4", + bg_color:"rgba(31, 119, 180, 0.3)", active: true }, { name: "ok", title: "OK", color: "#276900", + bg_color:"rgba(39, 105, 0, 0.3)", active: true }, { name: "error", title: "ERROR", - color: "#333333", + color: "#dc3545", + bg_color:"rgba(220, 53, 69, 0.3)", active: true }, { name: "interrupted", title: "INTERRUPTED", color: "#9b97e4", + bg_color:"rgba(155, 151, 228, 0.3)", active: true }, { name: "delay", title: "DELAY", color: "#808419", + bg_color:"rgba(128, 132, 25, 0.3)", active: true }, { name: "offline", title: "OFFLINE", color: "#9e9e9e", + bg_color:"rgba(158, 158, 158, 0.3)", active: true } ] guiDashboard.model.autoupdateInterval = 15000; +guiDashboard.model.skinsSettings = {}; +guiDashboard.model.selectedSkin = 'default'; +guiDashboard.model.dataFromApiLoaded = false; + /** - * Функция полностью копирует настройки для линий графика. - * Подразумевается, что данная функция вызывается, когда пришел из API пустой JSON. + * Function copies all properties of default chart line settings. + * This function is supposed to be called when empty JSON was received from API. */ guiDashboard.cloneChartLineSettingsTotally = function(){ guiDashboard.model.ChartLineSettings = JSON.parse(JSON.stringify(guiDashboard.model.defaultChartLineSettings)); @@ -205,8 +215,8 @@ guiDashboard.cloneChartLineSettingsTotally = function(){ } /** - * Функция обновляет часть настроек линий графика, данные по которым пришли из API. - * Подразумевается, что данная функция вызывается, когда пришел из API непустой JSON. + * Function updates properties of chart line settings, info about which was received from API. + * This function is supposed to be called when not empty JSON was received from API. */ guiDashboard.cloneChartLineSettingsFromApi = function(data){ guiDashboard.model.ChartLineSettings = JSON.parse(JSON.stringify(guiDashboard.model.defaultChartLineSettings)); @@ -230,8 +240,8 @@ guiDashboard.cloneChartLineSettingsFromApi = function(data){ } /** - * Функция полностью копирует настройки по умолчанию для виджетов. - * Подразумевается, что данная функция вызывается, когда пришел из API пустой JSON. + * Function copies all properties of default widget settings. + * This function is supposed to be called when empty JSON was received from API. */ guiDashboard.cloneDefaultWidgetsTotally = function(){ for(var i in guiDashboard.model.defaultWidgets[0]) @@ -247,10 +257,10 @@ guiDashboard.cloneDefaultWidgetsTotally = function(){ } /** - * Функция копирует "статичные" настройки по умолчанию для виджетов. - * Под "статичными" понимается name, title, opt, type. - * Данные настройки не меняются в ходе работы пользователя с интерфейсом. - * Подразумевается, что данная функция вызывается, когда пришел из API непустой JSON. + * Function copies all properties of default 'static' widget settings. + * 'Static' settings - settings, that don't change during user work with GUI. + * For example, name, title, opt, type. + * This function is supposed to be called when not empty JSON was received from API. */ guiDashboard.cloneDefaultWidgetsStaticSettingsOnly = function(){ for(var i in guiDashboard.model.defaultWidgets[0]) @@ -265,11 +275,8 @@ guiDashboard.cloneDefaultWidgetsStaticSettingsOnly = function(){ } /** - * Функция добавляет виджету оставшиеся(не "статичные") настройки. - * Функция проверяет есть ли соответсвуют ли пришедшие настройки для виджетов из API тем, - * что хранятся в массиве с настройками по умолчанию. - * Если данное свойство соответсвует, то его значение присваивается настройкам виджета. - * В противном случае ему присваивается настройка по умолчанию. + * Function adds not static properties to widget. + * Function sets value from API if it is, otherwise, it sets default value. */ guiDashboard.clonetWidgetsSettingsFromApiAndVerify = function(data){ guiDashboard.cloneDefaultWidgetsStaticSettingsOnly(); @@ -296,14 +303,11 @@ guiDashboard.clonetWidgetsSettingsFromApiAndVerify = function(data){ } /** - * Функция проверяет необходимо ли посылать запрос к API для загрузки - * пользовательских настроек Dashboard'a (настройки виджетов, настройки линий графика). - * Например, если в модели отсутствует какой-либо виджет, - * либо у виджета отсутсвует какое-нибудь свойство, - * то запрос к API будет отправлен. - * @param {Object} defaultObj - объект с настройками по умолчанию - * @param {Object} currentObj - объект с текущими настройками - * + * Function checks Necessity to send API request for loading of user's dashboard settings. + * For example, if some property is missed in current widget object + * or even if widget is missed, request will be sent. + * @param {Object} defaultObj - object with default settings. + * @param {Object} currentObj - object with current settings. */ guiDashboard.checkNecessityToLoadDashboardSettingsFromApi = function(defaultObj, currentObj) { @@ -331,21 +335,20 @@ guiDashboard.checkNecessityToLoadDashboardSettingsFromApi = function(defaultObj, if(bool1 || bool2) { - //нужно послать запрос к api + // request will be sent return true; } else { - //не нужно посылать запрос к api + // request will not be sent return false; } } /** - *Функция создает объект, в который вносит актуальные настройки виджета, - *на основе изменений, внесенных в guiDashboard.model.widgets[0][i]. - *localObj- guiDashboard.model.widgets[0][i] - * @type Object + * Function creates object with current widget settings, + * based on changes in guiDashboard.model.widgets[0][i]. + * @param localObj(Object) - guiDashboard.model.widgets[0][i]. */ guiDashboard.getNewWidgetSettings = function(localObj) { @@ -357,9 +360,9 @@ guiDashboard.getNewWidgetSettings = function(localObj) } /** - *Функция заправшивает у API пользовательские настройки - *(настройки виджетов, настройки линий графика, интервал автообновлений). - *Если они есть(пришел не пустой объект), то данные настройки добавляются в guiDashboard.model. + * Function sends request to API for getting user's settings + * (dashboard settings, chartline settings, autoupdate interval). + * If request answer is not empty, API settings will be added to Dashboard model. */ guiDashboard.getUserSettingsFromAPI = function() { @@ -425,6 +428,26 @@ guiDashboard.setUserSettingsFromApiAnswer = function(data) { guiDashboard.cloneDefaultAutoupdateInterval() } + + if(data.skinsSettings) + { + guiDashboard.cloneDataSkinsFromApi(data.skinsSettings); + } + else + { + guiLocalSettings.set('skins_settings', guiDashboard.model.skinsSettings); + } + + if(data.selectedSkin) + { + guiDashboard.cloneSelectedSkinFromApi(data.selectedSkin); + } + else + { + guiDashboard.setSelectedSkin(guiDashboard.model.selectedSkin); + } + + guiDashboard.model.dataFromApiLoaded = true; } /* @@ -446,8 +469,38 @@ guiDashboard.cloneDefaultAutoupdateInterval = function() } /** - *Функция сохраняет в API пользовательские настройки Dashboard'a - *(настройки виджетов, настройки линий графика). + * Function sets skinsSettings from API to local storage and to guiDashboard.model.skinsSettings. + */ +guiDashboard.cloneDataSkinsFromApi = function(skins) +{ + guiDashboard.model.skinsSettings = $.extend(true, {}, skins); + guiLocalSettings.set('skins_settings', guiDashboard.model.skinsSettings); +} + +/** + * Function sets selected skin. + */ +guiDashboard.setSelectedSkin = function(selectedSkin) +{ + guiCustomizer.setSkin(selectedSkin); + guiCustomizer.skin.init(); + guiCustomizer.render(); +} + +/** + * Function sets selected skin form api; + */ +guiDashboard.cloneSelectedSkinFromApi = function(selectedSkin) +{ + guiDashboard.model.selectedSkin = selectedSkin; + guiDashboard.setSelectedSkin(selectedSkin); +} + + + +/** + * Function sends request to API for putting user's settings + * (dashboard settings, chartline settings, autoupdate interval). */ guiDashboard.putUserDashboardSettingsToAPI = function() { @@ -466,12 +519,20 @@ guiDashboard.putUserDashboardSettingsToAPI = function() let query = { method: "post", data_type: ["user", userId, "settings"], - data:{widgetSettings:widgetSettings, chartLineSettings:chartLineSettings} + data:{ + autoupdateInterval: guiDashboard.model.autoupdateInterval, + widgetSettings:widgetSettings, + chartLineSettings:chartLineSettings, + skinsSettings: guiDashboard.model.skinsSettings, + selectedSkin: guiDashboard.model.selectedSkin, + } } let def = new $.Deferred(); - $.when(api.query(query, true)).fail(e => { + $.when(api.query(query, true)).done(d => { + def.resolve(); + }).fail(e => { console.warn(e) webGui.showErrors(e) def.reject() @@ -481,7 +542,7 @@ guiDashboard.putUserDashboardSettingsToAPI = function() } /** - *Функция, сортирующая массив объектов. + * Function sorts widgets array. */ guiDashboard.sortCountWidget=function(Obj1, Obj2) { @@ -489,7 +550,7 @@ guiDashboard.sortCountWidget=function(Obj1, Obj2) } /** - *Функция, меняющая свойство виджета active на false. + * Function toggles 'active' widget property to false. */ guiDashboard.setNewWidgetActiveValue = function(thisButton) { @@ -505,7 +566,7 @@ guiDashboard.setNewWidgetActiveValue = function(thisButton) } /** - *Функция, меняющая свойство виджета collapse на противоположное (true-false). + * Function toggles 'collapse' widget property to opposite (true/false). */ guiDashboard.setNewWidgetCollapseValue = function(thisButton) { @@ -516,7 +577,7 @@ guiDashboard.setNewWidgetCollapseValue = function(thisButton) { guiDashboard.model.widgets[0][i].collapse=!guiDashboard.model.widgets[0][i].collapse; - //скрываем селект с выбором периода на виджете-графике при его сворачивании + // hides select with period value on chart widget during its collapsing if(widgetName=="pmwChartWidget") { if(guiDashboard.model.widgets[0][i].collapse==false) @@ -534,8 +595,7 @@ guiDashboard.setNewWidgetCollapseValue = function(thisButton) } /** - *Функция, сохраняющая настройки виджетов/линий графика, - *внесенные в таблицу редактирования настроек Dashboard'a. + * Function gets dashboard settings from table in modal. */ guiDashboard.getOptionsFromTable = function(table_id, guiDashboard_obj) { @@ -573,7 +633,7 @@ guiDashboard.getOptionsFromTable = function(table_id, guiDashboard_obj) } /** - *Функция, сохраняющая настройки виджетов, внесенные в форму настроек виджетов Dashboard'a. + * Function gets dashboard settings from modal and send them to API. */ guiDashboard.saveWigdetsOptions = function() { @@ -582,72 +642,22 @@ guiDashboard.saveWigdetsOptions = function() } /** - *Функция, сохраняющая настройки виджетов, внесенные в форму настроек виджетов Dashboard'a, - *из модального окна на странице Dashboard'a. + * Function saves dashboard settings from modal. */ guiDashboard.saveWigdetsOptionsFromModal = function() { return $.when(guiDashboard.saveWigdetsOptions()).done(function(){ - return $.when(hidemodal(), guiDashboard.HideAfterSaveModalWindow()).done(function(){ - return spajs.openURL("/"); - }).promise(); - }).promise(); - -} - -/** - *Функция, сохраняющая настройки виджетов, внесенные в форму настроек виджетов Dashboard'a, - *из секции на странице профиля пользователя. - */ -guiDashboard.saveWigdetsOptionsFromProfile = function() -{ - return $.when(guiDashboard.saveWigdetsOptions()).done(function(){ - return guiPopUp.success("Dashboard widget options were successfully saved"); - }).fail(function(){ - return guiPopUp.error("Dashboard widget options were not saved"); + guiModal.modalClose(); + return spajs.openURL("/"); }).promise(); } /** - *Функция, сохраняющая настройки линий графика Dashboard'a. + * Function generates array with data for chart lines. */ -guiDashboard.saveChartLineSettings = function() +guiDashboard.getDataForStatusChart = function(tasks_data, tasks_data_t, status, date_format) { - guiDashboard.getOptionsFromTable("chart_line_settings_table", guiDashboard.model.ChartLineSettings); - guiDashboard.putUserDashboardSettingsToAPI(); -} - -/** - *Функция, сохраняющая настройки линий графика, внесенных в форму настроек виджетов Dashboard'a, - *из секции на странице профиля пользователя. - */ -guiDashboard.saveChartLineSettingsFromProfile = function() -{ - return $.when(guiDashboard.saveChartLineSettings()).done(function(){ - return guiPopUp.success("Dashboard chart lines settings were successfully saved"); - }).fail(function(){ - return guiPopUp.error("Dashboard chart lines settings were not saved"); - }).promise(); -} -/** - *Функция, сохраняющая все настройки, касающиеся Dashboard'a, со страницы профиля пользователя. - */ -guiDashboard.saveAllDashboardSettingsFromProfile = function(){ - guiDashboard.getOptionsFromTable("modal-table",guiDashboard.model.widgets[0]); - guiDashboard.getOptionsFromTable("chart_line_settings_table", guiDashboard.model.ChartLineSettings); - return $.when(guiDashboard.putUserDashboardSettingsToAPI()).done(function(){ - return guiPopUp.success("Dashboard settings were successfully saved"); - }).fail(function(){ - return guiPopUp.error("Dashboard settings were not saved"); - }).promise(); -} - -/** - * Функция, которая формирует массив данных для кривых графика по отдельному статусу - */ -guiDashboard.getDataForStatusChart = function(tasks_data, tasks_data_t, status) -{ for(var i in tasks_data) { tasks_data[i]=0; } @@ -656,14 +666,14 @@ guiDashboard.getDataForStatusChart = function(tasks_data, tasks_data_t, status) { var val = guiDashboard.statsData.jobs[guiDashboard.statsDataMomentType][i]; var time =+ moment(val[guiDashboard.statsDataMomentType]).tz(window.timeZone).format("x"); + time = moment(time).tz(window.timeZone).format(date_format); if(val.status==status){ tasks_data[time] = val.sum; - tasks_data_t.push(time) } } - var chart_tasks_data1 = [status]; + var chart_tasks_data1 = []; for(var j in tasks_data_t) { @@ -675,35 +685,16 @@ guiDashboard.getDataForStatusChart = function(tasks_data, tasks_data_t, status) } /** - * Функция, отправляющая запрос /api/v2/stats/, - * который дает нам информацию для виджетов класса pmwItemsCounter, - * а также для графика на странице Dashboard. + * Function sends API request (/api/v2/stats/). */ guiDashboard.loadStats=function() { var thisObj = this; - /*var limit=1; - return spajs.ajax.Call({ - url: hostname + "/api/v2/stats/?last="+guiDashboard.statsDataLastQuery, - type: "GET", - contentType: 'application/json', - data: "limit=" + encodeURIComponent(limit)+"&rand="+Math.random(), - success: function (data) - { - thisObj.statsData=data; - }, - error: function (e) - { - console.warn(e) - webGui.showErrors(e) - } - });*/ - let query = { type: "get", item: "stats", - filter:"last="+guiDashboard.statsDataLastQuery + filters:"last="+guiDashboard.statsDataLastQuery, } let def = new $.Deferred(); @@ -716,12 +707,12 @@ guiDashboard.loadStats=function() def.reject(e) }) - return def.promise(); /**/ + return def.promise(); } /** - *Функция вызывается, когда происходит изменение периода на графике(пользователь выбрал другой option в select). - *Функция обновляет значения переменных, которые в дальнейшем используются для запроса к api/v2/stats и отрисовки графика. + * Function is supposed to be called when period value was changed on Chart widget. + * Function updates values of variables that are used for API request (api/v2/stats) and for chart rendering. */ guiDashboard.updateStatsDataLast=function(thisEl) { @@ -745,43 +736,10 @@ guiDashboard.updateStatsDataLast=function(thisEl) break; } guiDashboard.statsDataLastQuery=+newLast; + guiLocalSettings.set('chart_period', +newLast); guiDashboard.updateData(); } -/** - * Ниже представлены 3 функции для работы с модальным окном - Set widget options - * guiDashboard.showModalWindow - открывает модальное окно, предварительно обновляя данные - * guiDashboard.HideAfterSaveModalWindow - скрывает модальное окно - * guiDashboard.renderModalWindow - отрисовывает модальное окно - */ -guiDashboard.showModalWindow = function() -{ - if($('div').is('#modal-widgets-settings')) - { - guiDashboard.model.widgets[0].sort(guiDashboard.sortCountWidget); - $('#modal-widgets-settings').empty(); - $('#modal-widgets-settings').html(guiDashboard.renderModalWindow()); - $("#modal-widgets-settings").modal('show'); - } -} - -guiDashboard.HideAfterSaveModalWindow = function() -{ - if($('div').is('#modal-widgets-settings')) - { - return $("#modal-widgets-settings").modal('hide'); - } - -} - -guiDashboard.renderModalWindow = function() -{ - var html=spajs.just.render('modalWidgetsSettings'); - return html; -} - - - guiDashboard.stopUpdates = function() { clearTimeout(this.model.updateTimeoutId) @@ -789,7 +747,7 @@ guiDashboard.stopUpdates = function() } /** - * Для перетаскивания виджетов и изменения их порядка + * Turn on/off drag-and-drop property of Dashboard widgets. */ guiDashboard.toggleSortable = function(thisButton) { @@ -828,17 +786,8 @@ tabSignal.connect('guiLocalSettings.hideMenu', function(){ }, 200) }) -/* -tabSignal.connect('hideLoadingProgress', function(){ - if(spajs.currentOpenMenu && spajs.currentOpenMenu.id == 'home') - { - guiDashboard.updateData() - } -})*/ - guiDashboard.updateData = function() { - var thisObj = this if(this.model.updateTimeoutId) { clearTimeout(this.model.updateTimeoutId) @@ -847,7 +796,7 @@ guiDashboard.updateData = function() $.when(guiDashboard.loadStats()).done(function() { - //обновляем счетчики для виджетов + // updates counters of widgets pmwHostsCounter.updateCount(); pmwTemplatesCounter.updateCount(); pmwGroupsCounter.updateCount(); @@ -855,101 +804,300 @@ guiDashboard.updateData = function() pmwInventoriesCounter.updateCount(); pmwUsersCounter.updateCount(); - //строим график - //определяем текущий месяц и год - var monthNum=moment().format("MM"); - var yearNum=moment().format("YYYY"); - var dayNum=moment().format("DD"); - var hourNum=",T00:00:00"; - var startTimeOrg=""; - - switch (guiDashboard.statsDataMomentType) { - case "year": - startTimeOrg=yearNum+"-01-01"+hourNum; - break; - case "month": - startTimeOrg=yearNum+"-"+monthNum+"-01"+hourNum; - break; - case "day": - startTimeOrg=yearNum+"-"+monthNum+"-"+dayNum+hourNum; - break; - } + // renders chart + guiDashboard.renderChart(); - //задаем стартовую дату для графика. - //guiDashboard.statsDataLast - количество периодов назад - //guiDashboard.statsDataMomentType - тип периода - месяц/год - var startTime =+ moment(startTimeOrg).subtract(guiDashboard.statsDataLast-1, guiDashboard.statsDataMomentType).tz(window.timeZone).format("x"); + // renders Statistic bars on chart + guiDashboard.renderChartProgressBars(); + }); - tasks_data = {}; - tasks_data_t = []; + this.model.updateTimeoutId = setTimeout(function(){ + guiDashboard.updateData() + }, 1000*30) +} - //формируем в цикле временные отрезки для графика относительно стартовой даты - for(var i = 0; i< guiDashboard.statsDataLast; i++) - { - //идем на период вперед - var time=+moment(startTime).add(i, guiDashboard.statsDataMomentType).tz(window.timeZone).format("x"); - tasks_data[time] = 0; - tasks_data_t.push(time); - } +/** + * Function gets Start time for chart. + * start time - time in milliseconds, from which chart will be representing data. + */ +guiDashboard.getChartStartTime = function() +{ + // defines current months and year + let monthNum=moment().format("MM"); + let yearNum=moment().format("YYYY"); + let dayNum=moment().format("DD"); + let hourNum=",T00:00:00"; + let startTimeOrg=""; + + switch (guiDashboard.statsDataMomentType) { + case "year": + startTimeOrg=yearNum+"-01-01"+hourNum; + break; + case "month": + startTimeOrg=yearNum+"-"+monthNum+"-01"+hourNum; + break; + case "day": + startTimeOrg=yearNum+"-"+monthNum+"-"+dayNum+hourNum; + break; + } - //массив для линий графика, которые необходимо отобразить на странице - var linesForChartArr = []; - //объект, хранящий в себе цвета этих линий - var colorPaternForLines = {}; - for(var i in guiDashboard.model.ChartLineSettings) - { - var lineChart = guiDashboard.model.ChartLineSettings[i]; + // guiDashboard.statsDataLast - amount of previous periods (periods to reduce) + // guiDashboard.statsDataMomentType - period type - month/year + let startTime =+ moment(startTimeOrg).subtract(guiDashboard.statsDataLast-1, guiDashboard.statsDataMomentType).tz(window.timeZone).format("x"); - //формируем массив значений для кривой all tasks - if(lineChart.name == 'all_tasks') - { - for (var i in guiDashboard.statsData.jobs[guiDashboard.statsDataMomentType]) { - var val = guiDashboard.statsData.jobs[guiDashboard.statsDataMomentType][i]; - var time = +moment(val[guiDashboard.statsDataMomentType]).tz(window.timeZone).format("x"); - if (!tasks_data[time]) { - tasks_data[time] = val.all; - tasks_data_t.push(time) - } - } - chart_tasks_start_x = ['time']; - chart_tasks_data = [lineChart.title]; - for (var j in tasks_data_t) { - var time = tasks_data_t[j] - chart_tasks_start_x.push(time / 1); - chart_tasks_data.push(tasks_data[time] / 1); - } + return startTime; +} + +/** + * Function gets data for Chart. + * @param startTime(number) - start time in milliseconds, from which chart will be representing data. + * @param date_format(string) - format of time labels for x axes on chart. + */ +guiDashboard.getChartData = function(startTime, date_format) +{ + let tasks_data = {}; + let tasks_data_t = []; + + // forms chart time intervals based on start date + for(let i = -1; i< guiDashboard.statsDataLast; i++) + { + // period up + let time =+ moment(startTime).add(i, guiDashboard.statsDataMomentType).tz(window.timeZone).format("x"); + time = moment(time).tz(window.timeZone).format(date_format); + tasks_data[time] = 0; + tasks_data_t.push(time); + } - linesForChartArr.push(chart_tasks_start_x); - if(lineChart.active == true) + // object for chart settings + let chart_data_obj = {datasets:[], labels:[]}; + for(let i in guiDashboard.model.ChartLineSettings) + { + let lineChart = guiDashboard.model.ChartLineSettings[i]; + + // forms array with values for 'all tasks' line + if(lineChart.name == 'all_tasks') + { + for (let i in guiDashboard.statsData.jobs[guiDashboard.statsDataMomentType]) { + let val = guiDashboard.statsData.jobs[guiDashboard.statsDataMomentType][i]; + let time = +moment(val[guiDashboard.statsDataMomentType]).tz(window.timeZone).format("x"); + time = moment(time).tz(window.timeZone).format(date_format); + if(tasks_data[time] !== undefined) { - linesForChartArr.push(chart_tasks_data); - colorPaternForLines[lineChart.title]=lineChart.color; + tasks_data[time] = val.all; } } - //формируем массив значений для кривой каждого статуса - if(lineChart.name != 'all_tasks' && lineChart.active == true) + let chart_tasks_data = []; + for (let j in tasks_data_t) { + let time = tasks_data_t[j] + chart_tasks_data.push(tasks_data[time] / 1); + chart_data_obj.labels.push(time); + } + + if(lineChart.active == true) { - var chart_tasks_data_var = guiDashboard.getDataForStatusChart(tasks_data, tasks_data_t, lineChart.title); - linesForChartArr.push(chart_tasks_data_var); - colorPaternForLines[lineChart.title]=lineChart.color; + + chart_data_obj.datasets.push({ + label: 'All tasks', + data: chart_tasks_data, + borderColor: lineChart.color, + backgroundColor: lineChart.bg_color, + }) + } + } + + // forms array with values for others line + if(lineChart.name != 'all_tasks' && lineChart.active == true) + { + let chart_tasks_data_var = guiDashboard.getDataForStatusChart(tasks_data, tasks_data_t, lineChart.title, date_format); + + chart_data_obj.datasets.push({ + label: lineChart.title, + data: chart_tasks_data_var, + borderColor: guiDashboard.getChartLineColor(lineChart), + backgroundColor: guiDashboard.getChartLineColor(lineChart, true), + }) + } + } + + return chart_data_obj; +} + +/** + * Function gets colors for chartLines. + * @param lineChart(object) - object with chartLine settings + * @param bg(boolean) - if true, function returns color for line's background, + * otherwise, it returns color for line's border. + */ +guiDashboard.getChartLineColor = function(lineChart, bg) +{ + let alpha = 1; + let prop = 'color'; + if(bg) + { + alpha = 0.3; + prop = 'bg_color'; + } + + if(guiCustomizer.skin.value['history_status_' + lineChart.name]) + { + if(guiCustomizer.skin.value['history_status_' + lineChart.name][0] == "#") + { + return hexToRgbA(guiCustomizer.skin.value['history_status_' + lineChart.name], alpha); + } + + return guiCustomizer.skin.value['history_status_' + lineChart.name] + + } + + return lineChart[prop]; +} + +/** + * Function sets chart data and chart settings. + */ +guiDashboard.setChartSettings = function(ctx, chart_data_obj) +{ + guiDashboard.model.historyChart = new Chart(ctx, { + type: 'line', + data: { + datasets: chart_data_obj.datasets, + labels: chart_data_obj.labels, + }, + + options:{ + maintainAspectRatio: false, + legend: { + labels: { + fontColor: guiCustomizer.skin.value.chart_legend_text_color, + }, + }, + scales: { + yAxes: [{ + ticks: { + beginAtZero:true, + fontColor: guiCustomizer.skin.value.chart_axes_text_color, + + }, + gridLines: { + color: guiCustomizer.skin.value.chart_axes_lines_color, + } + }], + xAxes: [{ + ticks: { + fontColor: guiCustomizer.skin.value.chart_axes_text_color, + + }, + gridLines: { + color: guiCustomizer.skin.value.chart_axes_lines_color, + } + }] } + } - //загружаем график, перечисляем массивы данных для графика и необходимые цвета для графиков - guiDashboard.model.historyChart.load({ - columns: linesForChartArr, - colors: colorPaternForLines - }); }); +} - this.model.updateTimeoutId = setTimeout(function(){ - guiDashboard.updateData() - }, 1000*30) +/** + * Function renders Dashboard chart + */ +guiDashboard.renderChart = function (need_update) +{ + let ctx = document.getElementById("chart_js_canvas"); + + if(ctx && ctx.getContext) + { + ctx = ctx.getContext('2d'); + + // defines current months and year + let startTime = guiDashboard.getChartStartTime(); + + let date_format = 'DD.MM.YY'; + + // gets data for chart + let chart_data_obj = guiDashboard.getChartData(startTime, date_format); + + // renders chart + if(guiDashboard.model.historyChart) + { + if(guiDashboard.updateChartOrNot(guiDashboard.model.historyChart, chart_data_obj) || need_update) + { + try + { + guiDashboard.model.historyChart.destroy(); + } + catch(e){} + + guiDashboard.setChartSettings(ctx, chart_data_obj); + } + } + else + { + guiDashboard.setChartSettings(ctx, chart_data_obj); + } + } } +/** + * Function renders progress bars with statistic near the chart. + */ +guiDashboard.renderChartProgressBars = function() +{ + let el = $("#chart_progress_bars"); + if(el.length != 0) + { + + let opt = { + settings: guiDashboard.model.ChartLineSettings, + stats_data: guiDashboard.statsData, + } + + let html = spajs.just.render('chart_progress_bars', {opt: opt}); + + $("#chart_progress_bars").html(html); + } +}; + + +/** + * Function check necessity of chart updating(render chart with new data.) + * If data for chart has changed, function return true, + * otherwise it returns false. + * @param chart(object) - chart object; + * @param new_data_obj(object) - object with new data for chart. + * @returns boolean + */ +guiDashboard.updateChartOrNot = function(chart, new_data_obj) +{ + let chart_data = chart.config.data; + let bool1 = deepEqual(chart_data.labels, new_data_obj.labels); + if(!bool1) + { + return true; + } + for(let i in chart_data.datasets) + { + let old_item = chart_data.datasets[i]; + for(let j in new_data_obj.datasets) + { + let new_item = new_data_obj.datasets[i]; + if(old_item.label == new_item.label) + { + let bool2 = deepEqual(new_item.data, old_item.data); + + if(!bool2) + { + return true; + } + } + } + } + + return false; +} guiDashboard.open = function(holder, menuInfo, data) { @@ -958,7 +1106,7 @@ guiDashboard.open = function(holder, menuInfo, data) return $.when(guiDashboard.getUserSettingsFromAPI()).always(function() { - // Инициализация всех виджетов на странице + // inits all widgets for(var i in guiDashboard.model.widgets) { for(var j in guiDashboard.model.widgets[i]) @@ -976,6 +1124,7 @@ guiDashboard.open = function(holder, menuInfo, data) } } + guiDashboard.model.historyChart = undefined; thisObj.updateData() $(holder).insertTpl(spajs.just.render(thisObj.tpl_name, {guiObj:thisObj})) @@ -984,46 +1133,13 @@ guiDashboard.open = function(holder, menuInfo, data) pmwAnsibleModuleWidget.render(); pmwChartWidget.render(); - guiDashboard.model.historyChart = c3.generate({ - bindto: '#c3-history-chart', - data: { - x: 'time', - columns: [ - ['time'], - ['All tasks'], - ['OK'], - ['ERROR'], - ['INTERRUPTED'], - ['DELAY'], - ['OFFLINE'] - ], - type: 'area', - types: { - OK: 'line', - ERROR: 'line', - INTERRUPTED: 'line', - DELAY: 'line', - OFFLINE: 'line' - }, - }, - axis: { - x: { - type: 'timeseries', - tick: { - format: '%Y-%m-%d' - } - } - }, - color: { - pattern: ['#1f77b4', '#276900', '#333333', '#9b97e4', '#808419', '#9e9e9e', '#d62728', '#9467bd', '#c5b0d5', '#8c564b', '#c49c94', '#e377c2', '#f7b6d2', '#7f7f7f', '#c7c7c7', '#bcbd22', '#dbdb8d', '#17becf', '#9edae5'] - } - }); if($('select').is('#chart-period')) { - $('#chart-period').val(guiDashboard.statsDataLastQuery).change(); + let chart_period = guiLocalSettings.get('chart_period') || guiDashboard.statsDataLastQuery; + $('#chart-period').val(chart_period).change(); } - //drag and drop для виджетов + //drag and drop for widgets if($('div').is('#dnd-container')) { widget_sort = Sortable.create(document.getElementById("dnd-container"), { @@ -1033,9 +1149,8 @@ guiDashboard.open = function(holder, menuInfo, data) disabled: true, onUpdate: function (evt) { - // console.log("onUpdate[1]", evt); var item = evt.item; // the current dragged HTMLElement - //запоминаем новый порядок сортировки + // saves new sorting order var divArr=$('.dnd-block'); var idArr=[]; for (var i=0; i 50) + { + type = 'textarea'; + } + else if (typeof value[field] == 'boolean') + { + type = 'boolean'; + } + else if (typeof value[field] == 'object') + { + if (Array.isArray(value[field])) + { + type = 'textarea'; + } + else if (value[field] === null) + { + type = 'null'; + } + else + { + type = 'ansible_json'; + if (allPropertiesIsObjects(value[field])) + { + options.divider = true; + options.title += ":"; + } + else + { + if (opt.title == options_field_title) + { + options.title = options_child_fields_subtitle + field; + } + } + } + } + + if ((typeof value[field] == "string" && value[field] == "") || + (Array.isArray(value[field]) && value[field].length == 0) || + (typeof value[field] == "object" && value[field] !== null && Array.isArray(value[field]) == false && Object.keys(value[field]).length == 0)) + { + type = 'hidden'; + } + + realElements[field] = new guiElements[type]($.extend({}, options), value[field]); + } + + return realElements; + } + + this.setValue = function(value) + { + this.value = value + let realElements = {} + if(value) + { + value = this.sortValue(value); + realElements = this.getRealElements(value); + } + + this.realElements = realElements; + } + + this.setValue(value) + + this.insertTestValue = function(value) + { + this.setValue(value); + return value; + } + + this.getValue = function() + { + let valueObj = {}; + for(let element_name in this.realElements) + { + let element = this.realElements[element_name]; + valueObj[element_name] = element.getValue(); + } + + return this.reductionToType(valueObj); + } + + this.getValidValue = function() + { + let valueObj = {}; + for(let element_name in this.realElements) + { + let element = this.realElements[element_name]; + valueObj[element_name] = element.getValidValue(); + } + + return valueObj; + } +} \ No newline at end of file diff --git a/polemarch/static/js/pmPeriodicTasks.js b/polemarch/static/js/pmPeriodicTasks.js index a04c961c..cfe4d744 100644 --- a/polemarch/static/js/pmPeriodicTasks.js +++ b/polemarch/static/js/pmPeriodicTasks.js @@ -291,6 +291,7 @@ function signal_gui_schema_name_periodic_task(data){ tabSignal.connect("gui.schema.name.periodic_task.edit", signal_gui_schema_name_periodic_task) tabSignal.connect("gui.schema.name.periodic_task.new", signal_gui_schema_name_periodic_task) +tabSignal.connect("gui.schema.name.periodic_task.get", signal_gui_schema_name_periodic_task) tabSignal.connect("guiList.renderLine.periodic_task", function(obj) diff --git a/polemarch/static/js/pmProjects.js b/polemarch/static/js/pmProjects.js index 6080eb3d..381f0d10 100644 --- a/polemarch/static/js/pmProjects.js +++ b/polemarch/static/js/pmProjects.js @@ -102,6 +102,7 @@ tabSignal.connect("openapi.schema.definition.AnsiblePlaybook", function(obj) { tabSignal.connect("openapi.schema", function(data) { window.guiSchema.path["/project/{pk}/"].schema.edit.fields.execute_view_data.format = 'null' + window.guiSchema.path["/project/{pk}/"].schema.get.fields.execute_view_data.format = 'null' }) @@ -167,38 +168,38 @@ gui_project = { let formData = { title:"Deploy", form:{ - 'inventory' : { - title:'inventory', - required:true, - format:'hybrid_autocomplete', - dynamic_properties:{ - list_obj: "/project/{pk}/inventory/", - value_field: "id", - view_field: "name", - } - }, - user:{ - title:'User', - description: "connect as this user (default=None)", - format:'string', - type: "string", - }, - key_file: { - title:'Key file', - description: "use this file to authenticate the connection", - format:'secretfile', - type: "string", - dynamic_properties:{ - list_obj: "/project/{pk}/inventory/", - value_field: "id", - view_field: "name", - } - }, - extra_vars: { - title:"Execute parametrs", - format:'form', - form:extra_fields + 'inventory' : { + title:'inventory', + required:true, + format:'hybrid_autocomplete', + dynamic_properties:{ + list_obj: "/project/{pk}/inventory/", + value_field: "id", + view_field: "name", } + }, + user:{ + title:'User', + description: "connect as this user (default=None)", + format:'string', + type: "string", + }, + key_file: { + title:'Key file', + description: "use this file to authenticate the connection", + format:'secretfile', + type: "string", + dynamic_properties:{ + list_obj: "/project/{pk}/inventory/", + value_field: "id", + view_field: "name", + } + }, + extra_vars: { + title:"Execute parametrs", + format:'form', + form:extra_fields + } } } @@ -207,80 +208,11 @@ gui_project = { }, } -guiElements.form = function(opt = {}, value, parent_object) +tabSignal.connect("openapi.schema.schema", function(obj) { - this.name = 'form' - guiElements.base.apply(this, arguments) - - this.realElements = {}; - - this.prepareFieldOptions = function(field) - { - if(field.enum) - { - field.format = "enum" - } - - return field - } - - this.setValue = function(value) - { - this.value = value - let realElements = {}; - if(value.form) - { - for(let i in value.form) - { - let field = value.form[i] - field.name = i - - field = this.prepareFieldOptions(field) - let type = getFieldType(field) - - realElements[i] = new guiElements[type]($.extend(true, {}, field), field.value); - } - } - - this.realElements = realElements - } - - if(opt.form && !value) + if (obj.path == '/project/{pk}/module/{module_id}/') { - this.setValue(opt) - } - else - { - this.setValue(value) - } - - this.insertTestValue = function(value) - { - this.setValue(value); - return value; - } - - this.getValue = function() - { - let valueObj = {}; - for(let element_name in this.realElements) - { - let element = this.realElements[element_name]; - valueObj[element_name] = element.getValue(); - } - - return this.reductionToType(valueObj); - } - - this.getValidValue = function() - { - let valueObj = {}; - for(let element_name in this.realElements) - { - let element = this.realElements[element_name]; - valueObj[element_name] = element.getValidValue(); - } - - return this.reductionToType(valueObj); + obj.value.fields.data.format = 'ansible_json' + obj.value.fields.data.hide_title = true; } -} \ No newline at end of file +}) \ No newline at end of file diff --git a/polemarch/static/js/pmTemplates.js b/polemarch/static/js/pmTemplates.js index 8acbf6a4..1770f8af 100644 --- a/polemarch/static/js/pmTemplates.js +++ b/polemarch/static/js/pmTemplates.js @@ -17,7 +17,7 @@ gui_project_template = { data_field = JSON.parse(template_data.data); } - if(template_data.kind.toLowerCase() == 'module') + if(template_data.kind && template_data.kind.toLowerCase() == 'module') { arr_data_fields = module_fields; delete_data_fields = playbook_fields; @@ -987,7 +987,7 @@ tabSignal.connect("openapi.schema", function(obj) { "canCreate": true, "schema": { "list": { - "fields": gui_project_template_option_variables_fields_Schema, + "fields": $.extend(true, {}, gui_project_template_option_variables_fields_Schema), "filters": { 0: { "name": "key", @@ -1014,7 +1014,7 @@ tabSignal.connect("openapi.schema", function(obj) { "key" ], "type": "object", - "properties": gui_project_template_option_variables_fields_Schema, + "properties": $.extend(true, {}, gui_project_template_option_variables_fields_Schema), "definition_name": "TemplateVariable", "definition_ref": "#/definitions/TemplateVariable" } @@ -1026,7 +1026,7 @@ tabSignal.connect("openapi.schema", function(obj) { } }, "new": { - "fields": gui_project_template_option_variables_fields_Schema, + "fields": $.extend(true, {}, gui_project_template_option_variables_fields_Schema), "query_type": "post", "operationId": "project_template_variables_add", "responses": { @@ -1037,7 +1037,7 @@ tabSignal.connect("openapi.schema", function(obj) { "key" ], "type": "object", - "properties": gui_project_template_option_variables_fields_Schema, + "properties": $.extend(true, {}, gui_project_template_option_variables_fields_Schema), "definition_name": "TemplateVariable", "definition_ref": "#/definitions/TemplateVariable" } @@ -1092,7 +1092,7 @@ tabSignal.connect("openapi.schema", function(obj) { "canEdit": true, "schema": { "get": { - "fields": gui_project_template_option_variables_fields_Schema, + "fields": $.extend(true, {}, gui_project_template_option_variables_fields_Schema), "filters": {}, "query_type": "get", "operationId": "project_template_variables_get", @@ -1104,7 +1104,7 @@ tabSignal.connect("openapi.schema", function(obj) { "key" ], "type": "object", - "properties": gui_project_template_option_variables_fields_Schema, + "properties": $.extend(true, {}, gui_project_template_option_variables_fields_Schema), "definition_name": "TemplateVariable", "definition_ref": "#/definitions/TemplateVariable" } @@ -1116,7 +1116,7 @@ tabSignal.connect("openapi.schema", function(obj) { } }, "edit": { - "fields": gui_project_template_option_variables_fields_Schema, + "fields": $.extend(true, {}, gui_project_template_option_variables_fields_Schema), "query_type": "patch", "operationId": "project_template_variables_edit", "responses": { @@ -1247,7 +1247,7 @@ tabSignal.connect("openapi.schema", function(obj) { } }, "new": { - "fields": gui_project_template_option_Schema, + "fields": $.extend(true, {}, gui_project_template_option_Schema), "query_type": "post", "operationId": "project_template_option_add", "responses": { @@ -1255,7 +1255,7 @@ tabSignal.connect("openapi.schema", function(obj) { "description": "Action accepted.", "schema": { "type": "object", - "properties": gui_project_template_option_Schema, + "properties": $.extend(true, {}, gui_project_template_option_Schema), "definition_name": "OneOption", "definition_ref": "#/definitions/OneOption" } @@ -1311,7 +1311,7 @@ tabSignal.connect("openapi.schema", function(obj) { "canEdit": true, "schema": { "get": { - "fields": gui_project_template_option_Schema, + "fields": $.extend(true, {}, gui_project_template_option_Schema), "filters": {}, "query_type": "get", "operationId": "project_template_option_get", @@ -1320,7 +1320,7 @@ tabSignal.connect("openapi.schema", function(obj) { "description": "Action accepted.", "schema": { "type": "object", - "properties": gui_project_template_option_Schema, + "properties": $.extend(true, {}, gui_project_template_option_Schema), "definition_name": "OneOption", "definition_ref": "#/definitions/OneOption" } @@ -1332,7 +1332,7 @@ tabSignal.connect("openapi.schema", function(obj) { } }, "edit": { - "fields": gui_project_template_option_Schema, + "fields": $.extend(true, {}, gui_project_template_option_Schema), "query_type": "patch", "operationId": "project_template_option_edit", "responses": { @@ -1340,7 +1340,7 @@ tabSignal.connect("openapi.schema", function(obj) { "description": "Action accepted.", "schema": { "type": "object", - "properties": gui_project_template_option_Schema, + "properties": $.extend(true, {}, gui_project_template_option_Schema), "definition_name": "OneOption", "definition_ref": "#/definitions/OneOption" } @@ -1392,7 +1392,7 @@ tabSignal.connect("openapi.schema", function(obj) { "canCreate": true, "schema": { "list": { - "fields": gui_project_template_option_variables_fields_Schema, + "fields": $.extend(true, {}, gui_project_template_option_variables_fields_Schema), "filters": { 0: { "name": "key", @@ -1419,7 +1419,7 @@ tabSignal.connect("openapi.schema", function(obj) { "key" ], "type": "object", - "properties": gui_project_template_option_variables_fields_Schema, + "properties": $.extend(true, {}, gui_project_template_option_variables_fields_Schema), "definition_name": "TemplateVariable", "definition_ref": "#/definitions/TemplateVariable" } @@ -1431,7 +1431,7 @@ tabSignal.connect("openapi.schema", function(obj) { } }, "new": { - "fields": gui_project_template_option_variables_fields_Schema, + "fields": $.extend(true, {}, gui_project_template_option_variables_fields_Schema), "query_type": "post", "operationId": "project_template_option_variables_add", "responses": { @@ -1442,7 +1442,7 @@ tabSignal.connect("openapi.schema", function(obj) { "key" ], "type": "object", - "properties": gui_project_template_option_variables_fields_Schema, + "properties": $.extend(true, {}, gui_project_template_option_variables_fields_Schema), "definition_name": "TemplateVariable", "definition_ref": "#/definitions/TemplateVariable" } @@ -1497,7 +1497,7 @@ tabSignal.connect("openapi.schema", function(obj) { "canEdit": true, "schema": { "get": { - "fields": gui_project_template_option_variables_fields_Schema, + "fields": $.extend(true, {}, gui_project_template_option_variables_fields_Schema), "filters": {}, "query_type": "get", "operationId": "project_template_option_variables_get", @@ -1509,7 +1509,7 @@ tabSignal.connect("openapi.schema", function(obj) { "key" ], "type": "object", - "properties": gui_project_template_option_variables_fields_Schema, + "properties": $.extend(true, {}, gui_project_template_option_variables_fields_Schema), "definition_name": "TemplateVariable", "definition_ref": "#/definitions/TemplateVariable" } @@ -1521,7 +1521,7 @@ tabSignal.connect("openapi.schema", function(obj) { } }, "edit": { - "fields": gui_project_template_option_variables_fields_Schema, + "fields": $.extend(true, {}, gui_project_template_option_variables_fields_Schema), "query_type": "patch", "operationId": "project_template_option_variables_edit", "responses": { @@ -1532,7 +1532,7 @@ tabSignal.connect("openapi.schema", function(obj) { "key" ], "type": "object", - "properties": gui_project_template_option_variables_fields_Schema, + "properties": $.extend(true, {}, gui_project_template_option_variables_fields_Schema), "definition_name": "TemplateVariable", "definition_ref": "#/definitions/TemplateVariable" } diff --git a/polemarch/static/js/pmUsers.js b/polemarch/static/js/pmUsers.js index 9e4573f7..64467a4c 100644 --- a/polemarch/static/js/pmUsers.js +++ b/polemarch/static/js/pmUsers.js @@ -10,6 +10,7 @@ tabSignal.connect("openapi.completed", function() { let user_settings = guiSchema.path['/user/{pk}/settings/']; user_settings.canEdit = true; + user_settings.canEditInView = true; user_settings.methodEdit = 'post'; ['chartLineSettings', 'widgetSettings'].forEach(function (name) { user_settings.schema.get.fields[name].format = 'inner_api_object'; @@ -21,13 +22,13 @@ tabSignal.connect("openapi.completed", function() user_settings.schema.get.fields['autoupdateInterval'].title = 'Data autoupdate interval'; user_settings.schema.get.fields['autoupdateInterval'].format = 'time_interval'; - user_settings.schema.post = { - fields: user_settings.schema.get, + user_settings.schema.edit = { + fields: $.extend(true, {}, user_settings.schema.get.fields), operationId: 'user_settings_edit', query_type: 'post', } - user_settings.method.post = 'post'; + user_settings.method.post = 'edit'; let user = guiSchema.path['/user/']; user.schema.new.fields['password'].format = "password"; @@ -44,5 +45,15 @@ gui_user_settings = { return $.when(base_update).done(data => { guiDashboard.setUserSettingsFromApiAnswer(data.data) }) - } + }, + + getDataFromForm: function() + { + let base_data_from_form = gui_base_object.getDataFromForm.apply(this, arguments); + + base_data_from_form['skinsSettings'] = $.extend(true, {}, guiDashboard.model.skinsSettings); + base_data_from_form['selectedSkin'] = guiDashboard.model.selectedSkin; + + return base_data_from_form; + }, } \ No newline at end of file diff --git a/polemarch/static/js/tests/pmElements.js b/polemarch/static/js/tests/pmElements.js new file mode 100644 index 00000000..d27298d4 --- /dev/null +++ b/polemarch/static/js/tests/pmElements.js @@ -0,0 +1,183 @@ +window.qunitTestsArray['guiElements.form'] = { + test:function() + { + syncQUnit.addTest('guiElements.form', function ( assert ) + { + let element; + let formData; + let done = assert.async(); + + $("#guiElementsTestForm").remove(); + $("body").append("
") + + + formData = { + title:"Deploy", + form:{ + 'inventory' : { + title:'inventory', + required:true, + format:'hybrid_autocomplete', + dynamic_properties:{ + list_obj: "/project/{pk}/inventory/", + value_field: "id", + view_field: "name", + } + }, + user:{ + title:'User', + description: "connect as this user (default=None)", + format:'string', + type: "string", + }, + key_file: { + title:'Key file', + description: "use this file to authenticate the connection", + format:'secretfile', + type: "string", + dynamic_properties:{ + list_obj: "/project/{pk}/inventory/", + value_field: "id", + view_field: "name", + } + }, + extra_vars: { + title:"Execute parametrs", + format:'form', + form:{ + varName: { + name:'varName', + title:'Name', + default:'NameDefaultValue', + format:'string', + help:'Name', + }, + varTask: { + name:'varTask', + title:'Name', + default:'B', + format:'enum', + help:'Name', + enum:['A', 'B', 'C'], + }, + varVersion: { + name:'varVersion', + title:'Name', + default:true, + format:'boolean', + help:'Name', + }, + RunBtn: { + name:'RunBtn', + title:'abc_yml', + value:'abc.yml', + format:'button', + text:'Run abc.yml', + onclick:function(){ + + let val = element.getValue() + val.playbook = this.getValue() + + assert.ok(val.extra_vars['RunBtn'] == 'abc.yml', 'guiElements.form test RunBtn'); + assert.ok(val.playbook == 'abc.yml', 'guiElements.form test playbook'); + + assert.ok(val.extra_vars['varName'] == 'NameDefaultValue', 'guiElements.form test varName'); + + assert.ok(val.extra_vars['varVersion'] == true, 'guiElements.form test values'); + + testdone(done) + }, + class:'gui-test-form' + }, + } + } + } + } + + element = new guiElements.form(undefined, formData); + $("#guiElementsTestForm").insertTpl(element.render()) + + setTimeout(() => { + $("#guiElementsTestForm .btn_abc_yml").trigger('click') + }, 50) + }); + } +} + + +window.qunitTestsArray['guiElements.ansible_json'] = { + test:function() + { + syncQUnit.addTest('guiElements.ansible_json', function ( assert ) + { + let done = assert.async(); + + $("#guiElementsTestForm").remove(); + + let moduleData = { + requirements:["botocore","boto3"], + description:["Gather facts about availability zones in AWS."], + author: "Henrique Rodrigues (github.com/Sodki)", + module: "aws_az_facts", + options: { + filters: { + default: null, + default2: {}, + empty_string: "", + required:false, + description:["A dict of filters to apply. Each dict item consists of a filter key and a filter value."], + } + }, + version_added:"2.5", + short_description:"Gather facts about availability zones in AWS.", + extends_documentation_fragment:["aws","ec2"], + notes: [], + } + + let sorted_value_keys = ["short_description", "description", "module", "version_added", "requirements", "extends_documentation_fragment", "options", "notes","author"]; + let realElements_types = { + short_description: "text_paragraph", + description: "text_paragraph", + module: "string", + version_added: "string", + requirements: "textarea", + extends_documentation_fragment: "textarea", + options: "ansible_json", + notes: "hidden", + author: "string", + } + + let options_filters_fields_types = { + default: "null", + default2: "hidden", + empty_string: "hidden", + required: "boolean", + description: "text_paragraph", + } + + let element = new guiElements.ansible_json(undefined, moduleData); + let sorted_value = element.sortValue(moduleData); + let realEleemnts = element.getRealElements(sorted_value, {}) + + + assert.equal(sorted_value_keys.length, Object.keys(sorted_value).length) + assert.ok(deepEqual(sorted_value_keys, Object.keys(sorted_value))) + + for(let field in realElements_types) + { + assert.equal(realElements_types[field], realEleemnts[field].name); + } + + let options_filters = realEleemnts["options"].realElements["filters"]; + + assert.equal(options_filters.name, "ansible_json") + + for(let field in options_filters_fields_types) + { + assert.equal(options_filters_fields_types[field], options_filters.realElements[field].name); + } + + testdone(done) + }); + } +} diff --git a/polemarch/static/js/tests/pmHook.js b/polemarch/static/js/tests/pmHook.js index 3b165139..7cb34ff1 100644 --- a/polemarch/static/js/tests/pmHook.js +++ b/polemarch/static/js/tests/pmHook.js @@ -12,6 +12,7 @@ window.qunitTestsArray['guiPaths.hook'] = { { is_valid:true, data:{ + type : {value:"HTTP"}, recipients:{ value:rundomString(6) } diff --git a/polemarch/static/js/tests/pmProjects.js b/polemarch/static/js/tests/pmProjects.js index 119e0c56..b02b6918 100644 --- a/polemarch/static/js/tests/pmProjects.js +++ b/polemarch/static/js/tests/pmProjects.js @@ -33,9 +33,14 @@ window.qunitTestsArray['guiPaths.project'] = { guiTests.hasCreateButton(false, test_name) guiTests.hasAddButton(false, test_name) + guiTests.clickAndWaitRedirect(".btn-edit-one-entity") guiTests.updateObject(test_name, {notes:{value:rundomString(6)}}, true) + + // //////////////////////////////////////////////// + // Тест project/{pk}/template/*** + // //////////////////////////////////////////////// guiTests.openPage(test_name, env, (env) =>{ return vstMakeLocalApiUrl("project/{pk}/template/new", {api_pk:env.objectId}) }) // Проверка того что страница с флагом api_obj.canCreate == true открывается @@ -52,19 +57,19 @@ window.qunitTestsArray['guiPaths.project'] = { let values = guiTests.setValues(assert, fieldsData) // Создали объект с набором случайных данных - $.when(window.curentPageObject.createAndGoEdit()).done(() => { - - guiTests.compareValues(assert, test_name, fieldsData, values) - - env.template_id = window.curentPageObject.model.data.id; - - assert.ok(true, 'guiPaths["project/{pk}/template/new"] create new template ok'); - - // @todo добавить проверку того что поля правильно меняются от значений других полей - - testdone(done) + $.when(window.curentPageObject.createAndGoEdit()).done(() => { + $.when(guiTests.actionAndWaitRedirect('project/{pk}/template/new', assert, () => { $(".btn-edit-one-entity").trigger('click') })).done(() => { + + guiTests.compareValues(assert, 'project/{pk}/template/new', fieldsData, values) + env.template_id = window.curentPageObject.model.data.id; + assert.ok(true, 'guiPaths["project/{pk}/template/new"] create new template ok'); + testdone(done) + }).fail((err) => { + assert.ok(false, 'guiPaths["project/{pk}/template/new"] create new template fail'); + testdone(done) + }) }).fail((err) => { - assert.ok(false, 'guiPaths["'+test_name+'new"] create new template fail'); + assert.ok(false, 'guiPaths["project/{pk}/template/new"] create new template fail'); testdone(done) }) }) @@ -92,6 +97,9 @@ window.qunitTestsArray['guiPaths.project'] = { guiTests.setValuesAndCreate(test_name, option_data, (data) =>{}, true) test_name = "project/{pk}/template/{template_id}/option/@testUptime" + guiTests.openPage(test_name, env, (env) =>{ return vstMakeLocalApiUrl("project/{pk}/template/{template_id}/option/@testUptime/edit", {api_pk:env.objectId, api_template_id:env.template_id}) }) + + test_name = "project/{pk}/template/{template_id}/option/@testUptime/edit" guiTests.updateObject("project/{pk}/template/{template_id}/option/@testUptime", {args:{value:"uptime"}}, true); @@ -140,6 +148,7 @@ window.qunitTestsArray['guiPaths.project'] = { guiTests.deleteObject(test_name) + // Удалить проект test_name = "project/{pk}" guiTests.openPage(test_name, env, (env) =>{ return vstMakeLocalApiUrl("project/{pk}", {api_pk:env.objectId, api_template_id:env.template_id}) @@ -148,111 +157,3 @@ window.qunitTestsArray['guiPaths.project'] = { guiTests.deleteObject(test_name) } } - - - -window.qunitTestsArray['guiElements.form'] = { - test:function() - { - syncQUnit.addTest('guiElements.form', function ( assert ) - { - let element; - let formData; - let done = assert.async(); - - $("#guiElementsTestForm").remove(); - $("body").append("
") - - - formData = { - title:"Deploy", - form:{ - 'inventory' : { - title:'inventory', - required:true, - format:'hybrid_autocomplete', - dynamic_properties:{ - list_obj: "/project/{pk}/inventory/", - value_field: "id", - view_field: "name", - } - }, - user:{ - title:'User', - description: "connect as this user (default=None)", - format:'string', - type: "string", - }, - key_file: { - title:'Key file', - description: "use this file to authenticate the connection", - format:'secretfile', - type: "string", - dynamic_properties:{ - list_obj: "/project/{pk}/inventory/", - value_field: "id", - view_field: "name", - } - }, - extra_vars: { - title:"Execute parametrs", - format:'form', - form:{ - varName: { - name:'varName', - title:'Name', - default:'NameDefaultValue', - format:'string', - help:'Name', - }, - varTask: { - name:'varTask', - title:'Name', - default:'B', - format:'enum', - help:'Name', - enum:['A', 'B', 'C'], - }, - varVersion: { - name:'varVersion', - title:'Name', - default:true, - format:'boolean', - help:'Name', - }, - RunBtn: { - name:'RunBtn', - title:'abc_yml', - value:'abc.yml', - format:'button', - text:'Run abc.yml', - onclick:function(){ - - let val = element.getValue() - val.playbook = this.getValue() - - assert.ok(val.extra_vars['RunBtn'] == 'abc.yml', 'guiElements.form test RunBtn'); - assert.ok(val.playbook == 'abc.yml', 'guiElements.form test playbook'); - - assert.ok(val.extra_vars['varName'] == 'NameDefaultValue', 'guiElements.form test varName'); - - assert.ok(val.extra_vars['varVersion'] == true, 'guiElements.form test values'); - - testdone(done) - }, - class:'gui-test-form' - }, - } - } - } - } - - element = new guiElements.form(undefined, formData); - $("#guiElementsTestForm").insertTpl(element.render()) - - setTimeout(() => { - $("#guiElementsTestForm .btn_abc_yml").trigger('click') - }, 50) - }); - } -} diff --git a/polemarch/static/js/tests/pmUsers.js b/polemarch/static/js/tests/pmUsers.js index cf63dd74..779964b6 100644 --- a/polemarch/static/js/tests/pmUsers.js +++ b/polemarch/static/js/tests/pmUsers.js @@ -5,6 +5,9 @@ window.qunitTestsArray['guiPaths.profile/settings'] = { let path = "profile/settings" guiTests.openPage(path) guiTests.hasElement(1, ".btn_save", path) + guiTests.hasEditButton(false) + guiTests.hasAddButton(0, path) + guiTests.hasElement(1, ".gui-field-chartLineSettings", path) guiTests.hasAddButton(0, path) diff --git a/polemarch/static/templates/pmDashboard.html b/polemarch/static/templates/pmDashboard.html index e863b83a..6d72c528 100644 --- a/polemarch/static/templates/pmDashboard.html +++ b/polemarch/static/templates/pmDashboard.html @@ -1,14 +1,15 @@ - + - + - - - - + + + - + diff --git a/polemarch/static/templates/pmElements.html b/polemarch/static/templates/pmElements.html new file mode 100644 index 00000000..5b84b722 --- /dev/null +++ b/polemarch/static/templates/pmElements.html @@ -0,0 +1,83 @@ + + + + + \ No newline at end of file diff --git a/polemarch/static/templates/pmHistory.html b/polemarch/static/templates/pmHistory.html index b3e5f1a8..e73cdc3f 100644 --- a/polemarch/static/templates/pmHistory.html +++ b/polemarch/static/templates/pmHistory.html @@ -2,103 +2,104 @@ - diff --git a/polemarch/static/templates/pmProject.html b/polemarch/static/templates/pmProject.html index 675c0c75..77636657 100644 --- a/polemarch/static/templates/pmProject.html +++ b/polemarch/static/templates/pmProject.html @@ -1,45 +1,8 @@ - - - - - - diff --git a/polemarch/static/templates/pmUsers.html b/polemarch/static/templates/pmUsers.html index eb388199..e69de29b 100644 --- a/polemarch/static/templates/pmUsers.html +++ b/polemarch/static/templates/pmUsers.html @@ -1,70 +0,0 @@ - - - - - \ No newline at end of file diff --git a/polemarch/templates/auth/login.html b/polemarch/templates/auth/login.html index 65164427..6260fd83 100644 --- a/polemarch/templates/auth/login.html +++ b/polemarch/templates/auth/login.html @@ -3,5 +3,5 @@ {% load i18n %} {% block login_logo %} - Polemarch logo + Polemarch logo {% endblock %} diff --git a/polemarch/templates/gui/gui.html b/polemarch/templates/gui/gui.html index c69962fd..cff09550 100644 --- a/polemarch/templates/gui/gui.html +++ b/polemarch/templates/gui/gui.html @@ -3,19 +3,18 @@ {% load i18n %} {% block logo %} - {% endblock %} {% block common_sciprt %} {% endblock %} \ No newline at end of file diff --git a/requirements-doc.txt b/requirements-doc.txt index 94af4f65..325f0570 100644 --- a/requirements-doc.txt +++ b/requirements-doc.txt @@ -1,2 +1,2 @@ # Docs -vstutils[doc]==1.2.2 +vstutils[doc]==1.3.2 diff --git a/requirements.txt b/requirements.txt index 249a096e..334e1c33 100644 --- a/requirements.txt +++ b/requirements.txt @@ -1,5 +1,5 @@ # Main -vstutils[rpc,ldap,doc,prod]==1.2.2 +vstutils[rpc,ldap,doc,prod]==1.3.2 pyyaml==3.13 docutils==0.14 markdown2==2.3.6 diff --git a/setup.cfg b/setup.cfg index bd2510fe..23db968a 100644 --- a/setup.cfg +++ b/setup.cfg @@ -32,7 +32,7 @@ project = 'Polemarch' repo = vstconsulting/polemarch assets = dist/polemarch-{release}-0.x86_64.rpm - dist/polemarch-_{release}-0_amd64.deb + dist/polemarch_{release}-0_amd64.deb dist/polemarch-{release}.tar.gz [aliases] diff --git a/setup.py b/setup.py index 8593807f..c6cc8532 100644 --- a/setup.py +++ b/setup.py @@ -123,7 +123,9 @@ def finalize_options(self): publish=self.publish, name=self.release, dry_run=self.dry_run ) if self.assets: - self._gh_kwargs['asset_pattern'] = self.assets.format(release=self.release) + assets = self.assets.format(release=self.release) + assets = list(filter(bool, assets.split('\n'))) + self._gh_kwargs['asset_pattern'] = assets if self.body: self._gh_kwargs['body'] = self.body diff --git a/tox.ini b/tox.ini index c363b778..507a6fe9 100644 --- a/tox.ini +++ b/tox.ini @@ -27,6 +27,7 @@ commands = deps = coverage: -rrequirements.txt install: cython>=0.28 + py27-install: multiprocessing -rrequirements-doc.txt -rrequirements-git.txt -rrequirements-test.txt @@ -88,6 +89,7 @@ whitelist_externals = cp make commands = + pip install -U -r ../requirements-doc.txt make html # cp -rv _build/html ../public deps =