├── .gitignore ├── sfswitch ├── __init__.py ├── asgi.py ├── wsgi.py ├── urls.py └── settings.py ├── enable_disable ├── __init__.py ├── management │ ├── __init__.py │ └── commands │ │ ├── __init__.py │ │ └── delete_jobs.py ├── migrations │ ├── __init__.py │ └── 0001_initial.py ├── tests.py ├── apps.py ├── forms.py ├── admin.py ├── models.py ├── views.py └── tasks.py ├── runtime.txt ├── db.sqlite3 ├── static ├── images │ ├── icon.png │ ├── logo.png │ ├── favicon.ico │ ├── i-icon.png │ ├── loading.gif │ ├── oh-dear.png │ ├── logo-small.png │ └── tquila-logo@1x.png ├── bootstrap │ ├── fonts │ │ ├── glyphicons-halflings-regular.eot │ │ ├── glyphicons-halflings-regular.ttf │ │ └── glyphicons-halflings-regular.woff │ └── css │ │ ├── bootstrap-theme.min.css │ │ ├── bootstrap-theme.css.map │ │ └── bootstrap-theme.css ├── js │ ├── cookie.js │ ├── main.js │ ├── metadata_update.js │ ├── jquery-migrate-1.1.0.min.js │ └── bootstrap-switch.min.js ├── jquery-syntaxhighlighter │ ├── prettify │ │ ├── prettify.min.css │ │ └── prettify.min.js │ ├── styles │ │ ├── style.min.css │ │ └── theme-balupton.min.css │ └── jquery.syntaxhighlighter.min.js └── css │ ├── styles.css │ └── bootstrap-switch.min.css ├── Procfile ├── templates ├── 500.html ├── logout.html ├── 404.html ├── loading.html ├── oauth_response.html ├── index.html ├── base.html └── job.html ├── README.md ├── manage.py └── requirements.txt /.gitignore: -------------------------------------------------------------------------------- 1 | *.pyc -------------------------------------------------------------------------------- /sfswitch/__init__.py: -------------------------------------------------------------------------------- 1 | -------------------------------------------------------------------------------- /enable_disable/__init__.py: -------------------------------------------------------------------------------- 1 | -------------------------------------------------------------------------------- /runtime.txt: -------------------------------------------------------------------------------- 1 | python-3.11.2 -------------------------------------------------------------------------------- /enable_disable/management/__init__.py: -------------------------------------------------------------------------------- 1 | -------------------------------------------------------------------------------- /enable_disable/migrations/__init__.py: -------------------------------------------------------------------------------- 1 | -------------------------------------------------------------------------------- /enable_disable/management/commands/__init__.py: -------------------------------------------------------------------------------- 1 | -------------------------------------------------------------------------------- /db.sqlite3: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/benedwards44/sfswitch/HEAD/db.sqlite3 -------------------------------------------------------------------------------- /enable_disable/tests.py: -------------------------------------------------------------------------------- 1 | from django.test import TestCase 2 | 3 | # Create your tests here. 4 | -------------------------------------------------------------------------------- /static/images/icon.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/benedwards44/sfswitch/HEAD/static/images/icon.png -------------------------------------------------------------------------------- /static/images/logo.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/benedwards44/sfswitch/HEAD/static/images/logo.png -------------------------------------------------------------------------------- /static/images/favicon.ico: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/benedwards44/sfswitch/HEAD/static/images/favicon.ico -------------------------------------------------------------------------------- /static/images/i-icon.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/benedwards44/sfswitch/HEAD/static/images/i-icon.png -------------------------------------------------------------------------------- /static/images/loading.gif: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/benedwards44/sfswitch/HEAD/static/images/loading.gif -------------------------------------------------------------------------------- /static/images/oh-dear.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/benedwards44/sfswitch/HEAD/static/images/oh-dear.png -------------------------------------------------------------------------------- /static/images/logo-small.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/benedwards44/sfswitch/HEAD/static/images/logo-small.png -------------------------------------------------------------------------------- /static/images/tquila-logo@1x.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/benedwards44/sfswitch/HEAD/static/images/tquila-logo@1x.png -------------------------------------------------------------------------------- /Procfile: -------------------------------------------------------------------------------- 1 | web: gunicorn sfswitch.wsgi --workers $WEB_CONCURRENCY 2 | worker: celery -A enable_disable.tasks worker -B --loglevel=info -------------------------------------------------------------------------------- /static/bootstrap/fonts/glyphicons-halflings-regular.eot: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/benedwards44/sfswitch/HEAD/static/bootstrap/fonts/glyphicons-halflings-regular.eot -------------------------------------------------------------------------------- /static/bootstrap/fonts/glyphicons-halflings-regular.ttf: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/benedwards44/sfswitch/HEAD/static/bootstrap/fonts/glyphicons-halflings-regular.ttf -------------------------------------------------------------------------------- /static/bootstrap/fonts/glyphicons-halflings-regular.woff: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/benedwards44/sfswitch/HEAD/static/bootstrap/fonts/glyphicons-halflings-regular.woff -------------------------------------------------------------------------------- /enable_disable/apps.py: -------------------------------------------------------------------------------- 1 | # -*- coding: utf-8 -*- 2 | from __future__ import unicode_literals 3 | 4 | from django.apps import AppConfig 5 | 6 | class EnableDisableConfig(AppConfig): 7 | name = 'enable_disable' 8 | -------------------------------------------------------------------------------- /enable_disable/forms.py: -------------------------------------------------------------------------------- 1 | from django import forms 2 | from django.forms import ModelForm 3 | 4 | class LoginForm(forms.Form): 5 | environment = forms.CharField(required=False) 6 | access_token = forms.CharField(required=False) 7 | instance_url = forms.CharField(required=False) 8 | username = forms.CharField(required=False) 9 | org_name = forms.CharField(required=False) 10 | org_id = forms.CharField(required=False) -------------------------------------------------------------------------------- /templates/500.html: -------------------------------------------------------------------------------- 1 | {% extends 'base.html' %} 2 | 3 | {% block content %} 4 | 5 |
6 | 7 |

500 - Website Error

8 | 9 | Doh 10 | 11 | 14 | 15 |
16 | 17 | {% endblock %} -------------------------------------------------------------------------------- /enable_disable/management/commands/delete_jobs.py: -------------------------------------------------------------------------------- 1 | from django.core.management.base import BaseCommand 2 | from enable_disable.models import Job 3 | import datetime 4 | 5 | class Command(BaseCommand): 6 | 7 | def handle(self, *args, **options): 8 | 9 | one_day_ago = datetime.datetime.now() - datetime.timedelta(hours=24) 10 | jobs = Job.objects.filter(created_date__lt = one_day_ago) 11 | jobs.delete() 12 | 13 | 14 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # Salesforce Switch 2 | 3 | Django application designed to run on Heroku for disabling and enabling Salesforce automations (Workflows, Process Flows, Validation Rules and Triggers) 4 | 5 | ## Setup 6 | 7 | To set this up on your own Heroku account, this app does require some knowledge of deploying Django apps. The following Heroku apps are required: 8 | - Heroku Postgres (or alternative database) 9 | - Redis To Go (or Heroku Redis - but might require some minor changes) -------------------------------------------------------------------------------- /sfswitch/asgi.py: -------------------------------------------------------------------------------- 1 | """ 2 | ASGI config for sfswitch project. 3 | 4 | It exposes the ASGI callable as a module-level variable named ``application``. 5 | 6 | For more information on this file, see 7 | https://docs.djangoproject.com/en/4.1/howto/deployment/asgi/ 8 | """ 9 | 10 | import os 11 | 12 | from django.core.asgi import get_asgi_application 13 | 14 | os.environ.setdefault('DJANGO_SETTINGS_MODULE', 'sfswitch.settings') 15 | 16 | application = get_asgi_application() 17 | -------------------------------------------------------------------------------- /sfswitch/wsgi.py: -------------------------------------------------------------------------------- 1 | """ 2 | WSGI config for sfswitch project. 3 | 4 | It exposes the WSGI callable as a module-level variable named ``application``. 5 | 6 | For more information on this file, see 7 | https://docs.djangoproject.com/en/4.1/howto/deployment/wsgi/ 8 | """ 9 | 10 | import os 11 | 12 | from django.core.wsgi import get_wsgi_application 13 | 14 | os.environ.setdefault('DJANGO_SETTINGS_MODULE', 'sfswitch.settings') 15 | 16 | application = get_wsgi_application() 17 | -------------------------------------------------------------------------------- /templates/logout.html: -------------------------------------------------------------------------------- 1 | {% extends 'base.html' %} 2 | 3 | {% block content %} 4 | 5 |

Salesforce Switch

6 | 7 | 10 | 11 | 12 | 13 | 18 | 19 | {% endblock %} -------------------------------------------------------------------------------- /static/js/cookie.js: -------------------------------------------------------------------------------- 1 | // using jQuery 2 | function getCookie(name) { 3 | var cookieValue = null; 4 | if (document.cookie && document.cookie != '') { 5 | var cookies = document.cookie.split(';'); 6 | for (var i = 0; i < cookies.length; i++) { 7 | var cookie = jQuery.trim(cookies[i]); 8 | // Does this cookie string begin with the name we want? 9 | if (cookie.substring(0, name.length + 1) == (name + '=')) { 10 | cookieValue = decodeURIComponent(cookie.substring(name.length + 1)); 11 | break; 12 | } 13 | } 14 | } 15 | return cookieValue; 16 | } -------------------------------------------------------------------------------- /static/jquery-syntaxhighlighter/prettify/prettify.min.css: -------------------------------------------------------------------------------- 1 | .str{color:#080}.kwd{color:#008}.com{color:#800}.typ{color:#606}.lit{color:#066}.pun{color:#660}.pln{color:#000}.tag{color:#008}.atn{color:#606}.atv{color:#080}.dec{color:#606}pre.prettyprint{padding:2px;border:1px solid #888}ol.linenums{margin-top:0;margin-bottom:0}li.L0,li.L1,li.L2,li.L3,li.L5,li.L6,li.L7,li.L8{list-style-type:none}li.L1,li.L3,li.L5,li.L7,li.L9{background:#eee}@media print{.str{color:#060}.kwd{color:#006;font-weight:bold}.com{color:#600;font-style:italic}.typ{color:#404;font-weight:bold}.lit{color:#044}.pun{color:#440}.pln{color:#000}.tag{color:#006;font-weight:bold}.atn{color:#404}.atv{color:#060}} -------------------------------------------------------------------------------- /templates/404.html: -------------------------------------------------------------------------------- 1 | {% extends 'base.html' %} 2 | 3 | {% block content %} 4 | 5 |
6 | 7 |

404 - Page Not Found

8 | 9 | Doh 10 | 11 | 16 | 17 |
18 | 19 | {% endblock %} -------------------------------------------------------------------------------- /manage.py: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env python 2 | """Django's command-line utility for administrative tasks.""" 3 | import os 4 | import sys 5 | 6 | 7 | def main(): 8 | """Run administrative tasks.""" 9 | os.environ.setdefault('DJANGO_SETTINGS_MODULE', 'sfswitch.settings') 10 | try: 11 | from django.core.management import execute_from_command_line 12 | except ImportError as exc: 13 | raise ImportError( 14 | "Couldn't import Django. Are you sure it's installed and " 15 | "available on your PYTHONPATH environment variable? Did you " 16 | "forget to activate a virtual environment?" 17 | ) from exc 18 | execute_from_command_line(sys.argv) 19 | 20 | 21 | if __name__ == '__main__': 22 | main() 23 | -------------------------------------------------------------------------------- /sfswitch/urls.py: -------------------------------------------------------------------------------- 1 | from django.urls import path 2 | from django.views.generic import TemplateView 3 | from django.contrib import admin 4 | 5 | from enable_disable import views 6 | 7 | urlpatterns = [ 8 | path('', views.index, name='index'), 9 | path('admin/', admin.site.urls), 10 | path('oauth_response/', views.oauth_response, name='oauth_response'), 11 | path('logout/', views.logout, name='logout'), 12 | path('loading//', views.loading), 13 | path('job_status//', views.job_status), 14 | path('job//', views.job), 15 | path('update_metadata///', views.update_metadata), 16 | path('check_deploy_status//', views.check_deploy_status), 17 | path('auth_details/', views.auth_details), 18 | ] 19 | -------------------------------------------------------------------------------- /requirements.txt: -------------------------------------------------------------------------------- 1 | amqp==5.1.1 2 | asgiref==3.6.0 3 | async-timeout==4.0.2 4 | billiard==3.6.4.0 5 | celery==5.2.7 6 | certifi==2022.12.7 7 | charset-normalizer==3.1.0 8 | click==8.1.3 9 | click-didyoumean==0.3.0 10 | click-plugins==1.1.1 11 | click-repl==0.2.0 12 | defusedxml==0.7.1 13 | diff-match-patch==20200713 14 | dj-database-url==1.3.0 15 | Django==4.2 16 | django-import-export==3.2.0 17 | et-xmlfile==1.1.0 18 | gunicorn==20.1.0 19 | idna==3.4 20 | kombu==5.2.4 21 | MarkupPy==1.14 22 | odfpy==1.4.1 23 | openpyxl==3.1.2 24 | prompt-toolkit==3.0.38 25 | psycopg2==2.9.6 26 | pytz==2023.3 27 | PyYAML==6.0 28 | redis==4.5.4 29 | requests==2.28.2 30 | six==1.16.0 31 | sqlparse==0.4.4 32 | suds==1.1.2 33 | tablib==3.4.0 34 | typing_extensions==4.5.0 35 | urllib3==1.26.15 36 | vine==5.0.0 37 | wcwidth==0.2.6 38 | whitenoise==6.4.0 39 | xlrd==2.0.1 40 | xlwt==1.3.0 41 | -------------------------------------------------------------------------------- /static/jquery-syntaxhighlighter/styles/style.min.css: -------------------------------------------------------------------------------- 1 | .prettyprint ol,.prettyprint ul{list-style:none}.prettyprint ol.linenums{list-style:decimal outside;padding-left:47px;color:#afafaf;font-size:12px}.prettyprint ol.linenums li{padding-left:15px;border-left:3px #6ce26c solid}.prettyprint li>*{font-size:14px}.prettyprint{border:5px solid #DDD;padding:7px 0 5px;font-size:14px;white-space:pre;overflow:auto;max-height:500px;width:100%;display:block}.prettyprint ol,.prettyprint ul{padding:5px 0}.prettyprint li.L0,.prettyprint li.L1,.prettyprint li.L2,.prettyprint li.L3,.prettyprint li.L4,.prettyprint li.L5,.prettyprint li.L6,.prettyprint li.L7,.prettyprint li.L8,.prettyprint li.L9{list-style:inherit;background:0}.prettyprint.alternate .prettyprint.alternate li.L1,.prettyprint.alternate li.L3,.prettyprint.alternate li.L5,.prettyprint.alternate li.L7,.prettyprint.alternate li.L9{background:#f5f5f5} -------------------------------------------------------------------------------- /static/jquery-syntaxhighlighter/styles/theme-balupton.min.css: -------------------------------------------------------------------------------- 1 | .prettyprint.theme-balupton{font-family:Consolas,'Bitstream Vera Sans Mono','Courier New',Courier,monospace;font-size:14px;font-style:normal;font-weight:normal;line-height:15px}.prettyprint.theme-balupton .com{color:#008200}.prettyprint.theme-balupton .lit{color:#066}.prettyprint.theme-balupton.lang-html .lit{color:#066}.prettyprint.theme-balupton.lang-html .kwd{color:#066;font-weight:bold}.prettyprint.theme-balupton.lang-html .atv+.pln,.prettyprint.theme-balupton.lang-html .pun+.pln{color:blue}.prettyprint.theme-balupton .atv,.prettyprint.theme-balupton .str{color:blue}.prettyprint.theme-balupton .atn{color:gray}.prettyprint.theme-balupton .pln{color:black}.prettyprint.theme-balupton .pun{color:#666}.prettyprint.theme-balupton .typ{color:#606}.prettyprint.theme-balupton .tag,.prettyprint.theme-balupton .kwd{color:#069;font-weight:bold} -------------------------------------------------------------------------------- /static/js/main.js: -------------------------------------------------------------------------------- 1 | $(document).ready(function() { 2 | 3 | $('.loading').hide(); 4 | 5 | $('.logging_out').hide(); 6 | 7 | /* Scroll to top logic */ 8 | var offset = 220; 9 | var duration = 500; 10 | $(window).scroll(function() { 11 | if (jQuery(this).scrollTop() > offset) { 12 | jQuery('.back-to-top').fadeIn(duration); 13 | } else { 14 | jQuery('.back-to-top').fadeOut(duration); 15 | } 16 | }); 17 | 18 | $('.back-to-top').click(function(event) { 19 | event.preventDefault(); 20 | jQuery('html, body').animate({scrollTop: 0}, duration); 21 | return false; 22 | }); 23 | 24 | }); 25 | 26 | function hideTable() 27 | { 28 | $('.login_table').hide(); 29 | $('.messages').hide(); 30 | $('.loading').show(); 31 | } 32 | 33 | function showTable() 34 | { 35 | $('.login_table').show(); 36 | $('.messages').show(); 37 | $('.loading').hide(); 38 | } 39 | 40 | function showLogout() 41 | { 42 | $('.login_table').hide(); 43 | $('.messages').hide(); 44 | $('.logging_out').show(); 45 | } 46 | 47 | function hideLogout() 48 | { 49 | $('.login_table').show(); 50 | $('.messages').show(); 51 | $('.logging_out').hide(); 52 | } -------------------------------------------------------------------------------- /enable_disable/admin.py: -------------------------------------------------------------------------------- 1 | from django.contrib import admin 2 | from enable_disable.models import Job, ValidationRule, WorkflowRule, ApexTrigger, Flow, DeployJob, DeployJobComponent 3 | from import_export.admin import ImportExportModelAdmin 4 | 5 | """ 6 | INLINES/RELATED LISTS 7 | """ 8 | class ValidationRuleInline(admin.TabularInline): 9 | fields = ['object_name','name','active'] 10 | ordering = ['object_name', 'name'] 11 | model = ValidationRule 12 | extra = 0 13 | 14 | class WorkflowRuleInline(admin.TabularInline): 15 | fields = ['object_name','name','active'] 16 | ordering = ['object_name', 'name'] 17 | model = WorkflowRule 18 | extra = 0 19 | 20 | class ApexTriggerInline(admin.TabularInline): 21 | fields = ['name','active', 'content', 'meta_content'] 22 | ordering = ['name'] 23 | model = ApexTrigger 24 | extra = 0 25 | 26 | class FlowInline(admin.TabularInline): 27 | fields = ['flow_id','name','active', 'active_version', 'latest_version'] 28 | ordering = ['name'] 29 | model = Flow 30 | extra = 0 31 | 32 | class DeployJobInline(admin.TabularInline): 33 | fields = ['metadata_type','status','error'] 34 | ordering = ['id'] 35 | model = DeployJob 36 | extra = 0 37 | 38 | class DeployJobComponentInline(admin.TabularInline): 39 | fields = ['validation_rule','workflow_rule','trigger','enable', 'status', 'error'] 40 | ordering = ['id'] 41 | model = DeployJobComponent 42 | extra = 0 43 | 44 | 45 | """ 46 | ADMIN CLASSES 47 | """ 48 | class JobAdmin(admin.ModelAdmin): 49 | list_display = ('created_date','finished_date','username','status','error') 50 | ordering = ['-created_date'] 51 | inlines = [ValidationRuleInline, WorkflowRuleInline, ApexTriggerInline, FlowInline, DeployJobInline] 52 | 53 | class DeployJobAdmin(admin.ModelAdmin): 54 | list_display = ('job','metadata_type','status','error') 55 | ordering = ['-id'] 56 | inlines = [DeployJobComponentInline] 57 | 58 | 59 | class ValidationRuleAdmin(ImportExportModelAdmin): 60 | resource_class = ValidationRule 61 | pass 62 | 63 | 64 | admin.site.register(Job, JobAdmin) 65 | admin.site.register(DeployJob, DeployJobAdmin) 66 | admin.site.register(ValidationRule, ValidationRuleAdmin) 67 | -------------------------------------------------------------------------------- /templates/loading.html: -------------------------------------------------------------------------------- 1 | {% extends 'base.html' %} 2 | 3 | {% load static %} 4 | 5 | {% block content %} 6 | 7 |

Salesforce Switch

8 |

9 | This tool provides an interface to easily enable and disable components in your Salesforce Org - Workflows, Triggers and Validation Rules. Very useful when doing data migrations and needing to disable certain automation. 10 |

11 |

12 | None of your organisation information or data is captured or kept from running this tool. 13 |

14 | 15 |
16 | 17 |
18 | Loading 19 |
20 | 21 |
22 |

Querying metadata

23 |

Building a list of validation rules, workflows and triggers...

24 |
25 | 26 |
27 | 28 | 37 | 38 | 68 | 69 | {% endblock %} -------------------------------------------------------------------------------- /templates/oauth_response.html: -------------------------------------------------------------------------------- 1 | {% extends 'base.html' %} 2 | 3 | {% load static %} 4 | 5 | {% block content %} 6 | 7 |

Salesforce Switch

8 |

9 | This tool provides an interface to easily enable and disable components in your Salesforce Org - Workflows, Triggers and Validation Rules. Very useful when doing data migrations and needing to disable certain automation. 10 |

11 |

12 | None of your organisation information or data is captured or kept from running this tool. 13 |

14 | 15 | 16 | 17 | 18 | 19 | {% csrf_token %} 20 | 21 |
22 | 23 | {% if messages %} 24 | 25 | 26 | 32 | 33 | 34 | {% endif %} 35 | 36 | {% if not error %} 37 | 38 |

Logged in as:

39 | 40 | 41 | 44 | 47 | 48 | 49 | 50 | 53 | 56 | 57 | 58 | 59 | 60 | 61 | 62 | 63 | 67 | 68 | 69 | {% else %} 70 | 71 | 72 | 82 | 83 | 84 | {% endif %} 85 | 86 | 87 | 88 |
89 | 90 |
91 | Loading 92 |
93 | 94 |
95 |

Querying metadata

96 |

Building a list of validation rules, workflows and triggers...

97 |
98 | 99 |
100 | 101 |
102 | 103 |
104 | Loading 105 |
106 | 107 |
108 |

Logging out...

109 |

Revoking OAuth token.

110 |
111 | 112 |
113 | 114 |
115 | {{ login_form.as_p }} 116 |
117 | 118 | 119 | 120 | 121 | 122 | {% endblock %} -------------------------------------------------------------------------------- /templates/index.html: -------------------------------------------------------------------------------- 1 | {% extends 'base.html' %} 2 | 3 | {% load static %} 4 | 5 | {% block content %} 6 | 7 |

Salesforce Switch

8 |

9 | This tool provides an interface to easily enable and disable components in your Salesforce Org - Workflows, Triggers and Validation Rules. Very useful when doing data migrations and needing to disable certain automation. 10 |

11 |

12 | None of your organisation information or data is captured or kept from running this tool. 13 |

14 | 25 | 32 | 33 |
34 | 35 |
36 | {% csrf_token %} 37 | 38 | 39 | 40 | {% if messages %} 41 | 42 | 48 | 49 | {% endif %} 50 | 51 | 52 | 55 | 61 | 64 | 65 | 66 | 67 | 68 |
69 |
70 | Loading 71 |
72 |
73 |

Accessing Salesforce...

74 |

Logging in with OAuth 2.0

75 |
76 |
77 | 78 |
79 | 80 |
81 | 82 | {% endblock %} -------------------------------------------------------------------------------- /sfswitch/settings.py: -------------------------------------------------------------------------------- 1 | import os 2 | import dj_database_url 3 | from pathlib import Path 4 | 5 | BASE_DIR = Path(__file__).resolve().parent.parent 6 | 7 | IS_HEROKU = "DYNO" in os.environ 8 | 9 | # SECURITY WARNING: keep the secret key used in production secret! 10 | SECRET_KEY = os.environ.get('SECRET_KEY', 'LOCAL') 11 | 12 | # SECURITY WARNING: don't run with debug turned on in production! 13 | DEBUG = os.environ.get('DEBUG') == '1' 14 | TEMPLATE_DEBUG = DEBUG 15 | THUMBNAIL_DEBUG = DEBUG 16 | 17 | ADMINS = ( 18 | ('Ben Edwards', 'ben@edwards.nz'), 19 | ) 20 | 21 | if IS_HEROKU: 22 | SECURE_PROXY_SSL_HEADER = ('HTTP_X_FORWARDED_PROTO', 'https') 23 | SECURE_SSL_REDIRECT = True 24 | 25 | ALLOWED_HOSTS = ['*'] 26 | 27 | # Application definition 28 | 29 | INSTALLED_APPS = ( 30 | 'django.contrib.admin', 31 | 'django.contrib.auth', 32 | 'django.contrib.contenttypes', 33 | 'django.contrib.sessions', 34 | 'django.contrib.messages', 35 | 'django.contrib.staticfiles', 36 | 'import_export', 37 | 'enable_disable', 38 | ) 39 | 40 | MIDDLEWARE = [ 41 | 'django.middleware.security.SecurityMiddleware', 42 | 'whitenoise.middleware.WhiteNoiseMiddleware', 43 | 'django.contrib.sessions.middleware.SessionMiddleware', 44 | 'django.middleware.common.CommonMiddleware', 45 | 'django.middleware.csrf.CsrfViewMiddleware', 46 | 'django.contrib.auth.middleware.AuthenticationMiddleware', 47 | 'django.contrib.messages.middleware.MessageMiddleware', 48 | 'django.middleware.clickjacking.XFrameOptionsMiddleware', 49 | ] 50 | 51 | TEMPLATES = [ 52 | { 53 | 'BACKEND': 'django.template.backends.django.DjangoTemplates', 54 | 'DIRS': [ 55 | BASE_DIR / 'templates' 56 | ], 57 | 'APP_DIRS': True, 58 | 'OPTIONS': { 59 | 'context_processors': [ 60 | 'django.template.context_processors.debug', 61 | 'django.template.context_processors.request', 62 | 'django.contrib.auth.context_processors.auth', 63 | 'django.contrib.messages.context_processors.messages', 64 | ], 65 | }, 66 | }, 67 | ] 68 | 69 | 70 | ROOT_URLCONF = 'sfswitch.urls' 71 | 72 | WSGI_APPLICATION = 'sfswitch.wsgi.application' 73 | 74 | MAX_CONN_AGE = 600 75 | 76 | DATABASES = { 77 | 'default': { 78 | 'ENGINE': 'django.db.backends.sqlite3', 79 | 'NAME': BASE_DIR / 'db.sqlite3', 80 | } 81 | } 82 | 83 | if "DATABASE_URL" in os.environ: 84 | # Configure Django for DATABASE_URL environment variable. 85 | DATABASES["default"] = dj_database_url.config( 86 | conn_max_age=MAX_CONN_AGE, ssl_require=True) 87 | 88 | # Celery settings 89 | BROKER_POOL_LIMIT = 1 90 | 91 | # Internationalization 92 | # https://docs.djangoproject.com/en/1.6/topics/i18n/ 93 | 94 | LANGUAGE_CODE = 'en-gb' 95 | TIME_ZONE = 'UTC' 96 | USE_I18N = True 97 | USE_L10N = True 98 | USE_TZ = True 99 | 100 | STATIC_ROOT = BASE_DIR / "staticfiles" 101 | STATICFILES_DIRS = [ 102 | BASE_DIR / "static", 103 | ] 104 | STATIC_URL = 'static/' 105 | STATICFILES_STORAGE = 'whitenoise.storage.CompressedManifestStaticFilesStorage' 106 | 107 | EMAIL_HOST = os.environ.get('EMAIL_HOST') 108 | EMAIL_HOST_USER = os.environ.get('EMAIL_HOST_USER') 109 | EMAIL_HOST_PASSWORD = os.environ.get('EMAIL_HOST_PASSWORD') 110 | EMAIL_PORT = 587 111 | EMAIL_USE_TLS = True 112 | 113 | SALESFORCE_CONSUMER_KEY = os.environ.get('SALESFORCE_CONSUMER_KEY') 114 | SALESFORCE_CONSUMER_SECRET = os.environ.get('SALESFORCE_CONSUMER_SECRET') 115 | SALESFORCE_REDIRECT_URI = os.environ.get('SALESFORCE_REDIRECT_URI') 116 | SALESFORCE_API_VERSION = int(os.environ.get('SALESFORCE_API_VERSION', '55')) 117 | 118 | DEFAULT_AUTO_FIELD = 'django.db.models.BigAutoField' -------------------------------------------------------------------------------- /static/css/styles.css: -------------------------------------------------------------------------------- 1 | body { 2 | font-family: "HelveticaNeueW02-45Ligh", "HelveticaNeue-Light", "Helvetica Neue Light", "Helvetica Neue", Helvetica, Arial, "Nimbus Sans L", sans-serif; 3 | color: #999a9b; 4 | } 5 | #wrapper { 6 | width: 100%; 7 | } 8 | #container { 9 | width: 900px; 10 | margin: 30px auto; 11 | } 12 | .inner { 13 | width: 600px; 14 | margin: 0px auto; 15 | } 16 | input[type="text"], input[type="password"] { 17 | width: 250px; 18 | padding: 5px; 19 | color: #63666a; 20 | font-size: 0.9em; 21 | padding-left: 0.4em; 22 | max-width: 19em; 23 | } 24 | input[type="submit"], button { 25 | cursor: pointer; 26 | -webkit-appearance: none!important; 27 | -moz-appearance: none!important; 28 | appearance: none!important; 29 | border: none; 30 | font-family: "HelveticaNeueW02-45Ligh", "HelveticaNeue-Light", "Helvetica Neue Light", "Helvetica Neue", Helvetica, Arial, "Nimbus Sans L", sans-serif; 31 | text-transform: uppercase; 32 | font-size: 0.9em; 33 | line-height: 1.8; 34 | color: white; 35 | text-decoration: none; 36 | background-color: rgba(255, 102, 0, 0.9); 37 | transition: background-color 0.3s; 38 | -moz-transition: background-color 0.3s; 39 | -webkit-transition: background-color 0.3s; 40 | -o-transition: background-color 0.3s; 41 | padding: 0.3em 1em; 42 | } 43 | h1 { 44 | font-family: "HelveticaNeueW02-Thin", "HelveticaNeueW02-45Ligh", "HelveticaNeue-Light", "Helvetica Neue Light", "HelveticaNeueW02-45Ligh", "HelveticaNeue-Light", "Helvetica Neue Light", "Helvetica Neue", Helvetica, Arial, "Nimbus Sans L", sans-serif; 45 | font-size: 2em; 46 | line-height: 1.1em; 47 | color: #ff6600; 48 | } 49 | h2 { 50 | font-family: "HelveticaNeueW02-Thin", "HelveticaNeueW02-45Ligh", "HelveticaNeue-Light", "Helvetica Neue Light", "HelveticaNeueW02-45Ligh", "HelveticaNeue-Light", "Helvetica Neue Light", "Helvetica Neue", Helvetica, Arial, "Nimbus Sans L", sans-serif; 51 | font-size: 1.5em; 52 | line-height: 1.1em; 53 | color: #ff6600; 54 | } 55 | h3 { 56 | color: #ff6600; 57 | } 58 | h4 { 59 | color: #ff6600; 60 | } 61 | #login_table { 62 | margin-top:20px; 63 | } 64 | #mti_wfs_colophon { 65 | display: none !important; 66 | } 67 | .grey { 68 | font-size: 20px; 69 | color: #999a9b !important; 70 | } 71 | 72 | .loading { 73 | width: 600px; 74 | height: 300px; 75 | padding: 20px 0 0 40px; 76 | } 77 | 78 | .logo { 79 | float: left; 80 | } 81 | 82 | .logo img { 83 | margin-top:10px;margin-left:10px; 84 | } 85 | 86 | .contact { 87 | float:right; 88 | margin: 10px 10px 0 0; 89 | } 90 | 91 | .contact a { 92 | color: #999a9b; 93 | text-decoration: none; 94 | } 95 | 96 | .contact a:hover { 97 | color: #ff6600; 98 | } 99 | 100 | .clear { 101 | clear:both; 102 | width: 100%; 103 | height:20px; 104 | } 105 | 106 | .object_link { 107 | color: #999a9b; 108 | text-decoration:none; 109 | font-size:0.8em; 110 | } 111 | 112 | a.object_link:hover { 113 | color: #ff6600; 114 | } 115 | 116 | .error { 117 | color: #900; 118 | } 119 | 120 | .back-to-top { 121 | position: fixed; 122 | bottom: 2em; 123 | right: 0px; 124 | text-decoration: none; 125 | color: #000000; 126 | background-color: rgba(235, 235, 235, 0.80); 127 | font-size: 12px; 128 | padding: 1em; 129 | display: none; 130 | } 131 | 132 | .back-to-top:hover { 133 | background-color: rgba(135, 135, 135, 0.50); 134 | color: #ff6600; 135 | text-decoration:none; 136 | } 137 | 138 | .info_icon { 139 | cursor:pointer; 140 | } 141 | 142 | .component { 143 | cursor:pointer; 144 | } 145 | 146 | #viewMetadataModal .modal-dialog { 147 | width: 800px; 148 | } 149 | 150 | #progressModal .modal-dialog { 151 | width: 600px; 152 | margin-top: 100px; 153 | } 154 | 155 | .deploy-actions { 156 | padding: 20px 0; 157 | } -------------------------------------------------------------------------------- /enable_disable/models.py: -------------------------------------------------------------------------------- 1 | from django.db import models 2 | 3 | class Job(models.Model): 4 | random_id = models.CharField(db_index=True, max_length=255, blank=True, null=True) 5 | created_date = models.DateTimeField(null=True, blank=True) 6 | finished_date = models.DateTimeField(null=True, blank=True) 7 | org_id = models.CharField(max_length=255) 8 | org_name = models.CharField(max_length=255, blank=True, null=True) 9 | username = models.CharField(max_length=255, blank=True, null=True) 10 | access_token = models.CharField(max_length=255, blank=True, null=True) 11 | instance_url = models.CharField(max_length=255, blank=True, null=True) 12 | json_message = models.TextField(blank=True, null=True) 13 | is_sandbox = models.BooleanField(default=False) 14 | status = models.CharField(max_length=255, blank=True, null=True) 15 | error = models.TextField(blank=True, null=True) 16 | 17 | def validation_rules(self): 18 | return self.validationrule_set.order_by('object_name', 'name') 19 | 20 | def workflow_rules(self): 21 | return self.workflowrule_set.order_by('object_name', 'name') 22 | 23 | def triggers(self): 24 | return self.apextrigger_set.order_by('name') 25 | 26 | def flows(self): 27 | return self.flow_set.order_by('name') 28 | 29 | def __str__(self): 30 | return '%s' % (self.random_id) 31 | 32 | class ValidationRule(models.Model): 33 | job = models.ForeignKey(Job, on_delete=models.deletion.CASCADE) 34 | object_name = models.CharField(max_length=255, blank=True, null=True) 35 | name = models.CharField(max_length=255, blank=True, null=True) 36 | active = models.BooleanField() 37 | description = models.TextField(blank=True, null=True) 38 | errorConditionFormula = models.TextField(blank=True, null=True) 39 | errorDisplayField = models.CharField(max_length=255, blank=True, null=True) 40 | errorMessage = models.TextField(blank=True, null=True) 41 | fullName = models.CharField(max_length=255, blank=True, null=True) 42 | 43 | def __str__(self): 44 | return '%s' % (self.fullName) 45 | 46 | class WorkflowRule(models.Model): 47 | job = models.ForeignKey(Job, on_delete=models.deletion.CASCADE) 48 | object_name = models.CharField(max_length=255, blank=True, null=True) 49 | name = models.CharField(max_length=255, blank=True, null=True) 50 | active = models.BooleanField() 51 | actions = models.TextField(blank=True, null=True) 52 | booleanFilter = models.CharField(max_length=255, blank=True, null=True) 53 | criteriaItems = models.TextField(blank=True, null=True) 54 | description = models.TextField(blank=True, null=True) 55 | formula = models.TextField(blank=True, null=True) 56 | fullName = models.CharField(max_length=255, blank=True, null=True) 57 | triggerType = models.CharField(max_length=255, blank=True, null=True) 58 | workflowTimeTriggers = models.TextField(blank=True, null=True) 59 | 60 | def __str__(self): 61 | return '%s' % (self.fullName) 62 | 63 | class ApexTrigger(models.Model): 64 | job = models.ForeignKey(Job, on_delete=models.deletion.CASCADE) 65 | active = models.BooleanField(default=False) 66 | content = models.TextField(blank=True, null=True) 67 | meta_content = models.TextField(blank=True, null=True) 68 | name = models.CharField(max_length=255, blank=True, null=True) 69 | 70 | def __str__(self): 71 | return '%s' % (self.name) 72 | 73 | class Flow(models.Model): 74 | job = models.ForeignKey(Job, on_delete=models.deletion.CASCADE) 75 | flow_id = models.CharField(max_length=255, blank=True, null=True) 76 | active = models.BooleanField(default=False) 77 | name = models.CharField(max_length=255, blank=True, null=True) 78 | latest_version = models.SmallIntegerField(blank=True, null=True) 79 | active_version = models.SmallIntegerField(blank=True, null=True) 80 | 81 | def __str__(self): 82 | return '%s' % (self.name) 83 | 84 | 85 | class DeployJob(models.Model): 86 | job = models.ForeignKey(Job, on_delete=models.deletion.CASCADE) 87 | metadata_type = models.CharField(max_length=255, blank=True, null=True) 88 | deploy_result = models.TextField(blank=True, null=True) 89 | status = models.CharField(max_length=255, blank=True, null=True) 90 | error = models.TextField(blank=True, null=True) 91 | 92 | def __str__(self): 93 | return '%s - %s' % (self.job.username, self.metadata_type) 94 | 95 | class DeployJobComponent(models.Model): 96 | deploy_job = models.ForeignKey(DeployJob, on_delete=models.deletion.CASCADE) 97 | validation_rule = models.ForeignKey(ValidationRule, blank=True, null=True, on_delete=models.deletion.CASCADE) 98 | workflow_rule = models.ForeignKey(WorkflowRule, blank=True, null=True, on_delete=models.deletion.CASCADE) 99 | flow = models.ForeignKey(Flow, blank=True, null=True, on_delete=models.deletion.CASCADE) 100 | trigger = models.ForeignKey(ApexTrigger, blank=True, null=True, on_delete=models.deletion.CASCADE) 101 | enable = models.BooleanField() 102 | status = models.CharField(max_length=255, blank=True, null=True) 103 | error = models.TextField(blank=True, null=True) 104 | 105 | -------------------------------------------------------------------------------- /static/js/metadata_update.js: -------------------------------------------------------------------------------- 1 | $.SyntaxHighlighter.init(); 2 | 3 | $(document).ready(function() 4 | { 5 | // init on/off switch 6 | $('input[type=checkbox]').bootstrapSwitch(); 7 | 8 | // Show metadata properties on click 9 | $('td.component').click(function() 10 | { 11 | if ( $(this).hasClass('trigger_click') ) 12 | { 13 | $('#viewMetadataLabel').text($(this).find('textarea').attr('id')); 14 | var $content = $('
' + $(this).find('textarea.content').val() + '

' + 15 | '
' + $(this).find('textarea.meta_content').val()
 16 | 										.replace(//g,'>')
 18 | 										.replace(/\n/g, '
') + '
'); 19 | $content.syntaxHighlight(); 20 | $('#viewMetadataBody').html($content); 21 | $.SyntaxHighlighter.init(); 22 | $('#viewMetadataModal .modal-dialog').width('90%'); 23 | } 24 | else 25 | { 26 | $('#viewMetadataLabel').text($(this).find('div').attr('id')); 27 | $('#viewMetadataBody').html($(this).find('div').html()); 28 | $('#viewMetadataModal .modal-dialog').width('800px'); 29 | } 30 | 31 | $('#viewMetadataModal').modal(); 32 | }); 33 | 34 | // Enable all button 35 | $('.enable').click(function () 36 | { 37 | $(this).closest('table').find('input[type=checkbox]').bootstrapSwitch('state', true); 38 | }); 39 | 40 | // Disable all button 41 | $('.disable').click(function () 42 | { 43 | $(this).closest('table').find('input[type=checkbox]').bootstrapSwitch('state', false); 44 | }); 45 | 46 | // Deploy changes 47 | $('.submit').click(function () 48 | { 49 | var metadata_type = $(this).attr('id').split('__')[1]; 50 | var componentIds = []; 51 | 52 | $(this).parent().parent().find('td.' + metadata_type).each(function () 53 | { 54 | var new_value = $(this).find('.new_value').bootstrapSwitch('state'); 55 | var old_value = $(this).find('.old_value').val() == 'True' || $(this).find('.old_value').val() == 'true'; 56 | 57 | if (new_value != old_value) 58 | { 59 | // Push to component update array 60 | var component = { 61 | component_id: $(this).attr('id'), 62 | enable: new_value 63 | }; 64 | componentIds.push(component); 65 | 66 | // Set new old_value 67 | $(this).find('.old_value').val($(this).find('.new_value').bootstrapSwitch('state')); 68 | } 69 | 70 | }); 71 | 72 | update_metadata(componentIds, metadata_type); 73 | 74 | }); 75 | 76 | // Rollback changes 77 | $('.rollback').click(function () 78 | { 79 | 80 | var rollbackAll = confirm('This will rollback all metadata for this metadata type to it\'s original state from when the metadata was first queried.'); 81 | 82 | if (rollbackAll) 83 | { 84 | 85 | var metadata_type = $(this).attr('id').split('__')[1]; 86 | var componentIds = []; 87 | 88 | $(this).parent().parent().find('td.' + metadata_type).each(function () 89 | { 90 | var new_value = $(this).find('.new_value').bootstrapSwitch('state'); 91 | var orig_value = $(this).find('.orig_value').val() == 'True' || $(this).find('.orig_value').val() == 'true'; 92 | 93 | if (new_value != orig_value) 94 | { 95 | // Push to component update array 96 | var component = { 97 | component_id: $(this).attr('id'), 98 | enable: orig_value 99 | }; 100 | componentIds.push(component); 101 | 102 | // Set old and new value back to original state 103 | $(this).find('.old_value').val(orig_value); 104 | $(this).find('.new_value').val(orig_value); 105 | $(this).find('.new_value').bootstrapSwitch('state', orig_value); 106 | } 107 | 108 | }); 109 | 110 | update_metadata(componentIds, metadata_type); 111 | } 112 | 113 | }); 114 | 115 | }); 116 | 117 | function updateModal(header, body, allow_close) 118 | { 119 | if (allow_close) 120 | { 121 | $('#progressModal .modal-header').html(''); 122 | $('#progressModal .modal-footer').html(''); 123 | } 124 | else 125 | { 126 | $('#progressModal .modal-header').html(''); 127 | $('#progressModal .modal-footer').html(''); 128 | } 129 | 130 | $('#progressModal .modal-body').html(body); 131 | } 132 | 133 | function check_status(job_id) 134 | { 135 | var refreshIntervalId = window.setInterval(function () 136 | { 137 | $.ajax({ 138 | url: '/check_deploy_status/' + job_id + '/', 139 | type: 'get', 140 | dataType: 'json', 141 | success: function(resp) 142 | { 143 | if (resp.status == 'Finished') 144 | { 145 | updateModal('Complete', 146 | '', 147 | true); 148 | clearInterval(refreshIntervalId); 149 | } 150 | else if (resp.status == 'Error') 151 | { 152 | updateModal('Error', 153 | '', 154 | true); 155 | clearInterval(refreshIntervalId); 156 | } 157 | // Else job is still running, this will re-run shortly. 158 | }, 159 | failure: function(resp) 160 | { 161 | updateModal('Error', 162 | '', 163 | true); 164 | clearInterval(refreshIntervalId); 165 | } 166 | }); 167 | }, 1000); 168 | } -------------------------------------------------------------------------------- /static/css/bootstrap-switch.min.css: -------------------------------------------------------------------------------- 1 | /* ======================================================================== 2 | * bootstrap-switch - v3.3.1 3 | * http://www.bootstrap-switch.org 4 | * ======================================================================== 5 | * Copyright 2012-2013 Mattia Larentis 6 | * 7 | * ======================================================================== 8 | * Licensed under the Apache License, Version 2.0 (the "License"); 9 | * you may not use this file except in compliance with the License. 10 | * You may obtain a copy of the License at 11 | * 12 | * http://www.apache.org/licenses/LICENSE-2.0 13 | * 14 | * Unless required by applicable law or agreed to in writing, software 15 | * distributed under the License is distributed on an "AS IS" BASIS, 16 | * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 17 | * See the License for the specific language governing permissions and 18 | * limitations under the License. 19 | * ======================================================================== 20 | */ 21 | 22 | .bootstrap-switch{display:inline-block;direction:ltr;cursor:pointer;border-radius:4px;border:1px solid;border-color:#ccc;position:relative;text-align:left;overflow:hidden;line-height:8px;-webkit-user-select:none;-moz-user-select:none;-ms-user-select:none;user-select:none;vertical-align:middle;-webkit-transition:border-color ease-in-out .15s,box-shadow ease-in-out .15s;transition:border-color ease-in-out .15s,box-shadow ease-in-out .15s}.bootstrap-switch .bootstrap-switch-container{display:inline-block;top:0;border-radius:4px;-webkit-transform:translate3d(0, 0, 0);transform:translate3d(0, 0, 0)}.bootstrap-switch .bootstrap-switch-handle-on,.bootstrap-switch .bootstrap-switch-handle-off,.bootstrap-switch .bootstrap-switch-label{-webkit-box-sizing:border-box;-moz-box-sizing:border-box;box-sizing:border-box;cursor:pointer;display:inline-block !important;height:100%;padding:6px 12px;font-size:14px;line-height:20px}.bootstrap-switch .bootstrap-switch-handle-on,.bootstrap-switch .bootstrap-switch-handle-off{text-align:center;z-index:1}.bootstrap-switch .bootstrap-switch-handle-on.bootstrap-switch-primary,.bootstrap-switch .bootstrap-switch-handle-off.bootstrap-switch-primary{color:#fff;background:#428bca}.bootstrap-switch .bootstrap-switch-handle-on.bootstrap-switch-info,.bootstrap-switch .bootstrap-switch-handle-off.bootstrap-switch-info{color:#fff;background:#5bc0de}.bootstrap-switch .bootstrap-switch-handle-on.bootstrap-switch-success,.bootstrap-switch .bootstrap-switch-handle-off.bootstrap-switch-success{color:#fff;background:#5cb85c}.bootstrap-switch .bootstrap-switch-handle-on.bootstrap-switch-warning,.bootstrap-switch .bootstrap-switch-handle-off.bootstrap-switch-warning{background:#f0ad4e;color:#fff}.bootstrap-switch .bootstrap-switch-handle-on.bootstrap-switch-danger,.bootstrap-switch .bootstrap-switch-handle-off.bootstrap-switch-danger{color:#fff;background:#d9534f}.bootstrap-switch .bootstrap-switch-handle-on.bootstrap-switch-default,.bootstrap-switch .bootstrap-switch-handle-off.bootstrap-switch-default{color:#000;background:#eee}.bootstrap-switch .bootstrap-switch-label{text-align:center;margin-top:-1px;margin-bottom:-1px;z-index:100;color:#333;background:#fff}.bootstrap-switch .bootstrap-switch-handle-on{border-bottom-left-radius:3px;border-top-left-radius:3px}.bootstrap-switch .bootstrap-switch-handle-off{border-bottom-right-radius:3px;border-top-right-radius:3px}.bootstrap-switch input[type='radio'],.bootstrap-switch input[type='checkbox']{position:absolute !important;top:0;left:0;opacity:0;filter:alpha(opacity=0);z-index:-1}.bootstrap-switch input[type='radio'].form-control,.bootstrap-switch input[type='checkbox'].form-control{height:auto}.bootstrap-switch.bootstrap-switch-mini .bootstrap-switch-handle-on,.bootstrap-switch.bootstrap-switch-mini .bootstrap-switch-handle-off,.bootstrap-switch.bootstrap-switch-mini .bootstrap-switch-label{padding:1px 5px;font-size:12px;line-height:1.5}.bootstrap-switch.bootstrap-switch-small .bootstrap-switch-handle-on,.bootstrap-switch.bootstrap-switch-small .bootstrap-switch-handle-off,.bootstrap-switch.bootstrap-switch-small .bootstrap-switch-label{padding:5px 10px;font-size:12px;line-height:1.5}.bootstrap-switch.bootstrap-switch-large .bootstrap-switch-handle-on,.bootstrap-switch.bootstrap-switch-large .bootstrap-switch-handle-off,.bootstrap-switch.bootstrap-switch-large .bootstrap-switch-label{padding:6px 16px;font-size:18px;line-height:1.33}.bootstrap-switch.bootstrap-switch-disabled,.bootstrap-switch.bootstrap-switch-readonly,.bootstrap-switch.bootstrap-switch-indeterminate{cursor:default !important}.bootstrap-switch.bootstrap-switch-disabled .bootstrap-switch-handle-on,.bootstrap-switch.bootstrap-switch-readonly .bootstrap-switch-handle-on,.bootstrap-switch.bootstrap-switch-indeterminate .bootstrap-switch-handle-on,.bootstrap-switch.bootstrap-switch-disabled .bootstrap-switch-handle-off,.bootstrap-switch.bootstrap-switch-readonly .bootstrap-switch-handle-off,.bootstrap-switch.bootstrap-switch-indeterminate .bootstrap-switch-handle-off,.bootstrap-switch.bootstrap-switch-disabled .bootstrap-switch-label,.bootstrap-switch.bootstrap-switch-readonly .bootstrap-switch-label,.bootstrap-switch.bootstrap-switch-indeterminate .bootstrap-switch-label{opacity:.5;filter:alpha(opacity=50);cursor:default !important}.bootstrap-switch.bootstrap-switch-animate .bootstrap-switch-container{-webkit-transition:margin-left .5s;transition:margin-left .5s}.bootstrap-switch.bootstrap-switch-inverse .bootstrap-switch-handle-on{border-bottom-left-radius:0;border-top-left-radius:0;border-bottom-right-radius:3px;border-top-right-radius:3px}.bootstrap-switch.bootstrap-switch-inverse .bootstrap-switch-handle-off{border-bottom-right-radius:0;border-top-right-radius:0;border-bottom-left-radius:3px;border-top-left-radius:3px}.bootstrap-switch.bootstrap-switch-focused{border-color:#66afe9;outline:0;-webkit-box-shadow:inset 0 1px 1px rgba(0,0,0,0.075),0 0 8px rgba(102,175,233,0.6);box-shadow:inset 0 1px 1px rgba(0,0,0,0.075),0 0 8px rgba(102,175,233,0.6)}.bootstrap-switch.bootstrap-switch-on .bootstrap-switch-label,.bootstrap-switch.bootstrap-switch-inverse.bootstrap-switch-off .bootstrap-switch-label{border-bottom-right-radius:3px;border-top-right-radius:3px}.bootstrap-switch.bootstrap-switch-off .bootstrap-switch-label,.bootstrap-switch.bootstrap-switch-inverse.bootstrap-switch-on .bootstrap-switch-label{border-bottom-left-radius:3px;border-top-left-radius:3px} -------------------------------------------------------------------------------- /templates/base.html: -------------------------------------------------------------------------------- 1 | {% load static %} 2 | 3 | 4 | 5 | Salesforce.com Switch 6 | 7 | 8 | 9 | 10 | 11 | {% block css %}{% endblock %} 12 | 13 | 14 | 15 | 16 | 19 | 20 | 21 | 22 | 23 | 24 | 25 | {% block scripts %}{% endblock %} 26 | 27 | 37 | 38 | 39 | 40 | 41 | {% if request.GET.noheader != '1' %} 42 | 43 | 44 | 98 | 99 |
100 | 101 | 131 | 132 | {% endif %} 133 | 134 |
135 | {% block content %}{% endblock %} 136 |
137 | 138 | 139 | 140 | -------------------------------------------------------------------------------- /static/jquery-syntaxhighlighter/jquery.syntaxhighlighter.min.js: -------------------------------------------------------------------------------- 1 | /* 2 | MIT License {@link http://creativecommons.org/licenses/MIT/} 3 | MIT License {@link http://creativecommons.org/licenses/MIT/} 4 | MIT License {@link http://creativecommons.org/licenses/MIT/} 5 | MIT License {@link http://creativecommons.org/licenses/MIT/} 6 | MIT License {@link http://creativecommons.org/licenses/MIT/} 7 | MIT License {@link http://creativecommons.org/licenses/MIT/} 8 | */ 9 | "undefined"===typeof window.console&&(window.console={}); 10 | "undefined"===typeof window.console.emulated&&("function"===typeof window.console.log?window.console.hasLog=!0:("undefined"===typeof window.console.log&&(window.console.log=function(){}),window.console.hasLog=!1),"function"===typeof window.console.debug?window.console.hasDebug=!0:("undefined"===typeof window.console.debug&&(window.console.debug=!window.console.hasLog?function(){}:function(){for(var a=["console.debug:"],b=0;b :first-child, li:last-child > :first-child").each(function(){var b=a(this),d=/^([\r\n\s\t]|\ )*$/.test(b.html()),c=b.parent(),c=b.siblings();if(d&&(c.length===0||c.length===1&&c.filter(":last").is("br"))){c=b.parent();b=c.val();c.next().val(b);c.remove()}});d.stripInitialWhitespace&&e.find("li:first-child > :first-child").each(function(){var b=a(this),c=(b.html().match(/^(([\r\n\s\t]|\ )+)/)|| 22 | [])[1]||"";c.length&&b.parent().siblings().children(":first-child").add(b).each(function(){var b=a(this),d=b.html(),d=d.replace(RegExp("^"+c,"gm"),"");b.html(d)})});d.wrapLines?e.css({"overflow-x":"hidden","overflow-y":"hidden","white-space":"pre-wrap","max-height":"none"}):e.css({"overflow-x":"auto","overflow-y":"auto","white-space":"normal","max-height":"500px"});return this}d.debug&&window.console.debug("SyntaxHighlighter.highlight: Chosen SyntaxHighlighter is not yet defined. Waiting 1200 ms then trying again."); 23 | setTimeout(function(){c.highlight.apply(c,[b])},1200)}}})(jQuery); -------------------------------------------------------------------------------- /static/js/jquery-migrate-1.1.0.min.js: -------------------------------------------------------------------------------- 1 | /*! jQuery Migrate v1.1.0 | (c) 2005, 2013 jQuery Foundation, Inc. and other contributors | jquery.org/license */ 2 | jQuery.migrateMute===void 0&&(jQuery.migrateMute=!0),function(e,t,n){"use strict";function r(n){o[n]||(o[n]=!0,e.migrateWarnings.push(n),t.console&&console.warn&&!e.migrateMute&&(console.warn("JQMIGRATE: "+n),e.migrateTrace&&console.trace&&console.trace()))}function a(t,a,o,i){if(Object.defineProperty)try{return Object.defineProperty(t,a,{configurable:!0,enumerable:!0,get:function(){return r(i),o},set:function(e){r(i),o=e}}),n}catch(s){}e._definePropertyBroken=!0,t[a]=o}var o={};e.migrateWarnings=[],!e.migrateMute&&t.console&&console.log&&console.log("JQMIGRATE: Logging is active"),e.migrateTrace===n&&(e.migrateTrace=!0),e.migrateReset=function(){o={},e.migrateWarnings.length=0},"BackCompat"===document.compatMode&&r("jQuery is not compatible with Quirks Mode");var i={},s=e.attr,u=e.attrHooks.value&&e.attrHooks.value.get||function(){return null},c=e.attrHooks.value&&e.attrHooks.value.set||function(){return n},l=/^(?:input|button)$/i,d=/^[238]$/,p=/^(?:autofocus|autoplay|async|checked|controls|defer|disabled|hidden|loop|multiple|open|readonly|required|scoped|selected)$/i,f=/^(?:checked|selected)$/i;a(e,"attrFn",i,"jQuery.attrFn is deprecated"),e.attr=function(t,a,o,i){var u=a.toLowerCase(),c=t&&t.nodeType;return i&&4>s.length&&(r("jQuery.fn.attr( props, pass ) is deprecated"),t&&!d.test(c)&&e.isFunction(e.fn[a]))?e(t)[a](o):("type"===a&&o!==n&&l.test(t.nodeName)&&t.parentNode&&r("Can't change the 'type' of an input or button in IE 6/7/8"),!e.attrHooks[u]&&p.test(u)&&(e.attrHooks[u]={get:function(t,r){var a,o=e.prop(t,r);return o===!0||"boolean"!=typeof o&&(a=t.getAttributeNode(r))&&a.nodeValue!==!1?r.toLowerCase():n},set:function(t,n,r){var a;return n===!1?e.removeAttr(t,r):(a=e.propFix[r]||r,a in t&&(t[a]=!0),t.setAttribute(r,r.toLowerCase())),r}},f.test(u)&&r("jQuery.fn.attr('"+u+"') may use property instead of attribute")),s.call(e,t,a,o))},e.attrHooks.value={get:function(e,t){var n=(e.nodeName||"").toLowerCase();return"button"===n?u.apply(this,arguments):("input"!==n&&"option"!==n&&r("jQuery.fn.attr('value') no longer gets properties"),t in e?e.value:null)},set:function(e,t){var a=(e.nodeName||"").toLowerCase();return"button"===a?c.apply(this,arguments):("input"!==a&&"option"!==a&&r("jQuery.fn.attr('value', val) no longer sets properties"),e.value=t,n)}};var g,h,v=e.fn.init,m=e.parseJSON,y=/^(?:[^<]*(<[\w\W]+>)[^>]*|#([\w\-]*))$/;e.fn.init=function(t,n,a){var o;return t&&"string"==typeof t&&!e.isPlainObject(n)&&(o=y.exec(t))&&o[1]&&("<"!==t.charAt(0)&&r("$(html) HTML strings must start with '<' character"),n&&n.context&&(n=n.context),e.parseHTML)?v.call(this,e.parseHTML(e.trim(t),n,!0),n,a):v.apply(this,arguments)},e.fn.init.prototype=e.fn,e.parseJSON=function(e){return e||null===e?m.apply(this,arguments):(r("jQuery.parseJSON requires a valid JSON string"),null)},e.uaMatch=function(e){e=e.toLowerCase();var t=/(chrome)[ \/]([\w.]+)/.exec(e)||/(webkit)[ \/]([\w.]+)/.exec(e)||/(opera)(?:.*version|)[ \/]([\w.]+)/.exec(e)||/(msie) ([\w.]+)/.exec(e)||0>e.indexOf("compatible")&&/(mozilla)(?:.*? rv:([\w.]+)|)/.exec(e)||[];return{browser:t[1]||"",version:t[2]||"0"}},g=e.uaMatch(navigator.userAgent),h={},g.browser&&(h[g.browser]=!0,h.version=g.version),h.chrome?h.webkit=!0:h.webkit&&(h.safari=!0),e.browser=h,a(e,"browser",h,"jQuery.browser is deprecated"),e.sub=function(){function t(e,n){return new t.fn.init(e,n)}e.extend(!0,t,this),t.superclass=this,t.fn=t.prototype=this(),t.fn.constructor=t,t.sub=this.sub,t.fn.init=function(r,a){return a&&a instanceof e&&!(a instanceof t)&&(a=t(a)),e.fn.init.call(this,r,a,n)},t.fn.init.prototype=t.fn;var n=t(document);return r("jQuery.sub() is deprecated"),t};var b=e.fn.data;e.fn.data=function(t){var a,o,i=this[0];return!i||"events"!==t||1!==arguments.length||(a=e.data(i,t),o=e._data(i,t),a!==n&&a!==o||o===n)?b.apply(this,arguments):(r("Use of jQuery.fn.data('events') is deprecated"),o)};var j=/\/(java|ecma)script/i,w=e.fn.andSelf||e.fn.addBack;e.fn.andSelf=function(){return r("jQuery.fn.andSelf() replaced by jQuery.fn.addBack()"),w.apply(this,arguments)},e.clean||(e.clean=function(t,a,o,i){a=a||document,a=!a.nodeType&&a[0]||a,a=a.ownerDocument||a,r("jQuery.clean() is deprecated");var s,u,c,l,d=[];if(e.merge(d,e.buildFragment(t,a).childNodes),o)for(c=function(e){return!e.type||j.test(e.type)?i?i.push(e.parentNode?e.parentNode.removeChild(e):e):o.appendChild(e):n},s=0;null!=(u=d[s]);s++)e.nodeName(u,"script")&&c(u)||(o.appendChild(u),u.getElementsByTagName!==n&&(l=e.grep(e.merge([],u.getElementsByTagName("script")),c),d.splice.apply(d,[s+1,0].concat(l)),s+=l.length));return d});var Q=e.event.add,x=e.event.remove,k=e.event.trigger,N=e.fn.toggle,C=e.fn.live,T=e.fn.die,M="ajaxStart|ajaxStop|ajaxSend|ajaxComplete|ajaxError|ajaxSuccess",S=RegExp("\\b(?:"+M+")\\b"),H=/(?:^|\s)hover(\.\S+|)\b/,A=function(t){return"string"!=typeof t||e.event.special.hover?t:(H.test(t)&&r("'hover' pseudo-event is deprecated, use 'mouseenter mouseleave'"),t&&t.replace(H,"mouseenter$1 mouseleave$1"))};e.event.props&&"attrChange"!==e.event.props[0]&&e.event.props.unshift("attrChange","attrName","relatedNode","srcElement"),e.event.dispatch&&a(e.event,"handle",e.event.dispatch,"jQuery.event.handle is undocumented and deprecated"),e.event.add=function(e,t,n,a,o){e!==document&&S.test(t)&&r("AJAX events should be attached to document: "+t),Q.call(this,e,A(t||""),n,a,o)},e.event.remove=function(e,t,n,r,a){x.call(this,e,A(t)||"",n,r,a)},e.fn.error=function(){var e=Array.prototype.slice.call(arguments,0);return r("jQuery.fn.error() is deprecated"),e.splice(0,0,"error"),arguments.length?this.bind.apply(this,e):(this.triggerHandler.apply(this,e),this)},e.fn.toggle=function(t,n){if(!e.isFunction(t)||!e.isFunction(n))return N.apply(this,arguments);r("jQuery.fn.toggle(handler, handler...) is deprecated");var a=arguments,o=t.guid||e.guid++,i=0,s=function(n){var r=(e._data(this,"lastToggle"+t.guid)||0)%i;return e._data(this,"lastToggle"+t.guid,r+1),n.preventDefault(),a[r].apply(this,arguments)||!1};for(s.guid=o;a.length>i;)a[i++].guid=o;return this.click(s)},e.fn.live=function(t,n,a){return r("jQuery.fn.live() is deprecated"),C?C.apply(this,arguments):(e(this.context).on(t,this.selector,n,a),this)},e.fn.die=function(t,n){return r("jQuery.fn.die() is deprecated"),T?T.apply(this,arguments):(e(this.context).off(t,this.selector||"**",n),this)},e.event.trigger=function(e,t,n,a){return!n&!S.test(e)&&r("Global events are undocumented and deprecated"),k.call(this,e,t,n||document,a)},e.each(M.split("|"),function(t,n){e.event.special[n]={setup:function(){var t=this;return t!==document&&(e.event.add(document,n+"."+e.guid,function(){e.event.trigger(n,null,t,!0)}),e._data(this,n,e.guid++)),!1},teardown:function(){return this!==document&&e.event.remove(document,n+"."+e._data(this,n)),!1}}})}(jQuery,window); 3 | //@ sourceMappingURL=dist/jquery-migrate.min.map -------------------------------------------------------------------------------- /enable_disable/migrations/0001_initial.py: -------------------------------------------------------------------------------- 1 | # Generated by Django 4.2 on 2024-03-26 01:14 2 | 3 | from django.db import migrations, models 4 | import django.db.models.deletion 5 | 6 | 7 | class Migration(migrations.Migration): 8 | 9 | initial = True 10 | 11 | dependencies = [ 12 | ] 13 | 14 | operations = [ 15 | migrations.CreateModel( 16 | name='ApexTrigger', 17 | fields=[ 18 | ('id', models.BigAutoField(auto_created=True, primary_key=True, serialize=False, verbose_name='ID')), 19 | ('active', models.BooleanField(default=False)), 20 | ('content', models.TextField(blank=True, null=True)), 21 | ('meta_content', models.TextField(blank=True, null=True)), 22 | ('name', models.CharField(blank=True, max_length=255, null=True)), 23 | ], 24 | ), 25 | migrations.CreateModel( 26 | name='DeployJob', 27 | fields=[ 28 | ('id', models.BigAutoField(auto_created=True, primary_key=True, serialize=False, verbose_name='ID')), 29 | ('metadata_type', models.CharField(blank=True, max_length=255, null=True)), 30 | ('deploy_result', models.TextField(blank=True, null=True)), 31 | ('status', models.CharField(blank=True, max_length=255, null=True)), 32 | ('error', models.TextField(blank=True, null=True)), 33 | ], 34 | ), 35 | migrations.CreateModel( 36 | name='Job', 37 | fields=[ 38 | ('id', models.BigAutoField(auto_created=True, primary_key=True, serialize=False, verbose_name='ID')), 39 | ('random_id', models.CharField(blank=True, db_index=True, max_length=255, null=True)), 40 | ('created_date', models.DateTimeField(blank=True, null=True)), 41 | ('finished_date', models.DateTimeField(blank=True, null=True)), 42 | ('org_id', models.CharField(max_length=255)), 43 | ('org_name', models.CharField(blank=True, max_length=255, null=True)), 44 | ('username', models.CharField(blank=True, max_length=255, null=True)), 45 | ('access_token', models.CharField(blank=True, max_length=255, null=True)), 46 | ('instance_url', models.CharField(blank=True, max_length=255, null=True)), 47 | ('json_message', models.TextField(blank=True, null=True)), 48 | ('is_sandbox', models.BooleanField(default=False)), 49 | ('status', models.CharField(blank=True, max_length=255, null=True)), 50 | ('error', models.TextField(blank=True, null=True)), 51 | ], 52 | ), 53 | migrations.CreateModel( 54 | name='WorkflowRule', 55 | fields=[ 56 | ('id', models.BigAutoField(auto_created=True, primary_key=True, serialize=False, verbose_name='ID')), 57 | ('object_name', models.CharField(blank=True, max_length=255, null=True)), 58 | ('name', models.CharField(blank=True, max_length=255, null=True)), 59 | ('active', models.BooleanField()), 60 | ('actions', models.TextField(blank=True, null=True)), 61 | ('booleanFilter', models.CharField(blank=True, max_length=255, null=True)), 62 | ('criteriaItems', models.TextField(blank=True, null=True)), 63 | ('description', models.TextField(blank=True, null=True)), 64 | ('formula', models.TextField(blank=True, null=True)), 65 | ('fullName', models.CharField(blank=True, max_length=255, null=True)), 66 | ('triggerType', models.CharField(blank=True, max_length=255, null=True)), 67 | ('workflowTimeTriggers', models.TextField(blank=True, null=True)), 68 | ('job', models.ForeignKey(on_delete=django.db.models.deletion.CASCADE, to='enable_disable.job')), 69 | ], 70 | ), 71 | migrations.CreateModel( 72 | name='ValidationRule', 73 | fields=[ 74 | ('id', models.BigAutoField(auto_created=True, primary_key=True, serialize=False, verbose_name='ID')), 75 | ('object_name', models.CharField(blank=True, max_length=255, null=True)), 76 | ('name', models.CharField(blank=True, max_length=255, null=True)), 77 | ('active', models.BooleanField()), 78 | ('description', models.TextField(blank=True, null=True)), 79 | ('errorConditionFormula', models.TextField(blank=True, null=True)), 80 | ('errorDisplayField', models.CharField(blank=True, max_length=255, null=True)), 81 | ('errorMessage', models.TextField(blank=True, null=True)), 82 | ('fullName', models.CharField(blank=True, max_length=255, null=True)), 83 | ('job', models.ForeignKey(on_delete=django.db.models.deletion.CASCADE, to='enable_disable.job')), 84 | ], 85 | ), 86 | migrations.CreateModel( 87 | name='Flow', 88 | fields=[ 89 | ('id', models.BigAutoField(auto_created=True, primary_key=True, serialize=False, verbose_name='ID')), 90 | ('flow_id', models.CharField(blank=True, max_length=255, null=True)), 91 | ('active', models.BooleanField(default=False)), 92 | ('name', models.CharField(blank=True, max_length=255, null=True)), 93 | ('latest_version', models.SmallIntegerField(blank=True, null=True)), 94 | ('active_version', models.SmallIntegerField(blank=True, null=True)), 95 | ('job', models.ForeignKey(on_delete=django.db.models.deletion.CASCADE, to='enable_disable.job')), 96 | ], 97 | ), 98 | migrations.CreateModel( 99 | name='DeployJobComponent', 100 | fields=[ 101 | ('id', models.BigAutoField(auto_created=True, primary_key=True, serialize=False, verbose_name='ID')), 102 | ('enable', models.BooleanField()), 103 | ('status', models.CharField(blank=True, max_length=255, null=True)), 104 | ('error', models.TextField(blank=True, null=True)), 105 | ('deploy_job', models.ForeignKey(on_delete=django.db.models.deletion.CASCADE, to='enable_disable.deployjob')), 106 | ('flow', models.ForeignKey(blank=True, null=True, on_delete=django.db.models.deletion.CASCADE, to='enable_disable.flow')), 107 | ('trigger', models.ForeignKey(blank=True, null=True, on_delete=django.db.models.deletion.CASCADE, to='enable_disable.apextrigger')), 108 | ('validation_rule', models.ForeignKey(blank=True, null=True, on_delete=django.db.models.deletion.CASCADE, to='enable_disable.validationrule')), 109 | ('workflow_rule', models.ForeignKey(blank=True, null=True, on_delete=django.db.models.deletion.CASCADE, to='enable_disable.workflowrule')), 110 | ], 111 | ), 112 | migrations.AddField( 113 | model_name='deployjob', 114 | name='job', 115 | field=models.ForeignKey(on_delete=django.db.models.deletion.CASCADE, to='enable_disable.job'), 116 | ), 117 | migrations.AddField( 118 | model_name='apextrigger', 119 | name='job', 120 | field=models.ForeignKey(on_delete=django.db.models.deletion.CASCADE, to='enable_disable.job'), 121 | ), 122 | ] 123 | -------------------------------------------------------------------------------- /enable_disable/views.py: -------------------------------------------------------------------------------- 1 | from django.shortcuts import render, get_object_or_404 2 | from django.template import RequestContext 3 | from django.http import HttpResponse, HttpResponseRedirect, JsonResponse 4 | from django.views.decorators.csrf import csrf_exempt 5 | from enable_disable.models import Job, ValidationRule, WorkflowRule, ApexTrigger, Flow, DeployJob, DeployJobComponent 6 | from enable_disable.forms import LoginForm 7 | from django.conf import settings 8 | from enable_disable.tasks import get_metadata, deploy_metadata 9 | from suds.client import Client 10 | import uuid 11 | import json 12 | import requests 13 | import datetime 14 | from time import sleep 15 | 16 | 17 | 18 | def index(request): 19 | 20 | if request.method == 'POST': 21 | 22 | login_form = LoginForm(request.POST) 23 | 24 | if login_form.is_valid(): 25 | 26 | environment = login_form.cleaned_data['environment'] 27 | 28 | oauth_url = 'https://login.salesforce.com/services/oauth2/authorize' 29 | if environment == 'Sandbox': 30 | oauth_url = 'https://test.salesforce.com/services/oauth2/authorize' 31 | 32 | oauth_url = oauth_url + '?response_type=code&client_id=' + settings.SALESFORCE_CONSUMER_KEY + '&redirect_uri=' + settings.SALESFORCE_REDIRECT_URI + '&state='+ environment 33 | 34 | return HttpResponseRedirect(oauth_url) 35 | else: 36 | login_form = LoginForm() 37 | 38 | return render(request, 'index.html', {'login_form': login_form }) 39 | 40 | 41 | def oauth_response(request): 42 | 43 | error_exists = False 44 | error_message = '' 45 | username = '' 46 | org_name = '' 47 | org_id = '' 48 | 49 | # On page load 50 | if request.GET: 51 | 52 | oauth_code = request.GET.get('code') 53 | environment = request.GET.get('state') 54 | access_token = '' 55 | instance_url = '' 56 | 57 | if 'Production' in environment: 58 | login_url = 'https://login.salesforce.com' 59 | else: 60 | login_url = 'https://test.salesforce.com' 61 | 62 | r = requests.post(login_url + '/services/oauth2/token', headers={ 'content-type':'application/x-www-form-urlencoded'}, data={'grant_type':'authorization_code','client_id': settings.SALESFORCE_CONSUMER_KEY,'client_secret':settings.SALESFORCE_CONSUMER_SECRET,'redirect_uri': settings.SALESFORCE_REDIRECT_URI,'code': oauth_code}) 63 | auth_response = json.loads(r.text) 64 | 65 | if 'error_description' in auth_response: 66 | error_exists = True 67 | error_message = auth_response['error_description'] 68 | else: 69 | access_token = auth_response['access_token'] 70 | instance_url = auth_response['instance_url'] 71 | user_id = auth_response['id'][-18:] 72 | org_id = auth_response['id'][:-19] 73 | org_id = org_id[-18:] 74 | 75 | # get username of the authenticated user 76 | r = requests.get(instance_url + '/services/data/v' + str(settings.SALESFORCE_API_VERSION) + '.0/sobjects/User/' + user_id + '?fields=Username', headers={'Authorization': 'OAuth ' + access_token}) 77 | query_response = json.loads(r.text) 78 | username = query_response['Username'] 79 | 80 | # get the org name of the authenticated user 81 | r = requests.get(instance_url + '/services/data/v' + str(settings.SALESFORCE_API_VERSION) + '.0/sobjects/Organization/' + org_id + '?fields=Name', headers={'Authorization': 'OAuth ' + access_token}) 82 | org_name = json.loads(r.text)['Name'] 83 | 84 | login_form = LoginForm(initial={'environment': environment, 'access_token': access_token, 'instance_url': instance_url, 'org_id': org_id, 'username': username, 'org_name':org_name}) 85 | 86 | # Run after user selects logout or get schema 87 | if request.POST: 88 | 89 | login_form = LoginForm(request.POST) 90 | 91 | if login_form.is_valid(): 92 | 93 | environment = login_form.cleaned_data['environment'] 94 | access_token = login_form.cleaned_data['access_token'] 95 | instance_url = login_form.cleaned_data['instance_url'] 96 | org_id = login_form.cleaned_data['org_id'] 97 | username = login_form.cleaned_data['username'] 98 | org_name = login_form.cleaned_data['org_name'] 99 | 100 | if 'logout' in request.POST: 101 | 102 | r = requests.post(instance_url + '/services/oauth2/revoke', headers={'content-type':'application/x-www-form-urlencoded'}, data={'token': access_token}) 103 | return HttpResponseRedirect('/logout?instance_prefix=' + instance_url.replace('https://','').replace('.salesforce.com','')) 104 | 105 | if 'get_metadata' in request.POST: 106 | 107 | job = Job() 108 | job.random_id = uuid.uuid4() 109 | job.created_date = datetime.datetime.now() 110 | job.status = 'Not Started' 111 | job.username = username 112 | job.org_id = org_id 113 | job.org_name = org_name 114 | job.instance_url = instance_url 115 | job.access_token = access_token 116 | job.is_sandbox = 'Production' not in environment 117 | job.save() 118 | 119 | # Start downloading metadata using async task 120 | get_metadata.delay(job.id) 121 | 122 | return HttpResponseRedirect('/loading/' + str(job.random_id)) 123 | 124 | return render( 125 | request, 126 | 'oauth_response.html', 127 | { 128 | 'error': error_exists, 129 | 'error_message': error_message, 130 | 'username': username, 131 | 'org_name': org_name, 132 | 'login_form': login_form 133 | } 134 | ) 135 | 136 | def logout(request): 137 | 138 | # Determine logout url based on environment 139 | instance_prefix = request.GET.get('instance_prefix') 140 | 141 | return render(request, 'logout.html', { 'instance_prefix': instance_prefix }) 142 | 143 | # AJAX endpoint for page to constantly check if job is finished 144 | def job_status(request, job_id): 145 | 146 | job = get_object_or_404(Job, random_id = job_id) 147 | 148 | response_data = { 149 | 'status': job.status, 150 | 'error': job.error 151 | } 152 | 153 | return JsonResponse(response_data) 154 | 155 | # Page for user to wait for job to run 156 | def loading(request, job_id): 157 | 158 | job = get_object_or_404(Job, random_id = job_id) 159 | 160 | if job.status == 'Finished': 161 | 162 | # Return URL when job is finished 163 | return_url = '/job/' + str(job.random_id) + '/' 164 | 165 | # If no header is in URL, keep it there 166 | if request.GET.noheader == '1': 167 | return_url += '?noheader=1' 168 | 169 | return HttpResponseRedirect(return_url) 170 | 171 | else: 172 | 173 | return render(request, 'loading.html', { 'job': job }) 174 | 175 | def job(request, job_id): 176 | """ 177 | Controller to page that displays metadata components 178 | 179 | """ 180 | 181 | job = get_object_or_404(Job, random_id = job_id) 182 | 183 | # Map of objects to their validation rules 184 | val_object_names = [] 185 | for val_rule in job.validation_rules(): 186 | val_object_names.append(val_rule.object_name) 187 | 188 | # Make a unique list 189 | val_object_names = list(set(val_object_names)) 190 | val_object_names.sort() 191 | 192 | # Map of objects to their workflow rules 193 | wf_object_names = [] 194 | for workflow_rule in job.workflow_rules(): 195 | wf_object_names.append(workflow_rule.object_name) 196 | 197 | # make a unique list 198 | wf_object_names = list(set(wf_object_names)) 199 | wf_object_names.sort() 200 | 201 | 202 | return render(request, 'job.html', { 203 | 'job': job, 204 | 'val_object_names': val_object_names, 205 | 'val_rules': job.validation_rules(), 206 | 'wf_object_names': wf_object_names, 207 | 'wf_rules': job.workflow_rules(), 208 | 'triggers': job.triggers(), 209 | 'flows': job.flows() 210 | }) 211 | 212 | 213 | 214 | def update_metadata(request, job_id, metadata_type): 215 | 216 | job = get_object_or_404(Job, random_id = job_id) 217 | 218 | deploy_job = DeployJob() 219 | deploy_job.job = job 220 | deploy_job.status = 'Not Started' 221 | deploy_job.metadata_type = metadata_type 222 | deploy_job.save() 223 | 224 | try: 225 | 226 | components_for_update = json.loads(request.POST.get('components')) 227 | 228 | for component in components_for_update: 229 | 230 | deploy_job_component = DeployJobComponent() 231 | deploy_job_component.deploy_job = deploy_job 232 | 233 | if metadata_type == 'validation_rule': 234 | 235 | deploy_job_component.validation_rule = ValidationRule.objects.get(id = int(component['component_id'])) 236 | 237 | elif metadata_type == 'workflow_rule': 238 | 239 | deploy_job_component.workflow_rule = WorkflowRule.objects.get(id = int(component['component_id'])) 240 | 241 | elif metadata_type == 'trigger': 242 | 243 | deploy_job_component.trigger = ApexTrigger.objects.get(id = int(component['component_id'])) 244 | 245 | elif metadata_type == 'flow': 246 | 247 | deploy_job_component.flow = Flow.objects.get(id = int(component['component_id'])) 248 | 249 | deploy_job_component.enable = component['enable'] 250 | deploy_job_component.save() 251 | 252 | deploy_metadata.delay(deploy_job.id) 253 | 254 | except Exception as error: 255 | 256 | deploy_job.status = 'Error' 257 | deploy_job.error = error 258 | deploy_job.save() 259 | 260 | return HttpResponse(deploy_job.id) 261 | 262 | 263 | def check_deploy_status(request, deploy_job_id): 264 | 265 | deploy_job = get_object_or_404(DeployJob, id = deploy_job_id) 266 | 267 | response_data = { 268 | 'status': deploy_job.status, 269 | 'error': deploy_job.error 270 | } 271 | 272 | return JsonResponse(response_data) 273 | 274 | 275 | @csrf_exempt 276 | def auth_details(request): 277 | """ 278 | RESTful endpoint to pass authentication details 279 | """ 280 | 281 | try: 282 | 283 | request_data = json.loads(request.body) 284 | 285 | # Check for all required fields 286 | if 'org_id' not in request_data or 'access_token' not in request_data or 'instance_url' not in request_data: 287 | 288 | response_data = { 289 | 'status': 'Error', 290 | 'success': False, 291 | 'error_text': 'Not all required fields were found in the message. Please ensure org_id, access_token and instance_url are all passed in the payload' 292 | } 293 | 294 | # All fields exist. Start job and send response 295 | else: 296 | 297 | # create the package record to store results 298 | job = Job() 299 | job.random_id = uuid.uuid4() 300 | job.created_date = datetime.datetime.now() 301 | job.status = 'Not Started' 302 | job.org_id = request_data['org_id'] 303 | job.instance_url = request_data['instance_url'] 304 | job.access_token = request_data['access_token'] 305 | job.save() 306 | 307 | # Attempt to get username and org name. 308 | try: 309 | 310 | # get the org name of the authenticated user 311 | r = requests.get(job.instance_url + '/services/data/v' + str(settings.SALESFORCE_API_VERSION) + '.0/sobjects/Organization/' + job.org_id + '?fields=Name', headers={'Authorization': 'OAuth ' + job.access_token}) 312 | job.org_name = json.loads(r.text)['Name'] 313 | job.save() 314 | 315 | # If there is an error, we can live with that. 316 | except: 317 | pass 318 | 319 | # Run job 320 | get_metadata.delay(job.id) 321 | 322 | # Build response 323 | response_data = { 324 | 'job_url': 'https://sfswitch.herokuapp.com/loading/' + str(job.random_id) + '/?noheader=1', 325 | 'status': 'Success', 326 | 'success': True 327 | } 328 | 329 | except Exception as error: 330 | 331 | # If there is an error, raise exception and return 332 | response_data = { 333 | 'status': 'Error', 334 | 'success': False, 335 | 'error_text': str(error) 336 | } 337 | 338 | return JsonResponse(response_data) 339 | -------------------------------------------------------------------------------- /static/js/bootstrap-switch.min.js: -------------------------------------------------------------------------------- 1 | /* ======================================================================== 2 | * bootstrap-switch - v3.3.1 3 | * http://www.bootstrap-switch.org 4 | * ======================================================================== 5 | * Copyright 2012-2013 Mattia Larentis 6 | * 7 | * ======================================================================== 8 | * Licensed under the Apache License, Version 2.0 (the "License"); 9 | * you may not use this file except in compliance with the License. 10 | * You may obtain a copy of the License at 11 | * 12 | * http://www.apache.org/licenses/LICENSE-2.0 13 | * 14 | * Unless required by applicable law or agreed to in writing, software 15 | * distributed under the License is distributed on an "AS IS" BASIS, 16 | * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 17 | * See the License for the specific language governing permissions and 18 | * limitations under the License. 19 | * ======================================================================== 20 | */ 21 | 22 | (function(){var t=[].slice;!function(e,i){"use strict";var n;return n=function(){function t(t,n){var o;null==n&&(n={}),this.$element=e(t),this.options=e.extend({},e.fn.bootstrapSwitch.defaults,{state:this.$element.is(":checked"),size:this.$element.data("size"),animate:this.$element.data("animate"),disabled:this.$element.is(":disabled"),readonly:this.$element.is("[readonly]"),indeterminate:this.$element.data("indeterminate"),inverse:this.$element.data("inverse"),radioAllOff:this.$element.data("radio-all-off"),onColor:this.$element.data("on-color"),offColor:this.$element.data("off-color"),onText:this.$element.data("on-text"),offText:this.$element.data("off-text"),labelText:this.$element.data("label-text"),handleWidth:this.$element.data("handle-width"),labelWidth:this.$element.data("label-width"),baseClass:this.$element.data("base-class"),wrapperClass:this.$element.data("wrapper-class")},n),this.$wrapper=e("
",{"class":function(t){return function(){var e;return e=[""+t.options.baseClass].concat(t._getClasses(t.options.wrapperClass)),e.push(t.options.state?""+t.options.baseClass+"-on":""+t.options.baseClass+"-off"),null!=t.options.size&&e.push(""+t.options.baseClass+"-"+t.options.size),t.options.disabled&&e.push(""+t.options.baseClass+"-disabled"),t.options.readonly&&e.push(""+t.options.baseClass+"-readonly"),t.options.indeterminate&&e.push(""+t.options.baseClass+"-indeterminate"),t.options.inverse&&e.push(""+t.options.baseClass+"-inverse"),t.$element.attr("id")&&e.push(""+t.options.baseClass+"-id-"+t.$element.attr("id")),e.join(" ")}}(this)()}),this.$container=e("
",{"class":""+this.options.baseClass+"-container"}),this.$on=e("",{html:this.options.onText,"class":""+this.options.baseClass+"-handle-on "+this.options.baseClass+"-"+this.options.onColor}),this.$off=e("",{html:this.options.offText,"class":""+this.options.baseClass+"-handle-off "+this.options.baseClass+"-"+this.options.offColor}),this.$label=e("",{html:this.options.labelText,"class":""+this.options.baseClass+"-label"}),this.$element.on("init.bootstrapSwitch",function(e){return function(){return e.options.onInit.apply(t,arguments)}}(this)),this.$element.on("switchChange.bootstrapSwitch",function(e){return function(){return e.options.onSwitchChange.apply(t,arguments)}}(this)),this.$container=this.$element.wrap(this.$container).parent(),this.$wrapper=this.$container.wrap(this.$wrapper).parent(),this.$element.before(this.options.inverse?this.$off:this.$on).before(this.$label).before(this.options.inverse?this.$on:this.$off),this.options.indeterminate&&this.$element.prop("indeterminate",!0),o=i.setInterval(function(t){return function(){return t.$wrapper.is(":visible")?(t._width(),t._containerPosition(null,function(){return t.options.animate?t.$wrapper.addClass(""+t.options.baseClass+"-animate"):void 0}),i.clearInterval(o)):void 0}}(this),50),this._elementHandlers(),this._handleHandlers(),this._labelHandlers(),this._formHandler(),this._externalLabelHandler(),this.$element.trigger("init.bootstrapSwitch")}return t.prototype._constructor=t,t.prototype.state=function(t,e){return"undefined"==typeof t?this.options.state:this.options.disabled||this.options.readonly?this.$element:this.options.state&&!this.options.radioAllOff&&this.$element.is(":radio")?this.$element:(this.options.indeterminate&&this.indeterminate(!1),t=!!t,this.$element.prop("checked",t).trigger("change.bootstrapSwitch",e),this.$element)},t.prototype.toggleState=function(t){return this.options.disabled||this.options.readonly?this.$element:this.options.indeterminate?(this.indeterminate(!1),this.state(!0)):this.$element.prop("checked",!this.options.state).trigger("change.bootstrapSwitch",t)},t.prototype.size=function(t){return"undefined"==typeof t?this.options.size:(null!=this.options.size&&this.$wrapper.removeClass(""+this.options.baseClass+"-"+this.options.size),t&&this.$wrapper.addClass(""+this.options.baseClass+"-"+t),this._width(),this._containerPosition(),this.options.size=t,this.$element)},t.prototype.animate=function(t){return"undefined"==typeof t?this.options.animate:(t=!!t,t===this.options.animate?this.$element:this.toggleAnimate())},t.prototype.toggleAnimate=function(){return this.options.animate=!this.options.animate,this.$wrapper.toggleClass(""+this.options.baseClass+"-animate"),this.$element},t.prototype.disabled=function(t){return"undefined"==typeof t?this.options.disabled:(t=!!t,t===this.options.disabled?this.$element:this.toggleDisabled())},t.prototype.toggleDisabled=function(){return this.options.disabled=!this.options.disabled,this.$element.prop("disabled",this.options.disabled),this.$wrapper.toggleClass(""+this.options.baseClass+"-disabled"),this.$element},t.prototype.readonly=function(t){return"undefined"==typeof t?this.options.readonly:(t=!!t,t===this.options.readonly?this.$element:this.toggleReadonly())},t.prototype.toggleReadonly=function(){return this.options.readonly=!this.options.readonly,this.$element.prop("readonly",this.options.readonly),this.$wrapper.toggleClass(""+this.options.baseClass+"-readonly"),this.$element},t.prototype.indeterminate=function(t){return"undefined"==typeof t?this.options.indeterminate:(t=!!t,t===this.options.indeterminate?this.$element:this.toggleIndeterminate())},t.prototype.toggleIndeterminate=function(){return this.options.indeterminate=!this.options.indeterminate,this.$element.prop("indeterminate",this.options.indeterminate),this.$wrapper.toggleClass(""+this.options.baseClass+"-indeterminate"),this._containerPosition(),this.$element},t.prototype.inverse=function(t){return"undefined"==typeof t?this.options.inverse:(t=!!t,t===this.options.inverse?this.$element:this.toggleInverse())},t.prototype.toggleInverse=function(){var t,e;return this.$wrapper.toggleClass(""+this.options.baseClass+"-inverse"),e=this.$on.clone(!0),t=this.$off.clone(!0),this.$on.replaceWith(t),this.$off.replaceWith(e),this.$on=t,this.$off=e,this.options.inverse=!this.options.inverse,this.$element},t.prototype.onColor=function(t){var e;return e=this.options.onColor,"undefined"==typeof t?e:(null!=e&&this.$on.removeClass(""+this.options.baseClass+"-"+e),this.$on.addClass(""+this.options.baseClass+"-"+t),this.options.onColor=t,this.$element)},t.prototype.offColor=function(t){var e;return e=this.options.offColor,"undefined"==typeof t?e:(null!=e&&this.$off.removeClass(""+this.options.baseClass+"-"+e),this.$off.addClass(""+this.options.baseClass+"-"+t),this.options.offColor=t,this.$element)},t.prototype.onText=function(t){return"undefined"==typeof t?this.options.onText:(this.$on.html(t),this._width(),this._containerPosition(),this.options.onText=t,this.$element)},t.prototype.offText=function(t){return"undefined"==typeof t?this.options.offText:(this.$off.html(t),this._width(),this._containerPosition(),this.options.offText=t,this.$element)},t.prototype.labelText=function(t){return"undefined"==typeof t?this.options.labelText:(this.$label.html(t),this._width(),this.options.labelText=t,this.$element)},t.prototype.handleWidth=function(t){return"undefined"==typeof t?this.options.handleWidth:(this.options.handleWidth=t,this._width(),this._containerPosition(),this.$element)},t.prototype.labelWidth=function(t){return"undefined"==typeof t?this.options.labelWidth:(this.options.labelWidth=t,this._width(),this._containerPosition(),this.$element)},t.prototype.baseClass=function(){return this.options.baseClass},t.prototype.wrapperClass=function(t){return"undefined"==typeof t?this.options.wrapperClass:(t||(t=e.fn.bootstrapSwitch.defaults.wrapperClass),this.$wrapper.removeClass(this._getClasses(this.options.wrapperClass).join(" ")),this.$wrapper.addClass(this._getClasses(t).join(" ")),this.options.wrapperClass=t,this.$element)},t.prototype.radioAllOff=function(t){return"undefined"==typeof t?this.options.radioAllOff:(t=!!t,t===this.options.radioAllOff?this.$element:(this.options.radioAllOff=t,this.$element))},t.prototype.onInit=function(t){return"undefined"==typeof t?this.options.onInit:(t||(t=e.fn.bootstrapSwitch.defaults.onInit),this.options.onInit=t,this.$element)},t.prototype.onSwitchChange=function(t){return"undefined"==typeof t?this.options.onSwitchChange:(t||(t=e.fn.bootstrapSwitch.defaults.onSwitchChange),this.options.onSwitchChange=t,this.$element)},t.prototype.destroy=function(){var t;return t=this.$element.closest("form"),t.length&&t.off("reset.bootstrapSwitch").removeData("bootstrap-switch"),this.$container.children().not(this.$element).remove(),this.$element.unwrap().unwrap().off(".bootstrapSwitch").removeData("bootstrap-switch"),this.$element},t.prototype._width=function(){var t,e;return t=this.$on.add(this.$off),t.add(this.$label).css("width",""),e="auto"===this.options.handleWidth?Math.max(this.$on.width(),this.$off.width()):this.options.handleWidth,t.width(e),this.$label.width(function(t){return function(i,n){return"auto"!==t.options.labelWidth?t.options.labelWidth:e>n?e:n}}(this)),this._handleWidth=this.$on.outerWidth(),this._labelWidth=this.$label.outerWidth(),this.$container.width(2*this._handleWidth+this._labelWidth),this.$wrapper.width(this._handleWidth+this._labelWidth)},t.prototype._containerPosition=function(t,e){return null==t&&(t=this.options.state),this.$container.css("margin-left",function(e){return function(){var i;return i=[0,"-"+e._handleWidth+"px"],e.options.indeterminate?"-"+e._handleWidth/2+"px":t?e.options.inverse?i[1]:i[0]:e.options.inverse?i[0]:i[1]}}(this)),e?setTimeout(function(){return e()},50):void 0},t.prototype._elementHandlers=function(){return this.$element.on({"change.bootstrapSwitch":function(t){return function(i,n){var o;return i.preventDefault(),i.stopImmediatePropagation(),o=t.$element.is(":checked"),t._containerPosition(o),o!==t.options.state?(t.options.state=o,t.$wrapper.toggleClass(""+t.options.baseClass+"-off").toggleClass(""+t.options.baseClass+"-on"),n?void 0:(t.$element.is(":radio")&&e("[name='"+t.$element.attr("name")+"']").not(t.$element).prop("checked",!1).trigger("change.bootstrapSwitch",!0),t.$element.trigger("switchChange.bootstrapSwitch",[o]))):void 0}}(this),"focus.bootstrapSwitch":function(t){return function(e){return e.preventDefault(),t.$wrapper.addClass(""+t.options.baseClass+"-focused")}}(this),"blur.bootstrapSwitch":function(t){return function(e){return e.preventDefault(),t.$wrapper.removeClass(""+t.options.baseClass+"-focused")}}(this),"keydown.bootstrapSwitch":function(t){return function(e){if(e.which&&!t.options.disabled&&!t.options.readonly)switch(e.which){case 37:return e.preventDefault(),e.stopImmediatePropagation(),t.state(!1);case 39:return e.preventDefault(),e.stopImmediatePropagation(),t.state(!0)}}}(this)})},t.prototype._handleHandlers=function(){return this.$on.on("click.bootstrapSwitch",function(t){return function(e){return e.preventDefault(),e.stopPropagation(),t.state(!1),t.$element.trigger("focus.bootstrapSwitch")}}(this)),this.$off.on("click.bootstrapSwitch",function(t){return function(e){return e.preventDefault(),e.stopPropagation(),t.state(!0),t.$element.trigger("focus.bootstrapSwitch")}}(this))},t.prototype._labelHandlers=function(){return this.$label.on({"mousedown.bootstrapSwitch touchstart.bootstrapSwitch":function(t){return function(e){return t._dragStart||t.options.disabled||t.options.readonly?void 0:(e.preventDefault(),e.stopPropagation(),t._dragStart=(e.pageX||e.originalEvent.touches[0].pageX)-parseInt(t.$container.css("margin-left"),10),t.options.animate&&t.$wrapper.removeClass(""+t.options.baseClass+"-animate"),t.$element.trigger("focus.bootstrapSwitch"))}}(this),"mousemove.bootstrapSwitch touchmove.bootstrapSwitch":function(t){return function(e){var i;if(null!=t._dragStart&&(e.preventDefault(),i=(e.pageX||e.originalEvent.touches[0].pageX)-t._dragStart,!(i<-t._handleWidth||i>0)))return t._dragEnd=i,t.$container.css("margin-left",""+t._dragEnd+"px")}}(this),"mouseup.bootstrapSwitch touchend.bootstrapSwitch":function(t){return function(e){var i;if(t._dragStart)return e.preventDefault(),t.options.animate&&t.$wrapper.addClass(""+t.options.baseClass+"-animate"),t._dragEnd?(i=t._dragEnd>-(t._handleWidth/2),t._dragEnd=!1,t.state(t.options.inverse?!i:i)):t.state(!t.options.state),t._dragStart=!1}}(this),"mouseleave.bootstrapSwitch":function(t){return function(){return t.$label.trigger("mouseup.bootstrapSwitch")}}(this)})},t.prototype._externalLabelHandler=function(){var t;return t=this.$element.closest("label"),t.on("click",function(e){return function(i){return i.preventDefault(),i.stopImmediatePropagation(),i.target===t[0]?e.toggleState():void 0}}(this))},t.prototype._formHandler=function(){var t;return t=this.$element.closest("form"),t.data("bootstrap-switch")?void 0:t.on("reset.bootstrapSwitch",function(){return i.setTimeout(function(){return t.find("input").filter(function(){return e(this).data("bootstrap-switch")}).each(function(){return e(this).bootstrapSwitch("state",this.checked)})},1)}).data("bootstrap-switch",!0)},t.prototype._getClasses=function(t){var i,n,o,s;if(!e.isArray(t))return[""+this.options.baseClass+"-"+t];for(n=[],o=0,s=t.length;s>o;o++)i=t[o],n.push(""+this.options.baseClass+"-"+i);return n},t}(),e.fn.bootstrapSwitch=function(){var i,o,s;return o=arguments[0],i=2<=arguments.length?t.call(arguments,1):[],s=this,this.each(function(){var t,a;return t=e(this),a=t.data("bootstrap-switch"),a||t.data("bootstrap-switch",a=new n(this,o)),"string"==typeof o?s=a[o].apply(a,i):void 0}),s},e.fn.bootstrapSwitch.Constructor=n,e.fn.bootstrapSwitch.defaults={state:!0,size:null,animate:!0,disabled:!1,readonly:!1,indeterminate:!1,inverse:!1,radioAllOff:!1,onColor:"primary",offColor:"default",onText:"ON",offText:"OFF",labelText:" ",handleWidth:"auto",labelWidth:"auto",baseClass:"bootstrap-switch",wrapperClass:"wrapper",onInit:function(){},onSwitchChange:function(){}}}(window.jQuery,window)}).call(this); -------------------------------------------------------------------------------- /static/jquery-syntaxhighlighter/prettify/prettify.min.js: -------------------------------------------------------------------------------- 1 | window.PR_SHOULD_USE_CONTINUATION=!0;window.PR_TAB_WIDTH=4;window.PR_normalizedHtml=window.PR=window.prettyPrintOne=window.prettyPrint=void 0;window._pr_isIE6=function(){var w=navigator&&navigator.userAgent&&navigator.userAgent.match(/\bMSIE ([678])\./),w=w?+w[1]:!1;window._pr_isIE6=function(){return w};return w}; 2 | (function(){function w(a){return a.replace(H,"&").replace(I,"<").replace(J,">")}function D(a,c,f){switch(a.nodeType){case 1:var n=a.tagName.toLowerCase();c.push("<",n);var e=a.attributes,i=e.length;if(i){if(f){for(var v=[],j=i;0<=--j;)v[j]=e[j];v.sort(function(a,c){return a.name");for(e= 3 | a.firstChild;e;e=e.nextSibling)D(e,c,f);(a.firstChild||!/^(?:br|link|img)$/.test(n))&&c.push("");break;case 3:case 4:c.push(w(a.nodeValue))}}function K(a){function c(a){if("\\"!==a.charAt(0))return a.charCodeAt(0);switch(a.charAt(1)){case "b":return 8;case "t":return 9;case "n":return 10;case "v":return 11;case "f":return 12;case "r":return 13;case "u":case "x":return parseInt(a.substring(2),16)||a.charCodeAt(1);case "0":case "1":case "2":case "3":case "4":case "5":case "6":case "7":return parseInt(a.substring(1), 4 | 8);default:return a.charCodeAt(1)}}function f(a){if(32>a)return(16>a?"\\x0":"\\x")+a.toString(16);a=String.fromCharCode(a);if("\\"===a||"-"===a||"["===a||"]"===a)a="\\"+a;return a}function n(a){for(var g=a.substring(1,a.length-1).match(RegExp("\\\\u[0-9A-Fa-f]{4}|\\\\x[0-9A-Fa-f]{2}|\\\\[0-3][0-7]{0,2}|\\\\[0-7]{1,2}|\\\\[\\s\\S]|-|[^-\\\\]","g")),a=[],b=[],j="^"===g[0],d=j?1:0,k=g.length;de||122e||90e||122k[0]&&(k[1]+1>k[0]&&b.push("-"),b.push(f(k[1]))); 6 | b.push("]");return b.join("")}function e(a){for(var g=a.source.match(RegExp("(?:\\[(?:[^\\x5C\\x5D]|\\\\[\\s\\S])*\\]|\\\\u[A-Fa-f0-9]{4}|\\\\x[A-Fa-f0-9]{2}|\\\\[0-9]+|\\\\[^ux0-9]|\\(\\?[:!=]|[\\(\\)\\^]|[^\\x5B\\x5C\\(\\)\\^]+)","g")),b=g.length,c=[],d=0,f=0;d/, 12 | null])):c.push([A,/^#[^\r\n]*/,null,"#"]));a.cStyleComments&&(f.push([A,/^\/\/[^\r\n]*/,null]),f.push([A,/^\/\*[\s\S]*?(?:\*\/|$)/,null]));a.regexLiterals&&f.push(["lang-regex",RegExp("^"+U+"(/(?=[^/*])(?:[^/\\x5B\\x5C]|\\x5C[\\s\\S]|\\x5B(?:[^\\x5C\\x5D]|\\x5C[\\s\\S])*(?:\\x5D|$))+/)")]);a=a.keywords.replace(/^\s+|\s+$/g,"");a.length&&f.push([N,RegExp("^(?:"+a.replace(/\s+/g,"|")+")\\b"),null]);c.push([u,/^\s+/,null," \r\n\t\u00a0"]);f.push([F,/^@[a-z_$][a-z_$@0-9]*/i,null],[O,/^@?[A-Z]+[a-z][A-Za-z_$@0-9]*/, 13 | null],[u,/^[a-z_$][a-z_$@0-9]*/i,null],[F,/^(?:0x[a-f0-9]+|(?:\d(?:_\d+)*\d*(?:\.\d*)?|\.\d\+)(?:e[+\-]?\d+)?)[a-z]*/i,null,"0123456789"],[B,/^.[^\s\w\.$@\'\"\`\/\#]*/,null]);return z(c,f)}function V(a){function c(a){if(a>v){j&&j!==o&&(i.push(""),j=null);!j&&o&&(j=o,i.push(''));var c=w(p(f.substring(v,a))).replace(d?g:s,"$1 ");d=q.test(c);i.push(c.replace(b,l));v=a}}var f=a.source,n=a.extractedTags,e=a.decorations,i=[],v=0,j=null,o=null,m=0,r=0,p=T(window.PR_TAB_WIDTH), 14 | s=/([\r\n ]) /g,g=/(^| ) /gm,b=/\r\n?|\n/g,q=/[ \r\n]$/,d=!0,k=window._pr_isIE6(),k=k?"PRE"===a.sourceNode.tagName?6===k?" \r\n":7===k?" 
\r":" \r":" 
":"
",h=a.sourceNode.className.match(/\blinenums\b(?::(\d+))?/),l;if(h){for(var x=[],t=0;10>t;++t)x[t]=k+'
  • ';var u=h[1]&&h[1].length?h[1]-1:0;i.push('
    1. ");l=function(){var a=x[++u%10];return j?""+a+'':a}}else l=k;for(;;)if(m"),j=null),i.push(n[m+1]),m+=2;else if(r");h&&i.push("
    ");a.prettyPrintedHtml=i.join("")}function l(a,c){for(var f=c.length;0<=--f;){var n=c[f];C.hasOwnProperty(n)?"console"in window&&console.warn("cannot override language handler %s",n):C[n]=a}}function M(a,c){if(!a||!C.hasOwnProperty(a))a=/^\s*q)b=p;else{for(--q;0<=(q=p.indexOf("&#",q+1));){var d=p.indexOf(";",q);if(0<=d){var k=p.substring(q+3,d),h=10;k&&"x"===k.charAt(0)&&(k=k.substring(1),h=16);var t=parseInt(k,h);isNaN(t)||(p=p.substring(0,q)+String.fromCharCode(t)+p.substring(d+1))}}b=p.replace($,"<").replace(aa,">").replace(ba, 18 | "'").replace(ca,'"').replace(da," ").replace(ea,"&")}f.push(b);i+=b.length}}c=f.join("");a.source=c;a.basePos=0;a.extractedTags=l;M(n,c)(a);V(a)}catch(u){"console"in window&&console.log(u&&u.stack?u.stack:u)}}var y="str",N="kwd",A="com",O="typ",F="lit",B="pun",u="pln",L="src",Q="nocode",U=function(){for(var a="! != !== # % %= & && &&= &= ( * *= += , -= -> / /= : :: ; < << <<= <= = == === > >= >> >>= >>> >>>= ? @ [ ^ ^= ^^ ^^= { | |= || ||= ~ break case continue delete do else finally instanceof return throw try typeof".split(" "), 19 | c="(?:^^|[+-]",f=0;f:&a-z])/g,"\\$1");return c+")\\s*"}(),H=/&/g,I=//g,S=/\"/g,$=/</g,aa=/>/g,ba=/'/g,ca=/"/g,ea=/&/g,da=/ /g,fa=/[\r\n]/g,G=null,W=RegExp("[^<]+|<\!--[\\s\\S]*?--\>||\"']|'[^']*'|\"[^\"]*\")*>|<","g"),X=/^<\!--/,Y=/^]*(?:>|$)/],[A,/^<\!--[\s\S]*?(?:-\->|$)/],["lang-",/^<\?([\s\S]+?)(?:\?>|$)/],["lang-",/^<%([\s\S]+?)(?:%>|$)/],[B,/^(?:<[%?]|[%?]>)/],["lang-",/^]*>([\s\S]+?)<\/xmp\b[^>]*>/i],["lang-js",/^]*>([\s\S]*?)(<\/script\b[^>]*>)/i],["lang-css",/^]*>([\s\S]*?)(<\/style\b[^>]*>)/i],["lang-in.tag",/^(<\/?[a-z][^<>]*>)/i]]),"default-markup htm html mxml xhtml xml xsl".split(" ")); 21 | l(z([[u,/^[\s]+/,null," \t\r\n"],["atv",/^(?:\"[^\"]*\"?|\'[^\']*\'?)/,null,"\"'"]],[["tag",/^^<\/?[a-z](?:[\w.:-]*\w)?|\/?>$/i],["atn",/^(?!style[\s=]|on)[a-z](?:[\w:-]*\w)?/i],["lang-uq.val",/^=\s*([^>\'\"\s]*(?:[^>\'\"\s\/]|\/(?=\s)))/],[B,/^[=<>\/]+/],["lang-js",/^on\w+\s*=\s*\"([^\"]+)\"/i],["lang-js",/^on\w+\s*=\s*\'([^\']+)\'/i],["lang-js",/^on\w+\s*=\s*([^\"\'>\s]+)/i],["lang-css",/^style\s*=\s*\"([^\"]+)\"/i],["lang-css",/^style\s*=\s*\'([^\']+)\'/i],["lang-css",/^style\s*=\s*([^\"\'>\s]+)/i]]), 22 | ["in.tag"]);l(z([],[["atv",/^[\s\S]+/]]),["uq.val"]);l(t({keywords:"break continue do else for if return while auto case char const default double enum extern float goto int long register short signed sizeof static struct switch typedef union unsigned void volatile catch class delete false import new operator private protected public this throw true try typeof alignof align_union asm axiom bool concept concept_map const_cast constexpr decltype dynamic_cast explicit export friend inline late_check mutable namespace nullptr reinterpret_cast static_assert static_cast template typeid typename using virtual wchar_t where ", 23 | hashComments:!0,cStyleComments:!0}),"c cc cpp cxx cyc m".split(" "));l(t({keywords:"null true false"}),["json"]);l(t({keywords:"break continue do else for if return while auto case char const default double enum extern float goto int long register short signed sizeof static struct switch typedef union unsigned void volatile catch class delete false import new operator private protected public this throw true try typeof abstract boolean byte extends final finally implements import instanceof null native package strictfp super synchronized throws transient as base by checked decimal delegate descending event fixed foreach from group implicit in interface internal into is lock object out override orderby params partial readonly ref sbyte sealed stackalloc string select uint ulong unchecked unsafe ushort var ", 24 | hashComments:!0,cStyleComments:!0,verbatimStrings:!0}),["cs"]);l(t({keywords:"break continue do else for if return while auto case char const default double enum extern float goto int long register short signed sizeof static struct switch typedef union unsigned void volatile catch class delete false import new operator private protected public this throw true try typeof abstract boolean byte extends final finally implements import instanceof null native package strictfp super synchronized throws transient ", 25 | cStyleComments:!0}),["java"]);l(t({keywords:"break continue do else for if return while case done elif esac eval fi function in local set then until ",hashComments:!0,multiLineStrings:!0}),["bsh","csh","sh"]);l(t({keywords:"break continue do else for if return while and as assert class def del elif except exec finally from global import in is lambda nonlocal not or pass print raise try with yield False True None ",hashComments:!0,multiLineStrings:!0,tripleQuotedStrings:!0}),["cv","py"]);l(t({keywords:"caller delete die do dump elsif eval exit foreach for goto if import last local my next no our print package redo require sub undef unless until use wantarray while BEGIN END ", 26 | hashComments:!0,multiLineStrings:!0,regexLiterals:!0}),["perl","pl","pm"]);l(t({keywords:"break continue do else for if return while alias and begin case class def defined elsif end ensure false in module next nil not or redo rescue retry self super then true undef unless until when yield BEGIN END ",hashComments:!0,multiLineStrings:!0,regexLiterals:!0}),["rb"]);l(t({keywords:"break continue do else for if return while auto case char const default double enum extern float goto int long register short signed sizeof static struct switch typedef union unsigned void volatile catch class delete false import new operator private protected public this throw true try typeof debugger eval export function get null set undefined var with Infinity NaN ", 27 | cStyleComments:!0,regexLiterals:!0}),["js"]);l(z([],[[y,/^[\s\S]+/]]),["regex"]);window.PR_normalizedHtml=D;window.prettyPrintOne=function(a,c){var f={sourceCodeHtml:a,langExtension:c};P(f);return f.prettyPrintedHtml};window.prettyPrint=function(a){function c(){for(var f=window.PR_SHOULD_USE_CONTINUATION?j.now()+250:Infinity;o\n')),G=!/)[\r\n]+/g,"$1").replace(/(?:[\r\n]+[ \t]*)+/g," "))}else{g=[];for(b=b.firstChild;b;b=b.nextSibling)D(b,g);g=g.join("")}g=g.replace(/(?:\r\n?|\n)$/,"");m={sourceCodeHtml:g,langExtension:i,sourceNode:e};P(m);if(i=m.prettyPrintedHtml)if(g=m.sourceNode,"XMP"===g.tagName){b=document.createElement("PRE");for(l=0;lli>a:hover,.dropdown-menu>li>a:focus{background-color:#e8e8e8;background-image:-webkit-linear-gradient(top,#f5f5f5 0,#e8e8e8 100%);background-image:-o-linear-gradient(top,#f5f5f5 0,#e8e8e8 100%);background-image:-webkit-gradient(linear,left top,left bottom,from(#f5f5f5),to(#e8e8e8));background-image:linear-gradient(to bottom,#f5f5f5 0,#e8e8e8 100%);filter:progid:DXImageTransform.Microsoft.gradient(startColorstr='#fff5f5f5', endColorstr='#ffe8e8e8', GradientType=0);background-repeat:repeat-x}.dropdown-menu>.active>a,.dropdown-menu>.active>a:hover,.dropdown-menu>.active>a:focus{background-color:#357ebd;background-image:-webkit-linear-gradient(top,#428bca 0,#357ebd 100%);background-image:-o-linear-gradient(top,#428bca 0,#357ebd 100%);background-image:-webkit-gradient(linear,left top,left bottom,from(#428bca),to(#357ebd));background-image:linear-gradient(to bottom,#428bca 0,#357ebd 100%);filter:progid:DXImageTransform.Microsoft.gradient(startColorstr='#ff428bca', endColorstr='#ff357ebd', GradientType=0);background-repeat:repeat-x}.navbar-default{background-image:-webkit-linear-gradient(top,#fff 0,#f8f8f8 100%);background-image:-o-linear-gradient(top,#fff 0,#f8f8f8 100%);background-image:-webkit-gradient(linear,left top,left bottom,from(#fff),to(#f8f8f8));background-image:linear-gradient(to bottom,#fff 0,#f8f8f8 100%);filter:progid:DXImageTransform.Microsoft.gradient(startColorstr='#ffffffff', endColorstr='#fff8f8f8', GradientType=0);filter:progid:DXImageTransform.Microsoft.gradient(enabled=false);background-repeat:repeat-x;border-radius:4px;-webkit-box-shadow:inset 0 1px 0 rgba(255,255,255,.15),0 1px 5px rgba(0,0,0,.075);box-shadow:inset 0 1px 0 rgba(255,255,255,.15),0 1px 5px rgba(0,0,0,.075)}.navbar-default .navbar-nav>.active>a{background-image:-webkit-linear-gradient(top,#ebebeb 0,#f3f3f3 100%);background-image:-o-linear-gradient(top,#ebebeb 0,#f3f3f3 100%);background-image:-webkit-gradient(linear,left top,left bottom,from(#ebebeb),to(#f3f3f3));background-image:linear-gradient(to bottom,#ebebeb 0,#f3f3f3 100%);filter:progid:DXImageTransform.Microsoft.gradient(startColorstr='#ffebebeb', endColorstr='#fff3f3f3', GradientType=0);background-repeat:repeat-x;-webkit-box-shadow:inset 0 3px 9px rgba(0,0,0,.075);box-shadow:inset 0 3px 9px rgba(0,0,0,.075)}.navbar-brand,.navbar-nav>li>a{text-shadow:0 1px 0 rgba(255,255,255,.25)}.navbar-inverse{background-image:-webkit-linear-gradient(top,#3c3c3c 0,#222 100%);background-image:-o-linear-gradient(top,#3c3c3c 0,#222 100%);background-image:-webkit-gradient(linear,left top,left bottom,from(#3c3c3c),to(#222));background-image:linear-gradient(to bottom,#3c3c3c 0,#222 100%);filter:progid:DXImageTransform.Microsoft.gradient(startColorstr='#ff3c3c3c', endColorstr='#ff222222', GradientType=0);filter:progid:DXImageTransform.Microsoft.gradient(enabled=false);background-repeat:repeat-x}.navbar-inverse .navbar-nav>.active>a{background-image:-webkit-linear-gradient(top,#222 0,#282828 100%);background-image:-o-linear-gradient(top,#222 0,#282828 100%);background-image:-webkit-gradient(linear,left top,left bottom,from(#222),to(#282828));background-image:linear-gradient(to bottom,#222 0,#282828 100%);filter:progid:DXImageTransform.Microsoft.gradient(startColorstr='#ff222222', endColorstr='#ff282828', GradientType=0);background-repeat:repeat-x;-webkit-box-shadow:inset 0 3px 9px rgba(0,0,0,.25);box-shadow:inset 0 3px 9px rgba(0,0,0,.25)}.navbar-inverse .navbar-brand,.navbar-inverse .navbar-nav>li>a{text-shadow:0 -1px 0 rgba(0,0,0,.25)}.navbar-static-top,.navbar-fixed-top,.navbar-fixed-bottom{border-radius:0}.alert{text-shadow:0 1px 0 rgba(255,255,255,.2);-webkit-box-shadow:inset 0 1px 0 rgba(255,255,255,.25),0 1px 2px rgba(0,0,0,.05);box-shadow:inset 0 1px 0 rgba(255,255,255,.25),0 1px 2px rgba(0,0,0,.05)}.alert-success{background-image:-webkit-linear-gradient(top,#dff0d8 0,#c8e5bc 100%);background-image:-o-linear-gradient(top,#dff0d8 0,#c8e5bc 100%);background-image:-webkit-gradient(linear,left top,left bottom,from(#dff0d8),to(#c8e5bc));background-image:linear-gradient(to bottom,#dff0d8 0,#c8e5bc 100%);filter:progid:DXImageTransform.Microsoft.gradient(startColorstr='#ffdff0d8', endColorstr='#ffc8e5bc', GradientType=0);background-repeat:repeat-x;border-color:#b2dba1}.alert-info{background-image:-webkit-linear-gradient(top,#d9edf7 0,#b9def0 100%);background-image:-o-linear-gradient(top,#d9edf7 0,#b9def0 100%);background-image:-webkit-gradient(linear,left top,left bottom,from(#d9edf7),to(#b9def0));background-image:linear-gradient(to bottom,#d9edf7 0,#b9def0 100%);filter:progid:DXImageTransform.Microsoft.gradient(startColorstr='#ffd9edf7', endColorstr='#ffb9def0', GradientType=0);background-repeat:repeat-x;border-color:#9acfea}.alert-warning{background-image:-webkit-linear-gradient(top,#fcf8e3 0,#f8efc0 100%);background-image:-o-linear-gradient(top,#fcf8e3 0,#f8efc0 100%);background-image:-webkit-gradient(linear,left top,left bottom,from(#fcf8e3),to(#f8efc0));background-image:linear-gradient(to bottom,#fcf8e3 0,#f8efc0 100%);filter:progid:DXImageTransform.Microsoft.gradient(startColorstr='#fffcf8e3', endColorstr='#fff8efc0', GradientType=0);background-repeat:repeat-x;border-color:#f5e79e}.alert-danger{background-image:-webkit-linear-gradient(top,#f2dede 0,#e7c3c3 100%);background-image:-o-linear-gradient(top,#f2dede 0,#e7c3c3 100%);background-image:-webkit-gradient(linear,left top,left bottom,from(#f2dede),to(#e7c3c3));background-image:linear-gradient(to bottom,#f2dede 0,#e7c3c3 100%);filter:progid:DXImageTransform.Microsoft.gradient(startColorstr='#fff2dede', endColorstr='#ffe7c3c3', GradientType=0);background-repeat:repeat-x;border-color:#dca7a7}.progress{background-image:-webkit-linear-gradient(top,#ebebeb 0,#f5f5f5 100%);background-image:-o-linear-gradient(top,#ebebeb 0,#f5f5f5 100%);background-image:-webkit-gradient(linear,left top,left bottom,from(#ebebeb),to(#f5f5f5));background-image:linear-gradient(to bottom,#ebebeb 0,#f5f5f5 100%);filter:progid:DXImageTransform.Microsoft.gradient(startColorstr='#ffebebeb', endColorstr='#fff5f5f5', GradientType=0);background-repeat:repeat-x}.progress-bar{background-image:-webkit-linear-gradient(top,#428bca 0,#3071a9 100%);background-image:-o-linear-gradient(top,#428bca 0,#3071a9 100%);background-image:-webkit-gradient(linear,left top,left bottom,from(#428bca),to(#3071a9));background-image:linear-gradient(to bottom,#428bca 0,#3071a9 100%);filter:progid:DXImageTransform.Microsoft.gradient(startColorstr='#ff428bca', endColorstr='#ff3071a9', GradientType=0);background-repeat:repeat-x}.progress-bar-success{background-image:-webkit-linear-gradient(top,#5cb85c 0,#449d44 100%);background-image:-o-linear-gradient(top,#5cb85c 0,#449d44 100%);background-image:-webkit-gradient(linear,left top,left bottom,from(#5cb85c),to(#449d44));background-image:linear-gradient(to bottom,#5cb85c 0,#449d44 100%);filter:progid:DXImageTransform.Microsoft.gradient(startColorstr='#ff5cb85c', endColorstr='#ff449d44', GradientType=0);background-repeat:repeat-x}.progress-bar-info{background-image:-webkit-linear-gradient(top,#5bc0de 0,#31b0d5 100%);background-image:-o-linear-gradient(top,#5bc0de 0,#31b0d5 100%);background-image:-webkit-gradient(linear,left top,left bottom,from(#5bc0de),to(#31b0d5));background-image:linear-gradient(to bottom,#5bc0de 0,#31b0d5 100%);filter:progid:DXImageTransform.Microsoft.gradient(startColorstr='#ff5bc0de', endColorstr='#ff31b0d5', GradientType=0);background-repeat:repeat-x}.progress-bar-warning{background-image:-webkit-linear-gradient(top,#f0ad4e 0,#ec971f 100%);background-image:-o-linear-gradient(top,#f0ad4e 0,#ec971f 100%);background-image:-webkit-gradient(linear,left top,left bottom,from(#f0ad4e),to(#ec971f));background-image:linear-gradient(to bottom,#f0ad4e 0,#ec971f 100%);filter:progid:DXImageTransform.Microsoft.gradient(startColorstr='#fff0ad4e', endColorstr='#ffec971f', GradientType=0);background-repeat:repeat-x}.progress-bar-danger{background-image:-webkit-linear-gradient(top,#d9534f 0,#c9302c 100%);background-image:-o-linear-gradient(top,#d9534f 0,#c9302c 100%);background-image:-webkit-gradient(linear,left top,left bottom,from(#d9534f),to(#c9302c));background-image:linear-gradient(to bottom,#d9534f 0,#c9302c 100%);filter:progid:DXImageTransform.Microsoft.gradient(startColorstr='#ffd9534f', endColorstr='#ffc9302c', GradientType=0);background-repeat:repeat-x}.progress-bar-striped{background-image:-webkit-linear-gradient(45deg,rgba(255,255,255,.15) 25%,transparent 25%,transparent 50%,rgba(255,255,255,.15) 50%,rgba(255,255,255,.15) 75%,transparent 75%,transparent);background-image:-o-linear-gradient(45deg,rgba(255,255,255,.15) 25%,transparent 25%,transparent 50%,rgba(255,255,255,.15) 50%,rgba(255,255,255,.15) 75%,transparent 75%,transparent);background-image:linear-gradient(45deg,rgba(255,255,255,.15) 25%,transparent 25%,transparent 50%,rgba(255,255,255,.15) 50%,rgba(255,255,255,.15) 75%,transparent 75%,transparent)}.list-group{border-radius:4px;-webkit-box-shadow:0 1px 2px rgba(0,0,0,.075);box-shadow:0 1px 2px rgba(0,0,0,.075)}.list-group-item.active,.list-group-item.active:hover,.list-group-item.active:focus{text-shadow:0 -1px 0 #3071a9;background-image:-webkit-linear-gradient(top,#428bca 0,#3278b3 100%);background-image:-o-linear-gradient(top,#428bca 0,#3278b3 100%);background-image:-webkit-gradient(linear,left top,left bottom,from(#428bca),to(#3278b3));background-image:linear-gradient(to bottom,#428bca 0,#3278b3 100%);filter:progid:DXImageTransform.Microsoft.gradient(startColorstr='#ff428bca', endColorstr='#ff3278b3', GradientType=0);background-repeat:repeat-x;border-color:#3278b3}.panel{-webkit-box-shadow:0 1px 2px rgba(0,0,0,.05);box-shadow:0 1px 2px rgba(0,0,0,.05)}.panel-default>.panel-heading{background-image:-webkit-linear-gradient(top,#f5f5f5 0,#e8e8e8 100%);background-image:-o-linear-gradient(top,#f5f5f5 0,#e8e8e8 100%);background-image:-webkit-gradient(linear,left top,left bottom,from(#f5f5f5),to(#e8e8e8));background-image:linear-gradient(to bottom,#f5f5f5 0,#e8e8e8 100%);filter:progid:DXImageTransform.Microsoft.gradient(startColorstr='#fff5f5f5', endColorstr='#ffe8e8e8', GradientType=0);background-repeat:repeat-x}.panel-primary>.panel-heading{background-image:-webkit-linear-gradient(top,#428bca 0,#357ebd 100%);background-image:-o-linear-gradient(top,#428bca 0,#357ebd 100%);background-image:-webkit-gradient(linear,left top,left bottom,from(#428bca),to(#357ebd));background-image:linear-gradient(to bottom,#428bca 0,#357ebd 100%);filter:progid:DXImageTransform.Microsoft.gradient(startColorstr='#ff428bca', endColorstr='#ff357ebd', GradientType=0);background-repeat:repeat-x}.panel-success>.panel-heading{background-image:-webkit-linear-gradient(top,#dff0d8 0,#d0e9c6 100%);background-image:-o-linear-gradient(top,#dff0d8 0,#d0e9c6 100%);background-image:-webkit-gradient(linear,left top,left bottom,from(#dff0d8),to(#d0e9c6));background-image:linear-gradient(to bottom,#dff0d8 0,#d0e9c6 100%);filter:progid:DXImageTransform.Microsoft.gradient(startColorstr='#ffdff0d8', endColorstr='#ffd0e9c6', GradientType=0);background-repeat:repeat-x}.panel-info>.panel-heading{background-image:-webkit-linear-gradient(top,#d9edf7 0,#c4e3f3 100%);background-image:-o-linear-gradient(top,#d9edf7 0,#c4e3f3 100%);background-image:-webkit-gradient(linear,left top,left bottom,from(#d9edf7),to(#c4e3f3));background-image:linear-gradient(to bottom,#d9edf7 0,#c4e3f3 100%);filter:progid:DXImageTransform.Microsoft.gradient(startColorstr='#ffd9edf7', endColorstr='#ffc4e3f3', GradientType=0);background-repeat:repeat-x}.panel-warning>.panel-heading{background-image:-webkit-linear-gradient(top,#fcf8e3 0,#faf2cc 100%);background-image:-o-linear-gradient(top,#fcf8e3 0,#faf2cc 100%);background-image:-webkit-gradient(linear,left top,left bottom,from(#fcf8e3),to(#faf2cc));background-image:linear-gradient(to bottom,#fcf8e3 0,#faf2cc 100%);filter:progid:DXImageTransform.Microsoft.gradient(startColorstr='#fffcf8e3', endColorstr='#fffaf2cc', GradientType=0);background-repeat:repeat-x}.panel-danger>.panel-heading{background-image:-webkit-linear-gradient(top,#f2dede 0,#ebcccc 100%);background-image:-o-linear-gradient(top,#f2dede 0,#ebcccc 100%);background-image:-webkit-gradient(linear,left top,left bottom,from(#f2dede),to(#ebcccc));background-image:linear-gradient(to bottom,#f2dede 0,#ebcccc 100%);filter:progid:DXImageTransform.Microsoft.gradient(startColorstr='#fff2dede', endColorstr='#ffebcccc', GradientType=0);background-repeat:repeat-x}.well{background-image:-webkit-linear-gradient(top,#e8e8e8 0,#f5f5f5 100%);background-image:-o-linear-gradient(top,#e8e8e8 0,#f5f5f5 100%);background-image:-webkit-gradient(linear,left top,left bottom,from(#e8e8e8),to(#f5f5f5));background-image:linear-gradient(to bottom,#e8e8e8 0,#f5f5f5 100%);filter:progid:DXImageTransform.Microsoft.gradient(startColorstr='#ffe8e8e8', endColorstr='#fff5f5f5', GradientType=0);background-repeat:repeat-x;border-color:#dcdcdc;-webkit-box-shadow:inset 0 1px 3px rgba(0,0,0,.05),0 1px 0 rgba(255,255,255,.1);box-shadow:inset 0 1px 3px rgba(0,0,0,.05),0 1px 0 rgba(255,255,255,.1)} -------------------------------------------------------------------------------- /templates/job.html: -------------------------------------------------------------------------------- 1 | {% extends 'base.html' %} 2 | 3 | {% block scripts %} 4 | 5 | 6 | 7 | 8 | 9 | 10 | 49 | 50 | {% endblock %} 51 | 52 | {% block content %} 53 | 54 |

    Salesforce Switch

    55 | 56 | 62 | 63 |

    {% if job.username %}{{ job.username }}{% endif %} ({{ job.org_name }})

    64 | 65 |
    66 | 67 |
    68 | 69 | 96 | 97 |
    98 | 99 |
    100 | 101 | {% if not val_object_names %} 102 | 103 | 106 | 107 | {% else %} 108 | 109 |
    110 | 111 | 114 | 117 | 118 |
    119 | 120 |
    121 | 122 |
    123 | 124 | {% for obj_name in val_object_names %} 125 | 126 | 127 | 128 | 129 | 130 | 131 | 134 | 140 | 145 | 146 | 147 | 148 | 149 | 150 | 151 | {% for val_rule in val_rules %} 152 | 153 | {% if obj_name == val_rule.object_name %} 154 | 155 | 156 | 157 | 194 | 199 | 200 | 201 | {% endif %} 202 | 203 | {% endfor %} 204 | 205 | 206 | 207 |
    132 | {{ obj_name }} 133 | 135 | 138 | 139 | 141 | 144 |
    158 | 159 | {{ val_rule.name }} 160 | 161 | 193 | 195 | 196 | 197 | 198 |
    208 | 209 | {% endfor %} 210 | 211 |
    212 | 213 | {% endif %} 214 | 215 |
    216 | 217 |
    218 | 219 | {% if not wf_object_names %} 220 | 221 | 224 | 225 | {% else %} 226 | 227 |
    228 | 229 | 232 | 235 | 236 |
    237 | 238 |
    239 | 240 |
    241 | 242 | {% for obj_name in wf_object_names %} 243 | 244 | 245 | 246 | 247 | 248 | 249 | 252 | 258 | 263 | 264 | 265 | 266 | 267 | 268 | 269 | {% for workflow_rule in wf_rules %} 270 | 271 | {% if obj_name == workflow_rule.object_name %} 272 | 273 | 274 | 337 | 342 | 343 | 344 | {% endif %} 345 | 346 | {% endfor %} 347 | 348 | 349 | 350 |
    250 | {{ obj_name }} 251 | 253 | 256 | 257 | 259 | 262 |
    275 | 276 | {{ workflow_rule.name }} 277 | 278 | 336 | 338 | 339 | 340 | 341 |
    351 | 352 | {% endfor %} 353 | 354 |
    355 | 356 | {% endif %} 357 | 358 |
    359 | 360 |
    361 | 362 | {% if not flows %} 363 | 364 | 367 | 368 | {% else %} 369 | 370 |
    371 | 372 | 375 | 378 | 379 |
    380 | 381 |
    382 | 383 |
    384 | 385 | 386 | 387 | 388 | 389 | 390 | 391 | 392 | 397 | 398 | 403 | 404 | 405 | 406 | 407 | 408 | 409 | 410 | {% for flow in flows %} 411 | 412 | 413 | 414 | 417 | 418 | 423 | 424 | 425 | 426 | {% endfor %} 427 | 428 | 429 | 430 |
    393 | 396 | 399 | 402 |
    415 | {{ flow.name }} 416 | 419 | 420 | 421 | 422 |
    431 | 432 |
    433 | 434 | {% endif %} 435 | 436 |
    437 | 438 |
    439 | 440 | {% if not triggers %} 441 | 442 | 445 | 446 | {% else %} 447 | 448 |
    449 | 450 | 454 | 455 | 458 | 461 | 462 |
    463 | 464 |
    465 | 466 |
    467 | 468 | 469 | 470 | 471 | 472 | 473 | 474 | 475 | 480 | 481 | 486 | 487 | 488 | 489 | 490 | 491 | 492 | 493 | {% for trigger in triggers %} 494 | 495 | 496 | 497 | 502 | 503 | 508 | 509 | 510 | 511 | {% endfor %} 512 | 513 | 514 | 515 |
    476 | 479 | 482 | 485 |
    498 | {{ trigger.name }} 499 | 500 | 501 | 504 | 505 | 506 | 507 |
    516 | 517 |
    518 | 519 | {% endif %} 520 | 521 |
    522 | 523 |
    524 | 525 |
    526 | 527 | 546 | 547 | 556 | 557 | {% endblock %} -------------------------------------------------------------------------------- /enable_disable/tasks.py: -------------------------------------------------------------------------------- 1 | from __future__ import absolute_import 2 | from celery import Celery 3 | from django.conf import settings 4 | from zipfile import ZipFile 5 | from suds.client import Client 6 | from base64 import b64encode, b64decode 7 | import requests 8 | import json 9 | import xml.etree.ElementTree as ET 10 | import os 11 | import glob 12 | import datetime 13 | import time 14 | import sys 15 | import traceback 16 | 17 | os.environ.setdefault('DJANGO_SETTINGS_MODULE', 'sfswitch.settings') 18 | app = Celery('tasks', broker=os.environ.get('REDIS_URL', 'redis://localhost')) 19 | 20 | import django 21 | django.setup() 22 | 23 | from enable_disable.models import Job, ValidationRule, WorkflowRule, ApexTrigger, Flow, DeployJob, DeployJobComponent 24 | 25 | @app.task 26 | def get_metadata(job_id): 27 | 28 | job = Job.objects.get(pk=job_id) 29 | 30 | job.status = 'Downloading Metadata' 31 | job.save() 32 | 33 | try: 34 | 35 | # instantiate the metadata WSDL 36 | metadata_client = Client('http://sfswitch.herokuapp.com/static/metadata-' + str(settings.SALESFORCE_API_VERSION) + '.xml') 37 | 38 | # URL for metadata API 39 | metadata_url = job.instance_url + '/services/Soap/m/' + str(settings.SALESFORCE_API_VERSION) + '.0/' + job.org_id 40 | 41 | # set the metadata url based on the login result 42 | metadata_client.set_options(location = metadata_url) 43 | 44 | # set the session id from the login result 45 | session_header = metadata_client.factory.create("SessionHeader") 46 | session_header.sessionId = job.access_token 47 | metadata_client.set_options(soapheaders = session_header) 48 | 49 | component_list = [] 50 | 51 | component = metadata_client.factory.create("ListMetadataQuery") 52 | component.type = 'ValidationRule' 53 | component_list.append(component) 54 | 55 | component = metadata_client.factory.create("ListMetadataQuery") 56 | component.type = 'WorkflowRule' 57 | component_list.append(component) 58 | 59 | component = metadata_client.factory.create("ListMetadataQuery") 60 | component.type = 'ApexTrigger' 61 | component_list.append(component) 62 | 63 | validation_rules = [] 64 | workflows = [] 65 | triggers = [] 66 | 67 | # Note: Only 3 metadata types supported 68 | for component in metadata_client.service.listMetadata(component_list, settings.SALESFORCE_API_VERSION): 69 | 70 | if component and component.fullName: 71 | 72 | if component.type == 'ValidationRule': 73 | validation_rules.append(component.fullName) 74 | 75 | if component.type == 'WorkflowRule': 76 | workflows.append(component.fullName) 77 | 78 | if component.type == 'ApexTrigger': 79 | triggers.append(component.fullName) 80 | 81 | # Logic to query for details for each type of metadata. 82 | # Note: Only 10 components are supported per query, so the list and counter are used to ensure that is met. 83 | 84 | query_list = [] 85 | loop_counter = 0 86 | 87 | for validation_rule in validation_rules: 88 | 89 | query_list.append(validation_rule) 90 | 91 | if len(query_list) >= 10 or (len(validation_rules) - loop_counter) <= 10: 92 | 93 | for component in metadata_client.service.readMetadata('ValidationRule', query_list)[0]: 94 | 95 | if component: 96 | 97 | val_rule = ValidationRule() 98 | val_rule.job = job 99 | val_rule.object_name = component.fullName.split('.')[0] 100 | val_rule.name = component.fullName.split('.')[1] 101 | val_rule.fullName = component.fullName 102 | val_rule.active = component.active 103 | 104 | if 'description' in component: 105 | val_rule.description = component.description.encode('ascii', 'replace') 106 | 107 | if 'errorConditionFormula' in component: 108 | val_rule.errorConditionFormula = component.errorConditionFormula.encode('ascii', 'replace') 109 | 110 | if 'errorDisplayField' in component: 111 | val_rule.errorDisplayField = component.errorDisplayField.encode('ascii', 'replace') 112 | 113 | if 'errorMessage' in component: 114 | val_rule.errorMessage = component.errorMessage.encode('ascii', 'replace') 115 | 116 | val_rule.save() 117 | 118 | query_list = [] 119 | 120 | loop_counter = loop_counter + 1 121 | 122 | query_list = [] 123 | loop_counter = 0 124 | 125 | for workflow in workflows: 126 | 127 | query_list.append(workflow) 128 | 129 | if len(query_list) >= 10 or (len(workflows) - loop_counter) <= 10: 130 | 131 | for component in metadata_client.service.readMetadata('WorkflowRule', query_list)[0]: 132 | 133 | if component: 134 | 135 | wflow_rule = WorkflowRule() 136 | wflow_rule.job = job 137 | wflow_rule.object_name = component.fullName.split('.')[0] 138 | wflow_rule.name = component.fullName.split('.')[1] 139 | wflow_rule.fullName = component.fullName 140 | wflow_rule.active = component.active 141 | 142 | if 'actions' in component: 143 | actions = '' 144 | for action in component.actions: 145 | actions += '- ' + action.type + ': ' + action.name + '\n' 146 | wflow_rule.actions = actions.rstrip() 147 | 148 | if 'booleanFilter' in component: 149 | wflow_rule.booleanFilter = component.booleanFilter.encode('ascii', 'replace') 150 | 151 | if 'criteriaItems' in component: 152 | criteria_items = '' 153 | for criteriaItem in component.criteriaItems: 154 | criteria_items += '- ' + criteriaItem.field.split('.')[1] + ' ' + criteriaItem.operation + ' ' 155 | if 'value' in criteriaItem: 156 | if criteriaItem.value: 157 | criteria_items += criteriaItem.value 158 | elif 'valueField' in criteriaItem: 159 | if criteriaItem.valueField: 160 | criteria_items += criteriaItem.valueField 161 | criteria_items += '\n' 162 | 163 | wflow_rule.criteriaItems = criteria_items.rstrip() 164 | 165 | if 'description' in component: 166 | wflow_rule.description = component.description.encode('ascii', 'replace') 167 | 168 | if 'formula' in component: 169 | wflow_rule.formula = component.formula.encode('ascii', 'replace') 170 | 171 | if 'triggerType' in component: 172 | wflow_rule.triggerType = component.triggerType.encode('ascii', 'replace') 173 | 174 | if 'workflowTimeTriggers' in component: 175 | time_triggers = '' 176 | for time_trigger in component.workflowTimeTriggers: 177 | time_triggers += '- ' + time_trigger.timeLength + ' ' + time_trigger.workflowTimeTriggerUnit + ':\n' 178 | if 'actions' in time_trigger: 179 | for action in time_trigger.actions: 180 | time_triggers += '---- ' + action.type + ': ' + action.name + '\n' 181 | time_triggers += '\n' 182 | wflow_rule.workflowTimeTriggers = time_triggers.rstrip() 183 | 184 | wflow_rule.save() 185 | 186 | query_list = [] 187 | 188 | loop_counter = loop_counter + 1 189 | 190 | # Query for flows 191 | # Note: Using the Tooling REST API, as the Metadata API didn't return the stuff I needed 192 | # And the Tooling SOAP API I couldn't get working 193 | request_url = job.instance_url + '/services/data/v' + str(settings.SALESFORCE_API_VERSION) + '.0/tooling/' 194 | request_url += 'query/?q=Select+Id,ActiveVersion.VersionNumber,LatestVersion.VersionNumber,DeveloperName+From+FlowDefinition' 195 | headers = { 196 | 'Accept': 'application/json', 197 | 'X-PrettyPrint': '1', 198 | 'Authorization': 'Bearer ' + job.access_token 199 | } 200 | 201 | flows_query = requests.get(request_url, headers = headers) 202 | 203 | if flows_query.status_code == 200 and 'records' in flows_query.json(): 204 | 205 | for component in flows_query.json()['records']: 206 | 207 | if component: 208 | 209 | flow = Flow() 210 | flow.job = job 211 | flow.name = component['DeveloperName'] 212 | flow.flow_id = component['Id'] 213 | flow.active = False 214 | 215 | if 'LatestVersion' in component and component['LatestVersion']: 216 | flow.latest_version = component['LatestVersion']['VersionNumber'] 217 | else: 218 | flow.latest_version = 1 219 | 220 | if 'ActiveVersion' in component and component['ActiveVersion']: 221 | flow.active_version = component['ActiveVersion']['VersionNumber'] 222 | flow.active = True 223 | 224 | flow.save() 225 | 226 | if triggers: 227 | 228 | # Get triggers 229 | retrieve_request = metadata_client.factory.create('RetrieveRequest') 230 | retrieve_request.apiVersion = settings.SALESFORCE_API_VERSION 231 | retrieve_request.singlePackage = True 232 | retrieve_request.packageNames = None 233 | retrieve_request.specificFiles = None 234 | 235 | trigger_retrieve_list = [] 236 | 237 | for trigger in triggers: 238 | 239 | trigger_to_retrieve = metadata_client.factory.create('PackageTypeMembers') 240 | trigger_to_retrieve.members = trigger 241 | trigger_to_retrieve.name = 'ApexTrigger' 242 | trigger_retrieve_list.append(trigger_to_retrieve) 243 | 244 | package_to_retrieve = metadata_client.factory.create('Package') 245 | package_to_retrieve.apiAccessLevel = None 246 | package_to_retrieve.types = trigger_retrieve_list 247 | package_to_retrieve.packageType = None # This stupid line of code took me ages to work out! 248 | 249 | # Add retrieve package to the retrieve request 250 | retrieve_request.unpackaged = package_to_retrieve 251 | 252 | # Start the async retrieve job 253 | retrieve_job = metadata_client.service.retrieve(retrieve_request) 254 | 255 | # Set the retrieve result - should be unfinished initially 256 | retrieve_result = metadata_client.service.checkRetrieveStatus(retrieve_job.id, True) 257 | 258 | # Continue to query retrieve result until it's done 259 | while not retrieve_result.done: 260 | 261 | # check job status 262 | retrieve_result = metadata_client.service.checkRetrieveStatus(retrieve_job.id, True) 263 | 264 | # sleep job for 3 seconds 265 | time.sleep(3) 266 | 267 | if not retrieve_result.success: 268 | 269 | job.status = 'Error' 270 | job.json_message = retrieve_result 271 | 272 | if 'errorMessage' in retrieve_result: 273 | job.error = retrieve_result.errorMessage 274 | 275 | if 'messages' in retrieve_result: 276 | job.error = retrieve_result.messages[0].problem 277 | 278 | else: 279 | 280 | job.json_message = retrieve_result 281 | 282 | # Save the zip file result to server 283 | zip_file = open('metadata.zip', 'wb') 284 | zip_file.write(b64decode(retrieve_result.zipFile)) 285 | zip_file.close() 286 | 287 | # Open zip file 288 | metadata = ZipFile('metadata.zip', 'r') 289 | 290 | # Loop through files in the zip file 291 | for filename in metadata.namelist(): 292 | 293 | try: 294 | 295 | if '-meta.xml' not in filename.split('/')[1]: 296 | 297 | trigger = ApexTrigger() 298 | trigger.job = job 299 | trigger.name = filename.split('/')[1][:-8] 300 | trigger.content = metadata.read(filename) 301 | trigger.save() 302 | 303 | else: 304 | 305 | # Take the previous trigger to assign meta content to 306 | trigger = ApexTrigger.objects.all().order_by('-id')[0] 307 | trigger.meta_content = metadata.read(filename) 308 | 309 | # Find status of trigger from meta xml 310 | for node in ET.fromstring(metadata.read(filename)): 311 | if 'status' in node.tag: 312 | trigger.active = node.text == 'Active' 313 | break 314 | 315 | trigger.save() 316 | 317 | # not in a folder (could be package.xml). Skip record 318 | except Exception as error: 319 | continue 320 | 321 | # Delete zip file, no need to store 322 | os.remove('metadata.zip') 323 | 324 | job.status = 'Finished' 325 | else: 326 | 327 | job.status = 'Finished' 328 | 329 | except Exception as error: 330 | 331 | job.status = 'Error' 332 | job.error = traceback.format_exc() 333 | 334 | job.finished_date = datetime.datetime.now() 335 | job.save() 336 | 337 | 338 | @app.task 339 | def deploy_metadata(deploy_job_id): 340 | 341 | deploy_job = DeployJob.objects.get(pk=deploy_job_id) 342 | 343 | deploy_job.status = 'Deploying' 344 | deploy_job.save() 345 | 346 | # Set up metadata API connection 347 | metadata_client = Client('http://sfswitch.herokuapp.com/static/metadata-' + str(settings.SALESFORCE_API_VERSION) + '.xml') 348 | metadata_url = deploy_job.job.instance_url + '/services/Soap/m/' + str(settings.SALESFORCE_API_VERSION) + '.0/' + deploy_job.job.org_id 349 | metadata_client.set_options(location = metadata_url) 350 | session_header = metadata_client.factory.create("SessionHeader") 351 | session_header.sessionId = deploy_job.job.access_token 352 | metadata_client.set_options(soapheaders = session_header) 353 | 354 | deploy_components = DeployJobComponent.objects.filter(deploy_job = deploy_job.id) 355 | 356 | try: 357 | 358 | if deploy_job.metadata_type == 'validation_rule': 359 | 360 | update_list = [] 361 | loop_counter = 0 362 | 363 | for deploy_component in deploy_components: 364 | 365 | update_list.append(deploy_component.validation_rule.fullName) 366 | 367 | if len(update_list) >= 10 or (len(deploy_components) - loop_counter) <= 10: 368 | 369 | update_components = metadata_client.service.readMetadata('ValidationRule', update_list)[0] 370 | for update_component in update_components: 371 | update_component.active = deploy_component.enable 372 | 373 | result = metadata_client.service.updateMetadata(update_components) 374 | 375 | if not result[0].success: 376 | 377 | deploy_job.status = 'Error' 378 | deploy_job.error = result[0].errors[0].message 379 | 380 | update_list = [] 381 | 382 | loop_counter = loop_counter + 1 383 | 384 | if deploy_job.status != 'Error': 385 | deploy_job.status = 'Finished' 386 | 387 | elif deploy_job.metadata_type == 'workflow_rule': 388 | 389 | update_list = [] 390 | loop_counter = 0 391 | 392 | for deploy_component in deploy_components: 393 | 394 | update_list.append(deploy_component.workflow_rule.fullName) 395 | 396 | if len(update_list) >= 10 or (len(deploy_components) - loop_counter) <= 10: 397 | 398 | update_components = metadata_client.service.readMetadata('WorkflowRule', update_list)[0] 399 | for update_component in update_components: 400 | update_component.active = deploy_component.enable 401 | 402 | result = metadata_client.service.updateMetadata(update_components) 403 | 404 | if not result[0].success: 405 | 406 | deploy_job.status = 'Error' 407 | deploy_job.error = result[0].errors[0].message 408 | 409 | update_list = [] 410 | 411 | loop_counter = loop_counter + 1 412 | 413 | if deploy_job.status != 'Error': 414 | deploy_job.status = 'Finished' 415 | 416 | elif deploy_job.metadata_type == 'flow': 417 | 418 | # Deploy flows using REST API 419 | # SOAP API doesn't support an easy update 420 | request_url = deploy_job.job.instance_url + '/services/data/v' + str(settings.SALESFORCE_API_VERSION) + '.0/tooling/sobjects/FlowDefinition/' 421 | headers = { 422 | 'Accept': 'application/json', 423 | 'X-PrettyPrint': '1', 424 | 'Authorization': 'Bearer ' + deploy_job.job.access_token, 425 | 'Content-Type': 'application/json' 426 | } 427 | 428 | for deploy_component in deploy_components: 429 | 430 | # Set the JSON body to patch 431 | if deploy_component.enable: 432 | 433 | flow_update = { 434 | 'Metadata': { 435 | 'activeVersionNumber': deploy_component.flow.latest_version 436 | } 437 | } 438 | 439 | else: 440 | 441 | flow_update = { 442 | 'Metadata': { 443 | 'activeVersionNumber': None 444 | } 445 | } 446 | 447 | # Execute patch request to update the flow 448 | result = requests.patch(request_url + deploy_component.flow.flow_id + '/', headers = headers, data = json.dumps(flow_update)) 449 | 450 | if result.status_code != 200 and result.status_code != 204: 451 | deploy_job.status = 'Error' 452 | deploy_job.error = result.json()[0]['errorCode'] + ': ' + result.json()[0]['message'] 453 | 454 | 455 | if deploy_job.status != 'Error': 456 | deploy_job.status = 'Finished' 457 | 458 | 459 | elif deploy_job.metadata_type == 'trigger': 460 | 461 | # Remove path if exists 462 | remove_triggers() 463 | 464 | # Create triggers directory 465 | os.mkdir('triggers') 466 | 467 | zip_file = ZipFile('deploy.zip', 'w') 468 | 469 | for deploy_component in deploy_components: 470 | 471 | trigger_file = open('triggers/' + deploy_component.trigger.name + '.trigger','wb') 472 | trigger_file.write(deploy_component.trigger.content) 473 | trigger_file.close() 474 | 475 | zip_file.write('triggers/' + deploy_component.trigger.name + '.trigger') 476 | 477 | trigger_meta_file = open('triggers/' + deploy_component.trigger.name + '.trigger-meta.xml','wb') 478 | meta_content = deploy_component.trigger.meta_content 479 | if deploy_component.enable: 480 | meta_content = meta_content.replace('Inactive', 'Active') 481 | else: 482 | meta_content = meta_content.replace('Active', 'Inactive') 483 | trigger_meta_file.write(meta_content) 484 | trigger_meta_file.close() 485 | 486 | zip_file.write('triggers/' + deploy_component.trigger.name + '.trigger-meta.xml') 487 | 488 | # Create package.xml 489 | package_xml = open('package.xml','wb') 490 | package_xml.write('\n') 491 | package_xml.write('\n') 492 | package_xml.write(' \n *\n ApexTrigger\n \n') 493 | package_xml.write(' ' + str(settings.SALESFORCE_API_VERSION) + '.0\n') 494 | package_xml.write('') 495 | package_xml.close() 496 | 497 | zip_file.write('package.xml') 498 | zip_file.close() 499 | 500 | # Open and encode zip file 501 | zip_file = open('deploy.zip', 'rb') 502 | zip_file_encoded = b64encode(zip_file.read()) 503 | 504 | # set deploy options 505 | deploy_options = metadata_client.factory.create('DeployOptions') 506 | deploy_options.allowMissingFiles = True 507 | deploy_options.autoUpdatePackage = True 508 | deploy_options.checkOnly = False 509 | deploy_options.ignoreWarnings = True 510 | deploy_options.performRetrieve = False 511 | deploy_options.purgeOnDelete = False 512 | deploy_options.rollbackOnError = True 513 | #deploy_options.runAllTests = False 514 | # Set the tests to run if production or sandbox 515 | deploy_options.testLevel = 'NoTestRun' if deploy_job.job.is_sandbox else 'RunLocalTests' 516 | deploy_options.runTests = None 517 | deploy_options.singlePackage = True 518 | 519 | # Start the async retrieve job 520 | deployment_job = metadata_client.service.deploy(zip_file_encoded, deploy_options) 521 | 522 | # Set the retrieve result - should be unfinished initially 523 | deployment_job_result = metadata_client.service.checkDeployStatus(deployment_job.id, True) 524 | 525 | # Continue to query retrieve result until it's done 526 | while not deployment_job_result.done: 527 | 528 | # check job status 529 | deployment_job_result = metadata_client.service.checkDeployStatus(deployment_job.id, True) 530 | 531 | # sleep job for 3 seconds 532 | time.sleep(1) 533 | 534 | deploy_job.deploy_result = deployment_job_result 535 | 536 | if not deployment_job_result.success: 537 | 538 | deploy_job.status = 'Error' 539 | 540 | all_errors = '' 541 | 542 | if deployment_job_result.numberComponentErrors > 0: 543 | 544 | # Component errors 545 | all_errors = 'Component Errors:\n' 546 | 547 | for error in deployment_job_result.details.componentFailures: 548 | 549 | all_errors += ' - ' + error.problem + '\n' 550 | 551 | # Any test errors 552 | if deployment_job_result.numberTestErrors > 0: 553 | 554 | all_errors += '\nTest Errors:\n' 555 | 556 | for error in deployment_job_result.details.runTestResult.failures: 557 | 558 | all_errors += ' - ' + error.name + '.' + error.methodName + ': ' + error.message + '\n' 559 | 560 | deploy_job.error = all_errors 561 | 562 | else: 563 | 564 | deploy_job.status = 'Finished' 565 | 566 | # Remove files 567 | remove_triggers() 568 | os.remove('package.xml') 569 | os.remove('deploy.zip') 570 | 571 | except Exception as error: 572 | 573 | deploy_job.status = 'Error' 574 | deploy_job.error = error 575 | 576 | deploy_job.save() 577 | 578 | 579 | def remove_triggers(): 580 | """ 581 | Remove the trigger files and folders if exists 582 | """ 583 | # Remove files 584 | for f in glob.glob('triggers/*'): 585 | os.remove(f) 586 | if os.path.exists('triggers'): 587 | os.rmdir('triggers') 588 | -------------------------------------------------------------------------------- /static/bootstrap/css/bootstrap-theme.css.map: -------------------------------------------------------------------------------- 1 | {"version":3,"file":"bootstrap-theme.css","sources":["less/theme.less","less/mixins/vendor-prefixes.less","bootstrap-theme.css","less/mixins/gradients.less","less/mixins/reset-filter.less"],"names":[],"mappings":"AAeA;;;;;;EAME,0CAAA;EC+CA,6FAAA;EACQ,qFAAA;EC5DT;AFiBC;;;;;;;;;;;;EC0CA,0DAAA;EACQ,kDAAA;EC7CT;AFqCC;;EAEE,wBAAA;EEnCH;AFwCD;EG/CI,0EAAA;EACA,qEAAA;EACA,+FAAA;EAAA,wEAAA;EAEA,wHAAA;ECnBF,qEAAA;EJ8BA,6BAAA;EACA,uBAAA;EA+B2C,2BAAA;EAA2B,oBAAA;EE7BvE;AFAC;;EAEE,2BAAA;EACA,8BAAA;EEEH;AFCC;;EAEE,2BAAA;EACA,uBAAA;EECH;AFEC;;EAEE,2BAAA;EACA,wBAAA;EEAH;AFeD;EGhDI,0EAAA;EACA,qEAAA;EACA,+FAAA;EAAA,wEAAA;EAEA,wHAAA;ECnBF,qEAAA;EJ8BA,6BAAA;EACA,uBAAA;EE0BD;AFxBC;;EAEE,2BAAA;EACA,8BAAA;EE0BH;AFvBC;;EAEE,2BAAA;EACA,uBAAA;EEyBH;AFtBC;;EAEE,2BAAA;EACA,wBAAA;EEwBH;AFRD;EGjDI,0EAAA;EACA,qEAAA;EACA,+FAAA;EAAA,wEAAA;EAEA,wHAAA;ECnBF,qEAAA;EJ8BA,6BAAA;EACA,uBAAA;EEkDD;AFhDC;;EAEE,2BAAA;EACA,8BAAA;EEkDH;AF/CC;;EAEE,2BAAA;EACA,uBAAA;EEiDH;AF9CC;;EAEE,2BAAA;EACA,wBAAA;EEgDH;AF/BD;EGlDI,0EAAA;EACA,qEAAA;EACA,+FAAA;EAAA,wEAAA;EAEA,wHAAA;ECnBF,qEAAA;EJ8BA,6BAAA;EACA,uBAAA;EE0ED;AFxEC;;EAEE,2BAAA;EACA,8BAAA;EE0EH;AFvEC;;EAEE,2BAAA;EACA,uBAAA;EEyEH;AFtEC;;EAEE,2BAAA;EACA,wBAAA;EEwEH;AFtDD;EGnDI,0EAAA;EACA,qEAAA;EACA,+FAAA;EAAA,wEAAA;EAEA,wHAAA;ECnBF,qEAAA;EJ8BA,6BAAA;EACA,uBAAA;EEkGD;AFhGC;;EAEE,2BAAA;EACA,8BAAA;EEkGH;AF/FC;;EAEE,2BAAA;EACA,uBAAA;EEiGH;AF9FC;;EAEE,2BAAA;EACA,wBAAA;EEgGH;AF7ED;EGpDI,0EAAA;EACA,qEAAA;EACA,+FAAA;EAAA,wEAAA;EAEA,wHAAA;ECnBF,qEAAA;EJ8BA,6BAAA;EACA,uBAAA;EE0HD;AFxHC;;EAEE,2BAAA;EACA,8BAAA;EE0HH;AFvHC;;EAEE,2BAAA;EACA,uBAAA;EEyHH;AFtHC;;EAEE,2BAAA;EACA,wBAAA;EEwHH;AF7FD;;ECbE,oDAAA;EACQ,4CAAA;EC8GT;AFvFD;;EGvEI,0EAAA;EACA,qEAAA;EACA,+FAAA;EAAA,wEAAA;EACA,6BAAA;EACA,wHAAA;EHsEF,2BAAA;EE6FD;AF3FD;;;EG5EI,0EAAA;EACA,qEAAA;EACA,+FAAA;EAAA,wEAAA;EACA,6BAAA;EACA,wHAAA;EH4EF,2BAAA;EEiGD;AFvFD;EG1FI,0EAAA;EACA,qEAAA;EACA,+FAAA;EAAA,wEAAA;EACA,6BAAA;EACA,wHAAA;ECnBF,qEAAA;EJ4GA,oBAAA;EC9CA,6FAAA;EACQ,qFAAA;EC4IT;AFlGD;EG1FI,0EAAA;EACA,qEAAA;EACA,+FAAA;EAAA,wEAAA;EACA,6BAAA;EACA,wHAAA;EF2CF,0DAAA;EACQ,kDAAA;ECqJT;AF/FD;;EAEE,gDAAA;EEiGD;AF7FD;EG5GI,0EAAA;EACA,qEAAA;EACA,+FAAA;EAAA,wEAAA;EACA,6BAAA;EACA,wHAAA;ECnBF,qEAAA;EFgOD;AFrGD;EG5GI,0EAAA;EACA,qEAAA;EACA,+FAAA;EAAA,wEAAA;EACA,6BAAA;EACA,wHAAA;EF2CF,yDAAA;EACQ,iDAAA;EC0KT;AF9GD;;EAWI,2CAAA;EEuGH;AFlGD;;;EAGE,kBAAA;EEoGD;AF1FD;EACE,+CAAA;EC3FA,4FAAA;EACQ,oFAAA;ECwLT;AFlFD;EGtJI,0EAAA;EACA,qEAAA;EACA,+FAAA;EAAA,wEAAA;EACA,6BAAA;EACA,wHAAA;EH8IF,uBAAA;EE8FD;AFzFD;EGvJI,0EAAA;EACA,qEAAA;EACA,+FAAA;EAAA,wEAAA;EACA,6BAAA;EACA,wHAAA;EH8IF,uBAAA;EEsGD;AFhGD;EGxJI,0EAAA;EACA,qEAAA;EACA,+FAAA;EAAA,wEAAA;EACA,6BAAA;EACA,wHAAA;EH8IF,uBAAA;EE8GD;AFvGD;EGzJI,0EAAA;EACA,qEAAA;EACA,+FAAA;EAAA,wEAAA;EACA,6BAAA;EACA,wHAAA;EH8IF,uBAAA;EEsHD;AFtGD;EGlKI,0EAAA;EACA,qEAAA;EACA,+FAAA;EAAA,wEAAA;EACA,6BAAA;EACA,wHAAA;ED2QH;AFnGD;EG5KI,0EAAA;EACA,qEAAA;EACA,+FAAA;EAAA,wEAAA;EACA,6BAAA;EACA,wHAAA;EDkRH;AFzGD;EG7KI,0EAAA;EACA,qEAAA;EACA,+FAAA;EAAA,wEAAA;EACA,6BAAA;EACA,wHAAA;EDyRH;AF/GD;EG9KI,0EAAA;EACA,qEAAA;EACA,+FAAA;EAAA,wEAAA;EACA,6BAAA;EACA,wHAAA;EDgSH;AFrHD;EG/KI,0EAAA;EACA,qEAAA;EACA,+FAAA;EAAA,wEAAA;EACA,6BAAA;EACA,wHAAA;EDuSH;AF3HD;EGhLI,0EAAA;EACA,qEAAA;EACA,+FAAA;EAAA,wEAAA;EACA,6BAAA;EACA,wHAAA;ED8SH;AF9HD;EGnJI,+MAAA;EACA,0MAAA;EACA,uMAAA;EDoRH;AF1HD;EACE,oBAAA;EC/IA,oDAAA;EACQ,4CAAA;EC4QT;AF3HD;;;EAGE,+BAAA;EGpME,0EAAA;EACA,qEAAA;EACA,+FAAA;EAAA,wEAAA;EACA,6BAAA;EACA,wHAAA;EHkMF,uBAAA;EEiID;AFvHD;ECjKE,mDAAA;EACQ,2CAAA;EC2RT;AFjHD;EG1NI,0EAAA;EACA,qEAAA;EACA,+FAAA;EAAA,wEAAA;EACA,6BAAA;EACA,wHAAA;ED8UH;AFvHD;EG3NI,0EAAA;EACA,qEAAA;EACA,+FAAA;EAAA,wEAAA;EACA,6BAAA;EACA,wHAAA;EDqVH;AF7HD;EG5NI,0EAAA;EACA,qEAAA;EACA,+FAAA;EAAA,wEAAA;EACA,6BAAA;EACA,wHAAA;ED4VH;AFnID;EG7NI,0EAAA;EACA,qEAAA;EACA,+FAAA;EAAA,wEAAA;EACA,6BAAA;EACA,wHAAA;EDmWH;AFzID;EG9NI,0EAAA;EACA,qEAAA;EACA,+FAAA;EAAA,wEAAA;EACA,6BAAA;EACA,wHAAA;ED0WH;AF/ID;EG/NI,0EAAA;EACA,qEAAA;EACA,+FAAA;EAAA,wEAAA;EACA,6BAAA;EACA,wHAAA;EDiXH;AF9ID;EGvOI,0EAAA;EACA,qEAAA;EACA,+FAAA;EAAA,wEAAA;EACA,6BAAA;EACA,wHAAA;EHqOF,uBAAA;EC1LA,2FAAA;EACQ,mFAAA;EC+UT","sourcesContent":["\n//\n// Load core variables and mixins\n// --------------------------------------------------\n\n@import \"variables.less\";\n@import \"mixins.less\";\n\n\n\n//\n// Buttons\n// --------------------------------------------------\n\n// Common styles\n.btn-default,\n.btn-primary,\n.btn-success,\n.btn-info,\n.btn-warning,\n.btn-danger {\n text-shadow: 0 -1px 0 rgba(0,0,0,.2);\n @shadow: inset 0 1px 0 rgba(255,255,255,.15), 0 1px 1px rgba(0,0,0,.075);\n .box-shadow(@shadow);\n\n // Reset the shadow\n &:active,\n &.active {\n .box-shadow(inset 0 3px 5px rgba(0,0,0,.125));\n }\n}\n\n// Mixin for generating new styles\n.btn-styles(@btn-color: #555) {\n #gradient > .vertical(@start-color: @btn-color; @end-color: darken(@btn-color, 12%));\n .reset-filter(); // Disable gradients for IE9 because filter bleeds through rounded corners\n background-repeat: repeat-x;\n border-color: darken(@btn-color, 14%);\n\n &:hover,\n &:focus {\n background-color: darken(@btn-color, 12%);\n background-position: 0 -15px;\n }\n\n &:active,\n &.active {\n background-color: darken(@btn-color, 12%);\n border-color: darken(@btn-color, 14%);\n }\n\n &:disabled,\n &[disabled] {\n background-color: darken(@btn-color, 12%);\n background-image: none;\n }\n}\n\n// Common styles\n.btn {\n // Remove the gradient for the pressed/active state\n &:active,\n &.active {\n background-image: none;\n }\n}\n\n// Apply the mixin to the buttons\n.btn-default { .btn-styles(@btn-default-bg); text-shadow: 0 1px 0 #fff; border-color: #ccc; }\n.btn-primary { .btn-styles(@btn-primary-bg); }\n.btn-success { .btn-styles(@btn-success-bg); }\n.btn-info { .btn-styles(@btn-info-bg); }\n.btn-warning { .btn-styles(@btn-warning-bg); }\n.btn-danger { .btn-styles(@btn-danger-bg); }\n\n\n\n//\n// Images\n// --------------------------------------------------\n\n.thumbnail,\n.img-thumbnail {\n .box-shadow(0 1px 2px rgba(0,0,0,.075));\n}\n\n\n\n//\n// Dropdowns\n// --------------------------------------------------\n\n.dropdown-menu > li > a:hover,\n.dropdown-menu > li > a:focus {\n #gradient > .vertical(@start-color: @dropdown-link-hover-bg; @end-color: darken(@dropdown-link-hover-bg, 5%));\n background-color: darken(@dropdown-link-hover-bg, 5%);\n}\n.dropdown-menu > .active > a,\n.dropdown-menu > .active > a:hover,\n.dropdown-menu > .active > a:focus {\n #gradient > .vertical(@start-color: @dropdown-link-active-bg; @end-color: darken(@dropdown-link-active-bg, 5%));\n background-color: darken(@dropdown-link-active-bg, 5%);\n}\n\n\n\n//\n// Navbar\n// --------------------------------------------------\n\n// Default navbar\n.navbar-default {\n #gradient > .vertical(@start-color: lighten(@navbar-default-bg, 10%); @end-color: @navbar-default-bg);\n .reset-filter(); // Remove gradient in IE<10 to fix bug where dropdowns don't get triggered\n border-radius: @navbar-border-radius;\n @shadow: inset 0 1px 0 rgba(255,255,255,.15), 0 1px 5px rgba(0,0,0,.075);\n .box-shadow(@shadow);\n\n .navbar-nav > .active > a {\n #gradient > .vertical(@start-color: darken(@navbar-default-bg, 5%); @end-color: darken(@navbar-default-bg, 2%));\n .box-shadow(inset 0 3px 9px rgba(0,0,0,.075));\n }\n}\n.navbar-brand,\n.navbar-nav > li > a {\n text-shadow: 0 1px 0 rgba(255,255,255,.25);\n}\n\n// Inverted navbar\n.navbar-inverse {\n #gradient > .vertical(@start-color: lighten(@navbar-inverse-bg, 10%); @end-color: @navbar-inverse-bg);\n .reset-filter(); // Remove gradient in IE<10 to fix bug where dropdowns don't get triggered\n\n .navbar-nav > .active > a {\n #gradient > .vertical(@start-color: @navbar-inverse-bg; @end-color: lighten(@navbar-inverse-bg, 2.5%));\n .box-shadow(inset 0 3px 9px rgba(0,0,0,.25));\n }\n\n .navbar-brand,\n .navbar-nav > li > a {\n text-shadow: 0 -1px 0 rgba(0,0,0,.25);\n }\n}\n\n// Undo rounded corners in static and fixed navbars\n.navbar-static-top,\n.navbar-fixed-top,\n.navbar-fixed-bottom {\n border-radius: 0;\n}\n\n\n\n//\n// Alerts\n// --------------------------------------------------\n\n// Common styles\n.alert {\n text-shadow: 0 1px 0 rgba(255,255,255,.2);\n @shadow: inset 0 1px 0 rgba(255,255,255,.25), 0 1px 2px rgba(0,0,0,.05);\n .box-shadow(@shadow);\n}\n\n// Mixin for generating new styles\n.alert-styles(@color) {\n #gradient > .vertical(@start-color: @color; @end-color: darken(@color, 7.5%));\n border-color: darken(@color, 15%);\n}\n\n// Apply the mixin to the alerts\n.alert-success { .alert-styles(@alert-success-bg); }\n.alert-info { .alert-styles(@alert-info-bg); }\n.alert-warning { .alert-styles(@alert-warning-bg); }\n.alert-danger { .alert-styles(@alert-danger-bg); }\n\n\n\n//\n// Progress bars\n// --------------------------------------------------\n\n// Give the progress background some depth\n.progress {\n #gradient > .vertical(@start-color: darken(@progress-bg, 4%); @end-color: @progress-bg)\n}\n\n// Mixin for generating new styles\n.progress-bar-styles(@color) {\n #gradient > .vertical(@start-color: @color; @end-color: darken(@color, 10%));\n}\n\n// Apply the mixin to the progress bars\n.progress-bar { .progress-bar-styles(@progress-bar-bg); }\n.progress-bar-success { .progress-bar-styles(@progress-bar-success-bg); }\n.progress-bar-info { .progress-bar-styles(@progress-bar-info-bg); }\n.progress-bar-warning { .progress-bar-styles(@progress-bar-warning-bg); }\n.progress-bar-danger { .progress-bar-styles(@progress-bar-danger-bg); }\n\n// Reset the striped class because our mixins don't do multiple gradients and\n// the above custom styles override the new `.progress-bar-striped` in v3.2.0.\n.progress-bar-striped {\n #gradient > .striped();\n}\n\n\n//\n// List groups\n// --------------------------------------------------\n\n.list-group {\n border-radius: @border-radius-base;\n .box-shadow(0 1px 2px rgba(0,0,0,.075));\n}\n.list-group-item.active,\n.list-group-item.active:hover,\n.list-group-item.active:focus {\n text-shadow: 0 -1px 0 darken(@list-group-active-bg, 10%);\n #gradient > .vertical(@start-color: @list-group-active-bg; @end-color: darken(@list-group-active-bg, 7.5%));\n border-color: darken(@list-group-active-border, 7.5%);\n}\n\n\n\n//\n// Panels\n// --------------------------------------------------\n\n// Common styles\n.panel {\n .box-shadow(0 1px 2px rgba(0,0,0,.05));\n}\n\n// Mixin for generating new styles\n.panel-heading-styles(@color) {\n #gradient > .vertical(@start-color: @color; @end-color: darken(@color, 5%));\n}\n\n// Apply the mixin to the panel headings only\n.panel-default > .panel-heading { .panel-heading-styles(@panel-default-heading-bg); }\n.panel-primary > .panel-heading { .panel-heading-styles(@panel-primary-heading-bg); }\n.panel-success > .panel-heading { .panel-heading-styles(@panel-success-heading-bg); }\n.panel-info > .panel-heading { .panel-heading-styles(@panel-info-heading-bg); }\n.panel-warning > .panel-heading { .panel-heading-styles(@panel-warning-heading-bg); }\n.panel-danger > .panel-heading { .panel-heading-styles(@panel-danger-heading-bg); }\n\n\n\n//\n// Wells\n// --------------------------------------------------\n\n.well {\n #gradient > .vertical(@start-color: darken(@well-bg, 5%); @end-color: @well-bg);\n border-color: darken(@well-bg, 10%);\n @shadow: inset 0 1px 3px rgba(0,0,0,.05), 0 1px 0 rgba(255,255,255,.1);\n .box-shadow(@shadow);\n}\n","// Vendor Prefixes\n//\n// All vendor mixins are deprecated as of v3.2.0 due to the introduction of\n// Autoprefixer in our Gruntfile. They will be removed in v4.\n\n// - Animations\n// - Backface visibility\n// - Box shadow\n// - Box sizing\n// - Content columns\n// - Hyphens\n// - Placeholder text\n// - Transformations\n// - Transitions\n// - User Select\n\n\n// Animations\n.animation(@animation) {\n -webkit-animation: @animation;\n -o-animation: @animation;\n animation: @animation;\n}\n.animation-name(@name) {\n -webkit-animation-name: @name;\n animation-name: @name;\n}\n.animation-duration(@duration) {\n -webkit-animation-duration: @duration;\n animation-duration: @duration;\n}\n.animation-timing-function(@timing-function) {\n -webkit-animation-timing-function: @timing-function;\n animation-timing-function: @timing-function;\n}\n.animation-delay(@delay) {\n -webkit-animation-delay: @delay;\n animation-delay: @delay;\n}\n.animation-iteration-count(@iteration-count) {\n -webkit-animation-iteration-count: @iteration-count;\n animation-iteration-count: @iteration-count;\n}\n.animation-direction(@direction) {\n -webkit-animation-direction: @direction;\n animation-direction: @direction;\n}\n.animation-fill-mode(@fill-mode) {\n -webkit-animation-fill-mode: @fill-mode;\n animation-fill-mode: @fill-mode;\n}\n\n// Backface visibility\n// Prevent browsers from flickering when using CSS 3D transforms.\n// Default value is `visible`, but can be changed to `hidden`\n\n.backface-visibility(@visibility){\n -webkit-backface-visibility: @visibility;\n -moz-backface-visibility: @visibility;\n backface-visibility: @visibility;\n}\n\n// Drop shadows\n//\n// Note: Deprecated `.box-shadow()` as of v3.1.0 since all of Bootstrap's\n// supported browsers that have box shadow capabilities now support it.\n\n.box-shadow(@shadow) {\n -webkit-box-shadow: @shadow; // iOS <4.3 & Android <4.1\n box-shadow: @shadow;\n}\n\n// Box sizing\n.box-sizing(@boxmodel) {\n -webkit-box-sizing: @boxmodel;\n -moz-box-sizing: @boxmodel;\n box-sizing: @boxmodel;\n}\n\n// CSS3 Content Columns\n.content-columns(@column-count; @column-gap: @grid-gutter-width) {\n -webkit-column-count: @column-count;\n -moz-column-count: @column-count;\n column-count: @column-count;\n -webkit-column-gap: @column-gap;\n -moz-column-gap: @column-gap;\n column-gap: @column-gap;\n}\n\n// Optional hyphenation\n.hyphens(@mode: auto) {\n word-wrap: break-word;\n -webkit-hyphens: @mode;\n -moz-hyphens: @mode;\n -ms-hyphens: @mode; // IE10+\n -o-hyphens: @mode;\n hyphens: @mode;\n}\n\n// Placeholder text\n.placeholder(@color: @input-color-placeholder) {\n &::-moz-placeholder { color: @color; // Firefox\n opacity: 1; } // See https://github.com/twbs/bootstrap/pull/11526\n &:-ms-input-placeholder { color: @color; } // Internet Explorer 10+\n &::-webkit-input-placeholder { color: @color; } // Safari and Chrome\n}\n\n// Transformations\n.scale(@ratio) {\n -webkit-transform: scale(@ratio);\n -ms-transform: scale(@ratio); // IE9 only\n -o-transform: scale(@ratio);\n transform: scale(@ratio);\n}\n.scale(@ratioX; @ratioY) {\n -webkit-transform: scale(@ratioX, @ratioY);\n -ms-transform: scale(@ratioX, @ratioY); // IE9 only\n -o-transform: scale(@ratioX, @ratioY);\n transform: scale(@ratioX, @ratioY);\n}\n.scaleX(@ratio) {\n -webkit-transform: scaleX(@ratio);\n -ms-transform: scaleX(@ratio); // IE9 only\n -o-transform: scaleX(@ratio);\n transform: scaleX(@ratio);\n}\n.scaleY(@ratio) {\n -webkit-transform: scaleY(@ratio);\n -ms-transform: scaleY(@ratio); // IE9 only\n -o-transform: scaleY(@ratio);\n transform: scaleY(@ratio);\n}\n.skew(@x; @y) {\n -webkit-transform: skewX(@x) skewY(@y);\n -ms-transform: skewX(@x) skewY(@y); // See https://github.com/twbs/bootstrap/issues/4885; IE9+\n -o-transform: skewX(@x) skewY(@y);\n transform: skewX(@x) skewY(@y);\n}\n.translate(@x; @y) {\n -webkit-transform: translate(@x, @y);\n -ms-transform: translate(@x, @y); // IE9 only\n -o-transform: translate(@x, @y);\n transform: translate(@x, @y);\n}\n.translate3d(@x; @y; @z) {\n -webkit-transform: translate3d(@x, @y, @z);\n transform: translate3d(@x, @y, @z);\n}\n.rotate(@degrees) {\n -webkit-transform: rotate(@degrees);\n -ms-transform: rotate(@degrees); // IE9 only\n -o-transform: rotate(@degrees);\n transform: rotate(@degrees);\n}\n.rotateX(@degrees) {\n -webkit-transform: rotateX(@degrees);\n -ms-transform: rotateX(@degrees); // IE9 only\n -o-transform: rotateX(@degrees);\n transform: rotateX(@degrees);\n}\n.rotateY(@degrees) {\n -webkit-transform: rotateY(@degrees);\n -ms-transform: rotateY(@degrees); // IE9 only\n -o-transform: rotateY(@degrees);\n transform: rotateY(@degrees);\n}\n.perspective(@perspective) {\n -webkit-perspective: @perspective;\n -moz-perspective: @perspective;\n perspective: @perspective;\n}\n.perspective-origin(@perspective) {\n -webkit-perspective-origin: @perspective;\n -moz-perspective-origin: @perspective;\n perspective-origin: @perspective;\n}\n.transform-origin(@origin) {\n -webkit-transform-origin: @origin;\n -moz-transform-origin: @origin;\n -ms-transform-origin: @origin; // IE9 only\n transform-origin: @origin;\n}\n\n\n// Transitions\n\n.transition(@transition) {\n -webkit-transition: @transition;\n -o-transition: @transition;\n transition: @transition;\n}\n.transition-property(@transition-property) {\n -webkit-transition-property: @transition-property;\n transition-property: @transition-property;\n}\n.transition-delay(@transition-delay) {\n -webkit-transition-delay: @transition-delay;\n transition-delay: @transition-delay;\n}\n.transition-duration(@transition-duration) {\n -webkit-transition-duration: @transition-duration;\n transition-duration: @transition-duration;\n}\n.transition-timing-function(@timing-function) {\n -webkit-transition-timing-function: @timing-function;\n transition-timing-function: @timing-function;\n}\n.transition-transform(@transition) {\n -webkit-transition: -webkit-transform @transition;\n -moz-transition: -moz-transform @transition;\n -o-transition: -o-transform @transition;\n transition: transform @transition;\n}\n\n\n// User select\n// For selecting text on the page\n\n.user-select(@select) {\n -webkit-user-select: @select;\n -moz-user-select: @select;\n -ms-user-select: @select; // IE10+\n user-select: @select;\n}\n",null,"// Gradients\n\n#gradient {\n\n // Horizontal gradient, from left to right\n //\n // Creates two color stops, start and end, by specifying a color and position for each color stop.\n // Color stops are not available in IE9 and below.\n .horizontal(@start-color: #555; @end-color: #333; @start-percent: 0%; @end-percent: 100%) {\n background-image: -webkit-linear-gradient(left, @start-color @start-percent, @end-color @end-percent); // Safari 5.1-6, Chrome 10+\n background-image: -o-linear-gradient(left, @start-color @start-percent, @end-color @end-percent); // Opera 12\n background-image: linear-gradient(to right, @start-color @start-percent, @end-color @end-percent); // Standard, IE10, Firefox 16+, Opera 12.10+, Safari 7+, Chrome 26+\n background-repeat: repeat-x;\n filter: e(%(\"progid:DXImageTransform.Microsoft.gradient(startColorstr='%d', endColorstr='%d', GradientType=1)\",argb(@start-color),argb(@end-color))); // IE9 and down\n }\n\n // Vertical gradient, from top to bottom\n //\n // Creates two color stops, start and end, by specifying a color and position for each color stop.\n // Color stops are not available in IE9 and below.\n .vertical(@start-color: #555; @end-color: #333; @start-percent: 0%; @end-percent: 100%) {\n background-image: -webkit-linear-gradient(top, @start-color @start-percent, @end-color @end-percent); // Safari 5.1-6, Chrome 10+\n background-image: -o-linear-gradient(top, @start-color @start-percent, @end-color @end-percent); // Opera 12\n background-image: linear-gradient(to bottom, @start-color @start-percent, @end-color @end-percent); // Standard, IE10, Firefox 16+, Opera 12.10+, Safari 7+, Chrome 26+\n background-repeat: repeat-x;\n filter: e(%(\"progid:DXImageTransform.Microsoft.gradient(startColorstr='%d', endColorstr='%d', GradientType=0)\",argb(@start-color),argb(@end-color))); // IE9 and down\n }\n\n .directional(@start-color: #555; @end-color: #333; @deg: 45deg) {\n background-repeat: repeat-x;\n background-image: -webkit-linear-gradient(@deg, @start-color, @end-color); // Safari 5.1-6, Chrome 10+\n background-image: -o-linear-gradient(@deg, @start-color, @end-color); // Opera 12\n background-image: linear-gradient(@deg, @start-color, @end-color); // Standard, IE10, Firefox 16+, Opera 12.10+, Safari 7+, Chrome 26+\n }\n .horizontal-three-colors(@start-color: #00b3ee; @mid-color: #7a43b6; @color-stop: 50%; @end-color: #c3325f) {\n background-image: -webkit-linear-gradient(left, @start-color, @mid-color @color-stop, @end-color);\n background-image: -o-linear-gradient(left, @start-color, @mid-color @color-stop, @end-color);\n background-image: linear-gradient(to right, @start-color, @mid-color @color-stop, @end-color);\n background-repeat: no-repeat;\n filter: e(%(\"progid:DXImageTransform.Microsoft.gradient(startColorstr='%d', endColorstr='%d', GradientType=1)\",argb(@start-color),argb(@end-color))); // IE9 and down, gets no color-stop at all for proper fallback\n }\n .vertical-three-colors(@start-color: #00b3ee; @mid-color: #7a43b6; @color-stop: 50%; @end-color: #c3325f) {\n background-image: -webkit-linear-gradient(@start-color, @mid-color @color-stop, @end-color);\n background-image: -o-linear-gradient(@start-color, @mid-color @color-stop, @end-color);\n background-image: linear-gradient(@start-color, @mid-color @color-stop, @end-color);\n background-repeat: no-repeat;\n filter: e(%(\"progid:DXImageTransform.Microsoft.gradient(startColorstr='%d', endColorstr='%d', GradientType=0)\",argb(@start-color),argb(@end-color))); // IE9 and down, gets no color-stop at all for proper fallback\n }\n .radial(@inner-color: #555; @outer-color: #333) {\n background-image: -webkit-radial-gradient(circle, @inner-color, @outer-color);\n background-image: radial-gradient(circle, @inner-color, @outer-color);\n background-repeat: no-repeat;\n }\n .striped(@color: rgba(255,255,255,.15); @angle: 45deg) {\n background-image: -webkit-linear-gradient(@angle, @color 25%, transparent 25%, transparent 50%, @color 50%, @color 75%, transparent 75%, transparent);\n background-image: -o-linear-gradient(@angle, @color 25%, transparent 25%, transparent 50%, @color 50%, @color 75%, transparent 75%, transparent);\n background-image: linear-gradient(@angle, @color 25%, transparent 25%, transparent 50%, @color 50%, @color 75%, transparent 75%, transparent);\n }\n}\n","// Reset filters for IE\n//\n// When you need to remove a gradient background, do not forget to use this to reset\n// the IE filter for IE9 and below.\n\n.reset-filter() {\n filter: e(%(\"progid:DXImageTransform.Microsoft.gradient(enabled = false)\"));\n}\n"]} -------------------------------------------------------------------------------- /static/bootstrap/css/bootstrap-theme.css: -------------------------------------------------------------------------------- 1 | /*! 2 | * Bootstrap v3.2.0 (http://getbootstrap.com) 3 | * Copyright 2011-2014 Twitter, Inc. 4 | * Licensed under MIT (https://github.com/twbs/bootstrap/blob/master/LICENSE) 5 | */ 6 | 7 | .btn-default, 8 | .btn-primary, 9 | .btn-success, 10 | .btn-info, 11 | .btn-warning, 12 | .btn-danger { 13 | text-shadow: 0 -1px 0 rgba(0, 0, 0, .2); 14 | -webkit-box-shadow: inset 0 1px 0 rgba(255, 255, 255, .15), 0 1px 1px rgba(0, 0, 0, .075); 15 | box-shadow: inset 0 1px 0 rgba(255, 255, 255, .15), 0 1px 1px rgba(0, 0, 0, .075); 16 | } 17 | .btn-default:active, 18 | .btn-primary:active, 19 | .btn-success:active, 20 | .btn-info:active, 21 | .btn-warning:active, 22 | .btn-danger:active, 23 | .btn-default.active, 24 | .btn-primary.active, 25 | .btn-success.active, 26 | .btn-info.active, 27 | .btn-warning.active, 28 | .btn-danger.active { 29 | -webkit-box-shadow: inset 0 3px 5px rgba(0, 0, 0, .125); 30 | box-shadow: inset 0 3px 5px rgba(0, 0, 0, .125); 31 | } 32 | .btn:active, 33 | .btn.active { 34 | background-image: none; 35 | } 36 | .btn-default { 37 | text-shadow: 0 1px 0 #fff; 38 | background-image: -webkit-linear-gradient(top, #fff 0%, #e0e0e0 100%); 39 | background-image: -o-linear-gradient(top, #fff 0%, #e0e0e0 100%); 40 | background-image: -webkit-gradient(linear, left top, left bottom, from(#fff), to(#e0e0e0)); 41 | background-image: linear-gradient(to bottom, #fff 0%, #e0e0e0 100%); 42 | filter: progid:DXImageTransform.Microsoft.gradient(startColorstr='#ffffffff', endColorstr='#ffe0e0e0', GradientType=0); 43 | filter: progid:DXImageTransform.Microsoft.gradient(enabled = false); 44 | background-repeat: repeat-x; 45 | border-color: #dbdbdb; 46 | border-color: #ccc; 47 | } 48 | .btn-default:hover, 49 | .btn-default:focus { 50 | background-color: #e0e0e0; 51 | background-position: 0 -15px; 52 | } 53 | .btn-default:active, 54 | .btn-default.active { 55 | background-color: #e0e0e0; 56 | border-color: #dbdbdb; 57 | } 58 | .btn-default:disabled, 59 | .btn-default[disabled] { 60 | background-color: #e0e0e0; 61 | background-image: none; 62 | } 63 | .btn-primary { 64 | background-image: -webkit-linear-gradient(top, #428bca 0%, #2d6ca2 100%); 65 | background-image: -o-linear-gradient(top, #428bca 0%, #2d6ca2 100%); 66 | background-image: -webkit-gradient(linear, left top, left bottom, from(#428bca), to(#2d6ca2)); 67 | background-image: linear-gradient(to bottom, #428bca 0%, #2d6ca2 100%); 68 | filter: progid:DXImageTransform.Microsoft.gradient(startColorstr='#ff428bca', endColorstr='#ff2d6ca2', GradientType=0); 69 | filter: progid:DXImageTransform.Microsoft.gradient(enabled = false); 70 | background-repeat: repeat-x; 71 | border-color: #2b669a; 72 | } 73 | .btn-primary:hover, 74 | .btn-primary:focus { 75 | background-color: #2d6ca2; 76 | background-position: 0 -15px; 77 | } 78 | .btn-primary:active, 79 | .btn-primary.active { 80 | background-color: #2d6ca2; 81 | border-color: #2b669a; 82 | } 83 | .btn-primary:disabled, 84 | .btn-primary[disabled] { 85 | background-color: #2d6ca2; 86 | background-image: none; 87 | } 88 | .btn-success { 89 | background-image: -webkit-linear-gradient(top, #5cb85c 0%, #419641 100%); 90 | background-image: -o-linear-gradient(top, #5cb85c 0%, #419641 100%); 91 | background-image: -webkit-gradient(linear, left top, left bottom, from(#5cb85c), to(#419641)); 92 | background-image: linear-gradient(to bottom, #5cb85c 0%, #419641 100%); 93 | filter: progid:DXImageTransform.Microsoft.gradient(startColorstr='#ff5cb85c', endColorstr='#ff419641', GradientType=0); 94 | filter: progid:DXImageTransform.Microsoft.gradient(enabled = false); 95 | background-repeat: repeat-x; 96 | border-color: #3e8f3e; 97 | } 98 | .btn-success:hover, 99 | .btn-success:focus { 100 | background-color: #419641; 101 | background-position: 0 -15px; 102 | } 103 | .btn-success:active, 104 | .btn-success.active { 105 | background-color: #419641; 106 | border-color: #3e8f3e; 107 | } 108 | .btn-success:disabled, 109 | .btn-success[disabled] { 110 | background-color: #419641; 111 | background-image: none; 112 | } 113 | .btn-info { 114 | background-image: -webkit-linear-gradient(top, #5bc0de 0%, #2aabd2 100%); 115 | background-image: -o-linear-gradient(top, #5bc0de 0%, #2aabd2 100%); 116 | background-image: -webkit-gradient(linear, left top, left bottom, from(#5bc0de), to(#2aabd2)); 117 | background-image: linear-gradient(to bottom, #5bc0de 0%, #2aabd2 100%); 118 | filter: progid:DXImageTransform.Microsoft.gradient(startColorstr='#ff5bc0de', endColorstr='#ff2aabd2', GradientType=0); 119 | filter: progid:DXImageTransform.Microsoft.gradient(enabled = false); 120 | background-repeat: repeat-x; 121 | border-color: #28a4c9; 122 | } 123 | .btn-info:hover, 124 | .btn-info:focus { 125 | background-color: #2aabd2; 126 | background-position: 0 -15px; 127 | } 128 | .btn-info:active, 129 | .btn-info.active { 130 | background-color: #2aabd2; 131 | border-color: #28a4c9; 132 | } 133 | .btn-info:disabled, 134 | .btn-info[disabled] { 135 | background-color: #2aabd2; 136 | background-image: none; 137 | } 138 | .btn-warning { 139 | background-image: -webkit-linear-gradient(top, #f0ad4e 0%, #eb9316 100%); 140 | background-image: -o-linear-gradient(top, #f0ad4e 0%, #eb9316 100%); 141 | background-image: -webkit-gradient(linear, left top, left bottom, from(#f0ad4e), to(#eb9316)); 142 | background-image: linear-gradient(to bottom, #f0ad4e 0%, #eb9316 100%); 143 | filter: progid:DXImageTransform.Microsoft.gradient(startColorstr='#fff0ad4e', endColorstr='#ffeb9316', GradientType=0); 144 | filter: progid:DXImageTransform.Microsoft.gradient(enabled = false); 145 | background-repeat: repeat-x; 146 | border-color: #e38d13; 147 | } 148 | .btn-warning:hover, 149 | .btn-warning:focus { 150 | background-color: #eb9316; 151 | background-position: 0 -15px; 152 | } 153 | .btn-warning:active, 154 | .btn-warning.active { 155 | background-color: #eb9316; 156 | border-color: #e38d13; 157 | } 158 | .btn-warning:disabled, 159 | .btn-warning[disabled] { 160 | background-color: #eb9316; 161 | background-image: none; 162 | } 163 | .btn-danger { 164 | background-image: -webkit-linear-gradient(top, #d9534f 0%, #c12e2a 100%); 165 | background-image: -o-linear-gradient(top, #d9534f 0%, #c12e2a 100%); 166 | background-image: -webkit-gradient(linear, left top, left bottom, from(#d9534f), to(#c12e2a)); 167 | background-image: linear-gradient(to bottom, #d9534f 0%, #c12e2a 100%); 168 | filter: progid:DXImageTransform.Microsoft.gradient(startColorstr='#ffd9534f', endColorstr='#ffc12e2a', GradientType=0); 169 | filter: progid:DXImageTransform.Microsoft.gradient(enabled = false); 170 | background-repeat: repeat-x; 171 | border-color: #b92c28; 172 | } 173 | .btn-danger:hover, 174 | .btn-danger:focus { 175 | background-color: #c12e2a; 176 | background-position: 0 -15px; 177 | } 178 | .btn-danger:active, 179 | .btn-danger.active { 180 | background-color: #c12e2a; 181 | border-color: #b92c28; 182 | } 183 | .btn-danger:disabled, 184 | .btn-danger[disabled] { 185 | background-color: #c12e2a; 186 | background-image: none; 187 | } 188 | .thumbnail, 189 | .img-thumbnail { 190 | -webkit-box-shadow: 0 1px 2px rgba(0, 0, 0, .075); 191 | box-shadow: 0 1px 2px rgba(0, 0, 0, .075); 192 | } 193 | .dropdown-menu > li > a:hover, 194 | .dropdown-menu > li > a:focus { 195 | background-color: #e8e8e8; 196 | background-image: -webkit-linear-gradient(top, #f5f5f5 0%, #e8e8e8 100%); 197 | background-image: -o-linear-gradient(top, #f5f5f5 0%, #e8e8e8 100%); 198 | background-image: -webkit-gradient(linear, left top, left bottom, from(#f5f5f5), to(#e8e8e8)); 199 | background-image: linear-gradient(to bottom, #f5f5f5 0%, #e8e8e8 100%); 200 | filter: progid:DXImageTransform.Microsoft.gradient(startColorstr='#fff5f5f5', endColorstr='#ffe8e8e8', GradientType=0); 201 | background-repeat: repeat-x; 202 | } 203 | .dropdown-menu > .active > a, 204 | .dropdown-menu > .active > a:hover, 205 | .dropdown-menu > .active > a:focus { 206 | background-color: #357ebd; 207 | background-image: -webkit-linear-gradient(top, #428bca 0%, #357ebd 100%); 208 | background-image: -o-linear-gradient(top, #428bca 0%, #357ebd 100%); 209 | background-image: -webkit-gradient(linear, left top, left bottom, from(#428bca), to(#357ebd)); 210 | background-image: linear-gradient(to bottom, #428bca 0%, #357ebd 100%); 211 | filter: progid:DXImageTransform.Microsoft.gradient(startColorstr='#ff428bca', endColorstr='#ff357ebd', GradientType=0); 212 | background-repeat: repeat-x; 213 | } 214 | .navbar-default { 215 | background-image: -webkit-linear-gradient(top, #fff 0%, #f8f8f8 100%); 216 | background-image: -o-linear-gradient(top, #fff 0%, #f8f8f8 100%); 217 | background-image: -webkit-gradient(linear, left top, left bottom, from(#fff), to(#f8f8f8)); 218 | background-image: linear-gradient(to bottom, #fff 0%, #f8f8f8 100%); 219 | filter: progid:DXImageTransform.Microsoft.gradient(startColorstr='#ffffffff', endColorstr='#fff8f8f8', GradientType=0); 220 | filter: progid:DXImageTransform.Microsoft.gradient(enabled = false); 221 | background-repeat: repeat-x; 222 | border-radius: 4px; 223 | -webkit-box-shadow: inset 0 1px 0 rgba(255, 255, 255, .15), 0 1px 5px rgba(0, 0, 0, .075); 224 | box-shadow: inset 0 1px 0 rgba(255, 255, 255, .15), 0 1px 5px rgba(0, 0, 0, .075); 225 | } 226 | .navbar-default .navbar-nav > .active > a { 227 | background-image: -webkit-linear-gradient(top, #ebebeb 0%, #f3f3f3 100%); 228 | background-image: -o-linear-gradient(top, #ebebeb 0%, #f3f3f3 100%); 229 | background-image: -webkit-gradient(linear, left top, left bottom, from(#ebebeb), to(#f3f3f3)); 230 | background-image: linear-gradient(to bottom, #ebebeb 0%, #f3f3f3 100%); 231 | filter: progid:DXImageTransform.Microsoft.gradient(startColorstr='#ffebebeb', endColorstr='#fff3f3f3', GradientType=0); 232 | background-repeat: repeat-x; 233 | -webkit-box-shadow: inset 0 3px 9px rgba(0, 0, 0, .075); 234 | box-shadow: inset 0 3px 9px rgba(0, 0, 0, .075); 235 | } 236 | .navbar-brand, 237 | .navbar-nav > li > a { 238 | text-shadow: 0 1px 0 rgba(255, 255, 255, .25); 239 | } 240 | .navbar-inverse { 241 | background-image: -webkit-linear-gradient(top, #3c3c3c 0%, #222 100%); 242 | background-image: -o-linear-gradient(top, #3c3c3c 0%, #222 100%); 243 | background-image: -webkit-gradient(linear, left top, left bottom, from(#3c3c3c), to(#222)); 244 | background-image: linear-gradient(to bottom, #3c3c3c 0%, #222 100%); 245 | filter: progid:DXImageTransform.Microsoft.gradient(startColorstr='#ff3c3c3c', endColorstr='#ff222222', GradientType=0); 246 | filter: progid:DXImageTransform.Microsoft.gradient(enabled = false); 247 | background-repeat: repeat-x; 248 | } 249 | .navbar-inverse .navbar-nav > .active > a { 250 | background-image: -webkit-linear-gradient(top, #222 0%, #282828 100%); 251 | background-image: -o-linear-gradient(top, #222 0%, #282828 100%); 252 | background-image: -webkit-gradient(linear, left top, left bottom, from(#222), to(#282828)); 253 | background-image: linear-gradient(to bottom, #222 0%, #282828 100%); 254 | filter: progid:DXImageTransform.Microsoft.gradient(startColorstr='#ff222222', endColorstr='#ff282828', GradientType=0); 255 | background-repeat: repeat-x; 256 | -webkit-box-shadow: inset 0 3px 9px rgba(0, 0, 0, .25); 257 | box-shadow: inset 0 3px 9px rgba(0, 0, 0, .25); 258 | } 259 | .navbar-inverse .navbar-brand, 260 | .navbar-inverse .navbar-nav > li > a { 261 | text-shadow: 0 -1px 0 rgba(0, 0, 0, .25); 262 | } 263 | .navbar-static-top, 264 | .navbar-fixed-top, 265 | .navbar-fixed-bottom { 266 | border-radius: 0; 267 | } 268 | .alert { 269 | text-shadow: 0 1px 0 rgba(255, 255, 255, .2); 270 | -webkit-box-shadow: inset 0 1px 0 rgba(255, 255, 255, .25), 0 1px 2px rgba(0, 0, 0, .05); 271 | box-shadow: inset 0 1px 0 rgba(255, 255, 255, .25), 0 1px 2px rgba(0, 0, 0, .05); 272 | } 273 | .alert-success { 274 | background-image: -webkit-linear-gradient(top, #dff0d8 0%, #c8e5bc 100%); 275 | background-image: -o-linear-gradient(top, #dff0d8 0%, #c8e5bc 100%); 276 | background-image: -webkit-gradient(linear, left top, left bottom, from(#dff0d8), to(#c8e5bc)); 277 | background-image: linear-gradient(to bottom, #dff0d8 0%, #c8e5bc 100%); 278 | filter: progid:DXImageTransform.Microsoft.gradient(startColorstr='#ffdff0d8', endColorstr='#ffc8e5bc', GradientType=0); 279 | background-repeat: repeat-x; 280 | border-color: #b2dba1; 281 | } 282 | .alert-info { 283 | background-image: -webkit-linear-gradient(top, #d9edf7 0%, #b9def0 100%); 284 | background-image: -o-linear-gradient(top, #d9edf7 0%, #b9def0 100%); 285 | background-image: -webkit-gradient(linear, left top, left bottom, from(#d9edf7), to(#b9def0)); 286 | background-image: linear-gradient(to bottom, #d9edf7 0%, #b9def0 100%); 287 | filter: progid:DXImageTransform.Microsoft.gradient(startColorstr='#ffd9edf7', endColorstr='#ffb9def0', GradientType=0); 288 | background-repeat: repeat-x; 289 | border-color: #9acfea; 290 | } 291 | .alert-warning { 292 | background-image: -webkit-linear-gradient(top, #fcf8e3 0%, #f8efc0 100%); 293 | background-image: -o-linear-gradient(top, #fcf8e3 0%, #f8efc0 100%); 294 | background-image: -webkit-gradient(linear, left top, left bottom, from(#fcf8e3), to(#f8efc0)); 295 | background-image: linear-gradient(to bottom, #fcf8e3 0%, #f8efc0 100%); 296 | filter: progid:DXImageTransform.Microsoft.gradient(startColorstr='#fffcf8e3', endColorstr='#fff8efc0', GradientType=0); 297 | background-repeat: repeat-x; 298 | border-color: #f5e79e; 299 | } 300 | .alert-danger { 301 | background-image: -webkit-linear-gradient(top, #f2dede 0%, #e7c3c3 100%); 302 | background-image: -o-linear-gradient(top, #f2dede 0%, #e7c3c3 100%); 303 | background-image: -webkit-gradient(linear, left top, left bottom, from(#f2dede), to(#e7c3c3)); 304 | background-image: linear-gradient(to bottom, #f2dede 0%, #e7c3c3 100%); 305 | filter: progid:DXImageTransform.Microsoft.gradient(startColorstr='#fff2dede', endColorstr='#ffe7c3c3', GradientType=0); 306 | background-repeat: repeat-x; 307 | border-color: #dca7a7; 308 | } 309 | .progress { 310 | background-image: -webkit-linear-gradient(top, #ebebeb 0%, #f5f5f5 100%); 311 | background-image: -o-linear-gradient(top, #ebebeb 0%, #f5f5f5 100%); 312 | background-image: -webkit-gradient(linear, left top, left bottom, from(#ebebeb), to(#f5f5f5)); 313 | background-image: linear-gradient(to bottom, #ebebeb 0%, #f5f5f5 100%); 314 | filter: progid:DXImageTransform.Microsoft.gradient(startColorstr='#ffebebeb', endColorstr='#fff5f5f5', GradientType=0); 315 | background-repeat: repeat-x; 316 | } 317 | .progress-bar { 318 | background-image: -webkit-linear-gradient(top, #428bca 0%, #3071a9 100%); 319 | background-image: -o-linear-gradient(top, #428bca 0%, #3071a9 100%); 320 | background-image: -webkit-gradient(linear, left top, left bottom, from(#428bca), to(#3071a9)); 321 | background-image: linear-gradient(to bottom, #428bca 0%, #3071a9 100%); 322 | filter: progid:DXImageTransform.Microsoft.gradient(startColorstr='#ff428bca', endColorstr='#ff3071a9', GradientType=0); 323 | background-repeat: repeat-x; 324 | } 325 | .progress-bar-success { 326 | background-image: -webkit-linear-gradient(top, #5cb85c 0%, #449d44 100%); 327 | background-image: -o-linear-gradient(top, #5cb85c 0%, #449d44 100%); 328 | background-image: -webkit-gradient(linear, left top, left bottom, from(#5cb85c), to(#449d44)); 329 | background-image: linear-gradient(to bottom, #5cb85c 0%, #449d44 100%); 330 | filter: progid:DXImageTransform.Microsoft.gradient(startColorstr='#ff5cb85c', endColorstr='#ff449d44', GradientType=0); 331 | background-repeat: repeat-x; 332 | } 333 | .progress-bar-info { 334 | background-image: -webkit-linear-gradient(top, #5bc0de 0%, #31b0d5 100%); 335 | background-image: -o-linear-gradient(top, #5bc0de 0%, #31b0d5 100%); 336 | background-image: -webkit-gradient(linear, left top, left bottom, from(#5bc0de), to(#31b0d5)); 337 | background-image: linear-gradient(to bottom, #5bc0de 0%, #31b0d5 100%); 338 | filter: progid:DXImageTransform.Microsoft.gradient(startColorstr='#ff5bc0de', endColorstr='#ff31b0d5', GradientType=0); 339 | background-repeat: repeat-x; 340 | } 341 | .progress-bar-warning { 342 | background-image: -webkit-linear-gradient(top, #f0ad4e 0%, #ec971f 100%); 343 | background-image: -o-linear-gradient(top, #f0ad4e 0%, #ec971f 100%); 344 | background-image: -webkit-gradient(linear, left top, left bottom, from(#f0ad4e), to(#ec971f)); 345 | background-image: linear-gradient(to bottom, #f0ad4e 0%, #ec971f 100%); 346 | filter: progid:DXImageTransform.Microsoft.gradient(startColorstr='#fff0ad4e', endColorstr='#ffec971f', GradientType=0); 347 | background-repeat: repeat-x; 348 | } 349 | .progress-bar-danger { 350 | background-image: -webkit-linear-gradient(top, #d9534f 0%, #c9302c 100%); 351 | background-image: -o-linear-gradient(top, #d9534f 0%, #c9302c 100%); 352 | background-image: -webkit-gradient(linear, left top, left bottom, from(#d9534f), to(#c9302c)); 353 | background-image: linear-gradient(to bottom, #d9534f 0%, #c9302c 100%); 354 | filter: progid:DXImageTransform.Microsoft.gradient(startColorstr='#ffd9534f', endColorstr='#ffc9302c', GradientType=0); 355 | background-repeat: repeat-x; 356 | } 357 | .progress-bar-striped { 358 | background-image: -webkit-linear-gradient(45deg, rgba(255, 255, 255, .15) 25%, transparent 25%, transparent 50%, rgba(255, 255, 255, .15) 50%, rgba(255, 255, 255, .15) 75%, transparent 75%, transparent); 359 | background-image: -o-linear-gradient(45deg, rgba(255, 255, 255, .15) 25%, transparent 25%, transparent 50%, rgba(255, 255, 255, .15) 50%, rgba(255, 255, 255, .15) 75%, transparent 75%, transparent); 360 | background-image: linear-gradient(45deg, rgba(255, 255, 255, .15) 25%, transparent 25%, transparent 50%, rgba(255, 255, 255, .15) 50%, rgba(255, 255, 255, .15) 75%, transparent 75%, transparent); 361 | } 362 | .list-group { 363 | border-radius: 4px; 364 | -webkit-box-shadow: 0 1px 2px rgba(0, 0, 0, .075); 365 | box-shadow: 0 1px 2px rgba(0, 0, 0, .075); 366 | } 367 | .list-group-item.active, 368 | .list-group-item.active:hover, 369 | .list-group-item.active:focus { 370 | text-shadow: 0 -1px 0 #3071a9; 371 | background-image: -webkit-linear-gradient(top, #428bca 0%, #3278b3 100%); 372 | background-image: -o-linear-gradient(top, #428bca 0%, #3278b3 100%); 373 | background-image: -webkit-gradient(linear, left top, left bottom, from(#428bca), to(#3278b3)); 374 | background-image: linear-gradient(to bottom, #428bca 0%, #3278b3 100%); 375 | filter: progid:DXImageTransform.Microsoft.gradient(startColorstr='#ff428bca', endColorstr='#ff3278b3', GradientType=0); 376 | background-repeat: repeat-x; 377 | border-color: #3278b3; 378 | } 379 | .panel { 380 | -webkit-box-shadow: 0 1px 2px rgba(0, 0, 0, .05); 381 | box-shadow: 0 1px 2px rgba(0, 0, 0, .05); 382 | } 383 | .panel-default > .panel-heading { 384 | background-image: -webkit-linear-gradient(top, #f5f5f5 0%, #e8e8e8 100%); 385 | background-image: -o-linear-gradient(top, #f5f5f5 0%, #e8e8e8 100%); 386 | background-image: -webkit-gradient(linear, left top, left bottom, from(#f5f5f5), to(#e8e8e8)); 387 | background-image: linear-gradient(to bottom, #f5f5f5 0%, #e8e8e8 100%); 388 | filter: progid:DXImageTransform.Microsoft.gradient(startColorstr='#fff5f5f5', endColorstr='#ffe8e8e8', GradientType=0); 389 | background-repeat: repeat-x; 390 | } 391 | .panel-primary > .panel-heading { 392 | background-image: -webkit-linear-gradient(top, #428bca 0%, #357ebd 100%); 393 | background-image: -o-linear-gradient(top, #428bca 0%, #357ebd 100%); 394 | background-image: -webkit-gradient(linear, left top, left bottom, from(#428bca), to(#357ebd)); 395 | background-image: linear-gradient(to bottom, #428bca 0%, #357ebd 100%); 396 | filter: progid:DXImageTransform.Microsoft.gradient(startColorstr='#ff428bca', endColorstr='#ff357ebd', GradientType=0); 397 | background-repeat: repeat-x; 398 | } 399 | .panel-success > .panel-heading { 400 | background-image: -webkit-linear-gradient(top, #dff0d8 0%, #d0e9c6 100%); 401 | background-image: -o-linear-gradient(top, #dff0d8 0%, #d0e9c6 100%); 402 | background-image: -webkit-gradient(linear, left top, left bottom, from(#dff0d8), to(#d0e9c6)); 403 | background-image: linear-gradient(to bottom, #dff0d8 0%, #d0e9c6 100%); 404 | filter: progid:DXImageTransform.Microsoft.gradient(startColorstr='#ffdff0d8', endColorstr='#ffd0e9c6', GradientType=0); 405 | background-repeat: repeat-x; 406 | } 407 | .panel-info > .panel-heading { 408 | background-image: -webkit-linear-gradient(top, #d9edf7 0%, #c4e3f3 100%); 409 | background-image: -o-linear-gradient(top, #d9edf7 0%, #c4e3f3 100%); 410 | background-image: -webkit-gradient(linear, left top, left bottom, from(#d9edf7), to(#c4e3f3)); 411 | background-image: linear-gradient(to bottom, #d9edf7 0%, #c4e3f3 100%); 412 | filter: progid:DXImageTransform.Microsoft.gradient(startColorstr='#ffd9edf7', endColorstr='#ffc4e3f3', GradientType=0); 413 | background-repeat: repeat-x; 414 | } 415 | .panel-warning > .panel-heading { 416 | background-image: -webkit-linear-gradient(top, #fcf8e3 0%, #faf2cc 100%); 417 | background-image: -o-linear-gradient(top, #fcf8e3 0%, #faf2cc 100%); 418 | background-image: -webkit-gradient(linear, left top, left bottom, from(#fcf8e3), to(#faf2cc)); 419 | background-image: linear-gradient(to bottom, #fcf8e3 0%, #faf2cc 100%); 420 | filter: progid:DXImageTransform.Microsoft.gradient(startColorstr='#fffcf8e3', endColorstr='#fffaf2cc', GradientType=0); 421 | background-repeat: repeat-x; 422 | } 423 | .panel-danger > .panel-heading { 424 | background-image: -webkit-linear-gradient(top, #f2dede 0%, #ebcccc 100%); 425 | background-image: -o-linear-gradient(top, #f2dede 0%, #ebcccc 100%); 426 | background-image: -webkit-gradient(linear, left top, left bottom, from(#f2dede), to(#ebcccc)); 427 | background-image: linear-gradient(to bottom, #f2dede 0%, #ebcccc 100%); 428 | filter: progid:DXImageTransform.Microsoft.gradient(startColorstr='#fff2dede', endColorstr='#ffebcccc', GradientType=0); 429 | background-repeat: repeat-x; 430 | } 431 | .well { 432 | background-image: -webkit-linear-gradient(top, #e8e8e8 0%, #f5f5f5 100%); 433 | background-image: -o-linear-gradient(top, #e8e8e8 0%, #f5f5f5 100%); 434 | background-image: -webkit-gradient(linear, left top, left bottom, from(#e8e8e8), to(#f5f5f5)); 435 | background-image: linear-gradient(to bottom, #e8e8e8 0%, #f5f5f5 100%); 436 | filter: progid:DXImageTransform.Microsoft.gradient(startColorstr='#ffe8e8e8', endColorstr='#fff5f5f5', GradientType=0); 437 | background-repeat: repeat-x; 438 | border-color: #dcdcdc; 439 | -webkit-box-shadow: inset 0 1px 3px rgba(0, 0, 0, .05), 0 1px 0 rgba(255, 255, 255, .1); 440 | box-shadow: inset 0 1px 3px rgba(0, 0, 0, .05), 0 1px 0 rgba(255, 255, 255, .1); 441 | } 442 | /*# sourceMappingURL=bootstrap-theme.css.map */ 443 | --------------------------------------------------------------------------------