diff --git a/.gitattributes b/.gitattributes new file mode 100644 index 0000000..5713ac7 --- /dev/null +++ b/.gitattributes @@ -0,0 +1 @@ +*.img filter=lfs diff=lfs merge=lfs -text diff --git a/.gitignore b/.gitignore new file mode 100644 index 0000000..e69de29 diff --git a/.idea/dataSources.ids b/.idea/dataSources.ids new file mode 100644 index 0000000..58cd68d --- /dev/null +++ b/.idea/dataSources.ids @@ -0,0 +1,6 @@ + + + + + + \ No newline at end of file diff --git a/.idea/dataSources.local.xml b/.idea/dataSources.local.xml new file mode 100644 index 0000000..2515303 --- /dev/null +++ b/.idea/dataSources.local.xml @@ -0,0 +1,10 @@ + + + + + memory + true + magpaqnujuklfc + + + \ No newline at end of file diff --git a/.idea/dataSources.xml b/.idea/dataSources.xml new file mode 100644 index 0000000..b5a82e1 --- /dev/null +++ b/.idea/dataSources.xml @@ -0,0 +1,11 @@ + + + + + postgresql + true + org.postgresql.Driver + jdbc:postgresql://ec2-54-83-18-87.compute-1.amazonaws.com:5432/dcv14dl16ev288 + + + \ No newline at end of file diff --git a/.idea/dictionaries/abdul.xml b/.idea/dictionaries/abdul.xml new file mode 100644 index 0000000..2c3ab07 --- /dev/null +++ b/.idea/dictionaries/abdul.xml @@ -0,0 +1,9 @@ + + + + openstack + retreive + retriveing + + + \ No newline at end of file diff --git a/.idea/inspectionProfiles/Project_Default.xml b/.idea/inspectionProfiles/Project_Default.xml new file mode 100644 index 0000000..71d7772 --- /dev/null +++ b/.idea/inspectionProfiles/Project_Default.xml @@ -0,0 +1,14 @@ + + + + \ No newline at end of file diff --git a/.idea/inspectionProfiles/profiles_settings.xml b/.idea/inspectionProfiles/profiles_settings.xml new file mode 100644 index 0000000..3b31283 --- /dev/null +++ b/.idea/inspectionProfiles/profiles_settings.xml @@ -0,0 +1,7 @@ + + + + \ No newline at end of file diff --git a/.idea/misc.xml b/.idea/misc.xml index cd9d35b..44c30f0 100644 --- a/.idea/misc.xml +++ b/.idea/misc.xml @@ -11,4 +11,7 @@ + + \ No newline at end of file diff --git a/.idea/workspace.xml b/.idea/workspace.xml index 8a29066..2bc1b0a 100644 --- a/.idea/workspace.xml +++ b/.idea/workspace.xml @@ -2,12 +2,20 @@ - + + + + + + - - - - + + + + + + + @@ -24,6 +32,19 @@ + + @@ -33,62 +54,44 @@ - - - - - - - - - - - - - - - - - - - - - - + + - - + + - - + + - - + + - - + + - - + + - + - - - + + + + + @@ -98,10 +101,10 @@ @@ -111,56 +114,57 @@ @@ -170,10 +174,10 @@ true - @@ -200,8 +204,8 @@ - + @@ -209,6 +213,10 @@ @@ -216,7 +224,7 @@ - @@ -226,7 +234,7 @@ @@ -505,12 +559,16 @@ + + + + + - @@ -606,7 +664,21 @@ - + + @@ -766,6 +841,7 @@ 1455703657914 - - + + - - - - - - - - - + + + + + + + + + - - - - + + + + - + - - - - - - - - - - - - + + + + + + + + + + + + @@ -843,394 +919,399 @@ file://$PROJECT_DIR$/ntt_tool/static/js/apps/controllers/cloudcontroller.js - 25 + 47 - - + - + - + - + - - - - + - - + + - + - - + + - + - - + + - + - - + + - + - - + + - + - - + + - + - - + + - + - - + + - + - - + + - + - - + + - + - - + + - + + - - + + - + - - + + - + - - + + - + - - + + - + - - + + - + - - + + - + - - + + - + + - - + + - + - - + + - + - - + + - + - - + + - + - - + + - + - - + + - + - - + + - + - - + + - - - - - - - - + - - - + + - + - - - + + - + - - + + - + - - - + + - + - - + + - + - - - + + - + - - - + + - + - - + + - + - - + + - + - - + + - + - - - + + - + - - - + + - - - - - - - - - - + + - + - - - + + - + - - - + + + + + + + + + + + + + + + + + + + + + + + + + + + + - + - - + + - + - - - + + + + + - + - - + + - + - - + + - + - - + + - + - - - + + + + + diff --git a/Makefile b/Makefile index a1c508a..72f168f 100644 --- a/Makefile +++ b/Makefile @@ -4,6 +4,12 @@ install: run: python manage.py runserver 0.0.0.0:8000 +makemigrations: + python manage.py makemigrations + +migrate: + python manage.py migrate + clear-cache: python manage.py clear_cache diff --git a/config.ini b/config.ini new file mode 100644 index 0000000..c2c6b89 --- /dev/null +++ b/config.ini @@ -0,0 +1,6 @@ +[DEFAULT] +keystone_auth_url = http://openstack.org/keystone/auth/url +keystone_user = admin +keystone_password = admin123 +keystone_tenant_name = Test + diff --git a/ntt_tool/.gitignore b/ntt_tool/.gitignore new file mode 100644 index 0000000..e7c5e4b --- /dev/null +++ b/ntt_tool/.gitignore @@ -0,0 +1,132 @@ +# Created by .ignore support plugin (hsz.mobi) +### SublimeText template +# cache files for sublime text +*.tmlanguage.cache +*.tmPreferences.cache +*.stTheme.cache + +# workspace files are user-specific +*.sublime-workspace + +# project files should be checked into the repository, unless a significant +# proportion of contributors will probably not be using SublimeText +# *.sublime-project + +# sftp configuration file +sftp-config.json +### Python template +# Byte-compiled / optimized / DLL files +__pycache__/ +*.py[cod] +*$py.class + +# C extensions +*.so + +# Distribution / packaging +.Python +env/ +build/ +develop-eggs/ +dist/ +downloads/ +eggs/ +.eggs/ +lib/ +lib64/ +parts/ +sdist/ +var/ +*.egg-info/ +.installed.cfg +*.egg + +# 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 + +# Translations +*.mo +*.pot + +# Django stuff: +*.log + +# Sphinx documentation +docs/_build/ + +# PyBuilder +target/ +### VirtualEnv template +# Virtualenv +# http://iamzed.com/2009/05/07/a-primer-on-virtualenv/ +.Python +[Bb]in +[Ii]nclude +[Ll]ib +[Ss]cripts +pyvenv.cfg +pip-selfcheck.json +### JetBrains template +# Covers JetBrains IDEs: IntelliJ, RubyMine, PhpStorm, AppCode, PyCharm, CLion, Android Studio + +*.iml + +## Directory-based project format: +.idea/ +# if you remove the above rule, at least ignore the following: + +# User-specific stuff: +# .idea/workspace.xml +# .idea/tasks.xml +# .idea/dictionaries + +# Sensitive or high-churn files: +# .idea/dataSources.ids +# .idea/dataSources.xml +# .idea/sqlDataSources.xml +# .idea/dynamic.xml +# .idea/uiDesigner.xml + +# Gradle: +# .idea/gradle.xml +# .idea/libraries + +# Mongo Explorer plugin: +# .idea/mongoSettings.xml + +## File-based project format: +*.ipr +*.iws + +## Plugin-specific files: + +# IntelliJ +/out/ + +# mpeltonen/sbt-idea plugin +.idea_modules/ + +# JIRA plugin +atlassian-ide-plugin.xml + +# Crashlytics plugin (for Android Studio and IntelliJ) +com_crashlytics_export_strings.xml +crashlytics.properties +crashlytics-build.properties diff --git a/ntt_tool/apps/cloud/apps.py b/ntt_tool/apps/cloud/apps.py index f9da1d3..6f2b436 100644 --- a/ntt_tool/apps/cloud/apps.py +++ b/ntt_tool/apps/cloud/apps.py @@ -5,3 +5,4 @@ class CloudConfig(AppConfig): name = 'cloudtraffic' + diff --git a/ntt_tool/apps/cloud/migrations/0001_initial.py b/ntt_tool/apps/cloud/migrations/0001_initial.py index aebd03c..be2718c 100644 --- a/ntt_tool/apps/cloud/migrations/0001_initial.py +++ b/ntt_tool/apps/cloud/migrations/0001_initial.py @@ -1,5 +1,5 @@ # -*- coding: utf-8 -*- -# Generated by Django 1.9.2 on 2016-02-25 15:52 +# Generated by Django 1.9.2 on 2016-04-15 06:57 from __future__ import unicode_literals from django.conf import settings @@ -27,13 +27,96 @@ class Migration(migrations.Migration): ('keystone_tenant_name', models.CharField(max_length=100)), ('created_on', models.DateTimeField(auto_now=True)), ('updated_on', models.DateTimeField(auto_now_add=True)), + ('creator', models.ForeignKey(blank=True, null=True, on_delete=django.db.models.deletion.CASCADE, to=settings.AUTH_USER_MODEL)), ], options={ 'db_table': 'cloud_details', }, ), migrations.CreateModel( - name='CloudTraffic', + name='Endpoint', + fields=[ + ('id', models.AutoField(auto_created=True, primary_key=True, serialize=False, verbose_name='ID')), + ('endpoint_id', models.CharField(max_length=100)), + ('name', models.CharField(max_length=100)), + ('ip_address', models.CharField(max_length=30)), + ('status', models.CharField(max_length=30)), + ('is_selected', models.BooleanField(default=False)), + ('is_dirty', models.BooleanField(default=False)), + ], + options={ + 'db_table': 'cloud_traffic_endpoints', + }, + ), + migrations.CreateModel( + name='Network', + fields=[ + ('id', models.AutoField(auto_created=True, primary_key=True, serialize=False, verbose_name='ID')), + ('network_id', models.CharField(max_length=255)), + ('network_name', models.CharField(max_length=255)), + ('shared', models.BooleanField(default=False)), + ('status', models.CharField(max_length=25)), + ('is_dirty', models.BooleanField(default=False)), + ('created_on', models.DateTimeField(auto_now=True)), + ('updated_on', models.DateTimeField(auto_now_add=True)), + ('creator', models.ForeignKey(blank=True, null=True, on_delete=django.db.models.deletion.CASCADE, to=settings.AUTH_USER_MODEL)), + ], + options={ + 'db_table': 'cloud_tenant_networks', + }, + ), + migrations.CreateModel( + name='Router', + fields=[ + ('id', models.AutoField(auto_created=True, primary_key=True, serialize=False, verbose_name='ID')), + ('router_id', models.CharField(max_length=255)), + ('router_name', models.CharField(max_length=255)), + ('status', models.CharField(max_length=25)), + ('is_dirty', models.BooleanField(default=False)), + ('created_on', models.DateTimeField(auto_now=True)), + ('updated_on', models.DateTimeField(auto_now_add=True)), + ('creator', models.ForeignKey(blank=True, null=True, on_delete=django.db.models.deletion.CASCADE, to=settings.AUTH_USER_MODEL)), + ], + options={ + 'db_table': 'cloud_tenant_routers', + }, + ), + migrations.CreateModel( + name='Subnet', + fields=[ + ('id', models.AutoField(auto_created=True, primary_key=True, serialize=False, verbose_name='ID')), + ('subnet_id', models.CharField(max_length=255)), + ('subnet_name', models.CharField(max_length=255)), + ('cidr', models.CharField(max_length=255)), + ('allocation_pool_start', models.GenericIPAddressField(blank=True, null=True)), + ('allocation_pool_end', models.GenericIPAddressField(blank=True, null=True)), + ('is_dirty', models.BooleanField(default=False)), + ('network', models.ForeignKey(on_delete=django.db.models.deletion.CASCADE, related_name='subnets', to='cloud.Network')), + ], + options={ + 'db_table': 'cloud_tenant_network_subnets', + }, + ), + migrations.CreateModel( + name='Tenant', + fields=[ + ('id', models.AutoField(auto_created=True, primary_key=True, serialize=False, verbose_name='ID')), + ('tenant_id', models.CharField(max_length=100)), + ('tenant_name', models.CharField(max_length=256)), + ('description', models.CharField(blank=True, max_length=256, null=True)), + ('enabled', models.BooleanField(default=False)), + ('is_dirty', models.BooleanField(default=False)), + ('created_on', models.DateTimeField(auto_now=True)), + ('updated_on', models.DateTimeField(auto_now_add=True)), + ('cloud', models.ForeignKey(blank=True, null=True, on_delete=django.db.models.deletion.CASCADE, related_name='tenants', to='cloud.Cloud')), + ('creator', models.ForeignKey(blank=True, null=True, on_delete=django.db.models.deletion.CASCADE, to=settings.AUTH_USER_MODEL)), + ], + options={ + 'db_table': 'cloud_tenants', + }, + ), + migrations.CreateModel( + name='Traffic', fields=[ ('id', models.AutoField(auto_created=True, primary_key=True, serialize=False, verbose_name='ID')), ('name', models.CharField(max_length=256)), @@ -44,29 +127,61 @@ class Migration(migrations.Migration): ('remote_pass', models.CharField(max_length=100)), ('test_method', models.CharField(max_length=100)), ('iperf_duration', models.IntegerField()), - ('tenant_type', models.CharField(choices=[('all', 'All'), ('intra-tenant', 'Intra Tenant'), ('inter-tenant', 'Inter Tenant'), ('south-north', 'South to North'), ('north-south', 'North to South')], default='all', max_length=20)), - ('step', models.IntegerField(default=1)), + ('test_type', models.CharField(choices=[('all', 'All'), ('intra-tenant', 'Intra Tenant'), ('inter-tenant', 'Inter Tenant'), ('south-north', 'South to North'), ('north-south', 'North to South')], default='all', max_length=20)), + ('external_host', models.CharField(blank=True, max_length=100, null=True)), + ('ssh_gateway', models.CharField(blank=True, max_length=100, null=True)), + ('test_environment', models.CharField(choices=[('dev', 'Development/Test'), ('prod', 'Production')], default='dev', max_length=20)), ('created_on', models.DateTimeField(auto_now=True)), ('updated_on', models.DateTimeField(auto_now_add=True)), - ('cloud', models.ForeignKey(on_delete=django.db.models.deletion.CASCADE, to='cloud.Cloud')), + ('cloud', models.ForeignKey(blank=True, null=True, on_delete=django.db.models.deletion.CASCADE, related_name='cloud_traffic', to='cloud.Cloud')), + ('creator', models.ForeignKey(blank=True, null=True, on_delete=django.db.models.deletion.CASCADE, to=settings.AUTH_USER_MODEL)), ], options={ 'db_table': 'cloud_traffic', }, ), migrations.CreateModel( - name='CloudTrafficTenants', + name='TrafficNetworksMap', fields=[ ('id', models.AutoField(auto_created=True, primary_key=True, serialize=False, verbose_name='ID')), - ('tenant_name', models.CharField(max_length=256)), - ('tenant_value', models.CharField(blank=True, max_length=256, null=True)), - ('created_on', models.DateTimeField(auto_now=True)), - ('updated_on', models.DateTimeField(auto_now_add=True)), - ('cloud_traffic', models.ForeignKey(on_delete=django.db.models.deletion.CASCADE, to='cloud.CloudTraffic')), - ('creator', models.ForeignKey(on_delete=django.db.models.deletion.CASCADE, to=settings.AUTH_USER_MODEL)), + ('ip_range_start', models.TextField(blank=True, max_length=15, null=True)), + ('ip_range_end', models.TextField(blank=True, max_length=15, null=True)), + ('endpoint_count', models.IntegerField(blank=True, null=True)), + ('network', models.ForeignKey(on_delete=django.db.models.deletion.CASCADE, to='cloud.Network')), + ('traffic', models.ForeignKey(on_delete=django.db.models.deletion.CASCADE, to='cloud.Traffic')), ], options={ - 'db_table': 'cloud_traffic_tenants', + 'db_table': 'cloud_traffic_networks_map', }, ), + migrations.AddField( + model_name='traffic', + name='selected_networks', + field=models.ManyToManyField(blank=True, related_name='selected_networks', through='cloud.TrafficNetworksMap', to='cloud.Network'), + ), + migrations.AddField( + model_name='traffic', + name='tenants', + field=models.ManyToManyField(blank=True, to='cloud.Tenant'), + ), + migrations.AddField( + model_name='router', + name='tenant', + field=models.ForeignKey(on_delete=django.db.models.deletion.CASCADE, related_name='routers', to='cloud.Tenant'), + ), + migrations.AddField( + model_name='network', + name='tenant', + field=models.ForeignKey(on_delete=django.db.models.deletion.CASCADE, related_name='networks', to='cloud.Tenant'), + ), + migrations.AddField( + model_name='endpoint', + name='network', + field=models.ForeignKey(on_delete=django.db.models.deletion.CASCADE, to='cloud.Network'), + ), + migrations.AddField( + model_name='endpoint', + name='traffic', + field=models.ForeignKey(on_delete=django.db.models.deletion.CASCADE, to='cloud.Traffic'), + ), ] diff --git a/ntt_tool/apps/cloud/migrations/0002_auto_20160225_1631.py b/ntt_tool/apps/cloud/migrations/0002_auto_20160225_1631.py deleted file mode 100644 index b3fd221..0000000 --- a/ntt_tool/apps/cloud/migrations/0002_auto_20160225_1631.py +++ /dev/null @@ -1,20 +0,0 @@ -# -*- coding: utf-8 -*- -# Generated by Django 1.9.2 on 2016-02-25 16:31 -from __future__ import unicode_literals - -from django.db import migrations - - -class Migration(migrations.Migration): - - dependencies = [ - ('cloud', '0001_initial'), - ] - - operations = [ - migrations.RenameField( - model_name='cloudtraffictenants', - old_name='tenant_value', - new_name='ssh_gateway', - ), - ] diff --git a/ntt_tool/apps/cloud/migrations/0002_traffictest.py b/ntt_tool/apps/cloud/migrations/0002_traffictest.py new file mode 100644 index 0000000..e1b6887 --- /dev/null +++ b/ntt_tool/apps/cloud/migrations/0002_traffictest.py @@ -0,0 +1,24 @@ +# -*- coding: utf-8 -*- +# Generated by Django 1.9.2 on 2016-04-18 08:26 +from __future__ import unicode_literals + +from django.db import migrations, models +import django.db.models.deletion + + +class Migration(migrations.Migration): + + dependencies = [ + ('cloud', '0001_initial'), + ] + + operations = [ + migrations.CreateModel( + name='TrafficTest', + fields=[ + ('id', models.AutoField(auto_created=True, primary_key=True, serialize=False, verbose_name='ID')), + ('started_on', models.DateTimeField()), + ('traffic', models.ForeignKey(on_delete=django.db.models.deletion.CASCADE, to='cloud.Traffic')), + ], + ), + ] diff --git a/ntt_tool/apps/cloud/migrations/0003_auto_20160225_1726.py b/ntt_tool/apps/cloud/migrations/0003_auto_20160225_1726.py deleted file mode 100644 index ae3334b..0000000 --- a/ntt_tool/apps/cloud/migrations/0003_auto_20160225_1726.py +++ /dev/null @@ -1,27 +0,0 @@ -# -*- coding: utf-8 -*- -# Generated by Django 1.9.2 on 2016-02-25 17:26 -from __future__ import unicode_literals - -from django.conf import settings -from django.db import migrations, models -import django.db.models.deletion - - -class Migration(migrations.Migration): - - dependencies = [ - ('cloud', '0002_auto_20160225_1631'), - ] - - operations = [ - migrations.AlterField( - model_name='cloudtraffictenants', - name='cloud_traffic', - field=models.ForeignKey(on_delete=django.db.models.deletion.CASCADE, related_name='cloud_traffic_tenants', to='cloud.CloudTraffic'), - ), - migrations.AlterField( - model_name='cloudtraffictenants', - name='creator', - field=models.ForeignKey(on_delete=django.db.models.deletion.CASCADE, related_name='cloud_traffic_tenants_creator', to=settings.AUTH_USER_MODEL), - ), - ] diff --git a/ntt_tool/apps/cloud/migrations/0004_auto_20160226_1322.py b/ntt_tool/apps/cloud/migrations/0004_auto_20160226_1322.py deleted file mode 100644 index c09b75b..0000000 --- a/ntt_tool/apps/cloud/migrations/0004_auto_20160226_1322.py +++ /dev/null @@ -1,24 +0,0 @@ -# -*- coding: utf-8 -*- -# Generated by Django 1.9.2 on 2016-02-26 13:22 -from __future__ import unicode_literals - -from django.db import migrations, models - - -class Migration(migrations.Migration): - - dependencies = [ - ('cloud', '0003_auto_20160225_1726'), - ] - - operations = [ - migrations.RemoveField( - model_name='cloudtraffic', - name='step', - ), - migrations.AddField( - model_name='cloudtraffic', - name='external_host', - field=models.CharField(blank=True, max_length=100, null=True), - ), - ] diff --git a/ntt_tool/apps/cloud/models.py b/ntt_tool/apps/cloud/models.py index 5835eb2..0ffe980 100644 --- a/ntt_tool/apps/cloud/models.py +++ b/ntt_tool/apps/cloud/models.py @@ -1,6 +1,7 @@ from __future__ import unicode_literals from django.contrib.auth.models import User from django.db import models +from django.utils import timezone class Cloud(models.Model): @@ -9,7 +10,7 @@ class Cloud(models.Model): keystone_user = models.CharField(max_length=100) keystone_password = models.CharField(max_length=250) keystone_tenant_name = models.CharField(max_length=100) - # creator = models.ForeignKey(User) #ToDo: Facing some issues in serializer validation + creator = models.ForeignKey(User, blank=True, null=True) created_on = models.DateTimeField(auto_now=True) updated_on = models.DateTimeField(auto_now_add=True) @@ -17,7 +18,67 @@ class Meta: db_table = "cloud_details" -class CloudTraffic(models.Model): +class Tenant(models.Model): + cloud = models.ForeignKey(Cloud, blank=True, null=True, related_name="tenants") + tenant_id = models.CharField(max_length=100) + tenant_name = models.CharField(max_length=256) + description = models.CharField(max_length=256, blank=True, null=True) + enabled = models.BooleanField(default=False) + is_dirty = models.BooleanField(default=False) + creator = models.ForeignKey(User, blank=True, null=True) + created_on = models.DateTimeField(auto_now=True) + updated_on = models.DateTimeField(auto_now_add=True) + + class Meta: + db_table = "cloud_tenants" + + +class Network(models.Model): + tenant = models.ForeignKey(Tenant, related_name="networks") + network_id = models.CharField(max_length=255) + network_name = models.CharField(max_length=255) + shared = models.BooleanField(default=False) + status = models.CharField(max_length=25) + is_dirty = models.BooleanField(default=False) + creator = models.ForeignKey(User, blank=True, null=True) + created_on = models.DateTimeField(auto_now=True) + updated_on = models.DateTimeField(auto_now_add=True) + + def __unicode__(self): + return "%s | %s | Tenant:%s" % (self.id, self.network_name, self.tenant.tenant_name) + + class Meta: + db_table = "cloud_tenant_networks" + + +class Subnet(models.Model): + network = models.ForeignKey(Network, related_name="subnets") + subnet_id = models.CharField(max_length=255) + subnet_name = models.CharField(max_length=255) + cidr = models.CharField(max_length=255) + allocation_pool_start = models.GenericIPAddressField(blank=True, null=True) + allocation_pool_end = models.GenericIPAddressField(blank=True, null=True) + is_dirty = models.BooleanField(default=False) + + class Meta: + db_table = "cloud_tenant_network_subnets" + + +class Router(models.Model): + tenant = models.ForeignKey(Tenant, related_name="routers") + router_id = models.CharField(max_length=255) + router_name = models.CharField(max_length=255) + status = models.CharField(max_length=25) + is_dirty = models.BooleanField(default=False) + creator = models.ForeignKey(User, blank=True, null=True) + created_on = models.DateTimeField(auto_now=True) + updated_on = models.DateTimeField(auto_now_add=True) + + class Meta: + db_table = "cloud_tenant_routers" + + +class Traffic(models.Model): TYPE_CHOICES = ( ('all', 'All'), ('intra-tenant', 'Intra Tenant'), @@ -26,8 +87,13 @@ class CloudTraffic(models.Model): ('north-south', 'North to South'), ) + TEST_ENVIRONMENT_CHOICES = ( + ('dev', 'Development/Test'), + ('prod', 'Production'), + ) + + cloud = models.ForeignKey(Cloud, blank=True, null=True, related_name="cloud_traffic") name = models.CharField(max_length=256) - cloud = models.ForeignKey(Cloud) allowed_delta_percentage = models.FloatField() test_result_path = models.CharField(max_length=250) number_of_workers = models.IntegerField() @@ -35,23 +101,48 @@ class CloudTraffic(models.Model): remote_pass = models.CharField(max_length=100) test_method = models.CharField(max_length=100) iperf_duration = models.IntegerField() - tenant_type = models.CharField(max_length=20, choices=TYPE_CHOICES, default='all') + test_type = models.CharField(max_length=20, choices=TYPE_CHOICES, default='all') + tenants = models.ManyToManyField(Tenant, blank=True) + selected_networks = models.ManyToManyField(Network, blank=True, through='TrafficNetworksMap', related_name="selected_networks") external_host = models.CharField(max_length=100, blank=True, null=True) - # creator = models.ForeignKey(User) + ssh_gateway = models.CharField(max_length=100, blank=True, null=True) + test_environment = models.CharField(max_length=20, choices=TEST_ENVIRONMENT_CHOICES, default='dev') + creator = models.ForeignKey(User, blank=True, null=True) created_on = models.DateTimeField(auto_now=True) updated_on = models.DateTimeField(auto_now_add=True) + def __unicode__(self): + return "%s | %s | Cloud:%s" % (self.id, self.name, self.cloud.name) + class Meta: db_table = "cloud_traffic" -class CloudTrafficTenants(models.Model): - cloud_traffic = models.ForeignKey(CloudTraffic, related_name="cloud_traffic_tenants") - tenant_name = models.CharField(max_length=256) - ssh_gateway = models.CharField(max_length=256, blank=True, null=True) - creator = models.ForeignKey(User, related_name="cloud_traffic_tenants_creator") - created_on = models.DateTimeField(auto_now=True) - updated_on = models.DateTimeField(auto_now_add=True) +class TrafficNetworksMap(models.Model): + traffic = models.ForeignKey(Traffic, on_delete=models.CASCADE) + network = models.ForeignKey(Network, on_delete=models.CASCADE) + ip_range_start = models.TextField(max_length=15, blank=True, null=True) + ip_range_end = models.TextField(max_length=15, blank=True, null=True) + endpoint_count = models.IntegerField(blank=True, null=True) class Meta: - db_table = "cloud_traffic_tenants" + db_table = "cloud_traffic_networks_map" + + +class Endpoint(models.Model): + traffic = models.ForeignKey(Traffic) + network = models.ForeignKey(Network) + endpoint_id = models.CharField(max_length=100) + name = models.CharField(max_length=100) + ip_address = models.CharField(max_length=30) + status = models.CharField(max_length=30) + is_selected = models.BooleanField(default=False) + is_dirty = models.BooleanField(default=False) + + class Meta: + db_table = "cloud_traffic_endpoints" + + +class TrafficTest(models.Model): + traffic = models.ForeignKey(Traffic) + started_on = models.DateTimeField() \ No newline at end of file diff --git a/ntt_tool/apps/cloud/openstackscripts/__init__.py b/ntt_tool/apps/cloud/openstackscripts/__init__.py new file mode 100644 index 0000000..e69de29 diff --git a/ntt_tool/apps/cloud/openstackscripts/config.py b/ntt_tool/apps/cloud/openstackscripts/config.py new file mode 100644 index 0000000..2ad1591 --- /dev/null +++ b/ntt_tool/apps/cloud/openstackscripts/config.py @@ -0,0 +1,29 @@ +import os +from django.conf import settings + +# NOVA CONFIGS +NOVA_FLOATING_IP_CREATION = True +NOVA_FLOATING_IP_POOL = 'public' + +NOVA_IMAGE_NAME = "ubuntu" + +NOVA_QUOTA_INSTANCES = -1 +NOVA_QUOTA_CORES = -1 +NOVA_QUOTA_RAM = -1 +NOVA_QUOTA_FIXED_IPS = -1 +NOVA_QUOTA_FLOATING_IPS = -1 + +NOVA_VM_FLAVOR = "traffic" +NOVA_VM_FLAVOR_RAM = 2048 # In Mega Bytes +NOVA_VM_FLAVOR_VCPUS = 1 +NOVA_VM_FLAVOR_DISK = 10 # In Giga Bytes + + +# GLANCE CONFIGS +GLANCE_ENDPOINT = "http://172.168.2.40:9292" + +GLANCE_IMAGE_NAME = NOVA_IMAGE_NAME +GLANCE_IMAGE_PATH = os.path.join(settings.MEDIA_ROOT, os.path.join("osimages", "ubuntu.img")) +GLANCE_IMAGE_DISK_FORMAT = "qcow2" +GLANCE_IMAGE_CONTAINER_FORMAT = "bare" + diff --git a/ntt_tool/apps/cloud/openstackscripts/credentials.py b/ntt_tool/apps/cloud/openstackscripts/credentials.py new file mode 100644 index 0000000..78bc8ba --- /dev/null +++ b/ntt_tool/apps/cloud/openstackscripts/credentials.py @@ -0,0 +1,49 @@ +from ntt_tool.apps.cloud.models import Cloud + + +def get_cloud_instance(cloud): + if not isinstance(cloud, Cloud): + return Cloud.objects.get(pk=cloud) + return cloud + + +def get_credentials(cloud): + try: + cloud = get_cloud_instance(cloud) + credentials = { + "username": cloud.keystone_user, + "password": cloud.keystone_password, + "auth_url": cloud.keystone_auth_url, + "tenant_name": cloud.keystone_tenant_name + } + return credentials + except Exception, e: + return {} + + +def get_nova_credentials(cloud): + try: + cloud = get_cloud_instance(cloud) + credentials = { + "username": cloud.keystone_user, + "api_key": cloud.keystone_password, + "auth_url": cloud.keystone_auth_url, + "project_id": cloud.keystone_tenant_name + } + return credentials + except Exception, e: + return {} + + +def get_nova_credentials_v3(cloud): + try: + cloud = get_cloud_instance(cloud) + credentials = { + "username": cloud.keystone_user, + "password": cloud.keystone_password, + "auth_url": cloud.keystone_auth_url, + "project_id": cloud.keystone_tenant_name, + } + return credentials + except Exception, e: + return {} \ No newline at end of file diff --git a/ntt_tool/apps/cloud/openstackscripts/endpoints.py b/ntt_tool/apps/cloud/openstackscripts/endpoints.py new file mode 100644 index 0000000..42fcd7a --- /dev/null +++ b/ntt_tool/apps/cloud/openstackscripts/endpoints.py @@ -0,0 +1,84 @@ +import time +from netaddr import IPRange +from django.db import transaction +from neutronclient.v2_0 import client as neutron_client +from novaclient.v1_1 import client as nova_client +from ntt_tool.apps.cloud.models import Network, Endpoint +from credentials import * +from novaclientutils import NovaClientUtils + + +class DiscoverEndpoints(object): + + def __init__(self, traffic_id, network_id): + self.traffic_id = traffic_id + self.network = Network.objects.get(pk=network_id) + self.neutron = neutron_client.Client(**get_credentials(self.network.tenant.cloud)) + self.nova = nova_client.Client(**get_nova_credentials(self.network.tenant.cloud)) + + def get_endpoints(self, ip_range_start, ip_range_end): + ip_range = IPRange(ip_range_start, ip_range_end) + subnet = self.network.subnets.first() + ports = self.neutron.list_ports(network_id=self.network.network_id).get('ports') + endpoints = [] + with transaction.atomic(): + filters = { + "traffic_id": self.traffic_id, + "network_id": self.network.id, + } + Endpoint.objects.filter(**filters).update(is_dirty=False) + for port in ports: + if port.get("device_owner") in ["compute:compute", "compute:nova"]: + if port['fixed_ips'][0]['subnet_id'] == subnet.subnet_id: + endpoint = self.nova.servers.get(port['device_id'].encode('unicode_escape')) + if endpoint and endpoint.status == 'ACTIVE': + ip_address = port['fixed_ips'][0]['ip_address'].encode('unicode_escape') + if ip_address in ip_range: + endpoint_obj, created = Endpoint.objects.get_or_create(traffic_id=self.traffic_id, + network_id=self.network.id, + endpoint_id=endpoint.id) + endpoint_obj.name = endpoint.name + endpoint_obj.ip_address = ip_address + endpoint_obj.status = endpoint.status + endpoint_obj.is_dirty = True + endpoint_obj.save() + endpoints.append(endpoint_obj) + Endpoint.objects.filter(**filters).filter(is_dirty=False).delete() + return endpoints + + +class LaunchEndpoints(NovaClientUtils): + + def __init__(self, traffic, network_id, **credentials): + super(LaunchEndpoints, self).__init__(**credentials) + self.traffic = traffic + self.network = Network.objects.get(pk=network_id) + + def launch(self, endpoint_count=1): + endpoints = [] + endpoint_name = "-".join([self.network.tenant.tenant_name, + self.network.network_name, + time.strftime("%Y%m%d%H%M%S")]) + launched_endpoints = self.launch_endpoint(self.network.tenant.tenant_id, + self.network.network_id, + endpoint_name, + endpoint_count) + + with transaction.atomic(): + filters = { + "traffic_id": self.traffic.id, + "network_id": self.network.id, + } + Endpoint.objects.filter(**filters).update(is_dirty=False) + for endpoint in launched_endpoints: + endpoint_obj, created = Endpoint.objects.get_or_create(traffic_id=self.traffic.id, + network_id=self.network.id, + endpoint_id=endpoint.id) + endpoint_obj.name = endpoint.name + endpoint_obj.ip_address = endpoint.addresses.get(self.network.network_name)[0].get("addr") + endpoint_obj.status = endpoint.status + endpoint_obj.is_dirty = True + endpoint_obj.save() + endpoints.append(endpoint_obj) + Endpoint.objects.filter(**filters).filter(is_dirty=False).delete() + return endpoints diff --git a/ntt_tool/apps/cloud/openstackscripts/glanceclientutils.py b/ntt_tool/apps/cloud/openstackscripts/glanceclientutils.py new file mode 100644 index 0000000..0962340 --- /dev/null +++ b/ntt_tool/apps/cloud/openstackscripts/glanceclientutils.py @@ -0,0 +1,19 @@ +from glanceclient.v2 import client as glance_client +from openstackclientutils import OpenStackClientUtils +from config import * + + +class GlanceClientUtils(OpenStackClientUtils): + client_class = glance_client.Client + + def __init__(self, **credentials): + super(GlanceClientUtils, self).__init__(**credentials) + self.glance = self.get_client_instance() + + def upload_image(self): + image = self.glance.images.create( + disk_format=GLANCE_IMAGE_DISK_FORMAT, + container_format=GLANCE_IMAGE_CONTAINER_FORMAT, + name=GLANCE_IMAGE_NAME) + self.glance.images.upload(image.id, open(GLANCE_IMAGE_PATH, 'rb')) + return image diff --git a/ntt_tool/apps/cloud/openstackscripts/keystoneclientutils.py b/ntt_tool/apps/cloud/openstackscripts/keystoneclientutils.py new file mode 100644 index 0000000..5c7961b --- /dev/null +++ b/ntt_tool/apps/cloud/openstackscripts/keystoneclientutils.py @@ -0,0 +1,16 @@ +from keystoneclient.v2_0 import client as keystone_client +from openstackclientutils import OpenStackClientUtils + + +class KeystoneClientUtils(OpenStackClientUtils): + client_class = keystone_client.Client + + def __init__(self, **credentials): + super(KeystoneClientUtils, self).__init__(**credentials) + self.keystone = self.get_client_instance() + + def get_token(self): + return self.keystone.auth_ref.get("token", {}).get("id") + + def get_tenants(self): + return self.keystone.tenants.list() diff --git a/ntt_tool/apps/cloud/openstackscripts/neutronclientutils.py b/ntt_tool/apps/cloud/openstackscripts/neutronclientutils.py new file mode 100644 index 0000000..0d12a86 --- /dev/null +++ b/ntt_tool/apps/cloud/openstackscripts/neutronclientutils.py @@ -0,0 +1,19 @@ +from neutronclient.v2_0 import client as neutron_client +from openstackclientutils import OpenStackClientUtils + + +class NeutronClientUtils(OpenStackClientUtils): + client_class = neutron_client.Client + + def __init__(self, **credentials): + super(NeutronClientUtils, self).__init__(**credentials) + self.neutron = self.get_client_instance() + + def list_networks(self, **kwargs): + return self.neutron.list_networks(**kwargs) + + def list_subnets(self, **kwargs): + return self.neutron.list_subnets(**kwargs) + + def show_subnet(self, subnet_id): + return self.neutron.show_subnet(subnet_id).get("subnet") diff --git a/ntt_tool/apps/cloud/openstackscripts/novaclientutils.py b/ntt_tool/apps/cloud/openstackscripts/novaclientutils.py new file mode 100644 index 0000000..01afd9e --- /dev/null +++ b/ntt_tool/apps/cloud/openstackscripts/novaclientutils.py @@ -0,0 +1,101 @@ +import time +import os +from novaclient.v1_1 import client as nova_client +from novaclient.exceptions import NotFound +from openstackclientutils import OpenStackClientUtils +from keystoneclientutils import KeystoneClientUtils +from glanceclientutils import GlanceClientUtils +from config import * + + +class NovaClientUtils(OpenStackClientUtils): + client_class = nova_client.Client + + def __init__(self, **credentials): + super(NovaClientUtils, self).__init__(**credentials) + self.nova = self.get_client_instance() + + def assign_floating_ip(self, instance): + if NOVA_FLOATING_IP_CREATION: + floating_ip = self.nova.floating_ips.create(NOVA_FLOATING_IP_POOL) + instance.add_floating_ip(floating_ip) + return True + return False + + def launch_endpoint(self, tenant_id, network_id, endpoint_name, min_count=1): + """ + Launches VM on given tenant and network + :param tenant_id: + :param network_id: + :param endpoint_name: + :return: VM instance + """ + self.nova.quotas.update(tenant_id, + instances=NOVA_QUOTA_INSTANCES, + cores=NOVA_QUOTA_CORES, + ram=NOVA_QUOTA_RAM, + fixed_ips=NOVA_QUOTA_FIXED_IPS, + floating_ips=NOVA_QUOTA_FLOATING_IPS) + + user_data = None + user_data_file_path = os.path.join(os.path.dirname(__file__), 'user.txt') + with open(user_data_file_path, 'r') as f: + user_data = f.read() + + image = None + try: + image = self.nova.images.find(name=NOVA_IMAGE_NAME) + except NotFound: + # Uploading image if does not exist. + keystone_credentials = { + "username": self.credentials.get("username"), + "password": self.credentials.get("api_key"), + "auth_url": self.credentials.get("auth_url"), + "tenant_name": self.credentials.get("project_id") + } + keystone = KeystoneClientUtils(**keystone_credentials) + glance = GlanceClientUtils(endpoint=GLANCE_ENDPOINT, + token=keystone.get_token()) + image = glance.upload_image() + + # Creating flavor + flavor = None + try: + flavor = self.nova.flavors.find(name=NOVA_VM_FLAVOR) + except NotFound: + flavor = self.nova.flavors.create(name=NOVA_VM_FLAVOR, + ram=NOVA_VM_FLAVOR_RAM, + vcpus=NOVA_VM_FLAVOR_VCPUS, + disk=NOVA_VM_FLAVOR_DISK) + + # Launching endpoints + first_instance = self.nova.servers.create(name=endpoint_name, + image=image, + flavor=flavor, + key_name="admin", + nics=[{'net-id': network_id}], + userdata=user_data, + min_count=min_count, + availability_zone="compute:openstack-aio") + + instances = [] + # Todo: As of now nova does not returns all the instances which it launched. It is returning only first + # Todo: Refer openstack blueprint + if min_count > 1: + for endpoint_index in range(1, min_count+1): + instance_name = "%s-%d" % (first_instance.name.rsplit('-', 1)[0], endpoint_index) + instance = self.nova.servers.find(name=instance_name) + while instance.status == 'BUILD': + time.sleep(5) + instance = self.nova.servers.get(instance.id) + # Assigning floating IP to the instance + if self.assign_floating_ip(instance): + instances.append(instance) + + return instances + + + + + + diff --git a/ntt_tool/apps/cloud/openstackscripts/openstackclientutils.py b/ntt_tool/apps/cloud/openstackscripts/openstackclientutils.py new file mode 100644 index 0000000..1ee1fb2 --- /dev/null +++ b/ntt_tool/apps/cloud/openstackscripts/openstackclientutils.py @@ -0,0 +1,21 @@ +class OpenStackClientUtils(object): + + def __init__(self, **credentials): + self.credentials = credentials + + def get_credentials(self): + return self.credentials + + def get_client_instance(self): + credentials = self.get_credentials() + return self.client_class(**credentials) + + + + + + + + + + diff --git a/ntt_tool/apps/cloud/openstackscripts/openstackcredentials.py b/ntt_tool/apps/cloud/openstackscripts/openstackcredentials.py new file mode 100644 index 0000000..e69de29 diff --git a/ntt_tool/apps/cloud/openstackscripts/tenantnetworkdiscovery.py b/ntt_tool/apps/cloud/openstackscripts/tenantnetworkdiscovery.py new file mode 100644 index 0000000..4841279 --- /dev/null +++ b/ntt_tool/apps/cloud/openstackscripts/tenantnetworkdiscovery.py @@ -0,0 +1,102 @@ +import logging +from django.db import transaction +from django.utils import timezone +from keystoneclientutils import KeystoneClientUtils +from neutronclientutils import NeutronClientUtils +from ntt_tool.apps.cloud.models import Tenant, Network, Subnet +from ntt_tool.apps.cloud.serializers import TenantSerializer + + +logger = logging.getLogger(__name__) + + +class NetworkSubnetDiscovery(NeutronClientUtils): + + def get_networks_and_subnets(self, user, cloud_id, tenants): + # neutron = self.get_client_instance() + + discovery_result = [] + with transaction.atomic(): + discovery_datetime = timezone.now() + + Tenant.objects.filter(cloud_id=cloud_id)\ + .update(is_dirty=False) + + for tenant in tenants: + tenant_obj = None + try: + tenant_obj = Tenant.objects.filter(tenant_id=tenant.id).get() + except Tenant.DoesNotExist: + tenant_obj = Tenant() + tenant_obj.cloud_id = cloud_id + tenant_obj.tenant_id = tenant.id + tenant_obj.tenant_name = tenant.name + tenant_obj.description = tenant.description + tenant_obj.enabled = tenant.enabled + tenant_obj.creator = user + tenant_obj.is_dirty = True + tenant_obj.updated_on = discovery_datetime + tenant_obj.save() + + # Setting all records belongs to tenant as not dirty/updated + Network.objects.filter(tenant__tenant_id=tenant.id)\ + .update(is_dirty=False) + + networks = self.list_networks(tenant_id=tenant.id) + for network in networks.get("networks", []): + network_obj = None + try: + filters = { + "tenant__tenant_id": tenant.id, + "network_name": network.get("name") + } + network_obj = Network.objects.filter(**filters).get() + except Network.DoesNotExist: + network_obj = Network() + network_obj.tenant = tenant_obj + network_obj.network_id = network.get("id") + network_obj.network_name = network.get("name") + network_obj.shared = network.get("shared") + network_obj.status = network.get("status") + network_obj.is_dirty = True + network_obj.creator = user + network_obj.updated_on = discovery_datetime + network_obj.save() + + # Setting all records belongs to tenant as not dirty/updated + Subnet.objects.filter(network__network_id=network.get("id"))\ + .update(is_dirty=False) + + subnets = self.list_subnets(network_id=network.get("id")) + for subnet in subnets.get("subnets"): + subnet_obj = None + try: + filters = { + "network__network_id": network.get("id"), + "subnet_id": subnet.get("id") + } + subnet_obj = Subnet.objects.filter(**filters).get() + except Subnet.DoesNotExist: + subnet_obj = Subnet() + subnet_obj.subnet_id = subnet.get("id") + subnet_obj.network_id = network_obj.id + subnet_obj.subnet_name = subnet.get("name") + subnet_obj.cidr = subnet.get("cidr") + subnet_obj.is_dirty = True + subnet_obj.save() + + # Deleting subnets which are got changed after discovery + Subnet.objects.filter(network__network_id=network.get("id"))\ + .filter(is_dirty=False).delete() + + # Deleting networks which are not got changed after discovery + Network.objects.filter(tenant__tenant_id=tenant_obj.id)\ + .filter(is_dirty=False).delete() + + serializer = TenantSerializer(tenant_obj) + discovery_result.append(serializer.data) + + # Deleting tenants which are got changed after discovery + Tenant.objects.filter(cloud_id=cloud_id)\ + .filter(is_dirty=False).delete() + return discovery_result diff --git a/ntt_tool/apps/cloud/openstackscripts/traffictest/README.md b/ntt_tool/apps/cloud/openstackscripts/traffictest/README.md new file mode 100644 index 0000000..b55a188 --- /dev/null +++ b/ntt_tool/apps/cloud/openstackscripts/traffictest/README.md @@ -0,0 +1,45 @@ +# datapath-traffic-test + +Collection of tools and utility scripts to validate datapath traffic between endpoints governed by a contract + +**Traffic Test between Local VMs on VMware:** + +**Starting Traffic:** +$ python main.py --cfgfile=conf/config.ini --action=start -t sample_timestamp_name_to_track + +**Note:** [Optional parameter to load endpoints as JSON input file +--jsopninput=sample_endpoints.json + +**Stoping Traffic:** +$ python main.py --cfgfile=conf/config.ini --action=stop -t sample_timestamp_name_to_track + +**Note:** [Optional parameter to load endpoints as JSON input file +--jsopninput=sample_endpoints.json + +**Output:** + ++--------------+----------+--------------+----------+---------------------+------------------+---------------+---------+---------+---------+-------------+ +| src_tenant | src_ep | dest_tenant | dest_ep | packets_transmitted | packets received | packet_loss % | rtt_min | rtt_avg | rtt_max | test_status | ++--------------+----------+--------------+----------+---------------------+------------------+---------------+---------+---------+---------+-------------+ +| traffic_test | 1.1.1.16 | traffic_test | 2.2.2.6 | 164 | 164 | 0 | 0.3 | 2.9 | 5.1 | Success | +| traffic_test | 1.1.1.16 | traffic_test | 1.1.1.15 | 167 | 167 | 0 | 0.3 | 2.8 | 5.1 | Success | +| traffic_test | 1.1.1.16 | traffic_test | 2.2.2.5 | 163 | 163 | 0 | 0.4 | 2.8 | 5.1 | Success | +| traffic_test | 1.1.1.16 | traffic_test | 1.1.1.17 | 166 | 166 | 0 | 0.6 | 33.1 | 1004.7 | Success | +| traffic_test | 1.1.1.17 | traffic_test | 2.2.2.6 | 164 | 164 | 0 | 1.1 | 2.8 | 5.1 | Success | +| traffic_test | 1.1.1.17 | traffic_test | 1.1.1.15 | 165 | 165 | 0 | 1.1 | 3.0 | 6.2 | Success | +| traffic_test | 1.1.1.17 | traffic_test | 2.2.2.5 | 162 | 162 | 0 | 0.4 | 27.4 | 1003.3 | Success | +| traffic_test | 1.1.1.17 | traffic_test | 1.1.1.16 | 166 | 166 | 0 | 0.6 | 3.3 | 7.8 | Success | +| traffic_test | 2.2.2.5 | traffic_test | 1.1.1.16 | 166 | 166 | 0 | 0.4 | 2.8 | 5.2 | Success | +| traffic_test | 2.2.2.5 | traffic_test | 1.1.1.17 | 164 | 0 | 100 | 0.0 | 0.0 | 0.0 | Failed | +| traffic_test | 2.2.2.5 | traffic_test | 2.2.2.6 | 162 | 162 | 0 | 1.1 | 9.1 | 1001.1 | Success | +| traffic_test | 2.2.2.5 | traffic_test | 1.1.1.15 | 165 | 165 | 0 | 0.3 | 3.0 | 5.1 | Success | +| traffic_test | 1.1.1.15 | traffic_test | 1.1.1.17 | 165 | 165 | 0 | 1.1 | 2.9 | 5.2 | Success | +| traffic_test | 1.1.1.15 | traffic_test | 2.2.2.6 | 163 | 163 | 0 | 0.2 | 2.9 | 5.1 | Success | +| traffic_test | 1.1.1.15 | traffic_test | 2.2.2.5 | 162 | 162 | 0 | 1.1 | 3.0 | 5.2 | Success | +| traffic_test | 1.1.1.15 | traffic_test | 1.1.1.16 | 166 | 166 | 0 | 0.3 | 2.9 | 5.1 | Success | +| traffic_test | 2.2.2.6 | traffic_test | 1.1.1.16 | 168 | 168 | 0 | 1.1 | 2.9 | 7.0 | Success | +| traffic_test | 2.2.2.6 | traffic_test | 1.1.1.17 | 165 | 0 | 100 | 0.0 | 0.0 | 0.0 | Failed | +| traffic_test | 2.2.2.6 | traffic_test | 1.1.1.15 | 166 | 166 | 0 | 0.8 | 9.0 | 1003.4 | Success | +| traffic_test | 2.2.2.6 | traffic_test | 2.2.2.5 | 163 | 163 | 0 | 0.4 | 21.3 | 1004.6 | Success | ++--------------+----------+--------------+----------+---------------------+------------------+---------------+---------+---------+---------+-------------+ + diff --git a/ntt_tool/apps/cloud/openstackscripts/traffictest/__init__.py b/ntt_tool/apps/cloud/openstackscripts/traffictest/__init__.py new file mode 100644 index 0000000..e69de29 diff --git a/ntt_tool/apps/cloud/openstackscripts/traffictest/conf/__init__.py b/ntt_tool/apps/cloud/openstackscripts/traffictest/conf/__init__.py new file mode 100644 index 0000000..e69de29 diff --git a/ntt_tool/apps/cloud/openstackscripts/traffictest/conf/config.ini b/ntt_tool/apps/cloud/openstackscripts/traffictest/conf/config.ini new file mode 100644 index 0000000..1cb480e --- /dev/null +++ b/ntt_tool/apps/cloud/openstackscripts/traffictest/conf/config.ini @@ -0,0 +1,34 @@ +# Openstack environment config +[default] +keystone_auth_url='http://172.168.2.40:5000/v2.0' +keystone_user='admin' +keystone_password='admin' +keystone_tenant_name='admin' + +# Traffic test config +[traffic] +allowed_delta_percentage = 3 +test_results_path = '~/dp_test_results' +number_of_workers = 10 +remote_user = 'ubuntu' +remote_pass = 'ubuntu' +# icmp test through hping3 & tcp, udp test through iperf +test_method = 'icmp','tcp','udp' +# tcp & udp traffic generation duration in seconds +iperf_duration = 60 +# intra-tenant/inter-tenant/south-north/north-south/all +type = 'intra-tenant' + +# specify tenant name +[tenants] +tenants = 'admin', + +# specify SSH jump-host/gateway IP based on the tenant name +[tenant_ssh_gateway] +#traffic_test = '172.168.2.53' +admin= '172.168.2.35' + +# specify SSH External host IP for south-north & north-south test +[external_host] +host = '10.1.25.129' + diff --git a/ntt_tool/apps/cloud/openstackscripts/traffictest/conf/logging.ini b/ntt_tool/apps/cloud/openstackscripts/traffictest/conf/logging.ini new file mode 100644 index 0000000..5091a55 --- /dev/null +++ b/ntt_tool/apps/cloud/openstackscripts/traffictest/conf/logging.ini @@ -0,0 +1,33 @@ +[loggers] +keys=root + +[handlers] +keys=consoleHandler,fileHandler + +[formatters] +keys=simpleFormatter,fileFormatter + +[logger_root] +level=INFO +handlers=consoleHandler,fileHandler + +[handler_consoleHandler] +class=StreamHandler +level=DEBUG +formatter=simpleFormatter +args=(sys.stdout,) + +[handler_fileHandler] +class=FileHandler +level=DEBUG +formatter=fileFormatter +args=('logs/dptest.log',) + +[formatter_simpleFormatter] +format=%(asctime)s - %(name)s - %(levelname)s - %(message)s +datefmt= + +[formatter_fileFormatter] +format=%(asctime)s - %(name)s - %(levelname)s - %(message)s +datefmt= + diff --git a/ntt_tool/apps/cloud/openstackscripts/traffictest/conf/os_env_conf.py b/ntt_tool/apps/cloud/openstackscripts/traffictest/conf/os_env_conf.py new file mode 100644 index 0000000..24bbe90 --- /dev/null +++ b/ntt_tool/apps/cloud/openstackscripts/traffictest/conf/os_env_conf.py @@ -0,0 +1,16 @@ +# Openstack environment config + +keystone_auth_url='http://10.30.120.50:5000/v2.0' +keystone_user='admin' +keystone_password='noir0123' +keystone_tenant_name='admin' + +# Traffic test config + +allowed_delta_percentage = 3 +test_results_path = '~/dp_test_results' +ssh_gateway = '192.168.61.2' +number_of_workers = 10 +remote_user = 'root' +remote_pass = 'root123' +test_method = 'hping' diff --git a/ntt_tool/apps/cloud/openstackscripts/traffictest/libs/__init__.py b/ntt_tool/apps/cloud/openstackscripts/traffictest/libs/__init__.py new file mode 100644 index 0000000..e69de29 diff --git a/ntt_tool/apps/cloud/openstackscripts/traffictest/libs/os_get_env.py b/ntt_tool/apps/cloud/openstackscripts/traffictest/libs/os_get_env.py new file mode 100644 index 0000000..857b06b --- /dev/null +++ b/ntt_tool/apps/cloud/openstackscripts/traffictest/libs/os_get_env.py @@ -0,0 +1,74 @@ +import logging +from keystoneclient.v2_0 import client as ksc +from neutronclient.neutron import client as nwc +from novaclient import client as novac +import pdb + +class OSUtils(object): + + def __init__(self, config): + self.logger = logging.getLogger(__name__) + self.config = config + self.ks_user = config['default']['keystone_user'] + self.ks_pass = config['default']['keystone_password'] + self.ks_auth_url = config['default']['keystone_auth_url'] + self.ks_tenant_name = config['default']['keystone_tenant_name'] + + self.ks = ksc.Client( + username=self.ks_user, + password=self.ks_pass, + tenant_name=self.ks_tenant_name, + auth_url=self.ks_auth_url) + self.logger.info('OSUtils intialized successfully') + + def get_neutron_client_by_tenant(self, tenant): + nwclient = nwc.Client( + '2.0', + username=self.ks_user, + password=self.ks_pass, + tenant_name=tenant, + auth_url=self.ks_auth_url) + return nwclient + + def get_nova_client_by_tenant(self, tenant): + novaclient = novac.Client( + '2', + username=self.ks_user, + api_key=self.ks_pass, + project_id=tenant, + auth_url=self.ks_auth_url) + return novaclient + + def get_tenants_list(self): + self.logger.info('Get tenant list from Keystone') + tenants = self.ks.tenants.list() + self.logger.info(tenants) + if self.config['traffic']['type'] == 'intra-tenant' or self.config['traffic']['type'] == 'all': + self.logger.info('\n\t\tPerforming Intra-tenant Test.\n') + if len(self.config['tenants']['tenants']) > 0: + self.logger.info('\n\t\tMore than one Tenant found. Processing first tenant alone by default.\n') + cfgtenants = self.config['tenants']['tenants'][0] + else: + cfgtenants = self.config['tenants']['tenants'] + if self.config['traffic']['type'] == 'inter-tenant' or self.config['traffic']['type'] == 'all': + self.logger.info('\n\n\t\tPerforming Inter-tenant Test.\n') + if len(self.config['tenants']['tenants']) < 2: + cfgtenants = '' + self.logger.info('\n\t\tMinimum Tenants count should be 2\n') + else: + cfgtenants = self.config['tenants']['tenants'] + if self.config['traffic']['type'] == 'south-north' or self.config['traffic']['type'] == 'all': + self.logger.info('\n\n\t\tPerforming south-north Test.\n') + if len(self.config['tenants']['tenants']) < 1: + cfgtenants = '' + else: + cfgtenants = self.config['tenants']['tenants'] + if self.config['traffic']['type'] == 'north-south' or self.config['traffic']['type'] == 'all': + self.logger.info('\n\n\t\tPerforming north-south Test.\n') + if len(self.config['tenants']['tenants']) < 1: + cfgtenants = '' + else: + cfgtenants = self.config['tenants']['tenants'] + + return {tenant.id: tenant.name for tenant in tenants if tenant.enabled and tenant.name in cfgtenants} + diff --git a/ntt_tool/apps/cloud/openstackscripts/traffictest/libs/tasks.py b/ntt_tool/apps/cloud/openstackscripts/traffictest/libs/tasks.py new file mode 100644 index 0000000..e69de29 diff --git a/ntt_tool/apps/cloud/openstackscripts/traffictest/libs/test_fabric.py b/ntt_tool/apps/cloud/openstackscripts/traffictest/libs/test_fabric.py new file mode 100644 index 0000000..9c26972 --- /dev/null +++ b/ntt_tool/apps/cloud/openstackscripts/traffictest/libs/test_fabric.py @@ -0,0 +1,54 @@ +from fabric.api import * +import fabric +from fabric.network import ssh +import sys + +#import pudb + +ssh.util.log_to_file("paramiko.log", 10) + +host_list = ['1.2.3.3', '1.2.3.2', '1.2.3.5'] +env.hosts = host_list +env.user = 'noiro' +env.password = 'noir0123' +env.gateway = '10.30.120.11' +#env.shell = "/bin/sh -c" + +@task +def get_hostname(): + out = run("hostname") + print out + +@task +@parallel +def start_traffic(env): + + for host in host_list: + if host != env.host_string: +# with cd("~/dp_test_results"): + sudo('hping3 %s --icmp --fast -q 2> sampletraffic-%s-%s.txt 1> /dev/null &' %(host,env.host_string.replace('.','_'), host.replace('.','_')), pty=False) + + +@task +@parallel +def stop_traffic(): +# with cd("~/dp_test_results"): + sudo("kill -SIGINT `pgrep hping3`") +# with cd("~/dp_test_results"): + for host in host_list: + if host != env.host_string: + out = sudo("cat sampletraffic-%s-%s.txt | grep loss" %(env.host_string.replace('.','_'), host.replace('.','_'))) + print out + +@task +@runs_once +def start_task(): + if sys.argv[1] == 'start': + execute(start_traffic, env) + elif sys.argv[1] == 'stop': + execute(stop_traffic, hosts = env.hosts) + else: + execute(get_hostname, hosts = env.hosts) + +if __name__ == '__main__': + start_task() diff --git a/ntt_tool/apps/cloud/openstackscripts/traffictest/libs/traf_tester.py b/ntt_tool/apps/cloud/openstackscripts/traffictest/libs/traf_tester.py new file mode 100644 index 0000000..864da9c --- /dev/null +++ b/ntt_tool/apps/cloud/openstackscripts/traffictest/libs/traf_tester.py @@ -0,0 +1,472 @@ +import json +import logging +import os +import pickle +import re +import time +import fabtools + +from itertools import * +from operator import * +from fabric.api import * +from django.conf import settings + + +traffic_test_script_dir = os.path.join(os.path.dirname(os.path.dirname(os.path.abspath(__file__))), 'scripts') +test_results_path = '~/dp_test_results' + +logger = logging.getLogger(__name__) + + +def unique_justseen(iterable, key=None): + "List unique elements, preserving order. Remember only the element just seen." + return imap(next, imap(itemgetter(1), groupby(iterable, key))) + + +def setup_env(config, endpoints): + env.hosts = endpoints.get("src_eps") + env.user = config.get('traffic', {}).get('remote_user') + env.password = config.get('traffic', {}).get('remote_pass') + env.skip_bad_hosts = True + if endpoints['test_type'] != 'north-south': + env.gateway = config.get('tenant_ssh_gateway', {}).get(endpoints.get('src_tenant')[0]) + else: + env.gateway = config.get('tenant_ssh_gateway', {}).get(endpoints.get('dest_tenant')[0]) + + +def install_hping(environment): + try: + out = run("python -c 'import platform;" + " print platform.linux_distribution()[0]'") + os_info = out + logger.debug("Host %s runs %s" % (env.host_string, os_info)) + if out.return_code == 0: + if os_info in ['CentOS', + 'Red Hat Enterprise Linux Server', + 'Fedora']: + out = sudo("yum -y install hping3") + if out.return_code == 0: + logger.info("Installed hping3 on %s" % (env.host_string)) + elif os_info in ['Ubuntu','LinuxMint']: + out = sudo("apt-get -y install hping3") + if out.return_code == 0: + logger.info("Installed hping3 on %s" % (env.host_string)) + out = run("mkdir %s" % (test_results_path)) + except SystemExit, e: + logger.warn("Exception while executing task: %s", str(e)) + + +def install_iperf(environment): + try: + # out = run("python -c 'import platform;" + # " print platform.linux_distribution()[0]'") + # os_info = out + + os_distribution_id = fabtools.system.distrib_id() + logger.debug("Host %s runs %s" % (env.host_string, os_distribution_id)) + # if out.return_code == 0: + if os_distribution_id in ['CentOS','Red Hat Enterprise Linux Server', 'Fedora']: + out = sudo("yum -y install iperf3") + if out.return_code == 0: + logger.info("Installed iperf3 on %s" % (env.host_string)) + elif os_distribution_id in ['Ubuntu','LinuxMint']: + if not fabtools.deb.is_installed("iperf3"): + fabtools.deb.update_index() + fabtools.deb.install("iperf3") + # run("sudo apt-get update -y") + # out = sudo("apt-get -y install iperf3") + # if out.return_code == 0: + # logger.info("Installed iperf3 on %s" % (env.host_string)) + run("mkdir %s" % (test_results_path)) + except SystemExit, e: + logger.warn("Exception while executing task: %s", str(e)) + + +def setup_iperf_env(config, src_tenant, dest_tenant, endpoints, test_type): + env.hosts = endpoints + env.user = config.get('traffic', {}).get('remote_user') + env.password = config.get('traffic', {}).get('remote_pass') + env.skip_bad_hosts = True + if test_type != 'north-south': + env.gateway = config.get('tenant_ssh_gateway', {}).get(src_tenant[0]) + else: + env.gateway = config.get('tenant_ssh_gateway', {}).get(dest_tenant[0]) + + +@task +@parallel +def create_test_results_directory(environment): + try: + run("mkdir %s" % (test_results_path)) + except SystemExit, e: + logger.warn("Exception while executing task: %s", str(e)) + + +@task +@parallel +def test_ping(environment, config, endpoints, contract, timestamp): + try: + for dest_ep in endpoints['dest_eps']: + if dest_ep != env.host_string: + #sudo("hping3 %s --icmp --fast -q 2>" + # " testtraffic-%s-%s-%s.txt 1> /dev/null &" % + sudo("hping3 %s --icmp --fast -q >" + " testtraffic-%s-%s-%s.txt 2>&1 &" % + (dest_ep, + env.host_string.replace('.', '_'), + dest_ep.replace('.', '_'), + timestamp), + pty=False) + + except SystemExit, e: + logger.warn("Exception while executing task: %s", str(e)) + + +@task +@parallel +def test_tcp(environment, config, server, endpoints, contract, timestamp): + try: + if server == '': + print "TCP server execution" + for src_ep in endpoints: + if src_ep == env.host_string: + print src_ep + + sudo("iperf3 -s -p 5201 -i 1 > tcptesttrafficserver-%s-%s.txt 2>&1 &" % + (env.host_string.replace('.', '_'), + timestamp), + pty=False) + + else: + print "TCP client execution" + for dest_ep in endpoints: + if dest_ep == env.host_string: + print dest_ep + + sudo("iperf3 -c %s -t %s -p 5201 > tcptesttrafficclient-%s-%s-%s.txt 2>&1 &" % + (server, + config['traffic']['iperf_duration'], + env.host_string.replace('.', '_'), + server.replace('.', '_'), + timestamp), + pty=False) + + except SystemExit, e: + logger.warn("Exception while executing task: %s", str(e)) + + +@task +@parallel +def test_udp(environment, config, server, endpoints, contract, timestamp): + try: + if server == '': + print "UDP server execution" + for src_ep in endpoints: + if src_ep == env.host_string: + print src_ep + + sudo("iperf3 -s -p 5202 -i 1 > udptesttrafficserver-%s-%s.txt 2>&1 &" % + (env.host_string.replace('.', '_'), + timestamp), + pty=False) + + else: + print "UDP client execution" + for dest_ep in endpoints: + if dest_ep == env.host_string: + print dest_ep + + sudo("iperf3 -c %s -u -t %s -p 5202 > udptesttrafficclient-%s-%s-%s.txt 2>&1 &" % + (server, + config['traffic']['iperf_duration'], + env.host_string.replace('.', '_'), + server.replace('.', '_'), + timestamp), + pty=False) + + except SystemExit, e: + logger.warn("Exception while executing task: %s", str(e)) + + +def capture_output(): + pass + + +@task +@parallel +def stop_traffic(environment, endpoints, timestamp): + try: + try: + sudo("kill -SIGINT `pgrep hping3`") + except SystemExit, e: + logger.warn("Exception while executing task: %s", str(e)) + + print "dest_eps are.....", endpoints['dest_eps'] + put(os.path.join(traffic_test_script_dir, "get_ping_statistics.py"), "get_ping_statistics.py") + out = run("python get_ping_statistics.py %s" % (timestamp)) + out_dict = json.JSONDecoder().decode(out) + output = {'src_tenant': endpoints['src_tenant'], 'dest_tenant': endpoints['dest_tenant'], 'test_result': out_dict} + return output + except SystemExit, e: + logger.warn("Exception while executing task: %s", str(e)) + + + +@task +@parallel +def stop_iperf_traffic(environment, traffic_type, server, dest_ep, src_tenant, dest_tenant, timestamp): + try: + + if server == '': + try: + print "Killing iperf on server" + sudo("kill -SIGINT `pgrep iperf`") + except SystemExit, e: + logger.warn("Exception while executing task: %s", str(e)) + + print "server endpoint is.....", dest_ep + + return True + else: + + try: + print "Killing iperf on client" + sudo("kill -SIGINT `pgrep iperf`") + except SystemExit, e: + logger.warn("Exception while executing task: %s", str(e)) + + print "client endpoint is.....", dest_ep + + if traffic_type == 'tcp': + put(os.path.join(traffic_test_script_dir, "get_iperf_tcp_statistics.py"), "get_iperf_tcp_statistics.py") + out = run("python get_iperf_tcp_statistics.py %s-%s-%s" % + (dest_ep.replace('.', '_'), + server.replace('.', '_'), + timestamp)) + + if traffic_type == 'udp': + put(os.path.join(traffic_test_script_dir, "get_iperf_udp_statistics.py"), "get_iperf_udp_statistics.py") + out = run("python get_iperf_udp_statistics.py %s-%s-%s" % + (dest_ep.replace('.', '_'), + server.replace('.', '_'), + timestamp)) + + out_dict = json.JSONDecoder().decode(out) + return {'src_tenant': src_tenant, 'dest_tenant': dest_tenant, 'test_result': out_dict} + except SystemExit, e: + logger.warn("Exception while executing task: %s", str(e)) + + +def format_icmp_test_results(data, allowed_delta_percentage): + status = None + test_results = [] + dest_ep_regex = ".*-*-(?P[0-9]+_[0-9]+_[0-9]+_[0-9]+)-.*" + for content in data: + for k, v in content.items(): + src_ep = k + src_tenant = v['src_tenant'] + dest_tenant = v['dest_tenant'] + test_result_files = v['test_result'].keys() + for test_result_file in test_result_files: + dest_ep_match = re.match(dest_ep_regex, test_result_file) + dest_ep = dest_ep_match.group('dest_ip') + packet_stats = v['test_result'][test_result_file]['packet_stats'] + packet_loss_percent = packet_stats['packet_loss'] # NOQA + try: + if packet_loss_percent <= int(allowed_delta_percentage): # NOQA + status = 'Success' + else: + status = 'Failed' + except ValueError: + status = 'Failed' + rtt_stats = v['test_result'][test_result_file]['rtt'] + test_results.append({ + "src_tenant": src_tenant, + "src_ep": src_ep, + "dest_tenant": dest_tenant, + "dest_ep": dest_ep.replace('_', '.'), + "packets_transmitted": packet_stats['packets_transmitted'], + "packets_received": packet_stats['packets_received'], + "packet_loss_percent": packet_loss_percent, + "rtt_min": rtt_stats['rtt_min'], + "rtt_avg": rtt_stats['rtt_avg'], + "rtt_max": rtt_stats['rtt_max'], + "status": status, + }) + return test_results + + +def format_tcp_test_results(data): + test_results = [] + status = None + dest_ep_regex = ".*-*-(?P[0-9]+_[0-9]+_[0-9]+_[0-9]+)-.*" + for content in data: + for k, v in content.items(): + src_ep = k + src_tenant = v['src_tenant'] + dest_tenant = v['dest_tenant'] + test_result_files = v['test_result'].keys() + for test_result_file in test_result_files: + dest_ep_match = re.match(dest_ep_regex, test_result_file) + dest_ep = dest_ep_match.group('dest_ip') + bandwidth_stats = v['test_result'][test_result_file]['bandwidth_stats'] + if bandwidth_stats['interval_time'] and bandwidth_stats['transferred'] and bandwidth_stats['bandwidth']: + status = "Success" + else: + status = "Failed" + test_results.append({ + "src_tenant": src_tenant, + "src_ep": src_ep, + "dest_tenant": dest_tenant, + "dest_ep": dest_ep.replace('_', '.'), + "interval_time": bandwidth_stats['interval_time'], + "transferred": bandwidth_stats['transferred'], + "bandwidth": bandwidth_stats['bandwidth'], + "retr": bandwidth_stats['retr'], + "status": status + }) + return test_results + + +def format_udp_test_results(data): + test_results = [] + status = None + dest_ep_regex = ".*-*-(?P[0-9]+_[0-9]+_[0-9]+_[0-9]+)-.*" + for content in data: + for k, v in content.items(): + src_ep = k + src_tenant = v['src_tenant'] + dest_tenant = v['dest_tenant'] + test_result_files = v['test_result'].keys() + for test_result_file in test_result_files: + dest_ep_match = re.match(dest_ep_regex, test_result_file) + dest_ep = dest_ep_match.group('dest_ip') + bandwidth_stats = v['test_result'][test_result_file]['bandwidth_stats'] + if bandwidth_stats['loss_percent'] != '': + bandwidth_loss_percent = \ + bandwidth_stats['loss_percent']+" %" # NOQA + else: + bandwidth_loss_percent = "" + if bandwidth_stats['interval_time'] and bandwidth_stats['transferred'] and bandwidth_stats['bandwidth']: + status = "Success" + else: + status = "Failed" + test_results.append({ + "src_tenant": src_tenant, + "src_ep": src_ep, + "dest_tenant": dest_tenant, + "dest_ep": dest_ep.replace('_', '.'), + "interval_time": bandwidth_stats['interval_time'], + "transferred": bandwidth_stats['transferred'], + "bandwidth": bandwidth_stats['bandwidth'], + "jitter": bandwidth_stats['jitter'], + "loss_datagram": bandwidth_stats['loss_datagram'], + "total_datagram": bandwidth_stats['total_datagram'], + "bandwidth_loss_percent": bandwidth_loss_percent, + "status": status + }) + return test_results + + +def start_task(config, endpoints_list, action, testPrefix=None): + timestamp = testPrefix + if not testPrefix: + timestamp = time.strftime("%Y-%m-%d-%H-%M-%S") + + output_table_data_list = [] + tcp_output_table_data_list = [] + udp_output_table_data_list = [] + table_data = {} + iperf_endpoint_list = list(unique_justseen(endpoints_list, key=itemgetter('test_type'))) + if action == 'start': + for endpoints in endpoints_list: + for contract in endpoints['contract']: + if contract['protocol'] == 'icmp': + setup_env(config, endpoints) + execute(create_test_results_directory, env) + execute(install_hping, env) + execute(test_ping, env, config, endpoints, contract, timestamp) + for fullendpoints in iperf_endpoint_list: + endpoints = {} + endpoints['contract'] = fullendpoints['contract'] + endpoints['src_tenant'] = fullendpoints['src_tenant'] + endpoints['src_eps'] = [fullendpoints['src_eps'][0]] + endpoints['dest_tenant'] = fullendpoints['dest_tenant'] + endpoints['dest_eps'] = [fullendpoints['dest_eps'][0]] + endpoints['test_type'] = fullendpoints['test_type'] + for contract in endpoints['contract']: + if contract['protocol'] == 'tcp': + server_ip = [endpoints['dest_eps'][0]] + client_ip = endpoints['src_eps'] + setup_iperf_env(config, endpoints['src_tenant'], endpoints['dest_tenant'], server_ip, endpoints['test_type']) + execute(create_test_results_directory, env) + execute(install_iperf, env) + server = '' + execute(test_tcp, env, config, server, server_ip, contract, timestamp) + setup_iperf_env(config, endpoints['src_tenant'], endpoints['dest_tenant'], client_ip, endpoints['test_type']) + execute(install_iperf, env) + server = server_ip[0] + execute(test_tcp, env, config, server, client_ip, contract, timestamp) + if contract['protocol'] == 'udp': + server_ip = [endpoints['dest_eps'][0]] + client_ip = endpoints['src_eps'] + setup_iperf_env(config, endpoints['src_tenant'], endpoints['dest_tenant'], server_ip, endpoints['test_type']) + execute(create_test_results_directory, env) + execute(install_iperf, env) + server = '' + execute(test_udp, env, config, server, server_ip, contract, timestamp) + setup_iperf_env(config, endpoints['src_tenant'], endpoints['dest_tenant'], client_ip, endpoints['test_type']) + execute(install_iperf, env) + server = server_ip[0] + execute(test_udp, env, config, server, client_ip, contract, timestamp) + + if action == 'stop': + for endpoints in endpoints_list: + for contract in endpoints['contract']: + if contract['protocol'] == 'icmp': + setup_env(config, endpoints) + table_data = execute(stop_traffic, env, endpoints, timestamp) + output_table_data_list.append(table_data) + for fullendpoints in iperf_endpoint_list: + endpoints = {} + endpoints['contract'] = fullendpoints['contract'] + endpoints['src_tenant'] = fullendpoints['src_tenant'] + endpoints['src_eps'] = [fullendpoints['src_eps'][0]] + endpoints['dest_tenant'] = fullendpoints['dest_tenant'] + endpoints['dest_eps'] = [fullendpoints['dest_eps'][0]] + endpoints['test_type'] = fullendpoints['test_type'] + for contract in endpoints['contract']: + if contract['protocol'] == 'tcp': + server_ip = [endpoints['dest_eps'][0]] + client_ip = endpoints['src_eps'] + setup_iperf_env(config, endpoints['src_tenant'], endpoints['dest_tenant'], server_ip, endpoints['test_type']) + server = '' + execute(stop_iperf_traffic, env, 'tcp', server, server_ip[0], endpoints['src_tenant'], endpoints['dest_tenant'], timestamp) + setup_iperf_env(config, endpoints['src_tenant'], endpoints['dest_tenant'], client_ip, endpoints['test_type']) + server = server_ip[0] + for dest_ep in client_ip: + table_data = execute(stop_iperf_traffic, env, 'tcp', server, dest_ep, endpoints['src_tenant'], endpoints['dest_tenant'], timestamp) + tcp_output_table_data_list.append(table_data) + if contract['protocol'] == 'udp': + server_ip = [endpoints['dest_eps'][0]] + client_ip = endpoints['src_eps'] + setup_iperf_env(config, endpoints['src_tenant'], endpoints['dest_tenant'], server_ip, endpoints['test_type']) + server = '' + execute(stop_iperf_traffic, env, 'udp', server, server_ip[0], endpoints['src_tenant'], endpoints['dest_tenant'], timestamp) + setup_iperf_env(config, endpoints['src_tenant'], endpoints['dest_tenant'], client_ip, endpoints['test_type']) + server = server_ip[0] + for dest_ep in client_ip: + table_data = execute(stop_iperf_traffic, env, 'udp', server, dest_ep, endpoints['src_tenant'], endpoints['dest_tenant'], timestamp) + udp_output_table_data_list.append(table_data) + + traffic_test_result = { + "icmp": format_icmp_test_results(output_table_data_list, config.get('traffic', {}).get('allowed_delta_percentage')), + "tcp": format_tcp_test_results(tcp_output_table_data_list), + "udp": format_udp_test_results(udp_output_table_data_list,) + } + + traffic_test_result_path = settings.MEDIA_ROOT + pickle.dump(traffic_test_result, open(os.path.join(traffic_test_result_path, "traffic-test-report.txt"), "wb")) + + return traffic_test_result + diff --git a/ntt_tool/apps/cloud/openstackscripts/traffictest/main.py b/ntt_tool/apps/cloud/openstackscripts/traffictest/main.py new file mode 100644 index 0000000..e81eaa4 --- /dev/null +++ b/ntt_tool/apps/cloud/openstackscripts/traffictest/main.py @@ -0,0 +1,429 @@ +import os +import logging +import logging.config +from libs import os_get_env as os_lib +from libs import traf_tester as remote_libs +from configobj import ConfigObj +from argparse import ArgumentParser +import pprint +import json +import pdb +from datetime import datetime, timedelta +from pytz import timezone + +if not os.path.exists('logs'): + os.makedirs('logs') +logging.config.fileConfig('conf/logging.ini') +logger = logging.getLogger(__name__) +logger.setLevel(logging.DEBUG) + +action_list = ['start', 'stop'] +parser = ArgumentParser(description="Datapath traffic testing") +parser.add_argument("-f", "--cfgfile", required=True, metavar="config_input_file") +parser.add_argument("-a", "--action", required=True) +parser.add_argument("-t", + "--tid", + required=True, + help="Timestamp ID to reference for labeling test output") +parser.add_argument("-i", "--jsoninput", required=False, metavar="endpoints_JSON_input_file") +args = parser.parse_args() +cfgfile = args.cfgfile +config = ConfigObj(cfgfile) +action = args.action +tid = args.tid if args.tid else None +json_input = args.jsoninput if args.jsoninput else None + +osutils = os_lib.OSUtils(config) + + +def get_routers(tenant): + neutron = osutils.get_neutron_client_by_tenant(tenant[1]) + routers = neutron.list_routers()['routers'] + routers = [router for router in routers if router['tenant_id'] == tenant[0]] + router_list = [] + for router in routers: + if router['name'] != 'PHYSICAL_GLOBAL_ROUTER_ID': + router_list.append(router) + return router_list + + +def get_ports(tenant): + neutron = osutils.get_neutron_client_by_tenant(tenant[1]) + ports = neutron.list_ports()['ports'] + port_list = [port for port in ports if port['tenant_id'] == tenant[0]] + return port_list + + +def get_subnet_detail(tenant, subnet): + neutron = osutils.get_neutron_client_by_tenant(tenant[1]) + return neutron.show_subnet(subnet) + + +def get_instance_detail(tenant, instance): + nova = osutils.get_nova_client_by_tenant(tenant[1]) + return nova.servers.get(instance) + + +def get_floatingips(tenant): + neutron = osutils.get_neutron_client_by_tenant(tenant[1]) + return neutron.list_floatingips() + + +def get_router_ports(router, ports): + """ + Returns a list of router interface ports for a given router + """ + router_port_list = [] + for port in ports: + if (port['device_id'] == router['id'] and + port['device_owner'] == 'network:router_interface'): + router_port_list.append(port) + return router_port_list + + +def get_port_subnets(tenant, port): + """ + Using the subnets for a tenant, obtain IP for a given port + """ + subnet = get_subnet_detail(tenant, port['fixed_ips'][0]['subnet_id'])['subnet'] + return {'id': port['fixed_ips'][0]['subnet_id'], 'name': subnet['name'], + 'ip_address': port['fixed_ips'][0]['ip_address']} + + +def get_instance_ports(ports): + """ + Returns a list of instance interface ports + """ + instance_port_list = [] + for port in ports: + if port['device_owner'] == 'compute:nova': + instance_port_list.append(port) + return instance_port_list + + +def get_subnet_endpoints(tenant, ports, subnet): + """ + Returns a list of vm ips for a a given subnet + """ + endpoints = [] + for port in ports: + endpoint_detail = {} + if port['fixed_ips'][0]['subnet_id'] == subnet['id']: + device_id = port['device_id'].encode('unicode_escape') + ip = port['fixed_ips'][0]['ip_address'].encode('unicode_escape') + endpoint_detail['device_id'] = device_id + endpoint_detail['ip_address'] = ip + endpoints.append(endpoint_detail) + return endpoints + + +def get_endpoint_floatingips(tenant, ip): + """ + Returns floating ip for a given fixed-ip. + """ + endpoint = '' + for entry in get_floatingips(tenant)['floatingips']: + if entry['fixed_ip_address'] == ip: + endpoint = entry['floating_ip_address'] + return endpoint.encode('unicode_escape') + + +def get_default_icmp_contract(): + contract = [{'name': 'allow_icmp', + 'protocol': 'icmp', + 'port': 'None', + 'direction': 'in', + 'action': 'allow'}] + return contract + + +def get_default_tcp_contract(): + contract = [{'name': 'allow_ssh', + 'protocol': 'tcp', + 'port': '22', + 'direction': 'in', + 'action': 'allow'}] + return contract + + +def get_default_udp_contract(): + contract = [{'name': 'allow_udp', + 'protocol': 'udp', + 'port': 'None', + 'direction': 'in', + 'action': 'allow'}] + return contract + + +def get_traffic_testing_endpoint(src_tenant, dest_tenant, + src_eps, dest_eps, contract, test_type): + endpoint = {'src_tenant': src_tenant, + 'dest_tenant': dest_tenant, + 'src_eps': src_eps, + 'dest_eps': dest_eps, + 'contract': contract, + 'test_type': test_type} + return endpoint + + +def discover_endpoints(): + logger.info('Discovering Live Endpoints') + logger.info('Initialize OSUtils') + endpoints_list = [] + endpoint_list = [] + logger.info('Get tenant details') + tenants = osutils.get_tenants_list() + tenant_data = {} + routers = {} + for tenant in tenants.items(): + router_data = {} + # retrieve all rotuers for a tenant + routers = get_routers(tenant) + ports = get_ports(tenant) + instance_ports = get_instance_ports(ports) + for router in routers: + router_detail = {} + router_detail['id'] = router['id'] + router_detail['ports'] = get_router_ports(router, ports) + subnets = [] + instance_list = [] + for port in router_detail['ports']: + subnets.append(get_port_subnets(tenant, port)) + router_detail['subnets'] = subnets + for subnet in router_detail['subnets']: + if subnet['id'] != '': + endpoint_detail = get_subnet_endpoints(tenant, instance_ports, subnet) + ip_list = [] + endpoint_list = [] + subnet['instances'] = endpoint_detail + for entry in endpoint_detail: + server = get_instance_detail(tenant, entry['device_id']) + if entry['device_id'] not in instance_list and \ + server.status == 'ACTIVE': + instance_list.append(entry['device_id']) + ip_list.append(entry['ip_address']) + endpoint_list.append({'instances': instance_list, + 'endpoints': ip_list}) + subnet['endpoints'] = ip_list + router_data[router['name'].encode('unicode_escape')] = router_detail + tenant_data[tenant[1]] = router_data + # print tenant_data + + test_method = config['traffic']['test_method'] + contract = [] + if 'icmp' in test_method or len(test_method) == 0: + contract.append(get_default_icmp_contract()[0]) + if 'tcp' in test_method: + contract.append(get_default_tcp_contract()[0]) + if 'udp' in test_method: + contract.append(get_default_udp_contract()[0]) + tenant = config['tenants']['tenants'] + pdb.set_trace() + if config['traffic']['type'] == 'intra-tenant' or config['traffic']['type'] == 'all': + # intra-tenant + for tenant in tenants.items(): + current_router = {} + current_net = {} + routers = get_routers(tenant) + for route in routers: + rsubnets = tenant_data[tenant[1]][route['name']]['subnets'] + if len(rsubnets) > 1: + current_net = {} + for entry in rsubnets: + tsrc = [] + tdest = [] + tsrc.append(entry['endpoints']) + for nextep in rsubnets: + if entry['name'] != nextep['name']: + tdest.append(nextep['endpoints']) + subnet_data = {'src_eps': [ep for eps in tsrc for ep in eps], + 'dest_eps': [ep for eps in tdest for ep in eps]} + current_net[entry['name']] = subnet_data + current_router[route['name']] = current_net + + for route in routers: + if route['name'] in current_router: + rsubnets = tenant_data[tenant[1]][route['name']]['subnets'] + for entry in rsubnets: + if entry['name'] in current_router[route['name']]: + net = current_router[route['name']][entry['name']] + if len(net['src_eps']) > 0 and \ + len(net['dest_eps']) > 0: + endpoints_list.append( + get_traffic_testing_endpoint(tenant[1], + tenant[1], + net['src_eps'], + net['dest_eps'], + contract, + 'intra-tenant')) + + if config['traffic']['type'] == 'inter-tenant' or config['traffic']['type'] == 'all': + # inter-tenant + for tenant in tenants.items(): + current_router = {} + current_net = {} + routers = get_routers(tenant) + for route in routers: + rsubnets = tenant_data[tenant[1]][route['name']]['subnets'] + if len(rsubnets) > 0: + current_net = {} + tsrc = [] + tdest = [] + for entry in rsubnets: + tsrc.append(entry['endpoints']) + src_eps = [ep for eps in tsrc for ep in eps] + for nexttenant in tenants.items(): + if tenant[1] != nexttenant[1]: + for nroute in tenant_data[nexttenant[1]]: + for entry in tenant_data[nexttenant[1]][nroute]['subnets']: + if len(rsubnets) > 0: + for ip in entry['endpoints']: + floating_ip = get_endpoint_floatingips(nexttenant, ip) + if floating_ip != '': + tdest.append(floating_ip) + dest_eps = tdest + if len(src_eps) > 0 and len(dest_eps) > 0: + endpoints_list.append( + get_traffic_testing_endpoint(tenant[1], + nexttenant[1], + src_eps, + dest_eps, + contract, + 'inter-tenant')) + + if config['traffic']['type'] == 'south-north' or config['traffic']['type'] == 'all': + # south-north + for tenant in tenants.items(): + current_router = {} + current_net = {} + routers = get_routers(tenant) + for route in routers: + rsubnets = tenant_data[tenant[1]][route['name']]['subnets'] + if len(rsubnets) > 0: + current_net = {} + tsrc = [] + for entry in rsubnets: + tsrc.append(entry['endpoints']) + src_eps = [ep for eps in tsrc for ep in eps] + if config['external_host']['host'] != '': + dest_eps = [config['external_host']['host']] + else: + dest_eps = [] + print "Please specify external host in the config" + if len(src_eps) > 0 and len(dest_eps) > 0: + endpoints_list.append( + get_traffic_testing_endpoint(tenant[1], + 'External Host', + src_eps, + dest_eps, + contract, + 'south-north')) + + if config['traffic']['type'] == 'north-south' or config['traffic']['type'] == 'all': + # north-south + for tenant in tenants.items(): + current_router = {} + current_net = {} + routers = get_routers(tenant) + for route in routers: + rsubnets = tenant_data[tenant[1]][route['name']]['subnets'] + if len(rsubnets) > 0: + current_net = {} + tdest = [] + if config['external_host']['host'] != '': + src_eps = [config['external_host']['host']] + else: + src_eps = [] + print "Please specify external host in the config" + for entry in rsubnets: + for ip in entry['endpoints']: + floating_ip = get_endpoint_floatingips(tenant, ip) + if floating_ip != '': + tdest.append(floating_ip) + dest_eps = tdest + if len(src_eps) > 0 and len(dest_eps) > 0: + endpoints_list.append( + get_traffic_testing_endpoint('External Host', + tenant[1], + src_eps, + dest_eps, + contract, + 'north-south')) + logger.debug("endpoints list = %s" % (pprint.pformat(endpoints_list))) + + + # #src_ip_list = ['192.168.61.131'] + # src_ip_list =['1.1.1.17'] + # dest_ip_list = ['192.168.61.229','192.168.61.132'] + # #dest_ip_list = ['192.168.61.129', '192.168.61.132'] + # dest_ip_list = ['10.1.25.128', '10.1.25.129', '10.1.25.130'] + # dest_ip_list = ['1.1.1.15','1.1.1.16','2.2.2.5','2.2.2.6'] + # contract = [{'name': 'allow_ssh', + # 'protocol': 'tcp', + # 'port': 22, + # 'direction': 'in', + # 'action': 'allow'}, + # {'name': 'allow_icmp', + # 'protocol': 'icmp', + # 'port': 'None', + # 'direction': 'in', + # 'action': 'allow'}] + + # endpoints = {'src_tenant': 'dummy_tenant', + # 'dest_tenant': 'dummy_tenant', + # 'src_eps': src_ip_list, + # 'dest_eps': dest_ip_list, + # 'contract': contract} + # endpoints_list = [endpoints] + + # # endpoints = {'src_tenant': 'sample', + # 'dest_tenant': 'sample', + # 'src_eps': src_ip_list, + # 'dest_eps': dest_ip_list, + # 'contract': contract} + # # endpoints_list = [endpoints] + + return endpoints_list + + +def main(): + logger.info('Start the program') + fmt = "%Y-%m-%d %H:%M:%S %Z%z" + start_time = datetime.now(timezone('US/Pacific')) + + if json_input: + # Passing preloaded Endpoints through JSON file + logger.info('Loading Endpoints from JSON Input File') + with open(json_input, 'r') as data_file: + endpoints_list = json.load(data_file) + logger.debug("JSON Input endpoints list = %s" % (pprint.pformat(endpoints_list))) + discovery_time = datetime.now(timezone('US/Pacific')) + else: + # Discovering Endpoints from Live Openstack setup + endpoints_list = discover_endpoints() + discovery_time = datetime.now(timezone('US/Pacific')) + + # Initiating Traffic Test on the target + if tid: + remote_libs.start_task(config, endpoints_list, action, tid) + else: + remote_libs.start_task(config, endpoints_list, action) + + # Traffic Test Summary + now_time = datetime.now(timezone('US/Pacific')) + print "-"*65 + print ("Traffic Test Time Summary :") + print "-"*65 + print ("Traffic Started Time:\t\t %s" % (start_time.strftime(fmt))) + print ("Endpoints Discovered Time:\t %s" % (discovery_time.strftime(fmt))) + print ("Current Time:\t\t\t %s" % (now_time.strftime(fmt))) + if action == 'start': + stop_time = now_time + timedelta(seconds=int(config['traffic']['iperf_duration'])) + print ("Estimated Traffic Stop Time:\t %s" % (stop_time.strftime(fmt))) + print "Status:\t\t\t\t\tTraffic Started !!" + else: + print "Status:\t\t\t\t\tTraffic Stopped !!" + print "-"*65 + +if __name__ == '__main__': + main() diff --git a/ntt_tool/apps/cloud/openstackscripts/traffictest/myendpoint.json b/ntt_tool/apps/cloud/openstackscripts/traffictest/myendpoint.json new file mode 100644 index 0000000..068fe00 --- /dev/null +++ b/ntt_tool/apps/cloud/openstackscripts/traffictest/myendpoint.json @@ -0,0 +1,22 @@ +[ +{"contract": [{"action": "allow", + "direction": "in", + "name": "allow_icmp", + "port": "None", + "protocol": "icmp"}, + {"action": "allow", + "direction": "in", + "name": "allow_ssh", + "port": "22", + "protocol": "tcp"}, + {"action": "allow", + "direction": "in", + "name": "allow_udp", + "port": "None", + "protocol": "udp"}], + "dest_eps": ["2.2.2.3"], + "dest_tenant": "admin", + "src_eps": ["3.3.3.3","1.1.1.5"], + "src_tenant": "admin", + "test_type": "intra-tenant" + }] diff --git a/ntt_tool/apps/cloud/openstackscripts/traffictest/requirements.txt b/ntt_tool/apps/cloud/openstackscripts/traffictest/requirements.txt new file mode 100644 index 0000000..17fd141 --- /dev/null +++ b/ntt_tool/apps/cloud/openstackscripts/traffictest/requirements.txt @@ -0,0 +1,42 @@ +Babel==1.3 +cliff==1.13.0 +cmd2==0.6.8 +configobj==5.0.6 +configparser==3.3.0.0r2 +debtcollector==0.5.0 +ecdsa==0.13 +Fabric==1.10.2 +futures==3.0.3 +iso8601==0.1.10 +logging==0.4.9.6 +monotonic==0.2 +msgpack-python==0.4.6 +multiprocessing==2.6.2.1 +netaddr==0.7.15 +netifaces==0.10.4 +oslo.config==1.15.0 +oslo.i18n==2.1.0 +oslo.serialization==1.7.0 +oslo.utils==1.9.0 +paramiko==1.15.2 +pbr==1.3.0 +pexpect==3.3 +prettytable==0.7.2 +pudb==2015.3 +pycrypto==2.6.1 +Pygments==2.0.2 +pyparsing==2.0.3 +python-group-based-policy-client==0.9.2 +python-heatclient==0.6.0 +python-keystoneclient==1.6.0 +python-neutronclient==2.6.0 +python-novaclient==2.17.0 +python-swiftclient==2.4.0 +pytz==2015.4 +PyYAML==3.11 +requests==2.7.0 +simplejson==3.7.3 +six==1.9.0 +stevedore==1.6.0 +urwid==1.3.0 +wrapt==1.10.5 diff --git a/ntt_tool/apps/cloud/openstackscripts/traffictest/sample_endpoints.json b/ntt_tool/apps/cloud/openstackscripts/traffictest/sample_endpoints.json new file mode 100644 index 0000000..c30cbbb --- /dev/null +++ b/ntt_tool/apps/cloud/openstackscripts/traffictest/sample_endpoints.json @@ -0,0 +1,86 @@ +[ +{"contract": [{"action": "allow", + "direction": "in", + "name": "allow_icmp", + "port": "None", + "protocol": "icmp"}, + {"action": "allow", + "direction": "in", + "name": "allow_ssh", + "port": "22", + "protocol": "tcp"}, + {"action": "allow", + "direction": "in", + "name": "allow_udp", + "port": "None", + "protocol": "udp"}], + "dest_eps": ["2.2.2.2"], + "dest_tenant": "admin", + "src_eps": ["1.1.1.2", "1.1.1.4"], + "src_tenant": "admin", + "test_type": "intra-tenant" + }, + {"contract": [{"action": "allow", + "direction": "in", + "name": "allow_icmp", + "port": "None", + "protocol": "icmp"}, + {"action": "allow", + "direction": "in", + "name": "allow_ssh", + "port": "22", + "protocol": "tcp"}, + {"action": "allow", + "direction": "in", + "name": "allow_udp", + "port": "None", + "protocol": "udp"}], + "dest_eps": ["10.1.65.107"], + "dest_tenant": "traffic_test", + "src_eps": ["1.1.1.2", "1.1.1.4", "2.2.2.2"], + "src_tenant": "admin", + "test_type": "inter-tenant" + }, + {"contract": [{"action": "allow", + "direction": "in", + "name": "allow_icmp", + "port": "None", + "protocol": "icmp"}, + {"action": "allow", + "direction": "in", + "name": "allow_ssh", + "port": "22", + "protocol": "tcp"}, + {"action": "allow", + "direction": "in", + "name": "allow_udp", + "port": "None", + "protocol": "udp"}], + "dest_eps": ["10.1.25.129"], + "dest_tenant": "External Host", + "src_eps": ["1.1.1.2", "1.1.1.4", "2.2.2.2"], + "src_tenant": "admin", + "test_type": "south-north" + }, + {"contract": [{"action": "allow", + "direction": "in", + "name": "allow_icmp", + "port": "None", + "protocol": "icmp"}, + {"action": "allow", + "direction": "in", + "name": "allow_ssh", + "port": "22", + "protocol": "tcp"}, + {"action": "allow", + "direction": "in", + "name": "allow_udp", + "port": "None", + "protocol": "udp"}], + "dest_eps": ["10.1.65.102"], + "dest_tenant": "admin", + "src_eps": ["10.1.25.129"], + "src_tenant": "External Host", + "test_type": "north-south" + } +] diff --git a/ntt_tool/apps/cloud/openstackscripts/traffictest/scripts/__init__.py b/ntt_tool/apps/cloud/openstackscripts/traffictest/scripts/__init__.py new file mode 100644 index 0000000..e69de29 diff --git a/ntt_tool/apps/cloud/openstackscripts/traffictest/scripts/get_iperf_tcp_statistics.py b/ntt_tool/apps/cloud/openstackscripts/traffictest/scripts/get_iperf_tcp_statistics.py new file mode 100644 index 0000000..e153a3f --- /dev/null +++ b/ntt_tool/apps/cloud/openstackscripts/traffictest/scripts/get_iperf_tcp_statistics.py @@ -0,0 +1,85 @@ +import json +import os +import re +import sys + + +fileid = sys.argv[1] +filedir = os.getcwd() + + +def get_list_of_filenames(): + file_list = [] + for file in os.listdir(filedir): + if file.startswith("tcptesttrafficclient") and file.endswith("%s.txt" % (fileid)): + file_list.append(file) + return file_list + + +def process_files(file_list): + out = {} + for file in file_list: + out[file] = get_test_results(file) + return out + + +def get_test_results(file): + """ + For a given test output file, return a tuple of the following format + (bandwidth_loss dict wth keys interval_time, transferred, bandwidth, + jitter, loss_datagram, total_datagram, loss_percent) + """ + + bandwidth_stats = \ + {'interval_time': '', # NOQA + 'transferred': '', # NOQA + 'bandwidth': '', # NOQA + 'retr': ''} # NOQA + reportflag = False + f = open(file, 'r') + for line in f: + if "- - " in line: + reportflag = True + if "[ ID]" in line and reportflag: + report = f.next() + report_data = report.split(']')[1].split(' ') + # also want packets transmitted, packets received, % packet loss + if str(report_data[2]) == 'sec': + interval_time = str(report_data[1]) + " " + str(report_data[2]) + transferred = str(report_data[3]) + bandwidth = str(report_data[4]) + if report_data[5] == '': + retr = str(report_data[5]) + str(report_data[6]) # NOQA + else: + retr = str(report_data[5]) + else: + interval_time = str(report_data[1]) + transferred = str(report_data[2]) + bandwidth = str(report_data[3]) + if report_data[4] == '': + retr = str(report_data[4]) + str(report_data[5]) # NOQA + else: + retr = str(report_data[4]) + + bandwidth_stats = \ + {'interval_time': interval_time, # NOQA + 'transferred': transferred, # NOQA + 'bandwidth': bandwidth, # NOQA + 'retr': retr} # NOQA + + test_results = {'bandwidth_stats': bandwidth_stats} + + return test_results + + +def main(): + file_list = get_list_of_filenames() + script_output = process_files(file_list) + json_output = json.JSONEncoder().encode(script_output) + # sample output + # {'test.txt': ({'packet_loss': '0%'}, + # {'rtt_min': '4.3', 'rtt_avg': '5.5', 'rtt_max': '6.3'})} + print json_output + +if __name__ == '__main__': + main() diff --git a/ntt_tool/apps/cloud/openstackscripts/traffictest/scripts/get_iperf_udp_statistics.py b/ntt_tool/apps/cloud/openstackscripts/traffictest/scripts/get_iperf_udp_statistics.py new file mode 100644 index 0000000..710c269 --- /dev/null +++ b/ntt_tool/apps/cloud/openstackscripts/traffictest/scripts/get_iperf_udp_statistics.py @@ -0,0 +1,84 @@ +import json +import os +import re +import sys + + +fileid = sys.argv[1] +filedir = os.getcwd() + + +def get_list_of_filenames(): + file_list = [] + for file in os.listdir(filedir): + if file.startswith("udptesttrafficclient") and file.endswith("%s.txt" % (fileid)): + file_list.append(file) + return file_list + + +def process_files(file_list): + out = {} + for file in file_list: + out[file] = get_test_results(file) + return out + + +def get_test_results(file): + """ + For a given test output file, return a tuple of the following format + (bandwidth_loss dict wth keys interval_time, transferred, bandwidth, + jitter, loss_datagram, total_datagram, loss_percent) + """ + bandwidth_stats = \ + {'interval_time': '', # NOQA + 'transferred': '', # NOQA + 'bandwidth': '', # NOQA + 'jitter': '', # NOQA + 'loss_datagram': '', # NOQA + 'total_datagram': '', # NOQA + 'loss_percent': ''} + + reportflag = False + f = open(file, 'r') + + for line in f: + if "- - " in line: + reportflag = True + if "[ ID]" in line and reportflag: + report = f.next() + report_data = report.split(']')[1].split(' ') + # also want packets transmitted, packets received, % packet loss + datagram = report.split('/')[2] + total_datagram = datagram.split('(')[0] + loss_percent = datagram.split('(')[1].split('%')[0] + if str(report_data[2]) == 'sec': + interval_time =str(report_data[1]) + " " + str(report_data[2]) + loss_datagram = report_data[6].split('/')[0] + else: + interval_time =str(report_data[1]) + loss_datagram = report_data[5].split('/')[0] + bandwidth_stats = \ + {'interval_time': interval_time, # NOQA + 'transferred': str(report_data[3]), # NOQA + 'bandwidth': str(report_data[4]), # NOQA + 'jitter': str(report_data[5]), # NOQA + 'loss_datagram': str(loss_datagram), # NOQA + 'total_datagram': str(total_datagram), # NOQA + 'loss_percent': str(loss_percent)} + + test_results = {'bandwidth_stats': bandwidth_stats} + + return test_results + + +def main(): + file_list = get_list_of_filenames() + script_output = process_files(file_list) + json_output = json.JSONEncoder().encode(script_output) + # sample output + # {'test.txt': ({'packet_loss': '0%'}, + # {'rtt_min': '4.3', 'rtt_avg': '5.5', 'rtt_max': '6.3'})} + print json_output + +if __name__ == '__main__': + main() diff --git a/ntt_tool/apps/cloud/openstackscripts/traffictest/scripts/get_ping_statistics.py b/ntt_tool/apps/cloud/openstackscripts/traffictest/scripts/get_ping_statistics.py new file mode 100644 index 0000000..7e6809b --- /dev/null +++ b/ntt_tool/apps/cloud/openstackscripts/traffictest/scripts/get_ping_statistics.py @@ -0,0 +1,78 @@ +import json +import os +import re +import sys + + +fileid = sys.argv[1] +filedir = os.getcwd() + + +def get_list_of_filenames(): + file_list = [] + for file in os.listdir(filedir): + if file.startswith("test") and file.endswith("%s.txt" % (fileid)): + file_list.append(file) + return file_list + + +def process_files(file_list): + out = {} + for file in file_list: + out[file] = get_test_results(file) + return out + + +def get_test_results(file): + """ + For a given test output file, return a tuple of the following format + (packet_loss dict wth keys packet_loss, + rtt dictionary with keys, rtt_min, rtt_avg, rtt_max) + """ + f = open(file, 'r') + + rtt_regex = ".*= (?P[0-9]+\.[0-9]+)/" \ + "(?P[0-9]+\.[0-9]+)/" \ + "(?P[0-9]+\.[0-9]+) ms" + + packet_loss_regex = "(?P[0-9]+) packets transmitted, " \ + "(?P[0-9]+) packets received," \ + " (?P[0-9]+)%.*" + + for line in f: + if "loss" in line: + # also want packets transmitted, packets received, % packet loss + packet_stats_match = re.match(packet_loss_regex, line) + + packet_stats = \ + {'packets_transmitted': int(packet_stats_match.group('packets_sent')), # NOQA + 'packets_received': int(packet_stats_match.group('packets_received')), # NOQA + 'packet_loss': int(packet_stats_match.group('packet_loss'))} + + if "round-trip" in line: + match_results = re.match(rtt_regex, line) + if (match_results is not None): + rtt_min = match_results.group('rtt_min') + rtt_avg = match_results.group('rtt_avg') + rtt_max = match_results.group('rtt_max') + + rtt = {'rtt_min': rtt_min, + 'rtt_avg': rtt_avg, + 'rtt_max': rtt_max} + + test_results = {'packet_stats': packet_stats, 'rtt': rtt} + + return test_results + + +def main(): + file_list = get_list_of_filenames() + script_output = process_files(file_list) + json_output = json.JSONEncoder().encode(script_output) + # sample output + {'test.txt': ({'packet_loss': '0%'}, + {'rtt_min': '4.3', 'rtt_avg': '5.5', 'rtt_max': '6.3'})} + print json_output + +if __name__ == '__main__': + main() diff --git a/ntt_tool/apps/cloud/openstackscripts/traffictest/traffictest.py b/ntt_tool/apps/cloud/openstackscripts/traffictest/traffictest.py new file mode 100644 index 0000000..c677b3a --- /dev/null +++ b/ntt_tool/apps/cloud/openstackscripts/traffictest/traffictest.py @@ -0,0 +1,91 @@ +import time +from ntt_tool.apps.cloud.models import Traffic, Endpoint +from utils import * +from ntt_tool.apps.cloud.openstackscripts.traffictest.libs import traf_tester + + +class TrafficTest(object): + + def __init__(self, traffic_id): + self.traffic = Traffic.objects.get(pk=traffic_id) + + def run_test(self, traffic_test_duration=1): + endpoints_list = self.generate_endpoints_contract_list() + setup_config = self.generate_setup_config() + traf_tester.start_task(setup_config, endpoints_list, "start", "_".join(self.traffic.name.split())) + time.sleep(60 * traffic_test_duration) + test_result = traf_tester.start_task(setup_config, endpoints_list, "stop", "_".join(self.traffic.name.split())) + return test_result + + def stop_test(self): + pass + + def generate_endpoints_contract_list(self): + endpoint_contract_configs = [] + if self.traffic.test_type == 'intra-tenant': + subnet_endpoints_map = {} + endpoints = Endpoint.objects.filter(traffic_id=self.traffic.id).filter(is_selected=True) + for endpoint in endpoints: + subnet = endpoint.network.subnets.first() + if subnet.subnet_name not in subnet_endpoints_map: + subnet_endpoints_map[subnet.subnet_name] = { + 'endpoints': [], + 'id': subnet.subnet_id, + 'name': subnet.subnet_name + } + subnet_endpoints_map[subnet.subnet_name]['endpoints'].append(endpoint.ip_address) + + net = {} + for entry in subnet_endpoints_map.values(): + tsrc = [] + tdest = [] + tsrc.append(entry['endpoints']) + for nextep in subnet_endpoints_map.values(): + if entry['name'] != nextep['name']: + tdest.append(nextep['endpoints']) + subnet_data = { + 'src_eps': [ep for eps in tsrc for ep in eps], + 'dest_eps': [ep for eps in tdest for ep in eps] + } + net[entry['name']] = subnet_data + + for k, src_dst_eps in net.iteritems(): + config = { + 'contract': [], + 'test_type': self.traffic.test_type, + 'src_tenant': [x.tenant_name for x in self.traffic.tenants.all()], + 'dest_eps': src_dst_eps.get('dest_eps'), + 'src_eps': src_dst_eps.get('src_eps') + } + for test_method in self.traffic.test_method.split(','): + config.get('contract').append(test_method_contracts(test_method)) + config['dest_tenant'] = config['src_tenant'] + endpoint_contract_configs.append(config) + return endpoint_contract_configs + + def generate_setup_config(self): + setup_config = {} + cloud = self.traffic.cloud + setup_config['default'] = { + 'keystone_auth_url': cloud.keystone_auth_url, + 'keystone_user': cloud.keystone_user, + 'keystone_password': cloud.keystone_password, + 'keystone_tenant_name': cloud.keystone_tenant_name + } + setup_config['traffic'] = { + 'allowed_delta_percentage': self.traffic.allowed_delta_percentage, + 'test_results_path': self.traffic.test_result_path, + 'number_of_workers': self.traffic.number_of_workers, + 'remote_user': self.traffic.remote_user, + 'remote_pass': self.traffic.remote_pass, + 'test_method': self.traffic.test_method.split(','), + 'iperf_duration': self.traffic.iperf_duration, + 'type': self.traffic.test_type + } + setup_config['tenants'] = {'tenants': []} + setup_config['tenant_ssh_gateway'] = {} + for tenant in self.traffic.tenants.all(): + setup_config['tenants']['tenants'].append(tenant.tenant_name) + setup_config['tenant_ssh_gateway'][tenant.tenant_name] = self.traffic.ssh_gateway + setup_config['external_host'] = {'host': self.traffic.external_host} + return setup_config diff --git a/ntt_tool/apps/cloud/openstackscripts/traffictest/utils.py b/ntt_tool/apps/cloud/openstackscripts/traffictest/utils.py new file mode 100644 index 0000000..7aae6ee --- /dev/null +++ b/ntt_tool/apps/cloud/openstackscripts/traffictest/utils.py @@ -0,0 +1,40 @@ +def get_default_icmp_contract(): + contract = { + 'name': 'allow_icmp', + 'protocol': 'icmp', + 'port': 'None', + 'direction': 'in', + 'action': 'allow' + } + return contract + + +def get_default_tcp_contract(): + contract = { + 'name': 'allow_ssh', + 'protocol': 'tcp', + 'port': '22', + 'direction': 'in', + 'action': 'allow' + } + return contract + + +def get_default_udp_contract(): + contract = { + 'name': 'allow_udp', + 'protocol': 'udp', + 'port': 'None', + 'direction': 'in', + 'action': 'allow' + } + return contract + + +def test_method_contracts(test_method): + function_dict = { + "icmp": get_default_icmp_contract, + "tcp": get_default_tcp_contract, + "udp": get_default_udp_contract + } + return function_dict[test_method]() diff --git a/ntt_tool/apps/cloud/openstackscripts/user.txt b/ntt_tool/apps/cloud/openstackscripts/user.txt new file mode 100644 index 0000000..294cbc2 --- /dev/null +++ b/ntt_tool/apps/cloud/openstackscripts/user.txt @@ -0,0 +1,7 @@ +#cloud-config +chpasswd: + list: | + root:ubuntu + ubuntu:ubuntu + expire: False +ssh_pwauth: True \ No newline at end of file diff --git a/ntt_tool/apps/cloud/serializers.py b/ntt_tool/apps/cloud/serializers.py index edbd1b7..ad1628a 100644 --- a/ntt_tool/apps/cloud/serializers.py +++ b/ntt_tool/apps/cloud/serializers.py @@ -3,36 +3,80 @@ class CloudSerializer(serializers.ModelSerializer): - class Meta: model = Cloud - exclude = ('updated_on',) + exclude = ('creator', 'updated_on') + +class SubnetSerializer(serializers.ModelSerializer): + class Meta: + model = Subnet -class CloudTrafficListSerializer(serializers.ModelSerializer): + +class NetworkSerializer(serializers.ModelSerializer): + subnets = SubnetSerializer(many=True, read_only=True) class Meta: - model = CloudTraffic + model = Network + exclude = ('creator', 'created_on', 'updated_on',) -class CloudTrafficRetrieveSerializer(serializers.ModelSerializer): +class RouterSerializer(serializers.ModelSerializer): + class Meta: + model = Router + exclude = ('creator', 'created_on', 'updated_on',) + +class TenantListSerializer(serializers.ModelSerializer): class Meta: - model = CloudTraffic - depth = 1 - exclude = ('created_on', 'updated_on') + model = Tenant + fields = ('id', 'tenant_name', 'enabled', 'description') -class CloudTrafficSerializer(serializers.ModelSerializer): +class TenantSerializer(serializers.ModelSerializer): + networks = NetworkSerializer(many=True, read_only=True) class Meta: - model = CloudTraffic + model = Tenant + exclude = ('creator', 'created_on', 'updated_on',) + +class TrafficListSerializer(serializers.ModelSerializer): + class Meta: + model = Traffic + exclude = ('creator', 'updated_on',) -class CloudTrafficTenantSerializer(serializers.ModelSerializer): +class TrafficSerializer(serializers.ModelSerializer): class Meta: - model = CloudTrafficTenants + model = Traffic + exclude = ('creator', 'updated_on',) +class TrafficNetworksMapSerializer(serializers.ModelSerializer): + class Meta: + model = TrafficNetworksMap + fields = ("network", "ip_range_start", "ip_range_end", "endpoint_count") + +class TrafficRetrieveSerializer(serializers.ModelSerializer): + tenants = TenantSerializer(many=True, required=False) + selected_networks = serializers.SerializerMethodField() + + class Meta: + model = Traffic + exclude = ('creator', 'updated_on',) + + def get_selected_networks(self, traffic): + objs = traffic.selected_networks.through.objects.filter(traffic=traffic) + serializer = TrafficNetworksMapSerializer(objs, many=True) + return serializer.data + + +class EndpointSerializer(serializers.ModelSerializer): + network_id = serializers.IntegerField(source='network.id', read_only=True) + network_name = serializers.CharField(source="network.network_name", read_only=True) + + class Meta: + model = Endpoint + fields = ('id', 'network_id', 'network_name', 'endpoint_id', 'name', 'status', 'ip_address', 'is_selected') \ No newline at end of file diff --git a/ntt_tool/apps/cloud/urls.py b/ntt_tool/apps/cloud/urls.py index 91db229..28b4e62 100644 --- a/ntt_tool/apps/cloud/urls.py +++ b/ntt_tool/apps/cloud/urls.py @@ -4,10 +4,9 @@ router = routers.SimpleRouter() router.register(r'cloud', CloudViewSet) -router.register(r'cloudtraffic', CloudTrafficViewSet) -router.register(r'cloudtraffictenant', CloudTrafficTenantsViewSet) +router.register(r'tenant', TenantViewSet) +router.register(r'traffic', TrafficViewSet) -urlpatterns = [ - # url('^cloudtraffics/(?P.+)/$', CloudTrafficListView.as_view()), -] + +urlpatterns = [] urlpatterns += router.urls diff --git a/ntt_tool/apps/cloud/views.py b/ntt_tool/apps/cloud/views.py index 17c21b9..e844dfd 100644 --- a/ntt_tool/apps/cloud/views.py +++ b/ntt_tool/apps/cloud/views.py @@ -1,29 +1,65 @@ -from rest_framework import viewsets -from rest_framework import generics +import json from rest_framework import status +from rest_framework import viewsets +from rest_framework.decorators import list_route, detail_route from rest_framework.permissions import IsAuthenticated from rest_framework.response import Response from rest_framework_jwt.authentication import JSONWebTokenAuthentication from models import * from serializers import * +from openstackscripts.keystoneclientutils import KeystoneClientUtils +from openstackscripts.novaclientutils import NovaClientUtils +from openstackscripts.tenantnetworkdiscovery import * +from openstackscripts.credentials import * +from openstackscripts.endpoints import DiscoverEndpoints, LaunchEndpoints +from openstackscripts.traffictest.traffictest import TrafficTest + +import os +import pickle +from django.core.mail import EmailMultiAlternatives +from django.template import Context +from django.template.loader import get_template +from django.template.loader import render_to_string + + +from django.core.mail import EmailMessage +from email.mime.text import MIMEText +from django.conf import settings + class CloudViewSet(viewsets.ModelViewSet): queryset = Cloud.objects.all() serializer_class = CloudSerializer - authentication_classes = [JSONWebTokenAuthentication] - permission_classes = [IsAuthenticated] + authentication_classes = (JSONWebTokenAuthentication,) + permission_classes = (IsAuthenticated,) + + def perform_create(self, serializer): + serializer.save(creator=self.request.user) -class CloudTrafficViewSet(viewsets.ModelViewSet): - queryset = CloudTraffic.objects.all() - serializer_class = CloudTrafficSerializer +class TenantViewSet(viewsets.ModelViewSet): + queryset = Tenant.objects.all() + serializer_class = TenantSerializer authentication_classes = (JSONWebTokenAuthentication,) permission_classes = (IsAuthenticated,) + action_serializers = { + 'retrieve': TenantSerializer, + 'list': TenantSerializer, + 'create': TenantSerializer, + 'update': TenantSerializer, + } + + def get_serializer_class(self): + if hasattr(self, 'action_serializers'): + if self.action in self.action_serializers: + return self.action_serializers[self.action] + return super(TrafficViewSet, self).get_serializer_class() def list(self, request, *args, **kwargs): cloud_id = self.request.GET.get("cloud_id") - queryset = self.filter_queryset(CloudTraffic.objects.filter(cloud_id=cloud_id)) + queryset = self.filter_queryset( + Tenant.objects.filter(cloud_id=cloud_id)) page = self.paginate_queryset(queryset) if page is not None: @@ -33,16 +69,54 @@ def list(self, request, *args, **kwargs): serializer = self.get_serializer(queryset, many=True) return Response(serializer.data) + @list_route(methods=["get"], url_path="discover/(?P[^/.]+)") + def discover(self, request, cloud_id=None): + cloud = None + try: + cloud = Cloud.objects.get(pk=cloud_id, creator=request.user) + except Cloud.DoesNotExist: + return Response(status=status.HTTP_404_NOT_FOUND) + + keystone_utils = KeystoneClientUtils(**get_credentials(cloud)) + tenants = keystone_utils.get_tenants() -class CloudTrafficTenantsViewSet(viewsets.ModelViewSet): - queryset = CloudTrafficTenants.objects.all() - serializer_class = CloudTrafficTenantSerializer + network_subnets_discovery = NetworkSubnetDiscovery(**get_credentials(cloud)) + tenant_networks_routers = network_subnets_discovery.get_networks_and_subnets( + request.user, + cloud_id, + tenants + ) + return Response(tenant_networks_routers) + + +class TrafficViewSet(viewsets.ModelViewSet): + queryset = Traffic.objects.all() + serializer_class = TrafficSerializer authentication_classes = (JSONWebTokenAuthentication,) permission_classes = (IsAuthenticated,) + action_serializers = { + 'retrieve': TrafficRetrieveSerializer, + 'list': TrafficListSerializer, + 'create': TrafficSerializer, + 'update': TrafficSerializer, + } + + def get_serializer_class(self): + """ + Overriding method to get custom serializer classes based on request method. + """ + if hasattr(self, 'action_serializers'): + if self.action in self.action_serializers: + return self.action_serializers[self.action] + return super(TrafficViewSet, self).get_serializer_class() def list(self, request, *args, **kwargs): - traffic_id = self.request.GET.get("traffic_id") - queryset = self.filter_queryset(CloudTrafficTenants.objects.filter(cloud_traffic_id=traffic_id)) + """ + Overriding list method to write custom queryset for retriveing traffic related to cloud. + """ + cloud_id = self.request.GET.get("cloud_id") + queryset = self.filter_queryset( + Traffic.objects.filter(cloud_id=cloud_id)) page = self.paginate_queryset(queryset) if page is not None: @@ -52,24 +126,177 @@ def list(self, request, *args, **kwargs): serializer = self.get_serializer(queryset, many=True) return Response(serializer.data) - def create(self, request, *args, **kwargs): - tenant = CloudTrafficTenants() - tenant.cloud_traffic_id = request.data.get("cloud_traffic_id") - tenant.tenant_name = request.data.get("tenant_name") - tenant.ssh_gateway = request.data.get("ssh_gateway") - tenant.creator = request.user - tenant.save() - - serializer = CloudTrafficTenantSerializer(tenant) - headers = self.get_success_headers(serializer.data) - return Response(serializer.data, status=status.HTTP_201_CREATED, headers=headers) - - def update(self, request, *args, **kwargs): - import pdb - pdb.set_trace() - partial = kwargs.pop('partial', False) + def retrieve(self, request, *args, **kwargs): + """ + Overriding method to retriveing only traffic info for edit and get related info for view traffic. + """ instance = self.get_object() - serializer = self.get_serializer(instance, data=request.data, partial=partial) - serializer.is_valid(raise_exception=True) - self.perform_update(serializer) + if json.loads(request.GET.get('get_related_data', 'false')): + serializer = self.get_serializer(instance) + else: + serializer = TrafficSerializer(instance) + return Response(serializer.data) + + def perform_create(self, serializer): + """ + Overriding method to provide selected tenants, creator and cloud_id at the time of creating traffic. + """ + serializer.save( + tenants=self.request.data.getlist("tenants[]"), + creator=self.request.user, + cloud_id=self.request.data.get("cloud_id") + ) + + @detail_route(methods=["get"], url_path="select/tenant/(?P[-\w]+)") + def select_tenant(self, request, pk=None, tenant_id=None): + """ + Method provides the functionality to select a tenant for traffic. + """ + with transaction.atomic(): + tenant = Tenant.objects.filter(tenant_id=tenant_id).get() + + traffic = Traffic.objects.get(pk=pk) + traffic.tenants.clear() + traffic.selected_networks.clear() + traffic.tenants.add(tenant) + traffic.save() + TrafficNetworksMap.objects.filter(traffic=traffic).delete() + + serializer = TenantSerializer(tenant) + return Response(serializer.data) + + @detail_route(methods=["get"], url_path="select/network") + def select_network(self, request, pk=None): + """ + Method to select networks for a selected tenant of a traffic. + """ + with transaction.atomic(): + traffic = Traffic.objects.get(pk=pk) + if json.loads(request.GET.get("is_selected")): + network = Network.objects.get(pk=request.GET.get("network_id")) + traffic_network_map, created = TrafficNetworksMap.objects.get_or_create(traffic=traffic, network=network) + traffic_network_map.save() + + # Subnet id of the first subnet of a network + subnet_obj = network.subnets.all()[0] + neutron_utils = NeutronClientUtils(**get_credentials(traffic.cloud)) + subnet = neutron_utils.show_subnet(subnet_id=subnet_obj.subnet_id) + subnet_obj.allocation_pool_start = subnet.get("allocation_pools")[0].get("start") + subnet_obj.allocation_pool_end = subnet.get("allocation_pools")[0].get("end") + subnet_obj.save() + + serializer = SubnetSerializer(subnet_obj) + return Response(serializer.data) + + TrafficNetworksMap.objects.filter(traffic=traffic)\ + .filter(network_id=request.GET.get("network_id")).delete() + return Response(status.HTTP_200_OK) + + @detail_route(methods=["get"], url_path="endpoints") + def endpoints(self, request, pk=None): + """ + Lists all the endpoints belongs to a specific network of a traffic. + """ + endpoints = Endpoint.objects.filter(traffic_id=pk) + serializer = EndpointSerializer(endpoints, many=True) + return Response(serializer.data) + + @detail_route(methods=["post"], url_path="endpoints/discover") + def discover_endpoints(self, request, pk=None): + """ + Discovers the endpoints from openstack environment for selected networks with given IP range of a traffic. + """ + response = [] + selected_networks = [] + for selected_range in json.loads(request.data.get("json", '[]')): + network_id = selected_range.get("network_id") + selected_networks.append(network_id) + filters = { + "traffic_id": pk, + "network_id": network_id + } + obj = TrafficNetworksMap.objects.filter(**filters).get() + obj.ip_range_start = selected_range.get("ip_range_start") + obj.ip_range_end = selected_range.get("ip_range_end") + obj.save() + + endpoint_discovery = DiscoverEndpoints(pk, network_id) + endpoints = endpoint_discovery.get_endpoints(obj.ip_range_start, obj.ip_range_end) + response.extend(endpoints) + + # Deleting endpoints for unselected networks (If selected previously and unselected in current request) + Endpoint.objects.filter(traffic_id=pk).exclude(network_id__in=selected_networks).delete() + + serializer = EndpointSerializer(response, many=True) + return Response(serializer.data) + + @detail_route(methods=["post"], url_path="endpoints/launch") + def launch_endpoints(self, request, pk=None): + response = [] + traffic = Traffic.objects.get(pk=pk) + nova_credentials = get_nova_credentials(traffic.cloud) + + selected_networks = [] + for selected_item in json.loads(request.data.get("json", '[]')): + network_id = selected_item.get("network_id") + selected_networks.append(network_id) + filters = { + "traffic_id": pk, + "network_id": network_id + } + obj, created = TrafficNetworksMap.objects.get_or_create(**filters) + obj.endpoint_count = selected_item.get("endpoint_count") + obj.save() + + launch_endpoint = LaunchEndpoints(traffic, network_id, **nova_credentials) + endpoints = launch_endpoint.launch(selected_item.get("endpoint_count")) + response.extend(endpoints) + + # Deleting endpoints for unselected networks (If selected previously and unselected in current request) + Endpoint.objects.filter(traffic_id=pk).exclude(network_id__in=selected_networks).delete() + + serializer = EndpointSerializer(response, many=True) + return Response(serializer.data) + + @detail_route(methods=["get"], url_path="endpoints/select") + def select_endpoint(self, request, pk=None): + filters = { + "traffic_id": pk, + "endpoint_id": request.GET.get("endpoint_id") + } + endpoint = Endpoint.objects.filter(**filters).get(pk=request.GET.get("endpoint_pk")) + endpoint.is_selected = json.loads(request.GET.get("is_selected")) + endpoint.save() + + serializer = EndpointSerializer(endpoint) return Response(serializer.data) + + @detail_route(methods=["get"], url_path="run/test") + def run_traffic_test(self, request, pk=None): + traffic_test = TrafficTest(pk) + test_result = traffic_test.run_test() + return Response(test_result) + + @detail_route(methods=["get"], url_path="email/report") + def email_report(self, request, pk=None): + context = None + file_path = os.path.join(settings.MEDIA_ROOT, "traffic-test-report.txt") + with open(file_path, 'r') as f: + test_result = pickle.load(f) # load file content as mydict + context = Context({ + 'test_result': test_result, + 'reciever_name': request.user.username, + }) + + sender = settings.EMAIL_HOST_USER + message = get_template('email_templates/traffic_test_report.html').render(context) + msg = EmailMessage("Traffic Test Report", message, to=['abdulgaffar@onecloudinc.com', request.user.email], from_email=sender) + msg.content_subtype = 'html' + + f = file(file_path, 'rb') + attachment = MIMEText(f.read()) + attachment.add_header('Content-Disposition', 'attachment', filename="traffic-test-report.txt") + msg.attach(attachment) + msg.send() + return Response(True) + diff --git a/ntt_tool/media/osimages/ubuntu.img b/ntt_tool/media/osimages/ubuntu.img new file mode 100644 index 0000000..4264222 --- /dev/null +++ b/ntt_tool/media/osimages/ubuntu.img @@ -0,0 +1,3 @@ +version https://git-lfs.github.com/spec/v1 +oid sha256:32d142beddf51b7439eebb319b9185458e9823f75355bf9b60531186e8030657 +size 259260928 diff --git a/ntt_tool/media/traffic-test-report.txt b/ntt_tool/media/traffic-test-report.txt new file mode 100644 index 0000000..9ece872 --- /dev/null +++ b/ntt_tool/media/traffic-test-report.txt @@ -0,0 +1,209 @@ +(dp0 +S'udp' +p1 +(lp2 +(dp3 +S'status' +p4 +S'Success' +p5 +sS'jitter' +p6 +V0.058 ms +p7 +sS'dest_ep' +p8 +V3.3.3.40 +p9 +sS'bandwidth' +p10 +V1.04 Mbits/sec +p11 +sS'bandwidth_loss_percent' +p12 +V0 % +p13 +sS'src_tenant' +p14 +(lp15 +Vntt-tenant_1 +p16 +asS'src_ep' +p17 +V2.2.2.42 +p18 +sS'loss_datagram' +p19 +V0 +p20 +sS'dest_tenant' +p21 +g15 +sS'interval_time' +p22 +V 0.00-10.00 sec +p23 +sS'transferred' +p24 +V1.24 MBytes +p25 +sS'total_datagram' +p26 +V159 +p27 +sasS'icmp' +p28 +(lp29 +(dp30 +g4 +g5 +sS'rtt_avg' +p31 +V8.6 +p32 +sg8 +V3.3.3.39 +p33 +sS'rtt_max' +p34 +V1004.8 +p35 +sg14 +(lp36 +Vntt-tenant_1 +p37 +asg17 +g18 +sS'rtt_min' +p38 +V0.2 +p39 +sS'packets_received' +p40 +I1908 +sS'packets_transmitted' +p41 +I1908 +sS'packet_loss_percent' +p42 +I0 +sg21 +g36 +sa(dp43 +g4 +g5 +sg31 +V4.9 +p44 +sg8 +V3.3.3.40 +p45 +sg34 +V1004.5 +p46 +sg14 +g36 +sg17 +g18 +sg38 +V0.1 +p47 +sg40 +I1909 +sg41 +I1910 +sg42 +I1 +sg21 +g36 +sa(dp48 +g4 +g5 +sg31 +V6.7 +p49 +sg8 +V2.2.2.42 +p50 +sg34 +V1004.4 +p51 +sg14 +(lp52 +Vntt-tenant_1 +p53 +asg17 +V3.3.3.39 +p54 +sg38 +V0.2 +p55 +sg40 +I1868 +sg41 +I1868 +sg42 +I0 +sg21 +g52 +sa(dp56 +g4 +g5 +sg31 +V6.6 +p57 +sg8 +V2.2.2.42 +p58 +sg34 +V1004.7 +p59 +sg14 +(lp60 +Vntt-tenant_1 +p61 +asg17 +V3.3.3.40 +p62 +sg38 +V0.2 +p63 +sg40 +I1856 +sg41 +I1866 +sg42 +I1 +sg21 +g60 +sasS'tcp' +p64 +(lp65 +(dp66 +g4 +g5 +sS'retr' +p67 +V0 +p68 +sg10 +V6.03 Gbits/sec +p69 +sg8 +V3.3.3.40 +p70 +sg21 +(lp71 +Vntt-tenant_1 +p72 +asg22 +V 0.00-10.00 sec +p73 +sg14 +g71 +sg17 +g18 +sg24 +V7.03 GBytes +p74 +sas. \ No newline at end of file diff --git a/ntt_tool/ntt.sql b/ntt_tool/ntt.sql new file mode 100644 index 0000000..42234bb --- /dev/null +++ b/ntt_tool/ntt.sql @@ -0,0 +1,408 @@ +-- MySQL dump 10.13 Distrib 5.6.25, for debian-linux-gnu (x86_64) +-- +-- Host: 127.0.0.1 Database: ntt +-- ------------------------------------------------------ +-- Server version 5.6.25-0ubuntu0.15.04.1 + +/*!40101 SET @OLD_CHARACTER_SET_CLIENT=@@CHARACTER_SET_CLIENT */; +/*!40101 SET @OLD_CHARACTER_SET_RESULTS=@@CHARACTER_SET_RESULTS */; +/*!40101 SET @OLD_COLLATION_CONNECTION=@@COLLATION_CONNECTION */; +/*!40101 SET NAMES utf8 */; +/*!40103 SET @OLD_TIME_ZONE=@@TIME_ZONE */; +/*!40103 SET TIME_ZONE='+00:00' */; +/*!40014 SET @OLD_UNIQUE_CHECKS=@@UNIQUE_CHECKS, UNIQUE_CHECKS=0 */; +/*!40014 SET @OLD_FOREIGN_KEY_CHECKS=@@FOREIGN_KEY_CHECKS, FOREIGN_KEY_CHECKS=0 */; +/*!40101 SET @OLD_SQL_MODE=@@SQL_MODE, SQL_MODE='NO_AUTO_VALUE_ON_ZERO' */; +/*!40111 SET @OLD_SQL_NOTES=@@SQL_NOTES, SQL_NOTES=0 */; + +-- +-- Table structure for table `auth_group` +-- + +DROP TABLE IF EXISTS `auth_group`; +/*!40101 SET @saved_cs_client = @@character_set_client */; +/*!40101 SET character_set_client = utf8 */; +CREATE TABLE `auth_group` ( + `id` int(11) NOT NULL AUTO_INCREMENT, + `name` varchar(80) NOT NULL, + PRIMARY KEY (`id`), + UNIQUE KEY `name` (`name`) +) ENGINE=InnoDB DEFAULT CHARSET=latin1; +/*!40101 SET character_set_client = @saved_cs_client */; + +-- +-- Dumping data for table `auth_group` +-- + +LOCK TABLES `auth_group` WRITE; +/*!40000 ALTER TABLE `auth_group` DISABLE KEYS */; +/*!40000 ALTER TABLE `auth_group` ENABLE KEYS */; +UNLOCK TABLES; + +-- +-- Table structure for table `auth_group_permissions` +-- + +DROP TABLE IF EXISTS `auth_group_permissions`; +/*!40101 SET @saved_cs_client = @@character_set_client */; +/*!40101 SET character_set_client = utf8 */; +CREATE TABLE `auth_group_permissions` ( + `id` int(11) NOT NULL AUTO_INCREMENT, + `group_id` int(11) NOT NULL, + `permission_id` int(11) NOT NULL, + PRIMARY KEY (`id`), + UNIQUE KEY `auth_group_permissions_group_id_0cd325b0_uniq` (`group_id`,`permission_id`), + KEY `auth_group_permissi_permission_id_84c5c92e_fk_auth_permission_id` (`permission_id`), + CONSTRAINT `auth_group_permissi_permission_id_84c5c92e_fk_auth_permission_id` FOREIGN KEY (`permission_id`) REFERENCES `auth_permission` (`id`), + CONSTRAINT `auth_group_permissions_group_id_b120cbf9_fk_auth_group_id` FOREIGN KEY (`group_id`) REFERENCES `auth_group` (`id`) +) ENGINE=InnoDB DEFAULT CHARSET=latin1; +/*!40101 SET character_set_client = @saved_cs_client */; + +-- +-- Dumping data for table `auth_group_permissions` +-- + +LOCK TABLES `auth_group_permissions` WRITE; +/*!40000 ALTER TABLE `auth_group_permissions` DISABLE KEYS */; +/*!40000 ALTER TABLE `auth_group_permissions` ENABLE KEYS */; +UNLOCK TABLES; + +-- +-- Table structure for table `auth_permission` +-- + +DROP TABLE IF EXISTS `auth_permission`; +/*!40101 SET @saved_cs_client = @@character_set_client */; +/*!40101 SET character_set_client = utf8 */; +CREATE TABLE `auth_permission` ( + `id` int(11) NOT NULL AUTO_INCREMENT, + `name` varchar(255) NOT NULL, + `content_type_id` int(11) NOT NULL, + `codename` varchar(100) NOT NULL, + PRIMARY KEY (`id`), + UNIQUE KEY `auth_permission_content_type_id_01ab375a_uniq` (`content_type_id`,`codename`), + CONSTRAINT `auth_permissi_content_type_id_2f476e4b_fk_django_content_type_id` FOREIGN KEY (`content_type_id`) REFERENCES `django_content_type` (`id`) +) ENGINE=InnoDB AUTO_INCREMENT=28 DEFAULT CHARSET=latin1; +/*!40101 SET character_set_client = @saved_cs_client */; + +-- +-- Dumping data for table `auth_permission` +-- + +LOCK TABLES `auth_permission` WRITE; +/*!40000 ALTER TABLE `auth_permission` DISABLE KEYS */; +INSERT INTO `auth_permission` VALUES (1,'Can add log entry',1,'add_logentry'),(2,'Can change log entry',1,'change_logentry'),(3,'Can delete log entry',1,'delete_logentry'),(4,'Can add permission',2,'add_permission'),(5,'Can change permission',2,'change_permission'),(6,'Can delete permission',2,'delete_permission'),(7,'Can add group',3,'add_group'),(8,'Can change group',3,'change_group'),(9,'Can delete group',3,'delete_group'),(10,'Can add user',4,'add_user'),(11,'Can change user',4,'change_user'),(12,'Can delete user',4,'delete_user'),(13,'Can add content type',5,'add_contenttype'),(14,'Can change content type',5,'change_contenttype'),(15,'Can delete content type',5,'delete_contenttype'),(16,'Can add session',6,'add_session'),(17,'Can change session',6,'change_session'),(18,'Can delete session',6,'delete_session'),(19,'Can add cloud',7,'add_cloud'),(20,'Can change cloud',7,'change_cloud'),(21,'Can delete cloud',7,'delete_cloud'),(22,'Can add cloud traffic',8,'add_cloudtraffic'),(23,'Can change cloud traffic',8,'change_cloudtraffic'),(24,'Can delete cloud traffic',8,'delete_cloudtraffic'),(25,'Can add cloud traffic tenants',9,'add_cloudtraffictenants'),(26,'Can change cloud traffic tenants',9,'change_cloudtraffictenants'),(27,'Can delete cloud traffic tenants',9,'delete_cloudtraffictenants'); +/*!40000 ALTER TABLE `auth_permission` ENABLE KEYS */; +UNLOCK TABLES; + +-- +-- Table structure for table `auth_user` +-- + +DROP TABLE IF EXISTS `auth_user`; +/*!40101 SET @saved_cs_client = @@character_set_client */; +/*!40101 SET character_set_client = utf8 */; +CREATE TABLE `auth_user` ( + `id` int(11) NOT NULL AUTO_INCREMENT, + `password` varchar(128) NOT NULL, + `last_login` datetime(6) DEFAULT NULL, + `is_superuser` tinyint(1) NOT NULL, + `username` varchar(30) NOT NULL, + `first_name` varchar(30) NOT NULL, + `last_name` varchar(30) NOT NULL, + `email` varchar(254) NOT NULL, + `is_staff` tinyint(1) NOT NULL, + `is_active` tinyint(1) NOT NULL, + `date_joined` datetime(6) NOT NULL, + PRIMARY KEY (`id`), + UNIQUE KEY `username` (`username`) +) ENGINE=InnoDB AUTO_INCREMENT=2 DEFAULT CHARSET=latin1; +/*!40101 SET character_set_client = @saved_cs_client */; + +-- +-- Dumping data for table `auth_user` +-- + +LOCK TABLES `auth_user` WRITE; +/*!40000 ALTER TABLE `auth_user` DISABLE KEYS */; +INSERT INTO `auth_user` VALUES (1,'pbkdf2_sha256$24000$z6Uj4Y0v3T7h$sGmO9zwP2Xxb/Zb1ecxR2gVMKuKGDUWJW1s7F0l00gg=',NULL,1,'admin','','','admin@gmail.com',1,1,'2016-02-27 05:34:15.827140'); +/*!40000 ALTER TABLE `auth_user` ENABLE KEYS */; +UNLOCK TABLES; + +-- +-- Table structure for table `auth_user_groups` +-- + +DROP TABLE IF EXISTS `auth_user_groups`; +/*!40101 SET @saved_cs_client = @@character_set_client */; +/*!40101 SET character_set_client = utf8 */; +CREATE TABLE `auth_user_groups` ( + `id` int(11) NOT NULL AUTO_INCREMENT, + `user_id` int(11) NOT NULL, + `group_id` int(11) NOT NULL, + PRIMARY KEY (`id`), + UNIQUE KEY `auth_user_groups_user_id_94350c0c_uniq` (`user_id`,`group_id`), + KEY `auth_user_groups_group_id_97559544_fk_auth_group_id` (`group_id`), + CONSTRAINT `auth_user_groups_group_id_97559544_fk_auth_group_id` FOREIGN KEY (`group_id`) REFERENCES `auth_group` (`id`), + CONSTRAINT `auth_user_groups_user_id_6a12ed8b_fk_auth_user_id` FOREIGN KEY (`user_id`) REFERENCES `auth_user` (`id`) +) ENGINE=InnoDB DEFAULT CHARSET=latin1; +/*!40101 SET character_set_client = @saved_cs_client */; + +-- +-- Dumping data for table `auth_user_groups` +-- + +LOCK TABLES `auth_user_groups` WRITE; +/*!40000 ALTER TABLE `auth_user_groups` DISABLE KEYS */; +/*!40000 ALTER TABLE `auth_user_groups` ENABLE KEYS */; +UNLOCK TABLES; + +-- +-- Table structure for table `auth_user_user_permissions` +-- + +DROP TABLE IF EXISTS `auth_user_user_permissions`; +/*!40101 SET @saved_cs_client = @@character_set_client */; +/*!40101 SET character_set_client = utf8 */; +CREATE TABLE `auth_user_user_permissions` ( + `id` int(11) NOT NULL AUTO_INCREMENT, + `user_id` int(11) NOT NULL, + `permission_id` int(11) NOT NULL, + PRIMARY KEY (`id`), + UNIQUE KEY `auth_user_user_permissions_user_id_14a6b632_uniq` (`user_id`,`permission_id`), + KEY `auth_user_user_perm_permission_id_1fbb5f2c_fk_auth_permission_id` (`permission_id`), + CONSTRAINT `auth_user_user_perm_permission_id_1fbb5f2c_fk_auth_permission_id` FOREIGN KEY (`permission_id`) REFERENCES `auth_permission` (`id`), + CONSTRAINT `auth_user_user_permissions_user_id_a95ead1b_fk_auth_user_id` FOREIGN KEY (`user_id`) REFERENCES `auth_user` (`id`) +) ENGINE=InnoDB DEFAULT CHARSET=latin1; +/*!40101 SET character_set_client = @saved_cs_client */; + +-- +-- Dumping data for table `auth_user_user_permissions` +-- + +LOCK TABLES `auth_user_user_permissions` WRITE; +/*!40000 ALTER TABLE `auth_user_user_permissions` DISABLE KEYS */; +/*!40000 ALTER TABLE `auth_user_user_permissions` ENABLE KEYS */; +UNLOCK TABLES; + +-- +-- Table structure for table `cloud_details` +-- + +DROP TABLE IF EXISTS `cloud_details`; +/*!40101 SET @saved_cs_client = @@character_set_client */; +/*!40101 SET character_set_client = utf8 */; +CREATE TABLE `cloud_details` ( + `id` int(11) NOT NULL AUTO_INCREMENT, + `name` varchar(256) NOT NULL, + `keystone_auth_url` varchar(2083) NOT NULL, + `keystone_user` varchar(100) NOT NULL, + `keystone_password` varchar(250) NOT NULL, + `keystone_tenant_name` varchar(100) NOT NULL, + `created_on` datetime(6) NOT NULL, + `updated_on` datetime(6) NOT NULL, + PRIMARY KEY (`id`) +) ENGINE=InnoDB AUTO_INCREMENT=5 DEFAULT CHARSET=latin1; +/*!40101 SET character_set_client = @saved_cs_client */; + +-- +-- Dumping data for table `cloud_details` +-- + +LOCK TABLES `cloud_details` WRITE; +/*!40000 ALTER TABLE `cloud_details` DISABLE KEYS */; +INSERT INTO `cloud_details` VALUES (1,'Onecloud','https://github.com/onecloud/ntt_tool_python','admin','admin','admin','2016-03-01 05:12:15.246958','2016-02-27 05:35:21.670275'),(2,'CloudUno','https://github.com/onecloud/ntt_tool_python','test','test','test','2016-02-27 05:58:02.993225','2016-02-27 05:58:02.993265'),(3,'Airtel','http://10.1.32.11:5000/v2.0','admin','oc123','admin','2016-02-29 09:56:59.959767','2016-02-29 09:56:59.959941'),(4,'Vodafone','http://h.com','hello','aadsfsaf','vodafone chennai','2016-02-29 10:35:12.595484','2016-02-29 10:35:12.595531'); +/*!40000 ALTER TABLE `cloud_details` ENABLE KEYS */; +UNLOCK TABLES; + +-- +-- Table structure for table `cloud_traffic` +-- + +DROP TABLE IF EXISTS `cloud_traffic`; +/*!40101 SET @saved_cs_client = @@character_set_client */; +/*!40101 SET character_set_client = utf8 */; +CREATE TABLE `cloud_traffic` ( + `id` int(11) NOT NULL AUTO_INCREMENT, + `name` varchar(256) NOT NULL, + `allowed_delta_percentage` double NOT NULL, + `test_result_path` varchar(250) NOT NULL, + `number_of_workers` int(11) NOT NULL, + `remote_user` varchar(100) NOT NULL, + `remote_pass` varchar(100) NOT NULL, + `test_method` varchar(100) NOT NULL, + `iperf_duration` int(11) NOT NULL, + `tenant_type` varchar(20) NOT NULL, + `created_on` datetime(6) NOT NULL, + `updated_on` datetime(6) NOT NULL, + `cloud_id` int(11) NOT NULL, + `external_host` varchar(100), + PRIMARY KEY (`id`), + KEY `cloud_traffic_cloud_id_ac74e46e_fk_cloud_details_id` (`cloud_id`), + CONSTRAINT `cloud_traffic_cloud_id_ac74e46e_fk_cloud_details_id` FOREIGN KEY (`cloud_id`) REFERENCES `cloud_details` (`id`) +) ENGINE=InnoDB AUTO_INCREMENT=4 DEFAULT CHARSET=latin1; +/*!40101 SET character_set_client = @saved_cs_client */; + +-- +-- Dumping data for table `cloud_traffic` +-- + +LOCK TABLES `cloud_traffic` WRITE; +/*!40000 ALTER TABLE `cloud_traffic` DISABLE KEYS */; +INSERT INTO `cloud_traffic` VALUES (1,'Hello Traffic',1,'sadad',1000,'ads','asd','asdfas',1,'all','2016-02-29 13:14:13.484433','2016-02-27 05:40:15.840123',1,'192.168.12.9'),(2,'vodafone bangalore',3,'/home/',2,'admin','admin','true',60,'all','2016-02-29 10:51:36.858036','2016-02-29 10:51:36.858071',4,NULL),(3,'Hello Traffic',2,'/home/',1000,'admin','admin','admintest',1,'all','2016-02-29 13:37:28.179671','2016-02-29 13:37:28.179714',1,NULL); +/*!40000 ALTER TABLE `cloud_traffic` ENABLE KEYS */; +UNLOCK TABLES; + +-- +-- Table structure for table `cloud_traffic_tenants` +-- + +DROP TABLE IF EXISTS `cloud_traffic_tenants`; +/*!40101 SET @saved_cs_client = @@character_set_client */; +/*!40101 SET character_set_client = utf8 */; +CREATE TABLE `cloud_traffic_tenants` ( + `id` int(11) NOT NULL AUTO_INCREMENT, + `tenant_name` varchar(256) NOT NULL, + `ssh_gateway` varchar(256) DEFAULT NULL, + `created_on` datetime(6) NOT NULL, + `updated_on` datetime(6) NOT NULL, + `cloud_traffic_id` int(11) NOT NULL, + `creator_id` int(11) NOT NULL, + PRIMARY KEY (`id`), + KEY `cloud_traffic_tena_cloud_traffic_id_1a906c3d_fk_cloud_traffic_id` (`cloud_traffic_id`), + KEY `cloud_traffic_tenants_creator_id_36dea512_fk_auth_user_id` (`creator_id`), + CONSTRAINT `cloud_traffic_tena_cloud_traffic_id_1a906c3d_fk_cloud_traffic_id` FOREIGN KEY (`cloud_traffic_id`) REFERENCES `cloud_traffic` (`id`), + CONSTRAINT `cloud_traffic_tenants_creator_id_36dea512_fk_auth_user_id` FOREIGN KEY (`creator_id`) REFERENCES `auth_user` (`id`) +) ENGINE=InnoDB AUTO_INCREMENT=4 DEFAULT CHARSET=latin1; +/*!40101 SET character_set_client = @saved_cs_client */; + +-- +-- Dumping data for table `cloud_traffic_tenants` +-- + +LOCK TABLES `cloud_traffic_tenants` WRITE; +/*!40000 ALTER TABLE `cloud_traffic_tenants` DISABLE KEYS */; +INSERT INTO `cloud_traffic_tenants` VALUES (2,'admin','192.168.12.9','2016-02-27 05:42:55.338374','2016-02-27 05:42:55.338482',1,1),(3,'172.168.2.44:8000','172.168.2.44:8000','2016-02-29 11:58:09.559724','2016-02-29 11:58:09.559757',2,1); +/*!40000 ALTER TABLE `cloud_traffic_tenants` ENABLE KEYS */; +UNLOCK TABLES; + +-- +-- Table structure for table `django_admin_log` +-- + +DROP TABLE IF EXISTS `django_admin_log`; +/*!40101 SET @saved_cs_client = @@character_set_client */; +/*!40101 SET character_set_client = utf8 */; +CREATE TABLE `django_admin_log` ( + `id` int(11) NOT NULL AUTO_INCREMENT, + `action_time` datetime(6) NOT NULL, + `object_id` longtext, + `object_repr` varchar(200) NOT NULL, + `action_flag` smallint(5) unsigned NOT NULL, + `change_message` longtext NOT NULL, + `content_type_id` int(11) DEFAULT NULL, + `user_id` int(11) NOT NULL, + PRIMARY KEY (`id`), + KEY `django_admin__content_type_id_c4bce8eb_fk_django_content_type_id` (`content_type_id`), + KEY `django_admin_log_user_id_c564eba6_fk_auth_user_id` (`user_id`), + CONSTRAINT `django_admin__content_type_id_c4bce8eb_fk_django_content_type_id` FOREIGN KEY (`content_type_id`) REFERENCES `django_content_type` (`id`), + CONSTRAINT `django_admin_log_user_id_c564eba6_fk_auth_user_id` FOREIGN KEY (`user_id`) REFERENCES `auth_user` (`id`) +) ENGINE=InnoDB DEFAULT CHARSET=latin1; +/*!40101 SET character_set_client = @saved_cs_client */; + +-- +-- Dumping data for table `django_admin_log` +-- + +LOCK TABLES `django_admin_log` WRITE; +/*!40000 ALTER TABLE `django_admin_log` DISABLE KEYS */; +/*!40000 ALTER TABLE `django_admin_log` ENABLE KEYS */; +UNLOCK TABLES; + +-- +-- Table structure for table `django_content_type` +-- + +DROP TABLE IF EXISTS `django_content_type`; +/*!40101 SET @saved_cs_client = @@character_set_client */; +/*!40101 SET character_set_client = utf8 */; +CREATE TABLE `django_content_type` ( + `id` int(11) NOT NULL AUTO_INCREMENT, + `app_label` varchar(100) NOT NULL, + `model` varchar(100) NOT NULL, + PRIMARY KEY (`id`), + UNIQUE KEY `django_content_type_app_label_76bd3d3b_uniq` (`app_label`,`model`) +) ENGINE=InnoDB AUTO_INCREMENT=10 DEFAULT CHARSET=latin1; +/*!40101 SET character_set_client = @saved_cs_client */; + +-- +-- Dumping data for table `django_content_type` +-- + +LOCK TABLES `django_content_type` WRITE; +/*!40000 ALTER TABLE `django_content_type` DISABLE KEYS */; +INSERT INTO `django_content_type` VALUES (1,'admin','logentry'),(3,'auth','group'),(2,'auth','permission'),(4,'auth','user'),(7,'cloud','cloud'),(8,'cloud','cloudtraffic'),(9,'cloud','cloudtraffictenants'),(5,'contenttypes','contenttype'),(6,'sessions','session'); +/*!40000 ALTER TABLE `django_content_type` ENABLE KEYS */; +UNLOCK TABLES; + +-- +-- Table structure for table `django_migrations` +-- + +DROP TABLE IF EXISTS `django_migrations`; +/*!40101 SET @saved_cs_client = @@character_set_client */; +/*!40101 SET character_set_client = utf8 */; +CREATE TABLE `django_migrations` ( + `id` int(11) NOT NULL AUTO_INCREMENT, + `app` varchar(255) NOT NULL, + `name` varchar(255) NOT NULL, + `applied` datetime(6) NOT NULL, + PRIMARY KEY (`id`) +) ENGINE=InnoDB AUTO_INCREMENT=17 DEFAULT CHARSET=latin1; +/*!40101 SET character_set_client = @saved_cs_client */; + +-- +-- Dumping data for table `django_migrations` +-- + +LOCK TABLES `django_migrations` WRITE; +/*!40000 ALTER TABLE `django_migrations` DISABLE KEYS */; +INSERT INTO `django_migrations` VALUES (1,'contenttypes','0001_initial','2016-02-27 05:30:29.318686'),(2,'auth','0001_initial','2016-02-27 05:30:30.564825'),(3,'admin','0001_initial','2016-02-27 05:30:30.919881'),(4,'admin','0002_logentry_remove_auto_add','2016-02-27 05:30:30.941389'),(5,'contenttypes','0002_remove_content_type_name','2016-02-27 05:30:31.229480'),(6,'auth','0002_alter_permission_name_max_length','2016-02-27 05:30:31.359888'),(7,'auth','0003_alter_user_email_max_length','2016-02-27 05:30:31.463488'),(8,'auth','0004_alter_user_username_opts','2016-02-27 05:30:31.476924'),(9,'auth','0005_alter_user_last_login_null','2016-02-27 05:30:31.600098'),(10,'auth','0006_require_contenttypes_0002','2016-02-27 05:30:31.605120'),(11,'auth','0007_alter_validators_add_error_messages','2016-02-27 05:30:31.616106'),(12,'cloud','0001_initial','2016-02-27 05:30:32.306419'),(13,'cloud','0002_auto_20160225_1631','2016-02-27 05:30:32.365825'),(14,'cloud','0003_auto_20160225_1726','2016-02-27 05:30:32.625484'),(15,'cloud','0004_auto_20160226_1322','2016-02-27 05:30:32.880957'),(16,'sessions','0001_initial','2016-02-27 05:30:32.969106'); +/*!40000 ALTER TABLE `django_migrations` ENABLE KEYS */; +UNLOCK TABLES; + +-- +-- Table structure for table `django_session` +-- + +DROP TABLE IF EXISTS `django_session`; +/*!40101 SET @saved_cs_client = @@character_set_client */; +/*!40101 SET character_set_client = utf8 */; +CREATE TABLE `django_session` ( + `session_key` varchar(40) NOT NULL, + `session_data` longtext NOT NULL, + `expire_date` datetime(6) NOT NULL, + PRIMARY KEY (`session_key`), + KEY `django_session_de54fa62` (`expire_date`) +) ENGINE=InnoDB DEFAULT CHARSET=latin1; +/*!40101 SET character_set_client = @saved_cs_client */; + +-- +-- Dumping data for table `django_session` +-- + +LOCK TABLES `django_session` WRITE; +/*!40000 ALTER TABLE `django_session` DISABLE KEYS */; +/*!40000 ALTER TABLE `django_session` ENABLE KEYS */; +UNLOCK TABLES; +/*!40103 SET TIME_ZONE=@OLD_TIME_ZONE */; + +/*!40101 SET SQL_MODE=@OLD_SQL_MODE */; +/*!40014 SET FOREIGN_KEY_CHECKS=@OLD_FOREIGN_KEY_CHECKS */; +/*!40014 SET UNIQUE_CHECKS=@OLD_UNIQUE_CHECKS */; +/*!40101 SET CHARACTER_SET_CLIENT=@OLD_CHARACTER_SET_CLIENT */; +/*!40101 SET CHARACTER_SET_RESULTS=@OLD_CHARACTER_SET_RESULTS */; +/*!40101 SET COLLATION_CONNECTION=@OLD_COLLATION_CONNECTION */; +/*!40111 SET SQL_NOTES=@OLD_SQL_NOTES */; + +-- Dump completed on 2016-03-01 10:47:01 diff --git a/ntt_tool/settings.py b/ntt_tool/settings.py index a4facaf..cee3d35 100644 --- a/ntt_tool/settings.py +++ b/ntt_tool/settings.py @@ -127,8 +127,9 @@ STATIC_URL = '/static/' STATICFILES_DIRS = [ - os.path.join(BASE_DIR, "ntt_tool/static") + os.path.join(BASE_DIR, os.path.join("ntt_tool", "static")) ] +MEDIA_ROOT = os.path.join(BASE_DIR, os.path.join("ntt_tool", "media")) REST_FRAMEWORK = { # Use Django's standard `django.contrib.auth` permissions, @@ -141,6 +142,7 @@ # 'rest_framework.authentication.BasicAuthentication', 'rest_framework_jwt.authentication.JSONWebTokenAuthentication', ], + 'EXCEPTION_HANDLER': 'ntt_tool.utils.custom_exception_handler.custom_exception_handler', 'PAGINATE_BY': 10, } @@ -151,3 +153,10 @@ } # LOGIN_REDIRECT_URL = '/api/profile/' + +# E-Mail Settings +EMAIL_HOST = 'smtp.gmail.com' +EMAIL_PORT = "587" +EMAIL_HOST_USER = 'abdulgaffar@onecloudinc.com' +EMAIL_HOST_PASSWORD = 'abdulgaffar' +EMAIL_USE_TLS = True \ No newline at end of file diff --git a/ntt_tool/static/css/custom.css b/ntt_tool/static/css/custom.css index c5994e9..578ac82 100644 --- a/ntt_tool/static/css/custom.css +++ b/ntt_tool/static/css/custom.css @@ -1,4 +1,5 @@ @import url('http://fonts.googleapis.com/css?family=Open+Sans'); +@import url('http://fonts.googleapis.com/css?family=Varela+Round'); body, html { padding: 0; @@ -12,12 +13,34 @@ body { color: #555; background-color: #f6f6f6; } - .navbar { box-shadow: 0 2px 2px rgba(0,0,0,.05),0 1px 0 rgba(0,0,0,.05); border: none; border-radius: 0; - background: #fff; + background: #0E3D59; + +} +.navbar-brand{ + font-family: 'Varela Round', 'sans-serif'; + color: #e9e9df; +} +.navbar-default .navbar-brand { + color: #e9e9df; +} +.navbar-default .navbar-brand:hover, +.navbar-default .navbar-brand:focus { + color: #fff; +} +.navbar-default .navbar-text { + color: #e9e9df; +} +.navbar-default .navbar-nav > li > a { + color: #e9e9df; +} +.navbar-default .navbar-nav > li > a:hover, +.navbar-default .navbar-nav > li > a:focus { + color: #fff; + background-color: transparent; } .navbar-header a { text-transform: uppercase; @@ -25,14 +48,69 @@ body { font-weight: 400; } -.page-header { - background: #f6f6f6; - border-color: #e9e9e9; +/* Headings */ + +.form_h2{ + color: #0E3D59; +} + +/* buttons */ + +/* table headings */ +th{ + color: #0E3D59; +} + +td > a{ + color: #555; +} + + +/* utility classes */ +.bg-white { + background: #fff; +} + +.border { + border: 1px solid #ddd; +} + +.noborder{ + border: none !important; } + +.no-margin-bottom { + margin-bottom: 0; +} + +.mb0 { + margin-bottom: 0; +} +/* end: utility classes */ + +.panel { + border-radius: 0; +} +.panel-default .panel-heading { + background: #dddddd; + border-radius: 0; +} + +.btn-primary{ + background-color: #0E3D59; + /*border: none;*/ +} + +/*.page-header {*/ + /*background: #f6f6f6;*/ + /*border-color: #e9e9e9;*/ +/*}*/ .page-header { border-bottom: 1px solid #e4e4e4; - margin: -18px -18px 18px -18px; - padding: 5px 18px; + margin-top: 0; + padding-left: 0; + padding-right: 0; + padding-bottom: 0; } .page-header h1 { color: #666; @@ -46,12 +124,11 @@ body { margin-top: 18px; } -.bg-white { - background: #fff; +.modal-body { + max-height: calc(100vh - 210px); + overflow-y: auto; } - - /* widgets */ .card { border-radius: 2px; @@ -76,4 +153,20 @@ body { .panel-heading .btn{ margin-top: 0; -} \ No newline at end of file +} +.breadcrumb { + padding: 0; + margin: 0; +} + +.list-group-item-heading-custom { + border-bottom: 1px solid #ddd; + margin-bottom: 15px; + padding-bottom: 10px; +} + +.tenants-list { + max-height: calc(100vh - 210px); + overflow-y: auto; +} + diff --git a/ntt_tool/static/images/logo.png b/ntt_tool/static/images/logo.png new file mode 100644 index 0000000..3fd811a Binary files /dev/null and b/ntt_tool/static/images/logo.png differ diff --git a/ntt_tool/static/js/apps/app.js b/ntt_tool/static/js/apps/app.js index 011ff9f..959c1cd 100644 --- a/ntt_tool/static/js/apps/app.js +++ b/ntt_tool/static/js/apps/app.js @@ -2,6 +2,8 @@ var nttApp = angular.module('nttApp', [ 'ngRoute', 'ngCookies', 'http-auth-interceptor', + 'ui.bootstrap', + 'checklist-model', ]); nttApp.config(['$httpProvider', '$interpolateProvider', function($httpProvider, $interpolateProvider){ @@ -29,45 +31,54 @@ nttApp.config(['$routeProvider', function($routeProvider){ templateUrl: '/static/partials/dashboard.html', }); - $routeProvider.when('/clouds/', { - controller: 'CloudsCtrl', - templateUrl: '/static/partials/cloud/clouds.html', + /** + * URL patterns for cloud + */ + $routeProvider.when('/cloud/', { + controller: 'CloudListCtrl', + templateUrl: '/static/partials/cloud/cloud_list.html', }); - $routeProvider.when('/cloud/:cloudId/', { + $routeProvider.when('/cloud/add/', { controller: 'CloudCtrl', - templateUrl: '/static/partials/cloud/cloud_view.html', + templateUrl: '/static/partials/cloud/cloud_form.html', }); - $routeProvider.when('/clouds/:event/', { + $routeProvider.when('/cloud/edit/:id/', { controller: 'CloudCtrl', - templateUrl: '/static/partials/cloud/cloud.html', + templateUrl: '/static/partials/cloud/cloud_form.html', }); - $routeProvider.when('/clouds/:event/:cloudId/', { + $routeProvider.when('/cloud/view/:id/', { controller: 'CloudCtrl', - templateUrl: '/static/partials/cloud/cloud.html', - }); - - $routeProvider.when('/cloudtraffic/view/:id/', { - controller: 'CloudTrafficViewCtrl', - templateUrl: '/static/partials/cloud/cloudtraffic_view.html', + templateUrl: '/static/partials/cloud/cloud_view.html', }); - $routeProvider.when('/cloudtraffic/:event/:cloudId/', { - controller: 'CloudTrafficCtrl', - templateUrl: '/static/partials/cloud/cloudtraffic_form.html', + /** + * URL patterns for cloud traffic + */ + $routeProvider.when('/cloud/traffic/add/:cloudId/', { + controller: 'TrafficCtrl', + templateUrl: '/static/partials/cloud/traffic/traffic_form.html' }); - $routeProvider.when('/cloudtraffic/:event/:cloudId/:trafficId/', { - controller: 'CloudTrafficCtrl', - templateUrl: '/static/partials/cloud/cloudtraffic_form.html', + $routeProvider.when('/cloud/traffic/edit/:cloudId/:id/', { + controller: 'TrafficCtrl', + templateUrl: '/static/partials/cloud/traffic/traffic_form.html' }); - $routeProvider.when('/cloudtraffictest/', { - controller: 'CloudTrafficTestCtrl', - templateUrl: '/static/partials/cloud/cloudtraffic_test.html' + $routeProvider.when('/cloud/traffic/view/:cloudId/:id/', { + controller: 'TrafficViewCtrl', + templateUrl: '/static/partials/cloud/traffic/traffic_view.html' }); }]); + + + + + + + + diff --git a/ntt_tool/static/js/apps/auth.js b/ntt_tool/static/js/apps/auth.js index 46f066a..7c1b5e5 100644 --- a/ntt_tool/static/js/apps/auth.js +++ b/ntt_tool/static/js/apps/auth.js @@ -9,7 +9,7 @@ nttApp.run(function($rootScope, $window, $location){ $rootScope.isLoggedin = true; $window.localStorage['isLoggedin'] = true; $window.localStorage['token'] = data.token; - $location.path("clouds"); + $location.path("cloud"); }); $rootScope.$on('event:auth-loginRequired', function (event) { diff --git a/ntt_tool/static/js/apps/controllers/cloudcontroller.js b/ntt_tool/static/js/apps/controllers/cloudcontroller.js index 16238d6..172e2e5 100644 --- a/ntt_tool/static/js/apps/controllers/cloudcontroller.js +++ b/ntt_tool/static/js/apps/controllers/cloudcontroller.js @@ -1,63 +1,105 @@ -nttApp.controller('CloudsCtrl', function($scope, cloudService){ - $scope.clouds = []; - $scope.getClouds = function(){ - cloudService.list().then(function(data){ - $scope.clouds = data; - }) - }; - $scope.getClouds(); +/** + * Controller to list all clouds and delete selected cloud + */ +nttApp.controller('CloudListCtrl', function($scope, cloudService){ + $scope.cloudList = []; + + cloudService.list().then(function(data){ + $scope.cloudList = data; + }); $scope.delete = function($index){ if(confirm("Are you sure want to delete?")){ - cloudService.delete($scope.clouds[$index].id).then(function(data){ - $scope.clouds.splice($index, 1); + cloudService.delete($scope.cloudList[$index].id).then(function(data){ + $scope.cloudList.splice($index, 1); }); } }; }); -nttApp.controller('CloudCtrl', function($scope, $routeParams, $location, cloudService, cloudTrafficService){ - $scope.cloudId = $routeParams.cloudId; - $scope.event = $routeParams.event; +nttApp.controller('CloudCtrl', function($scope, $routeParams, $location, cloudService){ + $scope.id = $routeParams.id; + $scope.event = $scope.id == undefined ? "add" : "edit"; $scope.cloud = {}; - $scope.cloudTrafficList = []; - if ($scope.cloudId != undefined){ - cloudService.get($scope.cloudId).then(function(data){ + if($scope.event == "edit"){ + cloudService.get($scope.id).then(function(data){ $scope.cloud = data; }); - - cloudTrafficService.list($scope.cloudId).then(function (data) { - $scope.cloudTrafficList = data; - }) - }; + } $scope.save = function(){ - if ($scope.event == 'add'){ - cloudService.create($scope.cloud).then(function (data) { - $location.path("cloudtraffic/add/"+ data.id +"/"); + if ($scope.event == "add") { + cloudService.create($scope.cloud).then(function (response) { + $location.path("cloud/view/" + response.id + "/"); }); } else { - cloudService.update($scope.cloud.id, $scope.cloud).then(function(data){ - $location.path("cloud/"+$scope.cloud.id+"/"); + cloudService.update($scope.cloud.id, $scope.cloud).then(function(response){ + $location.path("cloud/view/" + response.id + "/"); }); - }; - + } }; - $scope.deleteTraffic = function($index){ - if(confirm("Are you sure want to delete?")) { - cloudTrafficService.delete($scope.cloudTrafficList[$index].id).then(function (data) { - $scope.cloudTrafficList.splice($index, 1); - }); - } - } + //$scope.saveAndDiscover = function(){ + // if ($scope.event == "add") { + // cloudService.create($scope.cloud).then(function(response){ + // $location.path("cloud/view/" + response.id + "/").search('discover', 'true'); + // }); + // } + // else { + // cloudService.update($scope.cloud.id, $scope.cloud).then(function(response){ + // $location.path("cloud/view/" + response.id + "/").search('discover', 'true'); + // }); + // } + //} }); + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + nttApp.controller('CloudTrafficCtrl', function($scope, $routeParams, $location, cloudService, cloudTrafficService){ $scope.cloud = {}; $scope.event = $routeParams.event; @@ -65,7 +107,9 @@ nttApp.controller('CloudTrafficCtrl', function($scope, $routeParams, $location, $scope.cloud = data; }); - $scope.cloudTraffic = {}; + $scope.cloudTraffic = { + "tenant_type": "all" + }; if($scope.event == 'edit'){ cloudTrafficService.get($routeParams.trafficId).then(function(data){ $scope.cloudTraffic = data; @@ -103,7 +147,7 @@ nttApp.controller('CloudTrafficViewCtrl', function($scope, $routeParams, $locati }); } - $scope.tenantEvent = "Add"; + $scope.tenantEvent = "add"; $scope.tenants = []; $scope.tenant = {}; cloudTrafficTenantService.list($scope.cloudTrafficId).then(function(data){ @@ -111,7 +155,7 @@ nttApp.controller('CloudTrafficViewCtrl', function($scope, $routeParams, $locati }); $scope.addTenant = function(){ - $scope.tenantEvent = "Add"; + $scope.tenantEvent = "add"; $scope.tenant["cloud_traffic_id"] = $scope.cloudTraffic.id; cloudTrafficTenantService.create($scope.tenant).then(function(data){ $scope.tenants.push(data); @@ -120,7 +164,7 @@ nttApp.controller('CloudTrafficViewCtrl', function($scope, $routeParams, $locati }; $scope.editTenant = function($index){ - $scope.tenantEvent = "Edit"; + $scope.tenantEvent = "edit"; $scope.tenant = angular.copy($scope.tenants[$index]); cloudTrafficTenantService.update($scope.tenant.id, $scope.tenant).then(function(data){ $scope.tenant[$index] = data; diff --git a/ntt_tool/static/js/apps/controllers/maincontroller.js b/ntt_tool/static/js/apps/controllers/maincontroller.js index ef31ac7..96e9b9e 100644 --- a/ntt_tool/static/js/apps/controllers/maincontroller.js +++ b/ntt_tool/static/js/apps/controllers/maincontroller.js @@ -1,6 +1,6 @@ nttApp.controller('IndexCtrl', function($rootScope, $scope, $location){ if($rootScope.isLoggedin){ - $location.path('clouds'); + $location.path('cloud'); } }); diff --git a/ntt_tool/static/js/apps/controllers/tenantcontroller.js b/ntt_tool/static/js/apps/controllers/tenantcontroller.js new file mode 100644 index 0000000..0cf465d --- /dev/null +++ b/ntt_tool/static/js/apps/controllers/tenantcontroller.js @@ -0,0 +1,16 @@ +nttApp.controller('TenantListCtrl', function($scope, $routeParams, tenantService){ + $scope.cloudId = $routeParams.id; + $scope.tenants = []; + tenantService.list($scope.cloudId).then(function(response){ + $scope.tenants = response; + }); + + $scope.showLoading = false; + $scope.discover = function(){ + $scope.showLoading = true; + tenantService.discover($scope.cloudId).then(function(response){ + $scope.showLoading = false; + $scope.tenants = response; + }); + }; +}); \ No newline at end of file diff --git a/ntt_tool/static/js/apps/controllers/trafficcontroller.js b/ntt_tool/static/js/apps/controllers/trafficcontroller.js new file mode 100644 index 0000000..45cb3e7 --- /dev/null +++ b/ntt_tool/static/js/apps/controllers/trafficcontroller.js @@ -0,0 +1,223 @@ +nttApp.controller('TrafficListCtrl', function($scope, $routeParams, trafficService){ + $scope.trafficList = []; + trafficService.list($routeParams.id).then(function(response){ + $scope.trafficList = response; + }); + + $scope.delete = function($index){ + if (confirm("Are you sure want to delete?")){ + trafficService.delete($scope.trafficList[$index].id).then(function(response){ + $scope.trafficList.splice($index, 1); + }); + } + }; +}); + + +nttApp.controller('TrafficCtrl', function($scope, $routeParams, $location, trafficService, tenantService){ + $scope.cloudId = $routeParams.cloudId; + $scope.id = $routeParams.id; + $scope.event = $scope.id == undefined ? "add" : "edit"; + $scope.traffic = { + "cloud_id": $scope.cloudId, + "test_type": "intra-tenant", + "test_environment": "dev", + }; + + if ($scope.event == "edit"){ + trafficService.get($scope.id).then(function(response){ + $scope.traffic = response; + var testMethods = { + icmp: false, + tcp: false, + udp: false + }; + angular.forEach(response.test_method.split(","), function (testMethod, i) { + testMethods[testMethod] = true; + }); + $scope.traffic["test_method"] = testMethods; + }); + } + + $scope.save = function(){ + var selectedTestMethods = []; + angular.forEach($scope.traffic.test_method, function (isSelected, testMethod) { + if(isSelected){ + selectedTestMethods.push(testMethod) + } + }); + $scope.traffic["test_method"] = selectedTestMethods.join(); + + if($scope.event == "add") { + trafficService.create($scope.traffic).then(function (response) { + $location.path("cloud/traffic/view/" + $scope.cloudId + "/" + response.id + "/"); + }); + } + else { + trafficService.update($scope.traffic.id, $scope.traffic).then(function(response){ + $location.path("cloud/traffic/view/" + $scope.cloudId + "/" + $scope.traffic.id + "/"); + }); + } + }; +}); + + +nttApp.controller('TrafficViewCtrl', function($scope, $routeParams, trafficService, tenantService){ + $scope.cloudId = $routeParams.cloudId; + $scope.id = $routeParams.id; + $scope.traffic = {}; + + trafficService.getWithRelatedData($scope.id).then(function(response){ + $scope.traffic = response; + $scope.getTenants(); + $scope.getEndpoints(); + }); + + $scope.isAnyNetworkSelected = false; + $scope.tenants = []; + $scope.getTenants = function(){ + tenantService.list($scope.cloudId).then(function(response){ + $scope.tenants = response; + // Iterating through tenants to make radio button checked for matching tenant + angular.forEach(response, function(tenant, i){ + if ($scope.traffic.tenants[0].tenant_id == tenant.tenant_id){ + $scope.traffic.tenants[0] = tenant; + angular.forEach($scope.traffic.tenants[0].networks, function(network, j){ + angular.forEach($scope.traffic.selected_networks, function(selectedNetwork, k){ + if(selectedNetwork.network == network.id){ + network["is_selected"] = true; + if($scope.traffic.test_environment == 'prod'){ + network["endpoint_count"] = selectedNetwork.endpoint_count; + } + else{ + network.subnets[0]["ip_range_start"] = selectedNetwork.ip_range_start; + network.subnets[0]["ip_range_end"] = selectedNetwork.ip_range_end; + } + $scope.isAnyNetworkSelected = true; + } + }); + }); + } + }); + }); + }; + + $scope.selectTenant = function(tenant_id){ + trafficService.selectTenant($scope.traffic.id, tenant_id).then(function(response){ + console.log(response) + }); + }; + + $scope.selectNetwork = function($index, networkId, isSelected){ + var params = { + "network_id": networkId, + "is_selected": isSelected, + }; + trafficService.selectNetwork($scope.traffic.id, params).then(function(response){ + if(isSelected){ + $scope.traffic.tenants[0].networks[$index].subnets[0] = response; + } + }); + }; + + $scope.$watch('traffic.tenants[0].networks', function(newValues, oldValue, scope){ + var flag = false; + angular.forEach(newValues, function(network, i){ + if(network.is_selected){ + flag = true + } + }); + $scope.isAnyNetworkSelected = flag; + }, true); + + $scope.showLoadingEndpoints = false; + $scope.endpoints = []; + $scope.getEndpoints = function () { + trafficService.endpoints($scope.traffic.id).then(function(response){ + $scope.endpoints = response; + }); + }; + $scope.discoverEndpoints = function(){ + $scope.showLoadingEndpoints = true; + $scope.endpoints = []; + var selectedItems = []; + + angular.forEach($scope.traffic.tenants[0].networks, function(network, i){ + console.log(network.is_selected) + if(network.is_selected){ + if($scope.traffic.test_environment == 'prod'){ + selectedItems.push({ + "network_id": network.id, + "endpoint_count": network.endpoint_count + }) + } + else { + selectedItems.push({ + "network_id": network.id, + "ip_range_start": network.subnets[0].ip_range_start, + "ip_range_end": network.subnets[0].ip_range_end, + }); + } + } + }); + + if($scope.traffic.test_environment == "dev") { + trafficService.discoverEndpoints($scope.traffic.id, selectedItems).then(function (response) { + $scope.endpoints = response; + $scope.showLoadingEndpoints = false; + + }); + } + else { + trafficService.launchEndpoints($scope.traffic.id, selectedItems).then(function (response) { + $scope.endpoints = response; + $scope.showLoadingEndpoints = false; + }); + } + }; + + $scope.selectEndpoint = function($index) { + var endpoint = $scope.endpoints[$index]; + var params = { + "endpoint_pk": endpoint.id, + "endpoint_id": endpoint.endpoint_id, + "is_selected": endpoint.is_selected + }; + trafficService.selectEndpoint($scope.traffic.id, params).then(function (response) { + $scope.endpoints[$index] = response; + }); + }; + + + + $scope.testResult = {}; + $scope.testResultRunning = false; + $scope.runTrafficTest = function (trafficId) { + $scope.testResultRunning = true; + trafficService.runTrafficTest(trafficId).then(function (response) { + $scope.testResult = response; + $scope.testResultRunning = false; + }); + }; + + var doc = new jsPDF(); + var specialElementHandlers = { + '#editor': function (element, renderer) { + return true; + } + }; + $scope.exportTrafficTestResults = function () { + doc.fromHTML($('#traffic-test-results').html(), 10, 10, { + 'width': 200, + 'elementHandlers': specialElementHandlers + }); + doc.save('traffic-test-report.pdf'); + }; + + + $scope.emailReport = function () { + trafficService.emailReport($scope.traffic.id).then(function (response) { + console.log(response) + }); + } +}); diff --git a/ntt_tool/static/js/apps/directives/directives.js b/ntt_tool/static/js/apps/directives/directives.js new file mode 100644 index 0000000..2a548e4 --- /dev/null +++ b/ntt_tool/static/js/apps/directives/directives.js @@ -0,0 +1,3 @@ +/** + * Created by abdul on 24/3/16. + */ diff --git a/ntt_tool/static/js/apps/filters/filters.js b/ntt_tool/static/js/apps/filters/filters.js index 3e86b53..1522f72 100644 --- a/ntt_tool/static/js/apps/filters/filters.js +++ b/ntt_tool/static/js/apps/filters/filters.js @@ -4,4 +4,14 @@ nttApp.filter('capitalize', function () { return txt.charAt(0).toUpperCase() + txt.substr(1).toLowerCase(); }) : ''; } +}); + +nttApp.filter('isEmptyObject', function(){ + return function (obj) { + for(var prop in obj) { + if(obj.hasOwnProperty(prop)) + return false; + } + return true; + }; }); \ No newline at end of file diff --git a/ntt_tool/static/js/apps/services/cloudservice.js b/ntt_tool/static/js/apps/services/cloudservice.js index 3c06680..5ad56ca 100644 --- a/ntt_tool/static/js/apps/services/cloudservice.js +++ b/ntt_tool/static/js/apps/services/cloudservice.js @@ -6,7 +6,7 @@ nttApp.service('cloudService', function(dataService){ return dataService.get('/api/cloud/'+id+'/'); }; this.create = function(params){ - return dataService.post('/api/cloud/1/', params); + return dataService.post('/api/cloud/', params); }; this.update = function(pk, params){ return dataService.put('/api/cloud/'+pk+'/', params); @@ -16,38 +16,3 @@ nttApp.service('cloudService', function(dataService){ }; }); -nttApp.service('cloudTrafficService', function(dataService){ - this.list = function(cloudId){ - return dataService.get('/api/cloudtraffic/?cloud_id='+cloudId); - }; - this.get = function(id){ - return dataService.get('/api/cloudtraffic/'+id+'/'); - }; - this.create = function(params){ - return dataService.post('/api/cloudtraffic/', params); - }; - this.update = function(pk, params){ - return dataService.put('/api/cloudtraffic/'+pk+'/', params); - }; - this.delete = function(pk){ - return dataService.delete('/api/cloudtraffic/'+pk+'/'); - }; -}); - -nttApp.service('cloudTrafficTenantService', function(dataService){ - this.list = function(trafficId){ - return dataService.get('/api/cloudtraffictenant/?traffic_id='+trafficId); - }; - this.get = function(trafficId) { - return dataService.get('/api/cloudtraffictenant/'+trafficId+'/'); - }; - this.create = function(params){ - return dataService.post('/api/cloudtraffictenant/', params); - }; - this.update = function(pk, params){ - return dataService.put('/api/cloudtraffictenant/'+pk+'/', params); - }; - this.delete = function(pk){ - return dataService.delete('/api/cloudtraffictenant/'+pk+'/'); - }; -}); \ No newline at end of file diff --git a/ntt_tool/static/js/apps/services/dataservice.js b/ntt_tool/static/js/apps/services/dataservice.js index d67ab08..e6e852a 100644 --- a/ntt_tool/static/js/apps/services/dataservice.js +++ b/ntt_tool/static/js/apps/services/dataservice.js @@ -29,6 +29,25 @@ nttApp.service('dataService', ['$http', '$q', function ($http, $q) { return deferred.promise; }; + this.postJSON = function(url, params) { + var deferred = $q.defer(); + var request = $http({ + method: "post", + url: url, + data: $.param({"json": angular.toJson(params)}), + headers: { + 'Content-Type': 'application/x-www-form-urlencoded; charset=UTF-8' + } + }); + request.success(function (data, status, headers, config) { + deferred.resolve(data) + }); + request.error(function (response) { + deferred.reject(response); + }); + return deferred.promise; + }; + this.update = function (url, params) { var deferred = $q.defer(); var request = $http({ diff --git a/ntt_tool/static/js/apps/services/tenantservice.js b/ntt_tool/static/js/apps/services/tenantservice.js new file mode 100644 index 0000000..8727683 --- /dev/null +++ b/ntt_tool/static/js/apps/services/tenantservice.js @@ -0,0 +1,15 @@ +nttApp.service('tenantService', function (dataService) { + this.discover = function(cloudId){ + return dataService.get('/api/tenant/discover/' + cloudId + '/'); + }; + + this.list = function(cloudId){ + return dataService.get('/api/tenant/?cloud_id='+cloudId); + }; + + this.save = function(params){ + return dataService.post('/api/tenant/', params); + }; +}); + + diff --git a/ntt_tool/static/js/apps/services/trafficservice.js b/ntt_tool/static/js/apps/services/trafficservice.js new file mode 100644 index 0000000..c63cab7 --- /dev/null +++ b/ntt_tool/static/js/apps/services/trafficservice.js @@ -0,0 +1,63 @@ +nttApp.service('trafficService', function(dataService){ + this.list = function(cloudId){ + return dataService.get('/api/traffic/?cloud_id=' + cloudId); + }; + + this.get = function(pk) { + return dataService.get('/api/traffic/' + pk + '/'); + }; + + this.getWithRelatedData = function(pk) { + return dataService.get('/api/traffic/' + pk + '/?get_related_data=true'); + }; + + this.create = function(params){ + return dataService.post('/api/traffic/', params); + }; + + this.update = function(pk, params){ + return dataService.put('/api/traffic/'+pk+'/', params); + }; + + this.delete = function(pk){ + return dataService.delete('/api/traffic/'+pk+'/'); + }; + + this.selectTenant = function(pk, tenantId){ + return dataService.get('/api/traffic/' + pk + '/select/tenant/' + tenantId + '/'); + }; + + this.selectNetwork = function(pk, params){ + return dataService.get('/api/traffic/' + pk + '/select/network/?'+$.param(params)); + }; + + this.endpoints = function(pk){ + return dataService.get('/api/traffic/' + pk + '/endpoints/'); + }; + + this.discoverEndpoints = function(pk, params){ + return dataService.postJSON('/api/traffic/' +pk + '/endpoints/discover/', params); + }; +1 + this.launchEndpoints = function(pk, params) { + return dataService.postJSON('/api/traffic/' +pk + '/endpoints/launch/', params); + }; + + this.selectEndpoint = function (pk, params) { + return dataService.get('/api/traffic/' +pk + '/endpoints/select/?'+$.param(params)); + }; + + this.runTrafficTest = function(pk){ + return dataService.get('/api/traffic/' + pk + '/run/test/'); + }; + + this.emailReport = function (pk) { + return dataService.get('/api/traffic/' + pk + '/email/report/'); + } +}); + + + + + + diff --git a/ntt_tool/static/js/libs/angular/checklist-model.js b/ntt_tool/static/js/libs/angular/checklist-model.js new file mode 100644 index 0000000..391d831 --- /dev/null +++ b/ntt_tool/static/js/libs/angular/checklist-model.js @@ -0,0 +1,148 @@ +/** + * Checklist-model + * AngularJS directive for list of checkboxes + * https://github.com/vitalets/checklist-model + * License: MIT http://opensource.org/licenses/MIT + */ + +angular.module('checklist-model', []) +.directive('checklistModel', ['$parse', '$compile', function($parse, $compile) { + // contains + function contains(arr, item, comparator) { + if (angular.isArray(arr)) { + for (var i = arr.length; i--;) { + if (comparator(arr[i], item)) { + return true; + } + } + } + return false; + } + + // add + function add(arr, item, comparator) { + arr = angular.isArray(arr) ? arr : []; + if(!contains(arr, item, comparator)) { + arr.push(item); + } + return arr; + } + + // remove + function remove(arr, item, comparator) { + if (angular.isArray(arr)) { + for (var i = arr.length; i--;) { + if (comparator(arr[i], item)) { + arr.splice(i, 1); + break; + } + } + } + return arr; + } + + // http://stackoverflow.com/a/19228302/1458162 + function postLinkFn(scope, elem, attrs) { + // exclude recursion, but still keep the model + var checklistModel = attrs.checklistModel; + attrs.$set("checklistModel", null); + // compile with `ng-model` pointing to `checked` + $compile(elem)(scope); + attrs.$set("checklistModel", checklistModel); + + // getter / setter for original model + var getter = $parse(checklistModel); + var setter = getter.assign; + var checklistChange = $parse(attrs.checklistChange); + var checklistBeforeChange = $parse(attrs.checklistBeforeChange); + + // value added to list + var value = attrs.checklistValue ? $parse(attrs.checklistValue)(scope.$parent) : attrs.value; + + + var comparator = angular.equals; + + if (attrs.hasOwnProperty('checklistComparator')){ + if (attrs.checklistComparator[0] == '.') { + var comparatorExpression = attrs.checklistComparator.substring(1); + comparator = function (a, b) { + return a[comparatorExpression] === b[comparatorExpression]; + }; + + } else { + comparator = $parse(attrs.checklistComparator)(scope.$parent); + } + } + + // watch UI checked change + scope.$watch(attrs.ngModel, function(newValue, oldValue) { + if (newValue === oldValue) { + return; + } + + if (checklistBeforeChange && (checklistBeforeChange(scope) === false)) { + scope[attrs.ngModel] = contains(getter(scope.$parent), value, comparator); + return; + } + + setValueInChecklistModel(value, newValue); + + if (checklistChange) { + checklistChange(scope); + } + }); + + function setValueInChecklistModel(value, checked) { + var current = getter(scope.$parent); + if (angular.isFunction(setter)) { + if (checked === true) { + setter(scope.$parent, add(current, value, comparator)); + } else { + setter(scope.$parent, remove(current, value, comparator)); + } + } + + } + + // declare one function to be used for both $watch functions + function setChecked(newArr, oldArr) { + if (checklistBeforeChange && (checklistBeforeChange(scope) === false)) { + setValueInChecklistModel(value, scope[attrs.ngModel]); + return; + } + scope[attrs.ngModel] = contains(newArr, value, comparator); + } + + // watch original model change + // use the faster $watchCollection method if it's available + if (angular.isFunction(scope.$parent.$watchCollection)) { + scope.$parent.$watchCollection(checklistModel, setChecked); + } else { + scope.$parent.$watch(checklistModel, setChecked, true); + } + } + + return { + restrict: 'A', + priority: 1000, + terminal: true, + scope: true, + compile: function(tElement, tAttrs) { + if ((tElement[0].tagName !== 'INPUT' || tAttrs.type !== 'checkbox') && (tElement[0].tagName !== 'MD-CHECKBOX') && (!tAttrs.btnCheckbox)) { + throw 'checklist-model should be applied to `input[type="checkbox"]` or `md-checkbox`.'; + } + + if (!tAttrs.checklistValue && !tAttrs.value) { + throw 'You should provide `value` or `checklist-value`.'; + } + + // by default ngModel is 'checked', so we set it if not specified + if (!tAttrs.ngModel) { + // local scope var storing individual checkbox model + tAttrs.$set("ngModel", "checked"); + } + + return postLinkFn; + } + }; +}]); diff --git a/ntt_tool/static/js/libs/angular/ui-bootstrap-tpls-1.2.4.min.js b/ntt_tool/static/js/libs/angular/ui-bootstrap-tpls-1.2.4.min.js new file mode 100644 index 0000000..0eab296 --- /dev/null +++ b/ntt_tool/static/js/libs/angular/ui-bootstrap-tpls-1.2.4.min.js @@ -0,0 +1,10 @@ +/* + * angular-ui-bootstrap + * http://angular-ui.github.io/bootstrap/ + + * Version: 1.2.4 - 2016-03-06 + * License: MIT + */angular.module("ui.bootstrap",["ui.bootstrap.tpls","ui.bootstrap.collapse","ui.bootstrap.accordion","ui.bootstrap.alert","ui.bootstrap.buttons","ui.bootstrap.carousel","ui.bootstrap.dateparser","ui.bootstrap.isClass","ui.bootstrap.position","ui.bootstrap.datepicker","ui.bootstrap.debounce","ui.bootstrap.dropdown","ui.bootstrap.stackedMap","ui.bootstrap.modal","ui.bootstrap.paging","ui.bootstrap.pager","ui.bootstrap.pagination","ui.bootstrap.tooltip","ui.bootstrap.popover","ui.bootstrap.progressbar","ui.bootstrap.rating","ui.bootstrap.tabs","ui.bootstrap.timepicker","ui.bootstrap.typeahead"]),angular.module("ui.bootstrap.tpls",["uib/template/accordion/accordion-group.html","uib/template/accordion/accordion.html","uib/template/alert/alert.html","uib/template/carousel/carousel.html","uib/template/carousel/slide.html","uib/template/datepicker/datepicker.html","uib/template/datepicker/day.html","uib/template/datepicker/month.html","uib/template/datepicker/popup.html","uib/template/datepicker/year.html","uib/template/modal/backdrop.html","uib/template/modal/window.html","uib/template/pager/pager.html","uib/template/pagination/pagination.html","uib/template/tooltip/tooltip-html-popup.html","uib/template/tooltip/tooltip-popup.html","uib/template/tooltip/tooltip-template-popup.html","uib/template/popover/popover-html.html","uib/template/popover/popover-template.html","uib/template/popover/popover.html","uib/template/progressbar/bar.html","uib/template/progressbar/progress.html","uib/template/progressbar/progressbar.html","uib/template/rating/rating.html","uib/template/tabs/tab.html","uib/template/tabs/tabset.html","uib/template/timepicker/timepicker.html","uib/template/typeahead/typeahead-match.html","uib/template/typeahead/typeahead-popup.html"]),angular.module("ui.bootstrap.collapse",[]).directive("uibCollapse",["$animate","$q","$parse","$injector",function(a,b,c,d){var e=d.has("$animateCss")?d.get("$animateCss"):null;return{link:function(d,f,g){function h(){f.hasClass("collapse")&&f.hasClass("in")||b.resolve(l(d)).then(function(){f.removeClass("collapse").addClass("collapsing").attr("aria-expanded",!0).attr("aria-hidden",!1),e?e(f,{addClass:"in",easing:"ease",to:{height:f[0].scrollHeight+"px"}}).start()["finally"](i):a.addClass(f,"in",{to:{height:f[0].scrollHeight+"px"}}).then(i)})}function i(){f.removeClass("collapsing").addClass("collapse").css({height:"auto"}),m(d)}function j(){return f.hasClass("collapse")||f.hasClass("in")?void b.resolve(n(d)).then(function(){f.css({height:f[0].scrollHeight+"px"}).removeClass("collapse").addClass("collapsing").attr("aria-expanded",!1).attr("aria-hidden",!0),e?e(f,{removeClass:"in",to:{height:"0"}}).start()["finally"](k):a.removeClass(f,"in",{to:{height:"0"}}).then(k)}):k()}function k(){f.css({height:"0"}),f.removeClass("collapsing").addClass("collapse"),o(d)}var l=c(g.expanding),m=c(g.expanded),n=c(g.collapsing),o=c(g.collapsed);d.$eval(g.uibCollapse)||f.addClass("in").addClass("collapse").attr("aria-expanded",!0).attr("aria-hidden",!1).css({height:"auto"}),d.$watch(g.uibCollapse,function(a){a?j():h()})}}}]),angular.module("ui.bootstrap.accordion",["ui.bootstrap.collapse"]).constant("uibAccordionConfig",{closeOthers:!0}).controller("UibAccordionController",["$scope","$attrs","uibAccordionConfig",function(a,b,c){this.groups=[],this.closeOthers=function(d){var e=angular.isDefined(b.closeOthers)?a.$eval(b.closeOthers):c.closeOthers;e&&angular.forEach(this.groups,function(a){a!==d&&(a.isOpen=!1)})},this.addGroup=function(a){var b=this;this.groups.push(a),a.$on("$destroy",function(c){b.removeGroup(a)})},this.removeGroup=function(a){var b=this.groups.indexOf(a);-1!==b&&this.groups.splice(b,1)}}]).directive("uibAccordion",function(){return{controller:"UibAccordionController",controllerAs:"accordion",transclude:!0,templateUrl:function(a,b){return b.templateUrl||"uib/template/accordion/accordion.html"}}}).directive("uibAccordionGroup",function(){return{require:"^uibAccordion",transclude:!0,replace:!0,templateUrl:function(a,b){return b.templateUrl||"uib/template/accordion/accordion-group.html"},scope:{heading:"@",isOpen:"=?",isDisabled:"=?"},controller:function(){this.setHeading=function(a){this.heading=a}},link:function(a,b,c,d){d.addGroup(a),a.openClass=c.openClass||"panel-open",a.panelClass=c.panelClass||"panel-default",a.$watch("isOpen",function(c){b.toggleClass(a.openClass,!!c),c&&d.closeOthers(a)}),a.toggleOpen=function(b){a.isDisabled||b&&32!==b.which||(a.isOpen=!a.isOpen)};var e="accordiongroup-"+a.$id+"-"+Math.floor(1e4*Math.random());a.headingId=e+"-tab",a.panelId=e+"-panel"}}}).directive("uibAccordionHeading",function(){return{transclude:!0,template:"",replace:!0,require:"^uibAccordionGroup",link:function(a,b,c,d,e){d.setHeading(e(a,angular.noop))}}}).directive("uibAccordionTransclude",function(){return{require:"^uibAccordionGroup",link:function(a,b,c,d){a.$watch(function(){return d[c.uibAccordionTransclude]},function(a){if(a){var c=angular.element(b[0].querySelector("[uib-accordion-header]"));c.html(""),c.append(a)}})}}}),angular.module("ui.bootstrap.alert",[]).controller("UibAlertController",["$scope","$attrs","$interpolate","$timeout",function(a,b,c,d){a.closeable=!!b.close;var e=angular.isDefined(b.dismissOnTimeout)?c(b.dismissOnTimeout)(a.$parent):null;e&&d(function(){a.close()},parseInt(e,10))}]).directive("uibAlert",function(){return{controller:"UibAlertController",controllerAs:"alert",templateUrl:function(a,b){return b.templateUrl||"uib/template/alert/alert.html"},transclude:!0,replace:!0,scope:{type:"@",close:"&"}}}),angular.module("ui.bootstrap.buttons",[]).constant("uibButtonConfig",{activeClass:"active",toggleEvent:"click"}).controller("UibButtonsController",["uibButtonConfig",function(a){this.activeClass=a.activeClass||"active",this.toggleEvent=a.toggleEvent||"click"}]).directive("uibBtnRadio",["$parse",function(a){return{require:["uibBtnRadio","ngModel"],controller:"UibButtonsController",controllerAs:"buttons",link:function(b,c,d,e){var f=e[0],g=e[1],h=a(d.uibUncheckable);c.find("input").css({display:"none"}),g.$render=function(){c.toggleClass(f.activeClass,angular.equals(g.$modelValue,b.$eval(d.uibBtnRadio)))},c.on(f.toggleEvent,function(){if(!d.disabled){var a=c.hasClass(f.activeClass);(!a||angular.isDefined(d.uncheckable))&&b.$apply(function(){g.$setViewValue(a?null:b.$eval(d.uibBtnRadio)),g.$render()})}}),d.uibUncheckable&&b.$watch(h,function(a){d.$set("uncheckable",a?"":void 0)})}}}]).directive("uibBtnCheckbox",function(){return{require:["uibBtnCheckbox","ngModel"],controller:"UibButtonsController",controllerAs:"button",link:function(a,b,c,d){function e(){return g(c.btnCheckboxTrue,!0)}function f(){return g(c.btnCheckboxFalse,!1)}function g(b,c){return angular.isDefined(b)?a.$eval(b):c}var h=d[0],i=d[1];b.find("input").css({display:"none"}),i.$render=function(){b.toggleClass(h.activeClass,angular.equals(i.$modelValue,e()))},b.on(h.toggleEvent,function(){c.disabled||a.$apply(function(){i.$setViewValue(b.hasClass(h.activeClass)?f():e()),i.$render()})})}}}),angular.module("ui.bootstrap.carousel",[]).controller("UibCarouselController",["$scope","$element","$interval","$timeout","$animate",function(a,b,c,d,e){function f(){for(;t.length;)t.shift()}function g(a){for(var b=0;b1){q[d].element.data(r,c.direction);var j=p.getCurrentIndex();angular.isNumber(j)&&q[j].element&&q[j].element.data(r,c.direction),a.$currentTransition=!0,e.on("addClass",q[d].element,function(b,c){if("close"===c&&(a.$currentTransition=null,e.off("addClass",b),t.length)){var d=t.pop().slide,g=d.index,i=g>p.getCurrentIndex()?"next":"prev";f(),h(d,g,i)}})}a.active=c.index,s=c.index,g(d),l()}}function i(a){for(var b=0;b0&&(n=c(m,b))}function m(){var b=+a.interval;o&&!isNaN(b)&&b>0&&q.length?a.next():a.pause()}var n,o,p=this,q=p.slides=a.slides=[],r="uib-slideDirection",s=a.active,t=[],u=!1;p.addSlide=function(b,c){q.push({slide:b,element:c}),q.sort(function(a,b){return+a.slide.index>+b.slide.index}),(b.index===a.active||1===q.length&&!angular.isNumber(a.active))&&(a.$currentTransition&&(a.$currentTransition=null),s=b.index,a.active=b.index,g(s),p.select(q[i(b)]),1===q.length&&a.play())},p.getCurrentIndex=function(){for(var a=0;a0&&s===c?c>=q.length?(s=q.length-1,a.active=s,g(s),p.select(q[q.length-1])):(s=c,a.active=s,g(s),p.select(q[c])):s>c&&(s--,a.active=s),0===q.length&&(s=null,a.active=null,f())},p.select=a.select=function(b,c){var d=i(b.slide);void 0===c&&(c=d>p.getCurrentIndex()?"next":"prev"),b.slide.index===s||a.$currentTransition?b&&b.slide.index!==s&&a.$currentTransition&&t.push(q[d]):h(b.slide,d,c)},a.indexOfSlide=function(a){return+a.slide.index},a.isActive=function(b){return a.active===b.slide.index},a.pause=function(){a.noPause||(o=!1,j())},a.play=function(){o||(o=!0,l())},a.$on("$destroy",function(){u=!0,j()}),a.$watch("noTransition",function(a){e.enabled(b,!a)}),a.$watch("interval",l),a.$watchCollection("slides",k),a.$watch("active",function(a){if(angular.isNumber(a)&&s!==a){for(var b=0;b-1){var g=!1;a=a.split("");for(var h=f;h-1){a=a.split(""),e[f]="("+d.regex+")",a[f]="$";for(var g=f+1,h=f+d.key.length;h>g;g++)e[g]="",a[g]="$";a=a.join(""),c.push({index:f,key:d.key,apply:d[b],matcher:d.regex})}}),{regex:new RegExp("^"+e.join("")+"$"),map:d(c,"index")}}function f(a,b,c){return 1>c?!1:1===b&&c>28?29===c&&(a%4===0&&a%100!==0||a%400===0):3===b||5===b||8===b||10===b?31>c:!0}function g(a){return parseInt(a,10)}function h(a,b){return a&&b?l(a,b):a}function i(a,b){return a&&b?l(a,b,!0):a}function j(a,b){var c=Date.parse("Jan 01, 1970 00:00:00 "+a)/6e4;return isNaN(c)?b:c}function k(a,b){return a=new Date(a.getTime()),a.setMinutes(a.getMinutes()+b),a}function l(a,b,c){c=c?-1:1;var d=j(b,a.getTimezoneOffset());return k(a,c*(d-a.getTimezoneOffset()))}var m,n,o=/[\\\^\$\*\+\?\|\[\]\(\)\.\{\}]/g;this.init=function(){m=b.id,this.parsers={},this.formatters={},n=[{key:"yyyy",regex:"\\d{4}",apply:function(a){this.year=+a},formatter:function(a){var b=new Date;return b.setFullYear(Math.abs(a.getFullYear())),c(b,"yyyy")}},{key:"yy",regex:"\\d{2}",apply:function(a){this.year=+a+2e3},formatter:function(a){var b=new Date;return b.setFullYear(Math.abs(a.getFullYear())),c(b,"yy")}},{key:"y",regex:"\\d{1,4}",apply:function(a){this.year=+a},formatter:function(a){var b=new Date;return b.setFullYear(Math.abs(a.getFullYear())),c(b,"y")}},{key:"M!",regex:"0?[1-9]|1[0-2]",apply:function(a){this.month=a-1},formatter:function(a){var b=a.getMonth();return/^[0-9]$/.test(b)?c(a,"MM"):c(a,"M")}},{key:"MMMM",regex:b.DATETIME_FORMATS.MONTH.join("|"),apply:function(a){this.month=b.DATETIME_FORMATS.MONTH.indexOf(a)},formatter:function(a){return c(a,"MMMM")}},{key:"MMM",regex:b.DATETIME_FORMATS.SHORTMONTH.join("|"),apply:function(a){this.month=b.DATETIME_FORMATS.SHORTMONTH.indexOf(a)},formatter:function(a){return c(a,"MMM")}},{key:"MM",regex:"0[1-9]|1[0-2]",apply:function(a){this.month=a-1},formatter:function(a){return c(a,"MM")}},{key:"M",regex:"[1-9]|1[0-2]",apply:function(a){this.month=a-1},formatter:function(a){return c(a,"M")}},{key:"d!",regex:"[0-2]?[0-9]{1}|3[0-1]{1}",apply:function(a){this.date=+a},formatter:function(a){var b=a.getDate();return/^[1-9]$/.test(b)?c(a,"dd"):c(a,"d")}},{key:"dd",regex:"[0-2][0-9]{1}|3[0-1]{1}",apply:function(a){this.date=+a},formatter:function(a){return c(a,"dd")}},{key:"d",regex:"[1-2]?[0-9]{1}|3[0-1]{1}",apply:function(a){this.date=+a},formatter:function(a){return c(a,"d")}},{key:"EEEE",regex:b.DATETIME_FORMATS.DAY.join("|"),formatter:function(a){return c(a,"EEEE")}},{key:"EEE",regex:b.DATETIME_FORMATS.SHORTDAY.join("|"),formatter:function(a){return c(a,"EEE")}},{key:"HH",regex:"(?:0|1)[0-9]|2[0-3]",apply:function(a){this.hours=+a},formatter:function(a){return c(a,"HH")}},{key:"hh",regex:"0[0-9]|1[0-2]",apply:function(a){this.hours=+a},formatter:function(a){return c(a,"hh")}},{key:"H",regex:"1?[0-9]|2[0-3]",apply:function(a){this.hours=+a},formatter:function(a){return c(a,"H")}},{key:"h",regex:"[0-9]|1[0-2]",apply:function(a){this.hours=+a},formatter:function(a){return c(a,"h")}},{key:"mm",regex:"[0-5][0-9]",apply:function(a){this.minutes=+a},formatter:function(a){return c(a,"mm")}},{key:"m",regex:"[0-9]|[1-5][0-9]",apply:function(a){this.minutes=+a},formatter:function(a){return c(a,"m")}},{key:"sss",regex:"[0-9][0-9][0-9]",apply:function(a){this.milliseconds=+a},formatter:function(a){return c(a,"sss")}},{key:"ss",regex:"[0-5][0-9]",apply:function(a){this.seconds=+a},formatter:function(a){return c(a,"ss")}},{key:"s",regex:"[0-9]|[1-5][0-9]",apply:function(a){this.seconds=+a},formatter:function(a){return c(a,"s")}},{key:"a",regex:b.DATETIME_FORMATS.AMPMS.join("|"),apply:function(a){12===this.hours&&(this.hours=0),"PM"===a&&(this.hours+=12)},formatter:function(a){return c(a,"a")}},{key:"Z",regex:"[+-]\\d{4}",apply:function(a){var b=a.match(/([+-])(\d{2})(\d{2})/),c=b[1],d=b[2],e=b[3];this.hours+=g(c+d),this.minutes+=g(c+e)},formatter:function(a){return c(a,"Z")}},{key:"ww",regex:"[0-4][0-9]|5[0-3]",formatter:function(a){return c(a,"ww")}},{key:"w",regex:"[0-9]|[1-4][0-9]|5[0-3]",formatter:function(a){return c(a,"w")}},{key:"GGGG",regex:b.DATETIME_FORMATS.ERANAMES.join("|").replace(/\s/g,"\\s"),formatter:function(a){return c(a,"GGGG")}},{key:"GGG",regex:b.DATETIME_FORMATS.ERAS.join("|"),formatter:function(a){return c(a,"GGG")}},{key:"GG",regex:b.DATETIME_FORMATS.ERAS.join("|"),formatter:function(a){return c(a,"GG")}},{key:"G",regex:b.DATETIME_FORMATS.ERAS.join("|"),formatter:function(a){return c(a,"G")}}]},this.init(),this.filter=function(a,c){if(!angular.isDate(a)||isNaN(a)||!c)return"";c=b.DATETIME_FORMATS[c]||c,b.id!==m&&this.init(),this.formatters[c]||(this.formatters[c]=e(c,"formatter"));var d=this.formatters[c],f=d.map,g=c;return f.reduce(function(b,c,d){var e=g.match(new RegExp("(.*)"+c.key));e&&angular.isString(e[1])&&(b+=e[1],g=g.replace(e[1]+c.key,""));var h=d===f.length-1?g:"";return c.apply?b+c.apply.call(null,a)+h:b+h},"")},this.parse=function(c,d,g){if(!angular.isString(c)||!d)return c;d=b.DATETIME_FORMATS[d]||d,d=d.replace(o,"\\$&"),b.id!==m&&this.init(),this.parsers[d]||(this.parsers[d]=e(d,"apply"));var h=this.parsers[d],i=h.regex,j=h.map,k=c.match(i),l=!1;if(k&&k.length){var n,p;angular.isDate(g)&&!isNaN(g.getTime())?n={year:g.getFullYear(),month:g.getMonth(),date:g.getDate(),hours:g.getHours(),minutes:g.getMinutes(),seconds:g.getSeconds(),milliseconds:g.getMilliseconds()}:(g&&a.warn("dateparser:","baseDate is not a valid date"),n={year:1900,month:0,date:1,hours:0,minutes:0,seconds:0,milliseconds:0});for(var q=1,r=k.length;r>q;q++){var s=j[q-1];"Z"===s.matcher&&(l=!0),s.apply&&s.apply.call(n,k[q])}var t=l?Date.prototype.setUTCFullYear:Date.prototype.setFullYear,u=l?Date.prototype.setUTCHours:Date.prototype.setHours;return f(n.year,n.month,n.date)&&(!angular.isDate(g)||isNaN(g.getTime())||l?(p=new Date(0),t.call(p,n.year,n.month,n.date),u.call(p,n.hours||0,n.minutes||0,n.seconds||0,n.milliseconds||0)):(p=new Date(g),t.call(p,n.year,n.month,n.date),u.call(p,n.hours,n.minutes,n.seconds,n.milliseconds))),p}},this.toTimezone=h,this.fromTimezone=i,this.timezoneToOffset=j,this.addDateMinutes=k,this.convertTimezoneToLocal=l}]),angular.module("ui.bootstrap.isClass",[]).directive("uibIsClass",["$animate",function(a){var b=/^\s*([\s\S]+?)\s+on\s+([\s\S]+?)\s*$/,c=/^\s*([\s\S]+?)\s+for\s+([\s\S]+?)\s*$/;return{restrict:"A",compile:function(d,e){function f(a,b,c){i.push(a),j.push({scope:a,element:b}),o.forEach(function(b,c){g(b,a)}),a.$on("$destroy",h)}function g(b,d){var e=b.match(c),f=d.$eval(e[1]),g=e[2],h=k[b];if(!h){var i=function(b){var c=null;j.some(function(a){var d=a.scope.$eval(m);return d===b?(c=a,!0):void 0}),h.lastActivated!==c&&(h.lastActivated&&a.removeClass(h.lastActivated.element,f),c&&a.addClass(c.element,f),h.lastActivated=c)};k[b]=h={lastActivated:null,scope:d,watchFn:i,compareWithExp:g,watcher:d.$watch(g,i)}}h.watchFn(d.$eval(g))}function h(a){var b=a.targetScope,c=i.indexOf(b);if(i.splice(c,1),j.splice(c,1),i.length){var d=i[0];angular.forEach(k,function(a){a.scope===b&&(a.watcher=d.$watch(a.compareWithExp,a.watchFn),a.scope=d)})}else k={}}var i=[],j=[],k={},l=e.uibIsClass.match(b),m=l[2],n=l[1],o=n.split(",");return f}}}]),angular.module("ui.bootstrap.position",[]).factory("$uibPosition",["$document","$window",function(a,b){var c,d={normal:/(auto|scroll)/,hidden:/(auto|scroll|hidden)/},e={auto:/\s?auto?\s?/i,primary:/^(top|bottom|left|right)$/,secondary:/^(top|bottom|left|right|center)$/,vertical:/^(top|bottom)$/};return{getRawNode:function(a){return a.nodeName?a:a[0]||a},parseStyle:function(a){return a=parseFloat(a),isFinite(a)?a:0},offsetParent:function(c){function d(a){return"static"===(b.getComputedStyle(a).position||"static")}c=this.getRawNode(c);for(var e=c.offsetParent||a[0].documentElement;e&&e!==a[0].documentElement&&d(e);)e=e.offsetParent;return e||a[0].documentElement},scrollbarWidth:function(){if(angular.isUndefined(c)){var b=angular.element('
');a.find("body").append(b),c=b[0].offsetWidth-b[0].clientWidth,c=isFinite(c)?c:0,b.remove()}return c},isScrollable:function(a,c){a=this.getRawNode(a);var e=c?d.hidden:d.normal,f=b.getComputedStyle(a);return e.test(f.overflow+f.overflowY+f.overflowX)},scrollParent:function(c,e){c=this.getRawNode(c);var f=e?d.hidden:d.normal,g=a[0].documentElement,h=b.getComputedStyle(c),i="absolute"===h.position,j=c.parentElement||g;if(j===g||"fixed"===h.position)return g;for(;j.parentElement&&j!==g;){var k=b.getComputedStyle(j);if(i&&"static"!==k.position&&(i=!1),!i&&f.test(k.overflow+k.overflowY+k.overflowX))break;j=j.parentElement}return j},position:function(c,d){c=this.getRawNode(c);var e=this.offset(c);if(d){var f=b.getComputedStyle(c);e.top-=this.parseStyle(f.marginTop),e.left-=this.parseStyle(f.marginLeft)}var g=this.offsetParent(c),h={top:0,left:0};return g!==a[0].documentElement&&(h=this.offset(g),h.top+=g.clientTop-g.scrollTop,h.left+=g.clientLeft-g.scrollLeft),{width:Math.round(angular.isNumber(e.width)?e.width:c.offsetWidth),height:Math.round(angular.isNumber(e.height)?e.height:c.offsetHeight),top:Math.round(e.top-h.top),left:Math.round(e.left-h.left)}},offset:function(c){c=this.getRawNode(c);var d=c.getBoundingClientRect();return{width:Math.round(angular.isNumber(d.width)?d.width:c.offsetWidth),height:Math.round(angular.isNumber(d.height)?d.height:c.offsetHeight),top:Math.round(d.top+(b.pageYOffset||a[0].documentElement.scrollTop)),left:Math.round(d.left+(b.pageXOffset||a[0].documentElement.scrollLeft))}},viewportOffset:function(c,d,e){c=this.getRawNode(c),e=e!==!1?!0:!1;var f=c.getBoundingClientRect(),g={top:0,left:0,bottom:0,right:0},h=d?a[0].documentElement:this.scrollParent(c),i=h.getBoundingClientRect();if(g.top=i.top+h.clientTop,g.left=i.left+h.clientLeft,h===a[0].documentElement&&(g.top+=b.pageYOffset,g.left+=b.pageXOffset),g.bottom=g.top+h.clientHeight,g.right=g.left+h.clientWidth,e){var j=b.getComputedStyle(h);g.top+=this.parseStyle(j.paddingTop),g.bottom-=this.parseStyle(j.paddingBottom),g.left+=this.parseStyle(j.paddingLeft),g.right-=this.parseStyle(j.paddingRight)}return{top:Math.round(f.top-g.top),bottom:Math.round(g.bottom-f.bottom),left:Math.round(f.left-g.left),right:Math.round(g.right-f.right)}},parsePlacement:function(a){var b=e.auto.test(a);return b&&(a=a.replace(e.auto,"")),a=a.split("-"),a[0]=a[0]||"top",e.primary.test(a[0])||(a[0]="top"),a[1]=a[1]||"center",e.secondary.test(a[1])||(a[1]="center"),b?a[2]=!0:a[2]=!1,a},positionElements:function(a,c,d,f){a=this.getRawNode(a),c=this.getRawNode(c);var g=angular.isDefined(c.offsetWidth)?c.offsetWidth:c.prop("offsetWidth"),h=angular.isDefined(c.offsetHeight)?c.offsetHeight:c.prop("offsetHeight");d=this.parsePlacement(d);var i=f?this.offset(a):this.position(a),j={top:0,left:0,placement:""};if(d[2]){var k=this.viewportOffset(a,f),l=b.getComputedStyle(c),m={width:g+Math.round(Math.abs(this.parseStyle(l.marginLeft)+this.parseStyle(l.marginRight))),height:h+Math.round(Math.abs(this.parseStyle(l.marginTop)+this.parseStyle(l.marginBottom)))};if(d[0]="top"===d[0]&&m.height>k.top&&m.height<=k.bottom?"bottom":"bottom"===d[0]&&m.height>k.bottom&&m.height<=k.top?"top":"left"===d[0]&&m.width>k.left&&m.width<=k.right?"right":"right"===d[0]&&m.width>k.right&&m.width<=k.left?"left":d[0],d[1]="top"===d[1]&&m.height-i.height>k.bottom&&m.height-i.height<=k.top?"bottom":"bottom"===d[1]&&m.height-i.height>k.top&&m.height-i.height<=k.bottom?"top":"left"===d[1]&&m.width-i.width>k.right&&m.width-i.width<=k.left?"right":"right"===d[1]&&m.width-i.width>k.left&&m.width-i.width<=k.right?"left":d[1],"center"===d[1])if(e.vertical.test(d[0])){var n=i.width/2-g/2;k.left+n<0&&m.width-i.width<=k.right?d[1]="left":k.right+n<0&&m.width-i.width<=k.left&&(d[1]="right")}else{var o=i.height/2-m.height/2;k.top+o<0&&m.height-i.height<=k.bottom?d[1]="top":k.bottom+o<0&&m.height-i.height<=k.top&&(d[1]="bottom")}}switch(d[0]){case"top":j.top=i.top-h;break;case"bottom":j.top=i.top+i.height;break;case"left":j.left=i.left-g;break;case"right":j.left=i.left+i.width}switch(d[1]){case"top":j.top=i.top;break;case"bottom":j.top=i.top+i.height-h;break;case"left":j.left=i.left;break;case"right":j.left=i.left+i.width-g;break;case"center":e.vertical.test(d[0])?j.left=i.left+i.width/2-g/2:j.top=i.top+i.height/2-h/2}return j.top=Math.round(j.top),j.left=Math.round(j.left),j.placement="center"===d[1]?d[0]:d[0]+"-"+d[1],j},positionArrow:function(a,c){a=this.getRawNode(a);var d=a.querySelector(".tooltip-inner, .popover-inner");if(d){var f=angular.element(d).hasClass("tooltip-inner"),g=f?a.querySelector(".tooltip-arrow"):a.querySelector(".arrow");if(g){var h={top:"",bottom:"",left:"",right:""};if(c=this.parsePlacement(c),"center"===c[1])return void angular.element(g).css(h);var i="border-"+c[0]+"-width",j=b.getComputedStyle(g)[i],k="border-";k+=e.vertical.test(c[0])?c[0]+"-"+c[1]:c[1]+"-"+c[0],k+="-radius";var l=b.getComputedStyle(f?d:a)[k];switch(c[0]){case"top":h.bottom=f?"0":"-"+j;break;case"bottom":h.top=f?"0":"-"+j;break;case"left":h.right=f?"0":"-"+j;break;case"right":h.left=f?"0":"-"+j}h[c[1]]=l,angular.element(g).css(h)}}}}}]),angular.module("ui.bootstrap.datepicker",["ui.bootstrap.dateparser","ui.bootstrap.isClass","ui.bootstrap.position"]).value("$datepickerSuppressError",!1).value("uibDatepickerAttributeWarning",!0).constant("uibDatepickerConfig",{datepickerMode:"day",formatDay:"dd",formatMonth:"MMMM",formatYear:"yyyy",formatDayHeader:"EEE",formatDayTitle:"MMMM yyyy",formatMonthTitle:"yyyy",maxDate:null,maxMode:"year",minDate:null,minMode:"day",ngModelOptions:{},shortcutPropagation:!1,showWeeks:!0,yearColumns:5,yearRows:4}).controller("UibDatepickerController",["$scope","$attrs","$parse","$interpolate","$locale","$log","dateFilter","uibDatepickerConfig","$datepickerSuppressError","uibDatepickerAttributeWarning","uibDateParser",function(a,b,c,d,e,f,g,h,i,j,k){function l(b){a.datepickerMode=b,q&&(a.datepickerOptions.datepickerMode=b)}var m=this,n={$setViewValue:angular.noop},o={},p=[],q=!!b.datepickerOptions;if(this.modes=["day","month","year"],q)["customClass","dateDisabled","datepickerMode","formatDay","formatDayHeader","formatDayTitle","formatMonth","formatMonthTitle","formatYear","initDate","maxDate","maxMode","minDate","minMode","showWeeks","shortcutPropagation","startingDay","yearColumns","yearRows"].forEach(function(b){switch(b){case"customClass":case"dateDisabled":a[b]=a.datepickerOptions[b]||angular.noop;break;case"datepickerMode":a.datepickerMode=angular.isDefined(a.datepickerOptions.datepickerMode)?a.datepickerOptions.datepickerMode:h.datepickerMode;break;case"formatDay":case"formatDayHeader":case"formatDayTitle":case"formatMonth":case"formatMonthTitle":case"formatYear":m[b]=angular.isDefined(a.datepickerOptions[b])?d(a.datepickerOptions[b])(a.$parent):h[b];break;case"showWeeks":case"shortcutPropagation":case"yearColumns":case"yearRows":m[b]=angular.isDefined(a.datepickerOptions[b])?a.datepickerOptions[b]:h[b];break;case"startingDay":angular.isDefined(a.datepickerOptions.startingDay)?m.startingDay=a.datepickerOptions.startingDay:angular.isNumber(h.startingDay)?m.startingDay=h.startingDay:m.startingDay=(e.DATETIME_FORMATS.FIRSTDAYOFWEEK+8)%7;break;case"maxDate":case"minDate":a.datepickerOptions[b]?a.$watch(function(){return a.datepickerOptions[b]},function(a){a?angular.isDate(a)?m[b]=k.fromTimezone(new Date(a),o.timezone):m[b]=new Date(g(a,"medium")):m[b]=null,m.refreshView()}):m[b]=h[b]?k.fromTimezone(new Date(h[b]),o.timezone):null;break;case"maxMode":case"minMode":a.datepickerOptions[b]?a.$watch(function(){return a.datepickerOptions[b]},function(c){m[b]=a[b]=angular.isDefined(c)?c:datepickerOptions[b],("minMode"===b&&m.modes.indexOf(a.datepickerOptions.datepickerMode)m.modes.indexOf(m[b]))&&(a.datepickerMode=m[b],a.datepickerOptions.datepickerMode=m[b])}):m[b]=a[b]=h[b]||null;break;case"initDate":a.datepickerOptions.initDate?(m.activeDate=k.fromTimezone(a.datepickerOptions.initDate,o.timezone)||new Date,a.$watch(function(){return a.datepickerOptions.initDate},function(a){a&&(n.$isEmpty(n.$modelValue)||n.$invalid)&&(m.activeDate=k.fromTimezone(a,o.timezone),m.refreshView())})):m.activeDate=new Date}});else{if(angular.forEach(["formatDay","formatMonth","formatYear","formatDayHeader","formatDayTitle","formatMonthTitle"],function(c){m[c]=angular.isDefined(b[c])?d(b[c])(a.$parent):h[c],angular.isDefined(b[c])&&j&&f.warn("uib-datepicker "+c+" attribute usage is deprecated, use datepicker-options attribute instead")}),angular.forEach(["showWeeks","yearRows","yearColumns","shortcutPropagation"],function(c){m[c]=angular.isDefined(b[c])?a.$parent.$eval(b[c]):h[c],angular.isDefined(b[c])&&j&&f.warn("uib-datepicker "+c+" attribute usage is deprecated, use datepicker-options attribute instead")}),angular.forEach(["dateDisabled","customClass"],function(a){angular.isDefined(b[a])&&j&&f.warn("uib-datepicker "+a+" attribute usage is deprecated, use datepicker-options attribute instead")}),angular.isDefined(b.startingDay)?(j&&f.warn("uib-datepicker startingDay attribute usage is deprecated, use datepicker-options attribute instead"),m.startingDay=a.$parent.$eval(b.startingDay)):angular.isNumber(h.startingDay)?m.startingDay=h.startingDay:m.startingDay=(e.DATETIME_FORMATS.FIRSTDAYOFWEEK+8)%7,angular.forEach(["minDate","maxDate"],function(c){b[c]?(j&&f.warn("uib-datepicker "+c+" attribute usage is deprecated, use datepicker-options attribute instead"),p.push(a.$parent.$watch(b[c],function(a){a?angular.isDate(a)?m[c]=k.fromTimezone(new Date(a),o.timezone):m[c]=new Date(g(a,"medium")):m[c]=null,m.refreshView()}))):m[c]=h[c]?k.fromTimezone(new Date(h[c]),o.timezone):null}),angular.forEach(["minMode","maxMode"],function(c){b[c]?(j&&f.warn("uib-datepicker "+c+" attribute usage is deprecated, use datepicker-options attribute instead"),p.push(a.$parent.$watch(b[c],function(d){m[c]=a[c]=angular.isDefined(d)?d:b[c],("minMode"===c&&m.modes.indexOf(a.datepickerMode)m.modes.indexOf(m[c]))&&(a.datepickerMode=m[c])}))):m[c]=a[c]=h[c]||null}),angular.isDefined(b.initDate)){j&&f.warn("uib-datepicker initDate attribute usage is deprecated, use datepicker-options attribute instead");var r=k.fromTimezone(a.$parent.$eval(b.initDate),o.timezone);this.activeDate=isNaN(r)?new Date:r,p.push(a.$parent.$watch(b.initDate,function(a){a&&(n.$isEmpty(n.$modelValue)||n.$invalid)&&(a=k.fromTimezone(a,o.timezone),m.activeDate=isNaN(a)?new Date:a,m.refreshView())}))}else this.activeDate=new Date;b.datepickerMode&&j&&f.warn("uib-datepicker datepickerMode attribute usage is deprecated, use datepicker-options attribute instead"),a.datepickerMode=a.datepickerMode||h.datepickerMode}a.uniqueId="datepicker-"+a.$id+"-"+Math.floor(1e4*Math.random()),a.disabled=angular.isDefined(b.disabled)||!1,angular.isDefined(b.ngDisabled)&&p.push(a.$parent.$watch(b.ngDisabled,function(b){a.disabled=b,m.refreshView()})),a.isActive=function(b){return 0===m.compare(b.date,m.activeDate)?(a.activeDateId=b.uid,!0):!1},this.init=function(a){n=a,o=a.$options||h.ngModelOptions,this.activeDate=n.$modelValue||new Date,n.$render=function(){m.render()}},this.render=function(){if(n.$viewValue){var a=new Date(n.$viewValue),b=!isNaN(a);b?this.activeDate=k.fromTimezone(a,o.timezone):i||f.error('Datepicker directive: "ng-model" value must be a Date object')}this.refreshView()},this.refreshView=function(){if(this.element){a.selectedDt=null,this._refreshView(),a.activeDt&&(a.activeDateId=a.activeDt.uid);var b=n.$viewValue?new Date(n.$viewValue):null;b=k.fromTimezone(b,o.timezone),n.$setValidity("dateDisabled",!b||this.element&&!this.isDisabled(b))}},this.createDateObject=function(b,c){var d=n.$viewValue?new Date(n.$viewValue):null;d=k.fromTimezone(d,o.timezone);var e=new Date;e=k.fromTimezone(e,o.timezone);var f=this.compare(b,e),g={date:b,label:k.filter(b,c),selected:d&&0===this.compare(b,d),disabled:this.isDisabled(b),past:0>f,current:0===f,future:f>0,customClass:this.customClass(b)||null};return d&&0===this.compare(b,d)&&(a.selectedDt=g),m.activeDate&&0===this.compare(g.date,m.activeDate)&&(a.activeDt=g),g},this.isDisabled=function(b){return a.disabled||this.minDate&&this.compare(b,this.minDate)<0||this.maxDate&&this.compare(b,this.maxDate)>0||a.dateDisabled&&a.dateDisabled({date:b,mode:a.datepickerMode})},this.customClass=function(b){return a.customClass({date:b,mode:a.datepickerMode})},this.split=function(a,b){for(var c=[];a.length>0;)c.push(a.splice(0,b));return c; +},a.select=function(b){if(a.datepickerMode===m.minMode){var c=n.$viewValue?k.fromTimezone(new Date(n.$viewValue),o.timezone):new Date(0,0,0,0,0,0,0);c.setFullYear(b.getFullYear(),b.getMonth(),b.getDate()),c=k.toTimezone(c,o.timezone),n.$setViewValue(c),n.$render()}else m.activeDate=b,l(m.modes[m.modes.indexOf(a.datepickerMode)-1]),a.$emit("uib:datepicker.mode")},a.move=function(a){var b=m.activeDate.getFullYear()+a*(m.step.years||0),c=m.activeDate.getMonth()+a*(m.step.months||0);m.activeDate.setFullYear(b,c,1),m.refreshView()},a.toggleMode=function(b){b=b||1,a.datepickerMode===m.maxMode&&1===b||a.datepickerMode===m.minMode&&-1===b||(l(m.modes[m.modes.indexOf(a.datepickerMode)+b]),a.$emit("uib:datepicker.mode"))},a.keys={13:"enter",32:"space",33:"pageup",34:"pagedown",35:"end",36:"home",37:"left",38:"up",39:"right",40:"down"};var s=function(){m.element[0].focus()};a.$on("uib:datepicker.focus",s),a.keydown=function(b){var c=a.keys[b.which];if(c&&!b.shiftKey&&!b.altKey&&!a.disabled)if(b.preventDefault(),m.shortcutPropagation||b.stopPropagation(),"enter"===c||"space"===c){if(m.isDisabled(m.activeDate))return;a.select(m.activeDate)}else!b.ctrlKey||"up"!==c&&"down"!==c?(m.handleKeyDown(c,b),m.refreshView()):a.toggleMode("up"===c?1:-1)},a.$on("$destroy",function(){for(;p.length;)p.shift()()})}]).controller("UibDaypickerController",["$scope","$element","dateFilter",function(a,b,c){function d(a,b){return 1!==b||a%4!==0||a%100===0&&a%400!==0?f[b]:29}function e(a){var b=new Date(a);b.setDate(b.getDate()+4-(b.getDay()||7));var c=b.getTime();return b.setMonth(0),b.setDate(1),Math.floor(Math.round((c-b)/864e5)/7)+1}var f=[31,28,31,30,31,30,31,31,30,31,30,31];this.step={months:1},this.element=b,this.init=function(b){angular.extend(b,this),a.showWeeks=b.showWeeks,b.refreshView()},this.getDates=function(a,b){for(var c,d=new Array(b),e=new Date(a),f=0;b>f;)c=new Date(e),d[f++]=c,e.setDate(e.getDate()+1);return d},this._refreshView=function(){var b=this.activeDate.getFullYear(),d=this.activeDate.getMonth(),f=new Date(this.activeDate);f.setFullYear(b,d,1);var g=this.startingDay-f.getDay(),h=g>0?7-g:-g,i=new Date(f);h>0&&i.setDate(-h+1);for(var j=this.getDates(i,42),k=0;42>k;k++)j[k]=angular.extend(this.createDateObject(j[k],this.formatDay),{secondary:j[k].getMonth()!==d,uid:a.uniqueId+"-"+k});a.labels=new Array(7);for(var l=0;7>l;l++)a.labels[l]={abbr:c(j[l].date,this.formatDayHeader),full:c(j[l].date,"EEEE")};if(a.title=c(this.activeDate,this.formatDayTitle),a.rows=this.split(j,7),a.showWeeks){a.weekNumbers=[];for(var m=(11-this.startingDay)%7,n=a.rows.length,o=0;n>o;o++)a.weekNumbers.push(e(a.rows[o][m].date))}},this.compare=function(a,b){var c=new Date(a.getFullYear(),a.getMonth(),a.getDate()),d=new Date(b.getFullYear(),b.getMonth(),b.getDate());return c.setFullYear(a.getFullYear()),d.setFullYear(b.getFullYear()),c-d},this.handleKeyDown=function(a,b){var c=this.activeDate.getDate();if("left"===a)c-=1;else if("up"===a)c-=7;else if("right"===a)c+=1;else if("down"===a)c+=7;else if("pageup"===a||"pagedown"===a){var e=this.activeDate.getMonth()+("pageup"===a?-1:1);this.activeDate.setMonth(e,1),c=Math.min(d(this.activeDate.getFullYear(),this.activeDate.getMonth()),c)}else"home"===a?c=1:"end"===a&&(c=d(this.activeDate.getFullYear(),this.activeDate.getMonth()));this.activeDate.setDate(c)}}]).controller("UibMonthpickerController",["$scope","$element","dateFilter",function(a,b,c){this.step={years:1},this.element=b,this.init=function(a){angular.extend(a,this),a.refreshView()},this._refreshView=function(){for(var b,d=new Array(12),e=this.activeDate.getFullYear(),f=0;12>f;f++)b=new Date(this.activeDate),b.setFullYear(e,f,1),d[f]=angular.extend(this.createDateObject(b,this.formatMonth),{uid:a.uniqueId+"-"+f});a.title=c(this.activeDate,this.formatMonthTitle),a.rows=this.split(d,3)},this.compare=function(a,b){var c=new Date(a.getFullYear(),a.getMonth()),d=new Date(b.getFullYear(),b.getMonth());return c.setFullYear(a.getFullYear()),d.setFullYear(b.getFullYear()),c-d},this.handleKeyDown=function(a,b){var c=this.activeDate.getMonth();if("left"===a)c-=1;else if("up"===a)c-=3;else if("right"===a)c+=1;else if("down"===a)c+=3;else if("pageup"===a||"pagedown"===a){var d=this.activeDate.getFullYear()+("pageup"===a?-1:1);this.activeDate.setFullYear(d)}else"home"===a?c=0:"end"===a&&(c=11);this.activeDate.setMonth(c)}}]).controller("UibYearpickerController",["$scope","$element","dateFilter",function(a,b,c){function d(a){return parseInt((a-1)/f,10)*f+1}var e,f;this.element=b,this.yearpickerInit=function(){e=this.yearColumns,f=this.yearRows*e,this.step={years:f}},this._refreshView=function(){for(var b,c=new Array(f),g=0,h=d(this.activeDate.getFullYear());f>g;g++)b=new Date(this.activeDate),b.setFullYear(h+g,0,1),c[g]=angular.extend(this.createDateObject(b,this.formatYear),{uid:a.uniqueId+"-"+g});a.title=[c[0].label,c[f-1].label].join(" - "),a.rows=this.split(c,e),a.columns=e},this.compare=function(a,b){return a.getFullYear()-b.getFullYear()},this.handleKeyDown=function(a,b){var c=this.activeDate.getFullYear();"left"===a?c-=1:"up"===a?c-=e:"right"===a?c+=1:"down"===a?c+=e:"pageup"===a||"pagedown"===a?c+=("pageup"===a?-1:1)*f:"home"===a?c=d(this.activeDate.getFullYear()):"end"===a&&(c=d(this.activeDate.getFullYear())+f-1),this.activeDate.setFullYear(c)}}]).directive("uibDatepicker",function(){return{replace:!0,templateUrl:function(a,b){return b.templateUrl||"uib/template/datepicker/datepicker.html"},scope:{datepickerMode:"=?",datepickerOptions:"=?",dateDisabled:"&",customClass:"&",shortcutPropagation:"&?"},require:["uibDatepicker","^ngModel"],controller:"UibDatepickerController",controllerAs:"datepicker",link:function(a,b,c,d){var e=d[0],f=d[1];e.init(f)}}}).directive("uibDaypicker",function(){return{replace:!0,templateUrl:function(a,b){return b.templateUrl||"uib/template/datepicker/day.html"},require:["^uibDatepicker","uibDaypicker"],controller:"UibDaypickerController",link:function(a,b,c,d){var e=d[0],f=d[1];f.init(e)}}}).directive("uibMonthpicker",function(){return{replace:!0,templateUrl:function(a,b){return b.templateUrl||"uib/template/datepicker/month.html"},require:["^uibDatepicker","uibMonthpicker"],controller:"UibMonthpickerController",link:function(a,b,c,d){var e=d[0],f=d[1];f.init(e)}}}).directive("uibYearpicker",function(){return{replace:!0,templateUrl:function(a,b){return b.templateUrl||"uib/template/datepicker/year.html"},require:["^uibDatepicker","uibYearpicker"],controller:"UibYearpickerController",link:function(a,b,c,d){var e=d[0];angular.extend(e,d[1]),e.yearpickerInit(),e.refreshView()}}}).value("uibDatepickerPopupAttributeWarning",!0).constant("uibDatepickerPopupConfig",{altInputFormats:[],appendToBody:!1,clearText:"Clear",closeOnDateSelection:!0,closeText:"Done",currentText:"Today",datepickerPopup:"yyyy-MM-dd",datepickerPopupTemplateUrl:"uib/template/datepicker/popup.html",datepickerTemplateUrl:"uib/template/datepicker/datepicker.html",html5Types:{date:"yyyy-MM-dd","datetime-local":"yyyy-MM-ddTHH:mm:ss.sss",month:"yyyy-MM"},onOpenFocus:!0,showButtonBar:!0,placement:"auto bottom-left"}).controller("UibDatepickerPopupController",["$scope","$element","$attrs","$compile","$log","$parse","$window","$document","$rootScope","$uibPosition","dateFilter","uibDateParser","uibDatepickerPopupConfig","$timeout","uibDatepickerConfig","uibDatepickerPopupAttributeWarning",function(a,b,c,d,e,f,g,h,i,j,k,l,m,n,o,p){function q(a){return a.replace(/([A-Z])/g,function(a){return"-"+a.toLowerCase()})}function r(b){var c=l.parse(b,x,a.date);if(isNaN(c))for(var d=0;d
"),a.ngModelOptions=angular.copy(H),a.ngModelOptions.timezone=null,a.ngModelOptions.updateOnDefault===!0&&(a.ngModelOptions.updateOn=a.ngModelOptions.updateOn?a.ngModelOptions.updateOn+" default":"default"),D.attr({"ng-model":"date","ng-model-options":"ngModelOptions","ng-change":"dateSelection(date)","template-url":B}),E=angular.element(D.children()[0]),E.attr("template-url",C),L&&"month"===c.type&&(E.attr("datepicker-mode",'"month"'),E.attr("min-mode","month")),a.datepickerOptions&&E.attr("datepicker-options","datepickerOptions"),angular.forEach(["minMode","maxMode","datepickerMode","shortcutPropagation"],function(b){if(c[b]){p&&e.warn("uib-datepicker settings via uib-datepicker-popup attributes are deprecated and will be removed in UI Bootstrap 1.3, use datepicker-options attribute instead");var d=f(c[b]),g={get:function(){return d(a.$parent)}};if(E.attr(q(b),"watchData."+b),"datepickerMode"===b){var h=d.assign;g.set=function(b){h(a.$parent,b)}}Object.defineProperty(a.watchData,b,g)}}),angular.forEach(["minDate","maxDate","initDate"],function(b){if(c[b]){p&&e.warn("uib-datepicker settings via uib-datepicker-popup attributes are deprecated and will be removed in UI Bootstrap 1.3, use datepicker-options attribute instead");var d=f(c[b]);M.push(a.$parent.$watch(d,function(c){if("minDate"===b||"maxDate"===b)null===c?K[b]=null:angular.isDate(c)?K[b]=l.fromTimezone(new Date(c),H.timezone):K[b]=new Date(k(c,"medium")),a.watchData[b]=null===c?null:K[b];else{var d=c?new Date(c):new Date;a.watchData[b]=l.fromTimezone(d,H.timezone)}})),E.attr(q(b),"watchData."+b)}}),c.dateDisabled&&(p&&e.warn("uib-datepicker settings via uib-datepicker-popup attributes are deprecated and will be removed in UI Bootstrap 1.3, use datepicker-options attribute instead"),E.attr("date-disabled","dateDisabled({ date: date, mode: mode })")),angular.forEach(["formatDay","formatMonth","formatYear","formatDayHeader","formatDayTitle","formatMonthTitle","showWeeks","startingDay","yearRows","yearColumns"],function(a){angular.isDefined(c[a])&&(p&&e.warn("uib-datepicker settings via uib-datepicker-popup attributes are deprecated and will be removed in UI Bootstrap 1.3, use datepicker-options attribute instead"),E.attr(q(a),c[a]))}),c.customClass&&(p&&e.warn("uib-datepicker settings via uib-datepicker-popup attributes are deprecated and will be removed in UI Bootstrap 1.3, use datepicker-options attribute instead"),E.attr("custom-class","customClass({ date: date, mode: mode })")),L?G.$formatters.push(function(b){return a.date=l.fromTimezone(b,H.timezone),b}):(G.$$parserName="date",G.$validators.date=t,G.$parsers.unshift(s),G.$formatters.push(function(b){return G.$isEmpty(b)?(a.date=b,b):(a.date=l.fromTimezone(b,H.timezone),angular.isNumber(a.date)&&(a.date=new Date(a.date)),l.filter(a.date,x))})),G.$viewChangeListeners.push(function(){a.date=r(G.$viewValue)}),b.on("keydown",v),I=d(D)(a),D.remove(),z?h.find("body").append(I):b.after(I),a.$on("$destroy",function(){for(a.isOpen===!0&&(i.$$phase||a.$apply(function(){a.isOpen=!1})),I.remove(),b.off("keydown",v),h.off("click",u),F&&F.off("scroll",w),angular.element(g).off("resize",w);M.length;)M.shift()()})},a.getText=function(b){return a[b+"Text"]||m[b+"Text"]},a.isDisabled=function(b){return"today"===b&&(b=new Date),a.watchData.minDate&&a.compare(b,K.minDate)<0||a.watchData.maxDate&&a.compare(b,K.maxDate)>0},a.compare=function(a,b){return new Date(a.getFullYear(),a.getMonth(),a.getDate())-new Date(b.getFullYear(),b.getMonth(),b.getDate())},a.dateSelection=function(c){angular.isDefined(c)&&(a.date=c);var d=a.date?l.filter(a.date,x):null;b.val(d),G.$setViewValue(d),y&&(a.isOpen=!1,b[0].focus())},a.keydown=function(c){27===c.which&&(c.stopPropagation(),a.isOpen=!1,b[0].focus())},a.select=function(b,c){if(c.stopPropagation(),"today"===b){var d=new Date;angular.isDate(a.date)?(b=new Date(a.date),b.setFullYear(d.getFullYear(),d.getMonth(),d.getDate())):b=new Date(d.setHours(0,0,0,0))}a.dateSelection(b)},a.close=function(c){c.stopPropagation(),a.isOpen=!1,b[0].focus()},a.disabled=angular.isDefined(c.disabled)||!1,c.ngDisabled&&M.push(a.$parent.$watch(f(c.ngDisabled),function(b){a.disabled=b})),a.$watch("isOpen",function(d){d?a.disabled?a.isOpen=!1:n(function(){w(),A&&a.$broadcast("uib:datepicker.focus"),h.on("click",u);var d=c.popupPlacement?c.popupPlacement:m.placement;z||j.parsePlacement(d)[2]?(F=F||angular.element(j.scrollParent(b)),F&&F.on("scroll",w)):F=null,angular.element(g).on("resize",w)},0,!1):(h.off("click",u),F&&F.off("scroll",w),angular.element(g).off("resize",w))}),a.$on("uib:datepicker.mode",function(){n(w,0,!1)})}]).directive("uibDatepickerPopup",function(){return{require:["ngModel","uibDatepickerPopup"],controller:"UibDatepickerPopupController",scope:{datepickerOptions:"=?",isOpen:"=?",currentText:"@",clearText:"@",closeText:"@",dateDisabled:"&",customClass:"&"},link:function(a,b,c,d){var e=d[0],f=d[1];f.init(e)}}}).directive("uibDatepickerPopupWrap",function(){return{replace:!0,transclude:!0,templateUrl:function(a,b){return b.templateUrl||"uib/template/datepicker/popup.html"}}}),angular.module("ui.bootstrap.debounce",[]).factory("$$debounce",["$timeout",function(a){return function(b,c){var d;return function(){var e=this,f=Array.prototype.slice.call(arguments);d&&a.cancel(d),d=a(function(){b.apply(e,f)},c)}}}]),angular.module("ui.bootstrap.dropdown",["ui.bootstrap.position"]).constant("uibDropdownConfig",{appendToOpenClass:"uib-dropdown-open",openClass:"open"}).service("uibDropdownService",["$document","$rootScope",function(a,b){var c=null;this.open=function(b){c||(a.on("click",d),a.on("keydown",e)),c&&c!==b&&(c.isOpen=!1),c=b},this.close=function(b){c===b&&(c=null,a.off("click",d),a.off("keydown",e))};var d=function(a){if(c&&!(a&&"disabled"===c.getAutoClose()||a&&3===a.which)){var d=c.getToggleElement();if(!(a&&d&&d[0].contains(a.target))){var e=c.getDropdownElement();a&&"outsideClick"===c.getAutoClose()&&e&&e[0].contains(a.target)||(c.isOpen=!1,b.$$phase||c.$apply())}}},e=function(a){27===a.which?(c.focusToggleElement(),d()):c.isKeynavEnabled()&&-1!==[38,40].indexOf(a.which)&&c.isOpen&&(a.preventDefault(),a.stopPropagation(),c.focusDropdownEntry(a.which))}}]).controller("UibDropdownController",["$scope","$element","$attrs","$parse","uibDropdownConfig","uibDropdownService","$animate","$uibPosition","$document","$compile","$templateRequest",function(a,b,c,d,e,f,g,h,i,j,k){var l,m,n=this,o=a.$new(),p=e.appendToOpenClass,q=e.openClass,r=angular.noop,s=c.onToggle?d(c.onToggle):angular.noop,t=!1,u=null,v=!1,w=i.find("body");b.addClass("dropdown"),this.init=function(){if(c.isOpen&&(m=d(c.isOpen),r=m.assign,a.$watch(m,function(a){o.isOpen=!!a})),angular.isDefined(c.dropdownAppendTo)){var e=d(c.dropdownAppendTo)(o);e&&(u=angular.element(e))}t=angular.isDefined(c.dropdownAppendToBody),v=angular.isDefined(c.keyboardNav),t&&!u&&(u=w),u&&n.dropdownMenu&&(u.append(n.dropdownMenu),b.on("$destroy",function(){n.dropdownMenu.remove()}))},this.toggle=function(a){return o.isOpen=arguments.length?!!a:!o.isOpen,angular.isFunction(r)&&r(o,o.isOpen),o.isOpen},this.isOpen=function(){return o.isOpen},o.getToggleElement=function(){return n.toggleElement},o.getAutoClose=function(){return c.autoClose||"always"},o.getElement=function(){return b},o.isKeynavEnabled=function(){return v},o.focusDropdownEntry=function(a){var c=n.dropdownMenu?angular.element(n.dropdownMenu).find("a"):b.find("ul").eq(0).find("a");switch(a){case 40:angular.isNumber(n.selectedOption)?n.selectedOption=n.selectedOption===c.length-1?n.selectedOption:n.selectedOption+1:n.selectedOption=0;break;case 38:angular.isNumber(n.selectedOption)?n.selectedOption=0===n.selectedOption?0:n.selectedOption-1:n.selectedOption=c.length-1}c[n.selectedOption].focus()},o.getDropdownElement=function(){return n.dropdownMenu},o.focusToggleElement=function(){n.toggleElement&&n.toggleElement[0].focus()},o.$watch("isOpen",function(c,d){if(u&&n.dropdownMenu){var e,i,m=h.positionElements(b,n.dropdownMenu,"bottom-left",!0);if(e={top:m.top+"px",display:c?"block":"none"},i=n.dropdownMenu.hasClass("dropdown-menu-right"),i?(e.left="auto",e.right=window.innerWidth-(m.left+b.prop("offsetWidth"))+"px"):(e.left=m.left+"px",e.right="auto"),!t){var v=h.offset(u);e.top=m.top-v.top+"px",i?e.right=window.innerWidth-(m.left-v.left+b.prop("offsetWidth"))+"px":e.left=m.left-v.left+"px"}n.dropdownMenu.css(e)}var w=u?u:b,x=w.hasClass(u?p:q);if(x===!c&&g[c?"addClass":"removeClass"](w,u?p:q).then(function(){angular.isDefined(c)&&c!==d&&s(a,{open:!!c})}),c)n.dropdownMenuTemplateUrl&&k(n.dropdownMenuTemplateUrl).then(function(a){l=o.$new(),j(a.trim())(l,function(a){var b=a;n.dropdownMenu.replaceWith(b),n.dropdownMenu=b})}),o.focusToggleElement(),f.open(o);else{if(n.dropdownMenuTemplateUrl){l&&l.$destroy();var y=angular.element('');n.dropdownMenu.replaceWith(y),n.dropdownMenu=y}f.close(o),n.selectedOption=null}angular.isFunction(r)&&r(a,c)}),a.$on("$locationChangeSuccess",function(){"disabled"!==o.getAutoClose()&&(o.isOpen=!1)})}]).directive("uibDropdown",function(){return{controller:"UibDropdownController",link:function(a,b,c,d){d.init()}}}).directive("uibDropdownMenu",function(){return{restrict:"A",require:"?^uibDropdown",link:function(a,b,c,d){if(d&&!angular.isDefined(c.dropdownNested)){b.addClass("dropdown-menu");var e=c.templateUrl;e&&(d.dropdownMenuTemplateUrl=e),d.dropdownMenu||(d.dropdownMenu=b)}}}}).directive("uibDropdownToggle",function(){return{require:"?^uibDropdown",link:function(a,b,c,d){if(d){b.addClass("dropdown-toggle"),d.toggleElement=b;var e=function(e){e.preventDefault(),b.hasClass("disabled")||c.disabled||a.$apply(function(){d.toggle()})};b.bind("click",e),b.attr({"aria-haspopup":!0,"aria-expanded":!1}),a.$watch(d.isOpen,function(a){b.attr("aria-expanded",!!a)}),a.$on("$destroy",function(){b.unbind("click",e)})}}}}),angular.module("ui.bootstrap.stackedMap",[]).factory("$$stackedMap",function(){return{createNew:function(){var a=[];return{add:function(b,c){a.push({key:b,value:c})},get:function(b){for(var c=0;c0&&(b=t.top().value,b.modalDomEl.toggleClass(b.windowTopClass||"",a))}function l(){if(p&&-1===i()){var a=q;m(p,q,function(){a=null}),p=void 0,q=void 0}}function m(b,c,d,e){function g(){g.done||(g.done=!0,a.leave(b).then(function(){b.remove(),e&&e.resolve()}),c.$destroy(),d&&d())}var h,i=null,j=function(){return h||(h=f.defer(),i=h.promise),function(){h.resolve()}};return c.$broadcast(v.NOW_CLOSING_EVENT,j),f.when(i).then(g)}function n(a){if(a.isDefaultPrevented())return a;var b=t.top();if(b)switch(a.which){case 27:b.value.keyboard&&(a.preventDefault(),e.$apply(function(){v.dismiss(b.key,"escape key press")}));break;case 9:v.loadFocusElementList(b);var c=!1;a.shiftKey?(v.isFocusInFirstItem(a)||v.isModalFocused(a,b))&&(c=v.focusLastFocusableElement()):v.isFocusInLastItem(a)&&(c=v.focusFirstFocusableElement()),c&&(a.preventDefault(),a.stopPropagation())}}function o(a,b,c){return!a.value.modalScope.$broadcast("modal.closing",b,c).defaultPrevented}var p,q,r,s="modal-open",t=h.createNew(),u=g.createNew(),v={NOW_CLOSING_EVENT:"modal.stack.now-closing"},w=0,x="a[href], area[href], input:not([disabled]), button:not([disabled]),select:not([disabled]), textarea:not([disabled]), iframe, object, embed, *[tabindex], *[contenteditable=true]";return e.$watch(i,function(a){q&&(q.index=a)}),c.on("keydown",n),e.$on("$destroy",function(){c.off("keydown",n)}),v.open=function(b,f){var g=c[0].activeElement,h=f.openedClass||s;k(!1),t.add(b,{deferred:f.deferred,renderDeferred:f.renderDeferred,closedDeferred:f.closedDeferred,modalScope:f.scope,backdrop:f.backdrop,keyboard:f.keyboard,openedClass:f.openedClass,windowTopClass:f.windowTopClass,animation:f.animation,appendTo:f.appendTo}),u.put(h,b);var j=f.appendTo,l=i();if(!j.length)throw new Error("appendTo element not found. Make sure that the element passed is in DOM.");l>=0&&!p&&(q=e.$new(!0),q.modalOptions=f,q.index=l,p=angular.element('
'),p.attr("backdrop-class",f.backdropClass),f.animation&&p.attr("modal-animation","true"),d(p)(q),a.enter(p,j));var m=angular.element('
');m.attr({"template-url":f.windowTemplateUrl,"window-class":f.windowClass,"window-top-class":f.windowTopClass,size:f.size,index:t.length()-1,animate:"animate"}).html(f.content),f.animation&&m.attr("modal-animation","true"),a.enter(d(m)(f.scope),j).then(function(){f.scope.$$uibDestructionScheduled||a.addClass(j,h)}),t.top().value.modalDomEl=m,t.top().value.modalOpener=g,v.clearFocusListCache()},v.close=function(a,b){var c=t.get(a);return c&&o(c,b,!0)?(c.value.modalScope.$$uibDestructionScheduled=!0,c.value.deferred.resolve(b),j(a,c.value.modalOpener),!0):!c},v.dismiss=function(a,b){var c=t.get(a);return c&&o(c,b,!1)?(c.value.modalScope.$$uibDestructionScheduled=!0,c.value.deferred.reject(b),j(a,c.value.modalOpener),!0):!c},v.dismissAll=function(a){for(var b=this.getTop();b&&this.dismiss(b.key,a);)b=this.getTop()},v.getTop=function(){return t.top()},v.modalRendered=function(a){var b=t.get(a);b&&b.value.renderDeferred.resolve()},v.focusFirstFocusableElement=function(){return r.length>0?(r[0].focus(),!0):!1},v.focusLastFocusableElement=function(){return r.length>0?(r[r.length-1].focus(),!0):!1},v.isModalFocused=function(a,b){if(a&&b){var c=b.value.modalDomEl;if(c&&c.length)return(a.target||a.srcElement)===c[0]}return!1},v.isFocusInFirstItem=function(a){return r.length>0?(a.target||a.srcElement)===r[0]:!1},v.isFocusInLastItem=function(a){return r.length>0?(a.target||a.srcElement)===r[r.length-1]:!1},v.clearFocusListCache=function(){r=[],w=0},v.loadFocusElementList=function(a){if((void 0===r||!r.length)&&a){var b=a.value.modalDomEl;b&&b.length&&(r=b[0].querySelectorAll(x))}},v}]).provider("$uibModal",function(){var a={options:{animation:!0,backdrop:!0,keyboard:!0},$get:["$rootScope","$q","$document","$templateRequest","$controller","$uibResolve","$uibModalStack",function(b,c,d,e,f,g,h){function i(a){return a.template?c.when(a.template):e(angular.isFunction(a.templateUrl)?a.templateUrl():a.templateUrl)}var j={},k=null;return j.getPromiseChain=function(){return k},j.open=function(e){function j(){return r}var l=c.defer(),m=c.defer(),n=c.defer(),o=c.defer(),p={result:l.promise,opened:m.promise,closed:n.promise,rendered:o.promise,close:function(a){return h.close(p,a)},dismiss:function(a){return h.dismiss(p,a)}};if(e=angular.extend({},a.options,e),e.resolve=e.resolve||{},e.appendTo=e.appendTo||d.find("body").eq(0),!e.template&&!e.templateUrl)throw new Error("One of template or templateUrl options is required.");var q,r=c.all([i(e),g.resolve(e.resolve,{},null,null)]);return q=k=c.all([k]).then(j,j).then(function(a){var c=e.scope||b,d=c.$new();d.$close=p.close,d.$dismiss=p.dismiss,d.$on("$destroy",function(){d.$$uibDestructionScheduled||d.$dismiss("$uibUnscheduledDestruction")});var g,i,j={};e.controller&&(j.$scope=d,j.$uibModalInstance=p,angular.forEach(a[1],function(a,b){j[b]=a}),i=f(e.controller,j,!0),e.controllerAs?(g=i.instance,e.bindToController&&(g.$close=d.$close,g.$dismiss=d.$dismiss,angular.extend(g,c)),g=i(),d[e.controllerAs]=g):g=i(),angular.isFunction(g.$onInit)&&g.$onInit()),h.open(p,{scope:d,deferred:l,renderDeferred:o,closedDeferred:n,content:a[0],animation:e.animation,backdrop:e.backdrop,keyboard:e.keyboard,backdropClass:e.backdropClass,windowTopClass:e.windowTopClass,windowClass:e.windowClass,windowTemplateUrl:e.windowTemplateUrl,size:e.size,openedClass:e.openedClass,appendTo:e.appendTo}),m.resolve(!0)},function(a){m.reject(a),l.reject(a)})["finally"](function(){k===q&&(k=null)}),p},j}]};return a}),angular.module("ui.bootstrap.paging",[]).factory("uibPaging",["$parse",function(a){return{create:function(b,c,d){b.setNumPages=d.numPages?a(d.numPages).assign:angular.noop,b.ngModelCtrl={$setViewValue:angular.noop},b._watchers=[],b.init=function(a,e){b.ngModelCtrl=a,b.config=e,a.$render=function(){b.render()},d.itemsPerPage?b._watchers.push(c.$parent.$watch(d.itemsPerPage,function(a){b.itemsPerPage=parseInt(a,10),c.totalPages=b.calculateTotalPages(),b.updatePage()})):b.itemsPerPage=e.itemsPerPage,c.$watch("totalItems",function(a,d){(angular.isDefined(a)||a!==d)&&(c.totalPages=b.calculateTotalPages(),b.updatePage())})},b.calculateTotalPages=function(){var a=b.itemsPerPage<1?1:Math.ceil(c.totalItems/b.itemsPerPage);return Math.max(a||0,1)},b.render=function(){c.page=parseInt(b.ngModelCtrl.$viewValue,10)||1},c.selectPage=function(a,d){d&&d.preventDefault();var e=!c.ngDisabled||!d;e&&c.page!==a&&a>0&&a<=c.totalPages&&(d&&d.target&&d.target.blur(),b.ngModelCtrl.$setViewValue(a),b.ngModelCtrl.$render())},c.getText=function(a){return c[a+"Text"]||b.config[a+"Text"]},c.noPrevious=function(){return 1===c.page},c.noNext=function(){return c.page===c.totalPages},b.updatePage=function(){b.setNumPages(c.$parent,c.totalPages),c.page>c.totalPages?c.selectPage(c.totalPages):b.ngModelCtrl.$render()},c.$on("$destroy",function(){for(;b._watchers.length;)b._watchers.shift()()})}}}]),angular.module("ui.bootstrap.pager",["ui.bootstrap.paging"]).controller("UibPagerController",["$scope","$attrs","uibPaging","uibPagerConfig",function(a,b,c,d){a.align=angular.isDefined(b.align)?a.$parent.$eval(b.align):d.align,c.create(this,a,b)}]).constant("uibPagerConfig",{itemsPerPage:10,previousText:"« Previous",nextText:"Next »",align:!0}).directive("uibPager",["uibPagerConfig",function(a){return{scope:{totalItems:"=",previousText:"@",nextText:"@",ngDisabled:"="},require:["uibPager","?ngModel"],controller:"UibPagerController",controllerAs:"pager",templateUrl:function(a,b){return b.templateUrl||"uib/template/pager/pager.html"},replace:!0,link:function(b,c,d,e){var f=e[0],g=e[1];g&&f.init(g,a)}}}]),angular.module("ui.bootstrap.pagination",["ui.bootstrap.paging"]).controller("UibPaginationController",["$scope","$attrs","$parse","uibPaging","uibPaginationConfig",function(a,b,c,d,e){function f(a,b,c){return{number:a,text:b,active:c}}function g(a,b){var c=[],d=1,e=b,g=angular.isDefined(i)&&b>i;g&&(j?(d=Math.max(a-Math.floor(i/2),1),e=d+i-1,e>b&&(e=b,d=e-i+1)):(d=(Math.ceil(a/i)-1)*i+1,e=Math.min(d+i-1,b)));for(var h=d;e>=h;h++){var n=f(h,m(h),h===a);c.push(n)}if(g&&i>0&&(!j||k||l)){if(d>1){if(!l||d>3){var o=f(d-1,"...",!1);c.unshift(o)}if(l){if(3===d){var p=f(2,"2",!1);c.unshift(p)}var q=f(1,"1",!1); +c.unshift(q)}}if(b>e){if(!l||b-2>e){var r=f(e+1,"...",!1);c.push(r)}if(l){if(e===b-2){var s=f(b-1,b-1,!1);c.push(s)}var t=f(b,b,!1);c.push(t)}}}return c}var h=this,i=angular.isDefined(b.maxSize)?a.$parent.$eval(b.maxSize):e.maxSize,j=angular.isDefined(b.rotate)?a.$parent.$eval(b.rotate):e.rotate,k=angular.isDefined(b.forceEllipses)?a.$parent.$eval(b.forceEllipses):e.forceEllipses,l=angular.isDefined(b.boundaryLinkNumbers)?a.$parent.$eval(b.boundaryLinkNumbers):e.boundaryLinkNumbers,m=angular.isDefined(b.pageLabel)?function(c){return a.$parent.$eval(b.pageLabel,{$page:c})}:angular.identity;a.boundaryLinks=angular.isDefined(b.boundaryLinks)?a.$parent.$eval(b.boundaryLinks):e.boundaryLinks,a.directionLinks=angular.isDefined(b.directionLinks)?a.$parent.$eval(b.directionLinks):e.directionLinks,d.create(this,a,b),b.maxSize&&h._watchers.push(a.$parent.$watch(c(b.maxSize),function(a){i=parseInt(a,10),h.render()}));var n=this.render;this.render=function(){n(),a.page>0&&a.page<=a.totalPages&&(a.pages=g(a.page,a.totalPages))}}]).constant("uibPaginationConfig",{itemsPerPage:10,boundaryLinks:!1,boundaryLinkNumbers:!1,directionLinks:!0,firstText:"First",previousText:"Previous",nextText:"Next",lastText:"Last",rotate:!0,forceEllipses:!1}).directive("uibPagination",["$parse","uibPaginationConfig",function(a,b){return{scope:{totalItems:"=",firstText:"@",previousText:"@",nextText:"@",lastText:"@",ngDisabled:"="},require:["uibPagination","?ngModel"],controller:"UibPaginationController",controllerAs:"pagination",templateUrl:function(a,b){return b.templateUrl||"uib/template/pagination/pagination.html"},replace:!0,link:function(a,c,d,e){var f=e[0],g=e[1];g&&f.init(g,b)}}}]),angular.module("ui.bootstrap.tooltip",["ui.bootstrap.position","ui.bootstrap.stackedMap"]).provider("$uibTooltip",function(){function a(a){var b=/[A-Z]/g,c="-";return a.replace(b,function(a,b){return(b?c:"")+a.toLowerCase()})}var b={placement:"top",placementClassPrefix:"",animation:!0,popupDelay:0,popupCloseDelay:0,useContentExp:!1},c={mouseenter:"mouseleave",click:"click",outsideClick:"outsideClick",focus:"blur",none:""},d={};this.options=function(a){angular.extend(d,a)},this.setTriggers=function(a){angular.extend(c,a)},this.$get=["$window","$compile","$timeout","$document","$uibPosition","$interpolate","$rootScope","$parse","$$stackedMap",function(e,f,g,h,i,j,k,l,m){function n(a){if(27===a.which){var b=o.top();b&&(b.value.close(),o.removeTop(),b=null)}}var o=m.createNew();return h.on("keypress",n),k.$on("$destroy",function(){h.off("keypress",n)}),function(e,k,m,n){function p(a){var b=(a||n.trigger||m).split(" "),d=b.map(function(a){return c[a]||a});return{show:b,hide:d}}n=angular.extend({},b,d,n);var q=a(e),r=j.startSymbol(),s=j.endSymbol(),t="
';return{compile:function(a,b){var c=f(t);return function(a,b,d,f){function j(){N.isOpen?q():m()}function m(){(!M||a.$eval(d[k+"Enable"]))&&(u(),x(),N.popupDelay?G||(G=g(r,N.popupDelay,!1)):r())}function q(){s(),N.popupCloseDelay?H||(H=g(t,N.popupCloseDelay,!1)):t()}function r(){return s(),u(),N.content?(v(),void N.$evalAsync(function(){N.isOpen=!0,y(!0),S()})):angular.noop}function s(){G&&(g.cancel(G),G=null),I&&(g.cancel(I),I=null)}function t(){N&&N.$evalAsync(function(){N&&(N.isOpen=!1,y(!1),N.animation?F||(F=g(w,150,!1)):w())})}function u(){H&&(g.cancel(H),H=null),F&&(g.cancel(F),F=null)}function v(){D||(E=N.$new(),D=c(E,function(a){K?h.find("body").append(a):b.after(a)}),z())}function w(){s(),u(),A(),D&&(D.remove(),D=null),E&&(E.$destroy(),E=null)}function x(){N.title=d[k+"Title"],Q?N.content=Q(a):N.content=d[e],N.popupClass=d[k+"Class"],N.placement=angular.isDefined(d[k+"Placement"])?d[k+"Placement"]:n.placement;var b=i.parsePlacement(N.placement);J=b[1]?b[0]+"-"+b[1]:b[0];var c=parseInt(d[k+"PopupDelay"],10),f=parseInt(d[k+"PopupCloseDelay"],10);N.popupDelay=isNaN(c)?n.popupDelay:c,N.popupCloseDelay=isNaN(f)?n.popupCloseDelay:f}function y(b){P&&angular.isFunction(P.assign)&&P.assign(a,b)}function z(){R.length=0,Q?(R.push(a.$watch(Q,function(a){N.content=a,!a&&N.isOpen&&t()})),R.push(E.$watch(function(){O||(O=!0,E.$$postDigest(function(){O=!1,N&&N.isOpen&&S()}))}))):R.push(d.$observe(e,function(a){N.content=a,!a&&N.isOpen?t():S()})),R.push(d.$observe(k+"Title",function(a){N.title=a,N.isOpen&&S()})),R.push(d.$observe(k+"Placement",function(a){N.placement=a?a:n.placement;var b=i.parsePlacement(N.placement);J=b[1]?b[0]+"-"+b[1]:b[0],N.isOpen&&S()}))}function A(){R.length&&(angular.forEach(R,function(a){a()}),R.length=0)}function B(a){N&&N.isOpen&&D&&(b[0].contains(a.target)||D[0].contains(a.target)||q())}function C(){var a=d[k+"Trigger"];T(),L=p(a),"none"!==L.show&&L.show.forEach(function(a,c){"outsideClick"===a?(b.on("click",j),h.on("click",B)):a===L.hide[c]?b.on(a,j):a&&(b.on(a,m),b.on(L.hide[c],q)),b.on("keypress",function(a){27===a.which&&q()})})}var D,E,F,G,H,I,J,K=angular.isDefined(n.appendToBody)?n.appendToBody:!1,L=p(void 0),M=angular.isDefined(d[k+"Enable"]),N=a.$new(!0),O=!1,P=angular.isDefined(d[k+"IsOpen"])?l(d[k+"IsOpen"]):!1,Q=n.useContentExp?l(d[e]):!1,R=[],S=function(){D&&D.html()&&(I||(I=g(function(){var a=i.positionElements(b,D,N.placement,K);D.css({top:a.top+"px",left:a.left+"px"}),D.hasClass(a.placement.split("-")[0])||(D.removeClass(J.split("-")[0]),D.addClass(a.placement.split("-")[0])),D.hasClass(n.placementClassPrefix+a.placement)||(D.removeClass(n.placementClassPrefix+J),D.addClass(n.placementClassPrefix+a.placement)),D.hasClass("uib-position-measure")?(i.positionArrow(D,a.placement),D.removeClass("uib-position-measure")):J!==a.placement&&i.positionArrow(D,a.placement),J=a.placement,I=null},0,!1)))};N.origScope=a,N.isOpen=!1,o.add(N,{close:t}),N.contentExp=function(){return N.content},d.$observe("disabled",function(a){a&&s(),a&&N.isOpen&&t()}),P&&a.$watch(P,function(a){N&&!a===N.isOpen&&j()});var T=function(){L.show.forEach(function(a){"outsideClick"===a?b.off("click",j):(b.off(a,m),b.off(a,j))}),L.hide.forEach(function(a){"outsideClick"===a?h.off("click",B):b.off(a,q)})};C();var U=a.$eval(d[k+"Animation"]);N.animation=angular.isDefined(U)?!!U:n.animation;var V,W=k+"AppendToBody";V=W in d&&void 0===d[W]?!0:a.$eval(d[W]),K=angular.isDefined(V)?V:K,a.$on("$destroy",function(){T(),w(),o.remove(N),N=null})}}}}}]}).directive("uibTooltipTemplateTransclude",["$animate","$sce","$compile","$templateRequest",function(a,b,c,d){return{link:function(e,f,g){var h,i,j,k=e.$eval(g.tooltipTemplateTranscludeScope),l=0,m=function(){i&&(i.remove(),i=null),h&&(h.$destroy(),h=null),j&&(a.leave(j).then(function(){i=null}),i=j,j=null)};e.$watch(b.parseAsResourceUrl(g.uibTooltipTemplateTransclude),function(b){var g=++l;b?(d(b,!0).then(function(d){if(g===l){var e=k.$new(),i=d,n=c(i)(e,function(b){m(),a.enter(b,f)});h=e,j=n,h.$emit("$includeContentLoaded",b)}},function(){g===l&&(m(),e.$emit("$includeContentError",b))}),e.$emit("$includeContentRequested",b)):m()}),e.$on("$destroy",m)}}}]).directive("uibTooltipClasses",["$uibPosition",function(a){return{restrict:"A",link:function(b,c,d){if(b.placement){var e=a.parsePlacement(b.placement);c.addClass(e[0])}b.popupClass&&c.addClass(b.popupClass),b.animation()&&c.addClass(d.tooltipAnimationClass)}}}]).directive("uibTooltipPopup",function(){return{replace:!0,scope:{content:"@",placement:"@",popupClass:"@",animation:"&",isOpen:"&"},templateUrl:"uib/template/tooltip/tooltip-popup.html"}}).directive("uibTooltip",["$uibTooltip",function(a){return a("uibTooltip","tooltip","mouseenter")}]).directive("uibTooltipTemplatePopup",function(){return{replace:!0,scope:{contentExp:"&",placement:"@",popupClass:"@",animation:"&",isOpen:"&",originScope:"&"},templateUrl:"uib/template/tooltip/tooltip-template-popup.html"}}).directive("uibTooltipTemplate",["$uibTooltip",function(a){return a("uibTooltipTemplate","tooltip","mouseenter",{useContentExp:!0})}]).directive("uibTooltipHtmlPopup",function(){return{replace:!0,scope:{contentExp:"&",placement:"@",popupClass:"@",animation:"&",isOpen:"&"},templateUrl:"uib/template/tooltip/tooltip-html-popup.html"}}).directive("uibTooltipHtml",["$uibTooltip",function(a){return a("uibTooltipHtml","tooltip","mouseenter",{useContentExp:!0})}]),angular.module("ui.bootstrap.popover",["ui.bootstrap.tooltip"]).directive("uibPopoverTemplatePopup",function(){return{replace:!0,scope:{title:"@",contentExp:"&",placement:"@",popupClass:"@",animation:"&",isOpen:"&",originScope:"&"},templateUrl:"uib/template/popover/popover-template.html"}}).directive("uibPopoverTemplate",["$uibTooltip",function(a){return a("uibPopoverTemplate","popover","click",{useContentExp:!0})}]).directive("uibPopoverHtmlPopup",function(){return{replace:!0,scope:{contentExp:"&",title:"@",placement:"@",popupClass:"@",animation:"&",isOpen:"&"},templateUrl:"uib/template/popover/popover-html.html"}}).directive("uibPopoverHtml",["$uibTooltip",function(a){return a("uibPopoverHtml","popover","click",{useContentExp:!0})}]).directive("uibPopoverPopup",function(){return{replace:!0,scope:{title:"@",content:"@",placement:"@",popupClass:"@",animation:"&",isOpen:"&"},templateUrl:"uib/template/popover/popover.html"}}).directive("uibPopover",["$uibTooltip",function(a){return a("uibPopover","popover","click")}]),angular.module("ui.bootstrap.progressbar",[]).constant("uibProgressConfig",{animate:!0,max:100}).controller("UibProgressController",["$scope","$attrs","uibProgressConfig",function(a,b,c){function d(){return angular.isDefined(a.maxParam)?a.maxParam:c.max}var e=this,f=angular.isDefined(b.animate)?a.$parent.$eval(b.animate):c.animate;this.bars=[],a.max=d(),this.addBar=function(a,b,c){f||b.css({transition:"none"}),this.bars.push(a),a.max=d(),a.title=c&&angular.isDefined(c.title)?c.title:"progressbar",a.$watch("value",function(b){a.recalculatePercentage()}),a.recalculatePercentage=function(){var b=e.bars.reduce(function(a,b){return b.percent=+(100*b.value/b.max).toFixed(2),a+b.percent},0);b>100&&(a.percent-=b-100)},a.$on("$destroy",function(){b=null,e.removeBar(a)})},this.removeBar=function(a){this.bars.splice(this.bars.indexOf(a),1),this.bars.forEach(function(a){a.recalculatePercentage()})},a.$watch("maxParam",function(a){e.bars.forEach(function(a){a.max=d(),a.recalculatePercentage()})})}]).directive("uibProgress",function(){return{replace:!0,transclude:!0,controller:"UibProgressController",require:"uibProgress",scope:{maxParam:"=?max"},templateUrl:"uib/template/progressbar/progress.html"}}).directive("uibBar",function(){return{replace:!0,transclude:!0,require:"^uibProgress",scope:{value:"=",type:"@"},templateUrl:"uib/template/progressbar/bar.html",link:function(a,b,c,d){d.addBar(a,b,c)}}}).directive("uibProgressbar",function(){return{replace:!0,transclude:!0,controller:"UibProgressController",scope:{value:"=",maxParam:"=?max",type:"@"},templateUrl:"uib/template/progressbar/progressbar.html",link:function(a,b,c,d){d.addBar(a,angular.element(b.children()[0]),{title:c.title})}}}),angular.module("ui.bootstrap.rating",[]).constant("uibRatingConfig",{max:5,stateOn:null,stateOff:null,titles:["one","two","three","four","five"]}).controller("UibRatingController",["$scope","$attrs","uibRatingConfig",function(a,b,c){var d={$setViewValue:angular.noop},e=this;this.init=function(e){d=e,d.$render=this.render,d.$formatters.push(function(a){return angular.isNumber(a)&&a<<0!==a&&(a=Math.round(a)),a}),this.stateOn=angular.isDefined(b.stateOn)?a.$parent.$eval(b.stateOn):c.stateOn,this.stateOff=angular.isDefined(b.stateOff)?a.$parent.$eval(b.stateOff):c.stateOff;var f=angular.isDefined(b.titles)?a.$parent.$eval(b.titles):c.titles;this.titles=angular.isArray(f)&&f.length>0?f:c.titles;var g=angular.isDefined(b.ratingStates)?a.$parent.$eval(b.ratingStates):new Array(angular.isDefined(b.max)?a.$parent.$eval(b.max):c.max);a.range=this.buildTemplateObjects(g)},this.buildTemplateObjects=function(a){for(var b=0,c=a.length;c>b;b++)a[b]=angular.extend({index:b},{stateOn:this.stateOn,stateOff:this.stateOff,title:this.getTitle(b)},a[b]);return a},this.getTitle=function(a){return a>=this.titles.length?a+1:this.titles[a]},a.rate=function(b){!a.readonly&&b>=0&&b<=a.range.length&&(d.$setViewValue(d.$viewValue===b?0:b),d.$render())},a.enter=function(b){a.readonly||(a.value=b),a.onHover({value:b})},a.reset=function(){a.value=d.$viewValue,a.onLeave()},a.onKeydown=function(b){/(37|38|39|40)/.test(b.which)&&(b.preventDefault(),b.stopPropagation(),a.rate(a.value+(38===b.which||39===b.which?1:-1)))},this.render=function(){a.value=d.$viewValue,a.title=e.getTitle(a.value-1)}}]).directive("uibRating",function(){return{require:["uibRating","ngModel"],scope:{readonly:"=?readOnly",onHover:"&",onLeave:"&"},controller:"UibRatingController",templateUrl:"uib/template/rating/rating.html",replace:!0,link:function(a,b,c,d){var e=d[0],f=d[1];e.init(f)}}}),angular.module("ui.bootstrap.tabs",[]).controller("UibTabsetController",["$scope",function(a){function b(a){for(var b=0;bb.index?1:a.index0&&13>b:b>=0&&24>b;return c?(a.showMeridian&&(12===b&&(b=0),a.meridian===u[1]&&(b+=12)),b):void 0}function i(){var b=+a.minutes;return b>=0&&60>b?b:void 0}function j(){var b=+a.seconds;return b>=0&&60>b?b:void 0}function k(a){return null===a?"":angular.isDefined(a)&&a.toString().length<2?"0"+a:a.toString()}function l(a){m(),t.$setViewValue(new Date(r)),n(a)}function m(){t.$setValidity("time",!0),a.invalidHours=!1,a.invalidMinutes=!1,a.invalidSeconds=!1}function n(b){if(t.$modelValue){var c=r.getHours(),d=r.getMinutes(),e=r.getSeconds();a.showMeridian&&(c=0===c||12===c?12:c%12),a.hours="h"===b?c:k(c),"m"!==b&&(a.minutes=k(d)),a.meridian=r.getHours()<12?u[0]:u[1],"s"!==b&&(a.seconds=k(e)),a.meridian=r.getHours()<12?u[0]:u[1]}else a.hours=null,a.minutes=null,a.seconds=null,a.meridian=u[0]}function o(a){r=q(r,a),l()}function p(a,b){return q(a,60*b)}function q(a,b){var c=new Date(a.getTime()+1e3*b),d=new Date(a);return d.setHours(c.getHours(),c.getMinutes(),c.getSeconds()),d}var r=new Date,s=[],t={$setViewValue:angular.noop},u=angular.isDefined(c.meridians)?a.$parent.$eval(c.meridians):g.meridians||f.DATETIME_FORMATS.AMPMS;a.tabindex=angular.isDefined(c.tabindex)?c.tabindex:0,b.removeAttr("tabindex"),this.init=function(b,d){t=b,t.$render=this.render,t.$formatters.unshift(function(a){return a?new Date(a):null});var e=d.eq(0),f=d.eq(1),h=d.eq(2),i=angular.isDefined(c.mousewheel)?a.$parent.$eval(c.mousewheel):g.mousewheel;i&&this.setupMousewheelEvents(e,f,h);var j=angular.isDefined(c.arrowkeys)?a.$parent.$eval(c.arrowkeys):g.arrowkeys;j&&this.setupArrowkeyEvents(e,f,h),a.readonlyInput=angular.isDefined(c.readonlyInput)?a.$parent.$eval(c.readonlyInput):g.readonlyInput,this.setupInputEvents(e,f,h)};var v=g.hourStep;c.hourStep&&s.push(a.$parent.$watch(d(c.hourStep),function(a){v=+a}));var w=g.minuteStep;c.minuteStep&&s.push(a.$parent.$watch(d(c.minuteStep),function(a){w=+a}));var x;s.push(a.$parent.$watch(d(c.min),function(a){var b=new Date(a);x=isNaN(b)?void 0:b}));var y;s.push(a.$parent.$watch(d(c.max),function(a){var b=new Date(a);y=isNaN(b)?void 0:b}));var z=!1;c.ngDisabled&&s.push(a.$parent.$watch(d(c.ngDisabled),function(a){z=a})),a.noIncrementHours=function(){var a=p(r,60*v);return z||a>y||r>a&&x>a},a.noDecrementHours=function(){var a=p(r,60*-v);return z||x>a||a>r&&a>y},a.noIncrementMinutes=function(){var a=p(r,w);return z||a>y||r>a&&x>a},a.noDecrementMinutes=function(){var a=p(r,-w);return z||x>a||a>r&&a>y},a.noIncrementSeconds=function(){var a=q(r,A);return z||a>y||r>a&&x>a},a.noDecrementSeconds=function(){var a=q(r,-A);return z||x>a||a>r&&a>y},a.noToggleMeridian=function(){return r.getHours()<12?z||p(r,720)>y:z||p(r,-720)0};b.bind("mousewheel wheel",function(b){z||a.$apply(e(b)?a.incrementHours():a.decrementHours()),b.preventDefault()}),c.bind("mousewheel wheel",function(b){z||a.$apply(e(b)?a.incrementMinutes():a.decrementMinutes()),b.preventDefault()}),d.bind("mousewheel wheel",function(b){z||a.$apply(e(b)?a.incrementSeconds():a.decrementSeconds()),b.preventDefault()})},this.setupArrowkeyEvents=function(b,c,d){b.bind("keydown",function(b){z||(38===b.which?(b.preventDefault(),a.incrementHours(),a.$apply()):40===b.which&&(b.preventDefault(),a.decrementHours(),a.$apply()))}),c.bind("keydown",function(b){z||(38===b.which?(b.preventDefault(),a.incrementMinutes(),a.$apply()):40===b.which&&(b.preventDefault(),a.decrementMinutes(),a.$apply()))}),d.bind("keydown",function(b){z||(38===b.which?(b.preventDefault(),a.incrementSeconds(),a.$apply()):40===b.which&&(b.preventDefault(),a.decrementSeconds(),a.$apply()))})},this.setupInputEvents=function(b,c,d){if(a.readonlyInput)return a.updateHours=angular.noop,a.updateMinutes=angular.noop,void(a.updateSeconds=angular.noop);var e=function(b,c,d){t.$setViewValue(null),t.$setValidity("time",!1),angular.isDefined(b)&&(a.invalidHours=b),angular.isDefined(c)&&(a.invalidMinutes=c),angular.isDefined(d)&&(a.invalidSeconds=d)};a.updateHours=function(){var a=h(),b=i();t.$setDirty(),angular.isDefined(a)&&angular.isDefined(b)?(r.setHours(a),r.setMinutes(b),x>r||r>y?e(!0):l("h")):e(!0)},b.bind("blur",function(b){t.$setTouched(),null===a.hours||""===a.hours?e(!0):!a.invalidHours&&a.hours<10&&a.$apply(function(){a.hours=k(a.hours)})}),a.updateMinutes=function(){var a=i(),b=h();t.$setDirty(),angular.isDefined(a)&&angular.isDefined(b)?(r.setHours(b),r.setMinutes(a),x>r||r>y?e(void 0,!0):l("m")):e(void 0,!0)},c.bind("blur",function(b){t.$setTouched(),null===a.minutes?e(void 0,!0):!a.invalidMinutes&&a.minutes<10&&a.$apply(function(){a.minutes=k(a.minutes)})}),a.updateSeconds=function(){var a=j();t.$setDirty(),angular.isDefined(a)?(r.setSeconds(a),l("s")):e(void 0,void 0,!0)},d.bind("blur",function(b){!a.invalidSeconds&&a.seconds<10&&a.$apply(function(){a.seconds=k(a.seconds)})})},this.render=function(){var b=t.$viewValue;isNaN(b)?(t.$setValidity("time",!1),e.error('Timepicker directive: "ng-model" value must be a Date object, a number of milliseconds since 01.01.1970 or a string representing an RFC2822 or ISO 8601 date.')):(b&&(r=b),x>r||r>y?(t.$setValidity("time",!1),a.invalidHours=!0,a.invalidMinutes=!0):m(),n())},a.showSpinners=angular.isDefined(c.showSpinners)?a.$parent.$eval(c.showSpinners):g.showSpinners,a.incrementHours=function(){a.noIncrementHours()||o(60*v*60)},a.decrementHours=function(){a.noDecrementHours()||o(60*-v*60)},a.incrementMinutes=function(){a.noIncrementMinutes()||o(60*w)},a.decrementMinutes=function(){a.noDecrementMinutes()||o(60*-w)},a.incrementSeconds=function(){a.noIncrementSeconds()||o(A)},a.decrementSeconds=function(){a.noDecrementSeconds()||o(-A)},a.toggleMeridian=function(){var b=i(),c=h();a.noToggleMeridian()||(angular.isDefined(b)&&angular.isDefined(c)?o(720*(r.getHours()<12?60:-60)):a.meridian=a.meridian===u[0]?u[1]:u[0])},a.blur=function(){t.$setTouched()},a.$on("$destroy",function(){for(;s.length;)s.shift()()})}]).directive("uibTimepicker",["uibTimepickerConfig",function(a){return{require:["uibTimepicker","?^ngModel"],controller:"UibTimepickerController",controllerAs:"timepicker",replace:!0,scope:{},templateUrl:function(b,c){return c.templateUrl||a.templateUrl},link:function(a,b,c,d){var e=d[0],f=d[1];f&&e.init(f,b.find("input"))}}}]),angular.module("ui.bootstrap.typeahead",["ui.bootstrap.debounce","ui.bootstrap.position"]).factory("uibTypeaheadParser",["$parse",function(a){var b=/^\s*([\s\S]+?)(?:\s+as\s+([\s\S]+?))?\s+for\s+(?:([\$\w][\$\w\d]*))\s+in\s+([\s\S]+?)$/;return{parse:function(c){var d=c.match(b);if(!d)throw new Error('Expected typeahead specification in form of "_modelValue_ (as _label_)? for _item_ in _collection_" but got "'+c+'".');return{itemName:d[3],source:a(d[4]),viewMapper:a(d[2]||d[1]),modelMapper:a(d[1])}}}}]).controller("UibTypeaheadController",["$scope","$element","$attrs","$compile","$parse","$q","$timeout","$document","$window","$rootScope","$$debounce","$uibPosition","uibTypeaheadParser",function(a,b,c,d,e,f,g,h,i,j,k,l,m){function n(){N.moveInProgress||(N.moveInProgress=!0,N.$digest()),Y()}function o(){N.position=D?l.offset(b):l.position(b),N.position.top+=b.prop("offsetHeight")}var p,q,r=[9,13,27,38,40],s=200,t=a.$eval(c.typeaheadMinLength);t||0===t||(t=1),a.$watch(c.typeaheadMinLength,function(a){t=a||0===a?a:1});var u=a.$eval(c.typeaheadWaitMs)||0,v=a.$eval(c.typeaheadEditable)!==!1;a.$watch(c.typeaheadEditable,function(a){v=a!==!1});var w,x,y=e(c.typeaheadLoading).assign||angular.noop,z=e(c.typeaheadOnSelect),A=angular.isDefined(c.typeaheadSelectOnBlur)?a.$eval(c.typeaheadSelectOnBlur):!1,B=e(c.typeaheadNoResults).assign||angular.noop,C=c.typeaheadInputFormatter?e(c.typeaheadInputFormatter):void 0,D=c.typeaheadAppendToBody?a.$eval(c.typeaheadAppendToBody):!1,E=c.typeaheadAppendTo?a.$eval(c.typeaheadAppendTo):null,F=a.$eval(c.typeaheadFocusFirst)!==!1,G=c.typeaheadSelectOnExact?a.$eval(c.typeaheadSelectOnExact):!1,H=e(c.typeaheadIsOpen).assign||angular.noop,I=a.$eval(c.typeaheadShowHint)||!1,J=e(c.ngModel),K=e(c.ngModel+"($$$p)"),L=function(b,c){return angular.isFunction(J(a))&&q&&q.$options&&q.$options.getterSetter?K(b,{$$$p:c}):J.assign(b,c)},M=m.parse(c.uibTypeahead),N=a.$new(),O=a.$on("$destroy",function(){N.$destroy()});N.$on("$destroy",O);var P="typeahead-"+N.$id+"-"+Math.floor(1e4*Math.random());b.attr({"aria-autocomplete":"list","aria-expanded":!1,"aria-owns":P});var Q,R;I&&(Q=angular.element("
"),Q.css("position","relative"),b.after(Q),R=b.clone(),R.attr("placeholder",""),R.attr("tabindex","-1"),R.val(""),R.css({position:"absolute",top:"0px",left:"0px","border-color":"transparent","box-shadow":"none",opacity:1,background:"none 0% 0% / auto repeat scroll padding-box border-box rgb(255, 255, 255)",color:"#999"}),b.css({position:"relative","vertical-align":"top","background-color":"transparent"}),Q.append(R),R.after(b));var S=angular.element("
");S.attr({id:P,matches:"matches",active:"activeIdx",select:"select(activeIdx, evt)","move-in-progress":"moveInProgress",query:"query",position:"position","assign-is-open":"assignIsOpen(isOpen)",debounce:"debounceUpdate"}),angular.isDefined(c.typeaheadTemplateUrl)&&S.attr("template-url",c.typeaheadTemplateUrl),angular.isDefined(c.typeaheadPopupTemplateUrl)&&S.attr("popup-template-url",c.typeaheadPopupTemplateUrl);var T=function(){I&&R.val("")},U=function(){N.matches=[],N.activeIdx=-1,b.attr("aria-expanded",!1),T()},V=function(a){return P+"-option-"+a};N.$watch("activeIdx",function(a){0>a?b.removeAttr("aria-activedescendant"):b.attr("aria-activedescendant",V(a))});var W=function(a,b){return N.matches.length>b&&a?a.toUpperCase()===N.matches[b].label.toUpperCase():!1},X=function(c,d){var e={$viewValue:c};y(a,!0),B(a,!1),f.when(M.source(a,e)).then(function(f){var g=c===p.$viewValue;if(g&&w)if(f&&f.length>0){N.activeIdx=F?0:-1,B(a,!1),N.matches.length=0;for(var h=0;h0&&i.slice(0,c.length).toUpperCase()===c.toUpperCase()?R.val(c+i.slice(c.length)):R.val("")}}else U(),B(a,!0);g&&y(a,!1)},function(){U(),y(a,!1),B(a,!0)})};D&&(angular.element(i).on("resize",n),h.find("body").on("scroll",n));var Y=k(function(){N.matches.length&&o(),N.moveInProgress=!1},s);N.moveInProgress=!1,N.query=void 0;var Z,$=function(a){Z=g(function(){X(a)},u)},_=function(){Z&&g.cancel(Z)};U(),N.assignIsOpen=function(b){H(a,b)},N.select=function(d,e){var f,h,i={};x=!0,i[M.itemName]=h=N.matches[d].model,f=M.modelMapper(a,i),L(a,f),p.$setValidity("editable",!0),p.$setValidity("parse",!0),z(a,{$item:h,$model:f,$label:M.viewMapper(a,i),$event:e}),U(),N.$eval(c.typeaheadFocusOnSelect)!==!1&&g(function(){b[0].focus()},0,!1)},b.on("keydown",function(b){if(0!==N.matches.length&&-1!==r.indexOf(b.which)){if(-1===N.activeIdx&&(9===b.which||13===b.which)||9===b.which&&b.shiftKey)return U(),void N.$digest();b.preventDefault();var c;switch(b.which){case 9:case 13:N.$apply(function(){angular.isNumber(N.debounceUpdate)||angular.isObject(N.debounceUpdate)?k(function(){N.select(N.activeIdx,b)},angular.isNumber(N.debounceUpdate)?N.debounceUpdate:N.debounceUpdate["default"]):N.select(N.activeIdx,b)});break;case 27:b.stopPropagation(),U(),a.$digest();break;case 38:N.activeIdx=(N.activeIdx>0?N.activeIdx:N.matches.length)-1,N.$digest(),c=S.find("li")[N.activeIdx],c.parentNode.scrollTop=c.offsetTop;break;case 40:N.activeIdx=(N.activeIdx+1)%N.matches.length,N.$digest(),c=S.find("li")[N.activeIdx],c.parentNode.scrollTop=c.offsetTop}}}),b.bind("focus",function(a){w=!0,0!==t||p.$viewValue||g(function(){X(p.$viewValue,a)},0)}),b.bind("blur",function(a){A&&N.matches.length&&-1!==N.activeIdx&&!x&&(x=!0,N.$apply(function(){angular.isObject(N.debounceUpdate)&&angular.isNumber(N.debounceUpdate.blur)?k(function(){N.select(N.activeIdx,a)},N.debounceUpdate.blur):N.select(N.activeIdx,a)})),!v&&p.$error.editable&&(p.$viewValue="",b.val("")),w=!1,x=!1});var aa=function(c){b[0]!==c.target&&3!==c.which&&0!==N.matches.length&&(U(),j.$$phase||a.$digest())};h.on("click",aa),a.$on("$destroy",function(){h.off("click",aa),(D||E)&&ba.remove(),D&&(angular.element(i).off("resize",n),h.find("body").off("scroll",n)),S.remove(),I&&Q.remove()});var ba=d(S)(N);D?h.find("body").append(ba):E?angular.element(E).eq(0).append(ba):b.after(ba),this.init=function(b,c){p=b,q=c,N.debounceUpdate=p.$options&&e(p.$options.debounce)(a),p.$parsers.unshift(function(b){return w=!0,0===t||b&&b.length>=t?u>0?(_(),$(b)):X(b):(y(a,!1),_(),U()),v?b:b?void p.$setValidity("editable",!1):(p.$setValidity("editable",!0),null)}),p.$formatters.push(function(b){var c,d,e={};return v||p.$setValidity("editable",!0),C?(e.$model=b,C(a,e)):(e[M.itemName]=b,c=M.viewMapper(a,e),e[M.itemName]=void 0,d=M.viewMapper(a,e),c!==d?c:b)})}}]).directive("uibTypeahead",function(){return{controller:"UibTypeaheadController",require:["ngModel","^?ngModelOptions","uibTypeahead"],link:function(a,b,c,d){d[2].init(d[0],d[1])}}}).directive("uibTypeaheadPopup",["$$debounce",function(a){return{scope:{matches:"=",query:"=",active:"=",position:"&",moveInProgress:"=",select:"&",assignIsOpen:"&",debounce:"&"},replace:!0,templateUrl:function(a,b){return b.popupTemplateUrl||"uib/template/typeahead/typeahead-popup.html"},link:function(b,c,d){b.templateUrl=d.templateUrl,b.isOpen=function(){var a=b.matches.length>0;return b.assignIsOpen({isOpen:a}),a},b.isActive=function(a){return b.active===a},b.selectActive=function(a){b.active=a},b.selectMatch=function(c,d){var e=b.debounce();angular.isNumber(e)||angular.isObject(e)?a(function(){b.select({activeIdx:c,evt:d})},angular.isNumber(e)?e:e["default"]):b.select({activeIdx:c,evt:d})}}}}]).directive("uibTypeaheadMatch",["$templateRequest","$compile","$parse",function(a,b,c){return{scope:{index:"=",match:"=",query:"="},link:function(d,e,f){var g=c(f.templateUrl)(d.$parent)||"uib/template/typeahead/typeahead-match.html";a(g).then(function(a){var c=angular.element(a.trim());e.replaceWith(c),b(c)(d)})}}}]).filter("uibTypeaheadHighlight",["$sce","$injector","$log",function(a,b,c){function d(a){return a.replace(/([.?*+^$[\]\\(){}|-])/g,"\\$1")}function e(a){return/<.*>/g.test(a)}var f;return f=b.has("$sanitize"),function(b,g){return!f&&e(b)&&c.warn("Unsafe use of typeahead please use ngSanitize"),b=g?(""+b).replace(new RegExp(d(g),"gi"),"$&"):b,f||(b=a.trustAsHtml(b)),b}}]),angular.module("uib/template/accordion/accordion-group.html",[]).run(["$templateCache",function(a){a.put("uib/template/accordion/accordion-group.html",'
\n \n
\n
\n
\n
\n'); +}]),angular.module("uib/template/accordion/accordion.html",[]).run(["$templateCache",function(a){a.put("uib/template/accordion/accordion.html",'
')}]),angular.module("uib/template/alert/alert.html",[]).run(["$templateCache",function(a){a.put("uib/template/alert/alert.html",'\n')}]),angular.module("uib/template/carousel/carousel.html",[]).run(["$templateCache",function(a){a.put("uib/template/carousel/carousel.html",'\n')}]),angular.module("uib/template/carousel/slide.html",[]).run(["$templateCache",function(a){a.put("uib/template/carousel/slide.html",'
\n')}]),angular.module("uib/template/datepicker/datepicker.html",[]).run(["$templateCache",function(a){a.put("uib/template/datepicker/datepicker.html",'
\n \n \n \n
\n')}]),angular.module("uib/template/datepicker/day.html",[]).run(["$templateCache",function(a){a.put("uib/template/datepicker/day.html",'\n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n
{{::label.abbr}}
{{ weekNumbers[$index] }}\n \n
\n')}]),angular.module("uib/template/datepicker/month.html",[]).run(["$templateCache",function(a){a.put("uib/template/datepicker/month.html",'\n \n \n \n \n \n \n \n \n \n \n \n \n
\n \n
\n')}]),angular.module("uib/template/datepicker/popup.html",[]).run(["$templateCache",function(a){a.put("uib/template/datepicker/popup.html",'
\n \n
\n')}]),angular.module("uib/template/datepicker/year.html",[]).run(["$templateCache",function(a){a.put("uib/template/datepicker/year.html",'\n \n \n \n \n \n \n \n \n \n \n \n \n
\n \n
\n')}]),angular.module("uib/template/modal/backdrop.html",[]).run(["$templateCache",function(a){a.put("uib/template/modal/backdrop.html",'\n')}]),angular.module("uib/template/modal/window.html",[]).run(["$templateCache",function(a){a.put("uib/template/modal/window.html",'\n')}]),angular.module("uib/template/pager/pager.html",[]).run(["$templateCache",function(a){a.put("uib/template/pager/pager.html",'\n')}]),angular.module("uib/template/pagination/pager.html",[]).run(["$templateCache",function(a){a.put("uib/template/pagination/pager.html",'\n')}]),angular.module("uib/template/pagination/pagination.html",[]).run(["$templateCache",function(a){a.put("uib/template/pagination/pagination.html",'\n')}]),angular.module("uib/template/tooltip/tooltip-html-popup.html",[]).run(["$templateCache",function(a){a.put("uib/template/tooltip/tooltip-html-popup.html",'
\n
\n
\n
\n')}]),angular.module("uib/template/tooltip/tooltip-popup.html",[]).run(["$templateCache",function(a){a.put("uib/template/tooltip/tooltip-popup.html",'
\n
\n
\n
\n')}]),angular.module("uib/template/tooltip/tooltip-template-popup.html",[]).run(["$templateCache",function(a){a.put("uib/template/tooltip/tooltip-template-popup.html",'
\n
\n
\n
\n')}]),angular.module("uib/template/popover/popover-html.html",[]).run(["$templateCache",function(a){a.put("uib/template/popover/popover-html.html",'
\n
\n\n
\n

\n
\n
\n
\n')}]),angular.module("uib/template/popover/popover-template.html",[]).run(["$templateCache",function(a){a.put("uib/template/popover/popover-template.html",'
\n
\n\n
\n

\n
\n
\n
\n')}]),angular.module("uib/template/popover/popover.html",[]).run(["$templateCache",function(a){a.put("uib/template/popover/popover.html",'
\n
\n\n
\n

\n
\n
\n
\n')}]),angular.module("uib/template/progressbar/bar.html",[]).run(["$templateCache",function(a){a.put("uib/template/progressbar/bar.html",'
\n')}]),angular.module("uib/template/progressbar/progress.html",[]).run(["$templateCache",function(a){a.put("uib/template/progressbar/progress.html",'
')}]),angular.module("uib/template/progressbar/progressbar.html",[]).run(["$templateCache",function(a){a.put("uib/template/progressbar/progressbar.html",'
\n
\n
\n')}]),angular.module("uib/template/rating/rating.html",[]).run(["$templateCache",function(a){a.put("uib/template/rating/rating.html",'\n ({{ $index < value ? \'*\' : \' \' }})\n \n\n')}]),angular.module("uib/template/tabs/tab.html",[]).run(["$templateCache",function(a){a.put("uib/template/tabs/tab.html",'\n')}]),angular.module("uib/template/tabs/tabset.html",[]).run(["$templateCache",function(a){a.put("uib/template/tabs/tabset.html",'
\n \n
\n
\n
\n
\n
\n')}]),angular.module("uib/template/timepicker/timepicker.html",[]).run(["$templateCache",function(a){a.put("uib/template/timepicker/timepicker.html",'\n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n
  
\n \n :\n \n :\n \n
  
\n')}]),angular.module("uib/template/typeahead/typeahead-match.html",[]).run(["$templateCache",function(a){a.put("uib/template/typeahead/typeahead-match.html",'\n')}]),angular.module("uib/template/typeahead/typeahead-popup.html",[]).run(["$templateCache",function(a){a.put("uib/template/typeahead/typeahead-popup.html",'\n')}]),angular.module("ui.bootstrap.carousel").run(function(){!angular.$$csp().noInlineStyle&&!angular.$$uibCarouselCss&&angular.element(document).find("head").prepend(''),angular.$$uibCarouselCss=!0}),angular.module("ui.bootstrap.position").run(function(){!angular.$$csp().noInlineStyle&&!angular.$$uibPositionCss&&angular.element(document).find("head").prepend(''),angular.$$uibPositionCss=!0}),angular.module("ui.bootstrap.datepicker").run(function(){!angular.$$csp().noInlineStyle&&!angular.$$uibDatepickerCss&&angular.element(document).find("head").prepend(''),angular.$$uibDatepickerCss=!0}),angular.module("ui.bootstrap.tooltip").run(function(){!angular.$$csp().noInlineStyle&&!angular.$$uibTooltipCss&&angular.element(document).find("head").prepend(''),angular.$$uibTooltipCss=!0}),angular.module("ui.bootstrap.timepicker").run(function(){!angular.$$csp().noInlineStyle&&!angular.$$uibTimepickerCss&&angular.element(document).find("head").prepend(''),angular.$$uibTimepickerCss=!0}),angular.module("ui.bootstrap.typeahead").run(function(){!angular.$$csp().noInlineStyle&&!angular.$$uibTypeaheadCss&&angular.element(document).find("head").prepend(''),angular.$$uibTypeaheadCss=!0}); \ No newline at end of file diff --git a/ntt_tool/static/js/libs/jspdf/jspdf.min.js b/ntt_tool/static/js/libs/jspdf/jspdf.min.js new file mode 100644 index 0000000..c72711a --- /dev/null +++ b/ntt_tool/static/js/libs/jspdf/jspdf.min.js @@ -0,0 +1,171 @@ +/** + * jsPDF - PDF Document creation from JavaScript + * Version 1.0.272-git Built on 2014-09-29T15:09 + * CommitID d4770725ca + * + * Copyright (c) 2010-2014 James Hall, https://github.com/MrRio/jsPDF + * 2010 Aaron Spike, https://github.com/acspike + * 2012 Willow Systems Corporation, willow-systems.com + * 2012 Pablo Hess, https://github.com/pablohess + * 2012 Florian Jenett, https://github.com/fjenett + * 2013 Warren Weckesser, https://github.com/warrenweckesser + * 2013 Youssef Beddad, https://github.com/lifof + * 2013 Lee Driscoll, https://github.com/lsdriscoll + * 2013 Stefan Slonevskiy, https://github.com/stefslon + * 2013 Jeremy Morel, https://github.com/jmorel + * 2013 Christoph Hartmann, https://github.com/chris-rock + * 2014 Juan Pablo Gaviria, https://github.com/juanpgaviria + * 2014 James Makes, https://github.com/dollaruw + * 2014 Diego Casorran, https://github.com/diegocr + * + * Permission is hereby granted, free of charge, to any person obtaining + * a copy of this software and associated documentation files (the + * "Software"), to deal in the Software without restriction, including + * without limitation the rights to use, copy, modify, merge, publish, + * distribute, sublicense, and/or sell copies of the Software, and to + * permit persons to whom the Software is furnished to do so, subject to + * the following conditions: + * + * The above copyright notice and this permission notice shall be + * included in all copies or substantial portions of the Software. + * + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, + * EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF + * MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND + * NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE + * LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION + * OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION + * WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. + * + * Contributor(s): + * siefkenj, ahwolf, rickygu, Midnith, saintclair, eaparango, + * kim3er, mfo, alnorth, + */ +/** + * jsPDF addHTML PlugIn + * Copyright (c) 2014 Diego Casorran + * Licensed under the MIT License. + * http://opensource.org/licenses/mit-license + */ +/** + * jsPDF addImage plugin + * Copyright (c) 2012 Jason Siefken, https://github.com/siefkenj/ + * 2013 Chris Dowling, https://github.com/gingerchris + * 2013 Trinh Ho, https://github.com/ineedfat + * 2013 Edwin Alejandro Perez, https://github.com/eaparango + * 2013 Norah Smith, https://github.com/burnburnrocket + * 2014 Diego Casorran, https://github.com/diegocr + * 2014 James Robb, https://github.com/jamesbrobb + */ +/** + * jsPDF Cell plugin + * Copyright (c) 2013 Youssef Beddad, youssef.beddad@gmail.com + * 2013 Eduardo Menezes de Morais, eduardo.morais@usp.br + * 2013 Lee Driscoll, https://github.com/lsdriscoll + * 2014 Juan Pablo Gaviria, https://github.com/juanpgaviria + * 2014 James Hall, james@parall.ax + * 2014 Diego Casorran, https://github.com/diegocr + */ +/** + * jsPDF fromHTML plugin. BETA stage. API subject to change. Needs browser + * Copyright (c) 2012 Willow Systems Corporation, willow-systems.com + * 2014 Juan Pablo Gaviria, https://github.com/juanpgaviria + * 2014 Diego Casorran, https://github.com/diegocr + * 2014 Daniel Husar, https://github.com/danielhusar + * 2014 Wolfgang Gassler, https://github.com/woolfg + */ +/** + * jsPDF JavaScript plugin + * Copyright (c) 2013 Youssef Beddad, youssef.beddad@gmail.com + */ +/** + * jsPDF PNG PlugIn + * Copyright (c) 2014 James Robb, https://github.com/jamesbrobb + */ +/** +jsPDF Silly SVG plugin +Copyright (c) 2012 Willow Systems Corporation, willow-systems.com +*/ +/** + * jsPDF split_text_to_size plugin - MIT license. + * Copyright (c) 2012 Willow Systems Corporation, willow-systems.com + * 2014 Diego Casorran, https://github.com/diegocr + */ +/** +jsPDF standard_fonts_metrics plugin +Copyright (c) 2012 Willow Systems Corporation, willow-systems.com +MIT license. +*/ +/** + * jsPDF total_pages plugin + * Copyright (c) 2013 Eduardo Menezes de Morais, eduardo.morais@usp.br + */ +/* Blob.js + * A Blob implementation. + * 2014-07-24 + * By Eli Grey, http://eligrey.com + * By Devin Samarin, https://github.com/dsamarin + * License: X11/MIT + * See https://github.com/eligrey/Blob.js/blob/master/LICENSE.md + */ +/* FileSaver.js + * A saveAs() FileSaver implementation. + * 2014-08-29 + * By Eli Grey, http://eligrey.com + * License: X11/MIT + * See https://github.com/eligrey/FileSaver.js/blob/master/LICENSE.md + */ +/* + * Copyright (c) 2012 chick307 + * Licensed under the MIT License. + * http://opensource.org/licenses/mit-license + */ +/* + Deflate.js - https://github.com/gildas-lormeau/zip.js + Copyright (c) 2013 Gildas Lormeau. All rights reserved. + Redistribution and use in source and binary forms, with or without + modification, are permitted provided that the following conditions are met: + 1. Redistributions of source code must retain the above copyright notice, + this list of conditions and the following disclaimer. + 2. Redistributions in binary form must reproduce the above copyright + notice, this list of conditions and the following disclaimer in + the documentation and/or other materials provided with the distribution. + 3. The names of the authors may not be used to endorse or promote products + derived from this software without specific prior written permission. + THIS SOFTWARE IS PROVIDED ``AS IS'' AND ANY EXPRESSED OR IMPLIED WARRANTIES, + INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND + FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL JCRAFT, + INC. OR ANY CONTRIBUTORS TO THIS SOFTWARE BE LIABLE FOR ANY DIRECT, INDIRECT, + INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT + LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, + OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF + LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING + NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, + EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. + */ +/* +# PNG.js +# Copyright (c) 2011 Devon Govett +# MIT LICENSE +# +*/ +/* + * Extracted from pdf.js + * https://github.com/andreasgal/pdf.js + * Copyright (c) 2011 Mozilla Foundation + * Contributors: Andreas Gal + * Chris G Jones + * Shaon Barman + * Vivien Nicolas <21@vingtetun.org> + * Justin D'Arcangelo + * Yury Delendik + */ +/** + * JavaScript Polyfill functions for jsPDF + * Collected from public resources by + * https://github.com/diegocr + */ +!function(t,e){e["true"]=t;var n=function(t){"use strict";function e(e){var n={};this.subscribe=function(t,e,r){if("function"!=typeof e)return!1;n.hasOwnProperty(t)||(n[t]={});var s=Math.random().toString(35);return n[t][s]=[e,!!r],s},this.unsubscribe=function(t){for(var e in n)if(n[e][t])return delete n[e][t],!0;return!1},this.publish=function(r){if(n.hasOwnProperty(r)){var s=Array.prototype.slice.call(arguments,1),i=[];for(var o in n[r]){var a=n[r][o];try{a[0].apply(e,s)}catch(u){t.console&&console.error("jsPDF PubSub Error",u.message,u)}a[1]&&i.push(o)}i.length&&i.forEach(this.unsubscribe)}}}function n(a,u,c,l){var f={};"object"==typeof a&&(f=a,a=f.orientation,u=f.unit||u,c=f.format||c,l=f.compress||f.compressPdf||l),u=u||"mm",c=c||"a4",a=(""+(a||"P")).toLowerCase();var d,h,p,m,w,g,y,v,b,q=((""+c).toLowerCase(),!!l&&"function"==typeof Uint8Array),x=f.textColor||"0 g",k=f.drawColor||"0 G",_=f.fontSize||16,A=f.lineHeight||1.15,C=f.lineWidth||.200025,S=2,E=!1,z=[],T={},I={},B=0,O=[],P={},R=[],F=0,D=0,U=0,N={title:"",subject:"",author:"",keywords:"",creator:""},L={},j=new e(L),M=function(t){return t.toFixed(2)},H=function(t){return t.toFixed(3)},G=function(t){return("0"+parseInt(t)).slice(-2)},W=function(t){E?O[m].push(t):(U+=t.length+1,R.push(t))},V=function(){return S++,z[S]=U,W(S+" 0 obj"),S},J=function(t){W("stream"),W(t),W("endstream")},X=function(){var e,r,i,o,a,u,c,l,f;for(c=t.adler32cs||n.adler32cs,q&&"undefined"==typeof c&&(q=!1),e=1;B>=e;e++){if(V(),l=(w=P[e].width)*h,f=(g=P[e].height)*h,W("<>"),W("endobj"),r=O[e].join("\n"),V(),q){for(i=[],o=r.length;o--;)i[o]=r.charCodeAt(o);u=c.from(r),a=new s(6),a.append(new Uint8Array(i)),r=a.flush(),i=new Uint8Array(r.length+6),i.set(new Uint8Array([120,156])),i.set(r,2),i.set(new Uint8Array([255&u,u>>8&255,u>>16&255,u>>24&255]),r.length+2),r=String.fromCharCode.apply(null,i),W("<>")}else W("<>");J(r),W("endobj")}z[1]=U,W("1 0 obj"),W("<o;o++)d+=3+2*o+" 0 R ";W(d+"]"),W("/Count "+B),W(">>"),W("endobj")},Y=function(t){t.objectNumber=V(),W("<>"),W("endobj")},K=function(){for(var t in T)T.hasOwnProperty(t)&&Y(T[t])},Q=function(){j.publish("putXobjectDict")},$=function(){W("/ProcSet [/PDF /Text /ImageB /ImageC /ImageI]"),W("/Font <<");for(var t in T)T.hasOwnProperty(t)&&W("/"+t+" "+T[t].objectNumber+" 0 R");W(">>"),W("/XObject <<"),Q(),W(">>")},Z=function(){K(),j.publish("putResources"),z[2]=U,W("2 0 obj"),W("<<"),$(),W(">>"),W("endobj"),j.publish("postPutResources")},te=function(t,e,n){I.hasOwnProperty(e)||(I[e]={}),I[e][n]=t},ee=function(t,e,n,r){var s="F"+(Object.keys(T).length+1).toString(10),i=T[s]={id:s,PostScriptName:t,fontName:e,fontStyle:n,encoding:r,metadata:{}};return te(s,e,n),j.publish("addFont",i),s},ne=function(){for(var t="helvetica",e="times",n="courier",r="normal",s="bold",i="italic",o="bolditalic",a="StandardEncoding",u=[["Helvetica",t,r],["Helvetica-Bold",t,s],["Helvetica-Oblique",t,i],["Helvetica-BoldOblique",t,o],["Courier",n,r],["Courier-Bold",n,s],["Courier-Oblique",n,i],["Courier-BoldOblique",n,o],["Times-Roman",e,r],["Times-Bold",e,s],["Times-Italic",e,i],["Times-BoldItalic",e,o]],c=0,l=u.length;l>c;c++){var f=ee(u[c][0],u[c][1],u[c][2],a),d=u[c][0].split("-");te(f,d[0],d[1]||"")}j.publish("addFonts",{fonts:T,dictionary:I})},re=function(e){return e.foo=function(){try{return e.apply(this,arguments)}catch(n){var r=n.stack||"";~r.indexOf(" at ")&&(r=r.split(" at ")[1]);var s="Error in function "+r.split("\n")[0].split("<")[0]+": "+n.message;if(!t.console)throw new Error(s);t.console.error(s,n),t.alert&&alert(s)}},e.foo.bar=e,e.foo},se=function(t,e){var n,r,s,i,o,a,u,c,l;if(e=e||{},s=e.sourceEncoding||"Unicode",o=e.outputEncoding,(e.autoencode||o)&&T[d].metadata&&T[d].metadata[s]&&T[d].metadata[s].encoding&&(i=T[d].metadata[s].encoding,!o&&T[d].encoding&&(o=T[d].encoding),!o&&i.codePages&&(o=i.codePages[0]),"string"==typeof o&&(o=i[o]),o)){for(u=!1,a=[],n=0,r=t.length;r>n;n++)c=o[t.charCodeAt(n)],a.push(c?String.fromCharCode(c):t[n]),a[n].charCodeAt(0)>>8&&(u=!0);t=a.join("")}for(n=t.length;void 0===u&&0!==n;)t.charCodeAt(n-1)>>8&&(u=!0),n--;if(!u)return t;for(a=e.noBOM?[]:[254,255],n=0,r=t.length;r>n;n++){if(c=t.charCodeAt(n),l=c>>8,l>>8)throw new Error("Character at position "+n+" of string '"+t+"' exceeds 16bits. Cannot be encoded into UCS-2 BE");a.push(l),a.push(c-(l<<8))}return String.fromCharCode.apply(void 0,a)},ie=function(t,e){return se(t,e).replace(/\\/g,"\\\\").replace(/\(/g,"\\(").replace(/\)/g,"\\)")},oe=function(){W("/Producer (jsPDF "+n.version+")");for(var t in N)N.hasOwnProperty(t)&&N[t]&&W("/"+t.substr(0,1).toUpperCase()+t.substr(1)+" ("+ie(N[t])+")");var e=new Date,r=e.getTimezoneOffset(),s=0>r?"+":"-",i=Math.floor(Math.abs(r/60)),o=Math.abs(r%60),a=[s,G(i),"'",G(o),"'"].join("");W(["/CreationDate (D:",e.getFullYear(),G(e.getMonth()+1),G(e.getDate()),G(e.getHours()),G(e.getMinutes()),G(e.getSeconds()),a,")"].join(""))},ae=function(){switch(W("/Type /Catalog"),W("/Pages 1 0 R"),v||(v="fullwidth"),v){case"fullwidth":W("/OpenAction [3 0 R /FitH null]");break;case"fullheight":W("/OpenAction [3 0 R /FitV null]");break;case"fullpage":W("/OpenAction [3 0 R /Fit]");break;case"original":W("/OpenAction [3 0 R /XYZ null null 1]");break;default:var t=""+v;"%"===t.substr(t.length-1)&&(v=parseInt(v)/100),"number"==typeof v&&W("/OpenAction [3 0 R /XYZ null null "+M(v)+"]")}switch(b||(b="continuous"),b){case"continuous":W("/PageLayout /OneColumn");break;case"single":W("/PageLayout /SinglePage");break;case"two":case"twoleft":W("/PageLayout /TwoColumnLeft");break;case"tworight":W("/PageLayout /TwoColumnRight")}y&&W("/PageMode /"+y),j.publish("putCatalog")},ue=function(){W("/Size "+(S+1)),W("/Root "+S+" 0 R"),W("/Info "+(S-1)+" 0 R")},ce=function(t,e){var n="string"==typeof e&&e.toLowerCase();if("string"==typeof t){var r=t.toLowerCase();o.hasOwnProperty(r)&&(t=o[r][0]/h,e=o[r][1]/h)}if(Array.isArray(t)&&(e=t[1],t=t[0]),n){switch(n.substr(0,1)){case"l":e>t&&(n="s");break;case"p":t>e&&(n="s")}"s"===n&&(p=t,t=e,e=p)}E=!0,O[++B]=[],P[B]={width:Number(t)||w,height:Number(e)||g},fe(B)},le=function(){ce.apply(this,arguments),W(M(C*h)+" w"),W(k),0!==F&&W(F+" J"),0!==D&&W(D+" j"),j.publish("addPage",{pageNumber:B})},fe=function(t){t>0&&B>=t&&(m=t,w=P[t].width,g=P[t].height)},de=function(t,e){var n;t=void 0!==t?t:T[d].fontName,e=void 0!==e?e:T[d].fontStyle;try{n=I[t][e]}catch(r){}if(!n)throw new Error("Unable to look up font label for font '"+t+"', '"+e+"'. Refer to getFontList() for available fonts.");return n},he=function(){E=!1,S=2,R=[],z=[],W("%PDF-"+i),X(),Z(),V(),W("<<"),oe(),W(">>"),W("endobj"),V(),W("<<"),ae(),W(">>"),W("endobj");var t,e=U,n="0000000000";for(W("xref"),W("0 "+(S+1)),W(n+" 65535 f "),t=1;S>=t;t++)W((n+z[t]).slice(-10)+" 00000 n ");return W("trailer"),W("<<"),ue(),W(">>"),W("startxref"),W(e),W("%%EOF"),E=!0,R.join("\n")},pe=function(t){var e="S";return"F"===t?e="f":"FD"===t||"DF"===t?e="B":("f"===t||"f*"===t||"B"===t||"B*"===t)&&(e=t),e},me=function(){for(var t=he(),e=t.length,n=new ArrayBuffer(e),r=new Uint8Array(n);e--;)r[e]=t.charCodeAt(e);return n},we=function(){return new Blob([me()],{type:"application/pdf"})},ge=re(function(e,n){var s="dataur"===(""+e).substr(0,6)?"data:application/pdf;base64,"+btoa(he()):0;switch(e){case void 0:return he();case"save":if(navigator.getUserMedia&&(void 0===t.URL||void 0===t.URL.createObjectURL))return L.output("dataurlnewwindow");r(we(),n),"function"==typeof r.unload&&t.setTimeout&&setTimeout(r.unload,911);break;case"arraybuffer":return me();case"blob":return we();case"bloburi":case"bloburl":return t.URL&&t.URL.createObjectURL(we())||void 0;case"datauristring":case"dataurlstring":return s;case"dataurlnewwindow":var i=t.open(s);if(i||"undefined"==typeof safari)return i;case"datauri":case"dataurl":return t.document.location.href=s;default:throw new Error('Output type "'+e+'" is not supported.')}});switch(u){case"pt":h=1;break;case"mm":h=72/25.4;break;case"cm":h=72/2.54;break;case"in":h=72;break;case"px":h=96/72;break;case"pc":h=12;break;case"em":h=12;break;case"ex":h=6;break;default:throw"Invalid unit: "+u}L.internal={pdfEscape:ie,getStyle:pe,getFont:function(){return T[de.apply(L,arguments)]},getFontSize:function(){return _},getLineHeight:function(){return _*A},write:function(t){W(1===arguments.length?t:Array.prototype.join.call(arguments," "))},getCoordinateString:function(t){return M(t*h)},getVerticalCoordinateString:function(t){return M((g-t)*h)},collections:{},newObject:V,putStream:J,events:j,scaleFactor:h,pageSize:{get width(){return w},get height(){return g}},output:function(t,e){return ge(t,e)},getNumberOfPages:function(){return O.length-1},pages:O},L.addPage=function(){return le.apply(this,arguments),this},L.setPage=function(){return fe.apply(this,arguments),this},L.setDisplayMode=function(t,e,n){return v=t,b=e,y=n,this},L.text=function(t,e,n,r,s){function i(t){return t=t.split(" ").join(Array(f.TabLen||9).join(" ")),ie(t,r)}"number"==typeof t&&(p=n,n=e,e=t,t=p),"string"==typeof t&&t.match(/[\n\r]/)&&(t=t.split(/\r\n|\r|\n/g)),"number"==typeof r&&(s=r,r=null);var o,a="",u="Td";if(s){s*=Math.PI/180;var c=Math.cos(s),l=Math.sin(s);a=[M(c),M(l),M(-1*l),M(c),""].join(" "),u="Tm"}if(r=r||{},"noBOM"in r||(r.noBOM=!0),"autoencode"in r||(r.autoencode=!0),"string"==typeof t)t=i(t);else{if(!(t instanceof Array))throw new Error('Type of text must be string or Array. "'+t+'" is not recognized.');for(var m=t.concat(),w=[],y=m.length;y--;)w.push(i(m.shift()));var v=Math.ceil((g-n)*h/(_*A));v>=0&&vs;s++,e+=r)this.text(t[s],e,n)},L.line=function(t,e,n,r){return this.lines([[n-t,r-e]],t,e)},L.clip=function(){W("W"),W("S")},L.lines=function(t,e,n,r,s,i){var o,a,u,c,l,f,d,m,w,y,v;for("number"==typeof t&&(p=n,n=e,e=t,t=p),r=r||[1,1],W(H(e*h)+" "+H((g-n)*h)+" m "),o=r[0],a=r[1],c=t.length,y=e,v=n,u=0;c>u;u++)l=t[u],2===l.length?(y=l[0]*o+y,v=l[1]*a+v,W(H(y*h)+" "+H((g-v)*h)+" l")):(f=l[0]*o+y,d=l[1]*a+v,m=l[2]*o+y,w=l[3]*a+v,y=l[4]*o+y,v=l[5]*a+v,W(H(f*h)+" "+H((g-d)*h)+" "+H(m*h)+" "+H((g-w)*h)+" "+H(y*h)+" "+H((g-v)*h)+" c"));return i&&W(" h"),null!==s&&W(pe(s)),this},L.rect=function(t,e,n,r,s){pe(s);return W([M(t*h),M((g-e)*h),M(n*h),M(-r*h),"re"].join(" ")),null!==s&&W(pe(s)),this},L.triangle=function(t,e,n,r,s,i,o){return this.lines([[n-t,r-e],[s-n,i-r],[t-s,e-i]],t,e,[1,1],o,!0),this},L.roundedRect=function(t,e,n,r,s,i,o){var a=4/3*(Math.SQRT2-1);return this.lines([[n-2*s,0],[s*a,0,s,i-i*a,s,i],[0,r-2*i],[0,i*a,-(s*a),i,-s,i],[-n+2*s,0],[-(s*a),0,-s,-(i*a),-s,-i],[0,-r+2*i],[0,-(i*a),s*a,-i,s,-i]],t+s,e,[1,1],o),this},L.ellipse=function(t,e,n,r,s){var i=4/3*(Math.SQRT2-1)*n,o=4/3*(Math.SQRT2-1)*r;return W([M((t+n)*h),M((g-e)*h),"m",M((t+n)*h),M((g-(e-o))*h),M((t+i)*h),M((g-(e-r))*h),M(t*h),M((g-(e-r))*h),"c"].join(" ")),W([M((t-i)*h),M((g-(e-r))*h),M((t-n)*h),M((g-(e-o))*h),M((t-n)*h),M((g-e)*h),"c"].join(" ")),W([M((t-n)*h),M((g-(e+o))*h),M((t-i)*h),M((g-(e+r))*h),M(t*h),M((g-(e+r))*h),"c"].join(" ")),W([M((t+i)*h),M((g-(e+r))*h),M((t+n)*h),M((g-(e+o))*h),M((t+n)*h),M((g-e)*h),"c"].join(" ")),null!==s&&W(pe(s)),this},L.circle=function(t,e,n,r){return this.ellipse(t,e,n,n,r)},L.setProperties=function(t){for(var e in N)N.hasOwnProperty(e)&&t[e]&&(N[e]=t[e]);return this},L.setFontSize=function(t){return _=t,this},L.setFont=function(t,e){return d=de(t,e),this},L.setFontStyle=L.setFontType=function(t){return d=de(void 0,t),this},L.getFontList=function(){var t,e,n,r={};for(t in I)if(I.hasOwnProperty(t)){r[t]=n=[];for(e in I[t])I[t].hasOwnProperty(e)&&n.push(e)}return r},L.setLineWidth=function(t){return W((t*h).toFixed(2)+" w"),this},L.setDrawColor=function(t,e,n,r){var s;return s=void 0===e||void 0===r&&t===e===n?"string"==typeof t?t+" G":M(t/255)+" G":void 0===r?"string"==typeof t?[t,e,n,"RG"].join(" "):[M(t/255),M(e/255),M(n/255),"RG"].join(" "):"string"==typeof t?[t,e,n,r,"K"].join(" "):[M(t),M(e),M(n),M(r),"K"].join(" "),W(s),this},L.setFillColor=function(t,e,n,r){var s;return s=void 0===e||void 0===r&&t===e===n?"string"==typeof t?t+" g":M(t/255)+" g":void 0===r?"string"==typeof t?[t,e,n,"rg"].join(" "):[M(t/255),M(e/255),M(n/255),"rg"].join(" "):"string"==typeof t?[t,e,n,r,"k"].join(" "):[M(t),M(e),M(n),M(r),"k"].join(" "),W(s),this},L.setTextColor=function(t,e,n){if("string"==typeof t&&/^#[0-9A-Fa-f]{6}$/.test(t)){var r=parseInt(t.substr(1),16);t=r>>16&255,e=r>>8&255,n=255&r}return x=0===t&&0===e&&0===n||"undefined"==typeof e?H(t/255)+" g":[H(t/255),H(e/255),H(n/255),"rg"].join(" "),this},L.CapJoinStyles={0:0,butt:0,but:0,miter:0,1:1,round:1,rounded:1,circle:1,2:2,projecting:2,project:2,square:2,bevel:2},L.setLineCap=function(t){var e=this.CapJoinStyles[t];if(void 0===e)throw new Error("Line cap style of '"+t+"' is not recognized. See or extend .CapJoinStyles property for valid styles");return F=e,W(e+" J"),this},L.setLineJoin=function(t){var e=this.CapJoinStyles[t];if(void 0===e)throw new Error("Line join style of '"+t+"' is not recognized. See or extend .CapJoinStyles property for valid styles");return D=e,W(e+" j"),this},L.output=ge,L.save=function(t){L.output("save",t)};for(var ye in n.API)n.API.hasOwnProperty(ye)&&("events"===ye&&n.API.events.length?!function(t,e){var n,r,s;for(s=e.length-1;-1!==s;s--)n=e[s][0],r=e[s][1],t.subscribe.apply(t,[n].concat("function"==typeof r?[r]:r))}(j,n.API.events):L[ye]=n.API[ye]);return ne(),d="F1",le(c,a),j.publish("initialized"),L}var i="1.3",o={a0:[2383.94,3370.39],a1:[1683.78,2383.94],a2:[1190.55,1683.78],a3:[841.89,1190.55],a4:[595.28,841.89],a5:[419.53,595.28],a6:[297.64,419.53],a7:[209.76,297.64],a8:[147.4,209.76],a9:[104.88,147.4],a10:[73.7,104.88],b0:[2834.65,4008.19],b1:[2004.09,2834.65],b2:[1417.32,2004.09],b3:[1000.63,1417.32],b4:[708.66,1000.63],b5:[498.9,708.66],b6:[354.33,498.9],b7:[249.45,354.33],b8:[175.75,249.45],b9:[124.72,175.75],b10:[87.87,124.72],c0:[2599.37,3676.54],c1:[1836.85,2599.37],c2:[1298.27,1836.85],c3:[918.43,1298.27],c4:[649.13,918.43],c5:[459.21,649.13],c6:[323.15,459.21],c7:[229.61,323.15],c8:[161.57,229.61],c9:[113.39,161.57],c10:[79.37,113.39],dl:[311.81,623.62],letter:[612,792],"government-letter":[576,756],legal:[612,1008],"junior-legal":[576,360],ledger:[1224,792],tabloid:[792,1224],"credit-card":[153,243]};return n.API={events:[]},n.version="1.0.272-git 2014-09-29T15:09:diegocr","function"==typeof define&&define.amd?define("jsPDF",function(){return n}):t.jsPDF=n,n}("undefined"!=typeof self&&self||"undefined"!=typeof window&&window||this);!function(t){"use strict";t.addHTML=function(t,e,n,r,s){if("undefined"==typeof html2canvas&&"undefined"==typeof rasterizeHTML)throw new Error("You need either https://github.com/niklasvh/html2canvas or https://github.com/cburgmer/rasterizeHTML.js");"number"!=typeof e&&(r=e,s=n),"function"==typeof r&&(s=r,r=null);var i=this.internal,o=i.scaleFactor,a=i.pageSize.width,u=i.pageSize.height;if(r=r||{},r.onrendered=function(t){e=parseInt(e)||0,n=parseInt(n)||0;var i=r.dim||{},c=i.h||0,l=i.w||Math.min(a,t.width/o)-e,f="JPEG";if(r.format&&(f=r.format),t.height>u&&r.pagesplit){var d=function(){for(var r=0;;){var i=document.createElement("canvas");i.width=Math.min(a*o,t.width),i.height=Math.min(u*o,t.height-r);var c=i.getContext("2d");c.drawImage(t,0,r,t.width,i.height,0,0,i.width,i.height);var d=[i,e,r?0:n,i.width/o,i.height/o,f,null,"SLOW"];if(this.addImage.apply(this,d),r+=i.height,r>=t.height)break;this.addPage()}s(l,r,null,d)}.bind(this);if("CANVAS"===t.nodeName){var h=new Image;h.onload=d,h.src=t.toDataURL("image/png"),t=h}else d()}else{var p=Math.random().toString(35),m=[t,e,n,l,c,f,p,"SLOW"];this.addImage.apply(this,m),s(l,c,p,m)}}.bind(this),"undefined"!=typeof html2canvas&&!r.rstz)return html2canvas(t,r);if("undefined"!=typeof rasterizeHTML){var c="drawDocument";return"string"==typeof t&&(c=/^http/.test(t)?"drawURL":"drawHTML"),r.width=r.width||a*o,rasterizeHTML[c](t,void 0,r).then(function(t){r.onrendered(t.image)},function(t){s(null,t)})}return null}}(n.API),function(t){"use strict";var e="addImage_",n=["jpeg","jpg","png"],r=function(t){var e=this.internal.newObject(),n=this.internal.write,s=this.internal.putStream;if(t.n=e,n("<>"),"trns"in t&&t.trns.constructor==Array){for(var i="",o=0,a=t.trns.length;a>o;o++)i+=t.trns[o]+" "+t.trns[o]+" ";n("/Mask ["+i+"]")}if("smask"in t&&n("/SMask "+(e+1)+" 0 R"),n("/Length "+t.data.length+">>"),s(t.data),n("endobj"),"smask"in t){var u="/Predictor 15 /Colors 1 /BitsPerComponent "+t.bpc+" /Columns "+t.w,c={w:t.w,h:t.h,cs:"DeviceGray",bpc:t.bpc,dp:u,data:t.smask};"f"in t&&(c.f=t.f),r.call(this,c)}t.cs===this.color_spaces.INDEXED&&(this.internal.newObject(),n("<< /Length "+t.pal.length+">>"),s(this.arrayBufferToBinaryString(new Uint8Array(t.pal))),n("endobj"))},s=function(){var t=this.internal.collections[e+"images"];for(var n in t)r.call(this,t[n])},i=function(){var t,n=this.internal.collections[e+"images"],r=this.internal.write;for(var s in n)t=n[s],r("/I"+t.i,t.n,"0","R")},o=function(e){return e&&"string"==typeof e&&(e=e.toUpperCase()),e in t.image_compression?e:t.image_compression.NONE},a=function(){var t=this.internal.collections[e+"images"];return t||(this.internal.collections[e+"images"]=t={},this.internal.events.subscribe("putResources",s),this.internal.events.subscribe("putXobjectDict",i)),t},u=function(t){var e=0;return t&&(e=Object.keys?Object.keys(t).length:function(t){var e=0;for(var n in t)t.hasOwnProperty(n)&&e++;return e}(t)),e},c=function(t){return"undefined"==typeof t||null===t},l=function(e){return"string"==typeof e&&t.sHashCode(e)},f=function(t){return-1===n.indexOf(t)},d=function(e){return"function"!=typeof t["process"+e.toUpperCase()]},h=function(t){return"object"==typeof t&&1===t.nodeType},p=function(t,e,n){if("IMG"===t.nodeName&&t.hasAttribute("src")){var r=""+t.getAttribute("src");if(!n&&0===r.indexOf("data:image/"))return r;!e&&/\.png(?:[?#].*)?$/i.test(r)&&(e="png")}if("CANVAS"===t.nodeName)var s=t;else{var s=document.createElement("canvas");s.width=t.clientWidth||t.width,s.height=t.clientHeight||t.height;var i=s.getContext("2d");if(!i)throw"addImage requires canvas to be supported by browser.";if(n){var o,a,u,c,l,f,d,h,p=Math.PI/180;"object"==typeof n&&(o=n.x,a=n.y,u=n.bg,n=n.angle),h=n*p,c=Math.abs(Math.cos(h)),l=Math.abs(Math.sin(h)),f=s.width,d=s.height,s.width=d*l+f*c,s.height=d*c+f*l,isNaN(o)&&(o=s.width/2),isNaN(a)&&(a=s.height/2),i.clearRect(0,0,s.width,s.height),i.fillStyle=u||"white",i.fillRect(0,0,s.width,s.height),i.save(),i.translate(o,a),i.rotate(h),i.drawImage(t,-(f/2),-(d/2)),i.rotate(-h),i.translate(-o,-a),i.restore()}else i.drawImage(t,0,0,s.width,s.height)}return s.toDataURL("png"==(""+e).toLowerCase()?"image/png":"image/jpeg")},m=function(t,e){var n;if(e)for(var r in e)if(t===e[r].alias){n=e[r];break}return n},w=function(t,e,n){return t||e||(t=-96,e=-96),0>t&&(t=-1*n.w*72/t/this.internal.scaleFactor),0>e&&(e=-1*n.h*72/e/this.internal.scaleFactor),0===t&&(t=e*n.w/n.h),0===e&&(e=t*n.h/n.w),[t,e]},g=function(t,e,n,r,s,i,o){var a=w.call(this,n,r,s),u=this.internal.getCoordinateString,c=this.internal.getVerticalCoordinateString;n=a[0],r=a[1],o[i]=s,this.internal.write("q",u(n),"0 0",u(r),u(t),c(e+r),"cm /I"+s.i,"Do Q")};t.color_spaces={DEVICE_RGB:"DeviceRGB",DEVICE_GRAY:"DeviceGray",DEVICE_CMYK:"DeviceCMYK",CAL_GREY:"CalGray",CAL_RGB:"CalRGB",LAB:"Lab",ICC_BASED:"ICCBased",INDEXED:"Indexed",PATTERN:"Pattern",SEPERATION:"Seperation",DEVICE_N:"DeviceN"},t.decode={DCT_DECODE:"DCTDecode",FLATE_DECODE:"FlateDecode",LZW_DECODE:"LZWDecode",JPX_DECODE:"JPXDecode",JBIG2_DECODE:"JBIG2Decode",ASCII85_DECODE:"ASCII85Decode",ASCII_HEX_DECODE:"ASCIIHexDecode",RUN_LENGTH_DECODE:"RunLengthDecode",CCITT_FAX_DECODE:"CCITTFaxDecode"},t.image_compression={NONE:"NONE",FAST:"FAST",MEDIUM:"MEDIUM",SLOW:"SLOW"},t.sHashCode=function(t){return Array.prototype.reduce&&t.split("").reduce(function(t,e){return t=(t<<5)-t+e.charCodeAt(0),t&t},0)},t.isString=function(t){return"string"==typeof t},t.extractInfoFromBase64DataURI=function(t){return/^data:([\w]+?\/([\w]+?));base64,(.+?)$/g.exec(t)},t.supportsArrayBuffer=function(){return"undefined"!=typeof ArrayBuffer&&"undefined"!=typeof Uint8Array},t.isArrayBuffer=function(t){return this.supportsArrayBuffer()?t instanceof ArrayBuffer:!1},t.isArrayBufferView=function(t){return this.supportsArrayBuffer()?"undefined"==typeof Uint32Array?!1:t instanceof Int8Array||t instanceof Uint8Array||"undefined"!=typeof Uint8ClampedArray&&t instanceof Uint8ClampedArray||t instanceof Int16Array||t instanceof Uint16Array||t instanceof Int32Array||t instanceof Uint32Array||t instanceof Float32Array||t instanceof Float64Array:!1},t.binaryStringToUint8Array=function(t){for(var e=t.length,n=new Uint8Array(e),r=0;e>r;r++)n[r]=t.charCodeAt(r);return n},t.arrayBufferToBinaryString=function(t){this.isArrayBuffer(t)&&(t=new Uint8Array(t));for(var e="",n=t.byteLength,r=0;n>r;r++)e+=String.fromCharCode(t[r]);return e},t.arrayBufferToBase64=function(t){for(var e,n,r,s,i,o="",a="ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789+/",u=new Uint8Array(t),c=u.byteLength,l=c%3,f=c-l,d=0;f>d;d+=3)i=u[d]<<16|u[d+1]<<8|u[d+2],e=(16515072&i)>>18,n=(258048&i)>>12,r=(4032&i)>>6,s=63&i,o+=a[e]+a[n]+a[r]+a[s];return 1==l?(i=u[f],e=(252&i)>>2,n=(3&i)<<4,o+=a[e]+a[n]+"=="):2==l&&(i=u[f]<<8|u[f+1],e=(64512&i)>>10,n=(1008&i)>>4,r=(15&i)<<2,o+=a[e]+a[n]+a[r]+"="),o},t.createImageInfo=function(t,e,n,r,s,i,o,a,u,c,l,f){var d={alias:a,w:e,h:n,cs:r,bpc:s,i:o,data:t};return i&&(d.f=i),u&&(d.dp=u),c&&(d.trns=c),l&&(d.pal=l),f&&(d.smask=f),d},t.addImage=function(t,e,r,s,i,w,y,v,b){if("string"!=typeof e){var q=w;w=i,i=s,s=r,r=e,e=q}if("object"==typeof t&&!h(t)&&"imageData"in t){var x=t;t=x.imageData,e=x.format||e,r=x.x||r||0,s=x.y||s||0,i=x.w||i,w=x.h||w,y=x.alias||y,v=x.compression||v,b=x.rotation||x.angle||b}if(isNaN(r)||isNaN(s))throw console.error("jsPDF.addImage: Invalid coordinates",arguments),new Error("Invalid coordinates passed to jsPDF.addImage");var k,_=a.call(this);if(!(k=m(t,_))){var A;if(h(t)&&(t=p(t,e,b)),c(y)&&(y=l(t)),!(k=m(y,_))){if(this.isString(t)){var C=this.extractInfoFromBase64DataURI(t);C?(e=C[2],t=atob(C[3])):137===t.charCodeAt(0)&&80===t.charCodeAt(1)&&78===t.charCodeAt(2)&&71===t.charCodeAt(3)&&(e="png")}if(e=(e||"JPEG").toLowerCase(),f(e))throw new Error("addImage currently only supports formats "+n+", not '"+e+"'");if(d(e))throw new Error("please ensure that the plugin for '"+e+"' support is added");if(this.supportsArrayBuffer()&&(A=t,t=this.binaryStringToUint8Array(t)),k=this["process"+e.toUpperCase()](t,u(_),y,o(v),A),!k)throw new Error("An unkwown error occurred whilst processing the image")}}return g.call(this,r,s,i,w,k,k.i,_),this};var y=function(t){var e,n,r;if(255===!t.charCodeAt(0)||216===!t.charCodeAt(1)||255===!t.charCodeAt(2)||224===!t.charCodeAt(3)||!t.charCodeAt(6)==="J".charCodeAt(0)||!t.charCodeAt(7)==="F".charCodeAt(0)||!t.charCodeAt(8)==="I".charCodeAt(0)||!t.charCodeAt(9)==="F".charCodeAt(0)||0===!t.charCodeAt(10))throw new Error("getJpegSize requires a binary string jpeg file");for(var s=256*t.charCodeAt(4)+t.charCodeAt(5),i=4,o=t.length;o>i;){if(i+=s,255!==t.charCodeAt(i))throw new Error("getJpegSize could not find the size of the image");if(192===t.charCodeAt(i+1)||193===t.charCodeAt(i+1)||194===t.charCodeAt(i+1)||195===t.charCodeAt(i+1)||196===t.charCodeAt(i+1)||197===t.charCodeAt(i+1)||198===t.charCodeAt(i+1)||199===t.charCodeAt(i+1))return n=256*t.charCodeAt(i+5)+t.charCodeAt(i+6),e=256*t.charCodeAt(i+7)+t.charCodeAt(i+8),r=t.charCodeAt(i+9),[e,n,r];i+=2,s=256*t.charCodeAt(i)+t.charCodeAt(i+1)}},v=function(t){var e=t[0]<<8|t[1];if(65496!==e)throw new Error("Supplied data is not a JPEG");for(var n,r,s,i,o=t.length,a=(t[4]<<8)+t[5],u=4;o>u;){if(u+=a,n=b(t,u),a=(n[2]<<8)+n[3],(192===n[1]||194===n[1])&&255===n[0]&&a>7)return n=b(t,u+5),r=(n[2]<<8)+n[3],s=(n[0]<<8)+n[1],i=n[4],{width:r,height:s,numcomponents:i};u+=2}throw new Error("getJpegSizeFromBytes could not find the size of the image")},b=function(t,e){return t.subarray(e,e+5)};t.processJPEG=function(t,e,n,r,s){var i,o=this.color_spaces.DEVICE_RGB,a=this.decode.DCT_DECODE,u=8;return this.isString(t)?(i=y(t),this.createImageInfo(t,i[0],i[1],1==i[3]?this.color_spaces.DEVICE_GRAY:o,u,a,e,n)):(this.isArrayBuffer(t)&&(t=new Uint8Array(t)),this.isArrayBufferView(t)?(i=v(t),t=s||this.arrayBufferToBinaryString(t),this.createImageInfo(t,i.width,i.height,1==i.numcomponents?this.color_spaces.DEVICE_GRAY:o,u,a,e,n)):null)},t.processJPG=function(){return this.processJPEG.apply(this,arguments)}}(n.API),function(t){"use strict";t.autoPrint=function(){var t;return this.internal.events.subscribe("postPutResources",function(){t=this.internal.newObject(),this.internal.write("<< /S/Named /Type/Action /N/Print >>","endobj")}),this.internal.events.subscribe("putCatalog",function(){this.internal.write("/OpenAction "+t+" 0 R")}),this}}(n.API),function(t){"use strict";var e,n,r,s,i=3,o=13,a={x:void 0,y:void 0,w:void 0,h:void 0,ln:void 0},u=1,c=function(t,e,n,r,s){a={x:t,y:e,w:n,h:r,ln:s}},l=function(){return a},f={left:0,top:0,bottom:0};t.setHeaderFunction=function(t){s=t},t.getTextDimensions=function(t){e=this.internal.getFont().fontName,n=this.table_font_size||this.internal.getFontSize(),r=this.internal.getFont().fontStyle;var s,i,o=19.049976/25.4;return i=document.createElement("font"),i.id="jsPDFCell",i.style.fontStyle=r,i.style.fontName=e,i.style.fontSize=n+"pt",i.textContent=t,document.body.appendChild(i),s={w:(i.offsetWidth+1)*o,h:(i.offsetHeight+1)*o},document.body.removeChild(i),s},t.cellAddPage=function(){var t=this.margins||f;this.addPage(),c(t.left,t.top,void 0,void 0),u+=1},t.cellInitialize=function(){a={x:void 0,y:void 0,w:void 0,h:void 0,ln:void 0},u=1},t.cell=function(t,e,n,r,s,a,u){var d=l();if(void 0!==d.ln)if(d.ln===a)t=d.x+d.w,e=d.y;else{var h=this.margins||f;d.y+d.h+r+o>=this.internal.pageSize.height-h.bottom&&(this.cellAddPage(),this.printHeaders&&this.tableHeaderRow&&this.printHeaderRow(a,!0)),e=l().y+l().h}if(void 0!==s[0])if(this.printingHeaderRow?this.rect(t,e,n,r,"FD"):this.rect(t,e,n,r),"right"===u){if(s instanceof Array)for(var p=0;pn;n+=1)s=t[n],e?-1===e(i,s)&&(i=s):s>i&&(i=s);return i},t.table=function(e,n,r,s,i){if(!r)throw"No data for PDF table";var o,c,l,d,h,p,m,w,g,y,v=[],b=[],q={},x={},k=[],_=[],A=!1,C=!0,S=12,E=f;if(E.width=this.internal.pageSize.width,i&&(i.autoSize===!0&&(A=!0),i.printHeaders===!1&&(C=!1),i.fontSize&&(S=i.fontSize),i.margins&&(E=i.margins)),this.lnMod=0,a={x:void 0,y:void 0,w:void 0,h:void 0,ln:void 0},u=1,this.printHeaders=C,this.margins=E,this.setFontSize(S),this.table_font_size=S,void 0===s||null===s)v=Object.keys(r[0]);else if(s[0]&&"string"!=typeof s[0]){var z=19.049976/25.4;for(c=0,l=s.length;l>c;c+=1)o=s[c],v.push(o.name),b.push(o.prompt),x[o.name]=o.width*z}else v=s;if(A)for(y=function(t){return t[o]},c=0,l=v.length;l>c;c+=1){for(o=v[c],q[o]=r.map(y),k.push(this.getTextDimensions(b[c]||o).w),p=q[o],m=0,d=p.length;d>m;m+=1)h=p[m],k.push(this.getTextDimensions(h).w);x[o]=t.arrayMax(k)}if(C){var T=this.calculateLineHeight(v,x,b.length?b:v);for(c=0,l=v.length;l>c;c+=1)o=v[c],_.push([e,n,x[o],T,String(b.length?b[c]:o)]);this.setTableHeaderRow(_),this.printHeaderRow(1,!1)}for(c=0,l=r.length;l>c;c+=1){var T;for(w=r[c],T=this.calculateLineHeight(v,x,w),m=0,g=v.length;g>m;m+=1)o=v[m],this.cell(e,n,x[o],T,w[o],c+2,o.align)}return this.lastCellPos=a,this.table_x=e,this.table_y=n,this},t.calculateLineHeight=function(t,e,n){for(var r,s=0,o=0;os&&(s=a)}return s},t.setTableHeaderRow=function(t){this.tableHeaderRow=t},t.printHeaderRow=function(t,e){if(!this.tableHeaderRow)throw"Property tableHeaderRow does not exist.";var n,r,i,o;if(this.printingHeaderRow=!0,void 0!==s){var a=s(this,u);c(a[0],a[1],a[2],a[3],-1)}this.setFontStyle("bold");var l=[];for(i=0,o=this.tableHeaderRow.length;o>i;i+=1)this.setFillColor(200,200,200),n=this.tableHeaderRow[i],e&&(n[1]=this.margins&&this.margins.top||0,l.push(n)),r=[].concat(n),this.cell.apply(this,r.concat(t));l.length>0&&this.setTableHeaderRow(l),this.setFontStyle("normal"),this.printingHeaderRow=!1}}(n.API),function(t){var e,n,r,s,i,o,a,u,c,l,f,d,h,p,m,w,g,y,v;e=function(){function t(){}return function(e){return t.prototype=e,new t}}(),c=function(t){var e,n,r,s,i,o,a;for(n=0,r=t.length,e=void 0,s=!1,o=!1;!s&&n!==r;)e=t[n]=t[n].trimLeft(),e&&(s=!0),n++;for(n=r-1;r&&!o&&-1!==n;)e=t[n]=t[n].trimRight(),e&&(o=!0),n--;for(i=/\s+$/g,a=!0,n=0;n!==r;)e=t[n].replace(/\s+/g," "),a&&(e=e.trimLeft()),e&&(a=i.test(e)),t[n]=e,n++;return t},l=function(t,e,n,r){return this.pdf=t,this.x=e,this.y=n,this.settings=r,this.watchFunctions=[],this.init(),this},f=function(t){var e,n,s;for(e=void 0,s=t.split(","),n=s.shift();!e&&n;)e=r[n.trim().toLowerCase()],n=s.shift();return e},d=function(t){t="auto"===t?"0px":t,t.indexOf("em")>-1&&!isNaN(Number(t.replace("em","")))&&(t=18.719*Number(t.replace("em",""))+"px"),t.indexOf("pt")>-1&&!isNaN(Number(t.replace("pt","")))&&(t=1.333*Number(t.replace("pt",""))+"px");var e,n,r;return n=void 0,e=16,(r=h[t])?r:(r={"xx-small":9,"x-small":11,small:13,medium:16,large:19,"x-large":23,"xx-large":28,auto:0}[{css_line_height_string:t}],r!==n?h[t]=r/e:(r=parseFloat(t))?h[t]=r/e:(r=t.match(/([\d\.]+)(px)/),h[t]=3===r.length?parseFloat(r[1])/e:1))},u=function(t){var e,n,r;return r=function(t){var e;return e=function(t){return document.defaultView&&document.defaultView.getComputedStyle?document.defaultView.getComputedStyle(t,null):t.currentStyle?t.currentStyle:t.style}(t),function(t){return t=t.replace(/-\D/g,function(t){return t.charAt(1).toUpperCase()}),e[t]}}(t),e={},n=void 0,e["font-family"]=f(r("font-family"))||"times",e["font-style"]=s[r("font-style")]||"normal",e["text-align"]=TextAlignMap[r("text-align")]||"left",n=i[r("font-weight")]||"normal","bold"===n&&(e["font-style"]="normal"===e["font-style"]?n:n+e["font-style"]),e["font-size"]=d(r("font-size"))||1,e["line-height"]=d(r("line-height"))||1,e.display="inline"===r("display")?"inline":"block",n="block"===e.display,e["margin-top"]=n&&d(r("margin-top"))||0,e["margin-bottom"]=n&&d(r("margin-bottom"))||0,e["padding-top"]=n&&d(r("padding-top"))||0,e["padding-bottom"]=n&&d(r("padding-bottom"))||0,e["margin-left"]=n&&d(r("margin-left"))||0,e["margin-right"]=n&&d(r("margin-right"))||0,e["padding-left"]=n&&d(r("padding-left"))||0,e["padding-right"]=n&&d(r("padding-right"))||0,e["float"]=o[r("cssFloat")]||"none",e.clear=a[r("clear")]||"none",e},p=function(t,e,n){var r,s,i,o,a;if(i=!1,s=void 0,o=void 0,a=void 0,r=n["#"+t.id])if("function"==typeof r)i=r(t,e);else for(s=0,o=r.length;!i&&s!==o;)i=r[s](t,e),s++;if(r=n[t.nodeName],!i&&r)if("function"==typeof r)i=r(t,e);else for(s=0,o=r.length;!i&&s!==o;)i=r[s](t,e),s++;return i},v=function(t,e){var n,r,s,i,o,a,u,c,l,f; +for(n=[],r=[],s=0,f=t.rows[0].cells.length,c=t.clientWidth;f>s;)l=t.rows[0].cells[s],r[s]={name:l.textContent.toLowerCase().replace(/\s+/g,""),prompt:l.textContent.replace(/\r?\n/g,""),width:l.clientWidth/c*e.pdf.internal.pageSize.width},s++;for(s=1;sa;){if(s=i[a],"object"==typeof s){if(e.executeWatchFunctions(s),1===s.nodeType&&"HEADER"===s.nodeName){var w=s,g=e.pdf.margins_doc.top;e.pdf.internal.events.subscribe("addPage",function(){e.y=g,n(w,e,r),e.pdf.margins_doc.top=e.y+10,e.y+=10},!1)}if(8===s.nodeType&&"#comment"===s.nodeName)~s.textContent.indexOf("ADD_PAGE")&&(e.pdf.addPage(),e.y=e.pdf.margins_doc.top);else if(1!==s.nodeType||b[s.nodeName])if(3===s.nodeType){var y=s.nodeValue;if(s.nodeValue&&"LI"===s.parentNode.nodeName)if("OL"===s.parentNode.parentNode.nodeName)y=q++ +". "+y;else{var x=16*o["font-size"],k=2;x>20&&(k=3),h=function(t,e){this.pdf.circle(t,e,k,"FD")}}e.addText(y,o)}else"string"==typeof s&&e.addText(s,o);else{var _;if("IMG"===s.nodeName){var A=s.getAttribute("src");_=m[e.pdf.sHashCode(A)||A]}if(_){e.pdf.internal.pageSize.height-e.pdf.margins_doc.bottome.pdf.margins_doc.top&&(e.pdf.addPage(),e.y=e.pdf.margins_doc.top,e.executeWatchFunctions(s));var C=u(s),S=e.x,E=12/e.pdf.internal.scaleFactor,z=(C["margin-left"]+C["padding-left"])*E,T=(C["margin-right"]+C["padding-right"])*E,I=(C["margin-top"]+C["padding-top"])*E,B=(C["margin-bottom"]+C["padding-bottom"])*E;S+=void 0!==C["float"]&&"right"===C["float"]?e.settings.width-s.width-T:z,e.pdf.addImage(_,S,e.y+I,s.width,s.height),_=void 0,"right"===C["float"]||"left"===C["float"]?(e.watchFunctions.push(function(t,n,r,s){return e.y>=n?(e.x+=t,e.settings.width+=r,!0):s&&1===s.nodeType&&!b[s.nodeName]&&e.x+s.width>e.pdf.margins_doc.left+e.pdf.margins_doc.width?(e.x+=t,e.y=n,e.settings.width+=r,!0):!1}.bind(this,"left"===C["float"]?-s.width-z-T:0,e.y+s.height+I+B,s.width)),e.watchFunctions.push(function(t,n,r){return e.y0){s=s[0];var i=e.pdf.internal.write,o=e.y;e.pdf.internal.write=function(){},n(s,e,r);var a=Math.ceil(e.y-o)+5;e.y=o,e.pdf.internal.write=i,e.pdf.margins_doc.bottom+=a;for(var u=function(t){var i=void 0!==t?t.pageNumber:1,o=e.y;e.y=e.pdf.internal.pageSize.height-e.pdf.margins_doc.bottom,e.pdf.margins_doc.bottom-=a;for(var u=s.getElementsByTagName("span"),c=0;c-1&&(u[c].innerHTML=i),(" "+u[c].className+" ").replace(/[\n\t]/g," ").indexOf(" totalPages ")>-1&&(u[c].innerHTML="###jsPDFVarTotalPages###");n(s,e,r),e.pdf.margins_doc.bottom+=a,e.y=o},c=s.getElementsByTagName("span"),l=0;l-1&&e.pdf.internal.events.subscribe("htmlRenderingFinished",e.pdf.putTotalPages.bind(e.pdf,"###jsPDFVarTotalPages###"),!0);e.pdf.internal.events.subscribe("addPage",u,!1),u(),b.FOOTER=1}},y=function(t,e,r,s,i,o){if(!e)return!1;"string"==typeof e||e.parentNode||(e=""+e.innerHTML),"string"==typeof e&&(e=function(t){var e,n,r,s;return r="jsPDFhtmlText"+Date.now().toString()+(1e3*Math.random()).toFixed(0),s="position: absolute !important;clip: rect(1px 1px 1px 1px); /* IE6, IE7 */clip: rect(1px, 1px, 1px, 1px);padding:0 !important;border:0 !important;height: 1px !important;width: 1px !important; top:auto;left:-100px;overflow: hidden;",n=document.createElement("div"),n.style.cssText=s,n.innerHTML='