diff --git a/.dockerignore b/.dockerignore new file mode 100644 index 00000000..e81deadf --- /dev/null +++ b/.dockerignore @@ -0,0 +1,106 @@ +# Created by .ignore support plugin (hsz.mobi) +### Python template +# Byte-compiled / optimized / DLL files +__pycache__/ +*.py[cod] +*$py.class + +# C extensions +*.so + +# Distribution / packaging +.Python +build/ +develop-eggs/ +dist/ +downloads/ +eggs/ +.eggs/ +lib/ +lib64/ +parts/ +sdist/ +var/ +wheels/ +*.egg-info/ +.installed.cfg +*.egg +MANIFEST + +# PyInstaller +# Usually these files are written by a python script from a template +# before PyInstaller builds the exe, so as to inject date/other infos into it. +*.manifest +*.spec + +# Installer logs +pip-log.txt +pip-delete-this-directory.txt + +# Unit test / coverage reports +htmlcov/ +.tox/ +.coverage +.coverage.* +.cache +nosetests.xml +coverage.xml +*.cover +.hypothesis/ + +# Translations +*.mo +*.pot + +# Django stuff: +*.log +.static_storage/ +.media/ +local_settings.py + +# Flask stuff: +instance/ +.webassets-cache + +# Scrapy stuff: +.scrapy + +# Sphinx documentation +docs/_build/ + +# PyBuilder +target/ + +# Jupyter Notebook +.ipynb_checkpoints + +# pyenv +.python-version + +# celery beat schedule file +celerybeat-schedule + +# SageMath parsed files +*.sage.py + +# Environments +.env +.venv +env/ +venv/ +ENV/ +env.bak/ +venv.bak/ + +# Spyder project settings +.spyderproject +.spyproject + +# Rope project settings +.ropeproject + +# mkdocs documentation +/site + +# mypy +.mypy_cache/ diff --git a/.gitignore b/.gitignore index c28cbe72..77a09c89 100644 --- a/.gitignore +++ b/.gitignore @@ -11,7 +11,7 @@ build *egg-info dist env -polemarch/polemarch/static +# polemarch/polemarch/static *.log *.pid *nbproject diff --git a/Makefile b/Makefile index fcd37f16..ebbf5844 100644 --- a/Makefile +++ b/Makefile @@ -17,6 +17,10 @@ export DESCRIPTION SUMMARY = Infrastructure Heat Service for orchestration infrastructure by ansible. VENDOR = VST Consulting RELEASE = 0 +COMPOSE = docker-compose-testrun.yml +COMPOSE_ARGS = --abort-on-container-exit +COMPLEX_TESTS_COMPOSE = docker-compose-tests.yml +COMPLEX_TESTS_COMPOSE_ARGS = '--abort-on-container-exit --build' include rpm.mk include deb.mk @@ -66,7 +70,10 @@ build-clean: find . -name "*.pyc" -print0 | xargs -0 rm -rf -rm -rf build -rm -rf *.egg-info - -rm pylint_* + -rm -rf pylint_* + +clean_dist: + -rm -rf dist fclean: clean find ./polemarch -name "*.c" -print0 | xargs -0 rm -rf @@ -105,3 +112,12 @@ deb: mv -v ../$(NAME)_$(VER)*.deb dist/ # cleanup rm -rf debian + +compose: + docker-compose -f $(COMPOSE) build + +run: + docker-compose -f $(COMPOSE) up $(COMPOSE_ARGS) + +complex_tests: + $(MAKE) run COMPOSE=$(COMPLEX_TESTS_COMPOSE) COMPOSE_ARGS=$(COMPLEX_TESTS_COMPOSE_ARGS) diff --git a/deb.mk b/deb.mk index 79ef415b..6cab181b 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 +Build-Depends: debhelper (>= 9), python-virtualenv, python-pip, python-dev, gcc, libffi-dev, libssl-dev, libyaml-dev, libkrb5-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 +Depends: $${shlibs:Depends}, $${misc:Depends}, python-virtualenv, libffi6, libssl-dev, sshpass, libpython2.7, git, libyaml-dev, libkrb5-dev Description: $(SUMMARY) $(DESCRIPTION) endef @@ -67,6 +67,7 @@ override_dh_auto_install: rm -rf $(BUILDROOT)/* # install our package with all required python dependencies in virtualenv virtualenv --no-site-packages $(BUILDROOT)/$(INSTALLDIR) + rm -rf $(BUILDROOT)/$(INSTALLDIR)/local $(BUILDROOT)/$(INSTALLDIR)/bin/pip install $(PIPARGS) -r requirements-doc.txt $(BUILDROOT)/$(INSTALLDIR)/bin/pip install $(PIPARGS) dist/$(NAME)-$(VER).tar.gz $(BUILDROOT)/$(INSTALLDIR)/bin/pip install $(PIPARGS) -r requirements-git.txt diff --git a/docker-compose-testrun.yml b/docker-compose-testrun.yml new file mode 100644 index 00000000..6e7a4b3d --- /dev/null +++ b/docker-compose-testrun.yml @@ -0,0 +1,50 @@ +version: '3' +services: + db-server: + image: mysql:latest + environment: + - MYSQL_DATABASE=polemarch + - MYSQL_ROOT_PASSWORD=polemarch + - MYSQL_USER=polemarch + - MYSQL_PASSWORD=polemarch + volumes: + - "db_data:/var/lib/mysql" + memcache-server: + image: memcached + rabbitmq-server: + image: rabbitmq:latest + environment: + - RABBITMQ_DEFAULT_USER=polemarch + - RABBITMQ_DEFAULT_PASS=polemarch + - RABBITMQ_DEFAULT_VHOST=polemarch + polemarch: + build: + context: . + dockerfile: docker/Dockerfile.dev + entrypoint: /bin/bash -c "/bin/bash -c \"$${@}\"" + command: | + /bin/bash -c " + set -e + curl https://raw.githubusercontent.com/vishnubob/wait-for-it/master/wait-for-it.sh | tee ./wait-for-it.sh + chmod +x wait-for-it.sh + ./wait-for-it.sh db-server:3306 + ./wait-for-it.sh rabbitmq-server:5672 + mkdir -p /var/run/polemarch + chown polemarch:polemarch /projects /var/run/polemarch + sudo -u polemarch /opt/polemarch/bin/polemarchctl migrate + sudo -u polemarch /opt/polemarch/bin/uwsgi /opt/polemarch/lib/python2.7/site-packages/polemarch/web.ini + /bin/bash || exit 0 + " + external_links: + - db-server + - cache-server:memcache-server + - locks-server:memcache-server + - rabbitmq-server + volumes: + - "project_data:/projects" + ports: + - "8080:8080" + +volumes: + project_data: + db_data: \ No newline at end of file diff --git a/docker-compose-tests.rpm.yml b/docker-compose-tests.rpm.yml new file mode 100644 index 00000000..e267f257 --- /dev/null +++ b/docker-compose-tests.rpm.yml @@ -0,0 +1,36 @@ +version: '3.3' +services: + db-server: + container_name: dbtest + image: mysql:latest + environment: + - MYSQL_DATABASE=polemarch + - MYSQL_ROOT_PASSWORD=polemarch + - MYSQL_USER=polemarch + - MYSQL_PASSWORD=polemarch + volumes: + - type: tmpfs + target: /var/lib/mysql + memcache-server: + container_name: cachetest + image: memcached + polemarch_test_rpm: + build: + context: . + dockerfile: docker/Dockerfile.test.rpm + entrypoint: /bin/bash -c "/bin/bash -c \"$${@}\"" + command: | + /bin/bash -c " + set -e + curl https://raw.githubusercontent.com/vishnubob/wait-for-it/master/wait-for-it.sh > wait-for-it.sh + chmod +x wait-for-it.sh + ./wait-for-it.sh db-server:3306 + cd /home/polemarch + sudo -u polemarch -H /opt/polemarch/bin/coverage run --source='/opt/polemarch/lib/python2.7/site-packages/polemarch' -m polemarch test -v 2 polemarch.main.tests + sudo -u polemarch -H /opt/polemarch/bin/coverage report -m + /bin/bash || exit 0 + " + external_links: + - db-server + - cache-server:memcache-server + - locks-server:memcache-server diff --git a/docker/Dockerfile.dev b/docker/Dockerfile.dev new file mode 100644 index 00000000..a8780e4e --- /dev/null +++ b/docker/Dockerfile.dev @@ -0,0 +1,40 @@ +FROM centos:7 +MAINTAINER sergey.k@vstconsulting.net + +#Update and install required packages +RUN yum check-update || true +RUN yum install epel-release -y +RUN yum -y install sudo python-pip python-devel ansible sshpass git \ + rpm-build make gcc openssl-devel libffi libffi-devel \ + libyaml-devel python-virtualenv + +# For future tests +# RUN yum -y install https://centos7.iuscommunity.org/ius-release.rpm +# RUN yum -y install python3{5,6}u{,-devel,-pip} + + +# Install pip requires +RUN pip2 install -U pip +RUN pip2 install -U -I pip tox coverage rpmvenv editdistance pyyaml +RUN pip2 install -U 'cython>=0.25.2' + + +# Build and install rpm +COPY requirements-doc.txt /tmp/requirements-doc.txt +COPY requirements-git.txt /tmp/requirements-git.txt +COPY requirements.txt /tmp/requirements.txt +RUN pip2 install -U -r /tmp/requirements-doc.txt \ + -r /tmp/requirements-git.txt \ + -r /tmp/requirements.txt +RUN mkdir /tmp/polemarch +ADD . /tmp/polemarch/ +RUN cd /tmp/polemarch && make rpm +RUN yum install /tmp/polemarch/dist/*.rpm -y +RUN cp -vf /tmp/polemarch/test_settings.ini /etc/polemarch/settings.ini + + +# Run options +ENV DJANGO_DEBUG=true +EXPOSE 8080 + +ENTRYPOINT sudo -u polemarch /opt/polemarch/bin/uwsgi /opt/polemarch/lib/python2.7/site-packages/polemarch/web.ini diff --git a/docker/Dockerfile.test.rpm b/docker/Dockerfile.test.rpm new file mode 100644 index 00000000..e00858df --- /dev/null +++ b/docker/Dockerfile.test.rpm @@ -0,0 +1,17 @@ +FROM git.vst.lan/vst/tests:tox +MAINTAINER sergey.k@vstconsulting.net + +RUN mkdir /tmp/polemarch +ADD . /tmp/polemarch/ +RUN make rpm -C /tmp/polemarch/ +RUN yum install /tmp/polemarch/dist/*.rpm -y +RUN cp -vf /tmp/polemarch/test_settings.ini /etc/polemarch/settings.ini && \ + sudo chown polemarch:polemarch /etc/polemarch/settings.ini && \ + sudo -H -u polemarch /opt/polemarch/bin/pip install -r /tmp/polemarch/requirements-test.txt && \ + cp -vf /tmp/polemarch/.coveragerc /home/polemarch/ + + +# Run options +ENV DJANGO_DEBUG=true + +ENTRYPOINT sudo -H -u polemarch /opt/polemarch/bin/polemarchctl test -v2 polemarch.main.tests diff --git a/polemarch/__init__.py b/polemarch/__init__.py index f1f4426a..77136832 100644 --- a/polemarch/__init__.py +++ b/polemarch/__init__.py @@ -1,6 +1,6 @@ from .environment import prepare_environment -__version__ = "0.1.3" +__version__ = "0.1.4" def _main(**kwargs): # pylint: disable=unused-variable diff --git a/polemarch/api/permissions.py b/polemarch/api/permissions.py index dd2fd6bf..f1ceab66 100644 --- a/polemarch/api/permissions.py +++ b/polemarch/api/permissions.py @@ -6,15 +6,13 @@ def has_permission(self, request, view): return super(ModelPermission, self).has_permission(request, view) def has_object_permission(self, request, view, obj): - if request.user.is_staff: - return True - if request.user == obj: # nocv + if request.user.is_staff or request.user == obj: return True if request.method in permissions.SAFE_METHODS: # nocv return obj.viewable_by(request.user) # nocv if view.action in view.POST_WHITE_LIST: # nocv return obj.viewable_by(request.user) # nocv - return obj.editable_by(request.user) + return obj.editable_by(request.user) # noce class SuperUserPermission(ModelPermission): diff --git a/polemarch/api/v1/serializers.py b/polemarch/api/v1/serializers.py index 4c53f309..4de4edf1 100644 --- a/polemarch/api/v1/serializers.py +++ b/polemarch/api/v1/serializers.py @@ -3,6 +3,7 @@ import json import re +from collections import OrderedDict import six from django import dispatch from django.contrib.auth.models import User @@ -525,6 +526,8 @@ def execute(self): class TemplateSerializer(_WithVariablesSerializer): data = DictField(required=True, write_only=True) + options = DictField(write_only=True) + options_list = DictField(read_only=True) class Meta: model = models.Template @@ -533,6 +536,8 @@ class Meta: 'name', 'kind', 'data', + 'options', + 'options_list', ) def get_vars(self, representation): @@ -541,20 +546,41 @@ def get_vars(self, representation): except KeyError: return None + def set_opts_vars(self, rep, hidden_vars): + if not rep.get('vars', None): + return rep + var = rep['vars'] + for mask_key in hidden_vars: + if mask_key in var.keys(): + var[mask_key] = "[~~ENCRYPTED~~]" + return rep + + def repr_options(self, instance, data, hidden_vars): + hv = hidden_vars + hv = instance.HIDDEN_VARS if hv is None else hv + for name, rep in data.get('options', {}).items(): + data['options'][name] = self.set_opts_vars(rep, hv) + def to_representation(self, instance): + data = OrderedDict() if instance.kind in ["Task", "PeriodicTask", "Module"]: - return super(TemplateSerializer, self).to_representation( - instance, hidden_vars=models.PeriodicTask.HIDDEN_VARS + hidden_vars = models.PeriodicTask.HIDDEN_VARS + data = super(TemplateSerializer, self).to_representation( + instance, hidden_vars=hidden_vars ) + self.repr_options(instance, data, hidden_vars) elif instance.kind in ["Host", "Group"]: - return super(TemplateSerializer, self).to_representation( + data = super(TemplateSerializer, self).to_representation( instance, hidden_vars=models.Inventory.HIDDEN_VARS ) + return data class OneTemplateSerializer(TemplateSerializer): data = DictField(required=True) owner = UserSerializer(read_only=True) + options = DictField(required=False) + options_list = DictField(read_only=True) class Meta: model = models.Template @@ -564,11 +590,15 @@ class Meta: 'kind', 'owner', 'data', + 'options', + 'options_list', ) def execute(self, request): serializer = OneProjectSerializer(self.instance.project) - return self.instance.execute(serializer, request.user) + return self.instance.execute( + serializer, request.user, request.data.get('option', None) + ) ################################### @@ -697,6 +727,7 @@ class Meta: 'vars', 'owner', 'revision', + 'branch', 'url',) def inventories_operations(self, method, data): diff --git a/polemarch/api/v1/views.py b/polemarch/api/v1/views.py index d91dbdf9..d5ff1484 100644 --- a/polemarch/api/v1/views.py +++ b/polemarch/api/v1/views.py @@ -55,6 +55,15 @@ def update(self, request, *args, **kwargs): self.perform_update(serializer) return base.Response(serializer.data, 200).resp + @detail_route(methods=["post", "delete", "get"], url_path="settings") + def user_settings(self, request, *args, **kwargs): + obj = self.get_object() + method = request.method + if method != "GET": + obj.settings.data = request.data if method == "POST" else {} + obj.settings.save() + return base.Response(obj.settings.data, 200).resp + class TeamViewSet(base.PermissionMixin, base.ModelViewSetSet): model = serializers.models.UserGroup @@ -190,6 +199,18 @@ def facts(self, request, *args, **kwargs): objs = self.get_serializer(self.get_object()).get_facts(request) return base.Response(objs, 200).resp + @detail_route(methods=["delete"]) + def clear(self, request, *args, **kwargs): + default_message = "Output trancated.\n" + obj = self.get_object() + if obj.status in ["RUN", "DELAY"] or obj.raw_stdout == default_message: + raise excepts.NotAcceptable( + "Job is running or already trancated" + ) + obj.raw_stdout = default_message + result = self.get_serializer(obj).get_raw(request) + return base.Response(result, 204).resp + class TemplateViewSet(base.PermissionMixin, base.ModelViewSetSet): model = serializers.models.Template @@ -358,6 +379,7 @@ def list(self, request, *args, **kwargs): # pylint: disable=unused-argument stats = OrderedDict( projects=self._get_count_by_user(serializers.models.Project), + templates=self._get_count_by_user(serializers.models.Template), inventories=self._get_count_by_user(serializers.models.Inventory), groups=self._get_count_by_user(serializers.models.Group), hosts=self._get_count_by_user(serializers.models.Host), diff --git a/polemarch/main/migrations/0035_template_options_data.py b/polemarch/main/migrations/0035_template_options_data.py new file mode 100644 index 00000000..94bb7c56 --- /dev/null +++ b/polemarch/main/migrations/0035_template_options_data.py @@ -0,0 +1,20 @@ +# -*- coding: utf-8 -*- +# Generated by Django 1.11.3 on 2017-12-22 08:19 +from __future__ import unicode_literals + +from django.db import migrations, models + + +class Migration(migrations.Migration): + + dependencies = [ + ('main', '0034_periodictask_enabled'), + ] + + operations = [ + migrations.AddField( + model_name='template', + name='options_data', + field=models.TextField(default=''), + ), + ] diff --git a/polemarch/main/migrations/0035_usersettings.py b/polemarch/main/migrations/0035_usersettings.py new file mode 100644 index 00000000..edf5122c --- /dev/null +++ b/polemarch/main/migrations/0035_usersettings.py @@ -0,0 +1,37 @@ +# -*- coding: utf-8 -*- +# Generated by Django 1.11.3 on 2018-02-21 06:17 +from __future__ import unicode_literals + +from django.conf import settings +from django.db import migrations, models +import django.db.models.deletion + + +def create_user_settings(apps, schema_editor): + User = apps.get_registered_model('auth', 'User') + UserSettings = apps.get_registered_model('main', 'UserSettings') + for user in User.objects.all(): + UserSettings.objects.create(user=user) + + +class Migration(migrations.Migration): + + dependencies = [ + migrations.swappable_dependency(settings.AUTH_USER_MODEL), + ('main', '0034_periodictask_enabled'), + ] + + operations = [ + migrations.CreateModel( + name='UserSettings', + fields=[ + ('id', models.AutoField(max_length=20, primary_key=True, serialize=False)), + ('settings', models.TextField(default='{}')), + ('user', models.OneToOneField(on_delete=django.db.models.deletion.CASCADE, related_name='settings', related_query_name='settings', to=settings.AUTH_USER_MODEL)), + ], + options={ + 'abstract': False, + }, + ), + migrations.RunPython(create_user_settings), + ] diff --git a/polemarch/main/migrations/0036_merge_20180226_0050.py b/polemarch/main/migrations/0036_merge_20180226_0050.py new file mode 100644 index 00000000..5ce2c9d3 --- /dev/null +++ b/polemarch/main/migrations/0036_merge_20180226_0050.py @@ -0,0 +1,16 @@ +# -*- coding: utf-8 -*- +# Generated by Django 1.11.10 on 2018-02-26 00:50 +from __future__ import unicode_literals + +from django.db import migrations + + +class Migration(migrations.Migration): + + dependencies = [ + ('main', '0035_usersettings'), + ('main', '0035_template_options_data'), + ] + + operations = [ + ] diff --git a/polemarch/main/models/__init__.py b/polemarch/main/models/__init__.py index 8136c9da..df3fb6a2 100644 --- a/polemarch/main/models/__init__.py +++ b/polemarch/main/models/__init__.py @@ -13,7 +13,7 @@ from .vars import Variable from .hosts import Host, Group, Inventory from .projects import Project -from .users import BaseUser, UserGroup, ACLPermission +from .users import BaseUser, UserGroup, ACLPermission, UserSettings from .tasks import Task, PeriodicTask, History, HistoryLines, Template from .hooks import Hook from ..validators import RegexValidator @@ -134,6 +134,10 @@ def validate_template_args(instance, **kwargs): if instance.kind == "PeriodicTask" and instance.data["kind"] == "MODULE": command = "module" AnsibleArgumentsReference().validate_args(command, ansible_args) + for _, data in dict(instance.options).items(): + AnsibleArgumentsReference().validate_args( + command, data.get('vars', {}) + ) @receiver(signals.pre_delete, sender=Project) @@ -220,3 +224,8 @@ def polemarch_hook(instance, **kwargs): elif not created: when = "on_object_upd" send_polemarch_models(when, instance) + + +@receiver(signals.post_save, sender=BaseUser) +def create_settings_for_user(instance, **kwargs): + UserSettings.objects.get_or_create(user=instance) diff --git a/polemarch/main/models/projects.py b/polemarch/main/models/projects.py index 18584c2e..b63c2422 100644 --- a/polemarch/main/models/projects.py +++ b/polemarch/main/models/projects.py @@ -163,3 +163,7 @@ def sync(self, *args, **kwargs): @property def revision(self): return self.repo_class.revision() + + @property + def branch(self): + return self.repo_class.get_branch_name() diff --git a/polemarch/main/models/tasks.py b/polemarch/main/models/tasks.py index a5f9e5bb..aa481a33 100644 --- a/polemarch/main/models/tasks.py +++ b/polemarch/main/models/tasks.py @@ -162,6 +162,7 @@ class Template(ACLModel): name = models.CharField(max_length=512) kind = models.CharField(max_length=32) template_data = models.TextField(default="") + options_data = models.TextField(default="") inventory = models.CharField(max_length=128, default=None, blank=True, null=True) project = ForeignKeyACL(Project, @@ -182,13 +183,20 @@ class Meta: template_fields["Host"] = ["name", "vars"] template_fields["Group"] = template_fields["Host"] + ["children"] + excepted_execution_fields = ['inventory', 'project'] _exec_types = { "Task": "playbook", "Module": "module", } + def get_option_data(self, option): + return self.options.get(option, {}) + + def get_options_data(self): + return json.loads(self.options_data or '{}') + def get_data(self): - data = json.loads(self.template_data) + data = json.loads(self.template_data or '{}') if "project" in self.template_fields[self.kind] and self.project: data['project'] = self.project.id if "inventory" in self.template_fields[self.kind] and self.inventory: @@ -198,14 +206,18 @@ def get_data(self): data['inventory'] = self.inventory return data - def execute(self, serializer, user): + def execute(self, serializer, user, option=None): # pylint: disable=protected-access tp = self._exec_types.get(self.kind, None) if tp is None: raise UnsupportedMediaType(media_type=self.kind) data = self.get_data() data.pop("project", None) + option_data = self.get_option_data(option) + option_vars = option_data.pop("vars", {}) vars = data.pop("vars", {}) + vars.update(option_vars) + data.update(option_data) data.update(vars) return serializer._execution(tp, data, user) @@ -217,15 +229,36 @@ def _convert_to_data(self, value): else: raise ValueError("Unknown data type set.") - def keep_encrypted(self, new_vars): - if not self.template_data: - return new_vars - old_vars = self.data['vars'] + def __encrypt(self, new_vars, data_name='data'): + old_vars = getattr(self, data_name).get('vars', {}) for key in new_vars.keys(): if new_vars[key] == '[~~ENCRYPTED~~]': new_vars[key] = old_vars.get(key, new_vars[key]) return new_vars + def keep_encrypted_data(self, new_vars): + if not self.template_data: + return new_vars + return self.__encrypt(new_vars) + + def _validate_option_data(self, data): + errors = {} + for name in data.keys(): + if name in self.excepted_execution_fields: + errors['options'] = ['Disallowed to override {}.'.format(name)] + if errors: + raise ValidationError(errors) + + def set_options_data(self, value): + options_data = self._convert_to_data(value) + new = dict() + for option, data in options_data.items(): + self._validate_option_data(data) + if data.get('vars', None): + data['vars'] = self.__encrypt(data['vars'], 'options') + new[option] = data + self.options_data = json.dumps(new) + def set_data(self, value): data = self._convert_to_data(value) project_id = data.pop('project', None) @@ -240,7 +273,7 @@ def set_data(self, value): self.inventory = Inventory.objects.get(pk=int(inventory_id)).id except (ValueError, TypeError, Inventory.DoesNotExist): self.inventory = inventory_id - data['vars'] = self.keep_encrypted(data['vars']) + data['vars'] = self.keep_encrypted_data(data['vars']) self.template_data = json.dumps(data) def __setattr__(self, key, value): @@ -255,13 +288,29 @@ def data(self): @data.setter def data(self, value): - return self.set_data(value) + self.set_data(value) @data.deleter - def data(self): - self.template_data = "" # nocv - self.inventory = None # nocv - self.project = None # nocv + def data(self): # nocv + self.template_data = "" + self.inventory = None + self.project = None + + @property + def options(self): + return self.get_options_data() + + @options.setter + def options(self, value): + self.set_options_data(value) + + @options.deleter + def options(self): # nocv + self.options_data = '' + + @property + def options_list(self): + return list(self.options.keys()) class HistoryQuerySet(ACLHistoryQuerySet): diff --git a/polemarch/main/models/users.py b/polemarch/main/models/users.py index 01926b5c..69cd7a32 100644 --- a/polemarch/main/models/users.py +++ b/polemarch/main/models/users.py @@ -2,10 +2,11 @@ from __future__ import unicode_literals import logging +import json from django.contrib.auth.models import Group as BaseGroup from django.contrib.auth.models import User as BaseUser -from .base import models +from .base import models, BModel from . import acl @@ -31,3 +32,22 @@ def users_list(self): @users_list.setter def users_list(self, value): self.users.set(BaseUser.objects.filter(id__in=value)) + + +class UserSettings(BModel): + user = models.OneToOneField(BaseUser, + related_query_name="settings", + related_name="settings") + settings = models.TextField(default="{}") + + @property + def data(self): + return json.loads(self.settings) + + @data.setter + def data(self, value): + self.settings = json.dumps(value) + + @data.deleter + def data(self): + self.settings = '{}' diff --git a/polemarch/main/models/utils.py b/polemarch/main/models/utils.py index 01e1a6ff..b15d0e3c 100644 --- a/polemarch/main/models/utils.py +++ b/polemarch/main/models/utils.py @@ -1,6 +1,7 @@ # -*- coding: utf-8 -*- from __future__ import unicode_literals +import re import sys import logging from os.path import dirname @@ -8,6 +9,7 @@ import six from django.utils import timezone +from .hosts import Inventory from ...main.utils import (tmp_file, CmdExecutor, KVExchanger, CalledProcessError) @@ -92,6 +94,7 @@ class Inventory(object): def __init__(self, inventory, cwd="/tmp"): self.cwd = cwd self.__file = None + self.hidden_vars = Inventory.HIDDEN_VARS if isinstance(inventory, (six.string_types, six.text_type)): self.raw, self.keys = self.get_from_file(inventory) else: @@ -162,12 +165,23 @@ def workdir(self): def path_to_ansible(self): return dirname(sys.executable) + "/" + self.command_type + def hide_passwords(self, raw): + regex = r"" + for hide in self.inventory_object.hidden_vars: + regex += r"|" if regex else r"" + regex += r"(?<=" + hide + r"=).{1,}?(?=[\n\t\s])" + subst = "[~~ENCRYPTED~~]" + raw = re.sub(regex, subst, raw, 0, re.MULTILINE) + return raw + def prepare(self, target, inventory, history, project): project.check_path(inventory) self.target, self.project = target, project self.history = history if history else DummyHistory() self.inventory_object = self.Inventory(inventory, cwd=self.workdir) - self.history.raw_inventory = self.inventory_object.raw + self.history.raw_inventory = self.hide_passwords( + self.inventory_object.raw + ) self.history.status = "RUN" self.history.revision = project.revision self.history.save() diff --git a/polemarch/main/repo/__init__.py b/polemarch/main/repo/__init__.py new file mode 100644 index 00000000..68f1581b --- /dev/null +++ b/polemarch/main/repo/__init__.py @@ -0,0 +1,3 @@ +from .vcs import Git +from .tar import Tar +from .manual import Manual diff --git a/polemarch/main/repo/_base.py b/polemarch/main/repo/_base.py new file mode 100644 index 00000000..0f95f282 --- /dev/null +++ b/polemarch/main/repo/_base.py @@ -0,0 +1,135 @@ +# pylint: disable=expression-not-assigned,abstract-method,import-error +from __future__ import unicode_literals + +import os +import re +import shutil +import logging + +from six.moves.urllib.request import urlretrieve +from django.db import transaction + +logger = logging.getLogger("polemarch") + + +class _Base(object): + regex = r"(^[\w\d\.\-_]{1,})\.yml" + + def __init__(self, project, **options): + self.options = options + self.proj = project + self.path = self.proj.path + + def _set_status(self, status): + self.proj.set_status(status) + + def _set_tasks_list(self, playbooks_names): + self.proj.tasks.all().delete() + for playbook in playbooks_names: + name = playbook.split(".yml")[0] + self.proj.tasks.create(name=name, playbook=playbook) + + def _update_tasks(self, files): + reg = re.compile(self.regex) + playbooks = filter(reg.match, files) + self._set_tasks_list(playbooks) + + def _get_files(self, repo=None): + # pylint: disable=unused-argument + return os.listdir(self.path) + + def _operate(self, operation, **kwargs): + return operation(kwargs) + + def _make_operations(self, operation): + self._set_status("SYNC") + try: + with transaction.atomic(): + result = self._operate(operation) + self._set_status("OK") + self._update_tasks(self._get_files(result[0])) + except Exception as err: + logger.error("Project[{}] sync error:\n{}".format(self.proj, err)) + self._set_status("ERROR") + raise + else: + return result + + def make_clone(self, options): # pragma: no cover + ''' + Make operations for clone repo + :param options: any options, like env variables or any thing + :return: tuple object with 2 args: repo obj and fetch results + ''' + raise NotImplementedError + + def make_update(self, options): # pragma: no cover + ''' + Make operation for fetch repo tree + :param options: any options, like env variables or any thing + :return: tuple object with 2 args: repo obj and fetch results + ''' + raise NotImplementedError + + def get_revision(self, *args, **kwargs): + # pylint: disable=unused-argument + return "NO VCS" + + def get_branch_name(self): + return "NO VCS" + + def delete(self): + if os.path.exists(self.path): + if os.path.isfile(self.path): + os.remove(self.path) # nocv + else: + shutil.rmtree(self.path) + return "Repository removed!" + return "Repository does not exists." # nocv + + def clone(self): + # pylint: disable=broad-except + attempt = 2 + for __ in range(attempt): + try: + repo = self._make_operations(self.make_clone)[0] + return "Received {} files.".format(len(self._get_files(repo))) + except: + self.delete() + raise Exception("Clone didn't perform by {} attempts.".format(attempt)) + + def get(self): + # pylint: disable=broad-except + attempt = 2 + for __ in range(attempt): + try: + return self._make_operations(self.make_update) + except: + self.delete() + raise Exception("Upd didn't perform by {} attempts.".format(attempt)) + + def check(self): + pass # nocv + + def revision(self): + return self._operate(self.get_revision) + + +class _ArchiveRepo(_Base): + def make_clone(self, options): + os.mkdir(self.path) + archive = self._download(self.proj.repository, options) + self._extract(archive, self.path, options) + return None, None + + def make_update(self, options): + archive = self._download(self.proj.repository, options) + self._extract(archive, self.path, options) + return None, None + + def _download(self, url, options): + # pylint: disable=unused-argument + return urlretrieve(url)[0] # nocv + + def _extract(self, archive, path, options): + raise NotImplementedError # nocv diff --git a/polemarch/main/repo/manual.py b/polemarch/main/repo/manual.py new file mode 100644 index 00000000..3a2930db --- /dev/null +++ b/polemarch/main/repo/manual.py @@ -0,0 +1,18 @@ +# pylint: disable=expression-not-assigned,abstract-method,import-error +from __future__ import unicode_literals +from ._base import _Base, os + + +class Manual(_Base): + def make_clone(self, options): + try: + os.mkdir(self.path) + except OSError as oserror: + if oserror.errno == os.errno.EEXIST: + self.delete() + return self.make_clone(options) + raise # nocv + return None, None + + def make_update(self, options): + return None, None diff --git a/polemarch/main/repo/tar.py b/polemarch/main/repo/tar.py new file mode 100644 index 00000000..ddf1024b --- /dev/null +++ b/polemarch/main/repo/tar.py @@ -0,0 +1,18 @@ +# pylint: disable=expression-not-assigned,abstract-method,import-error +from __future__ import unicode_literals +import tarfile +from ._base import _ArchiveRepo, shutil + + +class Tar(_ArchiveRepo): + def _extract(self, archive, path, options): + # pylint: disable=broad-except + shutil.move(path, path + ".bak") + try: + with tarfile.open(archive) as arch: + arch.extractall(path) + except: + self.delete() + shutil.move(path + ".bak", path) + else: + shutil.rmtree(path + ".bak") diff --git a/polemarch/main/repo/vcs.py b/polemarch/main/repo/vcs.py new file mode 100644 index 00000000..6358aff0 --- /dev/null +++ b/polemarch/main/repo/vcs.py @@ -0,0 +1,127 @@ +# pylint: disable=expression-not-assigned,abstract-method,import-error +from __future__ import unicode_literals +import git +from ._base import _Base, os +from ..utils import tmp_file_context, raise_context + + +class _VCS(_Base): + def vsc_clone(self, *args, **kwargs): + raise NotImplementedError() + + def vcs_update(self, *args, **kwargs): + raise NotImplementedError() + + def get_repo(self, *args, **kwargs): + raise NotImplementedError() + + +class Git(_VCS): + _fetch_statuses = [ + "NEW_TAG", "NEW_HEAD", "HEAD_UPTODATE", + "TAG_UPDATE", "REJECTED", "FORCED_UPDATE", + "FAST_FORWARD", "ERROR" + ] + _ssh_options = { + "StrictHostKeyChecking": "no", + "TCPKeepAlive": "yes", + "IdentitiesOnly": "yes", + "UserKnownHostsFile": "/dev/null", + "PubkeyAuthentication": "yes" + } + + def __init__(self, *args, **kwargs): + super(Git, self).__init__(*args, **kwargs) + self.env = self.options.get("GIT_ENV", dict()) + self._fetch_map = { + 1 << x: self._fetch_statuses[x] for x in range(8) + } + + def get_repo(self): + return git.Repo(self.path) + + def vsc_clone(self, *args, **kwargs): + return git.Repo.clone_from(*args, **kwargs) + + def vcs_update(self, repo, env): + with repo.git.custom_environment(**env): + kwargs = self.options.get("FETCH_KWARGS", dict()) + fetch_result = repo.remotes.origin.pull(**kwargs) + return fetch_result + + def get_branch_name(self): + # pylint: disable=broad-except + with raise_context(): + return self.get_repo().active_branch.name + return "waiting..." + + def make_clone(self, env): + kw = dict(**self.options.get("CLONE_KWARGS", dict())) + branch = self.proj.vars.get('repo_branch', None) + if branch: + kw['branch'] = branch + repo = self.vsc_clone(self.proj.repository, self.path, env=env, **kw) + self.proj.variables.update_or_create( + key='repo_branch', defaults=dict(value=repo.active_branch.name) + ) + return repo, None + + def _get_or_create_repo(self, env): + try: + repo = self.get_repo() + branch = self.proj.vars.get('repo_branch', None) + if branch and repo.active_branch.name != branch: + self.delete() + raise git.NoSuchPathError + except git.NoSuchPathError: + repo = self.make_clone(env)[0] + return repo + + def make_update(self, env): + repo = self._get_or_create_repo(env) + return repo, self.vcs_update(repo, env) + + def get_revision(self, *args, **kwargs): + # pylint: disable=unused-argument + repo = self.get_repo() + return repo.head.object.hexsha + + def _with_password(self, tmp, env_vars): + env_vars.update(self.env.get("PASSWORD", dict())) + tmp.write("echo '{}'".format(self.proj.vars["repo_password"])) + os.chmod(tmp.name, 0o700) + env_vars["GIT_ASKPASS"] = env_vars.get("GIT_ASKPASS", tmp.name) + tmp.close() + return env_vars + + def _with_key(self, tmp, env_vars): + env_vars.update(self.env.get("KEY", dict())) + tmp.write(self.proj.vars["repo_key"]) + tmp.close() + ssh = "ssh -vT -i {} -F /dev/null".format(tmp.name) + for key, value in self._ssh_options.items(): + ssh += " -o {}={}".format(key, value) + env_vars["GIT_SSH_COMMAND"] = env_vars.get("GIT_SSH_COMMAND", ssh) + return env_vars + + def _operate(self, operation, **env_vars): + env_vars.update(self.env.get("GLOBAL", dict())) + with tmp_file_context(delete=False) as tmp: + if self.proj.vars.get("repo_password", None) is not None: + env_vars = self._with_password(tmp, env_vars) + elif self.proj.vars.get("repo_key", None) is not None: + env_vars = self._with_password(tmp, env_vars) + return super(Git, self)._operate(operation, **env_vars) + + def _get_files(self, repo=None): + return dict(repo.index.entries.keys()).keys() + + def get(self): + return {res.ref.remote_head: self._fetch_map[res.flags] + for res in super(Git, self).get()[1]} + + def revision(self): + try: + return self._operate(self.get_revision) + except git.GitError: + return "ERROR" diff --git a/polemarch/main/repo_backends.py b/polemarch/main/repo_backends.py deleted file mode 100644 index 13f3d4e9..00000000 --- a/polemarch/main/repo_backends.py +++ /dev/null @@ -1,236 +0,0 @@ -# pylint: disable=expression-not-assigned,abstract-method,import-error -from __future__ import unicode_literals - -import os -import re -import shutil -import logging -import tarfile - -import git -from six.moves.urllib.request import urlretrieve -from django.db import transaction -from .utils import tmp_file_context - -logger = logging.getLogger("polemarch") - - -class _Base(object): - regex = r"(^[\w\d\.\-_]{1,})\.yml" - - def __init__(self, project, **options): - self.options = options - self.proj = project - self.path = self.proj.path - - def _set_status(self, status): - self.proj.set_status(status) - - def _set_tasks_list(self, playbooks_names): - self.proj.tasks.all().delete() - for playbook in playbooks_names: - name = playbook.split(".yml")[0] - self.proj.tasks.create(name=name, playbook=playbook) - - def _update_tasks(self, files): - reg = re.compile(self.regex) - playbooks = filter(reg.match, files) - self._set_tasks_list(playbooks) - - def _get_files(self, repo=None): - # pylint: disable=unused-argument - return os.listdir(self.path) - - def _operate(self, operation, **kwargs): - return operation(kwargs) - - def _make_operations(self, operation): - self._set_status("SYNC") - try: - with transaction.atomic(): - result = self._operate(operation) - self._set_status("OK") - self._update_tasks(self._get_files(result[0])) - except Exception as err: - logger.error("Project[{}] sync error:\n{}".format(self.proj, err)) - self._set_status("ERROR") - raise - else: - return result - - def make_clone(self, options): # pragma: no cover - ''' - Make operations for clone repo - :param options: any options, like env variables or any thing - :return: tuple object with 2 args: repo obj and fetch results - ''' - raise NotImplementedError - - def make_update(self, options): # pragma: no cover - ''' - Make operation for fetch repo tree - :param options: any options, like env variables or any thing - :return: tuple object with 2 args: repo obj and fetch results - ''' - raise NotImplementedError - - def get_revision(self, *args, **kwargs): - # pylint: disable=unused-argument - return "NO VCS" - - def delete(self): - if os.path.exists(self.path): - if os.path.isfile(self.path): - os.remove(self.path) # nocv - else: - shutil.rmtree(self.path) - return "Repository removed!" - return "Repository does not exists." # nocv - - def clone(self): - repo = self._make_operations(self.make_clone)[0] - return "Received {} files.".format(len(self._get_files(repo))) - - def get(self): - return self._make_operations(self.make_update) - - def check(self): - pass # nocv - - def revision(self): - return self._operate(self.get_revision) - - -class Git(_Base): - _fetch_statuses = [ - "NEW_TAG", "NEW_HEAD", "HEAD_UPTODATE", - "TAG_UPDATE", "REJECTED", "FORCED_UPDATE", - "FAST_FORWARD", "ERROR" - ] - _ssh_options = { - "StrictHostKeyChecking": "no", - "TCPKeepAlive": "yes", - "IdentitiesOnly": "yes", - "UserKnownHostsFile": "/dev/null", - "PubkeyAuthentication": "yes" - } - - def __init__(self, *args, **kwargs): - super(Git, self).__init__(*args, **kwargs) - self.env = self.options.get("GIT_ENV", dict()) - self._fetch_map = { - 1 << x: self._fetch_statuses[x] for x in range(8) - } - - def make_clone(self, env): - repo = git.Repo.clone_from(self.proj.repository, self.path, env=env, - **self.options.get("CLONE_KWARGS", dict())) - return repo, None - - def _get_or_create_repo(self, env): - try: - repo = git.Repo(self.path) - except git.NoSuchPathError: - repo = self.make_clone(env)[0] - return repo - - def make_update(self, env): - repo = self._get_or_create_repo(env) - with repo.git.custom_environment(**env): - kwargs = self.options.get("FETCH_KWARGS", dict()) - fetch_result = repo.remotes.origin.pull(**kwargs) - return repo, fetch_result - - def get_revision(self, *args, **kwargs): - # pylint: disable=unused-argument - repo = git.Repo(self.path) - return repo.head.object.hexsha - - def _with_password(self, tmp, env_vars): - env_vars.update(self.env.get("PASSWORD", dict())) - tmp.write("echo '{}'".format(self.proj.vars["repo_password"])) - os.chmod(tmp.name, 0o700) - env_vars["GIT_ASKPASS"] = env_vars.get("GIT_ASKPASS", tmp.name) - tmp.close() - return env_vars - - def _with_key(self, tmp, env_vars): - env_vars.update(self.env.get("KEY", dict())) - tmp.write(self.proj.vars["repo_key"]) - tmp.close() - ssh = "ssh -vT -i {} -F /dev/null".format(tmp.name) - for key, value in self._ssh_options.items(): - ssh += " -o {}={}".format(key, value) - env_vars["GIT_SSH_COMMAND"] = env_vars.get("GIT_SSH_COMMAND", ssh) - return env_vars - - def _operate(self, operation, **env_vars): - env_vars.update(self.env.get("GLOBAL", dict())) - with tmp_file_context(delete=False) as tmp: - if self.proj.vars.get("repo_password", None) is not None: - env_vars = self._with_password(tmp, env_vars) - elif self.proj.vars.get("repo_key", None) is not None: - env_vars = self._with_password(tmp, env_vars) - return super(Git, self)._operate(operation, **env_vars) - - def _get_files(self, repo=None): - return dict(repo.index.entries.keys()).keys() - - def get(self): - return {res.ref.remote_head: self._fetch_map[res.flags] - for res in super(Git, self).get()[1]} - - def revision(self): - try: - return self._operate(self.get_revision) - except git.GitError: - return "ERROR" - - -class _ArchiveRepo(_Base): - def make_clone(self, options): - os.mkdir(self.path) - archive = self._download(self.proj.repository, options) - self._extract(archive, self.path, options) - return None, None - - def make_update(self, options): - archive = self._download(self.proj.repository, options) - self._extract(archive, self.path, options) - return None, None - - def _download(self, url, options): - # pylint: disable=unused-argument - return urlretrieve(url)[0] # nocv - - def _extract(self, archive, path, options): - raise NotImplementedError # nocv - - -class Tar(_ArchiveRepo): - def _extract(self, archive, path, options): - # pylint: disable=broad-except - shutil.move(path, path + ".bak") - try: - with tarfile.open(archive) as arch: - arch.extractall(path) - except: - self.delete() - shutil.move(path + ".bak", path) - else: - shutil.rmtree(path + ".bak") - - -class Manual(_Base): - def make_clone(self, options): - try: - os.mkdir(self.path) - except OSError as oserror: - if oserror.errno == os.errno.EEXIST: - self.delete() - return self.make_clone(options) - raise # nocv - return None, None - - def make_update(self, options): - return None, None diff --git a/polemarch/main/settings.ini b/polemarch/main/settings.ini index d8dfcf4c..40ce48d4 100644 --- a/polemarch/main/settings.ini +++ b/polemarch/main/settings.ini @@ -115,6 +115,10 @@ ############################################################## # pidfile = /var/run/polemarch/web.pid -# Path to log of web-server +# Should web-server run as daemon or as regular application ############################################################## -# daemonize = /var/log/polemarch/web.log \ No newline at end of file +# daemon = true + +# Path to log of web-server (applicable if daemon=true) +############################################################## +# log_file = /var/log/polemarch/web.log \ No newline at end of file diff --git a/polemarch/main/settings.py b/polemarch/main/settings.py index 06bc1583..d165b637 100644 --- a/polemarch/main/settings.py +++ b/polemarch/main/settings.py @@ -50,7 +50,8 @@ pass # SECURITY WARNING: don't run with debug turned on in production! -DEBUG = config.getboolean("main", "debug", fallback=False) +DEBUG = os.getenv('DJANGO_DEBUG', + config.getboolean("main", "debug", fallback=False)) # Directory for git projects PROJECTS_DIR = config.get("main", "projects_dir", fallback="{HOME}/projects").format(**__kwargs) @@ -152,7 +153,7 @@ pymysql.install_as_MySQLdb() if __DB_SETTINGS['ENGINE'] == 'django.db.polemarch.sqlite3': - __DB_OPTIONS["timeout"] = __DB_OPTIONS.get("timeout", 10) # nocv + __DB_OPTIONS["timeout"] = __DB_OPTIONS.get("timeout", 20) # nocv __DB_SETTINGS["OPTIONS"] = __DB_OPTIONS @@ -213,7 +214,10 @@ "polemarch.api.permissions.ModelPermission", ), 'EXCEPTION_HANDLER': 'polemarch.api.handlers.polemarch_exception_handler', - 'DEFAULT_FILTER_BACKENDS': ('rest_framework.filters.DjangoFilterBackend',), + 'DEFAULT_FILTER_BACKENDS': ( + 'rest_framework.filters.DjangoFilterBackend', + 'rest_framework.filters.OrderingFilter', + ), 'DEFAULT_PAGINATION_CLASS': 'rest_framework.pagination.LimitOffsetPagination', 'PAGE_SIZE': config.getint("web", "rest_page_limit", fallback=PAGE_LIMIT), } @@ -348,7 +352,7 @@ REPO_BACKENDS = { "GIT": { - "BACKEND": "polemarch.main.repo_backends.Git", + "BACKEND": "polemarch.main.repo.Git", "OPTIONS": { "CLONE_KWARGS": { "depth": 1 @@ -363,10 +367,10 @@ } }, "TAR": { - "BACKEND": "polemarch.main.repo_backends.Tar", + "BACKEND": "polemarch.main.repo.Tar", }, "MANUAL": { - "BACKEND": "polemarch.main.repo_backends.Manual", + "BACKEND": "polemarch.main.repo.Manual", } } diff --git a/polemarch/main/templates/gui/gui.html b/polemarch/main/templates/gui/gui.html index a44a5b9c..9ac34fc4 100644 --- a/polemarch/main/templates/gui/gui.html +++ b/polemarch/main/templates/gui/gui.html @@ -17,6 +17,7 @@ + @@ -63,7 +64,7 @@ 'templates/pmGroups', 'templates/pmItems', //'templates/pmHosts', - //'templates/pmUsers', + //'templates/pmUsers' ] // alert("New-") diff --git a/polemarch/main/templates/menu.html b/polemarch/main/templates/menu.html index 81bc7fa5..78ecea20 100644 --- a/polemarch/main/templates/menu.html +++ b/polemarch/main/templates/menu.html @@ -1,18 +1,19 @@ -{% load i18n %} -{% load staticfiles %} -{% load rest_framework %} -{% get_current_language as LANGUAGE_CODE %}{% get_current_language_bidi as LANGUAGE_BIDI %} - - {% if user.is_authenticated %} -
  • {% trans 'API' %}
  • - {% endif %} - - {% if user.is_active and user.is_staff %} - - {% endif %} - {% if user.is_authenticated %} -
  • Logout
  • - {% else %} -
  • Login
  • - {% endif %} +{% load i18n %} +{% load staticfiles %} +{% load rest_framework %} +{% get_current_language as LANGUAGE_CODE %}{% get_current_language_bidi as LANGUAGE_BIDI %} + + {% if user.is_authenticated %} +
  • {% trans 'API' %}
  • + {% endif %} + + {% if user.is_active and user.is_staff %} + + {% endif %} + {% if user.is_authenticated %} +
  • {{user.username}}
  • +
  • Logout
  • + {% else %} +
  • Login
  • + {% endif %} \ No newline at end of file diff --git a/polemarch/main/templates/models/group b/polemarch/main/templates/models/group index d0fde035..ba176d83 100644 --- a/polemarch/main/templates/models/group +++ b/polemarch/main/templates/models/group @@ -1,7 +1,7 @@ -[{{group.name}}{% if group.children %}:children{% endif %}] +{% autoescape off %}[{{group.name}}{% if group.children %}:children{% endif %}] {{objects}} {% if vars %} [{{group.name}}:vars] {{vars}} -{% endif %} \ No newline at end of file +{% endif %}{% endautoescape %} \ No newline at end of file diff --git a/polemarch/main/templates/models/inventory b/polemarch/main/templates/models/inventory index e57de98a..09f72529 100644 --- a/polemarch/main/templates/models/inventory +++ b/polemarch/main/templates/models/inventory @@ -1,4 +1,4 @@ -# Hosts {% for host in hosts %} +{% autoescape off %}# Hosts {% for host in hosts %} {{ host }}{% endfor %} {% if vars %}# Global vars @@ -6,4 +6,4 @@ {{ vars }}{% endif %} # Groups {% for group in groups %} -{{ group }}{% endfor %} \ No newline at end of file +{{ group }}{% endfor %}{% endautoescape %} \ No newline at end of file diff --git a/polemarch/main/tests/api.py b/polemarch/main/tests/api.py index b96efbab..0f395e90 100644 --- a/polemarch/main/tests/api.py +++ b/polemarch/main/tests/api.py @@ -279,6 +279,23 @@ def test_api_groups(self): result = self.get_result("get", url_ug) self.assertCount(result["users"], 0) + def test_users_localsettings(self): + user_url = "/api/v1/users/{}/".format(self.user.id) + result = self.get_result("get", user_url) + self.assertEqual(result["username"], self.user.username) + data = {"some": True} + result = self.get_result( + "post", "{}settings/".format(user_url), 200, data=json.dumps(data) + ) + self.assertEqual(result, data) + result = self.get_result("get", "{}settings/".format(user_url)) + self.assertEqual(result, data) + result = self.get_result("delete", "{}settings/".format(user_url), 200) + self.assertEqual(result, {}) + self.assertCount( + self.get_result("get", "{}settings/".format(user_url)), 0 + ) + class APITestCase(ApiUsersTestCase, ApiHostsTestCase, ApiGroupsTestCase, @@ -384,4 +401,3 @@ def test_statistic(self): self.assertEqual(result['users'], self.get_count(User)) # Check history counts self._check_stats_history(data['day'], result['jobs']['day']) - self._check_stats_history(data['year'], result['jobs']['year']) diff --git a/polemarch/main/tests/inventory.py b/polemarch/main/tests/inventory.py index b70ee758..48d807e1 100644 --- a/polemarch/main/tests/inventory.py +++ b/polemarch/main/tests/inventory.py @@ -458,6 +458,20 @@ def test_hosts_in_inventory(self): self._compare_list(url, "put", 200, inv_id, hosts_id, "hosts", hosts_id[2:3], 2) + def test_inventory_errors(self): + inv_url = '/api/v1/inventories' + inv_data = dict(name="inventory-test321", vars={}) + hst_data = dict(name="inventory-test321", type="HOST", vars={}) + inv_id = self._create_inventories([inv_data])[0] + hst_id = self._create_hosts([hst_data])[0] + data = [hst_id, 99999999] + url = "{}/{}/hosts/".format(inv_url, inv_id) + result = self.get_result('put', url, 200, data=json.dumps(data)) + self.assertNotIn(hst_id, result['failed_list']) + data = [str(hst_id), 9999] + result = self.get_result('put', url, 200, data=json.dumps(data)) + self.assertNotIn(hst_id, result['failed_list']) + def test_filter_inventory(self): base_url = "/api/v1/inventories/" f_url = "{}?name=_inventory".format(base_url) diff --git a/polemarch/main/tests/repo_backends.py b/polemarch/main/tests/repo_backends.py index 14bcbb5b..5851c7b3 100644 --- a/polemarch/main/tests/repo_backends.py +++ b/polemarch/main/tests/repo_backends.py @@ -12,7 +12,7 @@ from unittest.mock import patch from django.test import override_settings from .inventory import _ApiGHBaseTestCase -from ..repo_backends import _Base, logger, os +from ..repo._base import _Base, logger, os class Test(_Base): @@ -56,7 +56,12 @@ def test_git_import(self): repo = git.Repo.init(repo_dir) repo.index.add(["main.yml"]) repo.index.commit("no message") - revision = repo.head.object.hexsha + first_revision = repo.head.object.hexsha + repo.create_head('new_branch') + open(repo_dir + "/other.yml", 'a').close() + repo.index.add(["other.yml"]) + repo.index.commit("no message 2") + second_revision = repo.head.object.hexsha # actual test data = dict(name="GitProject{}".format(sys.version_info[0]), repository=repo_dir, @@ -67,17 +72,41 @@ def test_git_import(self): single_url = self.url + "{}/".format(prj_id) project = self.get_result("get", single_url) self.assertEqual(project['status'], "OK") + self.assertEqual(project['vars']['repo_branch'], "master") + self.assertEqual(project['branch'], "master") tasks_url = "/api/v1/tasks/?project={}".format(prj_id) tasks = self.get_result("get", tasks_url, 200) - self.assertEquals(tasks["count"], 1) + self.assertEquals(tasks["count"], 2) self.assertEquals(tasks["results"][0]["name"], "main") - self.assertEqual(project["revision"], revision) + self.assertEquals(tasks["results"][1]["name"], "other") + self.assertEqual(project["revision"], second_revision) self.get_result("post", single_url+"sync/", 200) + + # change project branch + data['vars']['repo_branch'] = "new_branch" + self.get_result("patch", single_url, data=json.dumps(data)) + project = self.get_result("get", single_url) + self.assertEqual(project['vars']['repo_branch'], "new_branch") + self.assertEqual(project['branch'], "master") + self.get_result("post", single_url + "sync/", 200) + project = self.get_result("get", single_url) + self.assertEqual(project['status'], "OK") + self.assertEqual(project['vars']['repo_branch'], "new_branch") + self.assertEqual(project['branch'], "new_branch") + self.assertEqual(project["revision"], first_revision) + + # clone with branch name + prj_id = self.get_result("post", self.url, data=json.dumps(data))['id'] + self.projects_to_delete.append(prj_id) + project = self.get_result("get", single_url) + self.assertEqual(project['status'], "OK") + self.assertEqual(project['vars']['repo_branch'], "new_branch") + self.assertEqual(project["revision"], first_revision) # delete test repository shutil.rmtree(repo_dir) - @patch('polemarch.main.repo_backends._ArchiveRepo._download') + @patch('polemarch.main.repo._base._ArchiveRepo._download') @override_settings(PROJECTS_DIR="/tmp/test_projs{}".format(sys.hexversion)) def test_tar_import(self, download): download.side_effect = [self.tests_path + '/test_repo.tar'] * 10 diff --git a/polemarch/main/tests/tasks.py b/polemarch/main/tests/tasks.py index 22449e16..6de5e801 100644 --- a/polemarch/main/tests/tasks.py +++ b/polemarch/main/tests/tasks.py @@ -43,13 +43,15 @@ def test_get_tasks(self): correct_simple_inventory = ( "127.0.1.1 ansible_user=centos " - "ansible_ssh_private_key_file=" + "ansible_ssh_private_key_file=[~~ENCRYPTED~~] " + "ansible_become_pass=[~~ENCRYPTED~~]" ) def create_inventory(self): inventory_data = dict(name="Inv1", vars={}) host_data = dict(name="127.0.1.1", type="HOST", vars={"ansible_user": "centos", + "ansible_become_pass": "secret", "ansible_ssh_private_key_file": "somekey"}) # make host, inventory inventory = self.post_result("/api/v1/inventories/", @@ -68,17 +70,18 @@ def create_inventory(self): def test_execute(self, subprocess_function): inv1, h1 = self.create_inventory() # mock side effect to get ansible-playbook args for assertions in test - result = ["", ""] + result = dict() def side_effect(call_args, *args, **kwargs): inventory_path = call_args[3] with open(inventory_path, 'r') as inventory_file: inventory = inventory_file.read().split('\n') - l = lambda x: x.startswith('127.') - result[0] = list(filter(l, inventory))[0] - key_path = result[0].split("=")[-1] - with open(key_path, 'r') as key_file: - result[1] = key_file.read() + sHst = inventory[1].split(" ") + result['host'] = sHst[0] + result['ansible_user'] = sHst[1].split("=")[1] + result['ansible_become_pass'] = sHst[1].split("=")[1] + with open(sHst[2].split("=")[1], 'r') as key_file: + result['ansible_ssh_private_key_file'] = key_file.read() subprocess_function.side_effect = side_effect # test that can't execute without inventory self.post_result( @@ -92,8 +95,9 @@ def side_effect(call_args, *args, **kwargs): call_args = subprocess_function.call_args[0][0] self.assertTrue(call_args[0].endswith("ansible-playbook")) self.assertTrue(call_args[1].endswith("first.yml")) - self.assertTrue(result[0].startswith(self.correct_simple_inventory)) - self.assertEquals(result[1], "somekey") + self.assertEquals(result['host'], "127.0.1.1") + self.assertEquals(result['ansible_user'], "centos") + self.assertEquals(result['ansible_ssh_private_key_file'], "somekey") # test simple execution sync subprocess_function.reset_mock() self.post_result( @@ -104,14 +108,15 @@ def side_effect(call_args, *args, **kwargs): call_args = subprocess_function.call_args[0][0] self.assertTrue(call_args[0].endswith("ansible-playbook")) self.assertTrue(call_args[1].endswith("first.yml")) - self.assertTrue(result[0].startswith(self.correct_simple_inventory)) - self.assertEquals(result[1], "somekey") + self.assertEquals(result['host'], "127.0.1.1") + self.assertEquals(result['ansible_user'], "centos") + self.assertEquals(result['ansible_ssh_private_key_file'], "somekey") @patch('polemarch.main.utils.CmdExecutor.execute') def test_execute_module(self, subprocess_function): inv1, h1 = self.create_inventory() # mock side effect to get ansible-playbook args for assertions in test - result = ["", ""] + result = dict() def side_effect(call_args, *args, **kwargs): # check additional args @@ -126,11 +131,12 @@ def side_effect(call_args, *args, **kwargs): inventory_path = call_args[3] with open(inventory_path, 'r') as inventory_file: inventory = inventory_file.read().split('\n') - l = lambda x: x.startswith('127.') - result[0] = list(filter(l, inventory))[0] - key_path = result[0].split("=")[-1] - with open(key_path, 'r') as key_file: - result[1] = key_file.read() + sHst = inventory[1].split(" ") + result['host'] = sHst[0] + result['ansible_user'] = sHst[1].split("=")[1] + result['ansible_become_pass'] = sHst[3].split("=")[1] + with open(sHst[2].split("=")[1], 'r') as key_file: + result['ansible_ssh_private_key_file'] = key_file.read() subprocess_function.side_effect = side_effect # test that can't execute without inventory self.post_result( @@ -147,11 +153,17 @@ def side_effect(call_args, *args, **kwargs): call_args = subprocess_function.call_args[0][0] self.assertTrue(call_args[0].endswith("ansible")) self.assertTrue(call_args[1].endswith("all")) - self.assertTrue(result[0].startswith(self.correct_simple_inventory)) - self.assertEquals(result[1], "somekey") + self.assertEquals(result['host'], "127.0.1.1") + self.assertEquals(result['ansible_user'], "centos") + self.assertEquals(result['ansible_ssh_private_key_file'], "somekey") + self.assertEquals(result['ansible_become_pass'], "secret") history = History.objects.get(id=answer["history_id"]) self.assertEquals(history.kind, "MODULE") self.assertEquals(history.mode, "shell") + self.assertIn( + "ansible_become_pass=[~~ENCRYPTED~~]", history.raw_inventory, + "\n"+history.raw_inventory + ) # test simple execution without args kw_list = [dict(args=""), dict(args=None), dict()] for kwargs in kw_list: @@ -664,8 +676,7 @@ def test_secret_periodictask_vars(self): inventory=self.inventory.id, name="one", vars={ "key-file": "secret", "private-key": "secret", - "vault-password-file": "secret", - "new-vault-password-file": "secret", + "vault-password-file": "secret" } ) @@ -773,6 +784,80 @@ def side_effect(call_args, *args, **kwargs): self.get_result("patch", single_url, data=json.dumps(ptask_data)) self.post_result(single_url + "execute/", code=415) + # Execution with options + ansible_args = [] + tmpl_with_opts = dict( + kind="Module", + name='Test opts', + data=dict( + module="shell", + group="all", + project=self.pr_tmplt.id, + inventory=self.history_inventory.id, + args="ls -la", + vars=dict( + forks=8, + ), + ), + options=dict( + one=dict(group='test_group'), + two=dict(args='pwd', vars=dict(forks=1)) + ) + ) + tmplt = self.post_result(url, data=json.dumps(tmpl_with_opts)) + for option in tmpl_with_opts['options'].keys(): + self.assertIn(option, tmplt['options_list'], tmplt) + single_url = "{}{}/".format(url, tmplt['id']) + # test playbook execution default + self.post_result(single_url + "execute/", 201) + self.assertIn(tmpl_with_opts['data']['module'], ansible_args) + self.assertIn(tmpl_with_opts['data']['group'], ansible_args) + self.assertIn('--forks', ansible_args) + self.assertIn( + str(tmpl_with_opts['data']['vars']['forks']), ansible_args + ) + + # test playbook execution one option + ansible_args = [] + self.post_result(single_url + "execute/", 201, data=dict(option='one')) + self.assertIn(tmpl_with_opts['data']['module'], ansible_args) + self.assertIn(tmpl_with_opts['options']['one']['group'], ansible_args) + self.assertIn('--forks', ansible_args) + self.assertIn( + str(tmpl_with_opts['data']['vars']['forks']), ansible_args + ) + + # test playbook execution two option + ansible_args = [] + self.post_result(single_url + "execute/", 201, data=dict(option='two')) + self.assertIn(tmpl_with_opts['data']['module'], ansible_args) + self.assertIn(tmpl_with_opts['options']['two']['args'], ansible_args) + self.assertIn('--forks', ansible_args) + self.assertIn( + str(tmpl_with_opts['options']['two']['vars']['forks']), + ansible_args + ) + + # test invalid attrs for options + invalid_inventory = dict(**tmpl_with_opts) + invalid_inventory['options']['three'] = dict(inventory='some_str') + res = self.post_result(url, 400, data=json.dumps(invalid_inventory)) + self.assertIn( + 'Disallowed to override inventory.', res['detail']['options'], res + ) + + # test encrypted keys + enc_keys = dict(**tmpl_with_opts) + enc_keys['options']['three'] = dict( + vars={"key-file": "some_very_secret_data"} + ) + res = self.post_result(url, data=json.dumps(enc_keys)) + single_url = "{}{}/".format(url, res['id']) + res = self.get_result("get", single_url) + self.assertEqual( + res['options']['three']['vars']['key-file'], '[~~ENCRYPTED~~]', res + ) + def test_string_template_data(self): tmplt_data = dict( name="test_tmplt", @@ -1052,6 +1137,18 @@ def test_history_raw_output(self): result = self.get_result("get", url + "?color=yes") self.assertEquals(result, raw_stdout) + # Clear output + history.status = "RUN" + history.save() + url = "/api/v1/history/{}/clear/".format(history.id) + self.get_result("delete", url, 406) + history.status = "OK" + history.save() + self.get_result("delete", url) + url = "/api/v1/history/{}/raw/".format(history.id) + result = self.get_result("get", url) + self.assertEquals(result, "Output trancated.\n") + def test_history_facts(self): history_kwargs = dict(project=self.ph, mode="setup", kind="MODULE", diff --git a/polemarch/static/css/gui.css b/polemarch/static/css/gui.css index c5ed90fc..a743ccba 100644 --- a/polemarch/static/css/gui.css +++ b/polemarch/static/css/gui.css @@ -27,28 +27,28 @@ * http://codepen.io/thomasgrant/pen/DgEFd */ #loader-wrapper { - position: fixed; - top: 0; - left: 0; - width: 100%; - height: 100%; - z-index: 1000; - background-color: rgba(0, 0, 0, 0.35); - display: none; - opacity: 0; + position: fixed; + top: 0; + left: 0; + width: 100%; + height: 100%; + z-index: 1000; + background-color: rgba(0, 0, 0, 0.35); + display: none; + opacity: 0; } -.in-loading #loader-wrapper { - display: block; - opacity: 1; - animation: appchat_load_success 0.1s; +.in-loading #loader-wrapper { + display: block; + opacity: 1; + animation: appchat_load_success 0.1s; } -.in-starting #loader-wrapper { - display: block; - opacity: 1; - background-color: rgb(255, 255, 255); - animation: appchat_load_start 0.3s; +.in-starting #loader-wrapper { + display: block; + opacity: 1; + background-color: rgb(255, 255, 255); + animation: appchat_load_start 0.3s; } @@ -59,129 +59,129 @@ }*/ @keyframes appchat_load_start { - from { opacity: 0; } - to { opacity: 0.35; } + from { opacity: 0; } + to { opacity: 0.35; } } @keyframes appchat_load_success { - from { opacity: 0; } - to { opacity: 0.35; } + from { opacity: 0; } + to { opacity: 0.35; } } #loader { - border: 3px solid transparent; - border-top-color: #3498db; - border-radius: 50%; - display: block; - margin: -75px 0 0 -75px; - width: 150px; - height: 150px; - position: relative; - top: 50%; - left: 50%; - -webkit-animation: spin 2s linear infinite; - -moz-animation: spin 2s linear infinite; - -o-animation: spin 2s linear infinite; - -ms-animation: spin 2s linear infinite; - animation: spin 2s linear infinite; + border: 3px solid transparent; + border-top-color: #3498db; + border-radius: 50%; + display: block; + margin: -75px 0 0 -75px; + width: 150px; + height: 150px; + position: relative; + top: 50%; + left: 50%; + -webkit-animation: spin 2s linear infinite; + -moz-animation: spin 2s linear infinite; + -o-animation: spin 2s linear infinite; + -ms-animation: spin 2s linear infinite; + animation: spin 2s linear infinite; } #loader:before { - border: 3px solid transparent; - border-top-color: #e74c3c; - border-radius: 50%; - content: ""; - position: absolute; - top: 5px; - right: 5px; - bottom: 5px; - left: 5px; - -webkit-animation: spin 3s linear infinite; - -moz-animation: spin 3s linear infinite; - -o-animation: spin 3s linear infinite; - -ms-animation: spin 3s linear infinite; - animation: spin 3s linear infinite; + border: 3px solid transparent; + border-top-color: #e74c3c; + border-radius: 50%; + content: ""; + position: absolute; + top: 5px; + right: 5px; + bottom: 5px; + left: 5px; + -webkit-animation: spin 3s linear infinite; + -moz-animation: spin 3s linear infinite; + -o-animation: spin 3s linear infinite; + -ms-animation: spin 3s linear infinite; + animation: spin 3s linear infinite; } #loader:after { - border: 3px solid transparent; - border-top-color: #f9c922; - border-radius: 50%; - content: ""; - position: absolute; - top: 15px; - left: 15px; - bottom: 15px; - right: 15px; - -webkit-animation: spin 1.5s linear infinite; - -moz-animation: spin 1.5s linear infinite; - -o-animation: spin 1.5s linear infinite; - -ms-animation: spin 1.5s linear infinite; - animation: spin 1.5s linear infinite; + border: 3px solid transparent; + border-top-color: #f9c922; + border-radius: 50%; + content: ""; + position: absolute; + top: 15px; + left: 15px; + bottom: 15px; + right: 15px; + -webkit-animation: spin 1.5s linear infinite; + -moz-animation: spin 1.5s linear infinite; + -o-animation: spin 1.5s linear infinite; + -ms-animation: spin 1.5s linear infinite; + animation: spin 1.5s linear infinite; } @-webkit-keyframes spin { - 0% { - -webkit-transform: rotate(0deg); - -ms-transform: rotate(0deg); - transform: rotate(0deg); - } - 100% { - -webkit-transform: rotate(360deg); - -ms-transform: rotate(360deg); - transform: rotate(360deg); - } + 0% { + -webkit-transform: rotate(0deg); + -ms-transform: rotate(0deg); + transform: rotate(0deg); + } + 100% { + -webkit-transform: rotate(360deg); + -ms-transform: rotate(360deg); + transform: rotate(360deg); + } } @-moz-keyframes spin { - 0% { - -webkit-transform: rotate(0deg); - -ms-transform: rotate(0deg); - transform: rotate(0deg); - } - 100% { - -webkit-transform: rotate(360deg); - -ms-transform: rotate(360deg); - transform: rotate(360deg); - } + 0% { + -webkit-transform: rotate(0deg); + -ms-transform: rotate(0deg); + transform: rotate(0deg); + } + 100% { + -webkit-transform: rotate(360deg); + -ms-transform: rotate(360deg); + transform: rotate(360deg); + } } @-o-keyframes spin { - 0% { - -webkit-transform: rotate(0deg); - -ms-transform: rotate(0deg); - transform: rotate(0deg); - } - 100% { - -webkit-transform: rotate(360deg); - -ms-transform: rotate(360deg); - transform: rotate(360deg); - } + 0% { + -webkit-transform: rotate(0deg); + -ms-transform: rotate(0deg); + transform: rotate(0deg); + } + 100% { + -webkit-transform: rotate(360deg); + -ms-transform: rotate(360deg); + transform: rotate(360deg); + } } @-ms-keyframes spin { - 0% { - -webkit-transform: rotate(0deg); - -ms-transform: rotate(0deg); - transform: rotate(0deg); - } - 100% { - -webkit-transform: rotate(360deg); - -ms-transform: rotate(360deg); - transform: rotate(360deg); - } + 0% { + -webkit-transform: rotate(0deg); + -ms-transform: rotate(0deg); + transform: rotate(0deg); + } + 100% { + -webkit-transform: rotate(360deg); + -ms-transform: rotate(360deg); + transform: rotate(360deg); + } } @keyframes spin { - 0% { - -webkit-transform: rotate(0deg); - -ms-transform: rotate(0deg); - transform: rotate(0deg); - } - 100% { - -webkit-transform: rotate(360deg); - -ms-transform: rotate(360deg); - transform: rotate(360deg); - } -} - -/* CSS для анимации ожидания в стиле ios (http://codepen.io/anon/pen/MmQJbx) */ + 0% { + -webkit-transform: rotate(0deg); + -ms-transform: rotate(0deg); + transform: rotate(0deg); + } + 100% { + -webkit-transform: rotate(360deg); + -ms-transform: rotate(360deg); + transform: rotate(360deg); + } +} + +/* CSS для анимации ожидания в стиле ios (http://codepen.io/anon/pen/MmQJbx) */ div.spinner { position: relative; width: 180px; @@ -197,72 +197,72 @@ div.gray.spinner div{ } div.spinner div { - width: 6%; - height: 16%; - background: #FFF; - position: absolute; - left: 49%; - top: 43%; - opacity: 0; - -webkit-border-radius: 50px; - -webkit-box-shadow: 0 0 3px rgba(0,0,0,0.2); - -webkit-animation: fade 1s linear infinite; + width: 6%; + height: 16%; + background: #FFF; + position: absolute; + left: 49%; + top: 43%; + opacity: 0; + -webkit-border-radius: 50px; + -webkit-box-shadow: 0 0 3px rgba(0,0,0,0.2); + -webkit-animation: fade 1s linear infinite; } @-webkit-keyframes fade { - from {opacity: 1;} - to {opacity: 0.25;} + from {opacity: 1;} + to {opacity: 0.25;} } div.spinner div.bar1 { - -webkit-transform:rotate(0deg) translate(0, -130%); - -webkit-animation-delay: 0s; -} + -webkit-transform:rotate(0deg) translate(0, -130%); + -webkit-animation-delay: 0s; +} div.spinner div.bar2 { - -webkit-transform:rotate(30deg) translate(0, -130%); - -webkit-animation-delay: -0.9167s; + -webkit-transform:rotate(30deg) translate(0, -130%); + -webkit-animation-delay: -0.9167s; } div.spinner div.bar3 { - -webkit-transform:rotate(60deg) translate(0, -130%); - -webkit-animation-delay: -0.833s; + -webkit-transform:rotate(60deg) translate(0, -130%); + -webkit-animation-delay: -0.833s; } div.spinner div.bar4 { - -webkit-transform:rotate(90deg) translate(0, -130%); - -webkit-animation-delay: -0.7497s; + -webkit-transform:rotate(90deg) translate(0, -130%); + -webkit-animation-delay: -0.7497s; } div.spinner div.bar5 { - -webkit-transform:rotate(120deg) translate(0, -130%); - -webkit-animation-delay: -0.667s; + -webkit-transform:rotate(120deg) translate(0, -130%); + -webkit-animation-delay: -0.667s; } div.spinner div.bar6 { - -webkit-transform:rotate(150deg) translate(0, -130%); - -webkit-animation-delay: -0.5837s; + -webkit-transform:rotate(150deg) translate(0, -130%); + -webkit-animation-delay: -0.5837s; } div.spinner div.bar7 { - -webkit-transform:rotate(180deg) translate(0, -130%); - -webkit-animation-delay: -0.5s; + -webkit-transform:rotate(180deg) translate(0, -130%); + -webkit-animation-delay: -0.5s; } div.spinner div.bar8 { - -webkit-transform:rotate(210deg) translate(0, -130%); - -webkit-animation-delay: -0.4167s; + -webkit-transform:rotate(210deg) translate(0, -130%); + -webkit-animation-delay: -0.4167s; } div.spinner div.bar9 { - -webkit-transform:rotate(240deg) translate(0, -130%); - -webkit-animation-delay: -0.333s; + -webkit-transform:rotate(240deg) translate(0, -130%); + -webkit-animation-delay: -0.333s; } div.spinner div.bar10 { - -webkit-transform:rotate(270deg) translate(0, -130%); - -webkit-animation-delay: -0.2497s; + -webkit-transform:rotate(270deg) translate(0, -130%); + -webkit-animation-delay: -0.2497s; } div.spinner div.bar11 { - -webkit-transform:rotate(300deg) translate(0, -130%); - -webkit-animation-delay: -0.167s; + -webkit-transform:rotate(300deg) translate(0, -130%); + -webkit-animation-delay: -0.167s; } div.spinner div.bar12 { - -webkit-transform:rotate(330deg) translate(0, -130%); - -webkit-animation-delay: -0.0833s; + -webkit-transform:rotate(330deg) translate(0, -130%); + -webkit-animation-delay: -0.0833s; } .in-starting div.spinner{ @@ -270,16 +270,16 @@ div.spinner div.bar12 { } .in-starting div.spinner { - margin-top: 50%; + margin-top: 50%; } .in-loading div.spinner { - margin-top: 50%; + margin-top: 50%; } .navbar-toggle .icon-bar { - background: #fff; + background: #fff; } @@ -299,7 +299,7 @@ div.spinner div.bar12 { .platform-android .for-android{ display: block !important; } - + .borderless td, .borderless th { border: none; border-top: none !important; @@ -354,23 +354,23 @@ div.spinner div.bar12 { color: #eee; cursor: pointer; } -.boolean-title{ +.boolean-title{ display: inline-block; position: absolute; padding-left: 28px; cursor: pointer; } - -table{ + +table{ table-layout: fixed; } .popover-title{ - color:#000; + color:#000; } .popover-content{ - color:#000; + color:#000; } .popover-content i{ @@ -398,33 +398,33 @@ table{ background-color: #3c8dbc; color: #fff; } - + .crontabEditor .btn{ /*background-color: #eee;*/ min-width: 42px; } .popover{ - max-width: none; + max-width: none; word-wrap: break-word; } @media (max-width: 768px) { .popover{ - max-width: 720px; + max-width: 720px; } } @media (max-width: 480px) { .popover{ - max-width: 420px; + max-width: 420px; } } @media (max-width: 320px) { .popover{ - max-width: 272px; + max-width: 272px; } } @@ -450,7 +450,7 @@ table{ } .history-status-OFFLINE{ - color: #9e9e9e; + color: #9e9e9e; } .history-status-INTERRUPTED{ @@ -462,7 +462,7 @@ table{ } .history-status-ERROR{ - + } .history-status-DELAY{ @@ -510,7 +510,7 @@ table{ } @media (min-width: 767px) { - .history-stdout{ + .history-stdout{ max-height: calc(100vh - 414px); } } @@ -521,9 +521,9 @@ table{ max-width: calc(100% - 440px); } } - + .error-note{ - color: #ff5252; + color: #ff5252; } .item-name{ @@ -531,10 +531,10 @@ table{ } .input-file-holder{ - + } -.btn-right{ +.btn-right{ float: right; margin-left: 5px; } @@ -574,4 +574,50 @@ table{ .top-line-btn{ margin-bottom: 5px; -} \ No newline at end of file +} + +.cursor-move:hover { + cursor:move; +} + +.handle-class:hover{ + cursor:move; +} + +.w-btn { + padding: 5px; + font-size: 12px; + background: transparent; + color: #97a0b3; +} + +.w-btn:focus{ + color: #97a0b3; +} + +.w-btn:focus ~ .period-list { + display: none; +} + +.w-btn:hover { + color:#606c84; +} + +.clear-input-and-paste { + float:left; + height: 34px; + line-height: 1.42857143; + padding: 6px 12px; + border: 1px solid #ccc; + position:absolute; + z-index:10; + right:0px; + color: #555; + text-align: center; +} + +.clear-input-and-paste:hover { + color: #fff; + background-color: #d9534f; +} + diff --git a/polemarch/static/js/common.js b/polemarch/static/js/common.js index 25dfed92..51b04ee6 100644 --- a/polemarch/static/js/common.js +++ b/polemarch/static/js/common.js @@ -136,5 +136,17 @@ if(pmLocalSettings.get('hideMenu')) function toIdString(str) { - return str.replace(/[^A-z0-9]/img, "_"); + return str.replace(/[^A-z0-9\-]/img, "_").replace(/[\[\]]/gi, "_"); } + +function hidemodal() { + + + var def= new $.Deferred(); + $(".modal.fade.in").on('hidden.bs.modal', function (e) { + def.resolve(); + }) + $(".modal.fade.in").modal('hide'); + + return def.promise(); +} \ No newline at end of file diff --git a/polemarch/static/js/crontabEditor.js b/polemarch/static/js/crontabEditor.js index fd17720d..bdd50b18 100644 --- a/polemarch/static/js/crontabEditor.js +++ b/polemarch/static/js/crontabEditor.js @@ -102,6 +102,14 @@ crontabEditor.setMinutes = function(value) crontabEditor.updateCronString(); } +/** + * Парсит отдельный элемент в cron строке + * @param {type} resArr + * @param {type} str + * @param {type} minInt + * @param {type} maxInt + * @returns {Array} + */ crontabEditor.parseItem = function(resArr, str, minInt, maxInt) { for(var i=minInt; i< maxInt; i++) @@ -172,7 +180,7 @@ crontabEditor.parseItem = function(resArr, str, minInt, maxInt) } else if(/^([0-9]+)\/([0-9]+)$/.test(Parts[i])) { - var match = /^([0-9]+)-([0-9]+)$/.exec(Parts[i]) + var match = /^([0-9]+)\/([0-9]+)$/.exec(Parts[i]) if(match[1]/1 > maxInt) { match[1] = minInt diff --git a/polemarch/static/js/jsonEditor.js b/polemarch/static/js/jsonEditor.js index 8026867d..45c3c3cf 100644 --- a/polemarch/static/js/jsonEditor.js +++ b/polemarch/static/js/jsonEditor.js @@ -1,5 +1,5 @@ -function jsonEditor(){ +function jsonEditor() { } @@ -17,108 +17,108 @@ jsonEditor.options['item'] = {} //////////////////////////////////////////////// jsonEditor.options['item']['ansible_connection'] = { - type:'text', - help:'Inventory Parameter - ansible_connection', - helpcontent:'Connection type to the host. This can be the name of any of\ + type: 'text', + help: 'Inventory Parameter - ansible_connection', + helpcontent: 'Connection type to the host. This can be the name of any of\ ansible’s connection plugins. SSH protocol types are smart, ssh or\ paramiko. The default is smart. Non-SSH based types are described\ in the next section.' } jsonEditor.options['item']['ansible_host'] = { - type:'text', - help:'Inventory Parameter - ansible_host', - helpcontent:'The name of the host to connect to, if different from the alias you wish to give to it.' + type: 'text', + help: 'Inventory Parameter - ansible_host', + helpcontent: 'The name of the host to connect to, if different from the alias you wish to give to it.' } jsonEditor.options['item']['ansible_port'] = { - type:'text', - help:'Inventory Parameter - ansible_port', - helpcontent:'The ssh port number, if not 22' + type: 'text', + help: 'Inventory Parameter - ansible_port', + helpcontent: 'The ssh port number, if not 22' } jsonEditor.options['item']['ansible_user'] = { - type:'text', - help:'Inventory Parameter - ansible_user', - helpcontent:'The default ssh user name to use.' + type: 'text', + help: 'Inventory Parameter - ansible_user', + helpcontent: 'The default ssh user name to use.' } jsonEditor.options['item']['ansible_ssh_pass'] = { - type:'password', - help:'Inventory Parameter - ansible_ssh_pass', - helpcontent:'The ssh password to use (never store this variable in plain text; always use a vault.)' + type: 'password', + help: 'Inventory Parameter - ansible_ssh_pass', + helpcontent: 'The ssh password to use (never store this variable in plain text; always use a vault.)' } jsonEditor.options['item']['ansible_ssh_private_key_file'] = { - type:'keyfile', - help:'Inventory Parameter - ansible_ssh_private_key_file', - helpcontent:'Private key file used by ssh. Useful if using multiple keys and you don’t want to use SSH agent.' + type: 'keyfile', + help: 'Inventory Parameter - ansible_ssh_private_key_file', + helpcontent: 'Private key file used by ssh. Useful if using multiple keys and you don’t want to use SSH agent.' } jsonEditor.options['item']['ansible_ssh_common_args'] = { - type:'text', - help:'Inventory Parameter - ansible_ssh_common_args', - helpcontent:'This setting is always appended to the default command line for sftp, scp, and ssh. Useful to configure a ProxyCommand for a certain host (or group).' + type: 'text', + help: 'Inventory Parameter - ansible_ssh_common_args', + helpcontent: 'This setting is always appended to the default command line for sftp, scp, and ssh. Useful to configure a ProxyCommand for a certain host (or group).' } jsonEditor.options['item']['ansible_sftp_extra_args'] = { - type:'text', - help:'Inventory Parameter - ansible_sftp_extra_args', - helpcontent:'This setting is always appended to the default sftp command line.' + type: 'text', + help: 'Inventory Parameter - ansible_sftp_extra_args', + helpcontent: 'This setting is always appended to the default sftp command line.' } jsonEditor.options['item']['ansible_scp_extra_args'] = { - type:'text', - help:'Inventory Parameter - ansible_scp_extra_args', - helpcontent:'This setting is always appended to the default scp command line.' + type: 'text', + help: 'Inventory Parameter - ansible_scp_extra_args', + helpcontent: 'This setting is always appended to the default scp command line.' } jsonEditor.options['item']['ansible_ssh_extra_args'] = { - type:'text', - help:'Inventory Parameter - ansible_ssh_extra_args', - helpcontent:'This setting is always appended to the default ssh command line.' + type: 'text', + help: 'Inventory Parameter - ansible_ssh_extra_args', + helpcontent: 'This setting is always appended to the default ssh command line.' } jsonEditor.options['item']['ansible_ssh_pipelining'] = { - type:'text', - help:'Inventory Parameter - ansible_ssh_pipelining', - helpcontent:'Determines whether or not to use SSH pipelining. This can override the pipelining setting in ansible.cfg.' + type: 'text', + help: 'Inventory Parameter - ansible_ssh_pipelining', + helpcontent: 'Determines whether or not to use SSH pipelining. This can override the pipelining setting in ansible.cfg.' } jsonEditor.options['item']['ansible_ssh_executable'] = { - type:'text', - help:'Inventory Parameter - ansible_ssh_executable', - helpcontent:'This setting overrides the default behavior to use the system ssh. This can override the ssh_executable setting in ansible.cfg.' + type: 'text', + help: 'Inventory Parameter - ansible_ssh_executable', + helpcontent: 'This setting overrides the default behavior to use the system ssh. This can override the ssh_executable setting in ansible.cfg.' } jsonEditor.options['item']['ansible_become'] = { - type:'text', - help:'Inventory Parameter - ansible_become', - helpcontent:'Equivalent to ansible_sudo or ansible_su, allows to force privilege escalation' + type: 'text', + help: 'Inventory Parameter - ansible_become', + helpcontent: 'Equivalent to ansible_sudo or ansible_su, allows to force privilege escalation' } jsonEditor.options['item']['ansible_become_method'] = { - type:'text', - help:'Inventory Parameter - ansible_become_method', - helpcontent:'Allows to set privilege escalation method' + type: 'text', + help: 'Inventory Parameter - ansible_become_method', + helpcontent: 'Allows to set privilege escalation method' } jsonEditor.options['item']['ansible_become_user'] = { - type:'text', - help:'Inventory Parameter - ansible_become_user', - helpcontent:'Equivalent to ansible_sudo_user or ansible_su_user, allows to set the user you become through privilege escalation' + type: 'text', + help: 'Inventory Parameter - ansible_become_user', + helpcontent: 'Equivalent to ansible_sudo_user or ansible_su_user, allows to set the user you become through privilege escalation' } jsonEditor.options['item']['ansible_become_pass'] = { - type:'password', - help:'Inventory Parameter - ansible_become_pass', - helpcontent:'Equivalent to ansible_sudo_pass or ansible_su_pass, allows you to set the privilege escalation password (never store this variable in plain text; always use a vault.)' + type: 'password', + help: 'Inventory Parameter - ansible_become_pass', + helpcontent: 'Equivalent to ansible_sudo_pass or ansible_su_pass, allows you to set the privilege escalation password (never store this variable in plain text; always use a vault.)' } jsonEditor.options['item']['ansible_shell_type'] = { - type:'text', - help:'Inventory Parameter - ansible_shell_type', - helpcontent:'The shell type of the target system. You should not use this \n\ + type: 'text', + help: 'Inventory Parameter - ansible_shell_type', + helpcontent: 'The shell type of the target system. You should not use this \n\ setting unless you have set the ansible_shell_executable to a \n\ non-Bourne (sh) compatible shell. By default commands are \n\ formatted using sh-style syntax. Setting this to csh or fish \n\ @@ -127,9 +127,9 @@ jsonEditor.options['item']['ansible_shell_type'] = { } jsonEditor.options['item']['ansible_python_interpreter'] = { - type:'text', - help:'Inventory Parameter - ansible_python_interpreter', - helpcontent:'The target host python path. This is useful for systems with \n\ + type: 'text', + help: 'Inventory Parameter - ansible_python_interpreter', + helpcontent: 'The target host python path. This is useful for systems with \n\ more than one Python or not located at /usr/bin/python such as\n\ *BSD, or where /usr/bin/python is not a 2.X series Python.\n\ We do not use the /usr/bin/env mechanism as that requires the\n\ @@ -139,9 +139,9 @@ jsonEditor.options['item']['ansible_python_interpreter'] = { } jsonEditor.options['item']['ansible_shell_executable'] = { - type:'text', - help:'Inventory Parameter - ansible_shell_executable', - helpcontent:'This sets the shell the ansible controller will use on the \n\ + type: 'text', + help: 'Inventory Parameter - ansible_shell_executable', + helpcontent: 'This sets the shell the ansible controller will use on the \n\ target machine, overrides executable in ansible.cfg which \n\ defaults to /bin/sh. You should really only change it if is not\n\ possible to use /bin/sh (i.e. /bin/sh is not installed on the\n\ @@ -175,103 +175,81 @@ jsonEditor.model.data = {} * @param {Object} opt * @returns {string} текст шаблона формы */ -jsonEditor.editor = function(json, opt) +jsonEditor.editor = function (json, opt) { - if(!opt) + if (!opt) { opt = {} } - if(!opt.title1) + if (!opt.title1) { opt.title1 = 'Variables' } - if(!opt.title2) + if (!opt.title2) { opt.title2 = 'Adding new variable' } - if(!opt.prefix) + if (!opt.prefix) { opt.prefix = 'prefix' } - opt.prefix = opt.prefix.replace(/[^A-z0-9]/g, "_").replace(/[\[\]]/gi, "_") + opt.prefix = toIdString(opt.prefix) jsonEditor.model.data[opt.prefix] = json; jsonEditor.model.form[opt.prefix] = { - showImportForm:false + showImportForm: false }; - return spajs.just.render('jsonEditor', {data:json, optionsblock:opt.block, opt:opt}) + return spajs.just.render('jsonEditor', {data: json, optionsblock: opt.block, opt: opt}) } -jsonEditor.jsonEditorScrollTo = function(param_name, prefix) +jsonEditor.jsonEditorScrollTo = function (param_name, prefix) { - if(!prefix) + if (!prefix) { prefix = "prefix" } - prefix = prefix.replace(/[^A-z0-9]/g, "_").replace(/[\[\]]/gi, "_") - $("body").scrollTo("#json_"+param_name+"_line"+prefix) + prefix = toIdString(prefix) + $("body").scrollTo("#json_" + param_name + "_line" + prefix) } -jsonEditor.jsonEditorGetValues = function(prefix) +jsonEditor.jsonEditorGetValues = function (prefix) { - if(!prefix) + if (!prefix) { prefix = "prefix" } - prefix = prefix.replace(/[^A-z0-9]/g, "_").replace(/[\[\]]/gi, "_") + prefix = toIdString(prefix) - if(jsonEditor.model.data[prefix] === undefined) + if (jsonEditor.model.data[prefix] === undefined) { return {} } return jsonEditor.model.data[prefix]; - - /*var data = {} - var arr = $(".jsonEditor-data"+prefix) - for(var i = 0; i< arr.length; i++) - { - var type = $(arr[i]).attr('data-type'); - var index = $(arr[i]).attr('data-json-name'); - - if(type == "boolean") - { - if($(arr[i]).hasClass('selected')) - { - data[index] = ""; - } - } - else - { - data[index] = $(arr[i]).val() - } - } - - return data*/ } -jsonEditor.jsonEditorRmVar = function(name, prefix) +jsonEditor.jsonEditorRmVar = function (name, prefix) { - if(!prefix) + if (!prefix) { prefix = "prefix" } - prefix = prefix.replace(/[^A-z0-9]/g, "_").replace(/[\[\]]/gi, "_") - $('#json_'+name+'_line'+prefix+'').remove() - if(!$(".jsonEditor-data"+prefix).length) + prefix = toIdString(prefix) + $('#json_' + name + '_line' + prefix + '').remove() + if (!$(".jsonEditor-data" + prefix).length) { - $("#jsonEditorVarListHolder"+prefix).hide() + $("#jsonEditorVarListHolder" + prefix).hide() } delete jsonEditor.model.data[prefix][name] - tabSignal.emit(prefix+".jsonEditorUpdate",{name:name, value:undefined, prefix:prefix}) - tabSignal.emit("jsonEditorUpdate",{name:name, value:undefined, prefix:prefix}) + tabSignal.emit(prefix + ".jsonEditorUpdate", {name: name, value: undefined, prefix: prefix}) + tabSignal.emit("jsonEditorUpdate", {name: name, value: undefined, prefix: prefix}) } /** @@ -281,68 +259,72 @@ jsonEditor.jsonEditorRmVar = function(name, prefix) * @param {string} optionsblock * @param {string} prefix */ -jsonEditor.__devAddVar = function(name, value, optionsblock, prefix) +jsonEditor.__devAddVar = function (name, value, optionsblock, prefix) { - if(!prefix) + if (!prefix) { prefix = "prefix" } - if(!optionsblock) + if (!optionsblock) { optionsblock = 'base' } jsonEditor.model.data[prefix][name] = value - $("#jsonEditorVarList"+prefix).appendTpl(spajs.just.render('jsonEditorLine', {name:name, value:value, optionsblock:optionsblock, opt:{prefix:prefix}})) - $("#jsonEditorVarListHolder"+prefix).show() + $("#jsonEditorVarList" + prefix).appendTpl(spajs.just.render('jsonEditorLine', {name: name, value: value, optionsblock: optionsblock, opt: {prefix: prefix}})) + $("#jsonEditorVarListHolder" + prefix).show() - tabSignal.emit(prefix+".jsonEditorUpdate",{name:name, value:value, prefix:prefix}) - tabSignal.emit("jsonEditorUpdate",{name:name, value:value, prefix:prefix}) + tabSignal.emit(prefix + ".jsonEditorUpdate", {name: name, value: value, prefix: prefix}) + tabSignal.emit("jsonEditorUpdate", {name: name, value: value, prefix: prefix}) } /** * Делает импорт переменных из формата инвентория */ -jsonEditor.jsonEditorImportVars = function(optionsblock, prefix) +jsonEditor.jsonEditorImportVars = function (optionsblock, prefix, varsText) { - if(!prefix) + if (!prefix) { prefix = "prefix" } - prefix = prefix.replace(/[^A-z0-9]/g, "_").replace(/[\[\]]/gi, "_") + prefix = toIdString(prefix) - if(!optionsblock) + if (!optionsblock) { optionsblock = 'base' } - var varsText = $('#new_json_vars'+prefix).val() + if (varsText == undefined) + { + varsText = $('#new_json_vars' + prefix).val() + } + var vars = varsText.split(/\n/gm) var varsresult = {} - for(var i in vars) + for (var i in vars) { - if(/^[\s \t]*$/.test(vars[i])) + if (/^[\s \t]*$/.test(vars[i])) { continue; } var res = jsonEditor.parseMonoVarsLine(i, vars[i]) - if(res !== false) + if (res !== false) { - if($("#json_"+res.name.replace(/[^A-z0-9]/g, "_").replace(/[\[\]]/gi, "_")+"_value"+prefix).length) + if ($("#json_" + res.name.replace(/[^A-z0-9\-]/g, "_").replace(/[\[\]]/gi, "_") + "_value" + prefix).length) { - $.notify("Var `"+res.name+"` already exists", "error"); + $.notify("Var `" + res.name + "` already exists", "error"); continue; } - if(/^-[A-z0-9]$/.test(res.name)) + if (/^-[A-z0-9]$/.test(res.name)) { - for(var i in jsonEditor.options[optionsblock]) + for (var i in jsonEditor.options[optionsblock]) { - if("-"+jsonEditor.options[optionsblock][i].alias == res.name) + if ("-" + jsonEditor.options[optionsblock][i].alias == res.name) { val.name = i break; @@ -350,10 +332,10 @@ jsonEditor.jsonEditorImportVars = function(optionsblock, prefix) } } - if(optionsblock && jsonEditor.options[optionsblock] && jsonEditor.options[optionsblock][res.name]) + if (optionsblock && jsonEditor.options[optionsblock] && jsonEditor.options[optionsblock][res.name]) { var optInfo = jsonEditor.options[optionsblock][res.name] - if(optInfo.type == 'error') + if (optInfo.type == 'error') { $.notify("Adding this variable will be the mistake", "error"); continue; @@ -364,24 +346,24 @@ jsonEditor.jsonEditorImportVars = function(optionsblock, prefix) varsresult[res.name] = res.value } } - $('#new_json_vars'+prefix).val(varsText) + $('#new_json_vars' + prefix).val(varsText) console.log(varsresult) var opt = { - prefix:prefix + prefix: prefix } - for(var i in varsresult) + for (var i in varsresult) { var val = varsresult[i] jsonEditor.model.data[prefix][i] = val - $("#jsonEditorVarList"+prefix).appendTpl(spajs.just.render('jsonEditorLine', {name:i, value:val, optionsblock:optionsblock, opt:opt})) - $("#jsonEditorVarListHolder"+prefix).show() + $("#jsonEditorVarList" + prefix).appendTpl(spajs.just.render('jsonEditorLine', {name: i, value: val, optionsblock: optionsblock, opt: opt})) + $("#jsonEditorVarListHolder" + prefix).show() - tabSignal.emit(prefix+".jsonEditorUpdate",{name:i, value:val, prefix:prefix}) - tabSignal.emit("jsonEditorUpdate",{name:i, value:val, prefix:prefix}) + tabSignal.emit(prefix + ".jsonEditorUpdate", {name: i, value: val, prefix: prefix}) + tabSignal.emit("jsonEditorUpdate", {name: i, value: val, prefix: prefix}) } } @@ -392,33 +374,30 @@ jsonEditor.jsonEditorImportVars = function(optionsblock, prefix) * Параметры из секции *:vars * Строка где после первого `=` всё остальное значение. */ -jsonEditor.parseMonoVarsLine = function(index, line) +jsonEditor.parseMonoVarsLine = function (index, line) { var vars = {} var param = /^([^=]+)="(.*)"$/.exec(line) - if(param) + if (param) { vars.name = param[1] vars.value = param[2] - } - else + } else { param = /^([^=]+)=(.*)$/.exec(line) - if(param) + if (param) { vars.name = param[1] vars.value = param[2] - } - else + } else { param = /^([^:]+):(.*)$/.exec(line) - if(param) + if (param) { vars.name = param[1] vars.value = param[2] - } - else + } else { return false; //throw "Error in line "+index+" invalid varibles string ("+line+")" @@ -432,39 +411,40 @@ jsonEditor.parseMonoVarsLine = function(index, line) } -jsonEditor.jsonEditorAddVar = function(optionsblock, prefix) +jsonEditor.jsonEditorAddVar = function (optionsblock, prefix) { - if(!prefix) + if (!prefix) { prefix = "prefix" } - prefix = prefix.replace(/[^A-z0-9]/g, "_").replace(/[\[\]]/gi, "_") + prefix = toIdString(prefix) - if(!optionsblock) + if (!optionsblock) { optionsblock = 'base' } - var name = $('#new_json_name'+prefix).val() - var value = $('#new_json_value'+prefix).val() - - if(!name) + //для autocomplete.js + var name = $('#new_json_name' + prefix).val() + var value = $('#new_json_value' + prefix).val() + + if (!name) { $.notify("Empty varible name", "error"); return; } - if($("#json_"+name.replace(/[^A-z0-9]/g, "_").replace(/[\[\]]/gi, "_")+"_value"+prefix).length) + if ($("#json_" + name.replace(/[^A-z0-9\-]/g, "_").replace(/[\[\]]/gi, "_") + "_value" + prefix).length) { $.notify("This var already exists", "error"); return; } - if(/^-[A-z0-9]$/.test(name)) + if (/^-[A-z0-9]$/.test(name)) { - for(var i in jsonEditor.options[optionsblock]) + for (var i in jsonEditor.options[optionsblock]) { - if("-"+jsonEditor.options[optionsblock][i].alias == name) + if ("-" + jsonEditor.options[optionsblock][i].alias == name) { name = i break; @@ -472,100 +452,137 @@ jsonEditor.jsonEditorAddVar = function(optionsblock, prefix) } } - if(optionsblock && jsonEditor.options[optionsblock] && jsonEditor.options[optionsblock][name]) + if (optionsblock && jsonEditor.options[optionsblock] && jsonEditor.options[optionsblock][name]) { var optInfo = jsonEditor.options[optionsblock][name] - if(optInfo.type == 'error') + if (optInfo.type == 'error') { $.notify("Adding this variable will be the mistake", "error"); return; } } - $('#new_json_name'+prefix).val('') - $('#new_json_value'+prefix).val('') + $('#new_json_name' + prefix).val('') + $('#new_json_value' + prefix).val('') var opt = { - prefix:prefix + prefix: prefix } - - if(optionsblock - && jsonEditor.options[optionsblock] - && jsonEditor.options[optionsblock][name] - && jsonEditor.options[optionsblock][name].type == 'boolean') + + if (optionsblock + && jsonEditor.options[optionsblock] + && jsonEditor.options[optionsblock][name] + && jsonEditor.options[optionsblock][name].type == 'boolean') { value = ""; } - + + removeLoadFileButton(); + jsonEditor.model.data[prefix][name] = value - $("#jsonEditorVarList"+prefix).appendTpl(spajs.just.render('jsonEditorLine', {name:name, value:value, optionsblock:optionsblock, opt:opt})) - $("#jsonEditorVarListHolder"+prefix).show() - - $("#new_json_name"+prefix).trigger('change'); + $("#jsonEditorVarList" + prefix).appendTpl(spajs.just.render('jsonEditorLine', {name: name, value: value, optionsblock: optionsblock, opt: opt})) + $("#jsonEditorVarListHolder" + prefix).show() + + + $("#new_json_name" + prefix).trigger('change'); + + tabSignal.emit(prefix + ".jsonEditorUpdate", {name: name, value: value, prefix: prefix}) + tabSignal.emit("jsonEditorUpdate", {name: name, value: value, prefix: prefix}) - tabSignal.emit(prefix+".jsonEditorUpdate",{name:name, value:value, prefix:prefix}) - tabSignal.emit("jsonEditorUpdate",{name:name, value:value, prefix:prefix}) } -jsonEditor.initAutoComplete = function(optionsblock, prefix) +jsonEditor.initAutoComplete = function (optionsblock, prefix) { - if(!prefix) + if (!prefix) { prefix = "prefix" } - prefix = prefix.replace(/[^A-z0-9]/g, "_").replace(/[\[\]]/gi, "_") + prefix = toIdString(prefix) new autoComplete({ - selector: '#new_json_name'+prefix, + selector: '#new_json_name' + prefix, minChars: 0, - cache:false, - showByClick:false, - menuClass:'new_json_name'+prefix, - renderItem: function(item, search) + cache: false, + showByClick: false, + menuClass: 'new_json_name' + prefix, + renderItem: function (item, search) { return '
    ' + item.value + ' - ' + item.help + '
    '; }, - onSelect: function(event, term, item) + onSelect: function (event, term, item) { //console.log('onSelect', term, item); var value = $(item).attr('data-value'); - $("#new_json_name"+prefix).val(value); + $("#new_json_name" + prefix).val(value); + var name = value; + var varType = jsonEditor.options[optionsblock][name].type; + removeLoadFileButton(); + if (varType == "keyfile" || varType == "textfile") + { + var elementVar = '"#new_json_value' + prefix + '"'; + var nameVar = '"ansible_ssh_private_key_file"'; + var prefixVar = '"prefix"'; + var inputVar = "" + + ""; + $("#new_json_value" + prefix).before(inputVar); + } + }, - source: function(term, response) + source: function (term, response) { term = term.toLowerCase(); var matches = [] - for(var i in jsonEditor.options[optionsblock]) + for (var i in jsonEditor.options[optionsblock]) { var val = jsonEditor.options[optionsblock][i] - if(i.toLowerCase().indexOf(term) != -1 - || (val['shortopts'] && val['shortopts'][0] && val['shortopts'][0].toLowerCase().indexOf(term) != -1) ) + if (i.toLowerCase().indexOf(term) != -1 + || (val['shortopts'] && val['shortopts'][0] && val['shortopts'][0].toLowerCase().indexOf(term) != -1)) { val.value = i matches.push(val) } } - if(matches.length) + if (matches.length) { response(matches.sort(jsonEditor.sortFunction)); + } + } + }); + $("#new_json_name" + prefix).change(function () + { + var name = $(this).val(); + if (jsonEditor.options[optionsblock][name] == undefined) { + removeLoadFileButton(); + } else + { + var varType = jsonEditor.options[optionsblock][name].type; + removeLoadFileButton(); + if (varType == "keyfile" || varType == "textfile") + { + var elementVar = '"#new_json_value' + prefix + '"'; + var nameVar = '"ansible_ssh_private_key_file"'; + var prefixVar = '"prefix"'; + var inputVar = "" + + ""; + $("#new_json_value" + prefix).before(inputVar); } } }); } -jsonEditor.sortFunction = function(a, b) +jsonEditor.sortFunction = function (a, b) { a = a.value b = b.value - for(var i in a) + for (var i in a) { - if(b.length <= i) + if (b.length <= i) { return 1; } - if(a.charCodeAt(i) != b.charCodeAt(i)) + if (a.charCodeAt(i) != b.charCodeAt(i)) { return a.charCodeAt(i) - b.charCodeAt(i) } @@ -574,28 +591,27 @@ jsonEditor.sortFunction = function(a, b) return 0; } -jsonEditor.initForm = function(optionsblock, prefix) +jsonEditor.initForm = function (optionsblock, prefix) { - if(!prefix) + if (!prefix) { prefix = "prefix" } - prefix = prefix.replace(/[^A-z0-9]/g, "_").replace(/[\[\]]/gi, "_") + prefix = toIdString(prefix) //console.log(optionsblock, jsonEditor.options[optionsblock]) - if(jsonEditor.options[optionsblock]) + if (jsonEditor.options[optionsblock]) { jsonEditor.initAutoComplete(optionsblock, prefix) return; - } - else + } else { return spajs.ajax.Call({ url: "/api/v1/ansible/cli_reference/", type: "GET", - contentType:'application/json', + contentType: 'application/json', data: "", - success: function(data) + success: function (data) { Object.assign(jsonEditor.options, data) jsonEditor.initAutoComplete(optionsblock, prefix) @@ -605,12 +621,12 @@ jsonEditor.initForm = function(optionsblock, prefix) } } -jsonEditor.loadFile = function(event, element, name, prefix) +jsonEditor.loadFile = function (event, element, name, prefix) { console.log("jsonEditor.loadFile", event.target.files) - for(var i=0; i 1024*1024*1) + if (event.target.files[i].size > 1024 * 1024 * 1) { $.notify("File too large", "error"); console.log("File too large " + event.target.files[i].size) @@ -618,15 +634,135 @@ jsonEditor.loadFile = function(event, element, name, prefix) } var reader = new FileReader(); - reader.onload = function(e) + reader.onload = function (e) { $(element)[0].setAttribute("value", e.target.result) $(element).val(e.target.result) - tabSignal.emit(prefix+".jsonEditorUpdate",{name:name, value:e.target.result, prefix:prefix}) - tabSignal.emit("jsonEditorUpdate",{name:name, value:e.target.result, prefix:prefix}) + tabSignal.emit(prefix + ".jsonEditorUpdate", {name: name, value: e.target.result, prefix: prefix}) + tabSignal.emit("jsonEditorUpdate", {name: name, value: e.target.result, prefix: prefix}) } reader.readAsText(event.target.files[i]); return; } +} + +/** + * Функция, убирающая кнопку-инпут для загрузки текстового файла/файла ключа + * при добавлении vars + */ +function removeLoadFileButton() +{ + if ($("#loadFileId")) { + $("#loadFileId").remove(); + } +} + +/** + * Функция, скрывающая textarea, при добавлении vars + */ +function removeNewJsonValueTeaxarea() +{ + if ($("#new_json_value_block")) { + $("#new_json_value_block").hide(); + } +} + +/** + * Функция, показывающая textarea, при добавлении vars + */ +function addNewJsonValueTeaxarea() +{ + $("#new_json_value_block").show(); +} + +/** + * Функция, изменяющая параметры textarea, в зависимости от типа инпута, + * при добавлении vars + */ +function changeTextareaSettings(element, options, prefix) +{ + var thisElement = $(element); + var thisOptions = options; + var name = thisElement.val(); + var prefix = prefix; + removeLoadFileButton(); + addNewJsonValueTeaxarea(); + if (name != undefined) + { + var inputType = jsonEditor.options[thisOptions][name].type; + console.log(inputType); + var textareaEl = $("#new_json_value" + prefix); + if (inputType == "textfile" || inputType == "keyfile") + { + var elementVar = '"#new_json_value' + prefix + '"'; + var nameVar = '"' + name + '"'; + var prefixVar = '"' + prefix + '"'; + var inputVar = "" + + ""; + $("#new_json_value" + prefix).before(inputVar); + makeNotOnlyNumberInput('new_json_value', prefix); + } else if (inputType == "boolean") + { + removeNewJsonValueTeaxarea(prefix); + makeNotOnlyNumberInput('new_json_value', prefix); + } else if (inputType == "integer") + { + makeNumberInputOnly('new_json_value', prefix); + + } else { + makeNotOnlyNumberInput('new_json_value', prefix); + } + } +} + +/** + * Функция, позволяющая вводить в инпут только цифры + */ +function makeNumberInputOnly(element, prefix) +{ + var element = element; + var prefix = prefix; + $('#' + element + prefix).val(""); + document.getElementById(element + prefix).onkeypress = function (e) + { + e = e || event; + if (e.ctrlKey || e.altKey || e.metaKey) + return; + var chr = getChar(e); + if (chr == null) + return; + if (chr < '0' || chr > '9') { + return false; + } + } + + function getChar(event) + { + if (event.which == null) { + if (event.keyCode < 32) + return null; + return String.fromCharCode(event.keyCode) + } + if (event.which != 0 && event.charCode != 0) { + if (event.which < 32) + return null; + return String.fromCharCode(event.which) + } + return null; + } + +} + +/** + * Функция, снимающая ограничение на ввод только цифр + */ +function makeNotOnlyNumberInput(element, prefix) +{ + var element = element; + var prefix = prefix; + document.getElementById('new_json_value' + prefix).onkeypress = function () + { + //console.log("не integer"); + } } \ No newline at end of file diff --git a/polemarch/static/js/libs/Sortable.min.js b/polemarch/static/js/libs/Sortable.min.js new file mode 100644 index 00000000..d69e8ebd --- /dev/null +++ b/polemarch/static/js/libs/Sortable.min.js @@ -0,0 +1,2 @@ +/*! Sortable 1.6.1 - MIT | git://github.com/rubaxa/Sortable.git */ +!function(a){"use strict";"function"==typeof define&&define.amd?define(a):"undefined"!=typeof module&&"undefined"!=typeof module.exports?module.exports=a():window.Sortable=a()}(function(){"use strict";function a(a,b){if(!a||!a.nodeType||1!==a.nodeType)throw"Sortable: `el` must be HTMLElement, and not "+{}.toString.call(a);this.el=a,this.options=b=t({},b),a[T]=this;var c={group:Math.random(),sort:!0,disabled:!1,store:null,handle:null,scroll:!0,scrollSensitivity:30,scrollSpeed:10,draggable:/[uo]l/i.test(a.nodeName)?"li":">*",ghostClass:"sortable-ghost",chosenClass:"sortable-chosen",dragClass:"sortable-drag",ignore:"a, img",filter:null,preventOnFilter:!0,animation:0,setData:function(a,b){a.setData("Text",b.textContent)},dropBubble:!1,dragoverBubble:!1,dataIdAttr:"data-id",delay:0,forceFallback:!1,fallbackClass:"sortable-fallback",fallbackOnBody:!1,fallbackTolerance:0,fallbackOffset:{x:0,y:0}};for(var d in c)!(d in b)&&(b[d]=c[d]);ga(b);for(var e in this)"_"===e.charAt(0)&&"function"==typeof this[e]&&(this[e]=this[e].bind(this));this.nativeDraggable=!b.forceFallback&&$,f(a,"mousedown",this._onTapStart),f(a,"touchstart",this._onTapStart),f(a,"pointerdown",this._onTapStart),this.nativeDraggable&&(f(a,"dragover",this),f(a,"dragenter",this)),ea.push(this._onDragOver),b.store&&this.sort(b.store.get(this))}function b(a,b){"clone"!==a.lastPullMode&&(b=!0),z&&z.state!==b&&(i(z,"display",b?"none":""),b||z.state&&(a.options.group.revertClone?(A.insertBefore(z,B),a._animate(w,z)):A.insertBefore(z,w)),z.state=b)}function c(a,b,c){if(a){c=c||V;do if(">*"===b&&a.parentNode===c||r(a,b))return a;while(a=d(a))}return null}function d(a){var b=a.host;return b&&b.nodeType?b:a.parentNode}function e(a){a.dataTransfer&&(a.dataTransfer.dropEffect="move"),a.preventDefault()}function f(a,b,c){a.addEventListener(b,c,Z)}function g(a,b,c){a.removeEventListener(b,c,Z)}function h(a,b,c){if(a)if(a.classList)a.classList[c?"add":"remove"](b);else{var d=(" "+a.className+" ").replace(R," ").replace(" "+b+" "," ");a.className=(d+(c?" "+b:"")).replace(R," ")}}function i(a,b,c){var d=a&&a.style;if(d){if(void 0===c)return V.defaultView&&V.defaultView.getComputedStyle?c=V.defaultView.getComputedStyle(a,""):a.currentStyle&&(c=a.currentStyle),void 0===b?c:c[b];b in d||(b="-webkit-"+b),d[b]=c+("string"==typeof c?"":"px")}}function j(a,b,c){if(a){var d=a.getElementsByTagName(b),e=0,f=d.length;if(c)for(;e5||b.clientX-(d.left+d.width)>5}function p(a){for(var b=a.tagName+a.className+a.src+a.href+a.textContent,c=b.length,d=0;c--;)d+=b.charCodeAt(c);return d.toString(36)}function q(a,b){var c=0;if(!a||!a.parentNode)return-1;for(;a&&(a=a.previousElementSibling);)"TEMPLATE"===a.nodeName.toUpperCase()||">*"!==b&&!r(a,b)||c++;return c}function r(a,b){if(a){b=b.split(".");var c=b.shift().toUpperCase(),d=new RegExp("\\s("+b.join("|")+")(?=\\s)","g");return!(""!==c&&a.nodeName.toUpperCase()!=c||b.length&&((" "+a.className+" ").match(d)||[]).length!=b.length)}return!1}function s(a,b){var c,d;return function(){void 0===c&&(c=arguments,d=this,setTimeout(function(){1===c.length?a.call(d,c[0]):a.apply(d,c),c=void 0},b))}}function t(a,b){if(a&&b)for(var c in b)b.hasOwnProperty(c)&&(a[c]=b[c]);return a}function u(a){return Y&&Y.dom?Y.dom(a).cloneNode(!0):X?X(a).clone(!0)[0]:a.cloneNode(!0)}function v(a){for(var b=a.getElementsByTagName("input"),c=b.length;c--;){var d=b[c];d.checked&&da.push(d)}}if("undefined"==typeof window||!window.document)return function(){throw new Error("Sortable.js requires a window with a document")};var w,x,y,z,A,B,C,D,E,F,G,H,I,J,K,L,M,N,O,P,Q={},R=/\s+/g,S=/left|right|inline/,T="Sortable"+(new Date).getTime(),U=window,V=U.document,W=U.parseInt,X=U.jQuery||U.Zepto,Y=U.Polymer,Z=!1,$=!!("draggable"in V.createElement("div")),_=function(a){return!navigator.userAgent.match(/(?:Trident.*rv[ :]?11\.|msie)/i)&&(a=V.createElement("x"),a.style.cssText="pointer-events:auto","auto"===a.style.pointerEvents)}(),aa=!1,ba=Math.abs,ca=Math.min,da=[],ea=[],fa=s(function(a,b,c){if(c&&b.scroll){var d,e,f,g,h,i,j=c[T],k=b.scrollSensitivity,l=b.scrollSpeed,m=a.clientX,n=a.clientY,o=window.innerWidth,p=window.innerHeight;if(E!==c&&(D=b.scroll,E=c,F=b.scrollFn,D===!0)){D=c;do if(D.offsetWidth-1:e==a)}}var c={},d=a.group;d&&"object"==typeof d||(d={name:d}),c.name=d.name,c.checkPull=b(d.pull,!0),c.checkPut=b(d.put),c.revertClone=d.revertClone,a.group=c};a.prototype={constructor:a,_onTapStart:function(a){var b,d=this,e=this.el,f=this.options,g=f.preventOnFilter,h=a.type,i=a.touches&&a.touches[0],j=(i||a).target,l=a.target.shadowRoot&&a.path&&a.path[0]||j,m=f.filter;if(v(e),!w&&!(/mousedown|pointerdown/.test(h)&&0!==a.button||f.disabled)&&!l.isContentEditable&&(j=c(j,f.draggable,e),j&&C!==j)){if(b=q(j,f.draggable),"function"==typeof m){if(m.call(this,a,j,this))return k(d,l,"filter",j,e,e,b),void(g&&a.preventDefault())}else if(m&&(m=m.split(",").some(function(a){if(a=c(l,a.trim(),e))return k(d,a,"filter",j,e,e,b),!0})))return void(g&&a.preventDefault());f.handle&&!c(l,f.handle,e)||this._prepareDragStart(a,i,j,b)}},_prepareDragStart:function(a,b,c,d){var e,g=this,i=g.el,l=g.options,n=i.ownerDocument;c&&!w&&c.parentNode===i&&(N=a,A=i,w=c,x=w.parentNode,B=w.nextSibling,C=c,L=l.group,J=d,this._lastX=(b||a).clientX,this._lastY=(b||a).clientY,w.style["will-change"]="all",e=function(){g._disableDelayedDrag(),w.draggable=g.nativeDraggable,h(w,l.chosenClass,!0),g._triggerDragStart(a,b),k(g,A,"choose",w,A,A,J)},l.ignore.split(",").forEach(function(a){j(w,a.trim(),m)}),f(n,"mouseup",g._onDrop),f(n,"touchend",g._onDrop),f(n,"touchcancel",g._onDrop),f(n,"pointercancel",g._onDrop),f(n,"selectstart",g),l.delay?(f(n,"mouseup",g._disableDelayedDrag),f(n,"touchend",g._disableDelayedDrag),f(n,"touchcancel",g._disableDelayedDrag),f(n,"mousemove",g._disableDelayedDrag),f(n,"touchmove",g._disableDelayedDrag),f(n,"pointermove",g._disableDelayedDrag),g._dragStartTimer=setTimeout(e,l.delay)):e())},_disableDelayedDrag:function(){var a=this.el.ownerDocument;clearTimeout(this._dragStartTimer),g(a,"mouseup",this._disableDelayedDrag),g(a,"touchend",this._disableDelayedDrag),g(a,"touchcancel",this._disableDelayedDrag),g(a,"mousemove",this._disableDelayedDrag),g(a,"touchmove",this._disableDelayedDrag),g(a,"pointermove",this._disableDelayedDrag)},_triggerDragStart:function(a,b){b=b||("touch"==a.pointerType?a:null),b?(N={target:w,clientX:b.clientX,clientY:b.clientY},this._onDragStart(N,"touch")):this.nativeDraggable?(f(w,"dragend",this),f(A,"dragstart",this._onDragStart)):this._onDragStart(N,!0);try{V.selection?setTimeout(function(){V.selection.empty()}):window.getSelection().removeAllRanges()}catch(a){}},_dragStarted:function(){if(A&&w){var b=this.options;h(w,b.ghostClass,!0),h(w,b.dragClass,!1),a.active=this,k(this,A,"start",w,A,A,J)}else this._nulling()},_emulateDragOver:function(){if(O){if(this._lastX===O.clientX&&this._lastY===O.clientY)return;this._lastX=O.clientX,this._lastY=O.clientY,_||i(y,"display","none");var a=V.elementFromPoint(O.clientX,O.clientY),b=a,c=ea.length;if(b)do{if(b[T]){for(;c--;)ea[c]({clientX:O.clientX,clientY:O.clientY,target:a,rootEl:b});break}a=b}while(b=b.parentNode);_||i(y,"display","")}},_onTouchMove:function(b){if(N){var c=this.options,d=c.fallbackTolerance,e=c.fallbackOffset,f=b.touches?b.touches[0]:b,g=f.clientX-N.clientX+e.x,h=f.clientY-N.clientY+e.y,j=b.touches?"translate3d("+g+"px,"+h+"px,0)":"translate("+g+"px,"+h+"px)";if(!a.active){if(d&&ca(ba(f.clientX-this._lastX),ba(f.clientY-this._lastY))w.offsetWidth,D=e.offsetHeight>w.offsetHeight,E=(v?(d.clientX-g.left)/t:(d.clientY-g.top)/u)>.5,F=e.nextElementSibling,J=!1;if(v){var K=w.offsetTop,N=e.offsetTop;J=K===N?e.previousElementSibling===w&&!C||E&&C:e.previousElementSibling===w||w.previousElementSibling===e?(d.clientY-g.top)/u>.5:N>K}else r||(J=F!==w&&!D||E&&D);var O=l(A,j,w,f,e,g,d,J);O!==!1&&(1!==O&&O!==-1||(J=1===O),aa=!0,setTimeout(n,30),b(p,q),w.contains(j)||(J&&!F?j.appendChild(w):e.parentNode.insertBefore(w,J?F:e)),x=w.parentNode,this._animate(f,w),this._animate(g,e))}}},_animate:function(a,b){var c=this.options.animation;if(c){var d=b.getBoundingClientRect();1===a.nodeType&&(a=a.getBoundingClientRect()),i(b,"transition","none"),i(b,"transform","translate3d("+(a.left-d.left)+"px,"+(a.top-d.top)+"px,0)"),b.offsetWidth,i(b,"transition","all "+c+"ms"),i(b,"transform","translate3d(0,0,0)"),clearTimeout(b.animated),b.animated=setTimeout(function(){i(b,"transition",""),i(b,"transform",""),b.animated=!1},c)}},_offUpEvents:function(){var a=this.el.ownerDocument;g(V,"touchmove",this._onTouchMove),g(V,"pointermove",this._onTouchMove),g(a,"mouseup",this._onDrop),g(a,"touchend",this._onDrop),g(a,"pointerup",this._onDrop),g(a,"touchcancel",this._onDrop),g(a,"pointercancel",this._onDrop),g(a,"selectstart",this)},_onDrop:function(b){var c=this.el,d=this.options;clearInterval(this._loopId),clearInterval(Q.pid),clearTimeout(this._dragStartTimer),g(V,"mousemove",this._onTouchMove),this.nativeDraggable&&(g(V,"drop",this),g(c,"dragstart",this._onDragStart)),this._offUpEvents(),b&&(P&&(b.preventDefault(),!d.dropBubble&&b.stopPropagation()),y&&y.parentNode&&y.parentNode.removeChild(y),A!==x&&"clone"===a.active.lastPullMode||z&&z.parentNode&&z.parentNode.removeChild(z),w&&(this.nativeDraggable&&g(w,"dragend",this),m(w),w.style["will-change"]="",h(w,this.options.ghostClass,!1),h(w,this.options.chosenClass,!1),k(this,A,"unchoose",w,x,A,J),A!==x?(K=q(w,d.draggable),K>=0&&(k(null,x,"add",w,x,A,J,K),k(this,A,"remove",w,x,A,J,K),k(null,x,"sort",w,x,A,J,K),k(this,A,"sort",w,x,A,J,K))):w.nextSibling!==B&&(K=q(w,d.draggable),K>=0&&(k(this,A,"update",w,x,A,J,K),k(this,A,"sort",w,x,A,J,K))),a.active&&(null!=K&&K!==-1||(K=J),k(this,A,"end",w,x,A,J,K),this.save()))),this._nulling()},_nulling:function(){A=w=x=y=B=z=C=D=E=N=O=P=K=G=H=M=L=a.active=null,da.forEach(function(a){a.checked=!0}),da.length=0},handleEvent:function(a){switch(a.type){case"drop":case"dragend":this._onDrop(a);break;case"dragover":case"dragenter":w&&(this._onDragOver(a),e(a));break;case"selectstart":a.preventDefault()}},toArray:function(){for(var a,b=[],d=this.el.children,e=0,f=d.length,g=this.options;e\ - '+menu.name+'\ - ' - - targetBlock.append(bottomMenu) - } - else if(menu.type == "custom") + if(menu.type == "custom") { targetBlock.append('
    '+menu.menuHtml+'
    '); } @@ -515,111 +483,14 @@ if(!window.spajs) { // Невидимый пункт меню. } - else if(menu.targetTab == "tablist1" || menu.targetTab == "tablist2" || menu.targetTab == "tablist3") - { - var imgHtml = ""; - if(menu.ico) - { - imgHtml = '
    \ - \ -
    ' - } - - var roomMenu = '
  • \ - \ - \ - '+imgHtml+'\ -
    '+menu.name+'
    \ -
    \ - \ -
    \ -
    \ -
  • '; - - //$("#left_sidebar .left_menu .ul_reset").append(roomMenu) - targetBlock.append(roomMenu) - } - else - { - var imgHtml = ""; - if(menu.ico) - { - imgHtml = '
    \ - \ -
    ' - } - - var roomMenu = ''; - - //$("#left_sidebar .left_menu .ul_reset").append(roomMenu) - targetBlock.append(roomMenu) - } - + spajs.sortMenu(targetBlock) if(menu.onInsert) { menu.onInsert($("#spajs-menu-"+menu.id)) } } - - /** - * Устанавливает значение на счётчик событий - * @param string menu_id - * @param string value если значение не задано то счётчик событий будет обнулён и спрятан - */ - spajs.setEventCounterValue = function(menu_id, value) - { - if(value === undefined || value === "" || value === false) - { - $("#spajs-menu-"+menu_id+" .spa-countNew").hide().html('') - } - else - { - $("#spajs-menu-"+menu_id+" .spa-countNew").show().html(value) - } - } - - /** - * Получает значение на счётчика событий - * @param string menu_id - */ - spajs.getEventCounterValue = function(menu_id) - { - return $("#spajs-menu-"+menu_id+" .spa-countNew").html() - } - - /** - * Добавляет значение на счётчика событий - * @param string menu_id - */ - spajs.addEventCounterValue = function(menu_id) - { - var count = parseInt($("#spajs-menu-"+menu_id+" .spa-countNew").show().html()); - if(count > 0) - { - $("#spajs-menu-"+menu_id+" .spa-countNew").html(count+1) - return count+1 - } - else - { - $("#spajs-menu-"+menu_id+" .spa-countNew").html(1) - return 1; - } - } - - + spajs.currentOpenMenu = undefined /** diff --git a/polemarch/static/js/pmDashboard.js b/polemarch/static/js/pmDashboard.js index bfadccef..37697c63 100644 --- a/polemarch/static/js/pmDashboard.js +++ b/polemarch/static/js/pmDashboard.js @@ -17,48 +17,564 @@ pmDashboard.model.count = { history:'-', } +pmDashboard.statsData={ + projects:'-', + inventories:'-', + hosts:'-', + groups:'-', + users:'-', + templates:'-' +} + +pmDashboard.statsDataLast=14; +pmDashboard.statsDataLastQuery=14; +pmDashboard.statsDataMomentType='day'; + +if(window.localStorage['selected-chart-period'] && window.localStorage['selected-chart-period-query'] && window.localStorage['selected-chart-period-type']) +{ + pmDashboard.statsDataLast=window.localStorage['selected-chart-period']; + pmDashboard.statsDataLastQuery=window.localStorage['selected-chart-period-query']; + pmDashboard.statsDataMomentType=window.localStorage['selected-chart-period-type']; +} + /** * Двумерный массив с описанием списка отображаемых виджетов в каждой строке - * - * @example + * + * @example * [ * [{ name:'pmwTemplatesCounter', // Имя класса виджета opt:{}, // Опции для виджета }] - ] + ] * * @type Array */ pmDashboard.model.widgets = [ [ + + ], +] + +/* +*Двумерный массив, хранящий в себе настройки виджетов по умолчанию. + */ +pmDashboard.model.defaultWidgets = [ + [ + { + name:'pmwTemplatesCounter', + title:'Templates Counter', + sortNum:0, + active:true, + opt:{}, + type:1, + collapse:false, + }, + { + name:'pmwProjectsCounter', + title:'Projects Counter', + sortNum:1, + active:true, + opt:{}, + type:1, + collapse:false, + }, + { + name:'pmwInventoriesCounter', + title:'Inventories Counter', + sortNum:2, + active:true, + opt:{}, + type:1, + collapse:false, + }, { - name:'pmwTemplatesCounter', - opt:{}, + name:'pmwHostsCounter', + title:'Hosts Counter', + sortNum:3, + active:true, + opt:{}, + type:1, + collapse:false, }, { - name:'pmwProjectsCounter', - opt:{}, + name:'pmwGroupsCounter', + title:'Groups Counter', + sortNum:4, + active:true, + opt:{}, + type:1, + collapse:false, }, { - name:'pmwInventoriesCounter', - opt:{}, + name:'pmwUsersCounter', + title:'Users Counter', + sortNum:5, + active:true, + opt:{}, + type:1, + collapse:false, }, { - name:'pmwHostsCounter', - opt:{}, + name:'pmwAnsibleModuleWidget', + title:'Run shell command', + sortNum:6, + active:true, + opt:{}, + type:0, + collapse:false, }, { - name:'pmwGroupsCounter', - opt:{}, + name:'pmwChartWidget', + title:'Tasks history', + sortNum:7, + active:true, + opt:{}, + type:0, + collapse:false, }, { - name:'pmwUsersCounter', - opt:{}, - }, /**/ + name:'pmwTasksTemplatesWidget', + title:'Templates Task', + sortNum:8, + active:true, + opt:{}, + type:0, + collapse:false, + }, + { + name:'pmwModulesTemplatesWidget', + title:'Templates Module', + sortNum:9, + active:true, + opt:{}, + type:0, + collapse:false, + },/**/ ], ] + +/** + * Функция полностью копирует настройки по умолчанию для виджетов. + * Подразумевается, что данная функция вызывается, когда пришел из API пустой JSON. + */ +pmDashboard.cloneDefaultWidgetsTotally = function(){ + for(var i in pmDashboard.model.defaultWidgets[0]) + { + pmDashboard.model.widgets[0][i]={}; + for (var j in pmDashboard.model.defaultWidgets[0][i]) + { + pmDashboard.model.widgets[0][i][j]=pmDashboard.model.defaultWidgets[0][i][j]; + } + } + console.log(pmDashboard.model.widgets[0]); + return pmDashboard.model.widgets[0]; +} + +/** + * Функция копирует "статичные" настройки по умолчанию для виджетов. + * Под "статичными" понимается name, title, opt, type. + * Данные настройки не меняются в ходе работы пользователя с интерфейсом. + * Подразумевается, что данная функция вызывается, когда пришел из API непустой JSON. + */ +pmDashboard.cloneDefaultWidgetsStaticSettingsOnly = function(){ + for(var i in pmDashboard.model.defaultWidgets[0]) + { + pmDashboard.model.widgets[0][i]={}; + pmDashboard.model.widgets[0][i].name=pmDashboard.model.defaultWidgets[0][i].name; + pmDashboard.model.widgets[0][i].title=pmDashboard.model.defaultWidgets[0][i].title; + pmDashboard.model.widgets[0][i].opt=pmDashboard.model.defaultWidgets[0][i].opt; + pmDashboard.model.widgets[0][i].type=pmDashboard.model.defaultWidgets[0][i].type; + } + return pmDashboard.model.widgets[0]; +} + +/** + * Функция добавляет виджету оставшиеся(не "статичные") настройки. + * Функция проверяет есть ли соответсвуют ли пришедшие настройки для виджетов из API тем, + * что хранятся в массиве с настройками по умолчанию. + * Если данное свойство соответсвует, то его значение присваивается настройкам виджета. + * В противном случае ему присваивается настройка по умолчанию. + */ +pmDashboard.clonetWidgetsSettingsFromApiAndVerify = function(data){ + pmDashboard.cloneDefaultWidgetsStaticSettingsOnly(); + for(var i in pmDashboard.model.defaultWidgets[0]) + { + for(var j in data) + { + if(pmDashboard.model.defaultWidgets[0][i].name==j) + { + for (var k in pmDashboard.model.defaultWidgets[0][i]) + { + if(k in data[j]){ + pmDashboard.model.widgets[0][i][k]=data[j][k]; + } + else + { + pmDashboard.model.widgets[0][i][k]=pmDashboard.model.defaultWidgets[0][i][k]; + } + } + } + } + } + return pmDashboard.model.widgets[0]; +} + +/** + * Функция проверяет необходимо ли посылать запрос к API для загрузки пользовательских настроек виджетов. + * Если в модели отсутствует какой-либо виджет, либо у виджета отсутсвует какое-нибудь свойство, + * то запрос к API будет отправлен. + */ +pmDashboard.checkWidgetSettings = function() +{ + var bool1=false, bool2=false; + for (var i in pmDashboard.model.defaultWidgets[0]){ + for (var j in pmDashboard.model.widgets[0]) + { + if(pmDashboard.model.defaultWidgets[0][i].name==pmDashboard.model.widgets[0][j].name) + { + for(var k in pmDashboard.model.defaultWidgets[0][i]) + { + if(!(k in pmDashboard.model.widgets[0][j])){ + bool1=true; + } + + } + } + } + } + + if(pmDashboard.model.defaultWidgets[0].length!=pmDashboard.model.widgets[0].length) + { + bool2=true; + } + + if(bool1 || bool2) + { + //нужно послать запрос к api + return true; + } + else + { + //не нужно посылать запрос к api + return false; + } +} + +/** + *Функция создает объект, в который вносит актуальные настройки виджета, + *на основе изменений, внесенных в pmDashboard.model.widgets[0][i]. + *localObj- pmDashboard.model.widgets[0][i] + * @type Object + */ +pmDashboard.getNewWidgetSettings = function(localObj) +{ + var obj={}; + obj.sortNum=localObj.sortNum; + obj.active=localObj.active; + obj.collapse=localObj.collapse; + return obj; +} + +/** + *Функция заправшивает у API пользовательские настройки виджетов. + *Если они есть(пришел не пустой объект), то данные настройки добавляются в local storage. + */ +pmDashboard.getUserWidgetSettingsFromAPI = function() +{ + var userId=window.my_user_id; + if(pmDashboard.checkWidgetSettings()) + { + return spajs.ajax.Call({ + url: "/api/v1/users/" + userId + "/settings/", + type: "GET", + contentType: 'application/json', + success: function (data) + { + if ($.isEmptyObject(data)) + { + console.log("empty object"); + pmDashboard.cloneDefaultWidgetsTotally(); + } + else + { + console.log("not empty object"); + pmDashboard.clonetWidgetsSettingsFromApiAndVerify(data); + pmDashboard.model.widgets[0].sort(pmDashboard.sortCountWidget); + } + }, + error: function (e) + { + console.warn(e) + polemarch.showErrors(e) + } + }); + } + else + { + return false; + } + + +} + +/** + *Функция сохраняет в API пользовательские настройки виджетов. + */ +pmDashboard.putUserWidgetSettingsToAPI = function() +{ + var userId=window.my_user_id; + var dataToPut= {}; + for(var i in pmDashboard.model.widgets[0]){ + var objName=pmDashboard.model.widgets[0][i].name; + dataToPut[objName]=pmDashboard.getNewWidgetSettings(pmDashboard.model.widgets[0][i]); + } + return spajs.ajax.Call({ + url: "/api/v1/users/" + userId + "/settings/", + type: "POST", + contentType: 'application/json', + data: JSON.stringify(dataToPut), + success: function (data) + { + //console.log("Data was posted"); + + }, + error: function (e) + { + console.warn(e) + polemarch.showErrors(e) + } + }); + +} + +/** + *Функция, сортирующая массив объектов. + */ +pmDashboard.sortCountWidget=function(Obj1, Obj2) +{ + return Obj1.sortNum-Obj2.sortNum; +} + +/** + *Функция, меняющая свойство виджета active на false. + */ +pmDashboard.setNewWidgetActiveValue = function(thisButton) +{ + var widgetName=thisButton.parentElement.parentElement.parentElement.parentElement.parentElement.getAttribute("id"); + for(var i in pmDashboard.model.widgets[0]) + { + if(pmDashboard.model.widgets[0][i].name==widgetName) + { + pmDashboard.model.widgets[0][i].active=false; + } + } + pmDashboard.putUserWidgetSettingsToAPI(); +} + +/** + *Функция, меняющая свойство виджета collapse на противоположное (true-false). + */ +pmDashboard.setNewWidgetCollapseValue = function(thisButton) +{ + var widgetName=thisButton.parentElement.parentElement.parentElement.parentElement.parentElement.getAttribute("id"); + for(var i in pmDashboard.model.widgets[0]) + { + if(pmDashboard.model.widgets[0][i].name==widgetName) + { + pmDashboard.model.widgets[0][i].collapse=!pmDashboard.model.widgets[0][i].collapse; + + //скрываем селект с выбором периода на виджете-графике при его сворачивании + if(widgetName=="pmwChartWidget") + { + if(pmDashboard.model.widgets[0][i].collapse==false) + { + $("#period-list").slideDown(400); + } + else + { + $("#period-list").slideUp(400); + } + } + } + } + pmDashboard.putUserWidgetSettingsToAPI(); +} + +/** + *Функция, сохраняющая настройки виджетов, внесенные в модальном окне. + */ +pmDashboard.saveWigdetsOptionsFromModal = function() +{ + var modalTable=document.getElementById("modal-table"); + var modalTableTr=modalTable.getElementsByTagName("tr"); + for(var i=1; i', - link:function(){ return '#'}, - help:'Copy' - }, - { - class:'btn btn-danger danger-right', - function:function(item_id){ return 'spajs.showLoader('+this.model.className+'.deleteItem('+item_id+')); return false;'}, - title:' ', - link:function(){ return '#'}, - }, - ], - sections:[ - function(section, item_id){ - return jsonEditor.editor(this.model.items[item_id].vars, {block:this.model.name}); - }, - function(section, item_id){ - return spajs.just.render("groups_sub_items", {item_id:item_id}) - } - ], - title: function(item_id){ - return "Group "+pmGroups.model.items[item_id].justText('name') - }, - short_title: function(item_id){ - return ""+pmGroups.model.items[item_id].justText('name', function(v){return v.slice(0, 20)}) - }, - fileds:[ - [ - { - filed: new filedsLib.filed.text(), - title:'Name', - name:'name', - placeholder:'Enter group name', - validator:pmGroups.validator, - fast_validator:pmGroups.fast_validator - }, - ] - ], - onUpdate:function(result) - { - return true; - }, - onBeforeSave:function(data, item_id) - { - data.vars = jsonEditor.jsonEditorGetValues() - return data; - }, -} - -/** - * @return $.Deferred - */ -pmGroups.setSubGroups = function(item_id, groups_ids) -{ - if(!item_id) - { - throw "Error in pmGroups.setSubGroups with item_id = `" + item_id + "`" - } - - if(!groups_ids) - { - groups_ids = [] - } - - return spajs.ajax.Call({ - url: "/api/v1/groups/"+item_id+"/groups/", - type: "PUT", - contentType:'application/json', - data:JSON.stringify(groups_ids), - success: function(data) - { - if(pmGroups.model.items[item_id]) - { - pmGroups.model.items[item_id].groups = [] - for(var i in groups_ids) - { - if(!pmGroups.model.items[groups_ids[i]]) - { - continue; - } - pmGroups.model.items[item_id].groups.push(pmGroups.model.items[groups_ids[i]]) - } - } - //console.log("group update", data); - }, - error:function(e) - { - console.warn("group "+item_id+" update error - " + JSON.stringify(e)); - polemarch.showErrors(e.responseJSON) - } - }); -} - -/** - * @return $.Deferred - */ -pmGroups.setSubHosts = function(item_id, hosts_ids) -{ - if(!item_id) - { - throw "Error in pmGroups.setSubHosts with item_id = `" + item_id + "`" - } - - if(!hosts_ids) - { - hosts_ids = [] - } - - return spajs.ajax.Call({ - url: "/api/v1/groups/"+item_id+"/hosts/", - type: "PUT", - contentType:'application/json', - data:JSON.stringify(hosts_ids), - success: function(data) - { - if(pmGroups.model.items[item_id]) - { - pmGroups.model.items[item_id].hosts = [] - for(var i in hosts_ids) - { - if(!pmHosts.model.items[hosts_ids[i]]) - { - continue; - } - pmGroups.model.items[item_id].hosts.push(pmHosts.model.items[hosts_ids[i]]) - } - } - //console.log("group update", data); - }, - error:function(e) - { - console.warn("group "+item_id+" update error - " + JSON.stringify(e)); - polemarch.showErrors(e.responseJSON) - } - }); -} - -/** - * @return $.Deferred - */ -pmGroups.addSubGroups = function(item_id, groups_ids) -{ - if(!item_id) - { - throw "Error in pmGroups.addSubGroups with item_id = `" + item_id + "`" - } - - if(!groups_ids) - { - groups_ids = [] - } - - var def = new $.Deferred(); - spajs.ajax.Call({ - url: "/api/v1/groups/"+item_id+"/groups/", - type: "POST", - contentType:'application/json', - data:JSON.stringify(groups_ids), - success: function(data) - { - if(data.not_found > 0) - { - $.notify("Item not found", "error"); - def.reject({text:"Item not found", status:404}) - return; - } - - if(pmGroups.model.items[item_id]) - { - if(!pmGroups.model.items[item_id].groups) - { - pmGroups.model.items[item_id].groups = [] - } - - for(var i in groups_ids) - { - pmGroups.model.items[item_id].groups.push(pmGroups.model.items[groups_ids[i]]) - } - } - //console.log("group update", data); - $.notify("Save", "success"); - def.resolve() - }, - error:function(e) - { - console.warn("group "+item_id+" update error - " + JSON.stringify(e)); - polemarch.showErrors(e.responseJSON) - def.reject(e) - } - }); - return def.promise(); -} - -/** - * @return $.Deferred - */ -pmGroups.addSubHosts = function(item_id, hosts_ids) -{ - if(!item_id) - { - throw "Error in pmGroups.addSubHosts with item_id = `" + item_id + "`" - } - - if(!hosts_ids) - { - hosts_ids = [] - } - - var def = new $.Deferred(); - spajs.ajax.Call({ - url: "/api/v1/groups/"+item_id+"/hosts/", - type: "POST", - contentType:'application/json', - data:JSON.stringify(hosts_ids), - success: function(data) - { - if(data.not_found > 0) - { - $.notify("Item not found", "error"); - def.reject({text:"Item not found", status:404}) - return; - } - - if(pmGroups.model.items[item_id]) - { - if(!pmGroups.model.items[item_id].hosts) - { - pmGroups.model.items[item_id].hosts = [] - } - - for(var i in hosts_ids) - { - pmGroups.model.items[item_id].hosts.push(pmHosts.model.items[hosts_ids[i]]) - } - } - //console.log("group update", data); - $.notify("Save", "success"); - def.resolve() - }, - error:function(e) - { - console.warn("group "+item_id+" update error - " + JSON.stringify(e)); - polemarch.showErrors(e.responseJSON) - def.reject(e) - } - }); - return def.promise(); -} - -/** - * Показывает форму со списком всех групп. - * @return $.Deferred - */ -pmGroups.showAddSubGroupsForm = function(item_id) -{ - if(!item_id) - { - throw "Error in pmGroups.showAddSubGroupsForm with item_id = `" + item_id + "`" - } - - return $.when(pmGroups.loadAllItems()).done(function(){ - $("#add_existing_item_to_group").remove() - $(".content").appendTpl(spajs.just.render('add_existing_groups_to_group', {item_id:item_id})) - $("#polemarch-model-items-select").select2({ width: '100%' }); - }).fail(function(){ - - }).promise() -} - -/** - * Показывает форму со списком всех хостов. - * @return $.Deferred - */ -pmGroups.showAddSubHostsForm = function(item_id) -{ - if(!item_id) - { - throw "Error in pmGroups.showAddSubHostsForm with item_id = `" + item_id + "`" - } - - return $.when(pmHosts.loadAllItems()).done(function(){ - $("#add_existing_item_to_group").remove() - $(".content").appendTpl(spajs.just.render('add_existing_hosts_to_group', {item_id:item_id})) - $("#polemarch-model-items-select").select2({ width: '100%' }); - }).fail(function(){ - - }).promise() -} - -/** - * Проверяет принадлежит ли host_id к группе item_id - * @param {Integer} item_id - * @param {Integer} host_id - * @returns {Boolean} - */ -pmGroups.hasHosts = function(item_id, host_id) -{ - if(!item_id) - { - throw "Error in pmGroups.hasHosts with item_id = `" + item_id + "`" - } - - if(pmGroups.model.items[item_id]) - { - for(var i in pmGroups.model.items[item_id].hosts) - { - if(pmGroups.model.items[item_id].hosts[i].id == host_id) - { - return true; - } - } - } - return false; -} - -/** - * Проверяет принадлежит ли host_id к группе item_id - * @param {Integer} item_id - * @param {Integer} host_id - * @returns {Boolean} - */ -pmGroups.hasGroups = function(item_id, group_id) -{ - if(!item_id) - { - throw "Error in pmGroups.hasGroups with item_id = `" + item_id + "`" - } - - if(pmGroups.model.items[item_id]) - { - for(var i in pmGroups.model.items[item_id].groups) - { - if(pmGroups.model.items[item_id].groups[i].id == group_id) - { - return true; - } - } - } - return false; -} - - - -/** - * Значение поля автокоплита для строки групп - * @see https://ansible-tips-and-tricks.readthedocs.io/en/latest/ansible/commands/#limit-to-one-or-more-hosts - * @param {string} prefix - * @returns {string} Значение поля автокоплита для строки групп - */ -pmGroups.getGroupsAutocompleteValue = function(prefix) -{ - if(!prefix) - { - prefix = "prefix" - } - return $('#groups_autocomplete_filed'+prefix).val() -} - -/** - * Поле автокоплита для строки групп - * @see https://ansible-tips-and-tricks.readthedocs.io/en/latest/ansible/commands/#limit-to-one-or-more-hosts - * @param {integer} inventory_id - * @param {string} value - * @param {string} prefix - * @returns {string} HTML шаблон - */ -pmGroups.groupsAutocompleteTemplate = function(inventory_id, value, prefix) -{ - if(value === undefined) - { - value = "all" - } - - if(!prefix) - { - prefix = "prefix" - } - - if(inventory_id) - { - $.when(pmInventories.loadItem(inventory_id)).done(function() - { - new autoComplete({ - selector: '#groups_autocomplete_filed'+prefix, - minChars: 0, - cache:false, - showByClick:true, - menuClass:'groups_autocomplete_filed'+prefix, - renderItem: function(item, search) - { - var text = item.name - if(item.isHost) - { - text += ' (Host)' - } - else - { - text += ' (Group)' - } - - return '
    ' + text + '
    '; - }, - onSelect: function(event, term, item) - { - //console.log('onSelect', term, item); - var value = $(item).attr('data-value'); - $("#groups_autocomplete_filed"+prefix).val(value); - $("#groups_autocomplete_filed"+prefix).attr({'data-hide':'hide'}); - - }, - source: function(original_term, response) - { - var isHide = $("#groups_autocomplete_filed"+prefix).attr('data-hide') - if(isHide == "hide") - { - $("#groups_autocomplete_filed"+prefix).attr({'data-hide':'show'}) - return; - } - - pmGroups.groupsAutocompleteMatcher(original_term, response, inventory_id) - } - }); - }) - } - - return spajs.just.render('groups_autocomplete_filed', {selectedInventory:inventory_id, value:value, prefix:prefix}) -} - -pmGroups.groupsAutocompleteMatcher = function(original_term, response, inventory_id) -{ - var addTermToMatches = false - var term = original_term - var baseTerm = "" - if(original_term.indexOf(':') >= 0) - { - addTermToMatches = true - term = original_term.replace(/^(.*):([^:]*)$/gim, "$2") - baseTerm = original_term.replace(/^(.*):([^:]*)$/gim, "$1:") - } - - if(term[0] == '!') - { - term = term.substring(1) - baseTerm = baseTerm + "!" - } - - var arrUsedItems = original_term.toLowerCase().replace(/!/gmi, "").split(/:/g) - - term = term.toLowerCase(); - - var matches = [] - var matchesAll = [] - - for(var i in pmInventories.model.items[inventory_id].groups) - { - var val = pmInventories.model.items[inventory_id].groups[i] - - if($.inArray(val.name.toLowerCase(), arrUsedItems) != -1) - { - continue; - } - - var text = val.name - if(addTermToMatches) - { - text = baseTerm+text - } - - if(val.name.toLowerCase().indexOf(term) == 0 ) - { - matches.push({ - value:text, - isHost:false, - name:val.name, - }) - } - - matchesAll.push({ - value:text, - isHost:false, - name:val.name, - }) - } - - for(var i in pmInventories.model.items[inventory_id].hosts) - { - var val = pmInventories.model.items[inventory_id].hosts[i] - if($.inArray(val.name.toLowerCase(), arrUsedItems) != -1) - { - continue; - } - - if(val.name.indexOf(":") != -1) - { - continue; - } - - var text = val.name - if(addTermToMatches) - { - text = baseTerm+text - } - - if(val.name.toLowerCase().indexOf(term) == 0 ) - { - matches.push({ - value:text, - isHost:true, - name:val.name, - }) - } - - matchesAll.push({ - value:text, - isHost:true, - name:val.name, - }) - } - - if(!addTermToMatches && "All".toLowerCase().indexOf(term) != -1 ) - { - matches.push({ - value:"all", - isHost:false, - name:"all", - }) - - matchesAll.push({ - value:"all", - isHost:false, - name:"all", - }) - } - - if(matches.length > 1 || addTermToMatches) - { - response(matches); - } - else - { - response(matchesAll) - } -} - -tabSignal.connect("polemarch.start", function() -{ - // groups - spajs.addMenu({ - id:"groups", - urlregexp:[/^groups$/, /^group$/, /^groups\/search\/?$/, /^groups\/page\/([0-9]+)$/], - onOpen:function(holder, menuInfo, data){return pmGroups.showList(holder, menuInfo, data);} - }) - - spajs.addMenu({ - id:"groups-search", - urlregexp:[/^groups\/search\/([A-z0-9 %\-.:,=]+)$/], - onOpen:function(holder, menuInfo, data){return pmGroups.showSearchResults(holder, menuInfo, data);} - }) - - spajs.addMenu({ - id:"group", - urlregexp:[/^group\/([0-9]+)$/, /^groups\/([0-9]+)$/], - onOpen:function(holder, menuInfo, data){return pmGroups.showItem(holder, menuInfo, data);} - }) - - spajs.addMenu({ - id:"newGroup", - urlregexp:[/^new-group$/, /^([A-z0-9_]+)\/([0-9]+)\/new-group$/], - onOpen:function(holder, menuInfo, data){return pmGroups.showNewItemPage(holder, menuInfo, data);} - }) - + +var pmGroups = inheritance(pmItems) +pmGroups.model.name = "groups" +pmGroups.model.page_name = "group" +pmGroups.model.bulk_name = "group" +pmGroups.model.className = "pmGroups" + +pmGroups.copyItem = function(item_id) +{ + var def = new $.Deferred(); + var thisObj = this; + + $.when(this.loadItem(item_id)).done(function() + { + var data = thisObj.model.items[item_id]; + delete data.id; + data.name = "copy-from-" + data.name + $.when(encryptedCopyModal.replace(data)).done(function(data) + { + spajs.ajax.Call({ + url: "/api/v1/"+thisObj.model.name+"/", + type: "POST", + contentType:'application/json', + data: JSON.stringify(data), + success: function(newItem) + { + thisObj.model.items[newItem.id] = newItem + + if(data.children) + { + var groups = [] + for(var i in data.groups) + { + groups.push(data.groups[i].id) + } + $.when(thisObj.setSubGroups(newItem.id, groups)).always(function(){ + def.resolve(newItem.id) + }) + } + else + { + var hosts = [] + for(var i in data.hosts) + { + hosts.push(data.hosts[i].id) + } + + $.when(thisObj.setSubHosts(newItem.id, hosts)).always(function(){ + def.resolve(newItem.id) + }) + } + }, + error:function(e) + { + def.reject(e) + } + }); + }).fail(function(e) + { + def.reject(e) + }) + }).fail(function(e) + { + def.reject(e) + }) + + return def.promise(); +} + + +pmGroups.model.page_list = { + buttons:[ + { + class:'btn btn-primary', + function:function(){ return "spajs.open({ menuId:'new-"+this.model.page_name+"'}); return false;"}, + title:'Create', + link:function(){ return '/?new-'+this.model.page_name}, + }, + ], + title: "Groups", + short_title: "Groups", + fileds:[ + { + title:'Name', + name:'name', + }, + ], + actions:[ + { + class:'btn btn-danger', + function:function(item){ return 'spajs.showLoader('+this.model.className+'.deleteItem('+item.id+')); return false;'}, + title:'Delete', + link:function(){ return '#'} + }, + { + class:'btn btn-default', + function:function(item){ return '';}, + title:function(item) + { + if(item.children) + { + return 'Create sub group' + } + + return 'Create sub host' + }, + link:function(item) + { + if(item.children) + { + return '/?group/'+item.id+'/new-group' + } + + return '/?group/'+item.id+'/new-host' + }, + }, + ] +} + +pmGroups.validator = function(value) +{ + if(value && !/[^A-z0-9_.\-]/.test(value)) + { + return true; + } + $.notify("Invalid value in field name it mast be as [^A-z0-9_.\-]", "error"); + return false; +} + +pmGroups.fast_validator = function(value) +{ + return /[^A-z0-9_.\-]/.test(value) +} + +pmGroups.model.page_new = { + title: "New group", + short_title: "New group", + fileds:[ + [ + { + filed: new filedsLib.filed.text(), + title:'Name', + name:'name', + placeholder:'Enter group name', + validator:pmGroups.validator, + fast_validator:pmGroups.fast_validator + }, + { + filed: new filedsLib.filed.boolean(), + title:'Children', + name:'children', + help:'If turn, then allow adding sub groups to group' + }, + ] + ], + sections:[ + function(section){ + return jsonEditor.editor({}, {block:this.model.name}); + } + ], + onBeforeSave:function(data) + { + data.vars = jsonEditor.jsonEditorGetValues() + return data; + }, + onCreate:function(data, status, xhr, callOpt) + { + var def = new $.Deferred(); + $.notify("Group created", "success"); + + if(callOpt.parent_item) + { + if(callOpt.parent_type == 'group') + { + $.when(pmGroups.addSubGroups(callOpt.parent_item, [data.id])).always(function(){ + $.when(spajs.open({ menuId:"group/"+callOpt.parent_item})).always(function(){ + def.resolve() + }) + }) + } + else if(callOpt.parent_type == 'inventory') + { + $.when(pmInventories.addSubGroups(callOpt.parent_item, [data.id])).always(function(){ + $.when(spajs.open({ menuId:"inventory/"+callOpt.parent_item})).always(function(){ + def.resolve() + }) + }) + } + else if(callOpt.parent_type == 'project') + { + $.when(pmProjects.addSubGroups(callOpt.parent_item, [data.id])).always(function(){ + $.when(spajs.open({ menuId:"project/"+callOpt.parent_item})).always(function(){ + def.resolve() + }) + }) + } + else + { + console.error("Не известный parent_type", callOpt.parent_type) + $.when(spajs.open({ menuId:"group/"+data.id})).always(function(){ + def.resolve() + }) + } + } + else + { + $.when(spajs.open({ menuId:"group/"+data.id})).always(function(){ + def.resolve() + }) + } + return def.promise(); + } +} + +pmGroups.model.page_item = { + buttons:[ + { + class:'btn btn-primary', + function:function(item_id){ return 'spajs.showLoader('+this.model.className+'.updateItem('+item_id+')); return false;'}, + title:'Save', + link:function(){ return '#'}, + }, + { + class:'btn btn-default copy-btn', + function:function(item_id){ return 'spajs.showLoader('+this.model.className+'.copyAndEdit('+item_id+')); return false;'}, + title:'', + link:function(){ return '#'}, + help:'Copy' + }, + { + class:'btn btn-danger danger-right', + function:function(item_id){ return 'spajs.showLoader('+this.model.className+'.deleteItem('+item_id+')); return false;'}, + title:' ', + link:function(){ return '#'}, + }, + ], + sections:[ + function(section, item_id){ + return jsonEditor.editor(this.model.items[item_id].vars, {block:this.model.name}); + }, + function(section, item_id){ + return spajs.just.render("groups_sub_items", {item_id:item_id}) + } + ], + title: function(item_id){ + return "Group "+pmGroups.model.items[item_id].justText('name') + }, + short_title: function(item_id){ + return ""+pmGroups.model.items[item_id].justText('name', function(v){return v.slice(0, 20)}) + }, + fileds:[ + [ + { + filed: new filedsLib.filed.text(), + title:'Name', + name:'name', + placeholder:'Enter group name', + validator:pmGroups.validator, + fast_validator:pmGroups.fast_validator + }, + ] + ], + onUpdate:function(result) + { + return true; + }, + onBeforeSave:function(data, item_id) + { + data.vars = jsonEditor.jsonEditorGetValues() + return data; + }, +} + +/** + * @return $.Deferred + */ +pmGroups.setSubGroups = function(item_id, groups_ids) +{ + var thisObj=this; + if(!item_id) + { + throw "Error in pmGroups.setSubGroups with item_id = `" + item_id + "`" + } + + if(!groups_ids) + { + groups_ids = [] + } + else + { + for(var i in groups_ids) + { + groups_ids[i]=+groups_ids[i]; + } + } + + return spajs.ajax.Call({ + url: "/api/v1/groups/"+item_id+"/groups/", + type: "PUT", + contentType:'application/json', + data:JSON.stringify(groups_ids), + success: function(data) + { + pmItems.checkSubItemsAndAdd(thisObj, pmGroups, data, item_id, "groups", groups_ids); + //console.log("group update", data); + }, + error:function(e) + { + console.warn("group "+item_id+" update error - " + JSON.stringify(e)); + polemarch.showErrors(e.responseJSON) + } + }); +} + +/** + * @return $.Deferred + */ +pmGroups.setSubHosts = function(item_id, hosts_ids) +{ + var thisObj=this; + if(!item_id) + { + throw "Error in pmGroups.setSubHosts with item_id = `" + item_id + "`" + } + + if(!hosts_ids) + { + hosts_ids = [] + } + else + { + for(var i in hosts_ids) + { + hosts_ids[i]=+hosts_ids[i]; + } + } + + return spajs.ajax.Call({ + url: "/api/v1/groups/"+item_id+"/hosts/", + type: "PUT", + contentType:'application/json', + data:JSON.stringify(hosts_ids), + success: function(data) + { + pmItems.checkSubItemsAndAdd(thisObj, pmHosts, data, item_id, "hosts", hosts_ids); + //console.log("group update", data); + }, + error:function(e) + { + console.warn("group "+item_id+" update error - " + JSON.stringify(e)); + polemarch.showErrors(e.responseJSON) + } + }); +} + +/** + * @return $.Deferred + */ +pmGroups.addSubGroups = function(item_id, groups_ids) +{ + if(!item_id) + { + throw "Error in pmGroups.addSubGroups with item_id = `" + item_id + "`" + } + + if(!groups_ids) + { + groups_ids = [] + } + + var def = new $.Deferred(); + spajs.ajax.Call({ + url: "/api/v1/groups/"+item_id+"/groups/", + type: "POST", + contentType:'application/json', + data:JSON.stringify(groups_ids), + success: function(data) + { + if(data.not_found > 0) + { + $.notify("Item not found", "error"); + def.reject({text:"Item not found", status:404}) + return; + } + + if(pmGroups.model.items[item_id]) + { + if(!pmGroups.model.items[item_id].groups) + { + pmGroups.model.items[item_id].groups = [] + } + + for(var i in groups_ids) + { + pmGroups.model.items[item_id].groups.push(pmGroups.model.items[groups_ids[i]]) + } + } + //console.log("group update", data); + $.notify("Save", "success"); + def.resolve() + }, + error:function(e) + { + console.warn("group "+item_id+" update error - " + JSON.stringify(e)); + polemarch.showErrors(e.responseJSON) + def.reject(e) + } + }); + return def.promise(); +} + +/** + * @return $.Deferred + */ +pmGroups.addSubHosts = function(item_id, hosts_ids) +{ + if(!item_id) + { + throw "Error in pmGroups.addSubHosts with item_id = `" + item_id + "`" + } + + if(!hosts_ids) + { + hosts_ids = [] + } + + var def = new $.Deferred(); + spajs.ajax.Call({ + url: "/api/v1/groups/"+item_id+"/hosts/", + type: "POST", + contentType:'application/json', + data:JSON.stringify(hosts_ids), + success: function(data) + { + if(data.not_found > 0) + { + $.notify("Item not found", "error"); + def.reject({text:"Item not found", status:404}) + return; + } + + if(pmGroups.model.items[item_id]) + { + if(!pmGroups.model.items[item_id].hosts) + { + pmGroups.model.items[item_id].hosts = [] + } + + for(var i in hosts_ids) + { + pmGroups.model.items[item_id].hosts.push(pmHosts.model.items[hosts_ids[i]]) + } + } + //console.log("group update", data); + $.notify("Save", "success"); + def.resolve() + }, + error:function(e) + { + console.warn("group "+item_id+" update error - " + JSON.stringify(e)); + polemarch.showErrors(e.responseJSON) + def.reject(e) + } + }); + return def.promise(); +} + +/** + * Показывает форму со списком всех групп. + * @return $.Deferred + */ +pmGroups.showAddSubGroupsForm = function(item_id) +{ + if(!item_id) + { + throw "Error in pmGroups.showAddSubGroupsForm with item_id = `" + item_id + "`" + } + + return $.when(pmGroups.loadAllItems()).done(function(){ + $("#add_existing_item_to_group").remove() + $(".content").appendTpl(spajs.just.render('add_existing_groups_to_group', {item_id:item_id})) + var scroll_el = "#add_existing_item_to_group"; + if ($(scroll_el).length != 0) { + $('html, body').animate({ scrollTop: $(scroll_el).offset().top }, 1000); + } + $("#polemarch-model-items-select").select2({ width: '100%' }); + }).fail(function(){ + + }).promise() +} + +/** + * Показывает форму со списком всех хостов. + * @return $.Deferred + */ +pmGroups.showAddSubHostsForm = function(item_id) +{ + if(!item_id) + { + throw "Error in pmGroups.showAddSubHostsForm with item_id = `" + item_id + "`" + } + + return $.when(pmHosts.loadAllItems()).done(function(){ + $("#add_existing_item_to_group").remove() + $(".content").appendTpl(spajs.just.render('add_existing_hosts_to_group', {item_id:item_id})) + var scroll_el = "#add_existing_item_to_group"; + if ($(scroll_el).length != 0) { + $('html, body').animate({ scrollTop: $(scroll_el).offset().top }, 1000); + } + $("#polemarch-model-items-select").select2({ width: '100%' }); + }).fail(function(){ + + }).promise() +} + +/** + * Проверяет принадлежит ли host_id к группе item_id + * @param {Integer} item_id + * @param {Integer} host_id + * @returns {Boolean} + */ +pmGroups.hasHosts = function(item_id, host_id) +{ + if(!item_id) + { + throw "Error in pmGroups.hasHosts with item_id = `" + item_id + "`" + } + + if(pmGroups.model.items[item_id]) + { + for(var i in pmGroups.model.items[item_id].hosts) + { + if(pmGroups.model.items[item_id].hosts[i].id == host_id) + { + return true; + } + } + } + return false; +} + +/** + * Проверяет принадлежит ли host_id к группе item_id + * @param {Integer} item_id + * @param {Integer} host_id + * @returns {Boolean} + */ +pmGroups.hasGroups = function(item_id, group_id) +{ + if(!item_id) + { + throw "Error in pmGroups.hasGroups with item_id = `" + item_id + "`" + } + + if(pmGroups.model.items[item_id]) + { + for(var i in pmGroups.model.items[item_id].groups) + { + if(pmGroups.model.items[item_id].groups[i].id == group_id) + { + return true; + } + } + } + return false; +} + + + +/** + * Значение поля автокоплита для строки групп + * @see https://ansible-tips-and-tricks.readthedocs.io/en/latest/ansible/commands/#limit-to-one-or-more-hosts + * @param {string} prefix + * @returns {string} Значение поля автокоплита для строки групп + */ +pmGroups.getGroupsAutocompleteValue = function(prefix) +{ + if(!prefix) + { + prefix = "prefix" + } + return $('#groups_autocomplete_filed'+prefix).val() + debugger; +} + +/** + * Поле автокоплита для строки групп + * @see https://ansible-tips-and-tricks.readthedocs.io/en/latest/ansible/commands/#limit-to-one-or-more-hosts + * @param {integer} inventory_id + * @param {string} value + * @param {string} prefix + * @returns {string} HTML шаблон + */ +pmGroups.groupsAutocompleteTemplate = function(inventory_id, value, prefix) +{ + if(value === undefined) + { + value = "all" + } + + if(!prefix) + { + prefix = "prefix" + } + + if(inventory_id) + { + $.when(pmInventories.loadItem(inventory_id)).done(function() + { + new autoComplete({ + selector: '#groups_autocomplete_filed'+prefix, + minChars: 0, + cache:false, + showByClick:true, + menuClass:'groups_autocomplete_filed'+prefix, + renderItem: function(item, search) + { + var text = item.name + if(item.isHost) + { + text += ' (Host)' + } + else + { + text += ' (Group)' + } + + return '
    ' + text + '
    '; + }, + onSelect: function(event, term, item) + { + //console.log('onSelect', term, item); + var value = $(item).attr('data-value'); + $("#groups_autocomplete_filed"+prefix).val(value); + $("#groups_autocomplete_filed"+prefix).attr({'data-hide':'hide'}); + + }, + source: function(original_term, response) + { + var isHide = $("#groups_autocomplete_filed"+prefix).attr('data-hide') + if(isHide == "hide") + { + $("#groups_autocomplete_filed"+prefix).attr({'data-hide':'show'}) + return; + } + + pmGroups.groupsAutocompleteMatcher(original_term, response, inventory_id) + } + }); + }) + } + + return spajs.just.render('groups_autocomplete_filed', {selectedInventory:inventory_id, value:value, prefix:prefix}) +} + +pmGroups.groupsAutocompleteMatcher = function(original_term, response, inventory_id) +{ + var addTermToMatches = false + var term = original_term + var baseTerm = "" + if(original_term.indexOf(':') >= 0) + { + addTermToMatches = true + term = original_term.replace(/^(.*):([^:]*)$/gim, "$2") + baseTerm = original_term.replace(/^(.*):([^:]*)$/gim, "$1:") + } + + if(term[0] == '!') + { + term = term.substring(1) + baseTerm = baseTerm + "!" + } + + var arrUsedItems = original_term.toLowerCase().replace(/!/gmi, "").split(/:/g) + + term = term.toLowerCase(); + + var matches = [] + var matchesAll = [] + + for(var i in pmInventories.model.items[inventory_id].groups) + { + var val = pmInventories.model.items[inventory_id].groups[i] + + if($.inArray(val.name.toLowerCase(), arrUsedItems) != -1) + { + continue; + } + + var text = val.name + if(addTermToMatches) + { + text = baseTerm+text + } + + if(val.name.toLowerCase().indexOf(term) == 0 ) + { + matches.push({ + value:text, + isHost:false, + name:val.name, + }) + } + + matchesAll.push({ + value:text, + isHost:false, + name:val.name, + }) + } + + for(var i in pmInventories.model.items[inventory_id].hosts) + { + var val = pmInventories.model.items[inventory_id].hosts[i] + if($.inArray(val.name.toLowerCase(), arrUsedItems) != -1) + { + continue; + } + + if(val.name.indexOf(":") != -1) + { + continue; + } + + var text = val.name + if(addTermToMatches) + { + text = baseTerm+text + } + + if(val.name.toLowerCase().indexOf(term) == 0 ) + { + matches.push({ + value:text, + isHost:true, + name:val.name, + }) + } + + matchesAll.push({ + value:text, + isHost:true, + name:val.name, + }) + } + + if(!addTermToMatches && "All".toLowerCase().indexOf(term) != -1 ) + { + matches.push({ + value:"all", + isHost:false, + name:"all", + }) + + matchesAll.push({ + value:"all", + isHost:false, + name:"all", + }) + } + + if(matches.length > 1 || addTermToMatches) + { + response(matches); + } + else + { + response(matchesAll) + } +} + +tabSignal.connect("polemarch.start", function() +{ + // groups + spajs.addMenu({ + id:"groups", + urlregexp:[/^groups$/, /^group$/, /^groups\/search\/?$/, /^groups\/page\/([0-9]+)$/], + onOpen:function(holder, menuInfo, data){return pmGroups.showList(holder, menuInfo, data);} + }) + + spajs.addMenu({ + id:"groups-search", + urlregexp:[/^groups\/search\/([A-z0-9 %\-.:,=]+)$/, /^groups\/search\/([A-z0-9 %\-.:,=]+)\/page\/([0-9]+)$/], + onOpen:function(holder, menuInfo, data){return pmGroups.showSearchResults(holder, menuInfo, data);} + }) + + spajs.addMenu({ + id:"group", + urlregexp:[/^group\/([0-9]+)$/, /^groups\/([0-9]+)$/], + onOpen:function(holder, menuInfo, data){return pmGroups.showItem(holder, menuInfo, data);} + }) + + spajs.addMenu({ + id:"newGroup", + urlregexp:[/^new-group$/, /^([A-z0-9_]+)\/([0-9]+)\/new-group$/], + onOpen:function(holder, menuInfo, data){return pmGroups.showNewItemPage(holder, menuInfo, data);} + }) + }) \ No newline at end of file diff --git a/polemarch/static/js/pmHistory.js b/polemarch/static/js/pmHistory.js index a0edd3bb..cf3169d7 100644 --- a/polemarch/static/js/pmHistory.js +++ b/polemarch/static/js/pmHistory.js @@ -1,703 +1,836 @@ - -var pmHistory = inheritance(pmItems) - -pmHistory.model.name = "history" -pmHistory.model.bulk_name = "history" -pmHistory.model.linePerPage = 130; -pmHistory.justDeepWatch('model'); -pmHistory.model.className = "pmHistory" - -pmHistory.cancelTask = function(item_id) -{ - return spajs.ajax.Call({ - url: "/api/v1/history/"+item_id+"/cancel/", - type: "POST", - contentType:'application/json', - success: function(data) - { - $.notify("Task canceled!", "warning"); - }, - error:function(e) - { - polemarch.showErrors(e.responseJSON) - } - }) -} - - -pmHistory.showSearchResults = function(holder, menuInfo, data) -{ - var thisObj = this; - - var search = this.searchStringToObject(decodeURIComponent(data.reg[1]), 'mode') - return $.when(this.sendSearchQuery(search)).done(function() - { - $(holder).insertTpl(spajs.just.render(thisObj.model.name+'_list', {query:decodeURIComponent(data.reg[1])})) - }).fail(function() - { - $.notify("", "error"); - }) -} - -pmHistory.search = function(query, options) -{ - if(options.inventory_id) - { - if(this.isEmptySearchQuery(query)) - { - return spajs.open({ menuId:'inventory/' + options.inventory_id +"/" + this.model.name, reopen:true}); - } - - return spajs.open({ menuId:'inventory/' + options.inventory_id +"/" + this.model.name+"/search/"+this.searchObjectToString(trim(query), 'mode'), reopen:true}); - } - else if(options.project_id) - { - if(this.isEmptySearchQuery(query)) - { - return spajs.open({ menuId:'project/' + options.project_id +"/" + this.model.name, reopen:true}); - } - - return spajs.open({ menuId:'project/' + options.project_id +"/" + this.model.name+"/search/"+this.searchObjectToString(trim(query), 'mode'), reopen:true}); - } - else if(this.isEmptySearchQuery(query)) - { - return spajs.open({ menuId:this.model.name, reopen:true}); - } - - return spajs.open({ menuId:this.model.name+"/search/"+this.searchObjectToString(trim(query)), reopen:true}); -} - - -pmHistory.showListInProjects = function(holder, menuInfo, data) -{ - var thisObj = this; - var offset = 0 - var limit = this.pageSize; - if(data.reg && data.reg[2] > 0) - { - offset = this.pageSize*(data.reg[2] - 1); - } - var project_id = data.reg[1]; - - return $.when(this.sendSearchQuery({project:project_id}, limit, offset), pmProjects.loadItem(project_id)).done(function() - { - $(holder).insertTpl(spajs.just.render(thisObj.model.name+'_listInProjects', {query:"", project_id:project_id})) - thisObj.model.selectedCount = $('.multiple-select .selected').length; - }).fail(function() - { - $.notify("", "error"); - }) -} - -pmHistory.showListInInventory = function(holder, menuInfo, data) -{ - var thisObj = this; - var offset = 0 - var limit = this.pageSize; - if(data.reg && data.reg[2] > 0) - { - offset = this.pageSize*(data.reg[2] - 1); - } - var inventory_id = data.reg[1]; - - return $.when(this.sendSearchQuery({inventory:inventory_id}, limit, offset), pmInventories.loadItem(inventory_id)).done(function() - { - $(holder).insertTpl(spajs.just.render(thisObj.model.name+'_listInInventory', {query:"", inventory_id:inventory_id})) - }).fail(function() - { - $.notify("", "error"); - }) -} - -pmHistory.showSearchResultsInProjects = function(holder, menuInfo, data) -{ - var thisObj = this; - var project_id = data.reg[1]; - - var search = this.searchStringToObject(decodeURIComponent(data.reg[2]), 'mode') - search['project'] = project_id - - return $.when(this.sendSearchQuery(search), pmProjects.loadItem(project_id)).done(function() - { - $(holder).insertTpl(spajs.just.render(thisObj.model.name+'_listInProjects', {query:decodeURIComponent(data.reg[2]), project_id:project_id})) - }).fail(function() - { - $.notify("", "error"); - }) -} - - -pmHistory.showItem = function(holder, menuInfo, data) -{ - var thisObj = this; - //console.log(menuInfo, data) - - var item_id = data.reg[1]; - return $.when(this.loadItem(item_id)).done(function() - { - if(pmHistory.model.items[item_id].initiator > 0 ) - { - pmUsers.loadItem(pmHistory.model.items[item_id].initiator); - } - $(holder).insertTpl(spajs.just.render(thisObj.model.name+'_page', {item_id:item_id, project_id:0})) - pmHistory.bindStdoutUpdates(item_id) - }).fail(function() - { - $.notify("", "error"); - }) -} - -pmHistory.showItemInProjects = function(holder, menuInfo, data) -{ - var thisObj = this; - //console.log(menuInfo, data) - var project_id = data.reg[1]; - var item_id = data.reg[2]; - return $.when(this.loadItem(item_id), pmProjects.loadItem(project_id)).done(function() - { - if(pmHistory.model.items[item_id].initiator > 0 ) - { - pmUsers.loadItem(pmHistory.model.items[item_id].initiator); - } - $(holder).insertTpl(spajs.just.render(thisObj.model.name+'_pageInProjects', {item_id:item_id, project_id:project_id})) - pmHistory.bindStdoutUpdates(item_id) - }).fail(function() - { - $.notify("", "error"); - }) -} - -pmHistory.bindStdoutUpdates = function(item_id) -{ - var thisObj = this; - $.when(this.loadNewLines(item_id, 0)).always(function() - { - var content = $('#history-stdout') - content.scroll(function() - { - // End of the document reached? - if (content.scrollTop() < 150) - { - if(thisObj.stdout_minline <= 1) - { - return; - } - - if(thisObj.inLoadTopData) - { - return; - } - - //pmHistory.lastContentScrollHeight = $('#history-stdout').prop('scrollHeight') - content.scrollTop() + 100; - - var stdout_minline = thisObj.model.items[item_id].stdout_minline; - if(stdout_minline <= 1) - { - return; - } - - thisObj.inLoadTopData = true; - $.when(thisObj.loadLines(item_id, {before:stdout_minline, limit:thisObj.model.linePerPage})).always(function() - { - var history_stdout = $("#history-stdout"); - if(!history_stdout || !history_stdout.length) - { - return; - } - - for(var i = stdout_minline-1; i > stdout_minline - thisObj.model.linePerPage; i = i -1) - { - if(thisObj.model.items[item_id].stdout[i] != undefined) - { - history_stdout.prepend(pmHistory.getLine(item_id, i)) - } - } - - thisObj.inLoadTopData = false; - if(content.scrollTop() < 10) - { - content.scrollTop(20) - } - }) - } - }); - }); -} - -/** - * Обновляет поле модел this.model.items[item_id] и ложит туда пользователя - */ -pmHistory.loadItem = function(item_id) -{ - var def = new $.Deferred(); - var thisObj = this; - - spajs.ajax.Call({ - url: "/api/v1/"+this.model.name+"/"+item_id+"/", - type: "GET", - contentType:'application/json', - data: "", - success: function(data) - { - if(!thisObj.model.items[item_id]) - { - thisObj.model.items[item_id] = {} - } - - for(var i in data) - { - thisObj.model.items[item_id][i] = data[i] - } - - var promise = undefined; - - if(data.initiator_type == 'scheduler') - { - promise = pmPeriodicTasks.loadItemsByIds([data.initiator]) - } - - if(data.initiator_type == 'users') - { - promise = pmUsers.loadItemsByIds([data.initiator]) - } - - pmHistory.model.items.justWatch(item_id); - - $.when(pmProjects.loadItem(data.project), promise).always(function(){ - def.resolve(data) - }) - }, - error:function(e) - { - console.warn("pmHistory.loadItem", e) - polemarch.showErrors(e) - def.reject(e) - } - }); - - return def.promise(); -} - -pmHistory.sendSearchQuery = function(query, limit, offset) -{ - if(!limit) - { - limit = 999; - } - - if(!offset) - { - offset = 0; - } - - var q = []; - for(var i in query) - { - q.push(encodeURIComponent(i)+"="+encodeURIComponent(query[i])) - } - - var def = new $.Deferred(); - var thisObj = this; - spajs.ajax.Call({ - url: "/api/v1/"+this.model.name+"/?"+q.join('&'), - type: "GET", - contentType:'application/json', - data: "limit="+encodeURIComponent(limit)+"&offset="+encodeURIComponent(offset), - success: function(data) - { - //console.log("update Items", data) - data.limit = limit - data.offset = offset - thisObj.model.itemslist = data - //thisObj.model.items = {} - - var projects = []; - var usersIds = []; - var periodicTasks = []; - - for(var i in data.results) - { - var val = data.results[i] - - thisObj.model.items[val.id] = val - - if(val.project && !pmProjects.model.items[val.project] && $.inArray(val.project, projects) == -1) - { - projects.push(val.project) - } - - if(val.initiator > 0 && val.initiator_type == 'users' && $.inArray(val.initiator, usersIds) == -1) - { - usersIds.push(val.initiator); - } - else if(val.initiator > 0 && val.initiator_type == 'scheduler' && $.inArray(val.initiator, periodicTasks) == -1) - { - periodicTasks.push(val.initiator); - } - } - - var users_promise = undefined; - var projects_promise = undefined; - var periodicTasks_promise = undefined; - - if(periodicTasks.length) - { - periodicTasks_promise = pmPeriodicTasks.loadItemsByIds(periodicTasks) - } - - if(usersIds.length) - { - users_promise = pmUsers.loadItemsByIds(usersIds) - } - - if(projects.length) - { - projects_promise = pmProjects.sendSearchQuery({id:projects.join(',')}) - } - - $.when(users_promise, projects_promise, periodicTasks_promise).done(function(){ - def.resolve(data) - }) - }, - error:function(e) - { - console.warn(e) - polemarch.showErrors(e) - def.reject(e) - } - }); - - return def.promise(); -} - -/** - * Обновляет поле модел this.model.itemslist и ложит туда список пользователей - * Обновляет поле модел this.model.items и ложит туда список инфу о пользователях по их id - */ -pmHistory.loadItems = function(limit, offset) -{ - if(!limit) - { - limit = 30; - } - - if(!offset) - { - offset = 0; - } - - var def = new $.Deferred(); - var thisObj = this; - spajs.ajax.Call({ - url: "/api/v1/"+this.model.name+"/", - type: "GET", - contentType:'application/json', - data: "limit="+encodeURIComponent(limit)+"&offset="+encodeURIComponent(offset), - success: function(data) - { - - //console.log("update Items", data) - data.limit = limit - data.offset = offset - thisObj.model.itemslist = data - //thisObj.model.items = {} - - var projects = []; - var usersIds = []; - var periodicTasks = []; - - for(var i in data.results) - { - var val = data.results[i] - - thisObj.model.items.justWatch(val.id); - thisObj.model.items[val.id] = mergeDeep(thisObj.model.items[val.id], val) - - if(val.project && !pmProjects.model.items[val.project] && $.inArray(val.project, projects) == -1) - { - projects.push(val.project) - } - - if(val.initiator > 0 && val.initiator_type == 'users' && $.inArray(val.initiator, usersIds) == -1) - { - usersIds.push(val.initiator); - } - else if(val.initiator > 0 && val.initiator_type == 'scheduler' && $.inArray(val.initiator, periodicTasks) == -1) - { - periodicTasks.push(val.initiator); - } - } - - var users_promise = undefined; - var projects_promise = undefined; - var periodicTasks_promise = undefined; - - if(periodicTasks.length) - { - periodicTasks_promise = pmPeriodicTasks.loadItemsByIds(periodicTasks) - } - - if(usersIds.length) - { - users_promise = pmUsers.loadItemsByIds(usersIds) - } - - if(projects.length) - { - projects_promise = pmProjects.sendSearchQuery({id:projects.join(',')}) - } - - $.when(users_promise, projects_promise, periodicTasks_promise).always(function(){ - def.resolve(data) - }) - }, - error:function(e) - { - console.warn(e) - polemarch.showErrors(e) - def.reject(e) - } - }); - - return def.promise(); -} - -pmHistory.stopUpdates = function() -{ - clearTimeout(this.model.updateTimeoutId) - this.model.updateTimeoutId = undefined; - - clearTimeout(this.model.loadNewLines_timeoutId) - this.model.loadNewLines_timeoutId = undefined; -} - -/** - * Подсветка синтаксиса - * @link https://habrahabr.ru/post/43030/ - * - * @param {String} code - * @returns {String} - */ -pmHistory.Syntax = function(code) -{ - var comments = []; // Тут собираем все каменты - var strings = []; // Тут собираем все строки - var res = []; // Тут собираем все RegExp - var all = { 'C': comments, 'S': strings, 'R': res }; - var safe = { '<': '<', '>': '>', '&': '&' }; - - var ansi_up = new AnsiUp; - ansi_up.use_classes = true; - var html = ansi_up.ansi_to_html(code); - return html - // Табуляцию заменяем неразрывными пробелами - .replace(/\t/g, - '    '); -} - -pmHistory.getLine = function(item_id, line_id) -{ - var line = this.model.items[item_id].stdout[line_id] - if(/^fatal:/.test(line.text)) - { - line.fatal = 'fatal'; - } - else - { - line.fatal = ''; - } - - return spajs.just.render(this.model.name+'_stdout_line', {line:line}) -} - -pmHistory.loadNewLines = function(item_id, last_stdout_maxline) -{ - var thisObj = this; - - if(last_stdout_maxline === undefined) - { - last_stdout_maxline = this.model.items[item_id].stdout_maxline; - } - - if(!last_stdout_maxline) - { - last_stdout_maxline = 0; - } - - return $.when(this.loadItem(item_id), this.loadLines(item_id, {after:last_stdout_maxline, limit:pmHistory.model.linePerPage})).always(function() - { - var addData = false; - var history_stdout = $("#history-stdout"); - if(!history_stdout || !history_stdout.length) - { - return; - } - - var needScrollDowun = $('#history-stdout').prop('scrollHeight') - $('#history-stdout').scrollTop() - history_stdout.css('height').replace("px", "")/1 < 100 - - if(last_stdout_maxline == 0) - { - for(var i in thisObj.model.items[item_id].stdout) - { - if(thisObj.model.items[item_id].stdout[i] != undefined) - { - history_stdout.append(pmHistory.getLine(item_id, i)) - addData = true; - } - } - } - else - { - for(var i = last_stdout_maxline+1; i <= thisObj.model.items[item_id].stdout_maxline; i++) - { - if(thisObj.model.items[item_id].stdout[i] != undefined) - { - history_stdout.append(pmHistory.getLine(item_id, i)) - addData = true; - } - } - } - - - if( addData && needScrollDowun) - { - // Прокручиваем в низ только если и так скрол был не сильно приподнят - thisObj.scrollBottom() - } - - if(thisObj.model.items[item_id].status == 'RUN' || thisObj.model.items[item_id].status == 'DELAY') - { - thisObj.loadNewLines_timeoutId = setTimeout(function(){ - thisObj.loadNewLines(item_id) - }, 5001) - } - }).promise() -} - -pmHistory.scrollBottom = function() -{ - jQuery('#history-stdout').scrollTop(9999999); -} -/** - * Обновляет поле модел this.model.itemslist и ложит туда список пользователей - * Обновляет поле модел this.model.items и ложит туда список инфу о пользователях по их id - */ -pmHistory.loadLines = function(item_id, opt) -{ - if(!opt.limit) - { - opt.limit = 30; - } - - if(!opt.offset) - { - opt.offset = 0; - } - - opt.format = 'json'; - - var def = new $.Deferred(); - spajs.ajax.Call({ - url: "/api/v1/history/"+item_id+"/lines/", - type: "GET", - contentType:'application/json', - data: opt, - success: function(data) - { - if(!pmHistory.model.items[item_id].stdout) - { - pmHistory.model.items[item_id].stdout = {} - pmHistory.model.items[item_id].stdout_count = 0 - pmHistory.model.items[item_id].stdout_maxline = 0 - pmHistory.model.items[item_id].stdout_minline = 999999999 - } - - pmHistory.model.items[item_id].stdout_count = data.count; - for(var i in data.results) - { - var line_number = data.results[i].line_number - - if(pmHistory.model.items[item_id].stdout_maxline < line_number) - { - pmHistory.model.items[item_id].stdout_maxline = line_number; - } - - if(pmHistory.model.items[item_id].stdout_minline > line_number) - { - pmHistory.model.items[item_id].stdout_minline = line_number; - } - - pmHistory.model.items[item_id].stdout[line_number] = {id:line_number, text:data.results[i].line} - } - - def.resolve() - - }, - error:function(e) - { - console.warn(e) - polemarch.showErrors(e) - def.reject(e) - } - }); - - return def.promise(); -} - - tabSignal.connect("polemarch.start", function() - { - // history - spajs.addMenu({ - id:"history", - urlregexp:[/^history$/, /^history\/search\/?$/, /^history\/page\/([0-9]+)$/], - onOpen:function(holder, menuInfo, data){return pmHistory.showUpdatedList(holder, menuInfo, data);}, - onClose:function(){return pmHistory.stopUpdates();}, - }) - - spajs.addMenu({ - id:"history-search", - urlregexp:[/^history\/search\/([A-z0-9 %\-.:,=]+)$/], - onOpen:function(holder, menuInfo, data){return pmHistory.showSearchResults(holder, menuInfo, data);} - }) - - spajs.addMenu({ - id:"history-item", - urlregexp:[/^history\/([0-9]+)$/], - onOpen:function(holder, menuInfo, data){return pmHistory.showItem(holder, menuInfo, data);}, - onClose:function(){return pmHistory.stopUpdates();} - }) - - spajs.addMenu({ - id:"history-item-in-project", - urlregexp:[/^project\/([0-9]+)\/history\/([0-9]+)$/], - onOpen:function(holder, menuInfo, data){return pmHistory.showItemInProjects(holder, menuInfo, data);}, - onClose:function(){return pmHistory.stopUpdates();} - }) - - spajs.addMenu({ - id:"project-history", - urlregexp:[/^project\/([0-9]+)\/history$/, /^project\/([0-9]+)\/history\/page\/([0-9]+)$/], - onOpen:function(holder, menuInfo, data){ - return pmHistory.showUpdatedList(holder, menuInfo, data, "showListInProjects", function(menuInfo, data) - { - var offset = 0 - var limit = pmHistory.pageSize; - if(data.reg && data.reg[2] > 0) - { - offset = pmHistory.pageSize*(data.reg[2] - 1); - } - var project_id = data.reg[1]; - - return pmHistory.sendSearchQuery({project:project_id}, limit, offset) - }); - }, - onClose:function(){return pmHistory.stopUpdates();}, - }) - - spajs.addMenu({ - id:"project-history-search", - urlregexp:[/^project\/([0-9]+)\/history\/search\/([A-z0-9 %\-.:,=]+)$/], - onOpen:function(holder, menuInfo, data){return pmHistory.showSearchResultsInProjects(holder, menuInfo, data);} - }) - - }) \ No newline at end of file + +var pmHistory = inheritance(pmItems) + +pmHistory.model.name = "history" +pmHistory.model.bulk_name = "history" +pmHistory.model.linePerPage = 130; +pmHistory.justDeepWatch('model'); +pmHistory.model.className = "pmHistory" + +pmHistory.cancelTask = function(item_id) +{ + return spajs.ajax.Call({ + url: "/api/v1/history/"+item_id+"/cancel/", + type: "POST", + contentType:'application/json', + success: function(data) + { + $.notify("Task canceled!", "warning"); + }, + error:function(e) + { + polemarch.showErrors(e.responseJSON) + } + }) +} + + +pmHistory.showSearchResults = function(holder, menuInfo, data) +{ + var thisObj = this; + + var limit = this.pageSize; + + if(data.reg && data.reg[2] > 0) + { + offset = this.pageSize*(data.reg[2] - 1); + } else { + offset=0; + } + + var search = this.searchStringToObject(decodeURIComponent(data.reg[1]), 'mode') + return $.when(this.sendSearchQuery(search,limit,offset)).done(function() + { + $(holder).insertTpl(spajs.just.render(thisObj.model.name+'_list', {query:decodeURIComponent(data.reg[1])})) + }).fail(function() + { + $.notify("", "error"); + }) +} + +pmHistory.search = function(query, options) +{ + if(options.inventory_id) + { + if(this.isEmptySearchQuery(query)) + { + return spajs.open({ menuId:'inventory/' + options.inventory_id +"/" + this.model.name, reopen:true}); + } + + return spajs.open({ menuId:'inventory/' + options.inventory_id +"/" + this.model.name+"/search/"+this.searchObjectToString(trim(query), 'mode'), reopen:true}); + } + else if(options.project_id) + { + if(this.isEmptySearchQuery(query)) + { + return spajs.open({ menuId:'project/' + options.project_id +"/" + this.model.name, reopen:true}); + } + + return spajs.open({ menuId:'project/' + options.project_id +"/" + this.model.name+"/search/"+this.searchObjectToString(trim(query), 'mode'), reopen:true}); + } + else if(this.isEmptySearchQuery(query)) + { + return spajs.open({ menuId:this.model.name, reopen:true}); + } + + return spajs.open({ menuId:this.model.name+"/search/"+this.searchObjectToString(trim(query)), reopen:true}); +} + + +pmHistory.showListInProjects = function(holder, menuInfo, data) +{ + var thisObj = this; + var offset = 0 + var limit = this.pageSize; + if(data.reg && data.reg[2] > 0) + { + offset = this.pageSize*(data.reg[2] - 1); + } + var project_id = data.reg[1]; + + return $.when(this.sendSearchQuery({project:project_id}, limit, offset), pmProjects.loadItem(project_id)).done(function() + { + $(holder).insertTpl(spajs.just.render(thisObj.model.name+'_listInProjects', {query:"", project_id:project_id})) + thisObj.model.selectedCount = $('.multiple-select .selected').length; + }).fail(function() + { + $.notify("", "error"); + }) +} + +pmHistory.showListInInventory = function(holder, menuInfo, data) +{ + var thisObj = this; + var offset = 0 + var limit = this.pageSize; + if(data.reg && data.reg[2] > 0) + { + offset = this.pageSize*(data.reg[2] - 1); + } + var inventory_id = data.reg[1]; + + return $.when(this.sendSearchQuery({inventory:inventory_id}, limit, offset), pmInventories.loadItem(inventory_id)).done(function() + { + $(holder).insertTpl(spajs.just.render(thisObj.model.name+'_listInInventory', {query:"", inventory_id:inventory_id})) + }).fail(function() + { + $.notify("", "error"); + }) +} + +pmHistory.showSearchResultsInProjects = function(holder, menuInfo, data) +{ + var thisObj = this; + var project_id = data.reg[1]; + + var search = this.searchStringToObject(decodeURIComponent(data.reg[2]), 'mode') + search['project'] = project_id + + return $.when(this.sendSearchQuery(search), pmProjects.loadItem(project_id)).done(function() + { + $(holder).insertTpl(spajs.just.render(thisObj.model.name+'_listInProjects', {query:decodeURIComponent(data.reg[2]), project_id:project_id})) + }).fail(function() + { + $.notify("", "error"); + }) +} + + +pmHistory.showItem = function(holder, menuInfo, data) +{ + var thisObj = this; + //console.log(menuInfo, data) + + var item_id = data.reg[1]; + return $.when(this.loadItem(item_id)).done(function() + { + if(pmHistory.model.items[item_id].initiator > 0 ) + { + pmUsers.loadItem(pmHistory.model.items[item_id].initiator); + } + + + if (pmHistory.model.items[item_id].inventory != null) { + var promiss = pmInventories.loadItem(pmHistory.model.items[item_id].inventory); + $.when(promiss).done(function () { + $(holder).insertTpl(spajs.just.render(thisObj.model.name + '_page', {item_id: item_id, project_id: 0})) + pmHistory.bindStdoutUpdates(item_id) + }).fail(function () { + $.notify("", "error"); + }); + } else { + $(holder).insertTpl(spajs.just.render(thisObj.model.name + '_page', {item_id: item_id, project_id: 0})) + pmHistory.bindStdoutUpdates(item_id) + } + + + + }).fail(function() + { + $.notify("", "error"); + }) +} + +pmHistory.showItemInProjects = function(holder, menuInfo, data) +{ + var thisObj = this; + //console.log(menuInfo, data) + var project_id = data.reg[1]; + var item_id = data.reg[2]; + return $.when(this.loadItem(item_id), pmProjects.loadItem(project_id)).done(function() + { + if(pmHistory.model.items[item_id].initiator > 0 ) + { + pmUsers.loadItem(pmHistory.model.items[item_id].initiator); + } + + if (pmHistory.model.items[item_id].inventory != null) { + var promiss = pmInventories.loadItem(pmHistory.model.items[item_id].inventory); + $.when(promiss).done(function () { + $(holder).insertTpl(spajs.just.render(thisObj.model.name + '_page', {item_id: item_id, project_id: 0})) + pmHistory.bindStdoutUpdates(item_id) + }).fail(function () { + $.notify("", "error"); + }); + } else { + $(holder).insertTpl(spajs.just.render(thisObj.model.name + '_page', {item_id: item_id, project_id: 0})) + pmHistory.bindStdoutUpdates(item_id) + } + + $(holder).insertTpl(spajs.just.render(thisObj.model.name+'_pageInProjects', {item_id:item_id, project_id:project_id})) + pmHistory.bindStdoutUpdates(item_id) + }).fail(function() + { + $.notify("", "error"); + }) +} + +pmHistory.bindStdoutUpdates = function(item_id) +{ + var thisObj = this; + $.when(this.loadNewLines(item_id, 0)).always(function() + { + var content = $('#history-stdout') + content.scroll(function() + { + // End of the document reached? + if (content.scrollTop() < 150) + { + if(thisObj.stdout_minline <= 1) + { + return; + } + + if(thisObj.inLoadTopData) + { + return; + } + + //pmHistory.lastContentScrollHeight = $('#history-stdout').prop('scrollHeight') - content.scrollTop() + 100; + + var stdout_minline = thisObj.model.items[item_id].stdout_minline; + if(stdout_minline <= 1) + { + return; + } + + thisObj.inLoadTopData = true; + $.when(thisObj.loadLines(item_id, {before:stdout_minline, limit:thisObj.model.linePerPage})).always(function() + { + var history_stdout = $("#history-stdout"); + if(!history_stdout || !history_stdout.length) + { + return; + } + + for(var i = stdout_minline-1; i > stdout_minline - thisObj.model.linePerPage; i = i -1) + { + if(thisObj.model.items[item_id].stdout[i] != undefined) + { + history_stdout.prepend(pmHistory.getLine(item_id, i)) + } + } + + thisObj.inLoadTopData = false; + if(content.scrollTop() < 10) + { + content.scrollTop(20) + } + }) + } + }); + }); +} + +/** + * Обновляет поле модел this.model.items[item_id] и ложит туда пользователя + */ +pmHistory.loadItem = function(item_id) +{ + var def = new $.Deferred(); + var thisObj = this; + + spajs.ajax.Call({ + url: "/api/v1/"+this.model.name+"/"+item_id+"/", + type: "GET", + contentType:'application/json', + data: "", + success: function(data) + { + if(!thisObj.model.items[item_id]) + { + thisObj.model.items[item_id] = {} + } + + for(var i in data) + { + thisObj.model.items[item_id][i] = data[i] + } + + var promise = undefined; + + if(data.initiator_type == 'scheduler') + { + promise = pmPeriodicTasks.loadItemsByIds([data.initiator]) + } + + if(data.initiator_type == 'users') + { + promise = pmUsers.loadItemsByIds([data.initiator]) + } + + pmHistory.model.items.justWatch(item_id); + + $.when(pmProjects.loadItem(data.project), promise).always(function(){ + def.resolve(data) + }) + }, + error:function(e) + { + console.warn("pmHistory.loadItem", e) + polemarch.showErrors(e) + def.reject(e) + } + }); + + return def.promise(); +} + +pmHistory.sendSearchQuery = function(query, limit, offset) +{ + if(!limit) + { + limit = 999; + } + + if(!offset) + { + offset = 0; + } + + var q = []; + for(var i in query) + { + q.push(encodeURIComponent(i)+"="+encodeURIComponent(query[i])) + } + + var def = new $.Deferred(); + var thisObj = this; + spajs.ajax.Call({ + url: "/api/v1/"+this.model.name+"/?"+q.join('&'), + type: "GET", + contentType:'application/json', + data: "limit="+encodeURIComponent(limit)+"&offset="+encodeURIComponent(offset), + success: function(data) + { + //console.log("update Items", data) + data.limit = limit + data.offset = offset + thisObj.model.itemslist = data + //thisObj.model.items = {} + + var projects = []; + var usersIds = []; + var periodicTasks = []; + + for(var i in data.results) + { + var val = data.results[i] + + thisObj.model.items[val.id] = val + + if(val.project && !pmProjects.model.items[val.project] && $.inArray(val.project, projects) == -1) + { + projects.push(val.project) + } + + if(val.initiator > 0 && val.initiator_type == 'users' && $.inArray(val.initiator, usersIds) == -1) + { + usersIds.push(val.initiator); + } + else if(val.initiator > 0 && val.initiator_type == 'scheduler' && $.inArray(val.initiator, periodicTasks) == -1) + { + periodicTasks.push(val.initiator); + } + } + + var users_promise = undefined; + var projects_promise = undefined; + var periodicTasks_promise = undefined; + + if(periodicTasks.length) + { + periodicTasks_promise = pmPeriodicTasks.loadItemsByIds(periodicTasks) + } + + if(usersIds.length) + { + users_promise = pmUsers.loadItemsByIds(usersIds) + } + + if(projects.length) + { + projects_promise = pmProjects.sendSearchQuery({id:projects.join(',')}) + } + + $.when(users_promise, projects_promise, periodicTasks_promise).done(function(){ + def.resolve(data) + }) + }, + error:function(e) + { + console.warn(e) + polemarch.showErrors(e) + def.reject(e) + } + }); + + return def.promise(); +} + +/** + *Функция проверяет, произошло ли изменение в количестве записей в истории. + *Если изменения произошли, то она обновляет соответствующее свойство в объекте this.model + */ + +pmHistory.ifIncreaseTotalCount = function() +{ + var def = new $.Deferred(); + var thisObj = this; + spajs.ajax.Call({ + url: "/api/v1/history", + type: "GET", + contentType:'application/json', + data: "limit=1&rand="+Math.random(), + success: function(data) + { + var totalCount=data.count; + //console.log("new totalCount="+totalCount, "old totalCount="+thisObj.model.totalCount); + if(thisObj.model.totalCount!=totalCount) + { + thisObj.model.totalCount=totalCount; + def.resolve(); + } + else + { + def.reject(); + } + + }, + error: function (){ + def.reject(); + } + }); + return def.promise(); +} + +/** + *Функция обновляет список записей в истории каждые 5 секунд. + *Если произошли изменения в количестве записей в списке, + *то содержимое страницы обновляется. + */ + +pmHistory.updateList = function (updated_ids) +{ + var thisObj = this; + return $.when(this.loadItemsByIds(updated_ids)).always(function () + { + if (thisObj.model.updateTimeoutId) + { + clearTimeout(thisObj.model.updateTimeoutId) + } + + thisObj.model.updateTimeoutId = setTimeout(function () { + thisObj.updateList(updated_ids) + }, 5001) + + $.when(pmHistory.ifIncreaseTotalCount()).done(function() { + spajs.openMenuFromUrl(); + }) + }).promise() +} + + + +/** + * Обновляет поле модел this.model.itemslist и ложит туда список пользователей + * Обновляет поле модел this.model.items и ложит туда список инфу о пользователях по их id + */ +pmHistory.loadItems = function(limit, offset) +{ + if(!limit) + { + limit = 30; + } + + if(!offset) + { + offset = 0; + } + + var def = new $.Deferred(); + var thisObj = this; + spajs.ajax.Call({ + url: "/api/v1/"+this.model.name+"/", + type: "GET", + contentType:'application/json', + data: "limit="+encodeURIComponent(limit)+"&offset="+encodeURIComponent(offset), + success: function(data) + { + + //console.log("update Items", data) + data.limit = limit + data.offset = offset + thisObj.model.itemslist = data + //thisObj.model.items = {} + ////// + thisObj.model.totalCount=data.count; + //console.log(thisObj.model); + //////// + var projects = []; + var usersIds = []; + var periodicTasks = []; + + for(var i in data.results) + { + var val = data.results[i] + + thisObj.model.items.justWatch(val.id); + thisObj.model.items[val.id] = mergeDeep(thisObj.model.items[val.id], val) + + if(val.project && !pmProjects.model.items[val.project] && $.inArray(val.project, projects) == -1) + { + projects.push(val.project) + } + + if(val.initiator > 0 && val.initiator_type == 'users' && $.inArray(val.initiator, usersIds) == -1) + { + usersIds.push(val.initiator); + } + else if(val.initiator > 0 && val.initiator_type == 'scheduler' && $.inArray(val.initiator, periodicTasks) == -1) + { + periodicTasks.push(val.initiator); + } + } + + var users_promise = undefined; + var projects_promise = undefined; + var periodicTasks_promise = undefined; + + if(periodicTasks.length) + { + periodicTasks_promise = pmPeriodicTasks.loadItemsByIds(periodicTasks) + } + + if(usersIds.length) + { + users_promise = pmUsers.loadItemsByIds(usersIds) + } + + if(projects.length) + { + projects_promise = pmProjects.sendSearchQuery({id:projects.join(',')}) + } + + $.when(users_promise, projects_promise, periodicTasks_promise).always(function(){ + def.resolve(data) + }) + }, + error:function(e) + { + console.warn(e) + polemarch.showErrors(e) + def.reject(e) + } + }); + + return def.promise(); +} + +pmHistory.stopUpdates = function() +{ + clearTimeout(this.model.updateTimeoutId) + this.model.updateTimeoutId = undefined; + + clearTimeout(this.model.loadNewLines_timeoutId) + this.model.loadNewLines_timeoutId = undefined; +} + +/** + * Подсветка синтаксиса + * @link https://habrahabr.ru/post/43030/ + * + * @param {String} code + * @returns {String} + */ +pmHistory.Syntax = function(code) +{ + var comments = []; // Тут собираем все каменты + var strings = []; // Тут собираем все строки + var res = []; // Тут собираем все RegExp + var all = { 'C': comments, 'S': strings, 'R': res }; + var safe = { '<': '<', '>': '>', '&': '&' }; + + var ansi_up = new AnsiUp; + ansi_up.use_classes = true; + var html = ansi_up.ansi_to_html(code); + return html + // Табуляцию заменяем неразрывными пробелами + .replace(/\t/g, + '    '); +} + +pmHistory.getLine = function(item_id, line_id) +{ + var line = this.model.items[item_id].stdout[line_id] + if(/^fatal:/.test(line.text)) + { + line.fatal = 'fatal'; + } + else + { + line.fatal = ''; + } + + return spajs.just.render(this.model.name+'_stdout_line', {line:line}) +} + +pmHistory.loadNewLines = function(item_id, last_stdout_maxline) +{ + var thisObj = this; + + if(last_stdout_maxline === undefined) + { + last_stdout_maxline = this.model.items[item_id].stdout_maxline; + } + + if(!last_stdout_maxline) + { + last_stdout_maxline = 0; + } + + return $.when(this.loadItem(item_id), this.loadLines(item_id, {after:last_stdout_maxline, limit:pmHistory.model.linePerPage})).always(function() + { + var addData = false; + var history_stdout = $("#history-stdout"); + if(!history_stdout || !history_stdout.length) + { + return; + } + + var needScrollDowun = $('#history-stdout').prop('scrollHeight') - $('#history-stdout').scrollTop() - history_stdout.css('height').replace("px", "")/1 < 100 + + if(last_stdout_maxline == 0) + { + for(var i in thisObj.model.items[item_id].stdout) + { + if(thisObj.model.items[item_id].stdout[i] != undefined) + { + history_stdout.append(pmHistory.getLine(item_id, i)) + addData = true; + } + } + } + else + { + for(var i = last_stdout_maxline+1; i <= thisObj.model.items[item_id].stdout_maxline; i++) + { + if(thisObj.model.items[item_id].stdout[i] != undefined) + { + history_stdout.append(pmHistory.getLine(item_id, i)) + addData = true; + } + } + } + + + if( addData && needScrollDowun) + { + // Прокручиваем в низ только если и так скрол был не сильно приподнят + thisObj.scrollBottom() + } + + if(thisObj.model.items[item_id].status == 'RUN' || thisObj.model.items[item_id].status == 'DELAY') + { + thisObj.loadNewLines_timeoutId = setTimeout(function(){ + thisObj.loadNewLines(item_id) + }, 5001) + } + }).promise() +} + +pmHistory.scrollBottom = function() +{ + jQuery('#history-stdout').scrollTop(9999999); +} +/** + * Обновляет поле модел this.model.itemslist и ложит туда список пользователей + * Обновляет поле модел this.model.items и ложит туда список инфу о пользователях по их id + */ +pmHistory.loadLines = function(item_id, opt) +{ + if(!opt.limit) + { + opt.limit = 30; + } + + if(!opt.offset) + { + opt.offset = 0; + } + + opt.format = 'json'; + + var def = new $.Deferred(); + spajs.ajax.Call({ + url: "/api/v1/history/"+item_id+"/lines/", + type: "GET", + contentType:'application/json', + data: opt, + success: function(data) + { + if(!pmHistory.model.items[item_id].stdout) + { + pmHistory.model.items[item_id].stdout = {} + pmHistory.model.items[item_id].stdout_count = 0 + pmHistory.model.items[item_id].stdout_maxline = 0 + pmHistory.model.items[item_id].stdout_minline = 999999999 + } + + pmHistory.model.items[item_id].stdout_count = data.count; + for(var i in data.results) + { + var line_number = data.results[i].line_number + + if(pmHistory.model.items[item_id].stdout_maxline < line_number) + { + pmHistory.model.items[item_id].stdout_maxline = line_number; + } + + if(pmHistory.model.items[item_id].stdout_minline > line_number) + { + pmHistory.model.items[item_id].stdout_minline = line_number; + } + + pmHistory.model.items[item_id].stdout[line_number] = {id:line_number, text:data.results[i].line} + } + + def.resolve() + + }, + error:function(e) + { + console.warn(e) + polemarch.showErrors(e) + def.reject(e) + } + }); + + return def.promise(); +} + +///////////////////////////////// +pmHistory.clearLogs=function(item_id) +{ + return spajs.ajax.Call({ + url: "/api/v1/history/"+item_id+"/clear/", + type: "DELETE", + contentType:'application/json', + success: function(data) + { + $.notify("Output trancated", "success"); + pmHistory.model.items[item_id].stdout={}; + spajs.openURL(window.location.href); + }, + error:function(e) + { + polemarch.showErrors(e.responseJSON) + } + }); +} + +pmHistory.hideClearLogsButton=function() +{ + if($('button').is('#clear_logs')) + { + $("#clear_logs").slideToggle(); + } +} +///////////////////////////////// +tabSignal.connect("polemarch.start", function() +{ + // history + spajs.addMenu({ + id:"history", + urlregexp:[/^history$/, /^history\/search\/?$/, /^history\/page\/([0-9]+)$/], + onOpen:function(holder, menuInfo, data){return pmHistory.showUpdatedList(holder, menuInfo, data);}, + onClose:function(){return pmHistory.stopUpdates();}, + }) + + spajs.addMenu({ + id:"history-search", + urlregexp:[/^history\/search\/([A-z0-9 %\-.:,=]+)$/, /^history\/search\/([A-z0-9 %\-.:,=]+)\/page\/([0-9]+)$/], + onOpen:function(holder, menuInfo, data){return pmHistory.showSearchResults(holder, menuInfo, data);} + }) + + spajs.addMenu({ + id:"history-item", + urlregexp:[/^history\/([0-9]+)$/], + onOpen:function(holder, menuInfo, data){return pmHistory.showItem(holder, menuInfo, data);}, + onClose:function(){return pmHistory.stopUpdates();} + }) + + spajs.addMenu({ + id:"history-item-in-project", + urlregexp:[/^project\/([0-9]+)\/history\/([0-9]+)$/], + onOpen:function(holder, menuInfo, data){return pmHistory.showItemInProjects(holder, menuInfo, data);}, + onClose:function(){return pmHistory.stopUpdates();} + }) + + spajs.addMenu({ + id:"project-history", + urlregexp:[/^project\/([0-9]+)\/history$/, /^project\/([0-9]+)\/history\/page\/([0-9]+)$/], + onOpen:function(holder, menuInfo, data){ + return pmHistory.showUpdatedList(holder, menuInfo, data, "showListInProjects", function(menuInfo, data) + { + var offset = 0 + var limit = pmHistory.pageSize; + if(data.reg && data.reg[2] > 0) + { + offset = pmHistory.pageSize*(data.reg[2] - 1); + } + var project_id = data.reg[1]; + + return pmHistory.sendSearchQuery({project:project_id}, limit, offset) + }); + }, + onClose:function(){return pmHistory.stopUpdates();}, + }) + + spajs.addMenu({ + id:"project-history-search", + urlregexp:[/^project\/([0-9]+)\/history\/search\/([A-z0-9 %\-.:,=]+)$/,/^project\/([0-9]+)\/history\/search\/([A-z0-9 %\-.:,=]+)\/page\/([0-9]+)$/], + onOpen:function(holder, menuInfo, data){return pmHistory.showSearchResultsInProjects(holder, menuInfo, data);} + }) + +}) \ No newline at end of file diff --git a/polemarch/static/js/pmHosts.js b/polemarch/static/js/pmHosts.js index 6b7d139e..61be26db 100644 --- a/polemarch/static/js/pmHosts.js +++ b/polemarch/static/js/pmHosts.js @@ -1,289 +1,291 @@ - -var pmHosts = inheritance(pmItems) - -pmHosts.model.name = "hosts" -pmHosts.model.page_name = "host" -pmHosts.model.bulk_name = "host" -pmHosts.model.className = "pmHosts" - -pmHosts.model.page_list = { - buttons:[ - { - class:'btn btn-primary', - function:function(){ return "spajs.open({ menuId:'new-"+this.model.page_name+"'}); return false;"}, - title:'Create', - link:function(){ return '/?new-'+this.model.page_name}, - }, - ], - title: "Hosts", - short_title: "Hosts", - fileds:[ - { - title:'Name', - name:'name', - }, - { - title:'Type', - name:'type', - style:function(item){ return 'style="width: 70px"'}, - class:function(item){ return 'class="hidden-xs"'}, - } - ], - actions:[ - { - class:'btn btn-danger', - function:function(item){ return 'spajs.showLoader('+this.model.className+'.deleteItem('+item.id+')); return false;'}, - title:'Delete', - link:function(){ return '#'} - } - ] -} - - -pmHosts.fileds = [ - [ - { - filed: new filedsLib.filed.text(), - title:'Name', - name:'name', - placeholder:'Enter host or range name', - help:'Host or range name', - validator:function(value) - { - if(this.validateRangeName(value) || this.validateHostName(value)) - { - return true; - } - - $.notify("Invalid value in field `name` it mast be valid host or range name", "error"); - return false; - }, - fast_validator:function(value){ return this.validateRangeName(value) || this.validateHostName(value)} - }, - ] -] - -pmHosts.model.page_new = { - title: "New host", - short_title: "New host", - fileds:pmHosts.fileds, - sections:[ - function(section){ - return jsonEditor.editor({}, {block:this.model.name}); - } - ], - onBeforeSave:function(data) - { - if(this.validateHostName(data.name)) - { - data.type = 'HOST' - } - else if(this.validateRangeName(data.name)) - { - data.type = 'RANGE' - } - else - { - $.notify("Error in host or range name", "error"); - return undefined; - } - - data.vars = jsonEditor.jsonEditorGetValues() - return data; - }, - onCreate:function(result, status, xhr, callOpt) - { - var def = new $.Deferred(); - $.notify("Host created", "success"); - - if(callOpt.parent_item) - { - if(callOpt.parent_type == 'group') - { - $.when(pmGroups.addSubHosts(callOpt.parent_item, [result.id])).always(function(){ - $.when(spajs.open({ menuId:"group/"+callOpt.parent_item})).always(function(){ - def.resolve() - }) - }) - } - else if(callOpt.parent_type == 'inventory') - { - $.when(pmInventories.addSubHosts(callOpt.parent_item, [result.id])).always(function(){ - $.when(spajs.open({ menuId:"inventory/"+callOpt.parent_item})).always(function(){ - def.resolve() - }) - }) - } - else if(callOpt.parent_type == 'project') - { - $.when(pmProjects.addSubHosts(callOpt.parent_item, [result.id])).always(function(){ - $.when(spajs.open({ menuId:"project/"+callOpt.parent_item})).always(function(){ - def.resolve() - }) - }) - } - else - { - console.error("Не известный parent_type", callOpt.parent_type) - $.when(spajs.open({ menuId:"host/"+result.id})).always(function(){ - def.resolve() - }) - } - } - else - { - $.when(spajs.open({ menuId:"host/"+result.id})).always(function(){ - def.resolve() - }) - } - - return def.promise(); - } -} - -pmHosts.model.page_item = { - buttons:[ - { - class:'btn btn-primary', - function:function(item_id){ return 'spajs.showLoader('+this.model.className+'.updateItem('+item_id+')); return false;'}, - title:'Save', - link:function(){ return '#'}, - }, - { - class:'btn btn-default copy-btn', - function:function(item_id){ return 'spajs.showLoader('+this.model.className+'.copyAndEdit('+item_id+')); return false;'}, - title:'', - link:function(){ return '#'}, - help:'Copy' - }, - { - class:'btn btn-danger danger-right', - function:function(item_id){ return 'spajs.showLoader('+this.model.className+'.deleteItem('+item_id+')); return false;'}, - title:' ', - link:function(){ return '#'}, - }, - ], - sections:[ - function(section, item_id){ - return jsonEditor.editor(this.model.items[item_id].vars, {block:this.model.name}); - } - ], - title: function(item_id){ - return "Host "+pmHosts.model.items[item_id].justText('name') - }, - short_title: function(item_id){ - return "Host "+pmHosts.model.items[item_id].justText('name', function(v){return v.slice(0, 20)}) - }, - fileds:pmHosts.fileds, - onUpdate:function(result) - { - return true; - }, - onBeforeSave:function(data, item_id) - { - data.vars = jsonEditor.jsonEditorGetValues() - if(this.validateHostName(data.name)) - { - data.type = 'HOST' - } - else if(this.validateRangeName(data.name)) - { - data.type = 'RANGE' - } - else - { - $.notify("Error in host or range name", "error"); - return undefined; - } - return data; - }, -} - -pmHosts.copyItem = function(item_id) -{ - var def = new $.Deferred(); - var thisObj = this; - - $.when(this.loadItem(item_id)).done(function() - { - var data = thisObj.model.items[item_id]; - $.when(encryptedCopyModal.replace(data)).done(function(data) - { - delete data.id; - spajs.ajax.Call({ - url: "/api/v1/"+thisObj.model.name+"/", - type: "POST", - contentType:'application/json', - data: JSON.stringify(data), - success: function(data) - { - thisObj.model.items[data.id] = data - def.resolve(data.id) - }, - error:function(e) - { - def.reject(e) - } - }); - }).fail(function(e) - { - def.reject(e) - }) - - }).fail(function(e) - { - def.reject(e) - }) - - - return def.promise(); -} - - -/* - * -detail:"database is locked" -error_type:"OperationalError" - * -for(var i =0; i< 10000; i++) -{ -setTimeout(function(){ - name = Math.random()+"-"+Math.random() - name = name.replace(/\./g, "") - spajs.ajax.Call({ - url: "/api/v1/hosts/", - type: "POST", - contentType:'application/json', - data: JSON.stringify({name:name, type:"HOST"}), - }) -}, i*400); -} - */ - - tabSignal.connect("polemarch.start", function() - { - // hosts - spajs.addMenu({ - id:"hosts", - urlregexp:[/^hosts$/, /^host$/, /^hosts\/search\/?$/, /^hosts\/page\/([0-9]+)$/], - onOpen:function(holder, menuInfo, data){return pmHosts.showList(holder, menuInfo, data);} - }) - - spajs.addMenu({ - id:"hosts-search", - urlregexp:[/^hosts\/search\/([A-z0-9 %\-.:,=]+)$/], - onOpen:function(holder, menuInfo, data){return pmHosts.showSearchResults(holder, menuInfo, data);} - }) - - spajs.addMenu({ - id:"host", - urlregexp:[/^host\/([0-9]+)$/, /^hosts\/([0-9]+)$/], - onOpen:function(holder, menuInfo, data){return pmHosts.showItem(holder, menuInfo, data);} - }) - - spajs.addMenu({ - id:"newHost", - urlregexp:[/^new-host$/, /^([A-z0-9_]+)\/([0-9]+)\/new-host$/], - onOpen:function(holder, menuInfo, data){return pmHosts.showNewItemPage(holder, menuInfo, data);} - }) - }) \ No newline at end of file + +var pmHosts = inheritance(pmItems) + +pmHosts.model.name = "hosts" +pmHosts.model.page_name = "host" +pmHosts.model.bulk_name = "host" +pmHosts.model.className = "pmHosts" + +pmHosts.model.page_list = { + buttons:[ + { + class:'btn btn-primary', + function:function(){ return "spajs.open({ menuId:'new-"+this.model.page_name+"'}); return false;"}, + title:'Create', + link:function(){ return '/?new-'+this.model.page_name}, + }, + ], + title: "Hosts", + short_title: "Hosts", + fileds:[ + { + title:'Name', + name:'name', + }, + { + title:'Type', + name:'type', + style:function(item){ return 'style="width: 70px"'}, + class:function(item){ return 'class="hidden-xs"'}, + } + ], + actions:[ + { + class:'btn btn-danger', + function:function(item){ return 'spajs.showLoader('+this.model.className+'.deleteItem('+item.id+')); return false;'}, + title:'Delete', + link:function(){ return '#'} + } + ] +} + + +pmHosts.fileds = [ + [ + { + filed: new filedsLib.filed.text(), + title:'Name', + name:'name', + placeholder:'Enter host or range name', + help:'Host or range name', + validator:function(value) + { + if(this.validateRangeName(value) || this.validateHostName(value)) + { + return true; + } + + $.notify("Invalid value in field `name` it mast be valid host or range name", "error"); + return false; + }, + fast_validator:function(value){ return this.validateRangeName(value) || this.validateHostName(value)} + }, + ] +] + +pmHosts.model.page_new = { + title: "New host", + short_title: "New host", + fileds:pmHosts.fileds, + sections:[ + function(section){ + return jsonEditor.editor({}, {block:this.model.name}); + } + ], + onBeforeSave:function(data) + { + if(this.validateHostName(data.name)) + { + data.type = 'HOST' + } + else if(this.validateRangeName(data.name)) + { + data.type = 'RANGE' + } + else + { + $.notify("Error in host or range name", "error"); + return undefined; + } + + data.vars = jsonEditor.jsonEditorGetValues() + return data; + }, + onCreate:function(result, status, xhr, callOpt) + { + var def = new $.Deferred(); + $.notify("Host created", "success"); + + if(callOpt.parent_item) + { + if(callOpt.parent_type == 'group') + { + $.when(pmGroups.addSubHosts(callOpt.parent_item, [result.id])).always(function(){ + $.when(spajs.open({ menuId:"group/"+callOpt.parent_item})).always(function(){ + def.resolve() + }) + }) + } + else if(callOpt.parent_type == 'inventory') + { + $.when(pmInventories.addSubHosts(callOpt.parent_item, [result.id])).always(function(){ + $.when(spajs.open({ menuId:"inventory/"+callOpt.parent_item})).always(function(){ + def.resolve() + }) + }) + } + else if(callOpt.parent_type == 'project') + { + $.when(pmProjects.addSubHosts(callOpt.parent_item, [result.id])).always(function(){ + $.when(spajs.open({ menuId:"project/"+callOpt.parent_item})).always(function(){ + def.resolve() + }) + }) + } + else + { + console.error("Не известный parent_type", callOpt.parent_type) + $.when(spajs.open({ menuId:"host/"+result.id})).always(function(){ + def.resolve() + }) + } + } + else + { + $.when(spajs.open({ menuId:"host/"+result.id})).always(function(){ + def.resolve() + }) + } + + return def.promise(); + } +} + +pmHosts.model.page_item = { + buttons:[ + { + class:'btn btn-primary', + function:function(item_id){ return 'spajs.showLoader('+this.model.className+'.updateItem('+item_id+')); return false;'}, + title:'Save', + link:function(){ return '#'}, + }, + { + class:'btn btn-default copy-btn', + function:function(item_id){ return 'spajs.showLoader('+this.model.className+'.copyAndEdit('+item_id+')); return false;'}, + title:'', + link:function(){ return '#'}, + help:'Copy' + }, + { + class:'btn btn-danger danger-right', + function:function(item_id){ return 'spajs.showLoader('+this.model.className+'.deleteItem('+item_id+')); return false;'}, + title:' ', + link:function(){ return '#'}, + }, + ], + sections:[ + function(section, item_id){ + return jsonEditor.editor(this.model.items[item_id].vars, {block:this.model.name}); + } + ], + title: function(item_id){ + return "Host "+pmHosts.model.items[item_id].justText('name') + }, + short_title: function(item_id){ + return "Host "+pmHosts.model.items[item_id].justText('name', function(v){return v.slice(0, 20)}) + }, + fileds:pmHosts.fileds, + onUpdate:function(result) + { + return true; + }, + onBeforeSave:function(data, item_id) + { + data.vars = jsonEditor.jsonEditorGetValues() + if(this.validateHostName(data.name)) + { + data.type = 'HOST' + } + else if(this.validateRangeName(data.name)) + { + data.type = 'RANGE' + } + else + { + $.notify("Error in host or range name", "error"); + return undefined; + } + return data; + }, +} + +pmHosts.copyItem = function(item_id) +{ + var def = new $.Deferred(); + var thisObj = this; + + $.when(this.loadItem(item_id)).done(function() + { + var data = thisObj.model.items[item_id]; + $.when(encryptedCopyModal.replace(data)).done(function(data) + { + delete data.id; + spajs.ajax.Call({ + url: "/api/v1/"+thisObj.model.name+"/", + type: "POST", + contentType:'application/json', + data: JSON.stringify(data), + success: function(data) + { + thisObj.model.items[data.id] = data + def.resolve(data.id) + }, + error:function(e) + { + def.reject(e) + } + }); + }).fail(function(e) + { + def.reject(e) + }) + + }).fail(function(e) + { + def.reject(e) + }) + + + return def.promise(); +} + + +/* + * +detail:"database is locked" +error_type:"OperationalError" + * +for(var i =0; i< 10000; i++) +{ +setTimeout(function(){ + name = Math.random()+"-"+Math.random() + name = name.replace(/\./g, "") + spajs.ajax.Call({ + url: "/api/v1/hosts/", + type: "POST", + contentType:'application/json', + data: JSON.stringify({name:name, type:"HOST"}), + }) +}, i*400); +} + */ + + tabSignal.connect("polemarch.start", function() + { + // hosts + spajs.addMenu({ + id:"hosts", + urlregexp:[/^hosts$/, /^host$/, /^hosts\/search\/?$/, /^hosts\/page\/([0-9]+)$/], + onOpen:function(holder, menuInfo, data){return pmHosts.showList(holder, menuInfo, data);} + }) + + spajs.addMenu({ + id:"hosts-search", + urlregexp:[/^hosts\/search\/([A-z0-9 %\-.:,=]+)$/, /^hosts\/search\/([A-z0-9 %\-.:,=]+)\/page\/([0-9]+)$/], + onOpen:function(holder, menuInfo, data){return pmHosts.showSearchResults(holder, menuInfo, data);} + }) + + spajs.addMenu({ + id:"host", + urlregexp:[/^host\/([0-9]+)$/, /^hosts\/([0-9]+)$/], + onOpen:function(holder, menuInfo, data){return pmHosts.showItem(holder, menuInfo, data);} + }) + + spajs.addMenu({ + id:"newHost", + urlregexp:[/^new-host$/, /^([A-z0-9_]+)\/([0-9]+)\/new-host$/], + onOpen:function(holder, menuInfo, data){return pmHosts.showNewItemPage(holder, menuInfo, data);} + }) + }) + + //изменение типа input'a на file при выборе \ No newline at end of file diff --git a/polemarch/static/js/pmInventories.js b/polemarch/static/js/pmInventories.js index 73c8dda6..7495ef59 100644 --- a/polemarch/static/js/pmInventories.js +++ b/polemarch/static/js/pmInventories.js @@ -1,1444 +1,1454 @@ - - -var pmInventories = inheritance(pmItems) -pmInventories.model.name = "inventories" -pmInventories.model.page_name = "inventory" -pmInventories.model.bulk_name = "inventory" -pmInventories.model.className = "pmInventories" - -/** - * Параметры из секции *:vars - * Строка где после первого `=` всё остальное значение. - */ -pmInventories.parseMonoVarsLine = function(index, line) -{ - var vars = {} - var param = /^([^=]+)="(.*)"$/.exec(line) - - if(param) - { - vars[param[1]] = param[2] - } - else - { - param = /^([^=]+)=(.*)$/.exec(line) - if(param) - { - vars[param[1]] = param[2] - } - else - { - throw "Error in line "+index+" invalid varibles string ("+line+")" - } - } - return vars; -} - -/** - * Параметры хоста - * Строка где может быть несколько параметров ключ=значение через пробел - */ -pmInventories.parseVarsLine = function(index, line) -{ - var vars = {} - do{ - if(line.length == 0) - { - break; - } - - var params = /^([^=]+)=["'](.*?)["'] +[^=]+=/.exec(line) - if(params) - { - params[1] = trim(params[1]) - vars[params[1]] = stripslashes(params[2]) - line = trim(line.slice(params[1].length + params[2].length + 3)) - continue; - } - - params = /^([^=]+)=([^ ]*) +[^=]+=/.exec(line) - if(params) - { - params[1] = trim(params[1]) - vars[params[1]] = stripslashes(params[2]) - line = trim(line.slice(params[1].length + params[2].length + 1)) - continue; - } - - params = /^([^=]+)=["'](.*?)["'] *$/.exec(line) - if(params) - { - params[1] = trim(params[1]) - vars[params[1]] = stripslashes(params[2]) - break; - } - - params = /^([^=]+)=([^ ]*) *$/.exec(line) - if(params) - { - params[1] = trim(params[1]) - vars[params[1]] = stripslashes(params[2]) - line = trim(line.slice(params[1].length + params[2].length + 1)) - continue; - } - else - { - throw "Error in line "+index+" invalid varibles string ("+line+")" - } - }while(true) - return vars; -} - -/** - * Хост и параметры - * Строка где идёт сначала имя хоста а потом его параметры в `parseVarsLine` - */ -pmInventories.parseHostLine = function(index, line, section, inventory) -{ - var params = /^([^ ]+)/.exec(line) - if(!params) - { - throw "Error in line "+index+" ("+line+")" - } - - var name = "" - var type = "" - if(pmItems.validateHostName(params[1])) - { - name = params[1] - type = "HOST" - } - else if(pmItems.validateRangeName(params[1])) - { - name = params[1] - type = "RANGE" - } - else - { - throw "Error in line "+index+" invalid host or range name ("+params[1]+")" - } - - line = trim(line.slice(name.length)) - - var host = { - name:name, - type:type, - vars:pmInventories.parseVarsLine(index, line) - } - - if(section !== "_hosts") - { - pmInventories.addGroupIfNotExists(inventory, section) - inventory.groups[section].hosts.push(host) - } - else - { - inventory.hosts.push(host) - } -} - -/** - * Парсит строку файла инвентория - * @param {integer} index - * @param {string} line - * @param {string} section - * @param {Object} inventory - * @returns {Boolean} - */ -pmInventories.parseLine = function(index, line, section, inventory) -{ - line = trim(line); - - if(section == "_hosts") - { - pmInventories.parseHostLine(index, line, section, inventory) - return true; - } - - if(section == "all:vars") - { - var vars = pmInventories.parseMonoVarsLine(index, line) - inventory.vars = Object.assign(inventory.vars, vars) - return true; - } - - if(/:vars$/.test(section)) - { - section = section.substring(0, section.length - ":vars".length) - - pmInventories.addGroupIfNotExists(inventory, section) - inventory.groups[section].vars = Object.assign(inventory.groups[section].vars, pmInventories.parseMonoVarsLine(index, line)) - return true; - } - - if(/:children$/.test(section)) - { - /** - * Параметры из секции *:children - * Строка где просто имя группы - */ - section = section.substring(0, section.length - ":children".length) - - pmInventories.addGroupIfNotExists(inventory, section) - inventory.groups[section].children = true - inventory.groups[section].groups.push(line) - pmInventories.addGroupIfNotExists(inventory, line) - return true; - } - - pmInventories.parseHostLine(index, line, section, inventory) - return false; -} - -/** - * Добавляет группу в инвенторий если её ещё нет - * @param {Object} inventory - * @param {string} group_name имя группы - * @returns {Boolean} true если группа добавлена. - */ -pmInventories.addGroupIfNotExists = function(inventory, group_name) -{ - if(!inventory.groups[group_name]) - { - inventory.groups[group_name] = { - vars:{}, - groups:[], - hosts:[], - } - - return true; - } - - return false; -} - - -/** - * Парсит файла инвентория - * @param {string} text текст файла инвентория - * @returns {Object} - */ -pmInventories.parseFromText = function(text) -{ - var lines = text.split(/\n/g) - - var cSection = "_hosts"; - var inventory = { - hosts:[], - groups:{}, - vars:{}, - name:new Date().toString() - } - - for(var i in lines) - { - var line = lines[i].replace(/^ */g, "") - - if(/^\s*[#;]\s+inventory name: (.*)/ig.test(line)) - { - var name = /^\s*[#;]\s+inventory name: (.*)/ig.exec(line) - inventory.name = name[1] - continue; - } - - if(/^\s*$/ig.test(line)) - { - continue; - } - if(/^\s*[#;]/ig.test(line)) - { - continue; - } - - console.log(i+":\t" + line) - - if(/^\[([A-z0-9\.:\-]+:vars)\]/ig.test(line)) - { - var res = /^\[([A-z0-9\.:\-]+)\]/ig.exec(line) - cSection = res[1] - - var group_name = cSection.substring(0, cSection.length - ":vars".length) - if(group_name != "all") - { - pmInventories.addGroupIfNotExists(inventory, group_name) - } - console.info("setSection:vars ", cSection) - continue; - } - if(/^\[([A-z0-9\.:\-]+:children)\]/ig.test(line)) - { - var res = /^\[([A-z0-9\.:\-]+)\]/ig.exec(line) - cSection = res[1] - pmInventories.addGroupIfNotExists(inventory, cSection.substring(0, cSection.length - ":children".length)) - console.info("setSection:children ", cSection) - continue; - } - if(/^\[([A-z0-9\.:\-]+)\]/ig.test(line)) - { - var res = /^\[([A-z0-9\.:\-]+)\]/ig.exec(line) - cSection = res[1] - pmInventories.addGroupIfNotExists(inventory, cSection) - console.info("setSection ", cSection) - continue; - } - - pmInventories.parseLine(i, line, cSection, inventory) - } - - pmInventories.addHierarchyDataToInventoryGroups(inventory) - console.log("\n\ninventory", inventory) - return inventory; -} - - - -/** - * Формирует вспомагательную информацию в объекте инвентория о вложенности групп друг в друга. - * @param {Object} inventory Инвенторий (Обязательный) - * @param {string} group_name (не обязательный) - * @param {integer} level (не обязательный) - * @param {Array} parents (не обязательный) - */ -pmInventories.addHierarchyDataToInventoryGroups = function(inventory, group_name, level, parents) -{ - if(!level) - { - level = 0 - } - - if(parents === undefined) - { - parents = [] - } - - if(group_name === undefined || group_name == 'all') - { - for(var i in inventory.groups) - { - delete inventory.groups[i]['dataLevel'] - } - - for(var i in inventory.groups) - { - pmInventories.addHierarchyDataToInventoryGroups(inventory, i, 1, ['all']) - } - - return; - } - - - if(inventory.groups[group_name].dataLevel && inventory.groups[group_name].dataLevel.level >= level ) - { - return; - } - - parents.push(group_name) - inventory.groups[group_name].dataLevel = { - level:level, - parents:parents, - } - - for(var i in inventory.groups[group_name].groups) - { - var hasError = false; - for(var j in inventory.groups[group_name].dataLevel.parents) - { - var val = inventory.groups[group_name].dataLevel.parents[j] - if(val == inventory.groups[group_name].groups[i]) - { - inventory.groups[group_name].dataLevel.error = "Group `"+val+"` is recursive include into group `"+inventory.groups[group_name].groups[i]+"`"; - console.warn(inventory.groups[group_name].dataLevel.error) - hasError = true - break; - } - } - - if(hasError) - { - continue; - } - - pmInventories.addHierarchyDataToInventoryGroups(inventory, inventory.groups[group_name].groups[i], level+1, parents.slice()) - } - - return; -} - -// ansible_ssh_private_key_file - запрашивать значение этого параметра. - -pmInventories.importFromFile = function(files_event) -{ - var def = new $.Deferred(); - this.model.files = files_event - this.model.importedInventories = {} - var thisObj = this; - for(var i=0; i - $.notify("Error in field ansible_ssh_private_key_file invalid value", "error"); - pmInventories.showInventoryVarsModal(); - def2.reject() - return def2.promise(); - } - - for(var i in inventory.hosts) - { - var val = inventory.hosts[i] - if(val.vars.ansible_ssh_private_key_file !== undefined && !/-----BEGIN RSA PRIVATE KEY-----/.test(val.vars.ansible_ssh_private_key_file)) - { - // - $.notify("Error in field ansible_ssh_private_key_file invalid value", "error"); - pmInventories.showHostVarsModal({group:'all', name:val.name}); - def2.reject() - return def2.promise(); - } - } - - for(var i in inventory.groups) - { - var val = inventory.groups[i] - if(val.vars.ansible_ssh_private_key_file !== undefined && !/-----BEGIN RSA PRIVATE KEY-----/.test(val.vars.ansible_ssh_private_key_file)) - { - // - $.notify("Error in field ansible_ssh_private_key_file invalid value", "error"); - pmInventories.showGroupVarsModal({name:i}); - def2.reject() - return def2.promise(); - } - - for(var j in val.hosts) - { - var hval = val.hosts[j] - if(hval.vars.ansible_ssh_private_key_file !== undefined && !/-----BEGIN RSA PRIVATE KEY-----/.test(hval.vars.ansible_ssh_private_key_file)) - { - // - $.notify("Error in field ansible_ssh_private_key_file invalid value", "error"); - pmInventories.showHostVarsModal({group:i, name:hval.name}); - def2.reject() - return def2.promise(); - } - } - } - - var def = new $.Deferred(); - - if($("#inventory_name").val() != "") - { - inventory.name = $("#inventory_name").val(); - } - - if(!inventory.name) - { - // inventory.name = "new imported inventory" - - $.notify("Error in field inventory name", "error"); - def2.reject({text:"Error in field inventory name"}) - return def2.promise(); - } - - var inventoryObject = { - name:inventory.name, - vars:inventory.vars - } - - var deleteBulk = [] - $.when(pmInventories.importItem(inventoryObject)).done(function(inventory_id) - { - deleteBulk.push({ - type:"del", - item:'inventory', - pk:inventory_id - }) - var bulkdata = [] - // Сбор групп и вложенных в них хостов - for(var i in inventory.groups) - { - var val = inventory.groups[i] - - bulkdata.push({ - type:"add", - item:'group', - data:{ - name:i, - children:val.children, - vars:val.vars - } - }) - - for(var j in val.hosts) - { - var hval = val.hosts[j] - bulkdata.push({ - type:"add", - item:'host', - data:{ - name:hval.name, - type:hval.type, - vars:hval.vars - } - }) - } - } - - // Сбор хостов вложенных к инвенторию - var bulkHosts = [] - for(var i in inventory.hosts) - { - var val = inventory.hosts[i] - bulkHosts.push({ - type:"add", - item:'host', - data:{ - name:val.name, - type:val.type, - vars:val.vars - } - }) - } - - // Добавление хостов вложенных к инвенторию - spajs.ajax.Call({ - url: "/api/v1/_bulk/", - type: "POST", - contentType:'application/json', - data:JSON.stringify(bulkHosts), - success: function(data) - { - var hasError = false; - var hosts_ids = [] - for(var i in data) - { - var val = data[i] - if(val.status != 201) - { - $.notify("Error "+val.status, "error"); - hasError = true; - continue; - } - hosts_ids.push(val.data.id) - deleteBulk.push({ - type:"del", - item:'host', - pk:val.data.id - }) - } - - if(hasError) - { - // По меньшей мере в одной операции была ошибка вставки. - // Инвенторий импортирован не полностью - def.reject(deleteBulk); - return; - } - - $.when(pmInventories.addSubHosts(inventory_id, hosts_ids)).done(function() - { - // Добавление групп и вложенных в них хостов - spajs.ajax.Call({ - url: "/api/v1/_bulk/", - type: "POST", - contentType:'application/json', - data:JSON.stringify(bulkdata), - success: function(data) - { - var igroups_ids = [] - var bulk_update = [] - var hasError = false; - for(var i in data) - { - deleteBulk.push({ - type:"del", - item:data.item, - pk:data[i].data.id - }) - - var val = data[i] - if(val.status != 201) - { - $.notify("Error "+val.status, "error"); - hasError = true; - } - - if(val.data.children !== undefined ) - { - igroups_ids.push(val.data.id) - - // Это группа - if(val.data.children) - { - if(inventory.groups[val.data.name].groups.length) - { - // Добавление подгрупп - var groups_ids = [] - for(var j in inventory.groups[val.data.name].groups) - { - // найти id группы и прекрепить. - for(var k in data) - { - if(data[k].data.children !== undefined && data[k].data.name == inventory.groups[val.data.name].groups[j]) - { - groups_ids.push(data[k].data.id) - break; - } - } - } - bulk_update.push({ - type: "mod", - item:'group', - method: "PUT", - data_type: 'groups', - pk:val.data.id, - data:groups_ids - }) - } - } - else - { - if(inventory.groups[val.data.name].hosts.length) - { - // Добавление хостов - var hosts_ids = [] - for(var j in inventory.groups[val.data.name].hosts) - { - // найти id группы и прекрепить. - for(var k in data) - { - if(data[k].data.children === undefined && data[k].data.name == inventory.groups[val.data.name].hosts[j].name) - { - hosts_ids.push(data[k].data.id) - break; - } - } - } - bulk_update.push({ - type: "mod", - item:'group', - method: "PUT", - data_type: 'hosts', - pk:val.data.id, - data:hosts_ids - }) - } - } - } - else - { - // Это хост - } - } - - if(hasError) - { - // По меньшей мере в одной операции была ошибка вставки. - // Инвенторий импортирован не полностью - def.reject(deleteBulk); - return; - } - - $.when(pmInventories.addSubGroups(inventory_id, igroups_ids)).done(function() - { - if(bulk_update.length) - { - spajs.ajax.Call({ - url: "/api/v1/_bulk/", - type: "POST", - contentType:'application/json', - data:JSON.stringify(bulk_update), - success: function(data) - { - var hasError = false; - for(var i in data) - { - var val = data[i] - if(val.status != 200) - { - $.notify("Error "+val.status, "error"); - hasError = true; - continue; - } - } - - if(hasError) - { - // По меньшей мере в одной операции была ошибка обновления. - // Инвенторий импортирован не полностью - def.reject(deleteBulk); - return; - } - - def.resolve(inventory_id); - }, - error:function(e) - { - console.warn(e) - polemarch.showErrors(e) - def.reject(deleteBulk); - } - }) - } - else - { - def.resolve(inventory_id); - } - }).fail(function(e){ - console.warn(e) - polemarch.showErrors(e) - def.reject(deleteBulk); - }) - }, - error:function(e) - { - console.warn(e) - polemarch.showErrors(e) - def.reject(deleteBulk); - } - }); - }).fail(function(e){ - console.warn(e) - polemarch.showErrors(e) - def.reject(deleteBulk); - }) - }, - error:function(e) - { - console.warn(e) - polemarch.showErrors(e) - def.reject(deleteBulk); - } - }) - }).fail(function(e) - { - console.warn(e) - polemarch.showErrors(e) - def.reject(deleteBulk); - }) - - $.when(def).done(function(inventory_id) - { - def2.resolve(inventory_id) - }).fail(function(delete_bulk) - { - $.when(spajs.ajax.Call({ - url: "/api/v1/_bulk/", - type: "POST", - contentType:'application/json', - data:JSON.stringify(delete_bulk), - })).always(function(){ - def2.reject() - }) - }) - - return def2.promise(); -} - - -pmInventories.showImportPage = function(holder, menuInfo, data) -{ - var def = new $.Deferred(); - - var text = spajs.just.render(this.model.name+'_import_page', {}) - $(holder).insertTpl(text) - - def.resolve() - return def.promise(); -} - -pmInventories.renderImportedInventory = function(imported) -{ - if(!imported || !imported.inventory) - { - return "" - } - - var text = spajs.just.render(this.model.name+'_imported_inventory', {inventory:imported.inventory, text:imported.text}) - return text; -} - -pmInventories.copyItem = function(item_id) -{ - var def = new $.Deferred(); - var thisObj = this; - - $.when(this.loadItem(item_id)).done(function() - { - var data = thisObj.model.items[item_id]; - delete data.id; - data.name = "copy from " + data.name - - $.when(encryptedCopyModal.replace(data)).done(function(data) - { - spajs.ajax.Call({ - url: "/api/v1/"+thisObj.model.name+"/", - type: "POST", - contentType:'application/json', - data: JSON.stringify(data), - success: function(newItem) - { - thisObj.model.items[newItem.id] = newItem - - var groups = [] - for(var i in data.groups) - { - groups.push(data.groups[i].id) - } - - var hosts = [] - for(var i in data.hosts) - { - hosts.push(data.hosts[i].id) - } - - $.when(thisObj.setSubGroups(newItem.id, groups), thisObj.setSubHosts(newItem.id, hosts)).always(function(){ - def.resolve(newItem.id) - }) - }, - error:function(e) - { - def.reject(e) - } - }); - }).fail(function(e) - { - def.reject(e) - }) - }).fail(function(e) - { - def.reject(e) - }) - - - return def.promise(); -} - - - - -pmInventories.model.page_list = { - title: "Inventories", - short_title: "Inventories", - buttons:[ - { - class:'btn btn-primary', - function:function(){ return "spajs.open({ menuId:'new-"+this.model.page_name+"'}); return false;"}, - title:'Create', - link:function(){ return '/?new-'+this.model.page_name}, - }, - { - tpl:function(){ - return spajs.just.render('inventories_btn_openImportPageAndImportFiles', {}) - }, - }, - ], - fileds:[ - { - title:'Name', - name:'name', - }, - ], - actions:[ - { - class:'btn btn-danger', - function:function(item){ return 'spajs.showLoader('+this.model.className+'.deleteItem('+item.id+')); return false;'}, - title:'Delete', - link:function(){ return '#'} - }, - { - class:'btn btn-default', - function:function(item){ return '';}, - title:'Create sub group', - link:function(item) - { - return '/?inventory/'+item.id+'/new-group' - }, - }, - { - class:'btn btn-default', - function:function(item){ return '';}, - title:'Create sub host', - link:function(item) - { - return '/?inventory/'+item.id+'/new-host' - }, - } - ] -} - -pmInventories.model.page_new = { - title: "New inventory", - short_title: "New inventory", - fileds:[ - [ - { - filed: new filedsLib.filed.text(), - title:'Name', - name:'name', - placeholder:'Enter inventory name', - validator:function(value){ - return filedsLib.validator.notEmpty(value, 'Name') - }, - fast_validator:filedsLib.validator.notEmpty - }, - ] - ], - sections:[ - function(section){ - return jsonEditor.editor({}, {block:this.model.name}); - } - ], - onBeforeSave:function(data) - { - data.vars = jsonEditor.jsonEditorGetValues() - return data; - }, - onCreate:function(data, status, xhr, callOpt) - { - var def = new $.Deferred(); - $.notify("Inventory created", "success"); - - if(callOpt.parent_item) - { - if(callOpt.parent_type == 'project') - { - $.when(pmProjects.addSubInventories(callOpt.parent_item, [data.id])).always(function(){ - $.when(spajs.open({ menuId:"project/"+callOpt.parent_item})).always(function(){ - def.resolve() - }) - }) - } - } - else - { - $.when(spajs.open({ menuId: this.model.page_name + "/"+data.id})).always(function(){ - def.resolve() - }) - } - return def.promise(); - } -} - -pmInventories.model.page_item = { - buttons:[ - { - class:'btn btn-primary', - function:function(item_id){ return 'spajs.showLoader('+this.model.className+'.updateItem('+item_id+')); return false;'}, - title:'Save', - link:function(){ return '#'}, - }, - { - class:'btn btn-primary', - function:function(item_id){ return 'return spajs.openURL(this.href);'}, - title:'History', - link:function(item_id){ return polemarch.opt.host + '/?inventory/' + item_id + '/history'}, - }, - { - class:'btn btn-default copy-btn', - function:function(item_id){ return 'spajs.showLoader('+this.model.className+'.copyAndEdit('+item_id+')); return false;'}, - title:'', - link:function(){ return '#'}, - help:'Copy' - }, - { - class:'btn btn-danger danger-right', - function:function(item_id){ return 'spajs.showLoader('+this.model.className+'.deleteItem('+item_id+')); return false;'}, - title:' ', - link:function(){ return '#'}, - }, - ], - sections:[ - function(section, item_id){ - return jsonEditor.editor(this.model.items[item_id].vars, {block:this.model.name}); - }, - function(section, item_id){ - return spajs.just.render("inventories_sub_items", {item_id:item_id}) - } - ], - title: function(item_id){ - return "Inventory "+this.model.items[item_id].justText('name') - }, - short_title: function(item_id){ - return "Inventory "+this.model.items[item_id].justText('name', function(v){return v.slice(0, 20)}) - }, - fileds:[ - [ - { - filed: new filedsLib.filed.text(), - title:'Name', - name:'name', - placeholder:'Enter inventory name', - validator:function(value){ - return filedsLib.validator.notEmpty(value, 'Name') - }, - fast_validator:function(value){ return value != '' && value} - }, - ] - ], - onUpdate:function(result) - { - return true; - }, - onBeforeSave:function(data, item_id) - { - data.vars = jsonEditor.jsonEditorGetValues() - return data; - }, -} - -/** - * Показывает форму со списком всех групп. - * @return $.Deferred - */ -pmInventories.showAddSubGroupsForm = function(item_id, holder) -{ - if(!item_id) - { - throw "Error in pmInventories.showAddSubGroupsForm with item_id = `" + item_id + "`" - } - - return $.when(pmGroups.loadAllItems()).done(function(){ - $("#add_existing_item_to_inventory").remove() - $(".content").appendTpl(spajs.just.render('add_existing_groups_to_inventory', {item_id:item_id})) - $("#polemarch-model-items-select").select2({ width: '100%' }); - }).fail(function(){ - - }).promise() -} - -/** - * Показывает форму со списком всех хостов. - * @return $.Deferred - */ -pmInventories.showAddSubHostsForm = function(item_id, holder) -{ - if(!item_id) - { - throw "Error in pmInventories.showAddSubHostsForm with item_id = `" + item_id + "`" - } - - return $.when(pmHosts.loadAllItems()).done(function(){ - $("#add_existing_item_to_inventory").remove() - $(".content").appendTpl(spajs.just.render('add_existing_hosts_to_inventory', {item_id:item_id})) - $("#polemarch-model-items-select").select2({ width: '100%' }); - }).fail(function(){ - - }).promise() -} - -/** - * Проверяет принадлежит ли host_id к группе item_id - * @param {Integer} item_id - * @param {Integer} host_id - * @returns {Boolean} - */ -pmInventories.hasHosts = function(item_id, host_id) -{ - if(!item_id) - { - throw "Error in pmInventories.hasHosts with item_id = `" + item_id + "`" - } - - if(pmInventories.model.items[item_id]) - { - for(var i in pmInventories.model.items[item_id].hosts) - { - if(pmInventories.model.items[item_id].hosts[i].id == host_id) - { - return true; - } - } - } - return false; -} - -/** - * Проверяет принадлежит ли host_id к группе item_id - * @param {Integer} item_id - * @param {Integer} host_id - * @returns {Boolean} - */ -pmInventories.hasGroups = function(item_id, group_id) -{ - if(!item_id) - { - throw "Error in pmInventories.hasGroups with item_id = `" + item_id + "`" - } - - if(pmInventories.model.items[item_id]) - { - for(var i in pmInventories.model.items[item_id].groups) - { - if(pmInventories.model.items[item_id].groups[i].id == group_id) - { - return true; - } - } - } - return false; -} - - -/** - * @return $.Deferred - */ -pmInventories.setSubGroups = function(item_id, groups_ids) -{ - if(!item_id) - { - throw "Error in pmInventories.setSubGroups with item_id = `" + item_id + "`" - } - - if(!groups_ids) - { - groups_ids = [] - } - - return spajs.ajax.Call({ - url: "/api/v1/inventories/"+item_id+"/groups/", - type: "PUT", - contentType:'application/json', - data:JSON.stringify(groups_ids), - success: function(data) - { - if(pmInventories.model.items[item_id]) - { - pmInventories.model.items[item_id].groups = [] - for(var i in groups_ids) - { - pmInventories.model.items[item_id].groups.push(pmGroups.model.items[groups_ids[i]]) - } - } - }, - error:function(e) - { - console.warn("group "+item_id+" update error - " + JSON.stringify(e)); - polemarch.showErrors(e.responseJSON) - } - }); -} - -/** - * @return $.Deferred - */ -pmInventories.setSubHosts = function(item_id, hosts_ids) -{ - if(!hosts_ids) - { - hosts_ids = [] - } - - if(!item_id) - { - throw "Error in pmInventories.setSubHosts with item_id = `" + item_id + "`" - } - - - return spajs.ajax.Call({ - url: "/api/v1/inventories/"+item_id+"/hosts/", - type: "PUT", - contentType:'application/json', - data:JSON.stringify(hosts_ids), - success: function(data) - { - if(pmInventories.model.items[item_id]) - { - pmInventories.model.items[item_id].hosts = [] - for(var i in hosts_ids) - { - pmInventories.model.items[item_id].hosts.push(pmHosts.model.items[hosts_ids[i]]) - } - } - }, - error:function(e) - { - console.warn("group "+item_id+" update error - " + JSON.stringify(e)); - polemarch.showErrors(e.responseJSON) - } - }); -} - -/** - * @return $.Deferred - */ -pmInventories.addSubGroups = function(item_id, groups_ids) -{ - if(!item_id) - { - throw "Error in pmInventories.addSubGroups with item_id = `" + item_id + "`" - } - - if(!groups_ids) - { - groups_ids = [] - } - - var def = new $.Deferred(); - spajs.ajax.Call({ - url: "/api/v1/inventories/"+item_id+"/groups/", - type: "POST", - contentType:'application/json', - data:JSON.stringify(groups_ids), - success: function(data) - { - if(data.not_found > 0) - { - $.notify("Item not found", "error"); - def.reject({text:"Item not found", status:404}) - return; - } - - if(pmInventories.model.items[item_id]) - { - if(!pmInventories.model.items[item_id].groups) - { - pmInventories.model.items[item_id].groups = [] - } - - for(var i in groups_ids) - { - pmInventories.model.items[item_id].groups.push(pmGroups.model.items[groups_ids[i]]) - } - } - - $.notify("Save", "success"); - def.resolve() - }, - error:function(e) - { - console.warn("group "+item_id+" update error - " + JSON.stringify(e)); - polemarch.showErrors(e.responseJSON) - def.reject(e) - } - }); - return def.promise(); -} - -/** - * @return $.Deferred - */ -pmInventories.addSubHosts = function(item_id, hosts_ids) -{ - if(!item_id) - { - throw "Error in pmInventories.addSubHosts with item_id = `" + item_id + "`" - } - - var def = new $.Deferred(); - if(!hosts_ids || hosts_ids.length == 0) - { - def.resolve() - return def.promise(); - } - - spajs.ajax.Call({ - url: "/api/v1/inventories/"+item_id+"/hosts/", - type: "POST", - contentType:'application/json', - data:JSON.stringify(hosts_ids), - success: function(data) - { - if(data.not_found > 0) - { - $.notify("Item not found", "error"); - def.reject({text:"Item not found", status:404}) - return; - } - - if(pmInventories.model.items[item_id]) - { - if(!pmInventories.model.items[item_id].hosts) - { - pmInventories.model.items[item_id].hosts = [] - } - - for(var i in hosts_ids) - { - pmInventories.model.items[item_id].hosts.push(pmHosts.model.items[hosts_ids[i]]) - } - } - - $.notify("Save", "success"); - def.resolve() - }, - error:function(e) - { - console.warn("group "+item_id+" update error - " + JSON.stringify(e)); - polemarch.showErrors(e.responseJSON) - def.reject(e) - } - }); - return def.promise(); -} - -pmInventories.validateGroupName = function(name) -{ - if(!name) - { - return false; - } - - if(/^[a-zA-Z0-9\-\._]*$/.test(name.toLowerCase())) - { - return true; - } - - return false; -} - - - - -/** - * Для ввода инвентория - * @type Object - */ -pmInventories.filed.inventoriesAutocomplete = inheritance(filedsLib.filed.simpleText) -pmInventories.filed.inventoriesAutocomplete.type = 'inventoriesAutocomplete' -pmInventories.filed.inventoriesAutocomplete.getValue = function(pmObj, filed) -{ - var inventory = $("#inventories-autocomplete").val() - if($("#inventory-source").val() != 'db') - { - inventory = $("#inventories-file").val() - if(!/^\.\//.test(inventory)) - { - inventory = trim("./"+inventory) - } - } - - - return inventory; -} - -/** - * Функция для рендера поля - * @type Object - */ -pmInventories.filed.inventoriesAutocomplete.render = function(pmObj, filed, item_id, opt) -{ - var html = spajs.just.render('filed_type_'+this.type, {pmObj:pmObj, filed:filed, item_id:item_id, filedObj:this, opt:opt}) - return spajs.just.onInsert(html, function() - { - // @FixMe требует чтоб были загружены все инвентории pmInventories.loadAllItems() - $("#inventories-autocomplete").select2({ width: '100%' }); - - if(filed.onchange && item_id) - { - filed.onchange({value:filed.getFiledValue.apply(pmObj, [item_id])}) - } - else if(filed.onchange) - { - if(pmInventories.model.itemslist.results[0]) - { - filed.onchange({value:pmInventories.model.itemslist.results[0].id}) - } - else - { - filed.onchange({value:""}) - } - } - }); -} - - - tabSignal.connect("polemarch.start", function() - { - // inventories - spajs.addMenu({ - id:"inventories", - urlregexp:[/^inventories$/, /^inventory$/, /^inventories\/search\/?$/, /^inventories\/page\/([0-9]+)$/], - onOpen:function(holder, menuInfo, data){return pmInventories.showList(holder, menuInfo, data);} - }) - - spajs.addMenu({ - id:"inventories-import", - urlregexp:[/^inventories\/import$/], - onOpen:function(holder, menuInfo, data){return pmInventories.showImportPage(holder, menuInfo, data);} - }) - - - spajs.addMenu({ - id:"inventories-search", - urlregexp:[/^inventories\/search\/([A-z0-9 %\-.:,=]+)$/], - onOpen:function(holder, menuInfo, data){return pmInventories.showSearchResults(holder, menuInfo, data);} - }) - - spajs.addMenu({ - id:"inventory", - urlregexp:[/^inventory\/([0-9]+)$/, /^inventories\/([0-9]+)$/], - onOpen:function(holder, menuInfo, data){return pmInventories.showItem(holder, menuInfo, data);} - }) - - spajs.addMenu({ - id:"inventory-history", - urlregexp:[/^inventory\/([0-9]+)\/history$/, /^inventory\/([0-9]+)\/history\/page\/([0-9]+)$/], - onOpen:function(holder, menuInfo, data){return pmHistory.showListInInventory(holder, menuInfo, data);} - }) - - spajs.addMenu({ - id:"newInventory", - urlregexp:[/^new-inventory$/, /^([A-z0-9_]+)\/([0-9]+)\/new-inventory$/], - onOpen:function(holder, menuInfo, data){return pmInventories.showNewItemPage(holder, menuInfo, data);} - }) - }) \ No newline at end of file + + +var pmInventories = inheritance(pmItems) +pmInventories.model.name = "inventories" +pmInventories.model.page_name = "inventory" +pmInventories.model.bulk_name = "inventory" +pmInventories.model.className = "pmInventories" + +/** + * Параметры из секции *:vars + * Строка где после первого `=` всё остальное значение. + */ +pmInventories.parseMonoVarsLine = function(index, line) +{ + var vars = {} + var param = /^([^=]+)="(.*)"$/.exec(line) + + if(param) + { + vars[param[1]] = param[2] + } + else + { + param = /^([^=]+)=(.*)$/.exec(line) + if(param) + { + vars[param[1]] = param[2] + } + else + { + throw "Error in line "+index+" invalid varibles string ("+line+")" + } + } + return vars; +} + +/** + * Параметры хоста + * Строка где может быть несколько параметров ключ=значение через пробел + */ +pmInventories.parseVarsLine = function(index, line) +{ + var vars = {} + do{ + if(line.length == 0) + { + break; + } + + var params = /^([^=]+)=["'](.*?)["'] +[^=]+=/.exec(line) + if(params) + { + params[1] = trim(params[1]) + vars[params[1]] = stripslashes(params[2]) + line = trim(line.slice(params[1].length + params[2].length + 3)) + continue; + } + + params = /^([^=]+)=([^ ]*) +[^=]+=/.exec(line) + if(params) + { + params[1] = trim(params[1]) + vars[params[1]] = stripslashes(params[2]) + line = trim(line.slice(params[1].length + params[2].length + 1)) + continue; + } + + params = /^([^=]+)=["'](.*?)["'] *$/.exec(line) + if(params) + { + params[1] = trim(params[1]) + vars[params[1]] = stripslashes(params[2]) + break; + } + + params = /^([^=]+)=([^ ]*) *$/.exec(line) + if(params) + { + params[1] = trim(params[1]) + vars[params[1]] = stripslashes(params[2]) + line = trim(line.slice(params[1].length + params[2].length + 1)) + continue; + } + else + { + throw "Error in line "+index+" invalid varibles string ("+line+")" + } + }while(true) + return vars; +} + +/** + * Хост и параметры + * Строка где идёт сначала имя хоста а потом его параметры в `parseVarsLine` + */ +pmInventories.parseHostLine = function(index, line, section, inventory) +{ + var params = /^([^ ]+)/.exec(line) + if(!params) + { + throw "Error in line "+index+" ("+line+")" + } + + var name = "" + var type = "" + if(pmItems.validateHostName(params[1])) + { + name = params[1] + type = "HOST" + } + else if(pmItems.validateRangeName(params[1])) + { + name = params[1] + type = "RANGE" + } + else + { + throw "Error in line "+index+" invalid host or range name ("+params[1]+")" + } + + line = trim(line.slice(name.length)) + + var host = { + name:name, + type:type, + vars:pmInventories.parseVarsLine(index, line) + } + + if(section !== "_hosts") + { + pmInventories.addGroupIfNotExists(inventory, section) + inventory.groups[section].hosts.push(host) + } + else + { + inventory.hosts.push(host) + } +} + +/** + * Парсит строку файла инвентория + * @param {integer} index + * @param {string} line + * @param {string} section + * @param {Object} inventory + * @returns {Boolean} + */ +pmInventories.parseLine = function(index, line, section, inventory) +{ + line = trim(line); + + if(section == "_hosts") + { + pmInventories.parseHostLine(index, line, section, inventory) + return true; + } + + if(section == "all:vars") + { + var vars = pmInventories.parseMonoVarsLine(index, line) + inventory.vars = Object.assign(inventory.vars, vars) + return true; + } + + if(/:vars$/.test(section)) + { + section = section.substring(0, section.length - ":vars".length) + + pmInventories.addGroupIfNotExists(inventory, section) + inventory.groups[section].vars = Object.assign(inventory.groups[section].vars, pmInventories.parseMonoVarsLine(index, line)) + return true; + } + + if(/:children$/.test(section)) + { + /** + * Параметры из секции *:children + * Строка где просто имя группы + */ + section = section.substring(0, section.length - ":children".length) + + pmInventories.addGroupIfNotExists(inventory, section) + inventory.groups[section].children = true + inventory.groups[section].groups.push(line) + pmInventories.addGroupIfNotExists(inventory, line) + return true; + } + + pmInventories.parseHostLine(index, line, section, inventory) + return false; +} + +/** + * Добавляет группу в инвенторий если её ещё нет + * @param {Object} inventory + * @param {string} group_name имя группы + * @returns {Boolean} true если группа добавлена. + */ +pmInventories.addGroupIfNotExists = function(inventory, group_name) +{ + if(!inventory.groups[group_name]) + { + inventory.groups[group_name] = { + vars:{}, + groups:[], + hosts:[], + } + + return true; + } + + return false; +} + + +/** + * Парсит файла инвентория + * @param {string} text текст файла инвентория + * @returns {Object} + */ +pmInventories.parseFromText = function(text) +{ + var lines = text.split(/\n/g) + + var cSection = "_hosts"; + var inventory = { + hosts:[], + groups:{}, + vars:{}, + name:new Date().toString() + } + + for(var i in lines) + { + var line = lines[i].replace(/^ */g, "") + + if(/^\s*[#;]\s+inventory name: (.*)/ig.test(line)) + { + var name = /^\s*[#;]\s+inventory name: (.*)/ig.exec(line) + inventory.name = name[1] + continue; + } + + if(/^\s*$/ig.test(line)) + { + continue; + } + if(/^\s*[#;]/ig.test(line)) + { + continue; + } + + console.log(i+":\t" + line) + + if(/^\[([A-z0-9\.:\-]+:vars)\]/ig.test(line)) + { + var res = /^\[([A-z0-9\.:\-]+)\]/ig.exec(line) + cSection = res[1] + + var group_name = cSection.substring(0, cSection.length - ":vars".length) + if(group_name != "all") + { + pmInventories.addGroupIfNotExists(inventory, group_name) + } + console.info("setSection:vars ", cSection) + continue; + } + if(/^\[([A-z0-9\.:\-]+:children)\]/ig.test(line)) + { + var res = /^\[([A-z0-9\.:\-]+)\]/ig.exec(line) + cSection = res[1] + pmInventories.addGroupIfNotExists(inventory, cSection.substring(0, cSection.length - ":children".length)) + console.info("setSection:children ", cSection) + continue; + } + if(/^\[([A-z0-9\.:\-]+)\]/ig.test(line)) + { + var res = /^\[([A-z0-9\.:\-]+)\]/ig.exec(line) + cSection = res[1] + pmInventories.addGroupIfNotExists(inventory, cSection) + console.info("setSection ", cSection) + continue; + } + + pmInventories.parseLine(i, line, cSection, inventory) + } + + pmInventories.addHierarchyDataToInventoryGroups(inventory) + console.log("\n\ninventory", inventory) + return inventory; +} + + + +/** + * Формирует вспомагательную информацию в объекте инвентория о вложенности групп друг в друга. + * @param {Object} inventory Инвенторий (Обязательный) + * @param {string} group_name (не обязательный) + * @param {integer} level (не обязательный) + * @param {Array} parents (не обязательный) + */ +pmInventories.addHierarchyDataToInventoryGroups = function(inventory, group_name, level, parents) +{ + if(!level) + { + level = 0 + } + + if(parents === undefined) + { + parents = [] + } + + if(group_name === undefined || group_name == 'all') + { + for(var i in inventory.groups) + { + delete inventory.groups[i]['dataLevel'] + } + + for(var i in inventory.groups) + { + pmInventories.addHierarchyDataToInventoryGroups(inventory, i, 1, ['all']) + } + + return; + } + + + if(inventory.groups[group_name].dataLevel && inventory.groups[group_name].dataLevel.level >= level ) + { + return; + } + + parents.push(group_name) + inventory.groups[group_name].dataLevel = { + level:level, + parents:parents, + } + + for(var i in inventory.groups[group_name].groups) + { + var hasError = false; + for(var j in inventory.groups[group_name].dataLevel.parents) + { + var val = inventory.groups[group_name].dataLevel.parents[j] + if(val == inventory.groups[group_name].groups[i]) + { + inventory.groups[group_name].dataLevel.error = "Group `"+val+"` is recursive include into group `"+inventory.groups[group_name].groups[i]+"`"; + console.warn(inventory.groups[group_name].dataLevel.error) + hasError = true + break; + } + } + + if(hasError) + { + continue; + } + + pmInventories.addHierarchyDataToInventoryGroups(inventory, inventory.groups[group_name].groups[i], level+1, parents.slice()) + } + + return; +} + +// ansible_ssh_private_key_file - запрашивать значение этого параметра. + +pmInventories.importFromFile = function(files_event) +{ + var def = new $.Deferred(); + this.model.files = files_event + this.model.importedInventories = {} + var thisObj = this; + for(var i=0; i + $.notify("Error in field ansible_ssh_private_key_file invalid value", "error"); + pmInventories.showInventoryVarsModal(); + def2.reject() + return def2.promise(); + } + + for(var i in inventory.hosts) + { + var val = inventory.hosts[i] + if(val.vars.ansible_ssh_private_key_file !== undefined && !/-----BEGIN RSA PRIVATE KEY-----/.test(val.vars.ansible_ssh_private_key_file)) + { + // + $.notify("Error in field ansible_ssh_private_key_file invalid value", "error"); + pmInventories.showHostVarsModal({group:'all', name:val.name}); + def2.reject() + return def2.promise(); + } + } + + for(var i in inventory.groups) + { + var val = inventory.groups[i] + if(val.vars.ansible_ssh_private_key_file !== undefined && !/-----BEGIN RSA PRIVATE KEY-----/.test(val.vars.ansible_ssh_private_key_file)) + { + // + $.notify("Error in field ansible_ssh_private_key_file invalid value", "error"); + pmInventories.showGroupVarsModal({name:i}); + def2.reject() + return def2.promise(); + } + + for(var j in val.hosts) + { + var hval = val.hosts[j] + if(hval.vars.ansible_ssh_private_key_file !== undefined && !/-----BEGIN RSA PRIVATE KEY-----/.test(hval.vars.ansible_ssh_private_key_file)) + { + // + $.notify("Error in field ansible_ssh_private_key_file invalid value", "error"); + pmInventories.showHostVarsModal({group:i, name:hval.name}); + def2.reject() + return def2.promise(); + } + } + } + + var def = new $.Deferred(); + + if($("#inventory_name").val() != "") + { + inventory.name = $("#inventory_name").val(); + } + + if(!inventory.name) + { + // inventory.name = "new imported inventory" + + $.notify("Error in field inventory name", "error"); + def2.reject({text:"Error in field inventory name"}) + return def2.promise(); + } + + var inventoryObject = { + name:inventory.name, + vars:inventory.vars + } + + var deleteBulk = [] + $.when(pmInventories.importItem(inventoryObject)).done(function(inventory_id) + { + deleteBulk.push({ + type:"del", + item:'inventory', + pk:inventory_id + }) + var bulkdata = [] + // Сбор групп и вложенных в них хостов + for(var i in inventory.groups) + { + var val = inventory.groups[i] + + bulkdata.push({ + type:"add", + item:'group', + data:{ + name:i, + children:val.children, + vars:val.vars + } + }) + + for(var j in val.hosts) + { + var hval = val.hosts[j] + bulkdata.push({ + type:"add", + item:'host', + data:{ + name:hval.name, + type:hval.type, + vars:hval.vars + } + }) + } + } + + // Сбор хостов вложенных к инвенторию + var bulkHosts = [] + for(var i in inventory.hosts) + { + var val = inventory.hosts[i] + bulkHosts.push({ + type:"add", + item:'host', + data:{ + name:val.name, + type:val.type, + vars:val.vars + } + }) + } + + // Добавление хостов вложенных к инвенторию + spajs.ajax.Call({ + url: "/api/v1/_bulk/", + type: "POST", + contentType:'application/json', + data:JSON.stringify(bulkHosts), + success: function(data) + { + var hasError = false; + var hosts_ids = [] + for(var i in data) + { + var val = data[i] + if(val.status != 201) + { + $.notify("Error "+val.status, "error"); + hasError = true; + continue; + } + hosts_ids.push(val.data.id) + deleteBulk.push({ + type:"del", + item:'host', + pk:val.data.id + }) + } + + if(hasError) + { + // По меньшей мере в одной операции была ошибка вставки. + // Инвенторий импортирован не полностью + def.reject(deleteBulk); + return; + } + + $.when(pmInventories.addSubHosts(inventory_id, hosts_ids)).done(function() + { + // Добавление групп и вложенных в них хостов + spajs.ajax.Call({ + url: "/api/v1/_bulk/", + type: "POST", + contentType:'application/json', + data:JSON.stringify(bulkdata), + success: function(data) + { + var igroups_ids = [] + var bulk_update = [] + var hasError = false; + for(var i in data) + { + deleteBulk.push({ + type:"del", + item:data.item, + pk:data[i].data.id + }) + + var val = data[i] + if(val.status != 201) + { + $.notify("Error "+val.status, "error"); + hasError = true; + } + + if(val.data.children !== undefined ) + { + igroups_ids.push(val.data.id) + + // Это группа + if(val.data.children) + { + if(inventory.groups[val.data.name].groups.length) + { + // Добавление подгрупп + var groups_ids = [] + for(var j in inventory.groups[val.data.name].groups) + { + // найти id группы и прекрепить. + for(var k in data) + { + if(data[k].data.children !== undefined && data[k].data.name == inventory.groups[val.data.name].groups[j]) + { + groups_ids.push(data[k].data.id) + break; + } + } + } + bulk_update.push({ + type: "mod", + item:'group', + method: "PUT", + data_type: 'groups', + pk:val.data.id, + data:groups_ids + }) + } + } + else + { + if(inventory.groups[val.data.name].hosts.length) + { + // Добавление хостов + var hosts_ids = [] + for(var j in inventory.groups[val.data.name].hosts) + { + // найти id группы и прекрепить. + for(var k in data) + { + if(data[k].data.children === undefined && data[k].data.name == inventory.groups[val.data.name].hosts[j].name) + { + hosts_ids.push(data[k].data.id) + break; + } + } + } + bulk_update.push({ + type: "mod", + item:'group', + method: "PUT", + data_type: 'hosts', + pk:val.data.id, + data:hosts_ids + }) + } + } + } + else + { + // Это хост + } + } + + if(hasError) + { + // По меньшей мере в одной операции была ошибка вставки. + // Инвенторий импортирован не полностью + def.reject(deleteBulk); + return; + } + + $.when(pmInventories.addSubGroups(inventory_id, igroups_ids)).done(function() + { + if(bulk_update.length) + { + spajs.ajax.Call({ + url: "/api/v1/_bulk/", + type: "POST", + contentType:'application/json', + data:JSON.stringify(bulk_update), + success: function(data) + { + var hasError = false; + for(var i in data) + { + var val = data[i] + if(val.status != 200) + { + $.notify("Error "+val.status, "error"); + hasError = true; + continue; + } + } + + if(hasError) + { + // По меньшей мере в одной операции была ошибка обновления. + // Инвенторий импортирован не полностью + def.reject(deleteBulk); + return; + } + + def.resolve(inventory_id); + }, + error:function(e) + { + console.warn(e) + polemarch.showErrors(e) + def.reject(deleteBulk); + } + }) + } + else + { + def.resolve(inventory_id); + } + }).fail(function(e){ + console.warn(e) + polemarch.showErrors(e) + def.reject(deleteBulk); + }) + }, + error:function(e) + { + console.warn(e) + polemarch.showErrors(e) + def.reject(deleteBulk); + } + }); + }).fail(function(e){ + console.warn(e) + polemarch.showErrors(e) + def.reject(deleteBulk); + }) + }, + error:function(e) + { + console.warn(e) + polemarch.showErrors(e) + def.reject(deleteBulk); + } + }) + }).fail(function(e) + { + console.warn(e) + polemarch.showErrors(e) + def.reject(deleteBulk); + }) + + $.when(def).done(function(inventory_id) + { + def2.resolve(inventory_id) + }).fail(function(delete_bulk) + { + $.when(spajs.ajax.Call({ + url: "/api/v1/_bulk/", + type: "POST", + contentType:'application/json', + data:JSON.stringify(delete_bulk), + })).always(function(){ + def2.reject() + }) + }) + + return def2.promise(); +} + + +pmInventories.showImportPage = function(holder, menuInfo, data) +{ + var def = new $.Deferred(); + + var text = spajs.just.render(this.model.name+'_import_page', {}) + $(holder).insertTpl(text) + + def.resolve() + return def.promise(); +} + +pmInventories.renderImportedInventory = function(imported) +{ + if(!imported || !imported.inventory) + { + return "" + } + + var text = spajs.just.render(this.model.name+'_imported_inventory', {inventory:imported.inventory, text:imported.text}) + return text; +} + +pmInventories.copyItem = function(item_id) +{ + var def = new $.Deferred(); + var thisObj = this; + + $.when(this.loadItem(item_id)).done(function() + { + var data = thisObj.model.items[item_id]; + delete data.id; + data.name = "copy from " + data.name + + $.when(encryptedCopyModal.replace(data)).done(function(data) + { + spajs.ajax.Call({ + url: "/api/v1/"+thisObj.model.name+"/", + type: "POST", + contentType:'application/json', + data: JSON.stringify(data), + success: function(newItem) + { + thisObj.model.items[newItem.id] = newItem + + var groups = [] + for(var i in data.groups) + { + groups.push(data.groups[i].id) + } + + var hosts = [] + for(var i in data.hosts) + { + hosts.push(data.hosts[i].id) + } + + $.when(thisObj.setSubGroups(newItem.id, groups), thisObj.setSubHosts(newItem.id, hosts)).always(function(){ + def.resolve(newItem.id) + }) + }, + error:function(e) + { + def.reject(e) + } + }); + }).fail(function(e) + { + def.reject(e) + }) + }).fail(function(e) + { + def.reject(e) + }) + + + return def.promise(); +} + + + + +pmInventories.model.page_list = { + title: "Inventories", + short_title: "Inventories", + buttons:[ + { + class:'btn btn-primary', + function:function(){ return "spajs.open({ menuId:'new-"+this.model.page_name+"'}); return false;"}, + title:'Create', + link:function(){ return '/?new-'+this.model.page_name}, + }, + { + tpl:function(){ + return spajs.just.render('inventories_btn_openImportPageAndImportFiles', {}) + }, + }, + ], + fileds:[ + { + title:'Name', + name:'name', + }, + ], + actions:[ + { + class:'btn btn-danger', + function:function(item){ return 'spajs.showLoader('+this.model.className+'.deleteItem('+item.id+')); return false;'}, + title:'Delete', + link:function(){ return '#'} + }, + { + class:'btn btn-default', + function:function(item){ return '';}, + title:'Create sub group', + link:function(item) + { + return '/?inventory/'+item.id+'/new-group' + }, + }, + { + class:'btn btn-default', + function:function(item){ return '';}, + title:'Create sub host', + link:function(item) + { + return '/?inventory/'+item.id+'/new-host' + }, + } + ] +} + +pmInventories.model.page_new = { + title: "New inventory", + short_title: "New inventory", + fileds:[ + [ + { + filed: new filedsLib.filed.text(), + title:'Name', + name:'name', + placeholder:'Enter inventory name', + validator:function(value){ + return filedsLib.validator.notEmpty(value, 'Name') + }, + fast_validator:filedsLib.validator.notEmpty + }, + ] + ], + sections:[ + function(section){ + return jsonEditor.editor({}, {block:this.model.name}); + } + ], + onBeforeSave:function(data) + { + data.vars = jsonEditor.jsonEditorGetValues() + return data; + }, + onCreate:function(data, status, xhr, callOpt) + { + var def = new $.Deferred(); + $.notify("Inventory created", "success"); + + if(callOpt.parent_item) + { + if(callOpt.parent_type == 'project') + { + $.when(pmProjects.addSubInventories(callOpt.parent_item, [data.id])).always(function(){ + $.when(spajs.open({ menuId:"project/"+callOpt.parent_item})).always(function(){ + def.resolve() + }) + }) + } + } + else + { + $.when(spajs.open({ menuId: this.model.page_name + "/"+data.id})).always(function(){ + def.resolve() + }) + } + return def.promise(); + } +} + +pmInventories.model.page_item = { + buttons:[ + { + class:'btn btn-primary', + function:function(item_id){ return 'spajs.showLoader('+this.model.className+'.updateItem('+item_id+')); return false;'}, + title:'Save', + link:function(){ return '#'}, + }, + { + class:'btn btn-primary', + function:function(item_id){ return 'return spajs.openURL(this.href);'}, + title:'History', + link:function(item_id){ return polemarch.opt.host + '/?inventory/' + item_id + '/history'}, + }, + { + class:'btn btn-default copy-btn', + function:function(item_id){ return 'spajs.showLoader('+this.model.className+'.copyAndEdit('+item_id+')); return false;'}, + title:'', + link:function(){ return '#'}, + help:'Copy' + }, + { + class:'btn btn-danger danger-right', + function:function(item_id){ return 'spajs.showLoader('+this.model.className+'.deleteItem('+item_id+')); return false;'}, + title:' ', + link:function(){ return '#'}, + }, + ], + sections:[ + function(section, item_id){ + return jsonEditor.editor(this.model.items[item_id].vars, {block:this.model.name}); + }, + function(section, item_id){ + return spajs.just.render("inventories_sub_items", {item_id:item_id}) + } + ], + title: function(item_id){ + return "Inventory "+this.model.items[item_id].justText('name') + }, + short_title: function(item_id){ + return "Inventory "+this.model.items[item_id].justText('name', function(v){return v.slice(0, 20)}) + }, + fileds:[ + [ + { + filed: new filedsLib.filed.text(), + title:'Name', + name:'name', + placeholder:'Enter inventory name', + validator:function(value){ + return filedsLib.validator.notEmpty(value, 'Name') + }, + fast_validator:function(value){ return value != '' && value} + }, + ] + ], + onUpdate:function(result) + { + return true; + }, + onBeforeSave:function(data, item_id) + { + data.vars = jsonEditor.jsonEditorGetValues() + return data; + }, +} + +/** + * Показывает форму со списком всех групп. + * @return $.Deferred + */ +pmInventories.showAddSubGroupsForm = function(item_id, holder) +{ + if(!item_id) + { + throw "Error in pmInventories.showAddSubGroupsForm with item_id = `" + item_id + "`" + } + + return $.when(pmGroups.loadAllItems()).done(function(){ + $("#add_existing_item_to_inventory").remove() + $(".content").appendTpl(spajs.just.render('add_existing_groups_to_inventory', {item_id:item_id})) + var scroll_el = "#add_existing_item_to_inventory"; + if ($(scroll_el).length != 0) { + $('html, body').animate({ scrollTop: $(scroll_el).offset().top }, 1000); + } + $("#polemarch-model-items-select").select2({ width: '100%' }); + }).fail(function(){ + + }).promise() +} + +/** + * Показывает форму со списком всех хостов. + * @return $.Deferred + */ +pmInventories.showAddSubHostsForm = function(item_id, holder) +{ + if(!item_id) + { + throw "Error in pmInventories.showAddSubHostsForm with item_id = `" + item_id + "`" + } + + return $.when(pmHosts.loadAllItems()).done(function(){ + $("#add_existing_item_to_inventory").remove() + $(".content").appendTpl(spajs.just.render('add_existing_hosts_to_inventory', {item_id:item_id})) + var scroll_el = "#add_existing_item_to_inventory"; + if ($(scroll_el).length != 0) { + $('html, body').animate({ scrollTop: $(scroll_el).offset().top }, 1000); + } + $("#polemarch-model-items-select").select2({ width: '100%' }); + }).fail(function(){ + + }).promise() +} + +/** + * Проверяет принадлежит ли host_id к группе item_id + * @param {Integer} item_id + * @param {Integer} host_id + * @returns {Boolean} + */ +pmInventories.hasHosts = function(item_id, host_id) +{ + if(!item_id) + { + throw "Error in pmInventories.hasHosts with item_id = `" + item_id + "`" + } + + if(pmInventories.model.items[item_id]) + { + for(var i in pmInventories.model.items[item_id].hosts) + { + if(pmInventories.model.items[item_id].hosts[i].id == host_id) + { + return true; + } + } + } + return false; +} + +/** + * Проверяет принадлежит ли host_id к группе item_id + * @param {Integer} item_id + * @param {Integer} host_id + * @returns {Boolean} + */ +pmInventories.hasGroups = function(item_id, group_id) +{ + if(!item_id) + { + throw "Error in pmInventories.hasGroups with item_id = `" + item_id + "`" + } + + if(pmInventories.model.items[item_id]) + { + for(var i in pmInventories.model.items[item_id].groups) + { + if(pmInventories.model.items[item_id].groups[i].id == group_id) + { + return true; + } + } + } + return false; +} + + +/** + * @return $.Deferred + */ +pmInventories.setSubGroups = function(item_id, groups_ids) +{ + var thisObj=this; + if(!item_id) + { + throw "Error in pmInventories.setSubGroups with item_id = `" + item_id + "`" + } + + if(!groups_ids) + { + groups_ids = [] + } + else + { + for(var i in groups_ids) + { + groups_ids[i]=+groups_ids[i]; + } + } + + return spajs.ajax.Call({ + url: "/api/v1/inventories/"+item_id+"/groups/", + type: "PUT", + contentType:'application/json', + data:JSON.stringify(groups_ids), + success: function(data) + { + pmItems.checkSubItemsAndAdd(thisObj, pmGroups, data, item_id, "groups", groups_ids); + }, + error:function(e) + { + console.warn("group "+item_id+" update error - " + JSON.stringify(e)); + polemarch.showErrors(e.responseJSON) + } + }); +} + +/** + * @return $.Deferred + */ +pmInventories.setSubHosts = function(item_id, hosts_ids) +{ + var thisObj=this; + if(!hosts_ids) + { + hosts_ids = [] + } + else { + for(var i in hosts_ids) + { + hosts_ids[i]=+hosts_ids[i]; + } + } + + if(!item_id) + { + throw "Error in pmInventories.setSubHosts with item_id = `" + item_id + "`" + } + + return spajs.ajax.Call({ + url: "/api/v1/inventories/"+item_id+"/hosts/", + type: "PUT", + contentType:'application/json', + data:JSON.stringify(hosts_ids), + success: function(data) + { + pmItems.checkSubItemsAndAdd(thisObj, pmHosts, data, item_id, "hosts", hosts_ids); + }, + error:function(e) + { + console.warn("group "+item_id+" update error - " + JSON.stringify(e)); + polemarch.showErrors(e.responseJSON) + } + }); +} + + + +/** + * @return $.Deferred + */ +pmInventories.addSubGroups = function(item_id, groups_ids) +{ + if(!item_id) + { + throw "Error in pmInventories.addSubGroups with item_id = `" + item_id + "`" + } + + if(!groups_ids) + { + groups_ids = [] + } + + var def = new $.Deferred(); + spajs.ajax.Call({ + url: "/api/v1/inventories/"+item_id+"/groups/", + type: "POST", + contentType:'application/json', + data:JSON.stringify(groups_ids), + success: function(data) + { + if(data.not_found > 0) + { + $.notify("Item not found", "error"); + def.reject({text:"Item not found", status:404}) + return; + } + + if(pmInventories.model.items[item_id]) + { + if(!pmInventories.model.items[item_id].groups) + { + pmInventories.model.items[item_id].groups = [] + } + + for(var i in groups_ids) + { + pmInventories.model.items[item_id].groups.push(pmGroups.model.items[groups_ids[i]]) + } + } + + $.notify("Save", "success"); + def.resolve() + }, + error:function(e) + { + console.warn("group "+item_id+" update error - " + JSON.stringify(e)); + polemarch.showErrors(e.responseJSON) + def.reject(e) + } + }); + return def.promise(); +} + +/** + * @return $.Deferred + */ +pmInventories.addSubHosts = function(item_id, hosts_ids) +{ + if(!item_id) + { + throw "Error in pmInventories.addSubHosts with item_id = `" + item_id + "`" + } + + var def = new $.Deferred(); + if(!hosts_ids || hosts_ids.length == 0) + { + def.resolve() + return def.promise(); + } + + spajs.ajax.Call({ + url: "/api/v1/inventories/"+item_id+"/hosts/", + type: "POST", + contentType:'application/json', + data:JSON.stringify(hosts_ids), + success: function(data) + { + if(data.not_found > 0) + { + $.notify("Item not found", "error"); + def.reject({text:"Item not found", status:404}) + return; + } + + if(pmInventories.model.items[item_id]) + { + if(!pmInventories.model.items[item_id].hosts) + { + pmInventories.model.items[item_id].hosts = [] + } + + for(var i in hosts_ids) + { + pmInventories.model.items[item_id].hosts.push(pmHosts.model.items[hosts_ids[i]]) + } + } + + $.notify("Save", "success"); + def.resolve() + }, + error:function(e) + { + console.warn("group "+item_id+" update error - " + JSON.stringify(e)); + polemarch.showErrors(e.responseJSON) + def.reject(e) + } + }); + return def.promise(); +} + +/**pmInventories.validateGroupName = function(name) + { + if(!name) + { + return false; + } + + if(/^[a-zA-Z0-9\-\._]*$/.test(name.toLowerCase())) + { + return true; + } + + return false; + }*/ + + + + +/** + * Для ввода инвентория + * @type Object + */ +pmInventories.filed.inventoriesAutocomplete = inheritance(filedsLib.filed.simpleText) +pmInventories.filed.inventoriesAutocomplete.type = 'inventoriesAutocomplete' +pmInventories.filed.inventoriesAutocomplete.getValue = function(pmObj, filed) +{ + var inventory = $("#inventories-autocomplete").val() + if($("#inventory-source").val() != 'db') + { + inventory = $("#inventories-file").val() + if(!/^\.\//.test(inventory)) + { + inventory = trim("./"+inventory) + } + } + + + return inventory; +} + +/** + * Функция для рендера поля + * @type Object + */ +pmInventories.filed.inventoriesAutocomplete.render = function(pmObj, filed, item_id, opt) +{ + var html = spajs.just.render('filed_type_'+this.type, {pmObj:pmObj, filed:filed, item_id:item_id, filedObj:this, opt:opt}) + return spajs.just.onInsert(html, function() + { + // @FixMe требует чтоб были загружены все инвентории pmInventories.loadAllItems() + $("#inventories-autocomplete").select2({ width: '100%' }); + + if(filed.onchange && item_id) + { + filed.onchange({value:filed.getFiledValue.apply(pmObj, [item_id])}) + } + else if(filed.onchange) + { + if(pmInventories.model.itemslist.results[0]) + { + filed.onchange({value:pmInventories.model.itemslist.results[0].id}) + } + else + { + filed.onchange({value:""}) + } + } + }); +} + + +tabSignal.connect("polemarch.start", function() +{ + // inventories + spajs.addMenu({ + id:"inventories", + urlregexp:[/^inventories$/, /^inventory$/, /^inventories\/search\/?$/, /^inventories\/page\/([0-9]+)$/], + onOpen:function(holder, menuInfo, data){return pmInventories.showList(holder, menuInfo, data);} + }) + + spajs.addMenu({ + id:"inventories-import", + urlregexp:[/^inventories\/import$/], + onOpen:function(holder, menuInfo, data){return pmInventories.showImportPage(holder, menuInfo, data);} + }) + + + spajs.addMenu({ + id:"inventories-search", + urlregexp:[/^inventories\/search\/([A-z0-9 %\-.:,=]+)$/, /^inventories\/search\/([A-z0-9 %\-.:,=]+)\/page\/([0-9]+)$/], + onOpen:function(holder, menuInfo, data){return pmInventories.showSearchResults(holder, menuInfo, data);} + }) + + spajs.addMenu({ + id:"inventory", + urlregexp:[/^inventory\/([0-9]+)$/, /^inventories\/([0-9]+)$/], + onOpen:function(holder, menuInfo, data){return pmInventories.showItem(holder, menuInfo, data);} + }) + + spajs.addMenu({ + id:"inventory-history", + urlregexp:[/^inventory\/([0-9]+)\/history$/, /^inventory\/([0-9]+)\/history\/page\/([0-9]+)$/], + onOpen:function(holder, menuInfo, data){return pmHistory.showListInInventory(holder, menuInfo, data);} + }) + + spajs.addMenu({ + id:"newInventory", + urlregexp:[/^new-inventory$/, /^([A-z0-9_]+)\/([0-9]+)\/new-inventory$/], + onOpen:function(holder, menuInfo, data){return pmInventories.showNewItemPage(holder, menuInfo, data);} + }) +}) diff --git a/polemarch/static/js/pmItems.js b/polemarch/static/js/pmItems.js index b9756739..e6ffdd2c 100644 --- a/polemarch/static/js/pmItems.js +++ b/polemarch/static/js/pmItems.js @@ -19,35 +19,32 @@ pmItems.model.className = "pmItems" pmItems.filed = {} -pmItems.toggleSelect = function(item_id, mode) +pmItems.toggleSelect = function (item_id, mode) { - if(!item_id) + if (!item_id) { return; } console.log(item_id, mode) - if(mode === undefined) + if (mode === undefined) { this.model.selectedItems[item_id] = !this.model.selectedItems[item_id] - if(this.model.selectedItems[item_id]) + if (this.model.selectedItems[item_id]) { this.model.selectedCount++ - } - else + } else { this.model.selectedCount-- } - } - else + } else { - if(this.model.selectedItems[item_id] != mode) + if (this.model.selectedItems[item_id] != mode) { - if(mode) + if (mode) { this.model.selectedCount++ - } - else + } else { this.model.selectedCount-- } @@ -55,7 +52,7 @@ pmItems.toggleSelect = function(item_id, mode) this.model.selectedItems[item_id] = mode } - if(this.model.selectedCount < 0) + if (this.model.selectedCount < 0) { this.model.selectedCount = 0; } @@ -68,23 +65,22 @@ pmItems.toggleSelect = function(item_id, mode) * @param {boolean} mode * @returns {promise} */ -pmItems.toggleSelectEachItem = function(mode) +pmItems.toggleSelectEachItem = function (mode) { var thisObj = this; - return $.when(this.loadAllItems()).done(function() + return $.when(this.loadAllItems()).done(function () { var delta = 0; - for(var i in thisObj.model.itemslist.results) + for (var i in thisObj.model.itemslist.results) { var item_id = thisObj.model.itemslist.results[i].id - if(thisObj.model.selectedItems[item_id] != mode) + if (thisObj.model.selectedItems[item_id] != mode) { - if(mode) + if (mode) { delta++ - } - else + } else { delta-- } @@ -93,7 +89,7 @@ pmItems.toggleSelectEachItem = function(mode) } thisObj.model.selectedCount += delta - if(thisObj.model.selectedCount < 0) + if (thisObj.model.selectedCount < 0) { thisObj.model.selectedCount = 0; } @@ -101,38 +97,38 @@ pmItems.toggleSelectEachItem = function(mode) }).promise() } -pmItems.toggleSelectAll = function(elements, mode) +pmItems.toggleSelectAll = function (elements, mode) { - for(var i=0; i< elements.length; i++) + for (var i = 0; i < elements.length; i++) { this.toggleSelect($(elements[i]).attr('data-id'), mode) } } -pmItems.validateHostName = function(name) +pmItems.validateHostName = function (name) { - if(!name) + if (!name) { return false; } var regexp = { - ipTest : /^((25[0-5]|2[0-4]\d|[01]?\d\d?)\.){3}(25[0-5]|2[0-4]\d|[01]?\d\d?)$/, - ip6Test : /((^|:)([0-9a-fA-F]{0,4})){1,8}$/, - domenTest : /^((\.{0,1}[a-z0-9][a-z0-9-]{0,62}[a-z0-9]\.{0,1})*)$/ + ipTest: /^((25[0-5]|2[0-4]\d|[01]?\d\d?)\.){3}(25[0-5]|2[0-4]\d|[01]?\d\d?)$/, + ip6Test: /((^|:)([0-9a-fA-F]{0,4})){1,8}$/, + domenTest: /^((\.{0,1}[a-z0-9][a-z0-9-]{0,62}[a-z0-9]\.{0,1})*)$/ } - if(regexp.ipTest.test(name.toLowerCase())) + if (regexp.ipTest.test(name.toLowerCase())) { return true; } - if(regexp.ip6Test.test(name.toLowerCase())) + if (regexp.ip6Test.test(name.toLowerCase())) { return true; } - if(regexp.domenTest.test(name.toLowerCase())) + if (regexp.domenTest.test(name.toLowerCase())) { return true; } @@ -140,9 +136,9 @@ pmItems.validateHostName = function(name) return false; } -pmItems.validateRangeName = function(name) +pmItems.validateRangeName = function (name) { - if(!name) + if (!name) { return false; } @@ -158,26 +154,26 @@ pmItems.validateRangeName = function(name) * @param {type} data * @returns {$.Deferred} */ -pmItems.showList = function(holder, menuInfo, data) +pmItems.showList = function (holder, menuInfo, data) { var thisObj = this; var offset = 0 var limit = this.pageSize; - if(data.reg && data.reg[1] > 0) + if (data.reg && data.reg[1] > 0) { - offset = this.pageSize*(data.reg[1] - 1); + offset = this.pageSize * (data.reg[1] - 1); } - return $.when(this.loadItems(limit, offset)).done(function() + return $.when(this.loadItems(limit, offset)).done(function () { - var tpl = thisObj.model.name+'_list' - if(!spajs.just.isTplExists(tpl)) + var tpl = thisObj.model.name + '_list' + if (!spajs.just.isTplExists(tpl)) { tpl = 'items_list' } - $(holder).insertTpl(spajs.just.render(tpl, {query:"", pmObj:thisObj, opt:{}})) - }).fail(function() + $(holder).insertTpl(spajs.just.render(tpl, {query: "", pmObj: thisObj, opt: {}})) + }).fail(function () { $.notify("", "error"); }) @@ -187,11 +183,11 @@ pmItems.showList = function(holder, menuInfo, data) * @param {string} query * @returns {HTML} Шаблон формы поиска */ -pmItems.searchFiled = function(options) +pmItems.searchFiled = function (options) { options.className = this.model.className; this.model.searchAdditionalData = options - return spajs.just.render('searchFiled', {opt:options}); + return spajs.just.render('searchFiled', {opt: options}); } /** @@ -199,14 +195,17 @@ pmItems.searchFiled = function(options) * @param {string} query * @returns {$.Deferred} */ -pmItems.search = function(query, options) +pmItems.search = function (query, options) { - if(this.isEmptySearchQuery(query)) - { - return spajs.open({ menuId:this.model.name, reopen:true}); + if (this.isEmptySearchQuery(query)) + { + return spajs.open({menuId: this.model.name, reopen: true}); + } + - return spajs.open({ menuId:this.model.name+"/search/"+this.searchObjectToString(trim(query)), reopen:true}); + return spajs.open({menuId: this.model.name + "/search/" + this.searchObjectToString(trim(query)), reopen: true}); + //this.paginationHtml(this.model.itemslist); } /** @@ -214,9 +213,9 @@ pmItems.search = function(query, options) * @param {type} query * @returns {Boolean} */ -pmItems.isEmptySearchQuery = function(query) +pmItems.isEmptySearchQuery = function (query) { - if(!query || !trim(query)) + if (!query || !trim(query)) { return true; } @@ -231,79 +230,88 @@ pmItems.isEmptySearchQuery = function(query) * @param {type} data * @returns {$.Deferred} */ -pmItems.showSearchResults = function(holder, menuInfo, data) +pmItems.showSearchResults = function (holder, menuInfo, data) { var thisObj = this; - return $.when(this.sendSearchQuery(this.searchStringToObject(decodeURIComponent(data.reg[1])))).done(function() + + var limit = this.pageSize; + + if (data.reg && data.reg[2] > 0) { - var tpl = thisObj.model.name+'_list' - if(!spajs.just.isTplExists(tpl)) + offset = this.pageSize * (data.reg[2] - 1); + } else { + offset = 0; + } + return $.when(this.sendSearchQuery(this.searchStringToObject(decodeURIComponent(data.reg[1])), limit, offset)).done(function () + { + var tpl = thisObj.model.name + '_list' + if (!spajs.just.isTplExists(tpl)) { tpl = 'items_list' } - - $(holder).insertTpl(spajs.just.render(tpl, {query:decodeURIComponent(data.reg[1]), pmObj:thisObj, opt:{}})) - }).fail(function() + + $(holder).insertTpl(spajs.just.render(tpl, {query: decodeURIComponent(data.reg[1]), pmObj: thisObj, opt: {}})) + }).fail(function () { $.notify("", "error"); }) } -pmItems.copyItem = function(item_id) +pmItems.copyItem = function (item_id) { var def = new $.Deferred(); var thisObj = this; - $.when(this.loadItem(item_id)).done(function() + $.when(this.loadItem(item_id)).done(function () { var data = thisObj.model.items[item_id]; delete data.id; data.name = "copy from " + data.name - $.when(encryptedCopyModal.replace(data)).done(function(data) + $.when(encryptedCopyModal.replace(data)).done(function (data) { spajs.ajax.Call({ - url: "/api/v1/"+thisObj.model.name+"/", + url: "/api/v1/" + thisObj.model.name + "/", type: "POST", - contentType:'application/json', + contentType: 'application/json', data: JSON.stringify(data), - success: function(data) + success: function (data) { thisObj.model.items[data.id] = data def.resolve(data.id) }, - error:function(e) + error: function (e) { def.reject(e) } }); - }).fail(function(e) + }).fail(function (e) { def.reject(e) }) - }).fail(function(){ + }).fail(function () { def.reject(e) }) return def.promise(); } -pmItems.importItem = function(data) +pmItems.importItem = function (data) { var def = new $.Deferred(); var thisObj = this; spajs.ajax.Call({ - url: "/api/v1/"+thisObj.model.name+"/", + url: "/api/v1/" + thisObj.model.name + "/", type: "POST", - contentType:'application/json', + contentType: 'application/json', data: JSON.stringify(data), - success: function(data) + success: function (data) { thisObj.model.items[data.id] = data def.resolve(data.id) }, - error:function(e) + error: function (e) { $.notify("Error in import item", "error"); polemarch.showErrors(e) @@ -313,65 +321,65 @@ pmItems.importItem = function(data) return def.promise(); } -pmItems.copyAndEdit = function(item_id) +pmItems.copyAndEdit = function (item_id) { var def = new $.Deferred(); var thisObj = this; - return $.when(this.copyItem(item_id)).done(function(newItemId) + return $.when(this.copyItem(item_id)).done(function (newItemId) { - $.when(spajs.open({ menuId:thisObj.model.page_name + "/"+newItemId})).done(function(){ + $.when(spajs.open({menuId: thisObj.model.page_name + "/" + newItemId})).done(function () { $.notify("Item was duplicate", "success"); def.resolve() - }).fail(function(e){ + }).fail(function (e) { $.notify("Error in duplicate item", "error"); polemarch.showErrors(e) def.reject(e) }) - }).fail(function(e){ + }).fail(function (e) { def.reject(e) }) return def.promise(); } -pmItems.showItem = function(holder, menuInfo, data) +pmItems.showItem = function (holder, menuInfo, data) { var thisObj = this; //console.log(menuInfo, data) - return $.when(this.loadItem(data.reg[1])).done(function() + return $.when(this.loadItem(data.reg[1])).done(function () { - var tpl = thisObj.model.name+'_page' - if(!spajs.just.isTplExists(tpl)) + var tpl = thisObj.model.name + '_page' + if (!spajs.just.isTplExists(tpl)) { tpl = 'items_page' } - $(holder).insertTpl(spajs.just.render(tpl, {item_id:data.reg[1], pmObj:thisObj, opt:{}})) - }).fail(function() + $(holder).insertTpl(spajs.just.render(tpl, {item_id: data.reg[1], pmObj: thisObj, opt: {}})) + }).fail(function () { $.notify("", "error"); }).promise() } -pmItems.showNewItemPage = function(holder, menuInfo, data) +pmItems.showNewItemPage = function (holder, menuInfo, data) { var def = new $.Deferred(); - var tpl = this.model.name+'_new_page' - if(!spajs.just.isTplExists(tpl)) + var tpl = this.model.name + '_new_page' + if (!spajs.just.isTplExists(tpl)) { tpl = 'items_new_page' } - var text = spajs.just.render(tpl, {parent_item:data.reg[2], parent_type:data.reg[1], pmObj:this, opt:{}}) + var text = spajs.just.render(tpl, {parent_item: data.reg[2], parent_type: data.reg[1], pmObj: this, opt: {}}) $(holder).insertTpl(text) def.resolve() return def.promise(); } -pmItems.loadAllItems = function() +pmItems.loadAllItems = function () { return this.loadItems(999999); } @@ -382,7 +390,7 @@ pmItems.loadAllItems = function() * @param {object} item загруженный с сервера элемента * @returns {object} обработаный элемент */ -pmItems.afterItemLoad = function(item) +pmItems.afterItemLoad = function (item) { return item; } @@ -391,25 +399,25 @@ pmItems.afterItemLoad = function(item) * Обновляет поле модел this.model.itemslist и ложит туда список пользователей * Обновляет поле модел this.model.items и ложит туда список инфу о пользователях по их id */ -pmItems.loadItems = function(limit, offset) +pmItems.loadItems = function (limit, offset) { - if(!limit) + if (!limit) { limit = 30; } - if(!offset) + if (!offset) { offset = 0; } var thisObj = this; return spajs.ajax.Call({ - url: "/api/v1/"+this.model.name+"/", + url: "/api/v1/" + this.model.name + "/", type: "GET", - contentType:'application/json', - data: "limit="+encodeURIComponent(limit)+"&offset="+encodeURIComponent(offset), - success: function(data) + contentType: 'application/json', + data: "limit=" + encodeURIComponent(limit) + "&offset=" + encodeURIComponent(offset), + success: function (data) { //console.log("update Items", data) data.limit = limit @@ -417,14 +425,14 @@ pmItems.loadItems = function(limit, offset) thisObj.model.itemslist = data //thisObj.model.items = {} - for(var i in data.results) + for (var i in data.results) { var val = thisObj.afterItemLoad(data.results[i]) thisObj.model.items.justWatch(val.id); thisObj.model.items[val.id] = mergeDeep(thisObj.model.items[val.id], val) } }, - error:function(e) + error: function (e) { console.warn(e) polemarch.showErrors(e) @@ -438,15 +446,15 @@ pmItems.loadItems = function(limit, offset) * @param {string} defaultName имя параметра по умолчанию * @returns {pmItems.searchStringToObject.search} объект для поиска */ -pmItems.searchStringToObject = function(query, defaultName) +pmItems.searchStringToObject = function (query, defaultName) { var search = {} - if(query == "") + if (query == "") { return search; } - if(!defaultName) + if (!defaultName) { defaultName = 'name' } @@ -462,7 +470,7 @@ pmItems.searchStringToObject = function(query, defaultName) * @param {string} defaultName имя параметра по умолчанию * @returns {string} строка для параметра страницы поиска */ -pmItems.searchObjectToString = function(query, defaultName) +pmItems.searchObjectToString = function (query, defaultName) { return encodeURIComponent(query); } @@ -472,46 +480,53 @@ pmItems.searchObjectToString = function(query, defaultName) * @param {string|object} query запрос * @param {integer} limit * @param {integer} offset + * @param {string} ordering - сортировка по какому-то свойству объекта(id, name и т.д). Для обратной сортировки передавать "-id" * @returns {jQuery.ajax|spajs.ajax.Call.defpromise|type|spajs.ajax.Call.opt|spajs.ajax.Call.spaAnonym$10|Boolean|undefined|spajs.ajax.Call.spaAnonym$9} */ -pmItems.sendSearchQuery = function(query, limit, offset) +pmItems.sendSearchQuery = function (query, limit, offset, ordering) { - if(!limit) + if (!limit) { limit = 999; } - if(!offset) + if (!offset) { offset = 0; } + if (!ordering) + { + ordering = ""; + } + var q = []; - q.push("limit="+encodeURIComponent(limit)) - q.push("offset="+encodeURIComponent(offset)) + q.push("limit=" + encodeURIComponent(limit)) + q.push("offset=" + encodeURIComponent(offset)) + q.push("ordering=" + encodeURIComponent(ordering)) - for(var i in query) + for (var i in query) { - if(Array.isArray(query[i])) + if (Array.isArray(query[i])) { - for(var j in query[i]) + for (var j in query[i]) { query[i][j] = encodeURIComponent(query[i][j]) } - q.push(encodeURIComponent(i)+"="+query[i].join(",")) + q.push(encodeURIComponent(i) + "=" + query[i].join(",")) continue; } - q.push(encodeURIComponent(i)+"="+encodeURIComponent(query[i])) + q.push(encodeURIComponent(i) + "=" + encodeURIComponent(query[i])) } var thisObj = this; return spajs.ajax.Call({ - url: "/api/v1/"+this.model.name+"/?"+q.join("&"), + url: "/api/v1/" + this.model.name + "/?" + q.join("&"), type: "GET", - contentType:'application/json', - success: function(data) + contentType: 'application/json', + success: function (data) { //console.log("update Items", data) data.limit = limit @@ -519,13 +534,13 @@ pmItems.sendSearchQuery = function(query, limit, offset) thisObj.model.itemslist = data //thisObj.model.items = {} - for(var i in data.results) + for (var i in data.results) { var val = data.results[i] thisObj.model.items[val.id] = val } }, - error:function(e) + error: function (e) { console.warn(e) polemarch.showErrors(e) @@ -533,9 +548,9 @@ pmItems.sendSearchQuery = function(query, limit, offset) }); } -pmItems.searchItems = function(query, attrName, limit, offset) +pmItems.searchItems = function (query, attrName, limit, offset) { - if(!attrName) + if (!attrName) { attrName = "name"; } @@ -545,12 +560,12 @@ pmItems.searchItems = function(query, attrName, limit, offset) return this.sendSearchQuery(q, limit, offset); } -pmItems.loadItemsByIds = function(ids) +pmItems.loadItemsByIds = function (ids) { - var q = {id:ids} - for(var i in ids) + var q = {id: ids} + for (var i in ids) { - if(this.model.items[ids[i]] === undefined) + if (this.model.items[ids[i]] === undefined) { this.model.items[ids[i]] = {} } @@ -560,9 +575,9 @@ pmItems.loadItemsByIds = function(ids) /** * Обновляет поле модел this.model.items[item_id] и ложит туда пользователя */ -pmItems.loadItem = function(item_id) +pmItems.loadItem = function (item_id) { - if(!item_id) + if (!item_id) { throw "Error in pmItems.loadItem with item_id = `" + item_id + "`" } @@ -570,24 +585,24 @@ pmItems.loadItem = function(item_id) var def = new $.Deferred(); var thisObj = this; - if(thisObj.model.items[item_id] === undefined) + if (thisObj.model.items[item_id] === undefined) { thisObj.model.items[item_id] = {} } spajs.ajax.Call({ - url: "/api/v1/"+this.model.name+"/"+item_id+"/", + url: "/api/v1/" + this.model.name + "/" + item_id + "/", type: "GET", - contentType:'application/json', + contentType: 'application/json', data: "", - success: function(data) + success: function (data) { //console.log("loadUser", data) thisObj.model.items.justWatch(item_id) thisObj.model.items[item_id] = thisObj.afterItemLoad(data) def.resolve(data) }, - error:function(e) + error: function (e) { console.warn(e) //polemarch.showErrors(e) @@ -601,31 +616,31 @@ pmItems.loadItem = function(item_id) /** * @return $.Deferred */ -pmItems.deleteItem = function(item_id, force) +pmItems.deleteItem = function (item_id, force) { - if(!item_id) + if (!item_id) { throw "Error in pmItems.deleteItem with item_id = `" + item_id + "`" } var def = new $.Deferred(); - if(!force && !confirm("Are you sure?")) + if (!force && !confirm("Are you sure?")) { def.reject(); return def.promise() } var thisObj = this; - $.when(this.deleteItemQuery(item_id)).done(function(data) + $.when(this.deleteItemQuery(item_id)).done(function (data) { - $.when(spajs.open({ menuId:thisObj.model.name})).done(function() + $.when(spajs.open({menuId: thisObj.model.name})).done(function () { def.resolve() - }).fail(function(e){ + }).fail(function (e) { def.reject(e); polemarch.showErrors(e.responseJSON) }) - }).fail(function(e){ + }).fail(function (e) { def.reject(e); polemarch.showErrors(e.responseJSON) }) @@ -633,54 +648,54 @@ pmItems.deleteItem = function(item_id, force) return def.promise() } -pmItems.multiOperationsOnEachRow = function(elements, operation) +pmItems.multiOperationsOnEachRow = function (elements, operation, force) { var def = new $.Deferred(); var item_ids = [] - for(var i=0; i< elements.length; i++) + for (var i = 0; i < elements.length; i++) { item_ids.push($(elements[i]).attr('data-id')) } - $.when(this.multiOperationsOnItems(operation, item_ids)).always(function(){ + $.when(this.multiOperationsOnItems(operation, item_ids, force)).always(function () { def.resolve() }) return def.promise(); } -pmItems.deleteRows = function(elements) +pmItems.deleteRows = function (elements) { - if($.inArray(this.model.bulk_name, ['history', 'host', 'group', 'inventory', 'project', 'periodictask', 'template']) != -1) + if ($.inArray(this.model.bulk_name, ['history', 'host', 'group', 'inventory', 'project', 'periodictask', 'template']) != -1) { var deleteBulk = [] - for(var i=0; i< elements.length; i++) + for (var i = 0; i < elements.length; i++) { deleteBulk.push({ - type:"del", - item:this.model.bulk_name, - pk:$(elements[i]).attr('data-id') + type: "del", + item: this.model.bulk_name, + pk: $(elements[i]).attr('data-id') }) } var thisObj = this; return $.when(spajs.ajax.Call({ - url: "/api/v1/_bulk/", - type: "POST", - contentType:'application/json', - data:JSON.stringify(deleteBulk) - })).always(function() + url: "/api/v1/_bulk/", + type: "POST", + contentType: 'application/json', + data: JSON.stringify(deleteBulk) + })).always(function () { - for(var i in deleteBulk) + for (var i in deleteBulk) { - $(".item-"+deleteBulk[i].pk).hide(); + $(".item-" + deleteBulk[i].pk).hide(); thisObj.toggleSelect(deleteBulk[i].pk, false); } spajs.openURL(window.location.href); }).promise(); } - $.when(this.multiOperationsOnEachRow(elements, 'deleteItemQuery')).always(function(){ + $.when(this.multiOperationsOnEachRow(elements, 'deleteItemQuery')).always(function () { spajs.openURL(window.location.href); }) } @@ -689,34 +704,34 @@ pmItems.deleteRows = function(elements) * Удалит все выделенные элементы * @returns {promise} */ -pmItems.deleteSelected = function() +pmItems.deleteSelected = function () { - if($.inArray(this.model.bulk_name, ['history', 'host', 'group', 'inventory', 'project', 'periodictask', 'template']) != -1) + if ($.inArray(this.model.bulk_name, ['history', 'host', 'group', 'inventory', 'project', 'periodictask', 'template']) != -1) { var thisObj = this; var deleteBulk = [] - for(var i in this.model.selectedItems) + for (var i in this.model.selectedItems) { - if(this.model.selectedItems[i]) + if (this.model.selectedItems[i]) { deleteBulk.push({ - type:"del", - item:this.model.bulk_name, - pk:i + type: "del", + item: this.model.bulk_name, + pk: i }) } } return $.when(spajs.ajax.Call({ - url: "/api/v1/_bulk/", - type: "POST", - contentType:'application/json', - data:JSON.stringify(deleteBulk) - })).always(function() + url: "/api/v1/_bulk/", + type: "POST", + contentType: 'application/json', + data: JSON.stringify(deleteBulk) + })).always(function () { - for(var i in deleteBulk) + for (var i in deleteBulk) { - $(".item-"+deleteBulk[i].pk).hide(); + $(".item-" + deleteBulk[i].pk).hide(); thisObj.toggleSelect(deleteBulk[i].pk, false); } spajs.openURL(window.location.href); @@ -724,39 +739,39 @@ pmItems.deleteSelected = function() } var item_ids = [] - for(var i in this.model.selectedItems) + for (var i in this.model.selectedItems) { - if(this.model.selectedItems[i]) + if (this.model.selectedItems[i]) { item_ids.push(i) } } - return $.when(this.multiOperationsOnItems('deleteItemQuery', item_ids)).always(function(){ + return $.when(this.multiOperationsOnItems('deleteItemQuery', item_ids)).always(function () { spajs.openURL(window.location.href); }).promise(); } -pmItems.multiOperationsOnItems = function(operation, item_ids, force, def) +pmItems.multiOperationsOnItems = function (operation, item_ids, force, def) { - if(!force && !confirm("Are you sure?")) + if (!force && !confirm("Are you sure?")) { return; } - if(def === undefined) + if (def === undefined) { def = new $.Deferred(); } - if(!item_ids || !item_ids.length) + if (!item_ids || !item_ids.length) { def.resolve() return def.promise(); } var thisObj = this; - $.when(this[operation](item_ids[0])).always(function(){ + $.when(this[operation](item_ids[0])).always(function () { item_ids.splice(0, 1) thisObj.multiOperationsOnItems(operation, item_ids, true, def); }) @@ -767,43 +782,43 @@ pmItems.multiOperationsOnItems = function(operation, item_ids, force, def) /** * @return $.Deferred */ -pmItems.deleteItemQuery = function(item_id) +pmItems.deleteItemQuery = function (item_id) { - $(".item-"+item_id).hide(); + $(".item-" + item_id).hide(); this.toggleSelect(item_id, false); return spajs.ajax.Call({ - url: "/api/v1/"+this.model.name+"/"+item_id+"/", + url: "/api/v1/" + this.model.name + "/" + item_id + "/", type: "DELETE", - contentType:'application/json', - success: function(data) + contentType: 'application/json', + success: function (data) { - $(".item-"+item_id).remove(); + $(".item-" + item_id).remove(); }, - error:function(e) + error: function (e) { - $(".item-"+item_id).show(); + $(".item-" + item_id).show(); } }); } -pmItems.updateList = function(updated_ids) +pmItems.updateList = function (updated_ids) { - + var thisObj = this; - $.when(this.loadItemsByIds(updated_ids)).always(function() + $.when(this.loadItemsByIds(updated_ids)).always(function () { - if(thisObj.model.updateTimeoutId) + if (thisObj.model.updateTimeoutId) { clearTimeout(thisObj.model.updateTimeoutId) } - thisObj.model.updateTimeoutId = setTimeout(function(){ + thisObj.model.updateTimeoutId = setTimeout(function () { thisObj.updateList(updated_ids) }, 5001) }) } -pmItems.stopUpdates = function() +pmItems.stopUpdates = function () { clearTimeout(this.model.updateTimeoutId) this.model.updateTimeoutId = undefined; @@ -818,26 +833,26 @@ pmItems.stopUpdates = function() * @param {function} searchFunction функция поиска новых данных * @returns {$.Deferred} */ -pmItems.showUpdatedList = function(holder, menuInfo, data, functionName) +pmItems.showUpdatedList = function (holder, menuInfo, data, functionName) { var thisObj = this; - if(functionName == undefined) + if (functionName == undefined) { functionName = "showList" } - return $.when(this[functionName](holder, menuInfo, data)).always(function(updated_data) + return $.when(this[functionName](holder, menuInfo, data)).always(function (updated_data) { var updated_ids = [] - if(updated_data.results) + if (updated_data.results) { - for(var i in updated_data.results) + for (var i in updated_data.results) { updated_ids.push(updated_data.results[i].id) } } - - thisObj.model.updateTimeoutId = setTimeout(function(){ + + thisObj.model.updateTimeoutId = setTimeout(function () { thisObj.updateList(updated_ids); }, 5001) }).promise(); @@ -847,39 +862,39 @@ pmItems.showUpdatedList = function(holder, menuInfo, data, functionName) // pagination //////////////////////////////////////////////// -pmItems.paginationHtml = function(list) +pmItems.paginationHtml = function (list) { var totalPage = list.count / list.limit - if(totalPage > Math.floor(totalPage)) + if (totalPage > Math.floor(totalPage)) { totalPage = Math.floor(totalPage) + 1 } var currentPage = 0; - if(list.offset) + if (list.offset) { currentPage = Math.floor(list.offset / list.limit) } var url = window.location.href return spajs.just.render('pagination', { - totalPage:totalPage, - currentPage:currentPage, - url:url}) + totalPage: totalPage, + currentPage: currentPage, + url: url}) } -pmItems.getTotalPages = function(list) +pmItems.getTotalPages = function (list) { var totalPage = list.count / list.limit return totalPage } -pmItems.exportSelecedToFile = function(){ +pmItems.exportSelecedToFile = function () { var item_ids = [] - for(var i in this.model.selectedItems) + for (var i in this.model.selectedItems) { - if(this.model.selectedItems[i]) + if (this.model.selectedItems[i]) { item_ids.push(i) } @@ -892,19 +907,19 @@ pmItems.exportSelecedToFile = function(){ * Добавление сущности * @return $.Deferred */ -pmItems.addItem = function(parent_type, parent_item, opt) +pmItems.addItem = function (parent_type, parent_item, opt) { var def = new $.Deferred(); var data = {} - for(var i in this.model.page_new.fileds) + for (var i in this.model.page_new.fileds) { - for(var j in this.model.page_new.fileds[i]) + for (var j in this.model.page_new.fileds[i]) { var val = this.model.page_new.fileds[i][j]; data[val.name] = val.filed.getValue(this, val) - if(val.validator !== undefined && !val.validator.apply(this, [data[val.name]])) + if (val.validator !== undefined && !val.validator.apply(this, [data[val.name]])) { def.reject() return def.promise(); @@ -912,10 +927,10 @@ pmItems.addItem = function(parent_type, parent_item, opt) } } - if(this.model.page_new.onBeforeSave) + if (this.model.page_new.onBeforeSave) { data = this.model.page_new.onBeforeSave.apply(this, [data, opt]); - if(data == undefined || data == false) + if (data == undefined || data == false) { def.reject() return def.promise(); @@ -924,28 +939,27 @@ pmItems.addItem = function(parent_type, parent_item, opt) var thisObj = this; spajs.ajax.Call({ - url: "/api/v1/"+this.model.name+"/", + url: "/api/v1/" + this.model.name + "/", type: "POST", - contentType:'application/json', + contentType: 'application/json', data: JSON.stringify(data), - success: function() + success: function () { var agrs = [] - for(var i =0; i l.toUpperCase()); + $.notify(itemType1+" with id="+itemType_ids[i]+" doesn't exist and wouldn't be added", "error"); } + else + { + thisObj.model.items[itemId][itemType].push(ObjToAdd.model.items[itemType_ids[i]]); + } + + } } - debugger; - } \ No newline at end of file +} + + +/*pmItems.getFiledByName = function(fileds, name) + { + for(var i in fileds) + { + for(var j in fileds[i]) + { + if(fileds[i][j].name == name) + { + return fileds[i][j] + } + } + } + debugger; + }*/ \ No newline at end of file diff --git a/polemarch/static/js/pmModuleTemplates.js b/polemarch/static/js/pmModuleTemplates.js index c39264e7..82a898e3 100644 --- a/polemarch/static/js/pmModuleTemplates.js +++ b/polemarch/static/js/pmModuleTemplates.js @@ -1,5 +1,5 @@ -var pmModuleTemplates = inheritance(pmTemplates) +var pmModuleTemplates = inheritance(pmTemplates) pmModuleTemplates.model.name = "templates" pmModuleTemplates.model.page_name = "template" @@ -10,10 +10,10 @@ pmModuleTemplates.model.className = "pmModuleTemplates" // Поддерживаемые kind /api/v1/templates/supported-kinds/ pmModuleTemplates.model.kind = "Module" -pmModuleTemplates.inventoriesAutocompletefiled = new pmInventories.filed.inventoriesAutocomplete() +pmModuleTemplates.inventoriesAutocompletefiled = new pmInventories.filed.inventoriesAutocomplete() pmTemplates.model.kindObjects[pmModuleTemplates.model.kind] = pmModuleTemplates - + /** * Для ввода пароля @@ -25,20 +25,41 @@ pmModuleTemplates.filed.selectProjectInventoryGroupAndModule.getValue = function return ''; } +pmModuleTemplates.filed.selectProjectInventoryGroupAndModuleForNewOption = inheritance(filedsLib.filed.simpleText) +pmModuleTemplates.filed.selectProjectInventoryGroupAndModuleForNewOption.type = 'selectProjectInventoryGroupAndModuleForNewOption' +pmModuleTemplates.filed.selectProjectInventoryGroupAndModuleForNewOption.getValue = function(pmObj, filed){ + return ''; +} + +pmModuleTemplates.filed.selectProjectInventoryGroupAndModuleForOption = inheritance(filedsLib.filed.simpleText) +pmModuleTemplates.filed.selectProjectInventoryGroupAndModuleForOption.type = 'selectProjectInventoryGroupAndModuleForOption' +pmModuleTemplates.filed.selectProjectInventoryGroupAndModuleForOption.getValue = function(pmObj, filed){ + return ''; +} /** * Функция для рендера текстового поля * @type Object */ -pmModuleTemplates.filed.selectProjectInventoryGroupAndModule.render = function(pmObj, filed, item_id){ +pmModuleTemplates.filed.selectProjectInventoryGroupAndModule.render = function(pmObj, filed, item_id){ var html = spajs.just.render('filed_type_'+this.type, {pmObj:pmObj, filed:filed, item_id:item_id}) return spajs.just.onInsert(html, function() - { + { $("#inventories-autocomplete").select2({ width: '100%' }); - $("#projects-autocomplete").select2({ width: '100%' }); + $("#projects-autocomplete").select2({ width: '100%' }); }) } +pmModuleTemplates.filed.selectProjectInventoryGroupAndModuleForNewOption.render = function(pmObj, filed, item_id){ + var html = spajs.just.render('filed_type_'+this.type, {pmObj:pmObj, filed:filed, item_id:item_id}) + return spajs.just.onInsert(html, function() {}) +} + +pmModuleTemplates.filed.selectProjectInventoryGroupAndModuleForOption.render = function(pmObj, filed, item_id){ + var html = spajs.just.render('filed_type_'+this.type, {pmObj:pmObj, filed:filed, item_id:item_id}) + return spajs.just.onInsert(html, function() {}) +} + pmModuleTemplates.model.page_list = { short_title: 'Module template', @@ -54,13 +75,22 @@ pmModuleTemplates.model.page_item = { }, { class:'btn btn-warning', - function:function(item_id){ + function:function(item_id){ return "spajs.showLoader("+this.model.className+".saveAndExecute("+item_id+")); return false;" }, title:'Save and execute', link:function(){ return '#'}, help:'Save and execute' - }, + }, + { + class:'btn btn-info', + function:function(item_id){ + return "spajs.showLoader("+this.model.className+".setNewOption("+item_id+")); return false;" + }, + title:'Create new option', + link:function(){ return '#'}, + help:'Create new option' + }, { class:'btn btn-default copy-btn', function:function(item_id){ return 'spajs.showLoader('+this.model.className+'.copyAndEdit('+item_id+')); return false;'}, @@ -72,12 +102,15 @@ pmModuleTemplates.model.page_item = { class:'btn btn-danger danger-right', function:function(item_id){ return 'spajs.showLoader('+this.model.className+'.deleteItem('+item_id+')); return false;'}, title:' ', - link:function(){ return '#'}, + link:function(){ return '#'}, }, ], sections:[ - function(section, item_id){ + function(section, item_id){ return jsonEditor.editor(pmModuleTemplates.model.items[item_id].data.vars, {block:'module', title1:'Arguments', title2:'Adding new argument', select2:true}); + }, + function(section, item_id){ + return spajs.just.render("options_section", {item_id:item_id}) } ], title: function(item_id){ @@ -92,7 +125,7 @@ pmModuleTemplates.model.page_item = { filed: new filedsLib.filed.text(), title:'Name', name:'name', - placeholder:'Enter template name', + placeholder:'Enter template name', validator:function(value){ return filedsLib.validator.notEmpty(value, 'Name') }, @@ -102,11 +135,11 @@ pmModuleTemplates.model.page_item = { { filed: new pmModuleTemplates.filed.selectProjectInventoryGroupAndModule(), name:'inventory', - }, + }, ] ], onUpdate:function(result) - { + { return true; }, onBeforeSave:function(data, item_id) @@ -120,12 +153,119 @@ pmModuleTemplates.model.page_item = { args:moduleArgsEditor.getModuleArgs(), vars:jsonEditor.jsonEditorGetValues(), } - + return data; }, } +pmModuleTemplates.model.page_item_new_option = { + buttons:[ + { + class:'btn btn-primary', + function:function(item_id){ return 'spajs.showLoader(pmModuleTemplates.saveOption('+item_id+')); return false;'}, + title:'Create', + link:function(){ return '#'}, + } + ], + sections:[ + function(section, item_id){ + pmModuleTemplates.model.items[item_id].data.varsForOption={}; + return jsonEditor.editor(pmModuleTemplates.model.items[item_id].data.varsForOption, {block:'module', title1:'Additional arguments', title2:'Adding new argument', select2:true}); + } + ], + title: function(item_id){ + return "New option for module template "+this.model.items[item_id].justText('name'); + }, + short_title: function(item_id){ + return "New option"; + }, + fileds:[ + [ + { + filed: new filedsLib.filed.text(), + title:'Name', + name:'option_name', + placeholder:'Enter option name', + value:'' + } + ], + [ + { + filed: new pmModuleTemplates.filed.selectProjectInventoryGroupAndModuleForNewOption(), + name:'inventory', + }, + ] + ], + onUpdate:function(result) + { + return true; + } +} + +pmModuleTemplates.model.page_item_option = { + buttons:[ + { + class:'btn btn-primary', + function:function(item_id){ return 'spajs.showLoader(pmModuleTemplates.saveOption('+item_id+')); return false;'}, + title:'Save', + link:function(){ return '#'}, + }, + { + class:'btn btn-warning', + title:'Save and execute', + function:function(item_id){ return "spajs.showLoader(pmModuleTemplates.saveAndExecuteOption("+item_id+")); return false;"}, + link:function(){ return '#'}, + help:'Save and execute' + }, + { + class:'btn btn-danger danger-right', + function:function(item_id){ + return 'spajs.showLoader(pmModuleTemplates.removeOption('+item_id+')); return false;' + }, + title:' ', + link:function(){ return '#'}, + } + ], + sections:[ + function(section, item_id){ + pmModuleTemplates.model.items[item_id].data.varsForOption={}; + for(var i in pmModuleTemplates.model.items[item_id].dataForOption.vars) + { + pmModuleTemplates.model.items[item_id].data.varsForOption[i]=pmModuleTemplates.model.items[item_id].dataForOption.vars[i]; + } + return jsonEditor.editor(pmModuleTemplates.model.items[item_id].data.varsForOption, {block:'module', title1:'Additional arguments', title2:'Adding new argument', select2:true}); + } + ], + title: function(item_id){ + return 'Option '+pmModuleTemplates.model.items[item_id].option_name+' for module template '+this.model.items[item_id].justText('name'); + }, + short_title: function(item_id){ + return pmModuleTemplates.model.items[item_id].option_name; + }, + fileds:[ + [ + { + filed: new filedsLib.filed.text(), + title:'Name', + name:'option_name', + placeholder:'Enter option name', + } + ], + [ + { + filed: new pmModuleTemplates.filed.selectProjectInventoryGroupAndModuleForOption(), + name:'inventory', + }, + ] + ], + onUpdate:function(result) + { + return true; + } +} + + pmModuleTemplates.saveAndExecute = function(item_id) { var def = new $.Deferred(); @@ -140,11 +280,231 @@ pmModuleTemplates.saveAndExecute = function(item_id) return def.promise() } +pmModuleTemplates.setNewOption = function(item_id) +{ + return spajs.openURL(window.location.href+"/new-option"); +} -pmModuleTemplates.showItem = function(holder, menuInfo, data) -{ - var item_id = data.reg[1] +pmModuleTemplates.saveOption = function(item_id) +{ + var optionName=$('#filed_option_name').val(); + optionName=optionName.trim(); + optionName=optionName.replace( /\s/g, "-" ); + var def = new $.Deferred(); + if(optionName=="") + { + $.notify("Option name is empty", "error"); + def.reject({text:"Option name is empty"}); + return def.promise(); + } + var templateData=this.model.items[item_id].data; + var optionData= { + module:moduleArgsEditor.getSelectedModuleName(), + //inventory:+pmModuleTemplates.inventoriesAutocompletefiled.getValue(), + //project:+$("#projects-autocomplete").val(), + group:pmGroups.getGroupsAutocompleteValue(), + args:moduleArgsEditor.getModuleArgs(), + vars:jsonEditor.jsonEditorGetValues(), + }; + var dataToAdd={}; + for(var i in optionData) + { + for (var j in templateData) + { + if(i==j && i!='vars') + { + if(optionData[i]!=templateData[j]) + { + dataToAdd[i]=optionData[i]; + } + } + } + } + + if(!($.isEmptyObject(optionData['vars']))) + { + for(var i in templateData['vars']) + { + if(optionData['vars'].hasOwnProperty(i)) + { + $.notify('Template has already argument "'+i+'" ', "error"); + def.reject({text:"Option is the same as the template"}); + return def.promise(); + } + } + dataToAdd['vars']=optionData['vars']; + } + + + if($.isEmptyObject(dataToAdd)) + { + $.notify("Option is absolutely the same as the template", "error"); + def.reject({text:"Option is the same as the template"}); + return def.promise(); + } + else + { + var dataToAdd1={options:{}}; + dataToAdd1.options=this.model.items[item_id].options; + if(dataToAdd1.options.hasOwnProperty(optionName)) + { + delete dataToAdd1.options[optionName]; + } + dataToAdd1.options[optionName]=dataToAdd; + var thisObj = this; + spajs.ajax.Call({ + url: "/api/v1/" + this.model.name + "/" + item_id + "/", + type: "PATCH", + contentType: 'application/json', + data: JSON.stringify(dataToAdd1), + success: function (data) + { + thisObj.model.items[item_id] = data + $.notify('Option "'+optionName+'" was successfully saved', "success"); + $.when(spajs.open({ menuId:"template/"+thisObj.model.kind+"/"+data.id+"/option/"+optionName})).always(function(){ + def.resolve(); + }); + }, + error: function (e) + { + def.reject(e) + polemarch.showErrors(e.responseJSON) + } + }); + } + return def.promise(); +} +pmModuleTemplates.saveAndExecuteOption = function(item_id) +{ + var def = new $.Deferred(); + $.when(pmModuleTemplates.saveOption(item_id)).done(function() + { + pmTemplates.model.kindObjects['Module'].execute(item_id, pmModuleTemplates.model.items[item_id].option_name); + def.resolve(); + }).fail(function(e) + { + def.reject(e); + polemarch.showErrors(e.responseJSON); + }).promise(); + + return def.promise(); +} + +pmModuleTemplates.showNewOptionPage = function(holder, menuInfo, data) +{ + var item_id = data.reg[1]; + var def = new $.Deferred(); + var thisObj = this; + $.when(pmInventories.loadAllItems(), pmProjects.loadAllItems(), pmModuleTemplates.loadItem(item_id)).done(function() + { + $.when(pmModuleTemplates.selectInventory(pmModuleTemplates.model.items[item_id].data.inventory)).always(function() + { + var tpl = 'new_option_page' + if(!spajs.just.isTplExists(tpl)) + { + tpl = 'items_page' + } + + $(holder).insertTpl(spajs.just.render(tpl, {item_id:item_id, pmObj:thisObj, opt:{}})) + def.resolve(); + }); + }).fail(function(e) + { + def.reject(e); + }) + + return def.promise() +} + +pmModuleTemplates.showOptionPage = function(holder, menuInfo, data) +{ + var item_id = data.reg[1]; + var option_name=data.reg[2]; + var def = new $.Deferred(); + var thisObj = this; + $.when(pmInventories.loadAllItems(), pmProjects.loadAllItems(), pmModuleTemplates.loadItem(item_id)).done(function() + { + $.when(pmModuleTemplates.selectInventory(pmModuleTemplates.model.items[item_id].data.inventory)).always(function() + { + pmModuleTemplates.model.items[item_id].option_name=option_name; + pmModuleTemplates.model.items[item_id].dataForOption={}; + for(var i in pmModuleTemplates.model.items[item_id].data) + { + if(i!='vars') + { + pmModuleTemplates.model.items[item_id].dataForOption[i]=pmModuleTemplates.model.items[item_id].data[i]; + } + } + var optionAPI=pmModuleTemplates.model.items[item_id].options[pmModuleTemplates.model.items[item_id].option_name]; + for(var i in optionAPI) + { + if(pmModuleTemplates.model.items[item_id].dataForOption.hasOwnProperty(i)) + { + pmModuleTemplates.model.items[item_id].dataForOption[i]=optionAPI[i]; + } + } + if(optionAPI.hasOwnProperty('vars')) + { + pmModuleTemplates.model.items[item_id].dataForOption['vars']={}; + for(var i in optionAPI['vars']) + { + pmModuleTemplates.model.items[item_id].dataForOption['vars'][i]=optionAPI['vars'][i]; + } + } + + var tpl = 'module_option_page' + if(!spajs.just.isTplExists(tpl)) + { + tpl = 'items_page' + } + + $(holder).insertTpl(spajs.just.render(tpl, {item_id:item_id, pmObj:thisObj, opt:{}})) + def.resolve(); + }); + }).fail(function(e) + { + def.reject(e); + }) + + return def.promise() +} + +pmModuleTemplates.removeOption = function(item_id) +{ + var def = new $.Deferred(); + var optionName=pmModuleTemplates.model.items[item_id].option_name; + delete pmModuleTemplates.model.items[item_id].options[optionName]; + var dataToAdd1={options:{}}; + dataToAdd1['options']=pmModuleTemplates.model.items[item_id].options; + var thisObj = this; + spajs.ajax.Call({ + url: "/api/v1/" + this.model.name + "/" + item_id + "/", + type: "PATCH", + contentType: 'application/json', + data: JSON.stringify(dataToAdd1), + success: function (data) + { + thisObj.model.items[item_id] = data + $.notify('Option "'+optionName+'" was successfully deleted', "success"); + $.when(spajs.open({ menuId:"template/"+thisObj.model.kind+"/"+data.id})).always(function(){ + def.resolve(); + }); + + }, + error: function (e) + { + def.reject(e) + polemarch.showErrors(e.responseJSON) + } + }); + return def.promise(); +} + + +pmModuleTemplates.showItem = function(holder, menuInfo, data) +{ + var item_id = data.reg[1]; var def = new $.Deferred(); var thisObj = this; $.when(pmInventories.loadAllItems(), pmProjects.loadAllItems(), pmModuleTemplates.loadItem(item_id)).done(function() @@ -157,7 +517,7 @@ pmModuleTemplates.showItem = function(holder, menuInfo, data) tpl = 'items_page' } - $(holder).insertTpl(spajs.just.render(tpl, {item_id:item_id, pmObj:thisObj, opt:{}})) + $(holder).insertTpl(spajs.just.render(tpl, {item_id:item_id, pmObj:thisObj, opt:{}})) def.resolve(); }); }).fail(function(e) @@ -178,7 +538,7 @@ pmModuleTemplates.showNewItemPage = function(holder, menuInfo, data) $("#inventories-autocomplete").select2({ width: '100%' }); $("#projects-autocomplete").select2({ width: '100%' }); - + def.resolve(); }).fail(function(e) { @@ -244,7 +604,7 @@ pmModuleTemplates.addItem = function() type: "POST", contentType:'application/json', data:JSON.stringify(data), - success: function(data) + success: function(data) { $.notify("template created", "success"); $.when(spajs.open({ menuId:"template/"+thisObj.model.kind+"/"+data.id})).always(function(){ @@ -260,19 +620,31 @@ pmModuleTemplates.addItem = function() return def.promise(); } - + tabSignal.connect("polemarch.start", function() -{ +{ + spajs.addMenu({ + id:"Module-new-option", + urlregexp:[/^template\/Module\/([0-9]+)\/new-option$/, /^templates\/Module\/([0-9]+)\/new-option$/], + onOpen:function(holder, menuInfo, data){return pmModuleTemplates.showNewOptionPage(holder, menuInfo, data);}, + }) + spajs.addMenu({ - id:"Module-item", - urlregexp:[/^template\/Module\/([0-9]+)$/, /^templates\/Module\/([0-9]+)$/], - onOpen:function(holder, menuInfo, data){return pmModuleTemplates.showItem(holder, menuInfo, data);}, + id:"Module-option", + urlregexp:[/^template\/Module\/([0-9]+)\/option\/([A-z0-9 %\-.:,=]+)$/, /^templates\/Module\/([0-9]+)\/option\/([A-z0-9 %\-.:,=]+)$/], + onOpen:function(holder, menuInfo, data){return pmModuleTemplates.showOptionPage(holder, menuInfo, data);}, }) - + spajs.addMenu({ - id:"module-new", + id:"Module-item", + urlregexp:[/^template\/Module\/([0-9]+)$/, /^templates\/Module\/([0-9]+)$/], + onOpen:function(holder, menuInfo, data){return pmModuleTemplates.showItem(holder, menuInfo, data);}, + }) + + spajs.addMenu({ + id:"module-new", urlregexp:[/^template\/new-module$/], onOpen:function(holder, menuInfo, data){return pmModuleTemplates.showNewItemPage(holder, menuInfo, data);} }) - + }) \ No newline at end of file diff --git a/polemarch/static/js/pmPeriodicTasks.js b/polemarch/static/js/pmPeriodicTasks.js index 968048b5..bfb64297 100644 --- a/polemarch/static/js/pmPeriodicTasks.js +++ b/polemarch/static/js/pmPeriodicTasks.js @@ -1,717 +1,717 @@ - -var pmPeriodicTasks = inheritance(pmItems) - -pmPeriodicTasks.model.page_name = "periodic-task" -pmPeriodicTasks.model.bulk_name = "periodictask" -pmPeriodicTasks.model.name = "periodic-tasks" -pmPeriodicTasks.model.selectedInventory = 0; -pmPeriodicTasks.model.className = "pmPeriodicTasks" - -pmPeriodicTasks.inventoriesAutocompletefiled = new pmInventories.filed.inventoriesAutocomplete() - - -pmPeriodicTasks.copyAndEdit = function(item_id) -{ - if(!item_id) - { - throw "Error in pmPeriodicTasks.copyAndEdit with item_id = `" + item_id + "`" - } - - var def = new $.Deferred(); - var thisObj = this; - return $.when(this.copyItem(item_id)).done(function(newItemId) - { - $.when(spajs.open({ menuId:"project/"+thisObj.model.items[item_id].project+"/"+thisObj.model.page_name + "/"+newItemId})).done(function(){ - $.notify("Item was duplicate", "success"); - def.resolve() - }).fail(function(e){ - $.notify("Error in duplicate item", "error"); - polemarch.showErrors(e) - def.reject(e) - }) - }).fail(function(e){ - def.reject(e) - }) - - return def.promise(); -} - -pmPeriodicTasks.copyItem = function(item_id) -{ - if(!item_id) - { - throw "Error in pmPeriodicTasks.copyItem with item_id = `" + item_id + "`" - } - - var def = new $.Deferred(); - var thisObj = this; - - $.when(this.loadItem(item_id)).done(function() - { - var data = thisObj.model.items[item_id]; - delete data.id; - data.name = "copy from " + data.name - data.vars.group = data.group - data.vars.args = data.args - - delete data.group; - delete data.args; - - $.when(encryptedCopyModal.replace(data)).done(function(data) - { - spajs.ajax.Call({ - url: "/api/v1/"+thisObj.model.name+"/", - type: "POST", - contentType:'application/json', - data: JSON.stringify(data), - success: function(data) - { - thisObj.model.items[data.id] = data - def.resolve(data.id) - }, - error:function(e) - { - def.reject(e) - } - }); - }).fail(function(e) - { - def.reject(e) - }) - - }).fail(function(e) - { - def.reject(e) - }) - - - return def.promise(); -} - - -pmPeriodicTasks.selectInventory = function(inventory_id) -{ - var def = new $.Deferred(); - var thisObj = this; - inventory_id = inventory_id/1 - if(inventory_id) - { - $.when(pmInventories.loadItem(inventory_id)).done(function(){ - thisObj.model.selectedInventory = inventory_id; - def.resolve(); - }).fail(function(e){ - def.reject(e); - }); - } - else - { - thisObj.model.selectedInventory = 0; - def.resolve(); - } - return def.promise() -} - -pmPeriodicTasks.deleteItem = function(item_id, force) -{ - if(!item_id) - { - throw "Error in pmPeriodicTasks.deleteItem with item_id = `" + item_id + "`" - } - - if(!force && !confirm("Are you sure?")) - { - return; - } - - var def = new $.Deferred(); - var thisObj = this; - $.when(this.loadItem(item_id)).done(function() - { - var project_id = pmPeriodicTasks.model.items[item_id].project; - $.when(thisObj.deleteItemQuery(item_id)).done(function(data) - { - $.when(spajs.open({ menuId: "project/"+project_id+"/periodic-tasks"})).done(function() - { - def.resolve() - }).fail(function(e){ - def.reject(e); - polemarch.showErrors(e.responseJSON) - }) - }).fail(function(e){ - def.reject(e); - polemarch.showErrors(e.responseJSON) - }) - }).fail(function(e){ - def.reject(e); - polemarch.showErrors(e.responseJSON) - }) - - return def.promise(); -} - -pmPeriodicTasks.execute = function(project_id, item_id) -{ - var def = new $.Deferred(); - spajs.ajax.Call({ - url: "/api/v1/"+this.model.name+"/" + item_id+"/execute/", - type: "POST", - data:JSON.stringify({}), - contentType:'application/json', - success: function(data) - { - $.notify("Started", "success"); - if(data && data.history_id) - { - $.when(spajs.open({ menuId:"project/"+project_id+"/history/"+data.history_id}) ).done(function(){ - def.resolve() - }).fail(function(e){ - def.reject(e) - }) - } - else - { - def.reject({text:"No history_id", status:500}) - } - }, - error:function(e) - { - def.reject(e) - polemarch.showErrors(e.responseJSON) - } - }) - - return def.promise(); -} - -/** - * Выделеть всё или снять выделение - * @param {boolean} mode - * @param {integer} project_id проект для которого с тасками работаем. - * @returns {promise} - */ -pmPeriodicTasks.toggleSelectEachItem = function(mode, project_id) -{ - var thisObj = this; - return $.when(this.searchItems(project_id, 'project')).done(function() - { - var delta = 0; - for(var i in thisObj.model.itemslist.results) - { - var item_id = thisObj.model.itemslist.results[i].id - - if(thisObj.model.selectedItems[item_id] != mode) - { - if(mode) - { - delta++ - } - else - { - delta-- - } - } - thisObj.model.selectedItems[item_id] = mode - } - thisObj.model.selectedCount += delta - - if(thisObj.model.selectedCount < 0) - { - thisObj.model.selectedCount = 0; - } - - }).promise() -} - -pmPeriodicTasks.showList = function(holder, menuInfo, data) -{ - var thisObj = this; - var offset = 0 - var limit = this.pageSize; - if(data.reg && data.reg[2] > 0) - { - offset = this.pageSize*(data.reg[2] - 1); - } - var project_id = data.reg[1]; - - return $.when(this.searchItems(project_id, 'project'), pmProjects.loadItem(project_id)).done(function() - { - $(holder).insertTpl(spajs.just.render(thisObj.model.name+'_list', {query:"", project_id:project_id})) - }).fail(function() - { - $.notify("", "error"); - }).promise(); -} - -pmPeriodicTasks.search = function(query, options) -{ - if(this.isEmptySearchQuery(query)) - { - return spajs.open({ menuId:'project/' + options.project_id +"/" + this.model.name, reopen:true}); - } - - return spajs.open({ menuId:'project/' + options.project_id +"/" + this.model.name+"/search/"+this.searchObjectToString(trim(query)), reopen:true}); -} - -pmPeriodicTasks.showSearchResults = function(holder, menuInfo, data) -{ - var thisObj = this; - var project_id = data.reg[1]; - - - var search = this.searchStringToObject(decodeURIComponent(data.reg[2])) - search['project'] = project_id - - return $.when(this.sendSearchQuery(search), pmProjects.loadItem(project_id)).done(function() - { - $(holder).insertTpl(spajs.just.render(thisObj.model.name+'_list', {query:decodeURIComponent(data.reg[2]), project_id:project_id})) - }).fail(function() - { - $.notify("", "error"); - }).promise(); -} - -pmPeriodicTasks.showNewItemPage = function(holder, menuInfo, data) -{ - var project_id = data.reg[1]; - var thisObj = this; - return $.when(pmTasks.searchItems(project_id, "project"), pmProjects.loadItem(project_id), pmInventories.loadAllItems()).done(function() - { - thisObj.model.newitem = {type:'INTERVAL', kind:'PLAYBOOK'} - $(holder).insertTpl(spajs.just.render(thisObj.model.name+'_new_page', {project_id:project_id})) - - $('#new_periodic-tasks_inventory').select2({ width: '100%' }); - - new autoComplete({ - selector: '#new_periodic-tasks_playbook', - minChars: 0, - cache:false, - showByClick:false, - renderItem: function(item, search) - { - return '
    ' + item.playbook + '
    '; - }, - onSelect: function(event, term, item) - { - $("#new_periodic-tasks_playbook").val($(item).text()); - //console.log('onSelect', term, item); - //var value = $(item).attr('data-value'); - }, - source: function(term, response) - { - term = term.toLowerCase(); - - var matches = [] - for(var i in pmTasks.model.items) - { - var val = pmTasks.model.items[i] - if(val.name.toLowerCase().indexOf(term) != -1 && val.project == project_id) - { - matches.push(val) - } - } - response(matches); - } - }); - }).fail(function() - { - $.notify("", "error"); - }).promise(); -} - -/** - * Для ввода пароля - * @type Object - */ -pmPeriodicTasks.filed.selectInventoryKindPlaybookGroupModuleAndTime = inheritance(filedsLib.filed.simpleText) -pmPeriodicTasks.filed.selectInventoryKindPlaybookGroupModuleAndTime.type = 'selectInventoryKindPlaybookGroupModuleAndTime' -pmPeriodicTasks.filed.selectInventoryKindPlaybookGroupModuleAndTime.getValue = function(pmObj, filed){ - return ''; -} - -pmPeriodicTasks.model.page_list = { - short_title: 'Periodic tasks', -} - -pmPeriodicTasks.model.page_item = { - buttons:[ - { - class:'btn btn-primary', - function:function(item_id, opt){ return 'spajs.showLoader('+this.model.className+'.updateItem('+item_id+', {project_id:'+opt.project_id+'})); return false;'}, - title:'Save', - link:function(){ return '#'}, - }, - { - class:'btn btn-warning', - function:function(item_id, opt){ - return "spajs.showLoader(pmPeriodicTasks.execute("+opt.project_id+", "+item_id+")); return false;" - }, - title:'Execute', - link:function(){ return '#'}, - help:'Execute' - }, - { - class:'btn btn-default copy-btn', - function:function(item_id){ return 'spajs.showLoader('+this.model.className+'.copyAndEdit('+item_id+')); return false;'}, - title:'', - link:function(){ return '#'}, - help:'Copy' - }, - { - class:'btn btn-danger danger-right', - function:function(item_id){ return 'spajs.showLoader('+this.model.className+'.deleteItem('+item_id+')); return false;'}, - title:' ', - link:function(){ return '#'}, - }, - ], - sections:[ - function(section, item_id) - { - return spajs.just.render('periodic-tasks_page_vars_section', { - item_id:item_id, - pmObj:this, - PLAYBOOK_VARS: jQuery.extend(true, {}, pmPeriodicTasks.model.items[item_id].vars), - MODULE_VARS: jQuery.extend(true, {}, pmPeriodicTasks.model.items[item_id].vars) - }) - } - ], - title: function(item_id){ - return "Periodic task "+this.model.items[item_id].justText('name') - }, - back_link: function(item_id, opt){ - return polemarch.opt.host + "/?project/" + opt.project_id + "/" + this.model.name; - }, - short_title: function(item_id){ - return this.model.items[item_id].justText('name', function(v){return v.slice(0, 20)}) - }, - fileds:[ - [ - { - filed: new filedsLib.filed.text(), - title:'Name', - name:'name', - placeholder:'Enter template name', - validator:function(value){ - return filedsLib.validator.notEmpty(value, 'Name') - }, - fast_validator:function(value){ return value != '' && value} - }, - { - filed: new filedsLib.filed.boolean(), - title:'Save in history', - name:'save_result', - help:'Save result of task in history', - }, - { - filed: new filedsLib.filed.boolean(), - title:'Enabled', - name:'enabled', - help:'', - }, - ],[ - { - filed: new pmPeriodicTasks.filed.selectInventoryKindPlaybookGroupModuleAndTime(), - name:'project', - }, - ] - ], - onUpdate:function(result) - { - return true; - }, - onBeforeSave:function(data, item_id, opt) - { - if(!opt || !opt.project_id) - { - throw "Error in pmPeriodicTasks.onBeforeSave with opt.project_id is null" - } - - data.project = opt.project_id - - data.type = $("#periodic-tasks_"+item_id+"_type").val() - data.inventory = pmPeriodicTasks.inventoriesAutocompletefiled.getValue() - - data.kind = $("#periodic-tasks_"+item_id+"_kind").val() - - if(!data.inventory) - { - $.notify("Invalid field `inventory` ", "error"); - return false; - } - - - if(data.kind == "MODULE") - { - data.mode = moduleArgsEditor.getSelectedModuleName() - if(!data.mode) - { - $.notify("Module name is empty", "error"); - return false; - } - } - else - { - data.mode = $("#periodic-tasks_"+item_id+"_playbook").val() - if(!data.mode) - { - $.notify("Playbook name is empty", "error"); - return false; - } - } - - if(data.type == "CRONTAB") - { - data.schedule = crontabEditor.getCronString() - } - else - { - data.schedule = $("#periodic-tasks_"+item_id+"_schedule_INTERVAL").val() - if(!data.schedule) - { - $.notify("Invalid field `Interval schedule` ", "error"); - return; - } - } - - data.vars = jsonEditor.jsonEditorGetValues(data.kind) - - if(data.kind == "MODULE") - { - data.vars.group = pmGroups.getGroupsAutocompleteValue() - data.vars.args = moduleArgsEditor.getModuleArgs(); - } - return data; - }, -} - -pmPeriodicTasks.showItem = function(holder, menuInfo, data) -{ - var def = new $.Deferred(); - var thisObj = this; - var item_id = data.reg[2]; - var project_id = data.reg[1]; - - $.when(pmPeriodicTasks.loadItem(item_id), pmTasks.loadAllItems(), pmInventories.loadAllItems(), pmProjects.loadItem(project_id)).done(function() - { - var tpl = thisObj.model.name+'_page' - if(!spajs.just.isTplExists(tpl)) - { - tpl = 'items_page' - } - - $(holder).insertTpl(spajs.just.render(tpl, {item_id:item_id, pmObj:thisObj, opt:{project_id:project_id}})) - pmPeriodicTasks.selectInventory(pmPeriodicTasks.model.items[item_id].inventory) - - $('#periodic-tasks_'+item_id+'_inventory').select2({ width: '100%' }); - - new autoComplete({ - selector: '#periodic-tasks_'+item_id+'_playbook', - minChars: 0, - cache:false, - showByClick:false, - renderItem: function(item, search) - { - return '
    ' + item.name + '.yaml
    '; - }, - onSelect: function(event, term, item) - { - $("#periodic-tasks_"+item_id+"_playbook").val($(item).text()); - //console.log('onSelect', term, item); - //var value = $(item).attr('data-value'); - }, - source: function(term, response) - { - term = term.toLowerCase(); - - var matches = [] - for(var i in pmTasks.model.items) - { - var val = pmTasks.model.items[i] - if(val.name.toLowerCase().indexOf(term) != -1 && val.project == project_id) - { - matches.push(val) - } - } - response(matches); - } - }); - - def.resolve(); - - }).fail(function(e) - { - $.notify("", "error"); - def.reject(e); - }) - - return def.promise() -} - - -/** - * @return $.Deferred - */ -pmPeriodicTasks.addItem = function(project_id) -{ - if(!project_id) - { - throw "Error in pmPeriodicTasks.addItem with project_id = `" + project_id + "`" - } - - var def = new $.Deferred(); - - var data = {} - - data.project = project_id - - data.name = $("#new_periodic-tasks_name").val() - data.type = $("#new_periodic-tasks_type").val() - data.inventory = pmPeriodicTasks.inventoriesAutocompletefiled.getValue() - - if(!data.name) - { - $.notify("Invalid field `name` ", "error"); - def.reject(); - return def.promise(); - } - - if(!data.inventory) - { - $.notify("Invalid field `inventory` ", "error"); - def.reject(); - return def.promise(); - } - - - data.kind = $("#new_periodic-tasks_kind").val() - - if(data.kind == "MODULE") - { - data.mode = moduleArgsEditor.getSelectedModuleName() - if(!data.mode) - { - $.notify("Module name is empty", "error"); - def.reject(); - return def.promise(); - } - } - else - { - data.mode = $("#new_periodic-tasks_playbook").val() - if(!data.mode) - { - $.notify("Playbook name is empty", "error"); - def.reject(); - return def.promise(); - } - } - - if(data.type == "CRONTAB") - { - data.schedule = crontabEditor.getCronString() - } - else - { - data.schedule = $("#new_periodic-tasks_schedule_INTERVAL").val() - if(!data.schedule) - { - $.notify("Invalid field `Interval schedule` ", "error"); - def.reject(); - return def.promise(); - } - } - - data.save_result = $("#new_periodic-tasks_save_result").hasClass('selected') - - data.vars = jsonEditor.jsonEditorGetValues(data.kind) - - if(data.kind == "MODULE") - { - data.vars.group = pmGroups.getGroupsAutocompleteValue() - data.vars.args = moduleArgsEditor.getModuleArgs(); - } - - spajs.ajax.Call({ - url: "/api/v1/"+this.model.name+"/", - type: "POST", - contentType:'application/json', - data: JSON.stringify(data), - success: function(data) - { - $.notify("periodic task created", "success"); - - $.when(spajs.open({ menuId:"project/"+project_id+"/periodic-task/"+data.id})).always(function(){ - def.resolve() - }) - }, - error:function(e) - { - polemarch.showErrors(e.responseJSON) - def.reject(e) - } - }); - return def.promise(); -} - -pmPeriodicTasks.loadItem = function(item_id) -{ - var thisObj = this; - return spajs.ajax.Call({ - url: "/api/v1/"+this.model.name+"/"+item_id+"/", - type: "GET", - contentType:'application/json', - data: "", - success: function(data) - { - if(data.kind == "MODULE") - { - if(data && data.vars && data.vars.group !== undefined) - { - data.group = data.vars.group - delete data.vars.group - } - - if(data && data.vars && data.vars.args !== undefined) - { - data.args = data.vars.args - delete data.vars.args - } - } - thisObj.model.items.justWatch(item_id) - thisObj.model.items[item_id] = data - }, - error:function(e) - { - console.warn(e) - polemarch.showErrors(e) - } - }); -} - -tabSignal.connect("polemarch.start", function() -{ - // tasks - spajs.addMenu({ - id:"PeriodicTasks", - urlregexp:[/^project\/([0-9]+)\/periodic-tasks$/, /^project\/([0-9]+)\/periodic-task$/, /^project\/([0-9]+)\/periodic-tasks\/page\/([0-9]+)$/], - onOpen:function(holder, menuInfo, data){return pmPeriodicTasks.showList(holder, menuInfo, data);} - }) - - spajs.addMenu({ - id:"PeriodicTasks-search", - urlregexp:[/^project\/([0-9]+)\/periodic-tasks\/search\/([A-z0-9 %\-.:,=]+)$/], - onOpen:function(holder, menuInfo, data){return pmPeriodicTasks.showSearchResults(holder, menuInfo, data);} - }) - - spajs.addMenu({ - id:"PeriodicTask", - urlregexp:[/^project\/([0-9]+)\/periodic-task\/([0-9]+)$/], - onOpen:function(holder, menuInfo, data){return pmPeriodicTasks.showItem(holder, menuInfo, data);} - }) - - spajs.addMenu({ - id:"newPeriodicTask", - urlregexp:[/^project\/([0-9]+)\/new-periodic-tasks$/], - onOpen:function(holder, menuInfo, data){return pmPeriodicTasks.showNewItemPage(holder, menuInfo, data);} - }) - + +var pmPeriodicTasks = inheritance(pmItems) + +pmPeriodicTasks.model.page_name = "periodic-task" +pmPeriodicTasks.model.bulk_name = "periodictask" +pmPeriodicTasks.model.name = "periodic-tasks" +pmPeriodicTasks.model.selectedInventory = 0; +pmPeriodicTasks.model.className = "pmPeriodicTasks" + +pmPeriodicTasks.inventoriesAutocompletefiled = new pmInventories.filed.inventoriesAutocomplete() + + +pmPeriodicTasks.copyAndEdit = function(item_id) +{ + if(!item_id) + { + throw "Error in pmPeriodicTasks.copyAndEdit with item_id = `" + item_id + "`" + } + + var def = new $.Deferred(); + var thisObj = this; + return $.when(this.copyItem(item_id)).done(function(newItemId) + { + $.when(spajs.open({ menuId:"project/"+thisObj.model.items[item_id].project+"/"+thisObj.model.page_name + "/"+newItemId})).done(function(){ + $.notify("Item was duplicate", "success"); + def.resolve() + }).fail(function(e){ + $.notify("Error in duplicate item", "error"); + polemarch.showErrors(e) + def.reject(e) + }) + }).fail(function(e){ + def.reject(e) + }) + + return def.promise(); +} + +pmPeriodicTasks.copyItem = function(item_id) +{ + if(!item_id) + { + throw "Error in pmPeriodicTasks.copyItem with item_id = `" + item_id + "`" + } + + var def = new $.Deferred(); + var thisObj = this; + + $.when(this.loadItem(item_id)).done(function() + { + var data = thisObj.model.items[item_id]; + delete data.id; + data.name = "copy from " + data.name + data.vars.group = data.group + data.vars.args = data.args + + delete data.group; + delete data.args; + + $.when(encryptedCopyModal.replace(data)).done(function(data) + { + spajs.ajax.Call({ + url: "/api/v1/"+thisObj.model.name+"/", + type: "POST", + contentType:'application/json', + data: JSON.stringify(data), + success: function(data) + { + thisObj.model.items[data.id] = data + def.resolve(data.id) + }, + error:function(e) + { + def.reject(e) + } + }); + }).fail(function(e) + { + def.reject(e) + }) + + }).fail(function(e) + { + def.reject(e) + }) + + + return def.promise(); +} + + +pmPeriodicTasks.selectInventory = function(inventory_id) +{ + var def = new $.Deferred(); + var thisObj = this; + inventory_id = inventory_id/1 + if(inventory_id) + { + $.when(pmInventories.loadItem(inventory_id)).done(function(){ + thisObj.model.selectedInventory = inventory_id; + def.resolve(); + }).fail(function(e){ + def.reject(e); + }); + } + else + { + thisObj.model.selectedInventory = 0; + def.resolve(); + } + return def.promise() +} + +pmPeriodicTasks.deleteItem = function(item_id, force) +{ + if(!item_id) + { + throw "Error in pmPeriodicTasks.deleteItem with item_id = `" + item_id + "`" + } + + if(!force && !confirm("Are you sure?")) + { + return; + } + + var def = new $.Deferred(); + var thisObj = this; + $.when(this.loadItem(item_id)).done(function() + { + var project_id = pmPeriodicTasks.model.items[item_id].project; + $.when(thisObj.deleteItemQuery(item_id)).done(function(data) + { + $.when(spajs.open({ menuId: "project/"+project_id+"/periodic-tasks"})).done(function() + { + def.resolve() + }).fail(function(e){ + def.reject(e); + polemarch.showErrors(e.responseJSON) + }) + }).fail(function(e){ + def.reject(e); + polemarch.showErrors(e.responseJSON) + }) + }).fail(function(e){ + def.reject(e); + polemarch.showErrors(e.responseJSON) + }) + + return def.promise(); +} + +pmPeriodicTasks.execute = function(project_id, item_id) +{ + var def = new $.Deferred(); + spajs.ajax.Call({ + url: "/api/v1/"+this.model.name+"/" + item_id+"/execute/", + type: "POST", + data:JSON.stringify({}), + contentType:'application/json', + success: function(data) + { + $.notify("Started", "success"); + if(data && data.history_id) + { + $.when(spajs.open({ menuId:"project/"+project_id+"/history/"+data.history_id}) ).done(function(){ + def.resolve() + }).fail(function(e){ + def.reject(e) + }) + } + else + { + def.reject({text:"No history_id", status:500}) + } + }, + error:function(e) + { + def.reject(e) + polemarch.showErrors(e.responseJSON) + } + }) + + return def.promise(); +} + +/** + * Выделеть всё или снять выделение + * @param {boolean} mode + * @param {integer} project_id проект для которого с тасками работаем. + * @returns {promise} + */ +pmPeriodicTasks.toggleSelectEachItem = function(mode, project_id) +{ + var thisObj = this; + return $.when(this.searchItems(project_id, 'project')).done(function() + { + var delta = 0; + for(var i in thisObj.model.itemslist.results) + { + var item_id = thisObj.model.itemslist.results[i].id + + if(thisObj.model.selectedItems[item_id] != mode) + { + if(mode) + { + delta++ + } + else + { + delta-- + } + } + thisObj.model.selectedItems[item_id] = mode + } + thisObj.model.selectedCount += delta + + if(thisObj.model.selectedCount < 0) + { + thisObj.model.selectedCount = 0; + } + + }).promise() +} + +pmPeriodicTasks.showList = function(holder, menuInfo, data) +{ + var thisObj = this; + var offset = 0 + var limit = this.pageSize; + if(data.reg && data.reg[2] > 0) + { + offset = this.pageSize*(data.reg[2] - 1); + } + var project_id = data.reg[1]; + + return $.when(this.searchItems(project_id, 'project'), pmProjects.loadItem(project_id)).done(function() + { + $(holder).insertTpl(spajs.just.render(thisObj.model.name+'_list', {query:"", project_id:project_id})) + }).fail(function() + { + $.notify("", "error"); + }).promise(); +} + +pmPeriodicTasks.search = function(query, options) +{ + if(this.isEmptySearchQuery(query)) + { + return spajs.open({ menuId:'project/' + options.project_id +"/" + this.model.name, reopen:true}); + } + + return spajs.open({ menuId:'project/' + options.project_id +"/" + this.model.name+"/search/"+this.searchObjectToString(trim(query)), reopen:true}); +} + +pmPeriodicTasks.showSearchResults = function(holder, menuInfo, data) +{ + var thisObj = this; + var project_id = data.reg[1]; + + + var search = this.searchStringToObject(decodeURIComponent(data.reg[2])) + search['project'] = project_id + + return $.when(this.sendSearchQuery(search), pmProjects.loadItem(project_id)).done(function() + { + $(holder).insertTpl(spajs.just.render(thisObj.model.name+'_list', {query:decodeURIComponent(data.reg[2]), project_id:project_id})) + }).fail(function() + { + $.notify("", "error"); + }).promise(); +} + +pmPeriodicTasks.showNewItemPage = function(holder, menuInfo, data) +{ + var project_id = data.reg[1]; + var thisObj = this; + return $.when(pmTasks.searchItems(project_id, "project"), pmProjects.loadItem(project_id), pmInventories.loadAllItems()).done(function() + { + thisObj.model.newitem = {type:'INTERVAL', kind:'PLAYBOOK'} + $(holder).insertTpl(spajs.just.render(thisObj.model.name+'_new_page', {project_id:project_id})) + + $('#new_periodic-tasks_inventory').select2({ width: '100%' }); + + new autoComplete({ + selector: '#new_periodic-tasks_playbook', + minChars: 0, + cache:false, + showByClick:false, + renderItem: function(item, search) + { + return '
    ' + item.playbook + '
    '; + }, + onSelect: function(event, term, item) + { + $("#new_periodic-tasks_playbook").val($(item).text()); + //console.log('onSelect', term, item); + //var value = $(item).attr('data-value'); + }, + source: function(term, response) + { + term = term.toLowerCase(); + + var matches = [] + for(var i in pmTasks.model.items) + { + var val = pmTasks.model.items[i] + if(val.name.toLowerCase().indexOf(term) != -1 && val.project == project_id) + { + matches.push(val) + } + } + response(matches); + } + }); + }).fail(function() + { + $.notify("", "error"); + }).promise(); +} + +/** + * Для ввода пароля + * @type Object + */ +pmPeriodicTasks.filed.selectInventoryKindPlaybookGroupModuleAndTime = inheritance(filedsLib.filed.simpleText) +pmPeriodicTasks.filed.selectInventoryKindPlaybookGroupModuleAndTime.type = 'selectInventoryKindPlaybookGroupModuleAndTime' +pmPeriodicTasks.filed.selectInventoryKindPlaybookGroupModuleAndTime.getValue = function(pmObj, filed){ + return ''; +} + +pmPeriodicTasks.model.page_list = { + short_title: 'Periodic tasks', +} + +pmPeriodicTasks.model.page_item = { + buttons:[ + { + class:'btn btn-primary', + function:function(item_id, opt){ return 'spajs.showLoader('+this.model.className+'.updateItem('+item_id+', {project_id:'+opt.project_id+'})); return false;'}, + title:'Save', + link:function(){ return '#'}, + }, + { + class:'btn btn-warning', + function:function(item_id, opt){ + return "spajs.showLoader(pmPeriodicTasks.execute("+opt.project_id+", "+item_id+")); return false;" + }, + title:'Execute', + link:function(){ return '#'}, + help:'Execute' + }, + { + class:'btn btn-default copy-btn', + function:function(item_id){ return 'spajs.showLoader('+this.model.className+'.copyAndEdit('+item_id+')); return false;'}, + title:'', + link:function(){ return '#'}, + help:'Copy' + }, + { + class:'btn btn-danger danger-right', + function:function(item_id){ return 'spajs.showLoader('+this.model.className+'.deleteItem('+item_id+')); return false;'}, + title:' ', + link:function(){ return '#'}, + }, + ], + sections:[ + function(section, item_id) + { + return spajs.just.render('periodic-tasks_page_vars_section', { + item_id:item_id, + pmObj:this, + PLAYBOOK_VARS: jQuery.extend(true, {}, pmPeriodicTasks.model.items[item_id].vars), + MODULE_VARS: jQuery.extend(true, {}, pmPeriodicTasks.model.items[item_id].vars) + }) + } + ], + title: function(item_id){ + return "Periodic task "+this.model.items[item_id].justText('name') + }, + back_link: function(item_id, opt){ + return polemarch.opt.host + "/?project/" + opt.project_id + "/" + this.model.name; + }, + short_title: function(item_id){ + return this.model.items[item_id].justText('name', function(v){return v.slice(0, 20)}) + }, + fileds:[ + [ + { + filed: new filedsLib.filed.text(), + title:'Name', + name:'name', + placeholder:'Enter template name', + validator:function(value){ + return filedsLib.validator.notEmpty(value, 'Name') + }, + fast_validator:function(value){ return value != '' && value} + }, + { + filed: new filedsLib.filed.boolean(), + title:'Save in history', + name:'save_result', + help:'Save result of task in history', + }, + { + filed: new filedsLib.filed.boolean(), + title:'Enabled', + name:'enabled', + help:'', + }, + ],[ + { + filed: new pmPeriodicTasks.filed.selectInventoryKindPlaybookGroupModuleAndTime(), + name:'project', + }, + ] + ], + onUpdate:function(result) + { + return true; + }, + onBeforeSave:function(data, item_id, opt) + { + if(!opt || !opt.project_id) + { + throw "Error in pmPeriodicTasks.onBeforeSave with opt.project_id is null" + } + + data.project = opt.project_id + + data.type = $("#periodic-tasks_"+item_id+"_type").val() + data.inventory = pmPeriodicTasks.inventoriesAutocompletefiled.getValue() + + data.kind = $("#periodic-tasks_"+item_id+"_kind").val() + + if(!data.inventory) + { + $.notify("Invalid field `inventory` ", "error"); + return false; + } + + + if(data.kind == "MODULE") + { + data.mode = moduleArgsEditor.getSelectedModuleName() + if(!data.mode) + { + $.notify("Module name is empty", "error"); + return false; + } + } + else + { + data.mode = $("#periodic-tasks_"+item_id+"_playbook").val() + if(!data.mode) + { + $.notify("Playbook name is empty", "error"); + return false; + } + } + + if(data.type == "CRONTAB") + { + data.schedule = crontabEditor.getCronString() + } + else + { + data.schedule = $("#periodic-tasks_"+item_id+"_schedule_INTERVAL").val() + if(!data.schedule) + { + $.notify("Invalid field `Interval schedule` ", "error"); + return; + } + } + + data.vars = jsonEditor.jsonEditorGetValues(data.kind) + + if(data.kind == "MODULE") + { + data.vars.group = pmGroups.getGroupsAutocompleteValue() + data.vars.args = moduleArgsEditor.getModuleArgs(); + } + return data; + }, +} + +pmPeriodicTasks.showItem = function(holder, menuInfo, data) +{ + var def = new $.Deferred(); + var thisObj = this; + var item_id = data.reg[2]; + var project_id = data.reg[1]; + + $.when(pmPeriodicTasks.loadItem(item_id), pmTasks.loadAllItems(), pmInventories.loadAllItems(), pmProjects.loadItem(project_id)).done(function() + { + var tpl = thisObj.model.name+'_page' + if(!spajs.just.isTplExists(tpl)) + { + tpl = 'items_page' + } + + $(holder).insertTpl(spajs.just.render(tpl, {item_id:item_id, pmObj:thisObj, opt:{project_id:project_id}})) + pmPeriodicTasks.selectInventory(pmPeriodicTasks.model.items[item_id].inventory) + + $('#periodic-tasks_'+item_id+'_inventory').select2({ width: '100%' }); + + new autoComplete({ + selector: '#periodic-tasks_'+item_id+'_playbook', + minChars: 0, + cache:false, + showByClick:false, + renderItem: function(item, search) + { + return '
    ' + item.name + '.yaml
    '; + }, + onSelect: function(event, term, item) + { + $("#periodic-tasks_"+item_id+"_playbook").val($(item).text()); + //console.log('onSelect', term, item); + //var value = $(item).attr('data-value'); + }, + source: function(term, response) + { + term = term.toLowerCase(); + + var matches = [] + for(var i in pmTasks.model.items) + { + var val = pmTasks.model.items[i] + if(val.name.toLowerCase().indexOf(term) != -1 && val.project == project_id) + { + matches.push(val) + } + } + response(matches); + } + }); + + def.resolve(); + + }).fail(function(e) + { + $.notify("", "error"); + def.reject(e); + }) + + return def.promise() +} + + +/** + * @return $.Deferred + */ +pmPeriodicTasks.addItem = function(project_id) +{ + if(!project_id) + { + throw "Error in pmPeriodicTasks.addItem with project_id = `" + project_id + "`" + } + + var def = new $.Deferred(); + + var data = {} + + data.project = project_id + + data.name = $("#new_periodic-tasks_name").val() + data.type = $("#new_periodic-tasks_type").val() + data.inventory = pmPeriodicTasks.inventoriesAutocompletefiled.getValue() + + if(!data.name) + { + $.notify("Invalid field `name` ", "error"); + def.reject(); + return def.promise(); + } + + if(!data.inventory) + { + $.notify("Invalid field `inventory` ", "error"); + def.reject(); + return def.promise(); + } + + + data.kind = $("#new_periodic-tasks_kind").val() + + if(data.kind == "MODULE") + { + data.mode = moduleArgsEditor.getSelectedModuleName() + if(!data.mode) + { + $.notify("Module name is empty", "error"); + def.reject(); + return def.promise(); + } + } + else + { + data.mode = $("#new_periodic-tasks_playbook").val() + if(!data.mode) + { + $.notify("Playbook name is empty", "error"); + def.reject(); + return def.promise(); + } + } + + if(data.type == "CRONTAB") + { + data.schedule = crontabEditor.getCronString() + } + else + { + data.schedule = $("#new_periodic-tasks_schedule_INTERVAL").val() + if(!data.schedule) + { + $.notify("Invalid field `Interval schedule` ", "error"); + def.reject(); + return def.promise(); + } + } + + data.save_result = $("#new_periodic-tasks_save_result").hasClass('selected') + + data.vars = jsonEditor.jsonEditorGetValues(data.kind) + + if(data.kind == "MODULE") + { + data.vars.group = pmGroups.getGroupsAutocompleteValue() + data.vars.args = moduleArgsEditor.getModuleArgs(); + } + + spajs.ajax.Call({ + url: "/api/v1/"+this.model.name+"/", + type: "POST", + contentType:'application/json', + data: JSON.stringify(data), + success: function(data) + { + $.notify("periodic task created", "success"); + + $.when(spajs.open({ menuId:"project/"+project_id+"/periodic-task/"+data.id})).always(function(){ + def.resolve() + }) + }, + error:function(e) + { + polemarch.showErrors(e.responseJSON) + def.reject(e) + } + }); + return def.promise(); +} + +pmPeriodicTasks.loadItem = function(item_id) +{ + var thisObj = this; + return spajs.ajax.Call({ + url: "/api/v1/"+this.model.name+"/"+item_id+"/", + type: "GET", + contentType:'application/json', + data: "", + success: function(data) + { + if(data.kind == "MODULE") + { + if(data && data.vars && data.vars.group !== undefined) + { + data.group = data.vars.group + delete data.vars.group + } + + if(data && data.vars && data.vars.args !== undefined) + { + data.args = data.vars.args + delete data.vars.args + } + } + thisObj.model.items.justWatch(item_id) + thisObj.model.items[item_id] = data + }, + error:function(e) + { + console.warn(e) + polemarch.showErrors(e) + } + }); +} + +tabSignal.connect("polemarch.start", function() +{ + // tasks + spajs.addMenu({ + id:"PeriodicTasks", + urlregexp:[/^project\/([0-9]+)\/periodic-tasks$/, /^project\/([0-9]+)\/periodic-task$/, /^project\/([0-9]+)\/periodic-tasks\/page\/([0-9]+)$/], + onOpen:function(holder, menuInfo, data){return pmPeriodicTasks.showList(holder, menuInfo, data);} + }) + + spajs.addMenu({ + id:"PeriodicTasks-search", + urlregexp:[/^project\/([0-9]+)\/periodic-tasks\/search\/([A-z0-9 %\-.:,=]+)$/, /^project\/([0-9]+)\/periodic-tasks\/search\/([A-z0-9 %\-.:,=]+)\/page\/([0-9]+)$/], + onOpen:function(holder, menuInfo, data){return pmPeriodicTasks.showSearchResults(holder, menuInfo, data);} + }) + + spajs.addMenu({ + id:"PeriodicTask", + urlregexp:[/^project\/([0-9]+)\/periodic-task\/([0-9]+)$/], + onOpen:function(holder, menuInfo, data){return pmPeriodicTasks.showItem(holder, menuInfo, data);} + }) + + spajs.addMenu({ + id:"newPeriodicTask", + urlregexp:[/^project\/([0-9]+)\/new-periodic-tasks$/], + onOpen:function(holder, menuInfo, data){return pmPeriodicTasks.showNewItemPage(holder, menuInfo, data);} + }) + }) \ No newline at end of file diff --git a/polemarch/static/js/pmProjects.js b/polemarch/static/js/pmProjects.js index c3ca1f3b..03b48932 100644 --- a/polemarch/static/js/pmProjects.js +++ b/polemarch/static/js/pmProjects.js @@ -1,607 +1,698 @@ - -var pmProjects = inheritance(pmItems) - -pmProjects.model.name = "projects" -pmProjects.model.page_name = "project" -pmProjects.model.className = "pmProjects" -pmProjects.model.bulk_name = "project" - -jsonEditor.options[pmProjects.model.name] = {}; -pmProjects.model.selectedInventory = 0 - -jsonEditor.options[pmProjects.model.name]['repo_password'] = { - type:'password', - help:'Password from repository', - helpcontent:'Password from repository required for GIT' -} - -/** - * Для ввода пароля - * @type Object - */ -pmProjects.filed.selectRepositoryType = inheritance(filedsLib.filed.simpleText) -pmProjects.filed.selectRepositoryType.type = 'selectRepositoryType' -pmProjects.filed.selectRepositoryType.getValue = function(pmObj, filed){ - return ''; -} - - -/** - * Вызывается после загрузки информации об элементе но до его вставки в любые массивы. - * Должна вернуть отредактированый или не изменный элемент - * @param {object} item загруженный с сервера элемента - * @returns {object} обработаный элемент - */ -pmProjects.afterItemLoad = function(item) -{ - if(item.status == "WAIT_SYNC" && item.revision == "ERROR") - { - item.revision = "WAIT SYNC" - } - return item; -} - -pmProjects.inventoriesAutocompletefiled = new pmInventories.filed.inventoriesAutocomplete() - -/** - * Описывает как формировать страницу списка элементов - * @type object - */ -pmProjects.model.page_list = { - // Массив для описания кнопок в верху страницы - buttons:[ - { - class:'btn btn-primary', // Класс - //function:function(){ return "spajs.open({ menuId:'new-"+this.model.page_name+"'}); return false;"}, // То что подставится в шаблон на onclick - onclick:function(){ spajs.open({ menuId:"new-"+this.model.page_name}); return false;}, // Функция вызываемая на onclick - title:'Create', // Текст на кнопке - link:function(){ return '/?new-'+this.model.page_name}, // То что попадёт в href - help:'' // Текст для поля подсказки title - }, - ], - title: "Projects", // Текст заголовка страницы - short_title: "Projects", // Короткий текст заголовка страницы - // Описание полей в списке элементов - fileds:[ - { - title:'Name', // Текст в заголовке - name:'name', // Имя поля в объекте из которого надо взять значение - }, - { - title:'Status', - name:'status', - /** - * Стиль элемента td в таблице - * @param {object} item объект для которого строится строка - * @param {object} opt объект доп параметров переданных в шаблон - * @returns {String} Стиль элемента td в таблице - */ - style:function(item, opt){ return 'style="width: 110px"'}, - /** - * Класс элемента td в таблице - * @param {object} item объект для которого строится строка - * @param {object} opt объект доп параметров переданных в шаблон - * @returns {String} Класс элемента td в таблице - */ - class:function(item, opt) - { - if(!item || !item.id) - { - return 'class="hidden-xs hidden-sm"'; - } - - return 'class="hidden-xs hidden-sm project-status ' - + this.model.items[item.id].justClassName('status', function(v){ return "project-status-"+v})+'"' - }, - /** - * Значение для ячейки в таблице - * @param {object} item объект для которого строится строка - * @param {String} filed_name имя поля - * @param {object} opt объект доп параметров переданных в шаблон - * @returns {String} Значение для ячейки в таблице - */ - value:function(item, filed_name, opt){ - return this.model.items[item.id].justText(filed_name) - }, - } - ], - // Список действий которые можно совершить из страницы просмотра списка - actions:[ - { - /** - * Функция для onclick - * @param {object} item объект для которого строится строка - * @param {object} opt объект доп параметров переданных в шаблон - * @returns {String} Функция для onclick - */ - function:function(item){ return 'spajs.showLoader('+this.model.className+'.syncRepo('+item.id+')); return false;'}, - /** - * Текст заголовка кнопки - */ - title:'Sync', - /** - * Функция для href - * @param {object} item объект для которого строится строка - * @param {object} opt объект доп параметров переданных в шаблон - * @returns {String} Функция для href - */ - link:function(){ return '#'} - }, - { - // separator - }, - { - function:function(item){ return "spajs.open({ menuId:'project/"+item.id+"/playbook/run'}); return false;"}, - title:'Run playbook', - link:function(){ return '#'} - }, - { - function:function(item){ return "spajs.open({ menuId:'project/"+item.id+"/ansible-module/run'}); return false;"}, - title:'Run ansible module', - link:function(){ return '#'} - }, - { - function:function(item){ return "spajs.open({ menuId:'project/"+item.id+"/periodic-tasks'}); return false;"}, - title:'Periodic tasks', - link:function(){ return '#'} - }, - { - function:function(item){ return "spajs.open({ menuId:'project/"+item.id+"/history'}); return false;"}, - title:'History', - link:function(){ return '#'} - }, - { - // separator - }, - { - class:'btn btn-danger', - function:function(item){ return 'spajs.showLoader('+this.model.className+'.deleteItem('+item.id+')); return false;'}, - title:'Delete', - link:function(){ return '#'} - }, - ] -} - -/** - * Описывает как формировать страницу создания элемента - * @type object - */ -pmProjects.model.page_new = { - title: "New project", // Текст заголовка страницы - short_title: "New project", // Короткий текст заголовка страницы - /** - * Содержит массив с массивами описаний полей в списке элементов - * Масиивы представляют собой блоки строк в которые вставляются поля ввода - * @type Array - */ - fileds:[ - [ - /** - * Поле ввода - * @todo В целом в следующем приступе неудержимого рефакторинга будет правильнее - * все параметры кроме filed перенести в конструктор поля filed - * и тогда массив fileds будет содержать только экземпляры filedsLib.filed.* или его наследников - */ - { - filed: new filedsLib.filed.text(), // Объект поля ввода - title:'Name', // Заголовок - name:'name', // Имя поля из которого брать значение - placeholder:'Project name', // Подсказка - help:'', // Подсказка - validator:function(value){ // Функция валидации должна вернуть true или false+вывод сообщения об ошибке - return filedsLib.validator.notEmpty(value, 'Name') - }, - fast_validator:function(value){ return value != '' && value} // Функция быстрой валидации должна вернуть true или false - }, - { - filed: new pmProjects.filed.selectRepositoryType(), - name:'repository', - }, - ] - ], - /** - * Список дополнительных блоков которые надо вставить в страницу - * @type Array - */ - sections:[ - /** - * @returns {String} Текст шаблона для вставки дополнительных блоков в страницу - */ - // function(){ return ''} - ], - /** - * Функция вызываемая до сохранения объекта, должна вернуть объект - * отправляемый на сохранение можно изменённый или вернуть false для того чтоб отменить сохранение. - * @param {object} data объект отправляемый на сохранение - * @returns {object|boolean} - */ - onBeforeSave:function(data) - { - data.repository = $("#new_project_repository").val() - data.vars = { - repo_type:$("#new_project_type").val(), - repo_password:$("#new_project_password").val(), - } - - if(!data.repository) - { - if(data.vars.repo_type == "MANUAL") - { - data.repository = "MANUAL" - } - else - { - $.notify("Invalid value in field `Repository URL`", "error"); - return false; - } - } - - return data; - }, - /** - * Функция вызываемая после сохранения объекта - * @param {object} result - * @returns {function|boolean} - */ - onCreate:function(result) - { - var def = new $.Deferred(); - $.notify("Project created", "success"); - $.when(spajs.open({ menuId:this.model.page_name+"/"+result.id})).always(function(){ - def.resolve() - }) - - return def.promise(); - } -} - - -/** - * Описывает как формировать страницу редактирования элемента - * @type object - */ -pmProjects.model.page_item = { - // Массив для описания кнопок в верху страницы - buttons:[ - { - class:'btn btn-primary', - /** - * @param {Integer} item_id Идентификатор редактируемого элемента - * @returns {String} То что подставится в шаблон на onclick - */ - function:function(item_id){ return 'spajs.showLoader('+this.model.className+'.updateItem('+item_id+')); return false;'}, - title:'Save', - link:function(){ return '#'}, - }, - { - class:'btn btn-warning', - function:function(item_id){ return 'spajs.showLoader('+this.model.className+'.syncRepo('+item_id+')); return false;'}, - title:' Sync', - link:function(){ return '#'}, - help:'Sync' - }, - { - class:'btn btn-info', - function:function(item_id){ return 'return spajs.openURL(this.href);'}, - title:' Run playbook', - link:function(item_id){ return polemarch.opt.host +'/?project/'+ item_id + '/playbook/run'}, - help:'Run playbook' - }, - { - class:'btn btn-info', - function:function(item_id){ return 'return spajs.openURL(this.href);'}, - title:' Run module', - link:function(item_id){ return polemarch.opt.host +'/?project/'+ item_id + '/ansible-module/run'}, - help:'Run module' - }, - { - class:'btn btn-info', - function:function(item_id){ return 'return spajs.openURL(this.href);'}, - title:' Periodic tasks', - link:function(item_id){ return polemarch.opt.host +'/?project/'+ item_id + '/periodic-tasks'}, - help:'Periodic tasks' - }, - { - class:'btn btn-info', - function:function(item_id){ return 'return spajs.openURL(this.href);'}, - title:' History', - link:function(item_id){ return polemarch.opt.host +'/?project/'+ item_id + '/history'}, - help:'history' - }, - { - tpl:function(item_id){ - return spajs.just.render('pmTasksTemplates_btn_importFromFile', {item_id:item_id}) - }, - }, - { - class:'btn btn-danger danger-right', - function:function(item_id){ return 'spajs.showLoader('+this.model.className+'.deleteItem('+item_id+')); return false;'}, - title:' ', - link:function(){ return '#'}, - }, - ], - sections:[], - /** - * @param {Integer} item_id Идентификатор редактируемого элемента - * @returns {String} То что подставится в шаблон на title - */ - title: function(item_id){ - return "Project "+this.model.items[item_id].justText('name') - }, - /** - * @param {Integer} item_id Идентификатор редактируемого элемента - * @returns {String} То что подставится в шаблон на short_title - */ - short_title: function(item_id){ - return "Project "+this.model.items[item_id].justText('name', function(v){return v.slice(0, 20)}) - }, - fileds:[ - [ - { - filed: new filedsLib.filed.text(), - title:'Name', - name:'name', - placeholder:'Enter project name', - validator:function(value){ - return filedsLib.validator.notEmpty(value, 'Name') - }, - fast_validator:function(value){ return value != '' && value} - }, - { - filed: new pmProjects.filed.selectRepositoryType(), - name:'repository', - }, - { - filed: new filedsLib.filed.disabled(), - name:'revision', - title:'Revision', - }, - { - filed: new filedsLib.filed.disabled(), - name:'status', - title:'Status', - }, - ] - ], - /** - * Функция вызываемая после сохранения объекта - * @param {object} result - * @returns {function|boolean} - */ - onUpdate:function(result) - { - return true; - }, - onBeforeSave:function(data, item_id) - { - data.repository = $("#project_"+item_id+"_repository").val() - - data.vars = { - repo_type:$("#project_"+item_id+"_type").val(), - repo_password:$("#project_"+item_id+"_password").val(), - } - - if(!data.repository) - { - if(data.vars.repo_type == "MANUAL") - { - data.repository = "MANUAL" - } - else - { - $.notify("Invalid value in field `Repository URL`", "error"); - return false; - } - } - - delete data.revision - delete data.repository - return data; - }, -} - -pmProjects.startUpdateProjectItem = function(item_id) -{ - var thisObj = this; - if(thisObj.model.items[item_id].status == "WAIT_SYNC" || thisObj.model.items[item_id].status == "SYNC") - { - thisObj.model.updateTimeoutId = setTimeout(function() - { - $.when(thisObj.loadItem(item_id)).always(function() - { - thisObj.startUpdateProjectItem(item_id) - }) - }, 5000) - } -} - -pmProjects.openItem = function(holder, menuInfo, data) -{ - var item_id = data.reg[1] - var def = new $.Deferred(); - $.when(pmProjects.supportedRepos()).always(function() - { - $.when(pmProjects.showItem(holder, menuInfo, data)) .always(function() - { - pmProjects.startUpdateProjectItem(item_id) - def.resolve(); - }) - }).promise(); - - return def.promise(); -} - -pmProjects.openNewItemPage = function(holder, menuInfo, data) -{ - var def = new $.Deferred(); - $.when(pmProjects.supportedRepos()).always(function() - { - $.when(pmProjects.showNewItemPage(holder, menuInfo, data)) .always(function() - { - def.resolve(); - }) - }) - - return def.promise(); -} - -/** - * Берёт данные со страницы "run playbook options" ( /?project/1/playbook/run ) для проекта и запускает выполнение Playbook - * @returns {$.Deferred} - */ -pmProjects.executePlaybook = function(project_id) -{ - var data_vars = jsonEditor.jsonEditorGetValues(); - data_vars.limit = pmGroups.getGroupsAutocompleteValue(); - return pmTasks.execute(project_id, pmProjects.inventoriesAutocompletefiled.getValue(), $('#playbook-autocomplete').val(), data_vars); -} - -/** - * Строит страницу "run playbook options" ( /?project/1/playbook/run ) для проекта - * @returns {$.Deferred} - */ -pmProjects.openRunPlaybookPage = function(holder, menuInfo, data) -{ - var def = new $.Deferred(); - var thisObj = this; - var project_id = data.reg[1] - $.when(pmTasks.searchItems(project_id, "project"), pmProjects.loadItem(project_id), pmInventories.loadAllItems()).done(function(results) - { - $(holder).insertTpl(spajs.just.render(thisObj.model.name+'_run_playbook', {item_id:project_id, query:project_id})) - - //$("#inventories-autocomplete").select2({ width: '100%' }); - - new autoComplete({ - selector: '#playbook-autocomplete', - minChars: 0, - cache:false, - showByClick:false, - menuClass:'playbook-autocomplete', - renderItem: function(item, search) - { - return '
    ' + item.playbook + '
    '; - }, - onSelect: function(event, term, item) - { - $("#playbook-autocomplete").val($(item).text()); - //console.log('onSelect', term, item); - //var value = $(item).attr('data-value'); - }, - source: function(term, response) - { - term = term.toLowerCase(); - - var matches = [] - for(var i in results[0].results) - { - var val = pmTasks.model.itemslist.results[i] - if(val.name.toLowerCase().indexOf(term) != -1 && val.project == project_id && val.name.toLowerCase() != term) - { - matches.push(val) - } - } - - if(matches.length) - { - response(matches); - } - } - }); - - def.resolve(); - }).fail(function(e) - { - def.reject(e); - }) - - return def.promise(); -} - -/** - * @return $.Deferred - */ -pmProjects.syncRepo = function(item_id) -{ - return spajs.ajax.Call({ - url: "/api/v1/projects/"+item_id+"/sync/", - type: "POST", - contentType:'application/json', - success: function(data) - { - $.notify("Send sync query", "success"); - }, - error:function(e) - { - console.warn("project "+item_id+" sync error - " + JSON.stringify(e)); - polemarch.showErrors(e.responseJSON) - } - }); -} - -/** - * @return $.Deferred - */ -pmProjects.supportedRepos = function() -{ - return spajs.ajax.Call({ - url: "/api/v1/projects/supported-repos/", - type: "GET", - contentType:'application/json', - success: function(data) - { - pmProjects.model.supportedRepos = data; - pmProjects.model.repository_type = data[0] - jsonEditor.options['projects'].repo_type = { - type:'select', - options:pmProjects.model.supportedRepos, - required:true, - } - }, - error:function(e) - { - console.warn("supportedRepos error - " + JSON.stringify(e)); - } - }); -} - -tabSignal.connect("polemarch.start", function() -{ - // projects - spajs.addMenu({ - id:"projects", - urlregexp:[/^projects$/, /^projects\/search\/?$/, /^project$/, /^projects\/page\/([0-9]+)$/], - onOpen:function(holder, menuInfo, data){return pmProjects.showUpdatedList(holder, menuInfo, data);}, - onClose:function(){return pmProjects.stopUpdates();}, - }) - - spajs.addMenu({ - id:"projects-search", - urlregexp:[/^projects\/search\/([A-z0-9 %\-.:,=]+)$/], - onOpen:function(holder, menuInfo, data){return pmProjects.showSearchResults(holder, menuInfo, data);} - }) - - spajs.addMenu({ - id:"project", - urlregexp:[/^project\/([0-9]+)$/, /^projects\/([0-9]+)$/], - onOpen:function(holder, menuInfo, data){return pmProjects.openItem(holder, menuInfo, data);}, - onClose:function(){return pmHistory.stopUpdates();}, - }) - - spajs.addMenu({ - id:"newProject", - urlregexp:[/^new-project$/], - onOpen:function(holder, menuInfo, data){return pmProjects.openNewItemPage(holder, menuInfo, data);} - }) - - spajs.addMenu({ - id:"project-run-playbook", - urlregexp:[/^project\/([0-9]+)\/playbook\/run$/], - onOpen:function(holder, menuInfo, data){return pmProjects.openRunPlaybookPage(holder, menuInfo, data);} - }) - - spajs.addMenu({ - id:"project-ansible-module-run", - urlregexp:[/^project\/([0-9]+)\/ansible-module\/run$/], - onOpen:function(holder, menuInfo, data){return pmAnsibleModule.showInProject(holder, menuInfo, data);} - }) + +var pmProjects = inheritance(pmItems) + +pmProjects.model.name = "projects" +pmProjects.model.page_name = "project" +pmProjects.model.className = "pmProjects" +pmProjects.model.bulk_name = "project" + +jsonEditor.options[pmProjects.model.name] = {}; +pmProjects.model.selectedInventory = 0 + +jsonEditor.options[pmProjects.model.name]['repo_password'] = { + type:'password', + help:'Password from repository', + helpcontent:'Password from repository required for GIT' +} + +/** + * Для ввода пароля + * @type Object + */ +pmProjects.filed.selectRepositoryType = inheritance(filedsLib.filed.simpleText) +pmProjects.filed.selectRepositoryType.type = 'selectRepositoryType' +pmProjects.filed.selectRepositoryType.getValue = function(pmObj, filed){ + return ''; +} + + +/** + * Вызывается после загрузки информации об элементе но до его вставки в любые массивы. + * Должна вернуть отредактированый или не изменный элемент + * @param {object} item загруженный с сервера элемента + * @returns {object} обработаный элемент + */ +pmProjects.afterItemLoad = function(item) +{ + if(item.status == "WAIT_SYNC" && item.revision == "ERROR") + { + item.revision = "WAIT SYNC" + } + return item; +} + +pmProjects.inventoriesAutocompletefiled = new pmInventories.filed.inventoriesAutocomplete() + +/** + * Описывает как формировать страницу списка элементов + * @type object + */ +pmProjects.model.page_list = { + // Массив для описания кнопок в верху страницы + buttons:[ + { + class:'btn btn-primary', // Класс + //function:function(){ return "spajs.open({ menuId:'new-"+this.model.page_name+"'}); return false;"}, // То что подставится в шаблон на onclick + onclick:function(){ spajs.open({ menuId:"new-"+this.model.page_name}); return false;}, // Функция вызываемая на onclick + title:'Create', // Текст на кнопке + link:function(){ return '/?new-'+this.model.page_name}, // То что попадёт в href + help:'' // Текст для поля подсказки title + }, + ], + title: "Projects", // Текст заголовка страницы + short_title: "Projects", // Короткий текст заголовка страницы + // Описание полей в списке элементов + fileds:[ + { + title:'Name', // Текст в заголовке + name:'name', // Имя поля в объекте из которого надо взять значение + }, + { + title:'Status', + name:'status', + /** + * Стиль элемента td в таблице + * @param {object} item объект для которого строится строка + * @param {object} opt объект доп параметров переданных в шаблон + * @returns {String} Стиль элемента td в таблице + */ + style:function(item, opt){ return 'style="width: 110px"'}, + /** + * Класс элемента td в таблице + * @param {object} item объект для которого строится строка + * @param {object} opt объект доп параметров переданных в шаблон + * @returns {String} Класс элемента td в таблице + */ + class:function(item, opt) + { + if(!item || !item.id) + { + return 'class="hidden-xs hidden-sm"'; + } + + return 'class="hidden-xs hidden-sm project-status ' + + this.model.items[item.id].justClassName('status', function(v){ return "project-status-"+v})+'"' + }, + /** + * Значение для ячейки в таблице + * @param {object} item объект для которого строится строка + * @param {String} filed_name имя поля + * @param {object} opt объект доп параметров переданных в шаблон + * @returns {String} Значение для ячейки в таблице + */ + value:function(item, filed_name, opt){ + return this.model.items[item.id].justText(filed_name) + }, + } + ], + // Список действий которые можно совершить из страницы просмотра списка + actions:[ + { + /** + * Функция для onclick + * @param {object} item объект для которого строится строка + * @param {object} opt объект доп параметров переданных в шаблон + * @returns {String} Функция для onclick + */ + function:function(item){ return 'spajs.showLoader('+this.model.className+'.syncRepo('+item.id+')); return false;'}, + /** + * Текст заголовка кнопки + */ + title:'Sync', + /** + * Функция для href + * @param {object} item объект для которого строится строка + * @param {object} opt объект доп параметров переданных в шаблон + * @returns {String} Функция для href + */ + link:function(){ return '#'} + }, + { + // separator + }, + { + function:function(item){ return "spajs.open({ menuId:'project/"+item.id+"/playbook/run'}); return false;"}, + title:'Run playbook', + link:function(){ return '#'} + }, + { + function:function(item){ return "spajs.open({ menuId:'project/"+item.id+"/ansible-module/run'}); return false;"}, + title:'Run ansible module', + link:function(){ return '#'} + }, + { + function:function(item){ return "spajs.open({ menuId:'project/"+item.id+"/periodic-tasks'}); return false;"}, + title:'Periodic tasks', + link:function(){ return '#'} + }, + { + function:function(item){ return "spajs.open({ menuId:'project/"+item.id+"/history'}); return false;"}, + title:'History', + link:function(){ return '#'} + }, + { + // separator + }, + { + class:'btn btn-danger', + function:function(item){ return 'spajs.showLoader('+this.model.className+'.deleteItem('+item.id+')); return false;'}, + title:'Delete', + link:function(){ return '#'} + }, + ] +} + +/** + * Описывает как формировать страницу создания элемента + * @type object + */ +pmProjects.model.page_new = { + title: "New project", // Текст заголовка страницы + short_title: "New project", // Короткий текст заголовка страницы + /** + * Содержит массив с массивами описаний полей в списке элементов + * Масиивы представляют собой блоки строк в которые вставляются поля ввода + * @type Array + */ + fileds:[ + [ + /** + * Поле ввода + * @todo В целом в следующем приступе неудержимого рефакторинга будет правильнее + * все параметры кроме filed перенести в конструктор поля filed + * и тогда массив fileds будет содержать только экземпляры filedsLib.filed.* или его наследников + */ + { + filed: new filedsLib.filed.text(), // Объект поля ввода + title:'Name', // Заголовок + name:'name', // Имя поля из которого брать значение + placeholder:'Project name', // Подсказка + help:'', // Подсказка + validator:function(value){ // Функция валидации должна вернуть true или false+вывод сообщения об ошибке + return filedsLib.validator.notEmpty(value, 'Name') + }, + fast_validator:function(value){ return value != '' && value} // Функция быстрой валидации должна вернуть true или false + }, + { + filed: new pmProjects.filed.selectRepositoryType(), + name:'repository', + }, + ] + ], + /** + * Список дополнительных блоков которые надо вставить в страницу + * @type Array + */ + sections:[ + /** + * @returns {String} Текст шаблона для вставки дополнительных блоков в страницу + */ + // function(){ return ''} + ], + /** + * Функция вызываемая до сохранения объекта, должна вернуть объект + * отправляемый на сохранение можно изменённый или вернуть false для того чтоб отменить сохранение. + * @param {object} data объект отправляемый на сохранение + * @returns {object|boolean} + */ + onBeforeSave:function(data) + { + data.repository = $("#new_project_repository").val() + data.vars = { + repo_type:$("#new_project_type").val(), + //repo_password:$("#new_project_password").val(), + } + + if(data.vars.repo_type == "GIT") + { + if($("#new_project_branch").val().trim()!="") + { + data.vars.repo_branch=$("#new_project_branch").val().trim(); + } + + if($("#new_project_password").val().trim()!="") + { + data.vars.repo_password=$("#new_project_password").val().trim(); + } + } + + + if(!data.repository) + { + if(data.vars.repo_type == "MANUAL") + { + data.repository = "MANUAL" + } + else + { + $.notify("Invalid value in field `Repository URL`", "error"); + return false; + } + } + + return data; + }, + /** + * Функция вызываемая после сохранения объекта + * @param {object} result + * @returns {function|boolean} + */ + onCreate:function(result) + { + var def = new $.Deferred(); + $.notify("Project created", "success"); + $.when(spajs.open({ menuId:this.model.page_name+"/"+result.id})).always(function(){ + def.resolve() + }) + + return def.promise(); + } +} + + +/** + * Описывает как формировать страницу редактирования элемента + * @type object + */ +pmProjects.model.page_item = { + // Массив для описания кнопок в верху страницы + buttons:[ + { + class:'btn btn-primary', + /** + * @param {Integer} item_id Идентификатор редактируемого элемента + * @returns {String} То что подставится в шаблон на onclick + */ + function:function(item_id){ return 'spajs.showLoader($.when('+this.model.className+'.updateItem('+item_id+')).done(function() {return spajs.openURL("'+window.location.href+'");})); return false;'}, + title:'Save', + link:function(){ return '#'}, + }, + { + class:'btn btn-warning', + function:function(item_id){ return 'spajs.showLoader('+this.model.className+'.syncRepoFromProjectPage('+item_id+')); return false;'}, + title:' Sync', + link:function(){ return '#'}, + help:'Sync' + }, + { + class:'btn btn-info', + function:function(item_id){ return 'return spajs.openURL(this.href);'}, + title:' Run playbook', + link:function(item_id){ return polemarch.opt.host +'/?project/'+ item_id + '/playbook/run'}, + help:'Run playbook' + }, + { + class:'btn btn-info', + function:function(item_id){ return 'return spajs.openURL(this.href);'}, + title:' Run module', + link:function(item_id){ return polemarch.opt.host +'/?project/'+ item_id + '/ansible-module/run'}, + help:'Run module' + }, + { + class:'btn btn-info', + function:function(item_id){ return 'return spajs.openURL(this.href);'}, + title:' Periodic tasks', + link:function(item_id){ return polemarch.opt.host +'/?project/'+ item_id + '/periodic-tasks'}, + help:'Periodic tasks' + }, + { + class:'btn btn-info', + function:function(item_id){ return 'return spajs.openURL(this.href);'}, + title:' History', + link:function(item_id){ return polemarch.opt.host +'/?project/'+ item_id + '/history'}, + help:'history' + }, + { + tpl:function(item_id){ + return spajs.just.render('pmTasksTemplates_btn_importFromFile', {item_id:item_id}) + }, + }, + { + class:'btn btn-danger danger-right', + function:function(item_id){ return 'spajs.showLoader('+this.model.className+'.deleteItem('+item_id+')); return false;'}, + title:' ', + link:function(){ return '#'}, + }, + ], + sections:[], + /** + * @param {Integer} item_id Идентификатор редактируемого элемента + * @returns {String} То что подставится в шаблон на title + */ + title: function(item_id){ + return "Project "+this.model.items[item_id].justText('name') + }, + /** + * @param {Integer} item_id Идентификатор редактируемого элемента + * @returns {String} То что подставится в шаблон на short_title + */ + short_title: function(item_id){ + return "Project "+this.model.items[item_id].justText('name', function(v){return v.slice(0, 20)}) + }, + fileds:[ + [ + { + filed: new filedsLib.filed.text(), + title:'Name', + name:'name', + placeholder:'Enter project name', + validator:function(value){ + return filedsLib.validator.notEmpty(value, 'Name') + }, + fast_validator:function(value){ return value != '' && value} + }, + { + filed: new pmProjects.filed.selectRepositoryType(), + name:'repository', + }, + { + filed: new filedsLib.filed.disabled(), + name:'revision', + title:'Revision', + }, + { + filed: new filedsLib.filed.disabled(), + name:'status', + title:'Status', + }, + ] + ], + /** + * Функция вызываемая после сохранения объекта + * @param {object} result + * @returns {function|boolean} + */ + onUpdate:function(result) + { + return true; + }, + onBeforeSave:function(data, item_id) + { + data.repository = $("#project_"+item_id+"_repository").val() + + data.vars = { + repo_type:$("#project_"+item_id+"_type").val(), + //repo_password:$("#project_"+item_id+"_password").val(), + } + + if(data.vars.repo_type=="GIT") + { + if($("#project_"+item_id+"_branch").val().trim()!="") + { + + data.vars.repo_branch=$("#project_"+item_id+"_branch").val().trim(); + } + else + { + $.notify("Branch name is empty", "error"); + return false; + } + + if($("#project_"+item_id+"_password").val().trim()!="") + { + data.vars.repo_password=$("#project_"+item_id+"_password").val().trim(); + } + } + + if(!data.repository) + { + if(data.vars.repo_type == "MANUAL") + { + data.repository = "MANUAL" + } + else + { + $.notify("Invalid value in field `Repository URL`", "error"); + return false; + } + } + + delete data.revision + delete data.repository + return data; + }, +} + +pmProjects.startUpdateProjectItem = function(item_id) +{ + var thisObj = this; + + if(thisObj.model.items[item_id].status == "WAIT_SYNC" || thisObj.model.items[item_id].status == "SYNC") + { + thisObj.model.updateTimeoutId = setTimeout(function() + { + $.when(thisObj.loadItem(item_id)).always(function() + { + thisObj.startUpdateProjectItem(item_id) + }) + }, 5000) + } + + if(thisObj.model.items[item_id].status != "WAIT_SYNC" || thisObj.model.items[item_id].status != "SYNC") + { + $("#branch_block").empty(); + $("#branch_block").html(pmProjects.renderBranchInput(item_id)); + } + +} + +pmProjects.openItem = function(holder, menuInfo, data) +{ + var item_id = data.reg[1] + var def = new $.Deferred(); + $.when(pmProjects.supportedRepos()).always(function() + { + $.when(pmProjects.showItem(holder, menuInfo, data)) .always(function() + { + pmProjects.startUpdateProjectItem(item_id) + def.resolve(); + }) + }).promise(); + + return def.promise(); +} + +pmProjects.openNewItemPage = function(holder, menuInfo, data) +{ + var def = new $.Deferred(); + $.when(pmProjects.supportedRepos()).always(function() + { + $.when(pmProjects.showNewItemPage(holder, menuInfo, data)) .always(function() + { + def.resolve(); + }) + }) + + return def.promise(); +} + +/** + * Берёт данные со страницы "run playbook options" ( /?project/1/playbook/run ) для проекта и запускает выполнение Playbook + * @returns {$.Deferred} + */ +pmProjects.executePlaybook = function(project_id) +{ + var data_vars = jsonEditor.jsonEditorGetValues(); + data_vars.limit = pmGroups.getGroupsAutocompleteValue(); + return pmTasks.execute(project_id, pmProjects.inventoriesAutocompletefiled.getValue(), $('#playbook-autocomplete').val(), $('#groups_autocomplete_filedprefix').val(), data_vars); +} + +/** + * Строит страницу "run playbook options" ( /?project/1/playbook/run ) для проекта + * @returns {$.Deferred} + */ +pmProjects.openRunPlaybookPage = function(holder, menuInfo, data) +{ + var def = new $.Deferred(); + var thisObj = this; + var project_id = data.reg[1] + $.when(pmTasks.searchItems(project_id, "project"), pmProjects.loadItem(project_id), pmInventories.loadAllItems()).done(function(results) + { + $(holder).insertTpl(spajs.just.render(thisObj.model.name+'_run_playbook', {item_id:project_id, query:project_id})) + + //$("#inventories-autocomplete").select2({ width: '100%' }); + + new autoComplete({ + selector: '#playbook-autocomplete', + minChars: 0, + cache:false, + showByClick:false, + menuClass:'playbook-autocomplete', + renderItem: function(item, search) + { + return '
    ' + item.playbook + '
    '; + }, + onSelect: function(event, term, item) + { + $("#playbook-autocomplete").val($(item).text()); + //console.log('onSelect', term, item); + //var value = $(item).attr('data-value'); + }, + source: function(term, response) + { + term = term.toLowerCase(); + + var matches = [] + for(var i in results[0].results) + { + var val = pmTasks.model.itemslist.results[i] + if(val.name.toLowerCase().indexOf(term) != -1 && val.project == project_id && val.name.toLowerCase() != term) + { + matches.push(val) + } + } + + if(matches.length) + { + response(matches); + } + } + }); + + def.resolve(); + }).fail(function(e) + { + def.reject(e); + }) + + return def.promise(); +} + +/** + * @return $.Deferred + */ +pmProjects.syncRepo = function(item_id) +{ + return spajs.ajax.Call({ + url: "/api/v1/projects/"+item_id+"/sync/", + type: "POST", + contentType:'application/json', + success: function(data) + { + $.notify("Send sync query", "success"); + }, + error:function(e) + { + console.warn("project "+item_id+" sync error - " + JSON.stringify(e)); + polemarch.showErrors(e.responseJSON) + } + }); + +} + +pmProjects.syncRepoFromProjectPage = function(item_id) +{ + var thisObj = this; + thisObj.model.items[item_id].status = "WAIT_SYNC"; + pmProjects.startUpdateProjectItem(item_id); + + return spajs.ajax.Call({ + url: "/api/v1/projects/"+item_id+"/sync/", + type: "POST", + contentType:'application/json', + success: function(data) + { + $.notify("Send sync query", "success"); + }, + error:function(e) + { + console.warn("project "+item_id+" sync error - " + JSON.stringify(e)); + polemarch.showErrors(e.responseJSON) + } + }); + +} + +/** + * @return $.Deferred + */ +pmProjects.supportedRepos = function() +{ + return spajs.ajax.Call({ + url: "/api/v1/projects/supported-repos/", + type: "GET", + contentType:'application/json', + success: function(data) + { + pmProjects.model.supportedRepos = data; + pmProjects.model.repository_type = data[0] + jsonEditor.options['projects'].repo_type = { + type:'select', + options:pmProjects.model.supportedRepos, + required:true, + } + }, + error:function(e) + { + console.warn("supportedRepos error - " + JSON.stringify(e)); + } + }); +} + + +pmProjects.clearAndPasteBranchInput = function(item_id, thisInput) +{ + $(thisInput).empty(); + $(thisInput).val(pmProjects.model.items[item_id].branch); + pmProjects.checkBranchInput(item_id); +} + +pmProjects.checkBranchInput = function(item_id) +{ + var branchInputValue=$("#project_"+pmProjects.model.items[item_id].id+"_branch").val().trim(); + if(branchInputValue!=pmProjects.model.items[item_id].vars.repo_branch) + { + pmProjects.model.items[item_id].vars.repo_branch=branchInputValue; + $("#branch_block").empty(); + var html=pmProjects.renderBranchInput(item_id); + $("#branch_block").html(html); + } +} + +pmProjects.renderBranchInput = function(item_id) +{ + var html=spajs.just.render('branch_input', {item_id:item_id, pmObj:pmProjects}); + return html; +} + +tabSignal.connect("polemarch.start", function() +{ + // projects + spajs.addMenu({ + id:"projects", + urlregexp:[/^projects$/, /^projects\/search\/?$/, /^project$/, /^projects\/page\/([0-9]+)$/], + onOpen:function(holder, menuInfo, data){return pmProjects.showUpdatedList(holder, menuInfo, data);}, + onClose:function(){return pmProjects.stopUpdates();}, + }) + + spajs.addMenu({ + id:"projects-search", + urlregexp:[/^projects\/search\/([A-z0-9 %\-.:,=]+)$/, /^projects\/search\/([A-z0-9 %\-.:,=]+)\/page\/([0-9]+)$/], + onOpen:function(holder, menuInfo, data){return pmProjects.showSearchResults(holder, menuInfo, data);} + }) + + spajs.addMenu({ + id:"project", + urlregexp:[/^project\/([0-9]+)$/, /^projects\/([0-9]+)$/], + onOpen:function(holder, menuInfo, data){return pmProjects.openItem(holder, menuInfo, data);}, + onClose:function(){return pmHistory.stopUpdates();}, + }) + + spajs.addMenu({ + id:"newProject", + urlregexp:[/^new-project$/], + onOpen:function(holder, menuInfo, data){return pmProjects.openNewItemPage(holder, menuInfo, data);} + }) + + spajs.addMenu({ + id:"project-run-playbook", + urlregexp:[/^project\/([0-9]+)\/playbook\/run$/], + onOpen:function(holder, menuInfo, data){return pmProjects.openRunPlaybookPage(holder, menuInfo, data);} + }) + + spajs.addMenu({ + id:"project-ansible-module-run", + urlregexp:[/^project\/([0-9]+)\/ansible-module\/run$/], + onOpen:function(holder, menuInfo, data){return pmAnsibleModule.showInProject(holder, menuInfo, data);} + }) }) \ No newline at end of file diff --git a/polemarch/static/js/pmTasks.js b/polemarch/static/js/pmTasks.js index d047fc52..73605ae4 100644 --- a/polemarch/static/js/pmTasks.js +++ b/polemarch/static/js/pmTasks.js @@ -1,155 +1,169 @@ - -/** - * Список playbook из всех проектов. - * Сейчас используется для автокомплитов при выборе playbook. - * И для запуска конкретного playbook - */ -var pmTasks = inheritance(pmItems) - -pmTasks.model.name = "tasks" -pmTasks.model.className = "pmTasks" - -pmTasks.execute = function(project_id, inventory, playbook, data_vars) -{ - var def = new $.Deferred(); - if(!playbook) - { - $.notify("Playbook name is empty", "error"); - def.reject({text:"Playbook name is empty"}); - return def.promise(); - } - - if(!(project_id/1)) - { - $.notify("Invalid field `project` ", "error"); - def.reject({text:"Invalid field `project` "}); - return def.promise(); - } - - if(data_vars == undefined) - { - data_vars = jsonEditor.jsonEditorGetValues(); - } - - data_vars.playbook = playbook - data_vars.inventory = inventory - spajs.ajax.Call({ - url: "/api/v1/projects/"+project_id+"/execute-playbook/", - type: "POST", - data:JSON.stringify(data_vars), - contentType:'application/json', - success: function(data) - { - $.notify("Started", "success"); - if(data && data.history_id) - { - $.when(spajs.open({ menuId:"project/"+project_id+"/history/"+data.history_id}) ).done(function(){ - def.resolve() - }).fail(function(e){ - def.reject(e) - }) - } - else - { - def.reject({text:"No history_id", status:500}) - } - }, - error:function(e) - { - def.reject(e) - polemarch.showErrors(e.responseJSON) - } - }) - - return def.promise(); -} - -/** - * Обновляет поле модел this.model.itemslist и ложит туда список пользователей - * Обновляет поле модел this.model.items и ложит туда список инфу о пользователях по их id - */ -pmTasks.loadItems = function(limit, offset) -{ - if(!limit) - { - limit = 30; - } - - if(!offset) - { - offset = 0; - } - - var thisObj = this; - return spajs.ajax.Call({ - url: "/api/v1/"+this.model.name+"/", - type: "GET", - contentType:'application/json', - data: "limit="+encodeURIComponent(limit)+"&offset="+encodeURIComponent(offset), - success: function(data) - { - //console.log("update Items", data) - data.limit = limit - data.offset = offset - thisObj.model.itemslist = data - - for(var i in data.results) - { - data.results[i].id = data.results[i].playbook - var val = data.results[i] - thisObj.model.items.justWatch(val.id); - thisObj.model.items[val.id] = mergeDeep(thisObj.model.items[val.id], val) - } - }, - error:function(e) - { - console.warn(e) - polemarch.showErrors(e) - } - }); -} - -pmTasks.sendSearchQuery = function(query, limit, offset) -{ - if(!limit) - { - limit = 999; - } - - if(!offset) - { - offset = 0; - } - - var q = []; - for(var i in query) - { - q.push(encodeURIComponent(i)+"="+encodeURIComponent(query[i])) - } - - var thisObj = this; - return spajs.ajax.Call({ - url: "/api/v1/"+this.model.name+"/?"+q.join('&'), - type: "GET", - contentType:'application/json', - data: "limit="+encodeURIComponent(limit)+"&offset="+encodeURIComponent(offset), - success: function(data) - { - //console.log("update Items", data) - thisObj.model.itemslist = data - - for(var i in data.results) - { - data.results[i].id = data.results[i].playbook - - var val = data.results[i] - thisObj.model.items[val.id] = val - } - }, - error:function(e) - { - console.warn(e) - polemarch.showErrors(e) - } - }); -} + +/** + * Список playbook из всех проектов. + * Сейчас используется для автокомплитов при выборе playbook. + * И для запуска конкретного playbook + */ +var pmTasks = inheritance(pmItems) + +pmTasks.model.name = "tasks" +pmTasks.model.className = "pmTasks" + +pmTasks.execute = function(project_id, inventory, playbook, group, data_vars) +{ + var def = new $.Deferred(); + if(!playbook) + { + $.notify("Playbook name is empty", "error"); + def.reject({text:"Playbook name is empty"}); + return def.promise(); + } + + if(!(project_id/1)) + { + $.notify("Invalid field `project` ", "error"); + def.reject({text:"Invalid field `project` "}); + return def.promise(); + } + + if(inventory=="./") + { + $.notify("Inventory name is empty", "error"); + def.reject({text:"Inventory name is empty"}); + return def.promise(); + } + + if(group=="") + { + $.notify("Group name is empty", "error"); + def.reject({text:"Group name is empty"}); + return def.promise(); + } + + if(data_vars == undefined) + { + data_vars = jsonEditor.jsonEditorGetValues(); + } + + data_vars.playbook = playbook + data_vars.inventory = inventory + spajs.ajax.Call({ + url: "/api/v1/projects/"+project_id+"/execute-playbook/", + type: "POST", + data:JSON.stringify(data_vars), + contentType:'application/json', + success: function(data) + { + $.notify("Started", "success"); + if(data && data.history_id) + { + $.when(spajs.open({ menuId:"project/"+project_id+"/history/"+data.history_id}) ).done(function(){ + def.resolve() + }).fail(function(e){ + def.reject(e) + }) + } + else + { + def.reject({text:"No history_id", status:500}) + } + }, + error:function(e) + { + def.reject(e) + polemarch.showErrors(e.responseJSON) + } + }) + + return def.promise(); +} + +/** + * Обновляет поле модел this.model.itemslist и ложит туда список пользователей + * Обновляет поле модел this.model.items и ложит туда список инфу о пользователях по их id + */ +pmTasks.loadItems = function(limit, offset) +{ + if(!limit) + { + limit = 30; + } + + if(!offset) + { + offset = 0; + } + + var thisObj = this; + return spajs.ajax.Call({ + url: "/api/v1/"+this.model.name+"/", + type: "GET", + contentType:'application/json', + data: "limit="+encodeURIComponent(limit)+"&offset="+encodeURIComponent(offset), + success: function(data) + { + //console.log("update Items", data) + data.limit = limit + data.offset = offset + thisObj.model.itemslist = data + + for(var i in data.results) + { + data.results[i].id = data.results[i].playbook + var val = data.results[i] + thisObj.model.items.justWatch(val.id); + thisObj.model.items[val.id] = mergeDeep(thisObj.model.items[val.id], val) + } + }, + error:function(e) + { + console.warn(e) + polemarch.showErrors(e) + } + }); +} + +pmTasks.sendSearchQuery = function(query, limit, offset) +{ + if(!limit) + { + limit = 999; + } + + if(!offset) + { + offset = 0; + } + + var q = []; + for(var i in query) + { + q.push(encodeURIComponent(i)+"="+encodeURIComponent(query[i])) + } + + var thisObj = this; + return spajs.ajax.Call({ + url: "/api/v1/"+this.model.name+"/?"+q.join('&'), + type: "GET", + contentType:'application/json', + data: "limit="+encodeURIComponent(limit)+"&offset="+encodeURIComponent(offset), + success: function(data) + { + //console.log("update Items", data) + thisObj.model.itemslist = data + + for(var i in data.results) + { + data.results[i].id = data.results[i].playbook + + var val = data.results[i] + thisObj.model.items[val.id] = val + } + }, + error:function(e) + { + console.warn(e) + polemarch.showErrors(e) + } + }); +} diff --git a/polemarch/static/js/pmTasksTemplates.js b/polemarch/static/js/pmTasksTemplates.js index 469c3a29..04dbd3c7 100644 --- a/polemarch/static/js/pmTasksTemplates.js +++ b/polemarch/static/js/pmTasksTemplates.js @@ -1,433 +1,819 @@ - - -var pmTasksTemplates = inheritance(pmTemplates) - - -pmTasksTemplates.model.name = "templates" -pmTasksTemplates.model.page_name = "template" -pmTasksTemplates.model.bulk_name = "template" -pmTasksTemplates.model.className = "pmTasksTemplates" - -// Поддерживаемые kind /api/v1/templates/supported-kinds/ -pmTasksTemplates.model.kind = "Task" -pmTemplates.model.kindObjects[pmTasksTemplates.model.kind] = pmTasksTemplates - -/** - * Для ввода пароля - * @type Object - */ -pmTasksTemplates.filed.selectProjectInventoryAndPlaybook = inheritance(filedsLib.filed.simpleText) -pmTasksTemplates.filed.selectProjectInventoryAndPlaybook.type = 'selectProjectInventoryAndPlaybook' -pmTasksTemplates.filed.selectProjectInventoryAndPlaybook.getValue = function(pmObj, filed){ - return ''; -} - - -/** - * Функция для рендера текстового поля - * @type Object - */ -pmTasksTemplates.filed.selectProjectInventoryAndPlaybook.render = function(pmObj, filed, item_id){ - var html = spajs.just.render('filed_type_'+this.type, {pmObj:pmObj, filed:filed, item_id:item_id}) - return spajs.just.onInsert(html, function() - { - $("#inventories-autocomplete").select2({ width: '100%' }); - $("#projects-autocomplete").select2({ width: '100%' }); - - new autoComplete({ - selector: '#playbook-autocomplete', - minChars: 0, - cache:false, - showByClick:false, - menuClass:'playbook-autocomplete', - renderItem: function(item, search) - { - return '
    ' + item.playbook + '
    '; - }, - onSelect: function(event, term, item) - { - $("#playbook-autocomplete").val($(item).text()); - //console.log('onSelect', term, item); - //var value = $(item).attr('data-value'); - }, - source: function(term, response) - { - term = term.toLowerCase(); - - var matches = [] - for(var i in pmTasks.model.items) - { - var val = pmTasks.model.items[i] - if(val.name.toLowerCase().indexOf(term) != -1 && thisObj.model.selectedProject == val.project) - { - matches.push(val) - } - } - if(matches.length) - { - response(matches); - } - } - }); - - }) -} - - // Export all selected templates - -pmTasksTemplates.model.page_list = { - buttons:[ - { - class:'btn btn-primary', - function:function(){ return "spajs.open({ menuId:'template/new-task'}); return false;"}, - title:'Create task template', - link:function(){ return '/?template/new-task'}, - }, - { - class:'btn btn-primary', - function:function(){ return "spajs.open({ menuId:'template/new-module'}); return false;"}, - title:'Create module template', - link:function(){ return '/?template/new-module'}, - }, - ], - actionsOnSelected:[ - {}, - { - class:'btn btn-primary', - function:function(){ return "pmTasksTemplates.exportSelecedToFile(); return false;"}, - title:'Export all selected templates', - link:function(){ return '#'}, - }, - ], - title: "Templates", - short_title: "Templates", - fileds:[ - { - title:'Name', - name:'name', - value:function(item) - { - return ''+item.name+''; - } - }, - { - title:'Kind', - name:'kind', - style:function(item){ return 'style="width: 110px"'}, - class:function(item) - { - return 'class="hidden-xs hidden-sm"'; - }, - value:function(item) - { - return item.kind; - } - } - ], - actions:[ - { - class:'btn btn-warning', - function:function(item){ return "spajs.showLoader(pmTemplates.model.kindObjects['"+item.kind+"'].execute("+item.id+")); return false;"}, - title:'Execute', - link:function(){ return '#'} - }, - ] -} - -pmTasksTemplates.model.page_item = { - buttons:[ - { - class:'btn btn-primary', - function:function(item_id){ return 'spajs.showLoader('+this.model.className+'.updateItem('+item_id+')); return false;'}, - title:'Save', - link:function(){ return '#'}, - }, - { - class:'btn btn-warning', - function:function(item_id){ - return "spajs.showLoader("+this.model.className+".saveAndExecute("+item_id+")); return false;" - }, - title:'Save and execute', - link:function(){ return '#'}, - help:'Save and execute' - }, - { - class:'btn btn-default copy-btn', - function:function(item_id){ return 'spajs.showLoader('+this.model.className+'.copyAndEdit('+item_id+')); return false;'}, - title:'', - link:function(){ return '#'}, - help:'Copy' - }, - { - class:'btn btn-danger danger-right', - function:function(item_id){ return 'spajs.showLoader('+this.model.className+'.deleteItem('+item_id+')); return false;'}, - title:' ', - link:function(){ return '#'}, - }, - ], - sections:[ - function(section, item_id){ - return jsonEditor.editor(pmTasksTemplates.model.items[item_id].data.vars, {block:'playbook', title1:'Arguments', title2:'Adding new argument', select2:true}); - } - ], - title: function(item_id){ - return "Task template "+this.model.items[item_id].justText('name') - }, - short_title: function(item_id){ - return this.model.items[item_id].justText('name', function(v){return v.slice(0, 20)}) - }, - fileds:[ - [ - { - filed: new filedsLib.filed.text(), - title:'Name', - name:'name', - placeholder:'Enter template name', - validator:function(value){ - return filedsLib.validator.notEmpty(value, 'Name') - }, - fast_validator:function(value){ return value != '' && value} - }, - // @todo дорефакторить поля ввода - ],[ - { - filed: new pmTasksTemplates.filed.selectProjectInventoryAndPlaybook(), - name:'project', - }, - ] - ], - onUpdate:function(result) - { - return true; - }, - onBeforeSave:function(data, item_id) - { - data.kind = this.model.kind - - data.data = { - inventory:pmTasksTemplates.inventoriesAutocompletefiled.getValue(), - vars:jsonEditor.jsonEditorGetValues() - } - - data.data.playbook = $("#playbook-autocomplete").val() - data.data.project = $("#projects-autocomplete").val()/1 - - return data; - }, -} - -pmTasksTemplates.saveAndExecute = function(item_id) -{ - var def = new $.Deferred(); - $.when(this.updateItem(item_id)).done(function() - { - $.when(pmTasksTemplates.execute(item_id)).always(function(){ - def.resolve(); - }) - }).fail(function(e){ - def.reject(e); - }) - return def.promise() -} - - -pmTasksTemplates.inventoriesAutocompletefiled = new pmInventories.filed.inventoriesAutocomplete() -pmTasksTemplates.showWidget = function(holder, kind) -{ - var thisObj = this; - var offset = 0 - var limit = this.pageSize; - return $.when(this.sendSearchQuery({kind:kind}, limit, offset)).done(function() - { - $(holder).insertTpl(spajs.just.render(thisObj.model.name+'_widget', {query:"", kind:kind})) - }).fail(function() - { - $.notify("", "error"); - }).promise() -} - -pmTasksTemplates.showTaskWidget = function(holder) -{ - return pmTasksTemplates.showWidget(holder, "Task") -} - -pmTasksTemplates.showModuleWidget = function(holder) -{ - return pmTasksTemplates.showWidget(holder, "Module") -} - -pmTasksTemplates.showItem = function(holder, menuInfo, data) -{ - var def = new $.Deferred(); - var thisObj = this; - var item_id = data.reg[1] - $.when(pmProjects.loadAllItems(), pmTasksTemplates.loadItem(item_id), pmInventories.loadAllItems(), pmTasks.loadAllItems()).done(function() - { - thisObj.model.selectedProject == pmTasksTemplates.model.items[item_id].project - - var tpl = thisObj.model.name+'_page' - if(!spajs.just.isTplExists(tpl)) - { - tpl = 'items_page' - } - - $(holder).insertTpl(spajs.just.render(tpl, {item_id:item_id, pmObj:thisObj, opt:{}})) - - def.resolve(); - }).fail(function(e) - { - def.reject(e); - }) - - return def.promise() -} - -pmTasksTemplates.selectProject = function(project_id){ - console.log("select project", project_id) - $(".autocomplete-suggestion").hide() - $(".playbook-project-"+project_id).show() - pmTasksTemplates.model.selectedProject = project_id -} - -pmTasksTemplates.showNewItemPage = function(holder, menuInfo, data) -{ - var def = new $.Deferred(); - var thisObj = this; - $.when(pmProjects.loadAllItems(), pmInventories.loadAllItems(), pmTasks.loadAllItems()).done(function() - { - $(holder).insertTpl(spajs.just.render(thisObj.model.name+'_new_page', {})) - - $("#inventories-autocomplete").select2({ width: '100%' }); - //$("#projects-autocomplete").select2({ width: '100%' }); - - new autoComplete({ - selector: '#playbook-autocomplete', - minChars: 0, - cache:false, - showByClick:false, - menuClass:'playbook-autocomplete', - renderItem: function(item, search) - { - var style = ""; - if(thisObj.model.selectedProject != item.project) - { - style = "style='disolay:none'" - } - return '
    ' + item.playbook + '
    '; - }, - onSelect: function(event, term, item) - { - $("#playbook-autocomplete").val($(item).text()); - //console.log('onSelect', term, item); - //var value = $(item).attr('data-value'); - }, - source: function(term, response) - { - term = term.toLowerCase(); - - var matches = [] - for(var i in pmTasks.model.items) - { - var val = pmTasks.model.items[i] - if(val.name.toLowerCase().indexOf(term) != -1 && thisObj.model.selectedProject == val.project) - { - matches.push(val) - } - } - if(matches.length) - { - response(matches); - } - } - }); - - def.resolve(); - }).fail(function(e) - { - def.reject(e); - }) - - return def.promise() -} - -/** - * @return $.Deferred - * @todo дорефакторить форму создания Task template - */ -pmTasksTemplates.addItem = function() -{ - var def = new $.Deferred(); - var data = {} - - var inventory = pmTasksTemplates.inventoriesAutocompletefiled.getValue() - - data.name = $("#Templates-name").val() - data.kind = this.model.kind - data.data = { - playbook:$("#playbook-autocomplete").val(), - inventory:inventory, - project:$("#projects-autocomplete").val(), - vars:jsonEditor.jsonEditorGetValues() - } - - - if(!data.name) - { - console.warn("Invalid value in field name") - $.notify("Invalid value in field name", "error"); - def.reject("Invalid value in field name") - return def.promise(); - } - - var thisObj = this; - spajs.ajax.Call({ - url: "/api/v1/templates/", - type: "POST", - contentType:'application/json', - data:JSON.stringify(data), - success: function(data) - { - $.notify("template created", "success"); - $.when(spajs.open({ menuId:"template/"+thisObj.model.kind+"/"+data.id})).always(function(){ - def.resolve() - }) - }, - error:function(e) - { - polemarch.showErrors(e.responseJSON) - def.reject(e) - } - }); - - return def.promise(); -} - - -tabSignal.connect("polemarch.start", function() -{ - // Tasks Templates - spajs.addMenu({ - id:"tasks", - urlregexp:[/^templates$/, /^templates\/page\/([0-9]+)$/], - onOpen:function(holder, menuInfo, data){return pmTasksTemplates.showList(holder, menuInfo, data);} - }) - - spajs.addMenu({ - id:"tasks-search", - urlregexp:[/^templates\/search\/([A-z0-9 %\-.:,=]+)$/], - onOpen:function(holder, menuInfo, data){return pmTasksTemplates.showSearchResults(holder, menuInfo, data);} - }) - - spajs.addMenu({ - id:"Task-item", - urlregexp:[/^template\/Task\/([0-9]+)$/, /^templates\/Task\/([0-9]+)$/], - onOpen:function(holder, menuInfo, data){return pmTasksTemplates.showItem(holder, menuInfo, data);}, - }) - - spajs.addMenu({ - id:"task-new", - urlregexp:[/^template\/new-task$/], - onOpen:function(holder, menuInfo, data){return pmTasksTemplates.showNewItemPage(holder, menuInfo, data);} - }) - + + +var pmTasksTemplates = inheritance(pmTemplates) + + +pmTasksTemplates.model.name = "templates" +pmTasksTemplates.model.page_name = "template" +pmTasksTemplates.model.bulk_name = "template" +pmTasksTemplates.model.className = "pmTasksTemplates" + +// Поддерживаемые kind /api/v1/templates/supported-kinds/ +pmTasksTemplates.model.kind = "Task" +pmTemplates.model.kindObjects[pmTasksTemplates.model.kind] = pmTasksTemplates + +/** + * Для ввода пароля + * @type Object + */ +pmTasksTemplates.filed.selectProjectInventoryAndPlaybook = inheritance(filedsLib.filed.simpleText) +pmTasksTemplates.filed.selectProjectInventoryAndPlaybook.type = 'selectProjectInventoryAndPlaybook' +pmTasksTemplates.filed.selectProjectInventoryAndPlaybook.getValue = function(pmObj, filed){ + return ''; +} + +pmTasksTemplates.filed.selectProjectInventoryAndPlaybookForNewOption = inheritance(filedsLib.filed.simpleText) +pmTasksTemplates.filed.selectProjectInventoryAndPlaybookForNewOption.type = 'selectProjectInventoryAndPlaybookForNewOption' +pmTasksTemplates.filed.selectProjectInventoryAndPlaybookForNewOption.getValue = function(pmObj, filed){ + return ''; +} + +pmTasksTemplates.filed.selectProjectInventoryAndPlaybookForOption = inheritance(filedsLib.filed.simpleText) +pmTasksTemplates.filed.selectProjectInventoryAndPlaybookForOption.type = 'selectProjectInventoryAndPlaybookForOption' +pmTasksTemplates.filed.selectProjectInventoryAndPlaybookForOption.getValue = function(pmObj, filed){ + return ''; +} + + +/** + * Функция для рендера текстового поля + * @type Object + */ +pmTasksTemplates.filed.selectProjectInventoryAndPlaybook.render = function(pmObj, filed, item_id){ + var html = spajs.just.render('filed_type_'+this.type, {pmObj:pmObj, filed:filed, item_id:item_id}) + return spajs.just.onInsert(html, function() + { + $("#inventories-autocomplete").select2({ width: '100%' }); + $("#projects-autocomplete").select2({ width: '100%' }); + + pmTasksTemplates.newAutoCompletePlaybook(); + + }) +} + +pmTasksTemplates.filed.selectProjectInventoryAndPlaybookForNewOption.render = function(pmObj, filed, item_id){ + var html = spajs.just.render('filed_type_'+this.type, {pmObj:pmObj, filed:filed, item_id:item_id}) + return spajs.just.onInsert(html, function() + { + pmTasksTemplates.newAutoCompletePlaybook(); + + }) +} + +pmTasksTemplates.filed.selectProjectInventoryAndPlaybookForOption.render = function(pmObj, filed, item_id){ + var html = spajs.just.render('filed_type_'+this.type, {pmObj:pmObj, filed:filed, item_id:item_id}) + return spajs.just.onInsert(html, function() + { + pmTasksTemplates.newAutoCompletePlaybook(); + + }) +} + + // Export all selected templates + +pmTasksTemplates.newAutoCompletePlaybook = function() +{ + return new autoComplete({ + selector: '#playbook-autocomplete', + minChars: 0, + cache:false, + showByClick:false, + menuClass:'playbook-autocomplete', + renderItem: function(item, search) + { + return '
    ' + item.playbook + '
    '; + }, + onSelect: function(event, term, item) + { + $("#playbook-autocomplete").val($(item).text()); + //console.log('onSelect', term, item); + //var value = $(item).attr('data-value'); + }, + source: function(term, response) + { + term = term.toLowerCase(); + + var matches = [] + for(var i in pmTasks.model.items) + { + var val = pmTasks.model.items[i] + if(val.name.toLowerCase().indexOf(term) != -1 && thisObj.model.selectedProject == val.project) + { + matches.push(val) + } + } + if(matches.length) + { + response(matches); + } + } + }); +} + +pmTasksTemplates.model.page_list = { + buttons:[ + { + class:'btn btn-primary', + function:function(){ return "spajs.open({ menuId:'template/new-task'}); return false;"}, + title:'Create task template', + link:function(){ return '/?template/new-task'}, + }, + { + class:'btn btn-primary', + function:function(){ return "spajs.open({ menuId:'template/new-module'}); return false;"}, + title:'Create module template', + link:function(){ return '/?template/new-module'}, + }, + ], + actionsOnSelected:[ + {}, + { + class:'btn btn-primary', + function:function(){ return "pmTasksTemplates.exportSelecedToFile(); return false;"}, + title:'Export all selected templates', + link:function(){ return '#'}, + }, + ], + title: "Templates", + short_title: "Templates", + fileds:[ + { + title:'Name', + name:'name', + value:function(item) + { + return ''+item.name+''; + } + }, + { + title:'Kind', + name:'kind', + style:function(item){ return 'style="width: 110px"'}, + class:function(item) + { + return 'class="hidden-xs hidden-sm"'; + }, + value:function(item) + { + return item.kind; + } + } + ], + actions:[ + { + class:'btn btn-warning', + function:function(item, option_name){ return "spajs.showLoader(pmTemplates.model.kindObjects['"+item.kind+"'].execute("+item.id+" , '"+option_name +"')); return false;"}, + title:'Execute', + link:function(){ return '#'} + }, + ] +} + +pmTasksTemplates.model.page_item = { + buttons:[ + { + class:'btn btn-primary', + function:function(item_id){ return 'spajs.showLoader('+this.model.className+'.updateItem('+item_id+')); return false;'}, + title:'Save', + link:function(){ return '#'}, + }, + { + class:'btn btn-warning', + function:function(item_id){ + return "spajs.showLoader("+this.model.className+".saveAndExecute("+item_id+")); return false;" + }, + title:'Save and execute', + link:function(){ return '#'}, + help:'Save and execute' + }, + + { + class:'btn btn-info', + function:function(item_id){ + return "spajs.showLoader("+this.model.className+".setNewOption("+item_id+")); return false;" + }, + title:'Create new option', + link:function(){ return '#'}, + help:'Create new option' + }, + + { + class:'btn btn-default copy-btn', + function:function(item_id){ return 'spajs.showLoader('+this.model.className+'.copyAndEdit('+item_id+')); return false;'}, + title:'', + link:function(){ return '#'}, + help:'Copy' + }, + { + class:'btn btn-danger danger-right', + function:function(item_id){ return 'spajs.showLoader('+this.model.className+'.deleteItem('+item_id+')); return false;'}, + title:' ', + link:function(){ return '#'}, + }, + ], + sections:[ + function(section, item_id){ + return jsonEditor.editor(pmTasksTemplates.model.items[item_id].data.vars, {block:'playbook', title1:'Arguments', title2:'Adding new argument', select2:true}); + }, + function(section, item_id){ + return spajs.just.render("options_section_task_page", {item_id:item_id}) + } + ], + title: function(item_id){ + return "Task template "+this.model.items[item_id].justText('name') + }, + short_title: function(item_id){ + return this.model.items[item_id].justText('name', function(v){return v.slice(0, 20)}) + }, + fileds:[ + [ + { + filed: new filedsLib.filed.text(), + title:'Name', + name:'name', + placeholder:'Enter template name', + validator:function(value){ + return filedsLib.validator.notEmpty(value, 'Name') + }, + fast_validator:function(value){ return value != '' && value} + }, + // @todo дорефакторить поля ввода + ],[ + { + filed: new pmTasksTemplates.filed.selectProjectInventoryAndPlaybook(), + name:'project', + }, + ] + ], + onUpdate:function(result) + { + return true; + }, + onBeforeSave:function(data, item_id) + { + data.kind = this.model.kind + + data.data = { + inventory:pmTasksTemplates.inventoriesAutocompletefiled.getValue(), + vars:jsonEditor.jsonEditorGetValues() + } + + data.data.playbook = $("#playbook-autocomplete").val() + data.data.project = $("#projects-autocomplete").val()/1 + + return data; + }, +} + + +pmTasksTemplates.model.page_item_new_option = { + buttons:[ + { + class:'btn btn-primary', + function:function(item_id){ return 'spajs.showLoader(pmTasksTemplates.saveOption('+item_id+')); return false;'}, + title:'Create', + link:function(){ return '#'}, + } + ], + sections:[ + function(section, item_id){ + pmTasksTemplates.model.items[item_id].data.varsForOption={}; + return jsonEditor.editor(pmTasksTemplates.model.items[item_id].data.varsForOption, {block:'playbook', title1:'Arguments', title2:'Adding new argument', select2:true}); + } + ], + title: function(item_id){ + return "New option for task template "+this.model.items[item_id].justText('name'); + }, + short_title: function(item_id){ + return "New option"; + }, + fileds:[ + [ + { + filed: new filedsLib.filed.text(), + title:'Name', + name:'option_name', + placeholder:'Enter option name', + value:'' + } + // @todo дорефакторить поля ввода + ],[ + { + filed: new pmTasksTemplates.filed.selectProjectInventoryAndPlaybookForNewOption(), + name:'project', + }, + ] + ], + onUpdate:function(result) + { + return true; + } +} + + +pmTasksTemplates.model.page_item_option = { + buttons:[ + { + class:'btn btn-primary', + function:function(item_id){ return 'spajs.showLoader(pmTasksTemplates.saveOption('+item_id+')); return false;'}, + title:'Save', + link:function(){ return '#'}, + }, + { + class:'btn btn-warning', + title:'Save and execute', + function:function(item_id){ return "spajs.showLoader(pmTasksTemplates.saveAndExecuteOption("+item_id+")); return false;"}, + link:function(){ return '#'}, + help:'Save and execute' + }, + { + class:'btn btn-danger danger-right', + function:function(item_id){ + return 'spajs.showLoader(pmTasksTemplates.removeOption('+item_id+')); return false;' + }, + title:' ', + link:function(){ return '#'}, + } + ], + sections:[ + function(section, item_id){ + pmTasksTemplates.model.items[item_id].data.varsForOption={}; + for(var i in pmTasksTemplates.model.items[item_id].dataForOption.vars) + { + pmTasksTemplates.model.items[item_id].data.varsForOption[i]=pmTasksTemplates.model.items[item_id].dataForOption.vars[i]; + } + return jsonEditor.editor(pmTasksTemplates.model.items[item_id].data.varsForOption, {block:'module', title1:'Additional arguments', title2:'Adding new argument', select2:true}); + } + ], + title: function(item_id){ + return 'Option '+pmTasksTemplates.model.items[item_id].option_name+' for task template '+this.model.items[item_id].justText('name'); + }, + short_title: function(item_id){ + return pmTasksTemplates.model.items[item_id].option_name; + }, + fileds:[ + [ + { + filed: new filedsLib.filed.text(), + title:'Name', + name:'option_name', + placeholder:'Enter option name' + } + // @todo дорефакторить поля ввода + ],[ + { + filed: new pmTasksTemplates.filed.selectProjectInventoryAndPlaybookForOption(), + name:'project', + }, + ] + ], + onUpdate:function(result) + { + return true; + } +} + + +pmTasksTemplates.saveAndExecute = function(item_id) +{ + var def = new $.Deferred(); + $.when(this.updateItem(item_id)).done(function() + { + $.when(pmTasksTemplates.execute(item_id)).always(function(){ + def.resolve(); + }) + }).fail(function(e){ + def.reject(e); + }) + return def.promise() +} + + +pmTasksTemplates.setNewOption = function(item_id) +{ + return spajs.openURL(window.location.href+"/new-option"); +} + +pmTasksTemplates.saveOption = function(item_id) +{ + var optionName=$('#filed_option_name').val(); + optionName=optionName.trim(); + optionName=optionName.replace( /\s/g, "-" ); + var def = new $.Deferred(); + if(optionName=="") + { + $.notify("Option name is empty", "error"); + def.reject({text:"Option name is empty"}); + return def.promise(); + } + var templateData=this.model.items[item_id].data; + var optionData= { + playbook:$("#playbook-autocomplete").val(), + args:moduleArgsEditor.getModuleArgs(), + vars:jsonEditor.jsonEditorGetValues(), + }; + var dataToAdd={}; + for(var i in optionData) + { + for (var j in templateData) + { + if(i==j && i!='vars') + { + if(optionData[i]!=templateData[j]) + { + dataToAdd[i]=optionData[i]; + } + } + } + } + + if(!($.isEmptyObject(optionData['vars']))) + { + for(var i in templateData['vars']) + { + if(optionData['vars'].hasOwnProperty(i)) + { + $.notify('Template has already argument "'+i+'" ', "error"); + def.reject({text:"Option is the same as the template"}); + return def.promise(); + } + } + dataToAdd['vars']=optionData['vars']; + } + + + if($.isEmptyObject(dataToAdd)) + { + $.notify("Option is absolutely the same as the template", "error"); + def.reject({text:"Option is the same as the template"}); + return def.promise(); + } + else + { + var dataToAdd1={options:{}}; + dataToAdd1.options=this.model.items[item_id].options; + if(dataToAdd1.options.hasOwnProperty(optionName)) + { + delete dataToAdd1.options[optionName]; + } + dataToAdd1.options[optionName]=dataToAdd; + var thisObj = this; + spajs.ajax.Call({ + url: "/api/v1/" + this.model.name + "/" + item_id + "/", + type: "PATCH", + contentType: 'application/json', + data: JSON.stringify(dataToAdd1), + success: function (data) + { + thisObj.model.items[item_id] = data + $.notify('Option "'+optionName+'" was successfully saved', "success"); + $.when(spajs.open({ menuId:"template/"+thisObj.model.kind+"/"+data.id+"/option/"+optionName})).always(function(){ + def.resolve(); + }); + }, + error: function (e) + { + def.reject(e) + polemarch.showErrors(e.responseJSON) + } + }); + } + return def.promise(); +} + +pmTasksTemplates.saveAndExecuteOption = function(item_id) +{ + var def = new $.Deferred(); + $.when(pmTasksTemplates.saveOption(item_id)).done(function() + { + pmTemplates.model.kindObjects['Task'].execute(item_id, pmTasksTemplates.model.items[item_id].option_name); + def.resolve(); + }).fail(function(e) + { + def.reject(e); + polemarch.showErrors(e.responseJSON); + }).promise(); + + return def.promise(); +} + +pmTasksTemplates.removeOption = function(item_id) +{ + var def = new $.Deferred(); + var optionName=pmTasksTemplates.model.items[item_id].option_name; + delete pmTasksTemplates.model.items[item_id].options[optionName]; + var dataToAdd1={options:{}}; + dataToAdd1['options']=pmTasksTemplates.model.items[item_id].options; + var thisObj = this; + spajs.ajax.Call({ + url: "/api/v1/" + this.model.name + "/" + item_id + "/", + type: "PATCH", + contentType: 'application/json', + data: JSON.stringify(dataToAdd1), + success: function (data) + { + thisObj.model.items[item_id] = data + $.notify('Option "'+optionName+'" was successfully deleted', "success"); + $.when(spajs.open({ menuId:"template/"+thisObj.model.kind+"/"+data.id})).always(function(){ + def.resolve(); + }); + + }, + error: function (e) + { + def.reject(e) + polemarch.showErrors(e.responseJSON) + } + }); + return def.promise(); +} + +pmTasksTemplates.showNewOptionPage = function(holder, menuInfo, data) +{ + var def = new $.Deferred(); + var thisObj = this; + var item_id = data.reg[1] + $.when(pmProjects.loadAllItems(), pmTasksTemplates.loadItem(item_id), pmInventories.loadAllItems(), pmTasks.loadAllItems()).done(function() + { + thisObj.model.selectedProject == pmTasksTemplates.model.items[item_id].project + + var tpl = 'new_option_page' + if(!spajs.just.isTplExists(tpl)) + { + tpl = 'items_page' + } + + $(holder).insertTpl(spajs.just.render(tpl, {item_id:item_id, pmObj:thisObj, opt:{}})) + def.resolve(); + }).fail(function(e) + { + def.reject(e); + }) + + return def.promise() +} + +pmTasksTemplates.showOptionPage = function(holder, menuInfo, data) +{ + var def = new $.Deferred(); + var thisObj = this; + var item_id = data.reg[1]; + var option_name=data.reg[2]; + $.when(pmProjects.loadAllItems(), pmTasksTemplates.loadItem(item_id), pmInventories.loadAllItems(), pmTasks.loadAllItems()).done(function() + { + thisObj.model.selectedProject == pmTasksTemplates.model.items[item_id].project; + + pmTasksTemplates.model.items[item_id].option_name=option_name; + pmTasksTemplates.model.items[item_id].dataForOption={}; + for(var i in pmTasksTemplates.model.items[item_id].data) + { + if(i!='vars') + { + pmTasksTemplates.model.items[item_id].dataForOption[i]=pmTasksTemplates.model.items[item_id].data[i]; + } + } + var optionAPI=pmTasksTemplates.model.items[item_id].options[pmTasksTemplates.model.items[item_id].option_name]; + for(var i in optionAPI) + { + if(pmTasksTemplates.model.items[item_id].dataForOption.hasOwnProperty(i)) + { + pmTasksTemplates.model.items[item_id].dataForOption[i]=optionAPI[i]; + } + } + if(optionAPI.hasOwnProperty('vars')) + { + pmTasksTemplates.model.items[item_id].dataForOption['vars']={}; + for(var i in optionAPI['vars']) + { + pmTasksTemplates.model.items[item_id].dataForOption['vars'][i]=optionAPI['vars'][i]; + } + } + + var tpl = 'module_option_page' + if(!spajs.just.isTplExists(tpl)) + { + tpl = 'items_page' + } + + $(holder).insertTpl(spajs.just.render(tpl, {item_id:item_id, pmObj:thisObj, opt:{}})) + def.resolve(); + }).fail(function(e) + { + def.reject(e); + }) + + return def.promise() +} + +pmTasksTemplates.inventoriesAutocompletefiled = new pmInventories.filed.inventoriesAutocomplete() +pmTasksTemplates.showWidget = function(holder, kind) +{ + var thisObj = this; + var offset = 0 + var limit = this.pageSize; + var ordering="-id"; + return $.when(this.sendSearchQuery({kind:kind}, limit, offset, ordering)).done(function() + { + $(holder).insertTpl(spajs.just.render(thisObj.model.name+'_widget', {query:"", kind:kind})) + }).fail(function() + { + $.notify("", "error"); + }).promise() +} + +pmTasksTemplates.showTaskWidget = function(holder) +{ + return pmTasksTemplates.showWidget(holder, "Task") +} + +pmTasksTemplates.showModuleWidget = function(holder) +{ + return pmTasksTemplates.showWidget(holder, "Module") +} + +pmTasksTemplates.showItem = function(holder, menuInfo, data) +{ + var def = new $.Deferred(); + var thisObj = this; + var item_id = data.reg[1] + $.when(pmProjects.loadAllItems(), pmTasksTemplates.loadItem(item_id), pmInventories.loadAllItems(), pmTasks.loadAllItems()).done(function() + { + thisObj.model.selectedProject == pmTasksTemplates.model.items[item_id].project + + var tpl = thisObj.model.name+'_page' + if(!spajs.just.isTplExists(tpl)) + { + tpl = 'items_page' + } + + $(holder).insertTpl(spajs.just.render(tpl, {item_id:item_id, pmObj:thisObj, opt:{}})) + def.resolve(); + }).fail(function(e) + { + def.reject(e); + }) + + return def.promise() +} + +pmTasksTemplates.selectProject = function(project_id){ + console.log("select project", project_id) + $(".autocomplete-suggestion").hide() + $(".playbook-project-"+project_id).show() + pmTasksTemplates.model.selectedProject = project_id +} + +pmTasksTemplates.showNewItemPage = function(holder, menuInfo, data) +{ + var def = new $.Deferred(); + var thisObj = this; + $.when(pmProjects.loadAllItems(), pmInventories.loadAllItems(), pmTasks.loadAllItems()).done(function() + { + $(holder).insertTpl(spajs.just.render(thisObj.model.name+'_new_page', {})) + + $("#inventories-autocomplete").select2({ width: '100%' }); + //$("#projects-autocomplete").select2({ width: '100%' }); + + new autoComplete({ + selector: '#playbook-autocomplete', + minChars: 0, + cache:false, + showByClick:false, + menuClass:'playbook-autocomplete', + renderItem: function(item, search) + { + var style = ""; + if(thisObj.model.selectedProject != item.project) + { + style = "style='disolay:none'" + } + return '
    ' + item.playbook + '
    '; + }, + onSelect: function(event, term, item) + { + $("#playbook-autocomplete").val($(item).text()); + //console.log('onSelect', term, item); + //var value = $(item).attr('data-value'); + }, + source: function(term, response) + { + term = term.toLowerCase(); + + var matches = [] + for(var i in pmTasks.model.items) + { + var val = pmTasks.model.items[i] + if(val.name.toLowerCase().indexOf(term) != -1 && thisObj.model.selectedProject == val.project) + { + matches.push(val) + } + } + if(matches.length) + { + response(matches); + } + } + }); + + def.resolve(); + }).fail(function(e) + { + def.reject(e); + }) + + return def.promise() +} + +/** + * @return $.Deferred + * @todo дорефакторить форму создания Task template + */ +pmTasksTemplates.addItem = function() +{ + var def = new $.Deferred(); + var data = {} + + var inventory = pmTasksTemplates.inventoriesAutocompletefiled.getValue() + + data.name = $("#Templates-name").val() + data.kind = this.model.kind + data.data = { + playbook:$("#playbook-autocomplete").val(), + inventory:inventory, + project:$("#projects-autocomplete").val(), + vars:jsonEditor.jsonEditorGetValues() + } + + + if(!data.name) + { + console.warn("Invalid value in field name") + $.notify("Invalid value in field name", "error"); + def.reject("Invalid value in field name") + return def.promise(); + } + + var thisObj = this; + spajs.ajax.Call({ + url: "/api/v1/templates/", + type: "POST", + contentType:'application/json', + data:JSON.stringify(data), + success: function(data) + { + $.notify("template created", "success"); + $.when(spajs.open({ menuId:"template/"+thisObj.model.kind+"/"+data.id})).always(function(){ + def.resolve() + }) + }, + error:function(e) + { + polemarch.showErrors(e.responseJSON) + def.reject(e) + } + }); + + return def.promise(); +} + + +tabSignal.connect("polemarch.start", function() +{ + // Tasks Templates + spajs.addMenu({ + id:"tasks", + urlregexp:[/^templates$/, /^templates\/page\/([0-9]+)$/], + onOpen:function(holder, menuInfo, data){return pmTasksTemplates.showList(holder, menuInfo, data);} + }) + + spajs.addMenu({ + id:"tasks-search", + urlregexp:[/^templates\/search\/([A-z0-9 %\-.:,=]+)$/, /^templates\/search\/([A-z0-9 %\-.:,=]+)\/page\/([0-9]+)$/], + onOpen:function(holder, menuInfo, data){return pmTasksTemplates.showSearchResults(holder, menuInfo, data);} + }) + + spajs.addMenu({ + id:"Task-item", + urlregexp:[/^template\/Task\/([0-9]+)$/, /^templates\/Task\/([0-9]+)$/], + onOpen:function(holder, menuInfo, data){return pmTasksTemplates.showItem(holder, menuInfo, data);}, + }) + + spajs.addMenu({ + id:"task-new", + urlregexp:[/^template\/new-task$/], + onOpen:function(holder, menuInfo, data){return pmTasksTemplates.showNewItemPage(holder, menuInfo, data);} + }) + + spajs.addMenu({ + id:"task-new-option", + urlregexp:[/^template\/Task\/([0-9]+)\/new-option$/, /^templates\/Task\/([0-9]+)\/new-option$/], + onOpen:function(holder, menuInfo, data){return pmTasksTemplates.showNewOptionPage(holder, menuInfo, data);} + }) + + spajs.addMenu({ + id:"task-option", + urlregexp:[/^template\/Task\/([0-9]+)\/option\/([A-z0-9 %\-.:,=]+)$/, /^templates\/Task\/([0-9]+)\/option\/([A-z0-9 %\-.:,=]+)$/], + onOpen:function(holder, menuInfo, data){return pmTasksTemplates.showOptionPage(holder, menuInfo, data);} + }) + }) \ No newline at end of file diff --git a/polemarch/static/js/pmTemplates.js b/polemarch/static/js/pmTemplates.js index 6a08ad86..d56b0afb 100644 --- a/polemarch/static/js/pmTemplates.js +++ b/polemarch/static/js/pmTemplates.js @@ -1,204 +1,235 @@ - -var pmTemplates = inheritance(pmItems) - -pmTemplates.model.name = "templates" - - -// Поддерживаемые kind /api/v1/templates/supported-kinds/ -pmTemplates.model.kind = "Task,Module" -pmTemplates.model.page_name = "templates" -pmTemplates.model.bulk_name = "template" -pmTemplates.model.className = "pmTemplates" - -pmTemplates.copyAndEdit = function(item_id) -{ - if(!item_id) - { - throw "Error in pmTemplates.copyAndEdit with item_id = `" + item_id + "`" - } - - var def = new $.Deferred(); - var thisObj = this; - return $.when(this.copyItem(item_id)).done(function(newItemId) - { - $.when(spajs.open({ menuId:thisObj.model.page_name + "/"+thisObj.model.items[item_id].kind+"/"+newItemId})).done(function(){ - $.notify("Item was duplicate", "success"); - def.resolve() - }).fail(function(e){ - $.notify("Error in duplicate item", "error"); - polemarch.showErrors(e) - def.reject(e) - }) - }).fail(function(e){ - def.reject(e) - }) - - return def.promise(); -} - -pmTemplates.execute = function(item_id) -{ - var def = new $.Deferred(); - spajs.ajax.Call({ - url: "/api/v1/"+this.model.name+"/" + item_id+"/execute/", - type: "POST", - data:JSON.stringify({}), - contentType:'application/json', - success: function(data) - { - $.notify("Started", "success"); - if(data && data.history_id) - { - $.when(spajs.open({ menuId:"history/"+data.history_id}) ).done(function(){ - def.resolve() - }).fail(function(e){ - def.reject(e) - }) - } - else - { - def.reject({text:"No history_id", status:500}) - } - }, - error:function(e) - { - def.reject(e) - polemarch.showErrors(e.responseJSON) - } - }) - - return def.promise(); -} - - -// Содержит соответсвия разных kind к объектами с ними работающими. -pmTemplates.model.kindObjects = {} - - -pmTemplates.exportToFile = function(item_ids) -{ - var def = new $.Deferred(); - if(!item_ids) - { - $.notify("No data for export", "error"); - def.reject("No data for export"); - return def.promise(); - } - - var data = { - "filter": { - "id__in": item_ids, - }, - } - - var thisObj = this; - spajs.ajax.Call({ - url: "/api/v1/"+this.model.name+"/filter/?detail=1", - type: "POST", - contentType:'application/json', - data:JSON.stringify(data), - success: function(data) - { - var filedata = [] - for(var i in data.results) - { - var val = data.results[i] - delete val['id']; - delete val['url']; - - filedata.push({ - item: thisObj.model.page_name, - data: val - }) - } - - var fileInfo = { - data:filedata, - count:filedata.length, - version:"1" - } - - var textFileAsBlob = new Blob([JSON.stringify(fileInfo)], { - type: 'text/plain' - }); - - var newLink = document.createElement('a') - newLink.href = window.URL.createObjectURL(textFileAsBlob) - newLink.download = thisObj.model.name+"-"+Date()+".json" - newLink.target = "_blanl" - var event = new MouseEvent("click"); - newLink.dispatchEvent(event); - - - def.resolve(); - }, - error:function(e) - { - console.warn(e) - polemarch.showErrors(e) - def.reject(e); - } - }); - - return def.promise(); -} - -pmTemplates.importFromFile = function(files_event, project_id) -{ - var def = new $.Deferred(); - this.model.files = files_event - - for(var i=0; i 1) - { - polemarch.showErrors("Error file version is "+filedata.version) - def.reject({text:"Error file version is "+filedata.version}); - return; - } - - for(var i in filedata.data) - { - var val = filedata.data[i] - val.data.data.project = project_id - val.type = "add" - bulkdata.push(val) - } - console.log(bulkdata) - - spajs.ajax.Call({ - url: "/api/v1/_bulk/", - type: "POST", - contentType:'application/json', - data:JSON.stringify(bulkdata), - success: function(data) - { - def.resolve(); - spajs.openURL(window.location.href); - }, - error:function(e) - { - console.warn(e) - polemarch.showErrors(e) - def.reject(e); - } - }); - }; - })(i); - reader.readAsText(files_event.target.files[i]); - - // Нет поддержки загрузки более одного файла за раз. - break; - } - - return def.promise(); + +var pmTemplates = inheritance(pmItems) + +pmTemplates.model.name = "templates" + + +// Поддерживаемые kind /api/v1/templates/supported-kinds/ +pmTemplates.model.kind = "Task,Module" +pmTemplates.model.page_name = "templates" +pmTemplates.model.bulk_name = "template" +pmTemplates.model.className = "pmTemplates" + +pmTemplates.copyAndEdit = function (item_id) +{ + if (!item_id) + { + throw "Error in pmTemplates.copyAndEdit with item_id = `" + item_id + "`" + } + + var def = new $.Deferred(); + var thisObj = this; + return $.when(this.copyItem(item_id)).done(function (newItemId) + { + $.when(spajs.open({menuId: thisObj.model.page_name + "/" + thisObj.model.items[item_id].kind + "/" + newItemId})).done(function () { + $.notify("Item was duplicate", "success"); + def.resolve() + }).fail(function (e) { + $.notify("Error in duplicate item", "error"); + polemarch.showErrors(e) + def.reject(e) + }) + }).fail(function (e) { + def.reject(e) + }) + + return def.promise(); +} + +pmTemplates.execute = function (item_id, option) +{ + if(!option || option=="[object Object]") + { + option={}; + } + else + { + option={"option": option}; + } + var def = new $.Deferred(); + spajs.ajax.Call({ + url: "/api/v1/" + this.model.name + "/" + item_id + "/execute/", + type: "POST", + data: JSON.stringify(option), + contentType: 'application/json', + success: function (data) + { + $.notify("Started", "success"); + if (data && data.history_id) + { + $.when(spajs.open({menuId: "history/" + data.history_id})).done(function () { + def.resolve() + }).fail(function (e) { + def.reject(e) + }) + } else + { + def.reject({text: "No history_id", status: 500}) + } + }, + error: function (e) + { + def.reject(e) + polemarch.showErrors(e.responseJSON) + } + }) + + return def.promise(); +} + + +// Содержит соответсвия разных kind к объектами с ними работающими. +pmTemplates.model.kindObjects = {} + + +pmTemplates.exportToFile = function (item_ids) +{ + var def = new $.Deferred(); + if (!item_ids) + { + $.notify("No data for export", "error"); + def.reject("No data for export"); + return def.promise(); + } + + var data = { + "filter": { + "id__in": item_ids, + }, + } + + var thisObj = this; + spajs.ajax.Call({ + url: "/api/v1/" + this.model.name + "/filter/?detail=1", + type: "POST", + contentType: 'application/json', + data: JSON.stringify(data), + success: function (data) + { + var filedata = [] + for (var i in data.results) + { + var val = data.results[i] + delete val['id']; + delete val['url']; + + filedata.push({ + item: thisObj.model.page_name, + data: val + }) + } + + var fileInfo = { + data: filedata, + count: filedata.length, + version: "1" + } + + var textFileAsBlob = new Blob([JSON.stringify(fileInfo)], { + type: 'text/plain' + }); + + var newLink = document.createElement('a') + newLink.href = window.URL.createObjectURL(textFileAsBlob) + newLink.download = thisObj.model.name + "-" + Date() + ".json" + newLink.target = "_blanl" + var event = new MouseEvent("click"); + newLink.dispatchEvent(event); + + + def.resolve(); + }, + error: function (e) + { + console.warn(e) + polemarch.showErrors(e) + def.reject(e); + } + }); + + return def.promise(); +} + +pmTemplates.importFromFile = function (files_event, project_id) +{ + var def = new $.Deferred(); + this.model.files = files_event + + for (var i = 0; i < files_event.target.files.length; i++) + { + var t1 = $(".input-file")[0].files[0]; + var fileParts = t1.name.split("."); + var fileExt = fileParts[fileParts.length - 1].toLowerCase(); + var fileSize = t1.size; + if (fileExt == "txt" || fileExt == "json") + { + if (fileSize <= 2000000) { + var reader = new FileReader(); + reader.onload = (function (index_in_files_array) + { + return function (e) + { + console.log(e) + var bulkdata = [] + try + { + var filedata = JSON.parse(e.target.result); + if (filedata.version / 1 > 1) + { + polemarch.showErrors("Error file version is " + filedata.version) + def.reject({text: "Error file version is " + filedata.version}); + return; + } + + for (var i in filedata.data) + { + var val = filedata.data[i] + val.data.data.project = project_id + val.type = "add" + bulkdata.push(val) + } + console.log(bulkdata) + + spajs.ajax.Call({ + url: "/api/v1/_bulk/", + type: "POST", + contentType: 'application/json', + data: JSON.stringify(bulkdata), + success: function (data) + { + def.resolve(); + $.notify("JSON-file was imported", "success"); + spajs.openURL(window.location.href); + }, + error: function (e) + { + console.warn(e) + polemarch.showErrors(e) + def.reject(e); + } + }); + } catch (e) + { + $.notify("Invalid/incorrect JSON-file", "error"); + def.reject(); + } + }; + })(i); + reader.readAsText(files_event.target.files[i]); + } else + { + $.notify("File's size has to be less, than 2MB", "error"); + def.reject(); + } + + } else + { + $.notify("File has to be .txt or .json format", "error"); + def.reject(); + } + // Нет поддержки загрузки более одного файла за раз. + break; + } + + return def.promise(); } \ No newline at end of file diff --git a/polemarch/static/js/pmUsers.js b/polemarch/static/js/pmUsers.js index b7a714f4..176490d9 100644 --- a/polemarch/static/js/pmUsers.js +++ b/polemarch/static/js/pmUsers.js @@ -1,273 +1,274 @@ - -var pmUsers = inheritance(pmItems) - -pmUsers.model.name = "users" -pmUsers.model.page_name = "user" -pmUsers.model.className = "pmUsers" - -pmUsers.model.page_list = { - buttons:[ - { - class:'btn btn-primary', - function:function(){ return "spajs.open({ menuId:'new-"+this.model.page_name+"'}); return false;"}, - title:'Create', - link:function(){ return '/?new-'+this.model.page_name}, - }, - ], - title: "Users", - short_title: "Users", - fileds:[ - { - title:'Name', - name:'username', - } - ], - actions:[ - { - class:'btn btn-danger', - function:function(item){ return 'spajs.showLoader('+this.model.className+'.deleteItem('+item.id+')); return false;'}, - title:'Delete', - link:function(){ return '#'} - } - ] -} - -pmUsers.model.page_new = { - title: "New user", - short_title: "New user", - fileds:[ - [ - { - filed: new filedsLib.filed.text(), - title:'User name', - name:'username', - placeholder:'Enter user name', - help:'', - validator:function(value){ - return filedsLib.validator.notEmpty(value, 'Name') - }, - fast_validator:function(value){ return value != '' && value} - }, - { - filed: new filedsLib.filed.password(), - title:'Password', - name:'password', - placeholder:'Enter user password', - help:'', - validator:function(value){ - return filedsLib.validator.notEmpty(value, 'Password') - }, - fast_validator:function(value){ return value != '' && value} - }, - ],[ - { - filed: new filedsLib.filed.text(), - title:'Email', - name:'email', - placeholder:'Enter user email', - help:'', - }, - { - filed: new filedsLib.filed.text(), - title:'First name', - name:'first_name', - placeholder:'Enter user first name', - help:'', - }, - ],[ - { - filed: new filedsLib.filed.text(), - title:'Last name', - name:'last_name', - placeholder:'Enter user last name', - help:'', - }, - { - filed: new filedsLib.filed.boolean(), - title:'Is active', - name:'is_active', - default:true, - } - ] - ], - onBeforeSave:function(data, item_id) - { - data.is_staff = true - return data; - }, - onCreate:function(result) - { - var def = new $.Deferred(); - $.notify("User created", "success"); - $.when(spajs.open({ menuId:pmUsers.model.page_name+"/"+result.id})).always(function(){ - def.resolve() - }) - - return def.promise(); - } -} - -pmUsers.model.page_item = { - buttons:[ - { - class:'btn btn-primary', - function:function(item_id){ return 'spajs.showLoader('+this.model.className+'.updateItem('+item_id+')); return false;'}, - title:'Save', - link:function(){ return '#'}, - }, - { - class:'btn btn-default copy-btn', - function:function(item_id){ return 'spajs.showLoader('+this.model.className+'.copyAndEdit('+item_id+')); return false;'}, - title:'', - link:function(){ return '#'}, - help:'Copy' - }, - { - class:'btn btn-danger danger-right', - function:function(item_id){ return 'spajs.showLoader('+this.model.className+'.deleteItem('+item_id+')); return false;'}, - title:' ', - link:function(){ return '#'}, - }, - ], - title: function(item_id){ - return "User "+pmUsers.model.items[item_id].justText('username') - }, - short_title: function(item_id){ - return "User "+pmUsers.model.items[item_id].justText('username', function(v){return v.slice(0, 20)}) - }, - fileds:[ - [ - { - filed: new filedsLib.filed.text(), - title:'User name', - name:'username', - placeholder:'Enter user name', - help:'', - validator:function(value){ - return filedsLib.validator.notEmpty(value, 'Name') - }, - fast_validator:function(value){ return value != '' && value} - }, - { - filed: new filedsLib.filed.password(), - title:'Password', - name:'password', - placeholder:'Enter user password', - help:'', - }, - ],[ - { - filed: new filedsLib.filed.text(), - title:'Email', - name:'email', - placeholder:'Enter user email', - help:'', - }, - { - filed: new filedsLib.filed.text(), - title:'First name', - name:'first_name', - placeholder:'Enter user first name', - help:'', - }, - ],[ - { - filed: new filedsLib.filed.text(), - title:'Last name', - name:'last_name', - placeholder:'Enter user last name', - help:'', - }, - { - filed: new filedsLib.filed.boolean(), - title:'Is active', - name:'is_active', - } - ] - ], - onUpdate:function(result) - { - return true; - }, - onBeforeSave:function(data, item_id) - { - if(!data.password) - { - delete data.password - } - data.is_staff = true - - return data; - }, -} - - -pmUsers.copyItem = function(item_id) -{ - var def = new $.Deferred(); - var thisObj = this; - - $.when(this.loadItem(item_id)).done(function() - { - var data = thisObj.model.items[item_id]; - delete data.id; - data.username = "copy-from-" + data.username - - $.when(encryptedCopyModal.replace(data)).done(function(data) - { - spajs.ajax.Call({ - url: "/api/v1/"+thisObj.model.name+"/", - type: "POST", - contentType:'application/json', - data: JSON.stringify(data), - success: function(data) - { - thisObj.model.items[data.id] = data - def.resolve(data.id) - }, - error:function(e) - { - def.reject(e) - } - }); - }).fail(function(e) - { - def.reject(e) - }) - }).fail(function(e) - { - def.reject(e) - }) - - - return def.promise(); -} - - tabSignal.connect("polemarch.start", function() - { - // users - spajs.addMenu({ - id:"users", - urlregexp:[/^users$/, /^user$/, /^users\/search\/?$/, /^users\/page\/([0-9]+)$/], - onOpen:function(holder, menuInfo, data){return pmUsers.showList(holder, menuInfo, data);} - }) - - spajs.addMenu({ - id:"users-search", - urlregexp:[/^users\/search\/([A-z0-9 %\-.:,=]+)$/], - onOpen:function(holder, menuInfo, data){return pmUsers.showSearchResults(holder, menuInfo, data);} - }) - - spajs.addMenu({ - id:"user", - urlregexp:[/^user\/([0-9]+)$/, /^users\/([0-9]+)$/], - onOpen:function(holder, menuInfo, data){return pmUsers.showItem(holder, menuInfo, data);} - }) - - spajs.addMenu({ - id:"newuser", - urlregexp:[/^new-user$/], - onOpen:function(holder, menuInfo, data){return pmUsers.showNewItemPage(holder, menuInfo, data);} - }) - - }) + +var pmUsers = inheritance(pmItems) + +pmUsers.model.name = "users" +pmUsers.model.page_name = "user" +pmUsers.model.className = "pmUsers" + +pmUsers.model.page_list = { + buttons:[ + { + class:'btn btn-primary', + function:function(){ return "spajs.open({ menuId:'new-"+this.model.page_name+"'}); return false;"}, + title:'Create', + link:function(){ return '/?new-'+this.model.page_name}, + }, + ], + title: "Users", + short_title: "Users", + fileds:[ + { + title:'Name', + name:'username', + } + ], + actions:[ + { + class:'btn btn-danger', + function:function(item){ return 'spajs.showLoader('+this.model.className+'.deleteItem('+item.id+')); return false;'}, + title:'Delete', + link:function(){ return '#'} + } + ] +} + +pmUsers.model.page_new = { + title: "New user", + short_title: "New user", + fileds:[ + [ + { + filed: new filedsLib.filed.text(), + title:'User name', + name:'username', + placeholder:'Enter user name', + help:'', + validator:function(value){ + return filedsLib.validator.notEmpty(value, 'Name') + }, + fast_validator:function(value){ return value != '' && value} + }, + { + filed: new filedsLib.filed.password(), + title:'Password', + name:'password', + placeholder:'Enter user password', + help:'', + validator:function(value){ + return filedsLib.validator.notEmpty(value, 'Password') + }, + fast_validator:function(value){ return value != '' && value} + }, + ],[ + { + filed: new filedsLib.filed.text(), + title:'Email', + name:'email', + placeholder:'Enter user email', + help:'', + }, + { + filed: new filedsLib.filed.text(), + title:'First name', + name:'first_name', + placeholder:'Enter user first name', + help:'', + }, + ],[ + { + filed: new filedsLib.filed.text(), + title:'Last name', + name:'last_name', + placeholder:'Enter user last name', + help:'', + }, + { + filed: new filedsLib.filed.boolean(), + title:'Is active', + name:'is_active', + default:true, + } + ] + ], + onBeforeSave:function(data, item_id) + { + data.is_staff = true + return data; + }, + onCreate:function(result) + { + var def = new $.Deferred(); + $.notify("User created", "success"); + $.when(spajs.open({ menuId:pmUsers.model.page_name+"/"+result.id})).always(function(){ + def.resolve() + }) + + return def.promise(); + } +} + +pmUsers.model.page_item = { + buttons:[ + { + class:'btn btn-primary', + function:function(item_id){ return 'spajs.showLoader('+this.model.className+'.updateItem('+item_id+')); return false;'}, + title:'Save', + link:function(){ return '#'}, + }, + { + class:'btn btn-default copy-btn', + function:function(item_id){ return 'spajs.showLoader('+this.model.className+'.copyAndEdit('+item_id+')); return false;'}, + title:'', + link:function(){ return '#'}, + help:'Copy' + }, + { + class:'btn btn-danger danger-right', + function:function(item_id){ return 'spajs.showLoader('+this.model.className+'.deleteItem('+item_id+')); return false;'}, + title:' ', + link:function(){ return '#'}, + }, + ], + title: function(item_id){ + return "User "+pmUsers.model.items[item_id].justText('username') + }, + short_title: function(item_id){ + return "User "+pmUsers.model.items[item_id].justText('username', function(v){return v.slice(0, 20)}) + }, + fileds:[ + [ + { + filed: new filedsLib.filed.text(), + title:'User name', + name:'username', + placeholder:'Enter user name', + help:'', + validator:function(value){ + return filedsLib.validator.notEmpty(value, 'Name') + }, + fast_validator:function(value){ return value != '' && value} + }, + { + filed: new filedsLib.filed.password(), + title:'Password', + name:'password', + placeholder:'Enter user password', + help:'', + }, + ],[ + { + filed: new filedsLib.filed.text(), + title:'Email', + name:'email', + placeholder:'Enter user email', + help:'', + }, + { + filed: new filedsLib.filed.text(), + title:'First name', + name:'first_name', + placeholder:'Enter user first name', + help:'', + }, + ],[ + { + filed: new filedsLib.filed.text(), + title:'Last name', + name:'last_name', + placeholder:'Enter user last name', + help:'', + }, + { + filed: new filedsLib.filed.boolean(), + title:'Is active', + name:'is_active', + } + ] + ], + onUpdate:function(result) + { + return true; + }, + onBeforeSave:function(data, item_id) + { + if(!data.password) + { + delete data.password + } + data.is_staff = true + + return data; + }, +} + + +pmUsers.copyItem = function(item_id) +{ + var def = new $.Deferred(); + var thisObj = this; + + $.when(this.loadItem(item_id)).done(function() + { + var data = thisObj.model.items[item_id]; + delete data.id; + data.username = "copy-from-" + data.username + + $.when(encryptedCopyModal.replace(data)).done(function(data) + { + spajs.ajax.Call({ + url: "/api/v1/"+thisObj.model.name+"/", + type: "POST", + contentType:'application/json', + data: JSON.stringify(data), + success: function(data) + { + thisObj.model.items[data.id] = data + def.resolve(data.id) + }, + error:function(e) + { + def.reject(e) + } + }); + }).fail(function(e) + { + def.reject(e) + }) + }).fail(function(e) + { + def.reject(e) + }) + + + return def.promise(); +} + + + tabSignal.connect("polemarch.start", function() + { + // users + spajs.addMenu({ + id:"users", + urlregexp:[/^users$/, /^user$/, /^users\/search\/?$/, /^users\/page\/([0-9]+)$/], + onOpen:function(holder, menuInfo, data){return pmUsers.showList(holder, menuInfo, data);} + }) + + spajs.addMenu({ + id:"users-search", + urlregexp:[/^users\/search\/([A-z0-9 %\-.:,=]+)$/, /^users\/search\/([A-z0-9 %\-.:,=]+)\/page\/([0-9]+)$/], + onOpen:function(holder, menuInfo, data){return pmUsers.showSearchResults(holder, menuInfo, data);} + }) + + spajs.addMenu({ + id:"user", + urlregexp:[/^user\/([0-9]+)$/, /^users\/([0-9]+)$/], + onOpen:function(holder, menuInfo, data){return pmUsers.showItem(holder, menuInfo, data);} + }) + + spajs.addMenu({ + id:"newuser", + urlregexp:[/^new-user$/], + onOpen:function(holder, menuInfo, data){return pmUsers.showNewItemPage(holder, menuInfo, data);} + }) + + }) diff --git a/polemarch/static/js/polemarch.js b/polemarch/static/js/polemarch.js index 6e387f99..259eb840 100644 --- a/polemarch/static/js/polemarch.js +++ b/polemarch/static/js/polemarch.js @@ -199,9 +199,6 @@ spajs.errorPage = function(holder, menuInfo, data, error_data) error.text = error_data.detail.toString() } } - - - debugger; - + $(holder).insertTpl(spajs.just.render("errorPage", {error:error, data:data, menuInfo:menuInfo})) } diff --git a/polemarch/static/js/tests/qUnitTest.js b/polemarch/static/js/tests/qUnitTest.js index 3e62940a..cb9f497c 100644 --- a/polemarch/static/js/tests/qUnitTest.js +++ b/polemarch/static/js/tests/qUnitTest.js @@ -366,6 +366,75 @@ window.qunitTestsArray.push({ }); }}) +/** + * Тестирование crontabEditor + */ +window.qunitTestsArray.push({ + step:1400, + test:function() +{ + syncQUnit.addTest('crontabEditor', function ( assert ) + { + var done = assert.async(); + + var cronString = "1 * * * *" + + crontabEditor.parseCronString(undefined) + assert.ok(cronString != crontabEditor.getCronString(), 'getCronString'); + + crontabEditor.parseCronString("1 5") + assert.ok(cronString != crontabEditor.getCronString(), 'getCronString'); + + crontabEditor.parseCronString(cronString) + assert.ok(cronString == crontabEditor.getCronString(), 'getCronString'); + + cronString = "1 1 1 1 1" + crontabEditor.parseCronString(cronString) + assert.ok("1 1 1 1 1" == crontabEditor.getCronString(), 'getCronString'); + + crontabEditor.setDaysOfWeek("1-2") + assert.ok("1 1 1 1 1,2" == crontabEditor.getCronString(), 'getCronString'); + + crontabEditor.setMonths("1-2") + assert.ok("1 1 1 1,2 1,2" == crontabEditor.getCronString(), 'getCronString'); + + crontabEditor.setDayOfMonth("1-2") + assert.ok("1 1 1,2 1,2 1,2" == crontabEditor.getCronString(), 'getCronString'); + + crontabEditor.setHours("1-2") + assert.ok("1 1,2 1,2 1,2 1,2" == crontabEditor.getCronString(), 'getCronString'); + + crontabEditor.setMinutes("1-2") + assert.ok("1,2 1,2 1,2 1,2 1,2" == crontabEditor.getCronString(), 'getCronString'); + + crontabEditor.setMinutes("1,2,7") + assert.ok("1,2,7 1,2 1,2 1,2 1,2" == crontabEditor.getCronString(), 'getCronString'); + + crontabEditor.setMinutes("1,2,*/7") + assert.ok("*/7,1,2 1,2 1,2 1,2 1,2" == crontabEditor.getCronString(), 'getCronString'); + + crontabEditor.setMinutes("1,2,3,4,*/7") + assert.ok("*/7,1-4 1,2 1,2 1,2 1,2" == crontabEditor.getCronString(), 'getCronString'); + + crontabEditor.setMinutes("1,2,3,4,*/7,45-51") + assert.ok("*/7,1-4,45-48,50,51 1,2 1,2 1,2 1,2" == crontabEditor.getCronString(), 'getCronString'); + + crontabEditor.setMinutes("1,2,3,4,*/7,45-51,17-30/2") + assert.ok("*/7,*/23,*/25,1-4,17,19,27,29,45,47,48,51 1,2 1,2 1,2 1,2" == crontabEditor.getCronString(), 'getCronString'); + + crontabEditor.setMinutes("1,2,3,4,*/7,45-51,17-380/2") + assert.ok("0-4,7,14,17,19,21,23,25,27-29,31,33,35,37,39,41-43,45-51,53,55-57,59 1,2 1,2 1,2 1,2" == crontabEditor.getCronString(), 'getCronString'); + + crontabEditor.setMinutes("1,2,3,4,*/7,45-51,170-38/2") + assert.ok("*/7,*/12,*/16,1-4,6,8,10,18,20,22,26,30,34,38,45-47,50,51 1,2 1,2 1,2 1,2" == crontabEditor.getCronString(), 'getCronString'); + + crontabEditor.setMinutes("1,2,3,4,5/5,45-51,170-38/2") + assert.ok("*/5,*/12,*/16,1-4,6,8,14,18,22,26,28,34,38,46,47,49,51 1,2 1,2 1,2 1,2" == crontabEditor.getCronString(), 'getCronString'); + + render(done) + }); +}}) + /** * Тестирование users */ @@ -389,6 +458,24 @@ window.qunitTestsArray.push({ }) }); + syncQUnit.addTest('Страница списка пользователей toggleSelectEachItem', function ( assert ) + { + var done = assert.async(); + + pmUsers.toggleSelectAll($('.multiple-select tr'), true); + + $.when(pmUsers.toggleSelectEachItem(true)).done(function() + { + assert.ok(true, 'ok:toggleSelectEachItem'); + render(done) + }).fail(function() + { + debugger; + assert.ok(false, 'error:toggleSelectEachItem'); + render(done) + }) + }) + syncQUnit.addTest('Открытие страницы добавления пользователя', function ( assert ) { var done = assert.async(); @@ -1383,6 +1470,38 @@ window.qunitTestsArray.push({ }) }) + syncQUnit.addTest('Проверка showAddSubHostsForm в inventory', function ( assert ) + { + var done = assert.async(); + var itemId = /inventory\/([0-9]+)/.exec(window.location.href)[1] + $.when(pmInventories.showAddSubHostsForm(itemId)).done(function() + { + assert.ok(true, 'Проверка showAddSubHostsForm успешна'); + render(done) + }).fail(function() + { + debugger; + assert.ok(false, 'Ошибка при showAddSubHostsForm'); + render(done) + }) + }) + + syncQUnit.addTest('Проверка showAddSubGroupsForm в inventory', function ( assert ) + { + var done = assert.async(); + var itemId = /inventory\/([0-9]+)/.exec(window.location.href)[1] + $.when(pmInventories.showAddSubGroupsForm(itemId)).done(function() + { + assert.ok(true, 'Проверка showAddSubGroupsForm успешна'); + render(done) + }).fail(function() + { + debugger; + assert.ok(false, 'Ошибка при showAddSubGroupsForm'); + render(done) + }) + }) + syncQUnit.addTest('Проверка добавления невалидных хостов к inventory', function ( assert ) { var done = assert.async(); @@ -2092,6 +2211,39 @@ window.qunitTestsArray.push({ render(done) }) }) + + syncQUnit.addTest('Страница periodic-tasks.toggleSelectEachItem', function ( assert ) + { + var done = assert.async(); + $.when(pmPeriodicTasks.toggleSelectEachItem(true, projectId)).done(function() + { + assert.ok(true, 'ok:toggleSelectEachItem'); + render(done) + }).fail(function() + { + debugger; + assert.ok(false, 'error:toggleSelectEachItem'); + render(done) + }) + }) + + syncQUnit.addTest('Страница periodic-tasks.search', function ( assert ) + { + var done = assert.async(); + $.when(pmPeriodicTasks.search("test", {project_id:projectId})).done(function() + { + assert.ok(true, 'ok:periodic-tasks.search'); + render(done) + }).fail(function() + { + debugger; + assert.ok(false, 'error:periodic-tasks.search'); + render(done) + }) + }) + + // pmPeriodicTasks.showSearchResults + /* syncQUnit.addTest('Страница нового inventory для проекта', function ( assert ) { @@ -2354,7 +2506,9 @@ window.qunitTestsArray.push({ { assert.ok(true, 'Успешно copyAndEdit add Item'); render(done) - }).fail(function(){ + }).fail(function() + { + debugger; assert.ok(false, 'Ошибка при copyAndEdit add Item'); render(done) }) @@ -2373,11 +2527,30 @@ window.qunitTestsArray.push({ { assert.ok(true, 'Успешно delete add Item'); render(done) - }).fail(function(){ + }).fail(function() + { + debugger; assert.ok(false, 'Ошибка при delete add Item'); render(done) }) }); + + syncQUnit.addTest('execute для Periodic Task', function ( assert ) + { + var done = assert.async(); + + // Удаление пользователя. + $.when(pmPeriodicTasks.execute(projectId, taskId)).done(function() + { + assert.ok(true, 'Успешно execute для pmPeriodicTasks'); + render(done) + }).fail(function() + { + debugger; + assert.ok(false, 'Ошибка при execute для pmPeriodicTasks'); + render(done) + }) + }); syncQUnit.addTest('Удаление periodic task', function ( assert ) { @@ -2388,7 +2561,9 @@ window.qunitTestsArray.push({ { assert.ok(true, 'Успешно delete Item'); render(done) - }).fail(function(){ + }).fail(function() + { + debugger; assert.ok(false, 'Ошибка при delete Item'); render(done) }) @@ -2404,6 +2579,7 @@ window.qunitTestsArray.push({ render(done) }).fail(function() { + debugger; assert.ok(false, 'Страница не открылась'); render(done) }) @@ -2419,6 +2595,7 @@ window.qunitTestsArray.push({ render(done) }).fail(function() { + debugger; assert.ok(false, 'Страница не открылась'); render(done) }) @@ -2437,7 +2614,9 @@ window.qunitTestsArray.push({ { assert.ok(true, 'Успешно delete Item'); render(done) - }).fail(function(){ + }).fail(function() + { + debugger; assert.ok(false, 'Ошибка при delete Item'); render(done) }) @@ -2496,7 +2675,10 @@ window.qunitTestsArray.push({ $("#Templates-name").val("!2 d#"); jsonEditor.__devAddVar("syntax-check32", "syntax-check32") - + + jsonEditor.jsonEditorImportVars("playbook", "prefix", "syntax-check=\n") + jsonEditor.jsonEditorImportVars("playbook", "prefix", "syntax-check:\n") + // Отправка формы с данными project $.when(pmTasksTemplates.addItem()).done(function() { @@ -2511,7 +2693,7 @@ window.qunitTestsArray.push({ }); syncQUnit.addTest('Сохранение шаблона задачи', function ( assert ) - { + { // Предполагается что мы от прошлого теста попали на страницу создания project var done = assert.async(); @@ -2623,6 +2805,29 @@ window.qunitTestsArray.push({ }) }); + syncQUnit.addTest('Удаление шаблона', function ( assert ) + { + var done = assert.async(); + + $.when(spajs.open({ menuId:"template/Task/"+itemId})).done(function() + { + $.when(pmTasksTemplates.saveAndExecute(itemId)).done(function() + { + assert.ok(true, 'Успешно pmTasksTemplates.saveAndExecute'); + render(done) + }).fail(function(){ + debugger; + assert.ok(false, 'Ошибка при pmTasksTemplates.saveAndExecute'); + render(done) + }) + }).fail(function() + { + debugger; + assert.ok(false, 'Ошибка при открытиии меню template/Module/'+itemId); + render(done) + }) + }); + syncQUnit.addTest('Удаление шаблона', function ( assert ) { var done = assert.async(); @@ -2638,15 +2843,7 @@ window.qunitTestsArray.push({ render(done) }) }); -}}) -/** - * Тестирование шаблонов модулей - */ -window.qunitTestsArray.push({ - step:800, - test:function() -{ syncQUnit.addTest('Список шаблонов', function ( assert ) { var done = assert.async(); @@ -2663,12 +2860,12 @@ window.qunitTestsArray.push({ }) }); - syncQUnit.addTest('Новый template/new-module', function ( assert ) + syncQUnit.addTest('Новый template/new-task', function ( assert ) { var done = assert.async(); // Открытие пункта меню new-project - $.when(spajs.open({ menuId:"template/new-module"})).done(function() + $.when(spajs.open({ menuId:"template/new-task"})).done(function() { assert.ok(true, 'Успешно открыто меню new-project'); render(done) @@ -2683,18 +2880,21 @@ window.qunitTestsArray.push({ var t = new Date(); t = t.getTime() - syncQUnit.addTest('Сохранение не валидного шаблона модуля', function ( assert ) + syncQUnit.addTest('Сохранение не валидного шаблона задачи', function ( assert ) { // Предполагается что мы от прошлого теста попали на страницу создания project var done = assert.async(); // Заполнение формы с данными project - $("#Templates-name").val("test-template-"+t); - - jsonEditor.__devAddVar("new-vault-password-file2", "syntax-check") - + $("#Templates-name").val("!2 d#"); + + jsonEditor.__devAddVar("syntax-check32", "syntax-check32") + + jsonEditor.jsonEditorImportVars("playbook", "prefix", "syntax-check=\n") + jsonEditor.jsonEditorImportVars("playbook", "prefix", "syntax-check:\n") + // Отправка формы с данными project - $.when(pmModuleTemplates.addItem()).done(function() + $.when(pmTasksTemplates.addItem()).done(function() { debugger; assert.ok(false, 'Успешно template add Item, а не должно было'); @@ -2705,25 +2905,18 @@ window.qunitTestsArray.push({ render(done) }) }); - - syncQUnit.addTest('Сохранение шаблона модуля', function ( assert ) - { + + syncQUnit.addTest('Сохранение шаблона задачи', function ( assert ) + { // Предполагается что мы от прошлого теста попали на страницу создания project var done = assert.async(); - jsonEditor.jsonEditorRmVar("new-vault-password-file2") - $("#new_json_nameprefix").val("new-vault-password-file"); - $("#new_json_valueprefix").val("syntax-check"); - jsonEditor.jsonEditorAddVar(); - - $("#inventories-autocomplete").val($("#inventories-autocomplete option")[0].value).trigger('change.select2'); - - // Заполнение формы с данными project $("#Templates-name").val("test-template-"+t); + jsonEditor.jsonEditorRmVar("syntax-check32"); // Отправка формы с данными project - $.when(pmModuleTemplates.addItem()).done(function() + $.when(pmTasksTemplates.addItem()).done(function() { assert.ok(true, 'Успешно template add Item'); render(done) @@ -2735,86 +2928,314 @@ window.qunitTestsArray.push({ }) }); - syncQUnit.addTest('Изменение не валидного шаблона модуля', function ( assert ) - { + syncQUnit.addTest('Открытие страницы создания новой опции шаблона задачи', function ( assert ) + { + // Предполагается что мы от прошлого теста попали на страницу создания project var done = assert.async(); + var itemId = /template\/Task\/([0-9]+)/.exec(window.location.href)[1]; - // Предполагается что мы от прошлого теста попали на страницу редактирования project - // с адресом http://192.168.0.12:8080/?group-5 - var itemId = /template\/Module\/([0-9]+)/.exec(window.location.href)[1] + $.when(spajs.open({ menuId:"template/Task/"+itemId+"/new-option"})).done(function() + { + assert.ok(true, 'Успешно открыто меню templates/Task/item_id/new-option'); + render(done) + }).fail(function() + { + debugger; + assert.ok(false, 'Ошибка при открытиии меню templates/Task/item_id/new-option'); + render(done) + }) + }); - $("#module-autocomplete").val("test2-playbook-"+t); - jsonEditor.__devAddVar("new-vault-password-file2", "syntax-check") + syncQUnit.addTest('Сохранение новой невалидной опции шаблона задачи', function ( assert ) + { + // Предполагается что мы от прошлого теста попали на страницу создания project + var done = assert.async(); + var itemId = /template\/Task\/([0-9]+)\/new-option/.exec(window.location.href)[1]; + $("#filed_option_name").val("test-option"); - $.when(pmModuleTemplates.updateItem(itemId)).done(function() + $.when(pmTasksTemplates.saveOption(itemId)).done(function() { debugger; - assert.ok(false, 'Успешно update add Item, а не должно было'); + assert.ok(false, 'Успешно сохранено, а не должно было'); render(done) }).fail(function(){ - assert.ok(true, 'Ошибка при update add Item, как и задумано'); + assert.ok(true, 'Ошибка при сохранении, как и задумано'); render(done) }) + + }); - syncQUnit.addTest('Изменение шаблона модуля', function ( assert ) + syncQUnit.addTest('Сохранение новой валидной опции шаблона задачи', function ( assert ) { + // Предполагается что мы от прошлого теста попали на страницу создания project var done = assert.async(); - - var itemId = /template\/Module\/([0-9]+)/.exec(window.location.href)[1] - - jsonEditor.jsonEditorRmVar("new-vault-password-file2") - $("#module-autocomplete").val("test2-playbook-"+t); + var itemId = /template\/Task\/([0-9]+)\/new-option/.exec(window.location.href)[1]; - $("#new_json_nameprefix").val("new-vault-password-file"); - $("#new_json_valueprefix").val("syntax-check"); + jsonEditor.jsonEditorRmVar("become2") + $("#new_json_nameprefix").val("become"); jsonEditor.jsonEditorAddVar(); - - $.when(pmModuleTemplates.updateItem(itemId)).done(function() + $.when(pmTasksTemplates.saveOption(itemId)).done(function() { - assert.ok(true, 'Успешно update add Item'); + assert.ok(true, 'Опция успешно сохранена'); render(done) }).fail(function(){ debugger; - assert.ok(false, 'Ошибка при update add Item'); + assert.ok(false, 'Ошибка при сохранении опции'); render(done) }) }); - var itemId = undefined - syncQUnit.addTest('Копирование template Module', function ( assert ) - { - itemId = /template\/Module\/([0-9]+)/.exec(window.location.href)[1] - + syncQUnit.addTest('Сохранение и запуск опции шаблона задачи', function ( assert ) + { + // Предполагается что мы от прошлого теста попали на страницу просмотра/редактирования опции шаблона var done = assert.async(); - $.when(pmModuleTemplates.copyAndEdit(itemId)).done(function() + var itemId = /template\/Task\/([0-9]+)\/option\/([A-z0-9 %\-.:,=]+)/.exec(window.location.href)[1]; + + $.when(pmTasksTemplates.saveAndExecuteOption(itemId)).done(function() { - assert.ok(true, 'Успешно copyAndEdit add Item'); + assert.ok(true, 'Опция успешно сохранена'); render(done) }).fail(function(){ debugger; - assert.ok(false, 'Ошибка при copyAndEdit add Item'); + assert.ok(false, 'Ошибка при сохранении опции'); render(done) }) }); -/* - syncQUnit.addTest('Выполнение template Module', function ( assert ) - { - var itemId = /template\/Module\/([0-9]+)/.exec(window.location.href)[1] - debugger; + + syncQUnit.addTest('Удаление шаблона', function ( assert ) + { var done = assert.async(); - $.when(pmModuleTemplates.execute(itemId)).done(function() + var itemId = /template\/Task\/([0-9]+)/.exec(window.location.href)[1]; + $.when(spajs.open({ menuId:"template/Task/"+itemId})).done(function() { - assert.ok(true, 'Успешно pmModuleTemplates.execute'); - render(done) - }).fail(function(){ - assert.ok(false, 'Ошибка при pmModuleTemplates.execute'); + $.when(pmTasksTemplates.saveAndExecute(itemId)).done(function() + { + assert.ok(true, 'Успешно pmTasksTemplates.saveAndExecute'); + render(done) + }).fail(function(){ + debugger; + assert.ok(false, 'Ошибка при pmTasksTemplates.saveAndExecute'); + render(done) + }) + }).fail(function() + { + debugger; + assert.ok(false, 'Ошибка при открытиии меню template/Module/'+itemId); render(done) }) - });*/ + }); + + syncQUnit.addTest('Удаление шаблона', function ( assert ) + { + var done = assert.async(); + var itemId = /template\/Task\/([0-9]+)/.exec(window.location.href)[1]; + // Удаление project. + $.when(pmTasksTemplates.deleteItem(itemId, true)).done(function() + { + assert.ok(true, 'Успешно delete Item'); + render(done) + }).fail(function(){ + debugger; + assert.ok(false, 'Ошибка при delete Item'); + render(done) + }) + }); +}}) + +/** + * Тестирование шаблонов модулей + */ +window.qunitTestsArray.push({ + step:800, + test:function() +{ + syncQUnit.addTest('Список шаблонов', function ( assert ) + { + var done = assert.async(); + + $.when(spajs.open({ menuId:"templates"})).done(function() + { + assert.ok(true, 'Успешно открыто меню templates'); + render(done) + }).fail(function() + { + debugger; + assert.ok(false, 'Ошибка при открытиии меню templates'); + render(done) + }) + }); + + syncQUnit.addTest('Новый template/new-module', function ( assert ) + { + var done = assert.async(); + + // Открытие пункта меню new-project + $.when(spajs.open({ menuId:"template/new-module"})).done(function() + { + assert.ok(true, 'Успешно открыто меню new-project'); + render(done) + }).fail(function() + { + debugger; + assert.ok(false, 'Ошибка при открытиии меню new-project'); + render(done) + }) + }); + + var t = new Date(); + t = t.getTime() + + syncQUnit.addTest('Сохранение не валидного шаблона модуля', function ( assert ) + { + // Предполагается что мы от прошлого теста попали на страницу создания project + var done = assert.async(); + + // Заполнение формы с данными project + $("#Templates-name").val("test-template-"+t); + + jsonEditor.__devAddVar("new-vault-password-file2", "syntax-check") + + // Отправка формы с данными project + $.when(pmModuleTemplates.addItem()).done(function() + { + debugger; + assert.ok(false, 'Успешно template add Item, а не должно было'); + render(done) + }).fail(function() + { + assert.ok(true, 'Ошибка при template add Item, как и задумано'); + render(done) + }) + }); + syncQUnit.addTest('Сохранение шаблона модуля', function ( assert ) + { + // Предполагается что мы от прошлого теста попали на страницу создания project + var done = assert.async(); + + jsonEditor.jsonEditorRmVar("new-vault-password-file2") + $("#new_json_nameprefix").val("new-vault-password-file"); + $("#new_json_valueprefix").val("syntax-check"); + jsonEditor.jsonEditorAddVar(); + + $("#inventories-autocomplete").val($("#inventories-autocomplete option")[0].value).trigger('change.select2'); + + + // Заполнение формы с данными project + $("#Templates-name").val("test-template-"+t); + + // Отправка формы с данными project + $.when(pmModuleTemplates.addItem()).done(function() + { + assert.ok(true, 'Успешно template add Item'); + render(done) + }).fail(function() + { + debugger; + assert.ok(false, 'Ошибка при template add Item'); + render(done) + }) + }); + + syncQUnit.addTest('Изменение не валидного шаблона модуля', function ( assert ) + { + var done = assert.async(); + + // Предполагается что мы от прошлого теста попали на страницу редактирования project + // с адресом http://192.168.0.12:8080/?group-5 + var itemId = /template\/Module\/([0-9]+)/.exec(window.location.href)[1] + + $("#module-autocomplete").val("test2-playbook-"+t); + jsonEditor.__devAddVar("new-vault-password-file2", "syntax-check") + + + $.when(pmModuleTemplates.updateItem(itemId)).done(function() + { + debugger; + assert.ok(false, 'Успешно update add Item, а не должно было'); + render(done) + }).fail(function(){ + assert.ok(true, 'Ошибка при update add Item, как и задумано'); + render(done) + }) + }); + + syncQUnit.addTest('Изменение шаблона модуля', function ( assert ) + { + var done = assert.async(); + + var itemId = /template\/Module\/([0-9]+)/.exec(window.location.href)[1] + + jsonEditor.jsonEditorRmVar("new-vault-password-file2") + $("#module-autocomplete").val("test2-playbook-"+t); + + $("#new_json_nameprefix").val("new-vault-password-file"); + $("#new_json_valueprefix").val("syntax-check"); + jsonEditor.jsonEditorAddVar(); + + + $.when(pmModuleTemplates.updateItem(itemId)).done(function() + { + assert.ok(true, 'Успешно update add Item'); + render(done) + }).fail(function(){ + debugger; + assert.ok(false, 'Ошибка при update add Item'); + render(done) + }) + }); + + var itemId = undefined + syncQUnit.addTest('Копирование template Module', function ( assert ) + { + itemId = /template\/Module\/([0-9]+)/.exec(window.location.href)[1] + + var done = assert.async(); + $.when(pmModuleTemplates.copyAndEdit(itemId)).done(function() + { + assert.ok(true, 'Успешно copyAndEdit add Item'); + render(done) + }).fail(function(){ + debugger; + assert.ok(false, 'Ошибка при copyAndEdit add Item'); + render(done) + }) + }); +/* + syncQUnit.addTest('Выполнение template Module', function ( assert ) + { + var itemId = /template\/Module\/([0-9]+)/.exec(window.location.href)[1] + debugger; + var done = assert.async(); + $.when(pmModuleTemplates.execute(itemId)).done(function() + { + assert.ok(true, 'Успешно pmModuleTemplates.execute'); + render(done) + }).fail(function(){ + assert.ok(false, 'Ошибка при pmModuleTemplates.execute'); + render(done) + }) + });*/ + + syncQUnit.addTest('pmTemplates.exportToFile', function ( assert ) + { + var done = assert.async(); + + $.when(pmTemplates.exportToFile([itemId])).done(function() + { + assert.ok(true, 'pmTemplates.exportToFile ok'); + render(done) + }).fail(function() + { + debugger; + assert.ok(false, 'pmTemplates.exportToFile error'); + render(done) + }) + }); + syncQUnit.addTest('Удаление копии template Module', function ( assert ) { var done = assert.async(); @@ -2835,6 +3256,29 @@ window.qunitTestsArray.push({ }) }); + syncQUnit.addTest('Удаление шаблона Module', function ( assert ) + { + var done = assert.async(); + + $.when(spajs.open({ menuId:"template/Module/"+itemId})).done(function() + { + $.when(pmModuleTemplates.saveAndExecute(itemId)).done(function() + { + assert.ok(true, 'Успешно pmModuleTemplates.saveAndExecute'); + render(done) + }).fail(function(){ + debugger; + assert.ok(false, 'Ошибка при pmModuleTemplates.saveAndExecute'); + render(done) + }) + }).fail(function() + { + debugger; + assert.ok(false, 'Ошибка при открытиии меню template/Module/'+itemId); + render(done) + }) + }); + syncQUnit.addTest('Удаление шаблона', function ( assert ) { var done = assert.async(); @@ -2850,6 +3294,210 @@ window.qunitTestsArray.push({ render(done) }) }); + + syncQUnit.addTest('Список шаблонов', function ( assert ) + { + var done = assert.async(); + + $.when(spajs.open({ menuId:"templates"})).done(function() + { + assert.ok(true, 'Успешно открыто меню templates'); + render(done) + }).fail(function() + { + debugger; + assert.ok(false, 'Ошибка при открытиии меню templates'); + render(done) + }) + }); + + syncQUnit.addTest('Новый template/new-module', function ( assert ) + { + var done = assert.async(); + + // Открытие пункта меню new-project + $.when(spajs.open({ menuId:"template/new-module"})).done(function() + { + assert.ok(true, 'Успешно открыто меню new-project'); + render(done) + }).fail(function() + { + debugger; + assert.ok(false, 'Ошибка при открытиии меню new-project'); + render(done) + }) + }); + + var t = new Date(); + t = t.getTime() + + syncQUnit.addTest('Сохранение не валидного шаблона модуля', function ( assert ) + { + // Предполагается что мы от прошлого теста попали на страницу создания project + var done = assert.async(); + + // Заполнение формы с данными project + $("#Templates-name").val("test-template-"+t); + + jsonEditor.__devAddVar("new-vault-password-file2", "syntax-check") + + // Отправка формы с данными project + $.when(pmModuleTemplates.addItem()).done(function() + { + debugger; + assert.ok(false, 'Успешно template add Item, а не должно было'); + render(done) + }).fail(function() + { + assert.ok(true, 'Ошибка при template add Item, как и задумано'); + render(done) + }) + }); + + syncQUnit.addTest('Сохранение шаблона модуля', function ( assert ) + { + // Предполагается что мы от прошлого теста попали на страницу создания project + var done = assert.async(); + + jsonEditor.jsonEditorRmVar("new-vault-password-file2") + $("#new_json_nameprefix").val("new-vault-password-file"); + $("#new_json_valueprefix").val("syntax-check"); + jsonEditor.jsonEditorAddVar(); + + $("#inventories-autocomplete").val($("#inventories-autocomplete option")[0].value).trigger('change.select2'); + + + // Заполнение формы с данными project + $("#Templates-name").val("test-template-"+t); + + // Отправка формы с данными project + $.when(pmModuleTemplates.addItem()).done(function() + { + assert.ok(true, 'Успешно template add Item'); + render(done) + }).fail(function() + { + debugger; + assert.ok(false, 'Ошибка при template add Item'); + render(done) + }) + }); + + syncQUnit.addTest('Открытие страницы создания новой опции шаблона модуля', function ( assert ) + { + // Предполагается что мы от прошлого теста попали на страницу создания project + var done = assert.async(); + var itemId = /template\/Module\/([0-9]+)/.exec(window.location.href)[1]; + + $.when(spajs.open({ menuId:"template/Module/"+itemId+"/new-option"})).done(function() + { + assert.ok(true, 'Успешно открыто меню templates/Module/item_id/new-option'); + render(done) + }).fail(function() + { + debugger; + assert.ok(false, 'Ошибка при открытиии меню templates/Module/item_id/new-option'); + render(done) + }) + }); + + syncQUnit.addTest('Сохранение новой невалидной опции шаблона модуля', function ( assert ) + { + // Предполагается что мы от прошлого теста попали на страницу создания project + var done = assert.async(); + var itemId = /template\/Module\/([0-9]+)\/new-option/.exec(window.location.href)[1]; + + $("#filed_option_name").val("test-option"); + + $.when(pmModuleTemplates.saveOption(itemId)).done(function() + { + debugger; + assert.ok(false, 'Успешно сохранено, а не должно было'); + render(done) + }).fail(function(){ + assert.ok(true, 'Ошибка при сохранении, как и задумано'); + render(done) + }) + + + }); + + syncQUnit.addTest('Сохранение новой валидной опции шаблона модуля', function ( assert ) + { + // Предполагается что мы от прошлого теста попали на страницу создания project + var done = assert.async(); + var itemId = /template\/Module\/([0-9]+)\/new-option/.exec(window.location.href)[1]; + + jsonEditor.jsonEditorRmVar('new-vault-password-file', 'prefix'); + jsonEditor.jsonEditorRmVar("become2") + $("#new_json_nameprefix").val("become"); + jsonEditor.jsonEditorAddVar(); + + $.when(pmModuleTemplates.saveOption(itemId)).done(function() + { + assert.ok(true, 'Опция успешно сохранена'); + render(done) + }).fail(function(){ + debugger; + assert.ok(false, 'Ошибка при сохранении опции'); + render(done) + }) + }); + + syncQUnit.addTest('Сохранение и запуск опции шаблона модуля', function ( assert ) + { + // Предполагается что мы от прошлого теста попали на страницу просмотра/редактирования опции шаблона + var done = assert.async(); + var itemId = /template\/Module\/([0-9]+)\/option\/([A-z0-9 %\-.:,=]+)/.exec(window.location.href)[1]; + + $.when(pmModuleTemplates.saveAndExecuteOption(itemId)).done(function() + { + assert.ok(true, 'Опция успешно сохранена'); + render(done) + }).fail(function(){ + debugger; + assert.ok(false, 'Ошибка при сохранении опции'); + render(done) + }) + }); + syncQUnit.addTest('Удаление шаблона Module', function ( assert ) + { + var done = assert.async(); + var itemId = /template\/Module\/([0-9]+)/.exec(window.location.href)[1] + $.when(spajs.open({ menuId:"template/Module/"+itemId})).done(function() + { + $.when(pmModuleTemplates.saveAndExecute(itemId)).done(function() + { + assert.ok(true, 'Успешно pmModuleTemplates.saveAndExecute'); + render(done) + }).fail(function(){ + debugger; + assert.ok(false, 'Ошибка при pmModuleTemplates.saveAndExecute'); + render(done) + }) + }).fail(function() + { + debugger; + assert.ok(false, 'Ошибка при открытиии меню template/Module/'+itemId); + render(done) + }) + }); + + syncQUnit.addTest('Удаление шаблона', function ( assert ) + { + var done = assert.async(); + var itemId = /template\/Module\/([0-9]+)/.exec(window.location.href)[1] + // Удаление project. + $.when(pmModuleTemplates.deleteItem(itemId, true)).done(function() + { + assert.ok(true, 'Успешно delete Item'); + render(done) + }).fail(function(){ + debugger; + assert.ok(false, 'Ошибка при delete Item'); + render(done) + }) + }); }}) /** @@ -2867,8 +3515,14 @@ window.qunitTestsArray.push({ $.when(spajs.open({ menuId:"home"})).done(function() { assert.ok(true, 'Успешно открыто меню pmDashboard'); + setTimeout(function(){// Ждём завершения всех асинхронных запросов на странице - render(done) + + tabSignal.emit('pmLocalSettings.hideMenu', {type:'set', name:'hideMenu', value:false}) + setTimeout(function() + { + render(done) + }, 500) }, 5000) }).fail(function() { @@ -2999,6 +3653,24 @@ window.qunitTestsArray.push({ render(done) }) }); + + syncQUnit.addTest('Страница ошибки 400 в history', function ( assert ) + { + var done = assert.async(); + + $.when(spajs.open({ menuId:"project/9999999999/history"})).done(function() + { + debugger; + assert.ok(false, 'Успешно открыто меню project/9999999999/history'); + render(done) + }).fail(function() + { + assert.ok(true, 'Ошибка при открытиии меню project/9999999999/history'); + render(done) + }) + }); + + }}) @@ -3025,6 +3697,84 @@ window.qunitTestsArray.push({ }) }); + + syncQUnit.addTest('Страница history toggleSelectEachItem', function ( assert ) + { + var done = assert.async(); + + pmHistory.toggleSelectAll($('.multiple-select tr'), true); + + $.when(pmHistory.toggleSelectEachItem(true)).done(function() + { + assert.ok(true, 'ok:toggleSelectEachItem'); + render(done) + }).fail(function() + { + debugger; + assert.ok(false, 'error:toggleSelectEachItem'); + render(done) + }) + }) + + syncQUnit.addTest('Страница history toggleSelectEachItem', function ( assert ) + { + var done = assert.async(); + + pmHistory.toggleSelectAll($('.multiple-select tr'), false); + + $.when(pmHistory.toggleSelectEachItem(false)).done(function() + { + $.when(pmHistory.deleteSelected()).done(function() + { + assert.ok(true, 'ok:deleteSelected'); + render(done) + }).fail(function() + { + debugger; + assert.ok(false, 'error:deleteSelected'); + render(done) + }) + + }).fail(function() + { + debugger; + assert.ok(false, 'error:toggleSelectEachItem'); + render(done) + }) + }) + + syncQUnit.addTest('Страница history deleteRows', function ( assert ) + { + var done = assert.async(); + + $.when(pmHistory.deleteRows([])).done(function() + { + assert.ok(true, 'ok:deleteRows'); + render(done) + }).fail(function() + { + debugger; + assert.ok(false, 'error:deleteRows'); + render(done) + }) + }) + + syncQUnit.addTest('Страница history multiOperationsOnEachRow.loadItem', function ( assert ) + { + var done = assert.async(); + + $.when(pmHistory.multiOperationsOnEachRow([], 'loadItem', true)).done(function() + { + assert.ok(true, 'ok:multiOperationsOnEachRow.loadItem'); + render(done) + }).fail(function() + { + debugger; + assert.ok(false, 'error:multiOperationsOnEachRow.loadItem'); + render(done) + }) + }) + syncQUnit.addTest('Страница history 2', function ( assert ) { var done = assert.async(); @@ -3061,6 +3811,7 @@ window.qunitTestsArray.push({ assert.ok(true, 'Ошибка cancelTask'); render(done) }) - }); + }); + }}) - + \ No newline at end of file diff --git a/polemarch/static/templates/filedsLib.html b/polemarch/static/templates/filedsLib.html index 8a661d50..4569dcc5 100644 --- a/polemarch/static/templates/filedsLib.html +++ b/polemarch/static/templates/filedsLib.html @@ -9,7 +9,9 @@ class="form-control" placeholder="<%- filed.placeholder %>" id="filed_<%- filed.name %>" - <% if(item_id){ %> + <% if(filed.value !== undefined){ %> + value="<%- filed.value %>" + <% } else if(item_id && pmObj.model.items[item_id][filed.name] !== undefined){ %> value="<%- pmObj.model.items[item_id][filed.name] %>" <% } %> > diff --git a/polemarch/static/templates/jsonEditor.html b/polemarch/static/templates/jsonEditor.html index b3af2182..857a2054 100644 --- a/polemarch/static/templates/jsonEditor.html +++ b/polemarch/static/templates/jsonEditor.html @@ -1,265 +1,274 @@ - - - - - - + + + \ No newline at end of file diff --git a/polemarch/static/templates/pmAnsibleModule.html b/polemarch/static/templates/pmAnsibleModule.html index a9fd777b..3fd6ddc9 100644 --- a/polemarch/static/templates/pmAnsibleModule.html +++ b/polemarch/static/templates/pmAnsibleModule.html @@ -69,9 +69,31 @@

    + + + + + + \ No newline at end of file diff --git a/polemarch/static/templates/pmHistory.html b/polemarch/static/templates/pmHistory.html index ce8c78e7..659fe781 100644 --- a/polemarch/static/templates/pmHistory.html +++ b/polemarch/static/templates/pmHistory.html @@ -1,641 +1,675 @@ - - - - - - - - - - - - - - - - - - + + + + + + + + + + + + + + + + + + diff --git a/polemarch/static/templates/pmItems.html b/polemarch/static/templates/pmItems.html index 32f5077f..1763f8e7 100644 --- a/polemarch/static/templates/pmItems.html +++ b/polemarch/static/templates/pmItems.html @@ -171,6 +171,22 @@

    <% } %> <% } %> + + <% if(pmObj.model.itemslist.results[i].options_list) { %> + <% if(pmObj.model.itemslist.results[i].options_list.length!=0) { %> + + <% for(var k in pmObj.model.itemslist.results[i].options_list) { %> +
  • + + Execute with <%= pmObj.model.itemslist.results[i].options_list[k] %> + +
  • + <% } %> + <% } %> + <% } %> + + diff --git a/polemarch/static/templates/pmModuleTemplates.html b/polemarch/static/templates/pmModuleTemplates.html index 7175045f..6c388a77 100644 --- a/polemarch/static/templates/pmModuleTemplates.html +++ b/polemarch/static/templates/pmModuleTemplates.html @@ -1,4 +1,4 @@ - + - + - \ No newline at end of file + + + + + + + + + + + + + + + + + + + \ No newline at end of file diff --git a/polemarch/static/templates/pmProjects.html b/polemarch/static/templates/pmProjects.html index 77e03d0f..f401e3d0 100644 --- a/polemarch/static/templates/pmProjects.html +++ b/polemarch/static/templates/pmProjects.html @@ -1,4 +1,4 @@ - + @@ -101,7 +127,7 @@ Import templates - + - \ No newline at end of file + + + \ No newline at end of file diff --git a/polemarch/static/templates/pmTasksTemplates.html b/polemarch/static/templates/pmTasksTemplates.html index 1a89f250..cbec322e 100644 --- a/polemarch/static/templates/pmTasksTemplates.html +++ b/polemarch/static/templates/pmTasksTemplates.html @@ -2,9 +2,48 @@ + + + + + + + + +