##// END OF EJS Templates
Generator parameter type fixed, devices status & warning messages
amorales -
r350:c2b9b4d3a22d
parent child
Show More

The requested changes are too big and content was truncated. Show full diff

1 NO CONTENT: new file 100644
@@ -0,0 +1,6
1 from django.contrib import admin
2 from .models import PedestalDevConfiguration
3
4 # Register your models here.
5
6 admin.site.register(PedestalDevConfiguration)
@@ -0,0 +1,80
1 import os
2 import json
3
4 from django import forms
5 from django.utils.safestring import mark_safe
6 from apps.main.models import Device
7 from apps.main.forms import add_empty_choice
8 from .models import PedestalDevConfiguration
9
10 def create_choices_from_model(model, conf_id, all_choice=False):
11
12 instance = globals()[model]
13 choices = instance.objects.all().values_list('pk', 'name')
14
15 return choices
16
17 class PedestalDevConfigurationForm(forms.ModelForm):
18
19 def __init__(self, *args, **kwargs):
20 super(PedestalDevConfigurationForm, self).__init__(*args, **kwargs)
21
22 instance = getattr(self, 'instance', None)
23
24 if instance and instance.pk:
25
26 devices = Device.objects.filter(device_type__name='pedestal_dev')
27 if instance.experiment:
28 self.fields['experiment'].widget.attrs['read_only'] = True
29 #self.fields['experiment'].widget.choices = [(instance.experiment.id, instance.experiment)]
30 self.fields['device'].widget.choices = [(device.id, device) for device in devices]
31
32 if 'initial' in kwargs and 'experiment' in kwargs['initial'] and kwargs['initial']['experiment'] not in (0, '0'):
33 self.fields['experiment'].widget.attrs['readonly'] = True
34
35 class Meta:
36 model = PedestalDevConfiguration
37 exclude = ('type', 'parameters', 'status', 'total_units', 'author', 'hash')
38
39 def clean(self):
40 form_data = super(PedestalDevConfigurationForm, self).clean()
41 return form_data
42
43 def save(self, *args, **kwargs):
44 conf = super(PedestalDevConfigurationForm, self).save(*args, **kwargs)
45 conf.save()
46 return conf
47
48 class ExtFileField(forms.FileField):
49 """
50 Same as forms.FileField, but you can specify a file extension whitelist.
51
52 >>> from django.core.files.uploadedfile import SimpleUploadedFile
53 >>>
54 >>> t = ExtFileField(ext_whitelist=(".pdf", ".txt"))
55 >>>
56 >>> t.clean(SimpleUploadedFile('filename.pdf', 'Some File Content'))
57 >>> t.clean(SimpleUploadedFile('filename.txt', 'Some File Content'))
58 >>>
59 >>> t.clean(SimpleUploadedFile('filename.exe', 'Some File Content'))
60 Traceback (most recent call last):
61 ...
62 ValidationError: [u'Not allowed filetype!']
63 """
64 def __init__(self, *args, **kwargs):
65 extensions = kwargs.pop("extensions")
66 self.extensions = [i.lower() for i in extensions]
67
68 super(ExtFileField, self).__init__(*args, **kwargs)
69
70 def clean(self, *args, **kwargs):
71 data = super(ExtFileField, self).clean(*args, **kwargs)
72 filename = data.name
73 ext = os.path.splitext(filename)[1]
74 ext = ext.lower()
75 if ext not in self.extensions:
76 raise forms.ValidationError('Not allowed file type: %s' % ext)
77
78 class PedestalDevImportForm(forms.Form):
79
80 file_name = ExtFileField(extensions=['.racp', '.json', '.dat']) No newline at end of file
@@ -0,0 +1,273
1 import ast
2 import json
3 import requests
4 import base64
5 import struct
6 from struct import pack
7 import time
8 from django.contrib import messages
9 from django.db import models
10 from django.urls import reverse
11 from django.core.validators import MinValueValidator, MaxValueValidator
12
13 from apps.main.models import Configuration
14
15 MODE_VALUE = (
16 ('SPD', 'speed'),
17 ('POS', 'position')
18 )
19
20 AXIS_VALUE = (
21 ('AZI', 'azimuth'),
22 ('ELE', 'elevation')
23 )
24
25 class PedestalDevConfiguration(Configuration):
26
27 mode = models.CharField(
28 verbose_name='Mode',
29 max_length=3,
30 choices=MODE_VALUE,
31 null=False,
32 blank=False
33 )
34
35 axis = models.CharField(
36 verbose_name='Axis',
37 max_length=3,
38 choices=AXIS_VALUE,
39 null=False,
40 blank=False
41 )
42
43 speed = models.FloatField(
44 verbose_name='Speed',
45 validators=[MinValueValidator(-20), MaxValueValidator(20)],
46 blank=True,
47 null=True
48 )
49
50 position = models.FloatField(
51 verbose_name='Position',
52 validators=[MinValueValidator(0), MaxValueValidator(360)],
53 blank=True,
54 null =True
55 )
56
57 class Meta:
58 db_table = 'pedestal_dev_configurations'
59
60 def __str__(self):
61 return str(self.label)
62
63 def get_absolute_url_plot(self):
64 return reverse('url_plot_pedestal_dev_pulses', args=[str(self.id)])
65
66 def request(self, cmd, method='get', **kwargs):
67
68 req = getattr(requests, method)(self.device.url(cmd), **kwargs)
69 payload = req.json()
70
71 return payload
72
73 def status_device(self):
74
75 try:
76 #self.device.status = 0
77 #payload = self.request('status')
78 payload = requests.get(self.device.url())
79 print(payload)
80 if payload:
81 self.device.status = 1
82 elif payload['status']=='disable':
83 self.device.status = 2
84 else:
85 self.device.status = 1
86 self.device.save()
87 self.message = 'Pedestal Dev status: {}'.format(payload['status'])
88 return False
89 except Exception as e:
90 if 'No route to host' not in str(e):
91 self.device.status = 4
92 self.device.save()
93 self.message = 'Pedestal Dev status: {}'.format(str(e))
94 return False
95
96 self.device.save()
97 return True
98
99 def reset_device(self):
100
101 try:
102 payload = self.request('reset', 'post')
103 if payload['reset']=='ok':
104 self.message = 'Pedestal Dev restarted OK'
105 self.device.status = 2
106 self.device.save()
107 else:
108 self.message = 'Pedestal Dev restart fail'
109 self.device.status = 4
110 self.device.save()
111 except Exception as e:
112 self.message = 'Pedestal Dev reset: {}'.format(str(e))
113 return False
114
115 return True
116
117 def stop_device(self):
118
119 try:
120 command = self.device.url() + "stop"
121 r = requests.get(command)
122 if r:
123 self.device.status = 4
124 self.device.save()
125 self.message = 'Pedestal Dev stopped'
126 else:
127 self.device.status = 4
128 self.device.save()
129 return False
130 except Exception as e:
131 if 'No route to host' not in str(e):
132 self.device.status = 4
133 else:
134 self.device.status = 0
135 #self.message = 'Pedestal Dev stop: {}'.format(str(e))
136 self.message = "Pedestal Dev can't start, please check network/device connection or IP address/port configuration"
137 self.device.save()
138 return False
139
140 return True
141
142 def start_device(self):
143
144 try:
145 pedestal = PedestalDevConfiguration.objects.get(pk=self)
146 print(pedestal)
147 pedestal_axis = pedestal.get_axis_display()
148 print(pedestal_axis)
149
150 if pedestal.mode=='SPD':
151 data = {'axis': pedestal_axis, 'speed': pedestal.speed}
152 json_data = json.dumps(data)
153
154 elif pedestal.mode=='POS':
155 data = {'axis': pedestal_axis, 'speed': pedestal.position}
156 json_data = json.dumps(data)
157
158 print(json_data)
159 base64_mode = base64.urlsafe_b64encode(json_data.encode('ascii'))
160 mode_url = self.device.url() + "aspeed?params="
161
162 complete_url = mode_url + base64_mode.decode('ascii')
163 print(complete_url)
164
165 #time.sleep(10)
166 r = requests.get(complete_url)
167
168 if r:
169 self.device.status = 3
170 self.device.save()
171 self.message = 'Pedestal Dev configured and started'
172 else:
173 return False
174 except Exception as e:
175 if 'No route to host' not in str(e):
176 self.device.status = 4
177 else:
178 self.device.status = 0
179 #self.message = 'Pedestal Dev start: {}'.format(str(e))
180 self.message = "Pedestal Dev can't start, please check network/device connection or IP address/port configuration"
181 self.device.save()
182 return False
183
184 return True
185
186 #def write_device(self, raw=False):
187
188 if not raw:
189 clock = RCClock.objects.get(rc_configuration=self)
190 print(clock)
191 if clock.mode:
192 data = {'default': clock.frequency}
193 else:
194 data = {'manual': [clock.multiplier, clock.divisor, clock.reference]}
195 payload = self.request('setfreq', 'post', data=json.dumps(data))
196 print(payload)
197 if payload['command'] != 'ok':
198 self.message = 'Pedestal Dev write: {}'.format(payload['command'])
199 else:
200 self.message = payload['programming']
201 if payload['programming'] == 'fail':
202 self.message = 'Pedestal Dev write: error programming CGS chip'
203
204 values = []
205 for pulse, delay in zip(self.get_pulses(), self.get_delays()):
206 while delay>65536:
207 values.append((pulse, 65535))
208 delay -= 65536
209 values.append((pulse, delay-1))
210 data = bytearray()
211 #reset
212 data.extend((128, 0))
213 #disable
214 data.extend((129, 0))
215 #SW switch
216 if self.control_sw:
217 data.extend((130, 2))
218 else:
219 data.extend((130, 0))
220 #divider
221 data.extend((131, self.clock_divider-1))
222 #enable writing
223 data.extend((139, 62))
224
225 last = 0
226 for tup in values:
227 vals = pack('<HH', last^tup[0], tup[1])
228 last = tup[0]
229 data.extend((133, vals[1], 132, vals[0], 133, vals[3], 132, vals[2]))
230
231 #enable
232 data.extend((129, 1))
233
234 if raw:
235 return b64encode(data)
236
237 try:
238 payload = self.request('stop', 'post')
239 payload = self.request('reset', 'post')
240 #payload = self.request('divider', 'post', data={'divider': self.clock_divider-1})
241 #payload = self.request('write', 'post', data=b64encode(bytearray((139, 62))), timeout=20)
242 n = len(data)
243 x = 0
244 #while x < n:
245 payload = self.request('write', 'post', data=b64encode(data))
246 # x += 1024
247
248 if payload['write']=='ok':
249 self.device.status = 3
250 self.device.save()
251 self.message = 'Pedestal Dev configured and started'
252 else:
253 self.device.status = 1
254 self.device.save()
255 self.message = 'Pedestal Dev write: {}'.format(payload['write'])
256 return False
257
258 #payload = self.request('start', 'post')
259
260 except Exception as e:
261 if 'No route to host' not in str(e):
262 self.device.status = 4
263 else:
264 self.device.status = 0
265 self.message = 'Pedestal Dev write: {}'.format(str(e))
266 self.device.save()
267 return False
268
269 return True
270
271
272 def get_absolute_url_import(self):
273 return reverse('url_import_pedestal_dev_conf', args=[str(self.id)])
@@ -0,0 +1,2
1 .bk-bs-container{margin-right:auto;margin-left:auto;padding-left:15px;padding-right:15px}@media(min-width:768px){.bk-bs-container{width:750px}}@media(min-width:992px){.bk-bs-container{width:970px}}@media(min-width:1200px){.bk-bs-container{width:1170px}}.bk-bs-container-fluid{margin-right:auto;margin-left:auto;padding-left:15px;padding-right:15px}.bk-bs-row{margin-left:-15px;margin-right:-15px}.bk-bs-col-xs-1,.bk-bs-col-sm-1,.bk-bs-col-md-1,.bk-bs-col-lg-1,.bk-bs-col-xs-2,.bk-bs-col-sm-2,.bk-bs-col-md-2,.bk-bs-col-lg-2,.bk-bs-col-xs-3,.bk-bs-col-sm-3,.bk-bs-col-md-3,.bk-bs-col-lg-3,.bk-bs-col-xs-4,.bk-bs-col-sm-4,.bk-bs-col-md-4,.bk-bs-col-lg-4,.bk-bs-col-xs-5,.bk-bs-col-sm-5,.bk-bs-col-md-5,.bk-bs-col-lg-5,.bk-bs-col-xs-6,.bk-bs-col-sm-6,.bk-bs-col-md-6,.bk-bs-col-lg-6,.bk-bs-col-xs-7,.bk-bs-col-sm-7,.bk-bs-col-md-7,.bk-bs-col-lg-7,.bk-bs-col-xs-8,.bk-bs-col-sm-8,.bk-bs-col-md-8,.bk-bs-col-lg-8,.bk-bs-col-xs-9,.bk-bs-col-sm-9,.bk-bs-col-md-9,.bk-bs-col-lg-9,.bk-bs-col-xs-10,.bk-bs-col-sm-10,.bk-bs-col-md-10,.bk-bs-col-lg-10,.bk-bs-col-xs-11,.bk-bs-col-sm-11,.bk-bs-col-md-11,.bk-bs-col-lg-11,.bk-bs-col-xs-12,.bk-bs-col-sm-12,.bk-bs-col-md-12,.bk-bs-col-lg-12{position:relative;min-height:1px;padding-left:15px;padding-right:15px}.bk-bs-col-xs-1,.bk-bs-col-xs-2,.bk-bs-col-xs-3,.bk-bs-col-xs-4,.bk-bs-col-xs-5,.bk-bs-col-xs-6,.bk-bs-col-xs-7,.bk-bs-col-xs-8,.bk-bs-col-xs-9,.bk-bs-col-xs-10,.bk-bs-col-xs-11,.bk-bs-col-xs-12{float:left}.bk-bs-col-xs-12{width:100%}.bk-bs-col-xs-11{width:91.66666667%}.bk-bs-col-xs-10{width:83.33333333%}.bk-bs-col-xs-9{width:75%}.bk-bs-col-xs-8{width:66.66666667%}.bk-bs-col-xs-7{width:58.33333333%}.bk-bs-col-xs-6{width:50%}.bk-bs-col-xs-5{width:41.66666667%}.bk-bs-col-xs-4{width:33.33333333%}.bk-bs-col-xs-3{width:25%}.bk-bs-col-xs-2{width:16.66666667%}.bk-bs-col-xs-1{width:8.33333333%}.bk-bs-col-xs-pull-12{right:100%}.bk-bs-col-xs-pull-11{right:91.66666667%}.bk-bs-col-xs-pull-10{right:83.33333333%}.bk-bs-col-xs-pull-9{right:75%}.bk-bs-col-xs-pull-8{right:66.66666667%}.bk-bs-col-xs-pull-7{right:58.33333333%}.bk-bs-col-xs-pull-6{right:50%}.bk-bs-col-xs-pull-5{right:41.66666667%}.bk-bs-col-xs-pull-4{right:33.33333333%}.bk-bs-col-xs-pull-3{right:25%}.bk-bs-col-xs-pull-2{right:16.66666667%}.bk-bs-col-xs-pull-1{right:8.33333333%}.bk-bs-col-xs-pull-0{right:0}.bk-bs-col-xs-push-12{left:100%}.bk-bs-col-xs-push-11{left:91.66666667%}.bk-bs-col-xs-push-10{left:83.33333333%}.bk-bs-col-xs-push-9{left:75%}.bk-bs-col-xs-push-8{left:66.66666667%}.bk-bs-col-xs-push-7{left:58.33333333%}.bk-bs-col-xs-push-6{left:50%}.bk-bs-col-xs-push-5{left:41.66666667%}.bk-bs-col-xs-push-4{left:33.33333333%}.bk-bs-col-xs-push-3{left:25%}.bk-bs-col-xs-push-2{left:16.66666667%}.bk-bs-col-xs-push-1{left:8.33333333%}.bk-bs-col-xs-push-0{left:0}.bk-bs-col-xs-offset-12{margin-left:100%}.bk-bs-col-xs-offset-11{margin-left:91.66666667%}.bk-bs-col-xs-offset-10{margin-left:83.33333333%}.bk-bs-col-xs-offset-9{margin-left:75%}.bk-bs-col-xs-offset-8{margin-left:66.66666667%}.bk-bs-col-xs-offset-7{margin-left:58.33333333%}.bk-bs-col-xs-offset-6{margin-left:50%}.bk-bs-col-xs-offset-5{margin-left:41.66666667%}.bk-bs-col-xs-offset-4{margin-left:33.33333333%}.bk-bs-col-xs-offset-3{margin-left:25%}.bk-bs-col-xs-offset-2{margin-left:16.66666667%}.bk-bs-col-xs-offset-1{margin-left:8.33333333%}.bk-bs-col-xs-offset-0{margin-left:0}@media(min-width:768px){.bk-bs-col-sm-1,.bk-bs-col-sm-2,.bk-bs-col-sm-3,.bk-bs-col-sm-4,.bk-bs-col-sm-5,.bk-bs-col-sm-6,.bk-bs-col-sm-7,.bk-bs-col-sm-8,.bk-bs-col-sm-9,.bk-bs-col-sm-10,.bk-bs-col-sm-11,.bk-bs-col-sm-12{float:left}.bk-bs-col-sm-12{width:100%}.bk-bs-col-sm-11{width:91.66666667%}.bk-bs-col-sm-10{width:83.33333333%}.bk-bs-col-sm-9{width:75%}.bk-bs-col-sm-8{width:66.66666667%}.bk-bs-col-sm-7{width:58.33333333%}.bk-bs-col-sm-6{width:50%}.bk-bs-col-sm-5{width:41.66666667%}.bk-bs-col-sm-4{width:33.33333333%}.bk-bs-col-sm-3{width:25%}.bk-bs-col-sm-2{width:16.66666667%}.bk-bs-col-sm-1{width:8.33333333%}.bk-bs-col-sm-pull-12{right:100%}.bk-bs-col-sm-pull-11{right:91.66666667%}.bk-bs-col-sm-pull-10{right:83.33333333%}.bk-bs-col-sm-pull-9{right:75%}.bk-bs-col-sm-pull-8{right:66.66666667%}.bk-bs-col-sm-pull-7{right:58.33333333%}.bk-bs-col-sm-pull-6{right:50%}.bk-bs-col-sm-pull-5{right:41.66666667%}.bk-bs-col-sm-pull-4{right:33.33333333%}.bk-bs-col-sm-pull-3{right:25%}.bk-bs-col-sm-pull-2{right:16.66666667%}.bk-bs-col-sm-pull-1{right:8.33333333%}.bk-bs-col-sm-pull-0{right:0}.bk-bs-col-sm-push-12{left:100%}.bk-bs-col-sm-push-11{left:91.66666667%}.bk-bs-col-sm-push-10{left:83.33333333%}.bk-bs-col-sm-push-9{left:75%}.bk-bs-col-sm-push-8{left:66.66666667%}.bk-bs-col-sm-push-7{left:58.33333333%}.bk-bs-col-sm-push-6{left:50%}.bk-bs-col-sm-push-5{left:41.66666667%}.bk-bs-col-sm-push-4{left:33.33333333%}.bk-bs-col-sm-push-3{left:25%}.bk-bs-col-sm-push-2{left:16.66666667%}.bk-bs-col-sm-push-1{left:8.33333333%}.bk-bs-col-sm-push-0{left:0}.bk-bs-col-sm-offset-12{margin-left:100%}.bk-bs-col-sm-offset-11{margin-left:91.66666667%}.bk-bs-col-sm-offset-10{margin-left:83.33333333%}.bk-bs-col-sm-offset-9{margin-left:75%}.bk-bs-col-sm-offset-8{margin-left:66.66666667%}.bk-bs-col-sm-offset-7{margin-left:58.33333333%}.bk-bs-col-sm-offset-6{margin-left:50%}.bk-bs-col-sm-offset-5{margin-left:41.66666667%}.bk-bs-col-sm-offset-4{margin-left:33.33333333%}.bk-bs-col-sm-offset-3{margin-left:25%}.bk-bs-col-sm-offset-2{margin-left:16.66666667%}.bk-bs-col-sm-offset-1{margin-left:8.33333333%}.bk-bs-col-sm-offset-0{margin-left:0}}@media(min-width:992px){.bk-bs-col-md-1,.bk-bs-col-md-2,.bk-bs-col-md-3,.bk-bs-col-md-4,.bk-bs-col-md-5,.bk-bs-col-md-6,.bk-bs-col-md-7,.bk-bs-col-md-8,.bk-bs-col-md-9,.bk-bs-col-md-10,.bk-bs-col-md-11,.bk-bs-col-md-12{float:left}.bk-bs-col-md-12{width:100%}.bk-bs-col-md-11{width:91.66666667%}.bk-bs-col-md-10{width:83.33333333%}.bk-bs-col-md-9{width:75%}.bk-bs-col-md-8{width:66.66666667%}.bk-bs-col-md-7{width:58.33333333%}.bk-bs-col-md-6{width:50%}.bk-bs-col-md-5{width:41.66666667%}.bk-bs-col-md-4{width:33.33333333%}.bk-bs-col-md-3{width:25%}.bk-bs-col-md-2{width:16.66666667%}.bk-bs-col-md-1{width:8.33333333%}.bk-bs-col-md-pull-12{right:100%}.bk-bs-col-md-pull-11{right:91.66666667%}.bk-bs-col-md-pull-10{right:83.33333333%}.bk-bs-col-md-pull-9{right:75%}.bk-bs-col-md-pull-8{right:66.66666667%}.bk-bs-col-md-pull-7{right:58.33333333%}.bk-bs-col-md-pull-6{right:50%}.bk-bs-col-md-pull-5{right:41.66666667%}.bk-bs-col-md-pull-4{right:33.33333333%}.bk-bs-col-md-pull-3{right:25%}.bk-bs-col-md-pull-2{right:16.66666667%}.bk-bs-col-md-pull-1{right:8.33333333%}.bk-bs-col-md-pull-0{right:0}.bk-bs-col-md-push-12{left:100%}.bk-bs-col-md-push-11{left:91.66666667%}.bk-bs-col-md-push-10{left:83.33333333%}.bk-bs-col-md-push-9{left:75%}.bk-bs-col-md-push-8{left:66.66666667%}.bk-bs-col-md-push-7{left:58.33333333%}.bk-bs-col-md-push-6{left:50%}.bk-bs-col-md-push-5{left:41.66666667%}.bk-bs-col-md-push-4{left:33.33333333%}.bk-bs-col-md-push-3{left:25%}.bk-bs-col-md-push-2{left:16.66666667%}.bk-bs-col-md-push-1{left:8.33333333%}.bk-bs-col-md-push-0{left:0}.bk-bs-col-md-offset-12{margin-left:100%}.bk-bs-col-md-offset-11{margin-left:91.66666667%}.bk-bs-col-md-offset-10{margin-left:83.33333333%}.bk-bs-col-md-offset-9{margin-left:75%}.bk-bs-col-md-offset-8{margin-left:66.66666667%}.bk-bs-col-md-offset-7{margin-left:58.33333333%}.bk-bs-col-md-offset-6{margin-left:50%}.bk-bs-col-md-offset-5{margin-left:41.66666667%}.bk-bs-col-md-offset-4{margin-left:33.33333333%}.bk-bs-col-md-offset-3{margin-left:25%}.bk-bs-col-md-offset-2{margin-left:16.66666667%}.bk-bs-col-md-offset-1{margin-left:8.33333333%}.bk-bs-col-md-offset-0{margin-left:0}}@media(min-width:1200px){.bk-bs-col-lg-1,.bk-bs-col-lg-2,.bk-bs-col-lg-3,.bk-bs-col-lg-4,.bk-bs-col-lg-5,.bk-bs-col-lg-6,.bk-bs-col-lg-7,.bk-bs-col-lg-8,.bk-bs-col-lg-9,.bk-bs-col-lg-10,.bk-bs-col-lg-11,.bk-bs-col-lg-12{float:left}.bk-bs-col-lg-12{width:100%}.bk-bs-col-lg-11{width:91.66666667%}.bk-bs-col-lg-10{width:83.33333333%}.bk-bs-col-lg-9{width:75%}.bk-bs-col-lg-8{width:66.66666667%}.bk-bs-col-lg-7{width:58.33333333%}.bk-bs-col-lg-6{width:50%}.bk-bs-col-lg-5{width:41.66666667%}.bk-bs-col-lg-4{width:33.33333333%}.bk-bs-col-lg-3{width:25%}.bk-bs-col-lg-2{width:16.66666667%}.bk-bs-col-lg-1{width:8.33333333%}.bk-bs-col-lg-pull-12{right:100%}.bk-bs-col-lg-pull-11{right:91.66666667%}.bk-bs-col-lg-pull-10{right:83.33333333%}.bk-bs-col-lg-pull-9{right:75%}.bk-bs-col-lg-pull-8{right:66.66666667%}.bk-bs-col-lg-pull-7{right:58.33333333%}.bk-bs-col-lg-pull-6{right:50%}.bk-bs-col-lg-pull-5{right:41.66666667%}.bk-bs-col-lg-pull-4{right:33.33333333%}.bk-bs-col-lg-pull-3{right:25%}.bk-bs-col-lg-pull-2{right:16.66666667%}.bk-bs-col-lg-pull-1{right:8.33333333%}.bk-bs-col-lg-pull-0{right:0}.bk-bs-col-lg-push-12{left:100%}.bk-bs-col-lg-push-11{left:91.66666667%}.bk-bs-col-lg-push-10{left:83.33333333%}.bk-bs-col-lg-push-9{left:75%}.bk-bs-col-lg-push-8{left:66.66666667%}.bk-bs-col-lg-push-7{left:58.33333333%}.bk-bs-col-lg-push-6{left:50%}.bk-bs-col-lg-push-5{left:41.66666667%}.bk-bs-col-lg-push-4{left:33.33333333%}.bk-bs-col-lg-push-3{left:25%}.bk-bs-col-lg-push-2{left:16.66666667%}.bk-bs-col-lg-push-1{left:8.33333333%}.bk-bs-col-lg-push-0{left:0}.bk-bs-col-lg-offset-12{margin-left:100%}.bk-bs-col-lg-offset-11{margin-left:91.66666667%}.bk-bs-col-lg-offset-10{margin-left:83.33333333%}.bk-bs-col-lg-offset-9{margin-left:75%}.bk-bs-col-lg-offset-8{margin-left:66.66666667%}.bk-bs-col-lg-offset-7{margin-left:58.33333333%}.bk-bs-col-lg-offset-6{margin-left:50%}.bk-bs-col-lg-offset-5{margin-left:41.66666667%}.bk-bs-col-lg-offset-4{margin-left:33.33333333%}.bk-bs-col-lg-offset-3{margin-left:25%}.bk-bs-col-lg-offset-2{margin-left:16.66666667%}.bk-bs-col-lg-offset-1{margin-left:8.33333333%}.bk-bs-col-lg-offset-0{margin-left:0}}table{max-width:100%;background-color:transparent}th{text-align:left}.bk-bs-table{width:100%;margin-bottom:20px}.bk-bs-table>thead>tr>th,.bk-bs-table>tbody>tr>th,.bk-bs-table>tfoot>tr>th,.bk-bs-table>thead>tr>td,.bk-bs-table>tbody>tr>td,.bk-bs-table>tfoot>tr>td{padding:8px;line-height:1.42857143;vertical-align:top;border-top:1px solid #ddd}.bk-bs-table>thead>tr>th{vertical-align:bottom;border-bottom:2px solid #ddd}.bk-bs-table>caption+thead>tr:first-child>th,.bk-bs-table>colgroup+thead>tr:first-child>th,.bk-bs-table>thead:first-child>tr:first-child>th,.bk-bs-table>caption+thead>tr:first-child>td,.bk-bs-table>colgroup+thead>tr:first-child>td,.bk-bs-table>thead:first-child>tr:first-child>td{border-top:0}.bk-bs-table>tbody+tbody{border-top:2px solid #ddd}.bk-bs-table .bk-bs-table{background-color:#fff}.bk-bs-table-condensed>thead>tr>th,.bk-bs-table-condensed>tbody>tr>th,.bk-bs-table-condensed>tfoot>tr>th,.bk-bs-table-condensed>thead>tr>td,.bk-bs-table-condensed>tbody>tr>td,.bk-bs-table-condensed>tfoot>tr>td{padding:5px}.bk-bs-table-bordered{border:1px solid #ddd}.bk-bs-table-bordered>thead>tr>th,.bk-bs-table-bordered>tbody>tr>th,.bk-bs-table-bordered>tfoot>tr>th,.bk-bs-table-bordered>thead>tr>td,.bk-bs-table-bordered>tbody>tr>td,.bk-bs-table-bordered>tfoot>tr>td{border:1px solid #ddd}.bk-bs-table-bordered>thead>tr>th,.bk-bs-table-bordered>thead>tr>td{border-bottom-width:2px}.bk-bs-table-striped>tbody>tr:nth-child(odd)>td,.bk-bs-table-striped>tbody>tr:nth-child(odd)>th{background-color:#f9f9f9}.bk-bs-table-hover>tbody>tr:hover>td,.bk-bs-table-hover>tbody>tr:hover>th{background-color:#f5f5f5}table col[class*="col-"]{position:static;float:none;display:table-column}table td[class*="col-"],table th[class*="col-"]{position:static;float:none;display:table-cell}.bk-bs-table>thead>tr>td.active,.bk-bs-table>tbody>tr>td.active,.bk-bs-table>tfoot>tr>td.active,.bk-bs-table>thead>tr>th.active,.bk-bs-table>tbody>tr>th.active,.bk-bs-table>tfoot>tr>th.active,.bk-bs-table>thead>tr.active>td,.bk-bs-table>tbody>tr.active>td,.bk-bs-table>tfoot>tr.active>td,.bk-bs-table>thead>tr.active>th,.bk-bs-table>tbody>tr.active>th,.bk-bs-table>tfoot>tr.active>th{background-color:#f5f5f5}.bk-bs-table-hover>tbody>tr>td.active:hover,.bk-bs-table-hover>tbody>tr>th.active:hover,.bk-bs-table-hover>tbody>tr.active:hover>td,.bk-bs-table-hover>tbody>tr.active:hover>th{background-color:#e8e8e8}.bk-bs-table>thead>tr>td.success,.bk-bs-table>tbody>tr>td.success,.bk-bs-table>tfoot>tr>td.success,.bk-bs-table>thead>tr>th.success,.bk-bs-table>tbody>tr>th.success,.bk-bs-table>tfoot>tr>th.success,.bk-bs-table>thead>tr.success>td,.bk-bs-table>tbody>tr.success>td,.bk-bs-table>tfoot>tr.success>td,.bk-bs-table>thead>tr.success>th,.bk-bs-table>tbody>tr.success>th,.bk-bs-table>tfoot>tr.success>th{background-color:#dff0d8}.bk-bs-table-hover>tbody>tr>td.success:hover,.bk-bs-table-hover>tbody>tr>th.success:hover,.bk-bs-table-hover>tbody>tr.success:hover>td,.bk-bs-table-hover>tbody>tr.success:hover>th{background-color:#d0e9c6}.bk-bs-table>thead>tr>td.info,.bk-bs-table>tbody>tr>td.info,.bk-bs-table>tfoot>tr>td.info,.bk-bs-table>thead>tr>th.info,.bk-bs-table>tbody>tr>th.info,.bk-bs-table>tfoot>tr>th.info,.bk-bs-table>thead>tr.info>td,.bk-bs-table>tbody>tr.info>td,.bk-bs-table>tfoot>tr.info>td,.bk-bs-table>thead>tr.info>th,.bk-bs-table>tbody>tr.info>th,.bk-bs-table>tfoot>tr.info>th{background-color:#d9edf7}.bk-bs-table-hover>tbody>tr>td.info:hover,.bk-bs-table-hover>tbody>tr>th.info:hover,.bk-bs-table-hover>tbody>tr.info:hover>td,.bk-bs-table-hover>tbody>tr.info:hover>th{background-color:#c4e3f3}.bk-bs-table>thead>tr>td.warning,.bk-bs-table>tbody>tr>td.warning,.bk-bs-table>tfoot>tr>td.warning,.bk-bs-table>thead>tr>th.warning,.bk-bs-table>tbody>tr>th.warning,.bk-bs-table>tfoot>tr>th.warning,.bk-bs-table>thead>tr.warning>td,.bk-bs-table>tbody>tr.warning>td,.bk-bs-table>tfoot>tr.warning>td,.bk-bs-table>thead>tr.warning>th,.bk-bs-table>tbody>tr.warning>th,.bk-bs-table>tfoot>tr.warning>th{background-color:#fcf8e3}.bk-bs-table-hover>tbody>tr>td.warning:hover,.bk-bs-table-hover>tbody>tr>th.warning:hover,.bk-bs-table-hover>tbody>tr.warning:hover>td,.bk-bs-table-hover>tbody>tr.warning:hover>th{background-color:#faf2cc}.bk-bs-table>thead>tr>td.danger,.bk-bs-table>tbody>tr>td.danger,.bk-bs-table>tfoot>tr>td.danger,.bk-bs-table>thead>tr>th.danger,.bk-bs-table>tbody>tr>th.danger,.bk-bs-table>tfoot>tr>th.danger,.bk-bs-table>thead>tr.danger>td,.bk-bs-table>tbody>tr.danger>td,.bk-bs-table>tfoot>tr.danger>td,.bk-bs-table>thead>tr.danger>th,.bk-bs-table>tbody>tr.danger>th,.bk-bs-table>tfoot>tr.danger>th{background-color:#f2dede}.bk-bs-table-hover>tbody>tr>td.danger:hover,.bk-bs-table-hover>tbody>tr>th.danger:hover,.bk-bs-table-hover>tbody>tr.danger:hover>td,.bk-bs-table-hover>tbody>tr.danger:hover>th{background-color:#ebcccc}@media(max-width:767px){.bk-bs-table-responsive{width:100%;margin-bottom:15px;overflow-y:hidden;overflow-x:scroll;-ms-overflow-style:-ms-autohiding-scrollbar;border:1px solid #ddd;-webkit-overflow-scrolling:touch}.bk-bs-table-responsive>.bk-bs-table{margin-bottom:0}.bk-bs-table-responsive>.bk-bs-table>thead>tr>th,.bk-bs-table-responsive>.bk-bs-table>tbody>tr>th,.bk-bs-table-responsive>.bk-bs-table>tfoot>tr>th,.bk-bs-table-responsive>.bk-bs-table>thead>tr>td,.bk-bs-table-responsive>.bk-bs-table>tbody>tr>td,.bk-bs-table-responsive>.bk-bs-table>tfoot>tr>td{white-space:nowrap}.bk-bs-table-responsive>.bk-bs-table-bordered{border:0}.bk-bs-table-responsive>.bk-bs-table-bordered>thead>tr>th:first-child,.bk-bs-table-responsive>.bk-bs-table-bordered>tbody>tr>th:first-child,.bk-bs-table-responsive>.bk-bs-table-bordered>tfoot>tr>th:first-child,.bk-bs-table-responsive>.bk-bs-table-bordered>thead>tr>td:first-child,.bk-bs-table-responsive>.bk-bs-table-bordered>tbody>tr>td:first-child,.bk-bs-table-responsive>.bk-bs-table-bordered>tfoot>tr>td:first-child{border-left:0}.bk-bs-table-responsive>.bk-bs-table-bordered>thead>tr>th:last-child,.bk-bs-table-responsive>.bk-bs-table-bordered>tbody>tr>th:last-child,.bk-bs-table-responsive>.bk-bs-table-bordered>tfoot>tr>th:last-child,.bk-bs-table-responsive>.bk-bs-table-bordered>thead>tr>td:last-child,.bk-bs-table-responsive>.bk-bs-table-bordered>tbody>tr>td:last-child,.bk-bs-table-responsive>.bk-bs-table-bordered>tfoot>tr>td:last-child{border-right:0}.bk-bs-table-responsive>.bk-bs-table-bordered>tbody>tr:last-child>th,.bk-bs-table-responsive>.bk-bs-table-bordered>tfoot>tr:last-child>th,.bk-bs-table-responsive>.bk-bs-table-bordered>tbody>tr:last-child>td,.bk-bs-table-responsive>.bk-bs-table-bordered>tfoot>tr:last-child>td{border-bottom:0}}fieldset{padding:0;margin:0;border:0;min-width:0}legend{display:block;width:100%;padding:0;margin-bottom:20px;font-size:21px;line-height:inherit;color:#333;border:0;border-bottom:1px solid #e5e5e5}label{display:inline-block;margin-bottom:5px;font-weight:bold}input[type="search"]{-webkit-box-sizing:border-box;-moz-box-sizing:border-box;box-sizing:border-box}input[type="radio"],input[type="checkbox"]{margin:4px 0 0;margin-top:1px \9;line-height:normal}input[type="file"]{display:block}input[type="range"]{display:block;width:100%}select[multiple],select[size]{height:auto}input[type="file"]:focus,input[type="radio"]:focus,input[type="checkbox"]:focus{outline:thin dotted;outline:5px auto -webkit-focus-ring-color;outline-offset:-2px}output{display:block;padding-top:7px;font-size:14px;line-height:1.42857143;color:#555}.bk-bs-form-control{display:block;width:100%;height:34px;padding:6px 12px;font-size:14px;line-height:1.42857143;color:#555;background-color:#fff;background-image:none;border:1px solid #ccc;border-radius:4px;-webkit-box-shadow:inset 0 1px 1px rgba(0,0,0,0.075);box-shadow:inset 0 1px 1px rgba(0,0,0,0.075);-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}.bk-bs-form-control:focus{border-color:#66afe9;outline:0;-webkit-box-shadow:inset 0 1px 1px rgba(0,0,0,.075),0 0 8px rgba(102,175,233,0.6);box-shadow:inset 0 1px 1px rgba(0,0,0,.075),0 0 8px rgba(102,175,233,0.6)}.bk-bs-form-control::-moz-placeholder{color:#999;opacity:1}.bk-bs-form-control:-ms-input-placeholder{color:#999}.bk-bs-form-control::-webkit-input-placeholder{color:#999}.bk-bs-form-control[disabled],.bk-bs-form-control[readonly],fieldset[disabled] .bk-bs-form-control{cursor:not-allowed;background-color:#eee;opacity:1}textarea.bk-bs-form-control{height:auto}input[type="search"]{-webkit-appearance:none}input[type="date"]{line-height:34px}.bk-bs-form-group{margin-bottom:15px}.bk-bs-radio,.bk-bs-checkbox{display:block;min-height:20px;margin-top:10px;margin-bottom:10px;padding-left:20px}.bk-bs-radio label,.bk-bs-checkbox label{display:inline;font-weight:normal;cursor:pointer}.bk-bs-radio input[type="radio"],.bk-bs-radio-inline input[type="radio"],.bk-bs-checkbox input[type="checkbox"],.bk-bs-checkbox-inline input[type="checkbox"]{float:left;margin-left:-20px}.bk-bs-radio+.bk-bs-radio,.bk-bs-checkbox+.bk-bs-checkbox{margin-top:-5px}.bk-bs-radio-inline,.bk-bs-checkbox-inline{display:inline-block;padding-left:20px;margin-bottom:0;vertical-align:middle;font-weight:normal;cursor:pointer}.bk-bs-radio-inline+.bk-bs-radio-inline,.bk-bs-checkbox-inline+.bk-bs-checkbox-inline{margin-top:0;margin-left:10px}input[type="radio"][disabled],input[type="checkbox"][disabled],.bk-bs-radio[disabled],.bk-bs-radio-inline[disabled],.bk-bs-checkbox[disabled],.bk-bs-checkbox-inline[disabled],fieldset[disabled] input[type="radio"],fieldset[disabled] input[type="checkbox"],fieldset[disabled] .bk-bs-radio,fieldset[disabled] .bk-bs-radio-inline,fieldset[disabled] .bk-bs-checkbox,fieldset[disabled] .bk-bs-checkbox-inline{cursor:not-allowed}.bk-bs-input-sm{height:30px;padding:5px 10px;font-size:12px;line-height:1.5;border-radius:3px}select.bk-bs-input-sm{height:30px;line-height:30px}textarea.bk-bs-input-sm,select[multiple].bk-bs-input-sm{height:auto}.bk-bs-input-lg{height:46px;padding:10px 16px;font-size:18px;line-height:1.33;border-radius:6px}select.bk-bs-input-lg{height:46px;line-height:46px}textarea.bk-bs-input-lg,select[multiple].bk-bs-input-lg{height:auto}.bk-bs-has-feedback{position:relative}.bk-bs-has-feedback .bk-bs-form-control{padding-right:42.5px}.bk-bs-has-feedback .bk-bs-form-control-feedback{position:absolute;top:25px;right:0;display:block;width:34px;height:34px;line-height:34px;text-align:center}.bk-bs-has-success .bk-bs-help-block,.bk-bs-has-success .bk-bs-control-label,.bk-bs-has-success .bk-bs-radio,.bk-bs-has-success .bk-bs-checkbox,.bk-bs-has-success .bk-bs-radio-inline,.bk-bs-has-success .bk-bs-checkbox-inline{color:#3c763d}.bk-bs-has-success .bk-bs-form-control{border-color:#3c763d;-webkit-box-shadow:inset 0 1px 1px rgba(0,0,0,0.075);box-shadow:inset 0 1px 1px rgba(0,0,0,0.075)}.bk-bs-has-success .bk-bs-form-control:focus{border-color:#2b542c;-webkit-box-shadow:inset 0 1px 1px rgba(0,0,0,0.075),0 0 6px #67b168;box-shadow:inset 0 1px 1px rgba(0,0,0,0.075),0 0 6px #67b168}.bk-bs-has-success .bk-bs-input-group-addon{color:#3c763d;border-color:#3c763d;background-color:#dff0d8}.bk-bs-has-success .bk-bs-form-control-feedback{color:#3c763d}.bk-bs-has-warning .bk-bs-help-block,.bk-bs-has-warning .bk-bs-control-label,.bk-bs-has-warning .bk-bs-radio,.bk-bs-has-warning .bk-bs-checkbox,.bk-bs-has-warning .bk-bs-radio-inline,.bk-bs-has-warning .bk-bs-checkbox-inline{color:#8a6d3b}.bk-bs-has-warning .bk-bs-form-control{border-color:#8a6d3b;-webkit-box-shadow:inset 0 1px 1px rgba(0,0,0,0.075);box-shadow:inset 0 1px 1px rgba(0,0,0,0.075)}.bk-bs-has-warning .bk-bs-form-control:focus{border-color:#66512c;-webkit-box-shadow:inset 0 1px 1px rgba(0,0,0,0.075),0 0 6px #c0a16b;box-shadow:inset 0 1px 1px rgba(0,0,0,0.075),0 0 6px #c0a16b}.bk-bs-has-warning .bk-bs-input-group-addon{color:#8a6d3b;border-color:#8a6d3b;background-color:#fcf8e3}.bk-bs-has-warning .bk-bs-form-control-feedback{color:#8a6d3b}.bk-bs-has-error .bk-bs-help-block,.bk-bs-has-error .bk-bs-control-label,.bk-bs-has-error .bk-bs-radio,.bk-bs-has-error .bk-bs-checkbox,.bk-bs-has-error .bk-bs-radio-inline,.bk-bs-has-error .bk-bs-checkbox-inline{color:#a94442}.bk-bs-has-error .bk-bs-form-control{border-color:#a94442;-webkit-box-shadow:inset 0 1px 1px rgba(0,0,0,0.075);box-shadow:inset 0 1px 1px rgba(0,0,0,0.075)}.bk-bs-has-error .bk-bs-form-control:focus{border-color:#843534;-webkit-box-shadow:inset 0 1px 1px rgba(0,0,0,0.075),0 0 6px #ce8483;box-shadow:inset 0 1px 1px rgba(0,0,0,0.075),0 0 6px #ce8483}.bk-bs-has-error .bk-bs-input-group-addon{color:#a94442;border-color:#a94442;background-color:#f2dede}.bk-bs-has-error .bk-bs-form-control-feedback{color:#a94442}.bk-bs-form-control-static{margin-bottom:0}.bk-bs-help-block{display:block;margin-top:5px;margin-bottom:10px;color:#737373}@media(min-width:768px){.bk-bs-form-inline .bk-bs-form-group{display:inline-block;margin-bottom:0;vertical-align:middle}.bk-bs-form-inline .bk-bs-form-control{display:inline-block;width:auto;vertical-align:middle}.bk-bs-form-inline .bk-bs-input-group>.bk-bs-form-control{width:100%}.bk-bs-form-inline .bk-bs-control-label{margin-bottom:0;vertical-align:middle}.bk-bs-form-inline .bk-bs-radio,.bk-bs-form-inline .bk-bs-checkbox{display:inline-block;margin-top:0;margin-bottom:0;padding-left:0;vertical-align:middle}.bk-bs-form-inline .bk-bs-radio input[type="radio"],.bk-bs-form-inline .bk-bs-checkbox input[type="checkbox"]{float:none;margin-left:0}.bk-bs-form-inline .bk-bs-has-feedback .bk-bs-form-control-feedback{top:0}}.bk-bs-form-horizontal .bk-bs-control-label,.bk-bs-form-horizontal .bk-bs-radio,.bk-bs-form-horizontal .bk-bs-checkbox,.bk-bs-form-horizontal .bk-bs-radio-inline,.bk-bs-form-horizontal .bk-bs-checkbox-inline{margin-top:0;margin-bottom:0;padding-top:7px}.bk-bs-form-horizontal .bk-bs-radio,.bk-bs-form-horizontal .bk-bs-checkbox{min-height:27px}.bk-bs-form-horizontal .bk-bs-form-group{margin-left:-15px;margin-right:-15px}.bk-bs-form-horizontal .bk-bs-form-control-static{padding-top:7px}@media(min-width:768px){.bk-bs-form-horizontal .bk-bs-control-label{text-align:right}}.bk-bs-form-horizontal .bk-bs-has-feedback .bk-bs-form-control-feedback{top:0;right:15px}.bk-bs-btn{display:inline-block;margin-bottom:0;font-weight:normal;text-align:center;vertical-align:middle;cursor:pointer;background-image:none;border:1px solid transparent;white-space:nowrap;padding:6px 12px;font-size:14px;line-height:1.42857143;border-radius:4px;-webkit-user-select:none;-moz-user-select:none;-ms-user-select:none;user-select:none}.bk-bs-btn:focus,.bk-bs-btn:active:focus,.bk-bs-btn.bk-bs-active:focus{outline:thin dotted;outline:5px auto -webkit-focus-ring-color;outline-offset:-2px}.bk-bs-btn:hover,.bk-bs-btn:focus{color:#333;text-decoration:none}.bk-bs-btn:active,.bk-bs-btn.bk-bs-active{outline:0;background-image:none;-webkit-box-shadow:inset 0 3px 5px rgba(0,0,0,0.125);box-shadow:inset 0 3px 5px rgba(0,0,0,0.125)}.bk-bs-btn.bk-bs-disabled,.bk-bs-btn[disabled],fieldset[disabled] .bk-bs-btn{cursor:not-allowed;pointer-events:none;opacity:.65;filter:alpha(opacity=65);-webkit-box-shadow:none;box-shadow:none}.bk-bs-btn-default{color:#333;background-color:#fff;border-color:#ccc}.bk-bs-btn-default:hover,.bk-bs-btn-default:focus,.bk-bs-btn-default:active,.bk-bs-btn-default.bk-bs-active,.bk-bs-open .bk-bs-dropdown-toggle.bk-bs-btn-default{color:#333;background-color:#ebebeb;border-color:#adadad}.bk-bs-btn-default:active,.bk-bs-btn-default.bk-bs-active,.bk-bs-open .bk-bs-dropdown-toggle.bk-bs-btn-default{background-image:none}.bk-bs-btn-default.bk-bs-disabled,.bk-bs-btn-default[disabled],fieldset[disabled] .bk-bs-btn-default,.bk-bs-btn-default.bk-bs-disabled:hover,.bk-bs-btn-default[disabled]:hover,fieldset[disabled] .bk-bs-btn-default:hover,.bk-bs-btn-default.bk-bs-disabled:focus,.bk-bs-btn-default[disabled]:focus,fieldset[disabled] .bk-bs-btn-default:focus,.bk-bs-btn-default.bk-bs-disabled:active,.bk-bs-btn-default[disabled]:active,fieldset[disabled] .bk-bs-btn-default:active,.bk-bs-btn-default.bk-bs-disabled.bk-bs-active,.bk-bs-btn-default[disabled].bk-bs-active,fieldset[disabled] .bk-bs-btn-default.bk-bs-active{background-color:#fff;border-color:#ccc}.bk-bs-btn-default .bk-bs-badge{color:#fff;background-color:#333}.bk-bs-btn-primary{color:#fff;background-color:#428bca;border-color:#357ebd}.bk-bs-btn-primary:hover,.bk-bs-btn-primary:focus,.bk-bs-btn-primary:active,.bk-bs-btn-primary.bk-bs-active,.bk-bs-open .bk-bs-dropdown-toggle.bk-bs-btn-primary{color:#fff;background-color:#3276b1;border-color:#285e8e}.bk-bs-btn-primary:active,.bk-bs-btn-primary.bk-bs-active,.bk-bs-open .bk-bs-dropdown-toggle.bk-bs-btn-primary{background-image:none}.bk-bs-btn-primary.bk-bs-disabled,.bk-bs-btn-primary[disabled],fieldset[disabled] .bk-bs-btn-primary,.bk-bs-btn-primary.bk-bs-disabled:hover,.bk-bs-btn-primary[disabled]:hover,fieldset[disabled] .bk-bs-btn-primary:hover,.bk-bs-btn-primary.bk-bs-disabled:focus,.bk-bs-btn-primary[disabled]:focus,fieldset[disabled] .bk-bs-btn-primary:focus,.bk-bs-btn-primary.bk-bs-disabled:active,.bk-bs-btn-primary[disabled]:active,fieldset[disabled] .bk-bs-btn-primary:active,.bk-bs-btn-primary.bk-bs-disabled.bk-bs-active,.bk-bs-btn-primary[disabled].bk-bs-active,fieldset[disabled] .bk-bs-btn-primary.bk-bs-active{background-color:#428bca;border-color:#357ebd}.bk-bs-btn-primary .bk-bs-badge{color:#428bca;background-color:#fff}.bk-bs-btn-success{color:#fff;background-color:#5cb85c;border-color:#4cae4c}.bk-bs-btn-success:hover,.bk-bs-btn-success:focus,.bk-bs-btn-success:active,.bk-bs-btn-success.bk-bs-active,.bk-bs-open .bk-bs-dropdown-toggle.bk-bs-btn-success{color:#fff;background-color:#47a447;border-color:#398439}.bk-bs-btn-success:active,.bk-bs-btn-success.bk-bs-active,.bk-bs-open .bk-bs-dropdown-toggle.bk-bs-btn-success{background-image:none}.bk-bs-btn-success.bk-bs-disabled,.bk-bs-btn-success[disabled],fieldset[disabled] .bk-bs-btn-success,.bk-bs-btn-success.bk-bs-disabled:hover,.bk-bs-btn-success[disabled]:hover,fieldset[disabled] .bk-bs-btn-success:hover,.bk-bs-btn-success.bk-bs-disabled:focus,.bk-bs-btn-success[disabled]:focus,fieldset[disabled] .bk-bs-btn-success:focus,.bk-bs-btn-success.bk-bs-disabled:active,.bk-bs-btn-success[disabled]:active,fieldset[disabled] .bk-bs-btn-success:active,.bk-bs-btn-success.bk-bs-disabled.bk-bs-active,.bk-bs-btn-success[disabled].bk-bs-active,fieldset[disabled] .bk-bs-btn-success.bk-bs-active{background-color:#5cb85c;border-color:#4cae4c}.bk-bs-btn-success .bk-bs-badge{color:#5cb85c;background-color:#fff}.bk-bs-btn-info{color:#fff;background-color:#5bc0de;border-color:#46b8da}.bk-bs-btn-info:hover,.bk-bs-btn-info:focus,.bk-bs-btn-info:active,.bk-bs-btn-info.bk-bs-active,.bk-bs-open .bk-bs-dropdown-toggle.bk-bs-btn-info{color:#fff;background-color:#39b3d7;border-color:#269abc}.bk-bs-btn-info:active,.bk-bs-btn-info.bk-bs-active,.bk-bs-open .bk-bs-dropdown-toggle.bk-bs-btn-info{background-image:none}.bk-bs-btn-info.bk-bs-disabled,.bk-bs-btn-info[disabled],fieldset[disabled] .bk-bs-btn-info,.bk-bs-btn-info.bk-bs-disabled:hover,.bk-bs-btn-info[disabled]:hover,fieldset[disabled] .bk-bs-btn-info:hover,.bk-bs-btn-info.bk-bs-disabled:focus,.bk-bs-btn-info[disabled]:focus,fieldset[disabled] .bk-bs-btn-info:focus,.bk-bs-btn-info.bk-bs-disabled:active,.bk-bs-btn-info[disabled]:active,fieldset[disabled] .bk-bs-btn-info:active,.bk-bs-btn-info.bk-bs-disabled.bk-bs-active,.bk-bs-btn-info[disabled].bk-bs-active,fieldset[disabled] .bk-bs-btn-info.bk-bs-active{background-color:#5bc0de;border-color:#46b8da}.bk-bs-btn-info .bk-bs-badge{color:#5bc0de;background-color:#fff}.bk-bs-btn-warning{color:#fff;background-color:#f0ad4e;border-color:#eea236}.bk-bs-btn-warning:hover,.bk-bs-btn-warning:focus,.bk-bs-btn-warning:active,.bk-bs-btn-warning.bk-bs-active,.bk-bs-open .bk-bs-dropdown-toggle.bk-bs-btn-warning{color:#fff;background-color:#ed9c28;border-color:#d58512}.bk-bs-btn-warning:active,.bk-bs-btn-warning.bk-bs-active,.bk-bs-open .bk-bs-dropdown-toggle.bk-bs-btn-warning{background-image:none}.bk-bs-btn-warning.bk-bs-disabled,.bk-bs-btn-warning[disabled],fieldset[disabled] .bk-bs-btn-warning,.bk-bs-btn-warning.bk-bs-disabled:hover,.bk-bs-btn-warning[disabled]:hover,fieldset[disabled] .bk-bs-btn-warning:hover,.bk-bs-btn-warning.bk-bs-disabled:focus,.bk-bs-btn-warning[disabled]:focus,fieldset[disabled] .bk-bs-btn-warning:focus,.bk-bs-btn-warning.bk-bs-disabled:active,.bk-bs-btn-warning[disabled]:active,fieldset[disabled] .bk-bs-btn-warning:active,.bk-bs-btn-warning.bk-bs-disabled.bk-bs-active,.bk-bs-btn-warning[disabled].bk-bs-active,fieldset[disabled] .bk-bs-btn-warning.bk-bs-active{background-color:#f0ad4e;border-color:#eea236}.bk-bs-btn-warning .bk-bs-badge{color:#f0ad4e;background-color:#fff}.bk-bs-btn-danger{color:#fff;background-color:#d9534f;border-color:#d43f3a}.bk-bs-btn-danger:hover,.bk-bs-btn-danger:focus,.bk-bs-btn-danger:active,.bk-bs-btn-danger.bk-bs-active,.bk-bs-open .bk-bs-dropdown-toggle.bk-bs-btn-danger{color:#fff;background-color:#d2322d;border-color:#ac2925}.bk-bs-btn-danger:active,.bk-bs-btn-danger.bk-bs-active,.bk-bs-open .bk-bs-dropdown-toggle.bk-bs-btn-danger{background-image:none}.bk-bs-btn-danger.bk-bs-disabled,.bk-bs-btn-danger[disabled],fieldset[disabled] .bk-bs-btn-danger,.bk-bs-btn-danger.bk-bs-disabled:hover,.bk-bs-btn-danger[disabled]:hover,fieldset[disabled] .bk-bs-btn-danger:hover,.bk-bs-btn-danger.bk-bs-disabled:focus,.bk-bs-btn-danger[disabled]:focus,fieldset[disabled] .bk-bs-btn-danger:focus,.bk-bs-btn-danger.bk-bs-disabled:active,.bk-bs-btn-danger[disabled]:active,fieldset[disabled] .bk-bs-btn-danger:active,.bk-bs-btn-danger.bk-bs-disabled.bk-bs-active,.bk-bs-btn-danger[disabled].bk-bs-active,fieldset[disabled] .bk-bs-btn-danger.bk-bs-active{background-color:#d9534f;border-color:#d43f3a}.bk-bs-btn-danger .bk-bs-badge{color:#d9534f;background-color:#fff}.bk-bs-btn-link{color:#428bca;font-weight:normal;cursor:pointer;border-radius:0}.bk-bs-btn-link,.bk-bs-btn-link:active,.bk-bs-btn-link[disabled],fieldset[disabled] .bk-bs-btn-link{background-color:transparent;-webkit-box-shadow:none;box-shadow:none}.bk-bs-btn-link,.bk-bs-btn-link:hover,.bk-bs-btn-link:focus,.bk-bs-btn-link:active{border-color:transparent}.bk-bs-btn-link:hover,.bk-bs-btn-link:focus{color:#2a6496;text-decoration:underline;background-color:transparent}.bk-bs-btn-link[disabled]:hover,fieldset[disabled] .bk-bs-btn-link:hover,.bk-bs-btn-link[disabled]:focus,fieldset[disabled] .bk-bs-btn-link:focus{color:#999;text-decoration:none}.bk-bs-btn-lg,.bk-bs-btn-group-lg>.bk-bs-btn{padding:10px 16px;font-size:18px;line-height:1.33;border-radius:6px}.bk-bs-btn-sm,.bk-bs-btn-group-sm>.bk-bs-btn{padding:5px 10px;font-size:12px;line-height:1.5;border-radius:3px}.bk-bs-btn-xs,.bk-bs-btn-group-xs>.bk-bs-btn{padding:1px 5px;font-size:12px;line-height:1.5;border-radius:3px}.bk-bs-btn-block{display:block;width:100%;padding-left:0;padding-right:0}.bk-bs-btn-block+.bk-bs-btn-block{margin-top:5px}input[type="submit"].bk-bs-btn-block,input[type="reset"].bk-bs-btn-block,input[type="button"].bk-bs-btn-block{width:100%}.bk-bs-caret{display:inline-block;width:0;height:0;margin-left:2px;vertical-align:middle;border-top:4px solid;border-right:4px solid transparent;border-left:4px solid transparent}.bk-bs-dropdown{position:relative}.bk-bs-dropdown-toggle:focus{outline:0}.bk-bs-dropdown-menu{position:absolute;top:100%;left:0;z-index:1000;display:none;float:left;min-width:160px;padding:5px 0;margin:2px 0 0;list-style:none;font-size:14px;background-color:#fff;border:1px solid #ccc;border:1px solid rgba(0,0,0,0.15);border-radius:4px;-webkit-box-shadow:0 6px 12px rgba(0,0,0,0.175);box-shadow:0 6px 12px rgba(0,0,0,0.175);background-clip:padding-box}.bk-bs-dropdown-menu.bk-bs-pull-right{right:0;left:auto}.bk-bs-dropdown-menu .bk-bs-divider{height:1px;margin:9px 0;overflow:hidden;background-color:#e5e5e5}.bk-bs-dropdown-menu>li>a{display:block;padding:3px 20px;clear:both;font-weight:normal;line-height:1.42857143;color:#333;white-space:nowrap}.bk-bs-dropdown-menu>li>a:hover,.bk-bs-dropdown-menu>li>a:focus{text-decoration:none;color:#262626;background-color:#f5f5f5}.bk-bs-dropdown-menu>.bk-bs-active>a,.bk-bs-dropdown-menu>.bk-bs-active>a:hover,.bk-bs-dropdown-menu>.bk-bs-active>a:focus{color:#fff;text-decoration:none;outline:0;background-color:#428bca}.bk-bs-dropdown-menu>.bk-bs-disabled>a,.bk-bs-dropdown-menu>.bk-bs-disabled>a:hover,.bk-bs-dropdown-menu>.bk-bs-disabled>a:focus{color:#999}.bk-bs-dropdown-menu>.bk-bs-disabled>a:hover,.bk-bs-dropdown-menu>.bk-bs-disabled>a:focus{text-decoration:none;background-color:transparent;background-image:none;filter:progid:DXImageTransform.bk-bs-Microsoft.bk-bs-gradient(enabled = false);cursor:not-allowed}.bk-bs-open>.bk-bs-dropdown-menu{display:block}.bk-bs-open>a{outline:0}.bk-bs-dropdown-menu-right{left:auto;right:0}.bk-bs-dropdown-menu-left{left:0;right:auto}.bk-bs-dropdown-header{display:block;padding:3px 20px;font-size:12px;line-height:1.42857143;color:#999}.bk-bs-dropdown-backdrop{position:fixed;left:0;right:0;bottom:0;top:0;z-index:990}.bk-bs-pull-right>.bk-bs-dropdown-menu{right:0;left:auto}.bk-bs-dropup .bk-bs-caret,.bk-bs-navbar-fixed-bottom .bk-bs-dropdown .bk-bs-caret{border-top:0;border-bottom:4px solid;content:""}.bk-bs-dropup .bk-bs-dropdown-menu,.bk-bs-navbar-fixed-bottom .bk-bs-dropdown .bk-bs-dropdown-menu{top:auto;bottom:100%;margin-bottom:1px}@media(min-width:768px){.bk-bs-navbar-right .bk-bs-dropdown-menu{left:auto;right:0}.bk-bs-navbar-right .bk-bs-dropdown-menu-left{left:0;right:auto}}.bk-bs-btn-group,.bk-bs-btn-group-vertical{position:relative;display:inline-block;vertical-align:middle}.bk-bs-btn-group>.bk-bs-btn,.bk-bs-btn-group-vertical>.bk-bs-btn{position:relative;float:left}.bk-bs-btn-group>.bk-bs-btn:hover,.bk-bs-btn-group-vertical>.bk-bs-btn:hover,.bk-bs-btn-group>.bk-bs-btn:focus,.bk-bs-btn-group-vertical>.bk-bs-btn:focus,.bk-bs-btn-group>.bk-bs-btn:active,.bk-bs-btn-group-vertical>.bk-bs-btn:active,.bk-bs-btn-group>.bk-bs-btn.bk-bs-active,.bk-bs-btn-group-vertical>.bk-bs-btn.bk-bs-active{z-index:2}.bk-bs-btn-group>.bk-bs-btn:focus,.bk-bs-btn-group-vertical>.bk-bs-btn:focus{outline:0}.bk-bs-btn-group .bk-bs-btn+.bk-bs-btn,.bk-bs-btn-group .bk-bs-btn+.bk-bs-btn-group,.bk-bs-btn-group .bk-bs-btn-group+.bk-bs-btn,.bk-bs-btn-group .bk-bs-btn-group+.bk-bs-btn-group{margin-left:-1px}.bk-bs-btn-toolbar{margin-left:-5px}.bk-bs-btn-toolbar .bk-bs-btn-group,.bk-bs-btn-toolbar .bk-bs-input-group{float:left}.bk-bs-btn-toolbar>.bk-bs-btn,.bk-bs-btn-toolbar>.bk-bs-btn-group,.bk-bs-btn-toolbar>.bk-bs-input-group{margin-left:5px}.bk-bs-btn-group>.bk-bs-btn:not(:first-child):not(:last-child):not(.bk-bs-dropdown-toggle){border-radius:0}.bk-bs-btn-group>.bk-bs-btn:first-child{margin-left:0}.bk-bs-btn-group>.bk-bs-btn:first-child:not(:last-child):not(.bk-bs-dropdown-toggle){border-bottom-right-radius:0;border-top-right-radius:0}.bk-bs-btn-group>.bk-bs-btn:last-child:not(:first-child),.bk-bs-btn-group>.bk-bs-dropdown-toggle:not(:first-child){border-bottom-left-radius:0;border-top-left-radius:0}.bk-bs-btn-group>.bk-bs-btn-group{float:left}.bk-bs-btn-group>.bk-bs-btn-group:not(:first-child):not(:last-child)>.bk-bs-btn{border-radius:0}.bk-bs-btn-group>.bk-bs-btn-group:first-child>.bk-bs-btn:last-child,.bk-bs-btn-group>.bk-bs-btn-group:first-child>.bk-bs-dropdown-toggle{border-bottom-right-radius:0;border-top-right-radius:0}.bk-bs-btn-group>.bk-bs-btn-group:last-child>.bk-bs-btn:first-child{border-bottom-left-radius:0;border-top-left-radius:0}.bk-bs-btn-group .bk-bs-dropdown-toggle:active,.bk-bs-btn-group.bk-bs-open .bk-bs-dropdown-toggle{outline:0}.bk-bs-btn-group>.bk-bs-btn+.bk-bs-dropdown-toggle{padding-left:8px;padding-right:8px}.bk-bs-btn-group>.bk-bs-btn-lg+.bk-bs-dropdown-toggle{padding-left:12px;padding-right:12px}.bk-bs-btn-group.bk-bs-open .bk-bs-dropdown-toggle{-webkit-box-shadow:inset 0 3px 5px rgba(0,0,0,0.125);box-shadow:inset 0 3px 5px rgba(0,0,0,0.125)}.bk-bs-btn-group.bk-bs-open .bk-bs-dropdown-toggle.bk-bs-btn-link{-webkit-box-shadow:none;box-shadow:none}.bk-bs-btn .bk-bs-caret{margin-left:0}.bk-bs-btn-lg .bk-bs-caret{border-width:5px 5px 0;border-bottom-width:0}.bk-bs-dropup .bk-bs-btn-lg .bk-bs-caret{border-width:0 5px 5px}.bk-bs-btn-group-vertical>.bk-bs-btn,.bk-bs-btn-group-vertical>.bk-bs-btn-group,.bk-bs-btn-group-vertical>.bk-bs-btn-group>.bk-bs-btn{display:block;float:none;width:100%;max-width:100%}.bk-bs-btn-group-vertical>.bk-bs-btn-group>.bk-bs-btn{float:none}.bk-bs-btn-group-vertical>.bk-bs-btn+.bk-bs-btn,.bk-bs-btn-group-vertical>.bk-bs-btn+.bk-bs-btn-group,.bk-bs-btn-group-vertical>.bk-bs-btn-group+.bk-bs-btn,.bk-bs-btn-group-vertical>.bk-bs-btn-group+.bk-bs-btn-group{margin-top:-1px;margin-left:0}.bk-bs-btn-group-vertical>.bk-bs-btn:not(:first-child):not(:last-child){border-radius:0}.bk-bs-btn-group-vertical>.bk-bs-btn:first-child:not(:last-child){border-top-right-radius:4px;border-bottom-right-radius:0;border-bottom-left-radius:0}.bk-bs-btn-group-vertical>.bk-bs-btn:last-child:not(:first-child){border-bottom-left-radius:4px;border-top-right-radius:0;border-top-left-radius:0}.bk-bs-btn-group-vertical>.bk-bs-btn-group:not(:first-child):not(:last-child)>.bk-bs-btn{border-radius:0}.bk-bs-btn-group-vertical>.bk-bs-btn-group:first-child:not(:last-child)>.bk-bs-btn:last-child,.bk-bs-btn-group-vertical>.bk-bs-btn-group:first-child:not(:last-child)>.bk-bs-dropdown-toggle{border-bottom-right-radius:0;border-bottom-left-radius:0}.bk-bs-btn-group-vertical>.bk-bs-btn-group:last-child:not(:first-child)>.bk-bs-btn:first-child{border-top-right-radius:0;border-top-left-radius:0}.bk-bs-btn-group-justified{display:table;width:100%;table-layout:fixed;border-collapse:separate}.bk-bs-btn-group-justified>.bk-bs-btn,.bk-bs-btn-group-justified>.bk-bs-btn-group{float:none;display:table-cell;width:1%}.bk-bs-btn-group-justified>.bk-bs-btn-group .bk-bs-btn{width:100%}[data-bk-bs-toggle="buttons"]>.bk-bs-btn>input[type="radio"],[data-bk-bs-toggle="buttons"]>.bk-bs-btn>input[type="checkbox"]{display:none}.bk-bs-input-group{position:relative;display:table;border-collapse:separate}.bk-bs-input-group[class*="col-"]{float:none;padding-left:0;padding-right:0}.bk-bs-input-group .bk-bs-form-control{position:relative;z-index:2;float:left;width:100%;margin-bottom:0}.bk-bs-input-group-lg>.bk-bs-form-control,.bk-bs-input-group-lg>.bk-bs-input-group-addon,.bk-bs-input-group-lg>.bk-bs-input-group-btn>.bk-bs-btn{height:46px;padding:10px 16px;font-size:18px;line-height:1.33;border-radius:6px}select.bk-bs-input-group-lg>.bk-bs-form-control,select.bk-bs-input-group-lg>.bk-bs-input-group-addon,select.bk-bs-input-group-lg>.bk-bs-input-group-btn>.bk-bs-btn{height:46px;line-height:46px}textarea.bk-bs-input-group-lg>.bk-bs-form-control,textarea.bk-bs-input-group-lg>.bk-bs-input-group-addon,textarea.bk-bs-input-group-lg>.bk-bs-input-group-btn>.bk-bs-btn,select[multiple].bk-bs-input-group-lg>.bk-bs-form-control,select[multiple].bk-bs-input-group-lg>.bk-bs-input-group-addon,select[multiple].bk-bs-input-group-lg>.bk-bs-input-group-btn>.bk-bs-btn{height:auto}.bk-bs-input-group-sm>.bk-bs-form-control,.bk-bs-input-group-sm>.bk-bs-input-group-addon,.bk-bs-input-group-sm>.bk-bs-input-group-btn>.bk-bs-btn{height:30px;padding:5px 10px;font-size:12px;line-height:1.5;border-radius:3px}select.bk-bs-input-group-sm>.bk-bs-form-control,select.bk-bs-input-group-sm>.bk-bs-input-group-addon,select.bk-bs-input-group-sm>.bk-bs-input-group-btn>.bk-bs-btn{height:30px;line-height:30px}textarea.bk-bs-input-group-sm>.bk-bs-form-control,textarea.bk-bs-input-group-sm>.bk-bs-input-group-addon,textarea.bk-bs-input-group-sm>.bk-bs-input-group-btn>.bk-bs-btn,select[multiple].bk-bs-input-group-sm>.bk-bs-form-control,select[multiple].bk-bs-input-group-sm>.bk-bs-input-group-addon,select[multiple].bk-bs-input-group-sm>.bk-bs-input-group-btn>.bk-bs-btn{height:auto}.bk-bs-input-group-addon,.bk-bs-input-group-btn,.bk-bs-input-group .bk-bs-form-control{display:table-cell}.bk-bs-input-group-addon:not(:first-child):not(:last-child),.bk-bs-input-group-btn:not(:first-child):not(:last-child),.bk-bs-input-group .bk-bs-form-control:not(:first-child):not(:last-child){border-radius:0}.bk-bs-input-group-addon,.bk-bs-input-group-btn{width:1%;white-space:nowrap;vertical-align:middle}.bk-bs-input-group-addon{padding:6px 12px;font-size:14px;font-weight:normal;line-height:1;color:#555;text-align:center;background-color:#eee;border:1px solid #ccc;border-radius:4px}.bk-bs-input-group-addon.bk-bs-input-sm{padding:5px 10px;font-size:12px;border-radius:3px}.bk-bs-input-group-addon.bk-bs-input-lg{padding:10px 16px;font-size:18px;border-radius:6px}.bk-bs-input-group-addon input[type="radio"],.bk-bs-input-group-addon input[type="checkbox"]{margin-top:0}.bk-bs-input-group .bk-bs-form-control:first-child,.bk-bs-input-group-addon:first-child,.bk-bs-input-group-btn:first-child>.bk-bs-btn,.bk-bs-input-group-btn:first-child>.bk-bs-btn-group>.bk-bs-btn,.bk-bs-input-group-btn:first-child>.bk-bs-dropdown-toggle,.bk-bs-input-group-btn:last-child>.bk-bs-btn:not(:last-child):not(.bk-bs-dropdown-toggle),.bk-bs-input-group-btn:last-child>.bk-bs-btn-group:not(:last-child)>.bk-bs-btn{border-bottom-right-radius:0;border-top-right-radius:0}.bk-bs-input-group-addon:first-child{border-right:0}.bk-bs-input-group .bk-bs-form-control:last-child,.bk-bs-input-group-addon:last-child,.bk-bs-input-group-btn:last-child>.bk-bs-btn,.bk-bs-input-group-btn:last-child>.bk-bs-btn-group>.bk-bs-btn,.bk-bs-input-group-btn:last-child>.bk-bs-dropdown-toggle,.bk-bs-input-group-btn:first-child>.bk-bs-btn:not(:first-child),.bk-bs-input-group-btn:first-child>.bk-bs-btn-group:not(:first-child)>.bk-bs-btn{border-bottom-left-radius:0;border-top-left-radius:0}.bk-bs-input-group-addon:last-child{border-left:0}.bk-bs-input-group-btn{position:relative;font-size:0;white-space:nowrap}.bk-bs-input-group-btn>.bk-bs-btn{position:relative}.bk-bs-input-group-btn>.bk-bs-btn+.bk-bs-btn{margin-left:-1px}.bk-bs-input-group-btn>.bk-bs-btn:hover,.bk-bs-input-group-btn>.bk-bs-btn:focus,.bk-bs-input-group-btn>.bk-bs-btn:active{z-index:2}.bk-bs-input-group-btn:first-child>.bk-bs-btn,.bk-bs-input-group-btn:first-child>.bk-bs-btn-group{margin-right:-1px}.bk-bs-input-group-btn:last-child>.bk-bs-btn,.bk-bs-input-group-btn:last-child>.bk-bs-btn-group{margin-left:-1px}.bk-bs-nav{margin-bottom:0;padding-left:0;list-style:none}.bk-bs-nav>li{position:relative;display:block}.bk-bs-nav>li>a{position:relative;display:block;padding:10px 15px}.bk-bs-nav>li>a:hover,.bk-bs-nav>li>a:focus{text-decoration:none;background-color:#eee}.bk-bs-nav>li.bk-bs-disabled>a{color:#999}.bk-bs-nav>li.bk-bs-disabled>a:hover,.bk-bs-nav>li.bk-bs-disabled>a:focus{color:#999;text-decoration:none;background-color:transparent;cursor:not-allowed}.bk-bs-nav .bk-bs-open>a,.bk-bs-nav .bk-bs-open>a:hover,.bk-bs-nav .bk-bs-open>a:focus{background-color:#eee;border-color:#428bca}.bk-bs-nav .bk-bs-nav-divider{height:1px;margin:9px 0;overflow:hidden;background-color:#e5e5e5}.bk-bs-nav>li>a>img{max-width:none}.bk-bs-nav-tabs{border-bottom:1px solid #ddd}.bk-bs-nav-tabs>li{float:left;margin-bottom:-1px}.bk-bs-nav-tabs>li>a{margin-right:2px;line-height:1.42857143;border:1px solid transparent;border-radius:4px 4px 0 0}.bk-bs-nav-tabs>li>a:hover{border-color:#eee #eee #ddd}.bk-bs-nav-tabs>li.bk-bs-active>a,.bk-bs-nav-tabs>li.bk-bs-active>a:hover,.bk-bs-nav-tabs>li.bk-bs-active>a:focus{color:#555;background-color:#fff;border:1px solid #ddd;border-bottom-color:transparent;cursor:default}.bk-bs-nav-tabs.bk-bs-nav-justified{width:100%;border-bottom:0}.bk-bs-nav-tabs.bk-bs-nav-justified>li{float:none}.bk-bs-nav-tabs.bk-bs-nav-justified>li>a{text-align:center;margin-bottom:5px}.bk-bs-nav-tabs.bk-bs-nav-justified>.bk-bs-dropdown .bk-bs-dropdown-menu{top:auto;left:auto}@media(min-width:768px){.bk-bs-nav-tabs.bk-bs-nav-justified>li{display:table-cell;width:1%}.bk-bs-nav-tabs.bk-bs-nav-justified>li>a{margin-bottom:0}}.bk-bs-nav-tabs.bk-bs-nav-justified>li>a{margin-right:0;border-radius:4px}.bk-bs-nav-tabs.bk-bs-nav-justified>.bk-bs-active>a,.bk-bs-nav-tabs.bk-bs-nav-justified>.bk-bs-active>a:hover,.bk-bs-nav-tabs.bk-bs-nav-justified>.bk-bs-active>a:focus{border:1px solid #ddd}@media(min-width:768px){.bk-bs-nav-tabs.bk-bs-nav-justified>li>a{border-bottom:1px solid #ddd;border-radius:4px 4px 0 0}.bk-bs-nav-tabs.bk-bs-nav-justified>.bk-bs-active>a,.bk-bs-nav-tabs.bk-bs-nav-justified>.bk-bs-active>a:hover,.bk-bs-nav-tabs.bk-bs-nav-justified>.bk-bs-active>a:focus{border-bottom-color:#fff}}.bk-bs-nav-pills>li{float:left}.bk-bs-nav-pills>li>a{border-radius:4px}.bk-bs-nav-pills>li+li{margin-left:2px}.bk-bs-nav-pills>li.bk-bs-active>a,.bk-bs-nav-pills>li.bk-bs-active>a:hover,.bk-bs-nav-pills>li.bk-bs-active>a:focus{color:#fff;background-color:#428bca}.bk-bs-nav-stacked>li{float:none}.bk-bs-nav-stacked>li+li{margin-top:2px;margin-left:0}.bk-bs-nav-justified{width:100%}.bk-bs-nav-justified>li{float:none}.bk-bs-nav-justified>li>a{text-align:center;margin-bottom:5px}.bk-bs-nav-justified>.bk-bs-dropdown .bk-bs-dropdown-menu{top:auto;left:auto}@media(min-width:768px){.bk-bs-nav-justified>li{display:table-cell;width:1%}.bk-bs-nav-justified>li>a{margin-bottom:0}}.bk-bs-nav-tabs-justified{border-bottom:0}.bk-bs-nav-tabs-justified>li>a{margin-right:0;border-radius:4px}.bk-bs-nav-tabs-justified>.bk-bs-active>a,.bk-bs-nav-tabs-justified>.bk-bs-active>a:hover,.bk-bs-nav-tabs-justified>.bk-bs-active>a:focus{border:1px solid #ddd}@media(min-width:768px){.bk-bs-nav-tabs-justified>li>a{border-bottom:1px solid #ddd;border-radius:4px 4px 0 0}.bk-bs-nav-tabs-justified>.bk-bs-active>a,.bk-bs-nav-tabs-justified>.bk-bs-active>a:hover,.bk-bs-nav-tabs-justified>.bk-bs-active>a:focus{border-bottom-color:#fff}}.bk-bs-tab-content>.bk-bs-tab-pane{display:none}.bk-bs-tab-content>.bk-bs-active{display:block}.bk-bs-nav-tabs .bk-bs-dropdown-menu{margin-top:-1px;border-top-right-radius:0;border-top-left-radius:0}.bk-bs-label{display:inline;padding:.2em .6em .3em;font-size:75%;font-weight:bold;line-height:1;color:#fff;text-align:center;white-space:nowrap;vertical-align:baseline;border-radius:.25em}.bk-bs-label[href]:hover,.bk-bs-label[href]:focus{color:#fff;text-decoration:none;cursor:pointer}.bk-bs-label:empty{display:none}.bk-bs-btn .bk-bs-label{position:relative;top:-1px}.bk-bs-label-default{background-color:#999}.bk-bs-label-default[href]:hover,.bk-bs-label-default[href]:focus{background-color:gray}.bk-bs-label-primary{background-color:#428bca}.bk-bs-label-primary[href]:hover,.bk-bs-label-primary[href]:focus{background-color:#3071a9}.bk-bs-label-success{background-color:#5cb85c}.bk-bs-label-success[href]:hover,.bk-bs-label-success[href]:focus{background-color:#449d44}.bk-bs-label-info{background-color:#5bc0de}.bk-bs-label-info[href]:hover,.bk-bs-label-info[href]:focus{background-color:#31b0d5}.bk-bs-label-warning{background-color:#f0ad4e}.bk-bs-label-warning[href]:hover,.bk-bs-label-warning[href]:focus{background-color:#ec971f}.bk-bs-label-danger{background-color:#d9534f}.bk-bs-label-danger[href]:hover,.bk-bs-label-danger[href]:focus{background-color:#c9302c}.bk-bs-panel{margin-bottom:20px;background-color:#fff;border:1px solid transparent;border-radius:4px;-webkit-box-shadow:0 1px 1px rgba(0,0,0,0.05);box-shadow:0 1px 1px rgba(0,0,0,0.05)}.bk-bs-panel-body{padding:15px}.bk-bs-panel-heading{padding:10px 15px;border-bottom:1px solid transparent;border-top-right-radius:3px;border-top-left-radius:3px}.bk-bs-panel-heading>.bk-bs-dropdown .bk-bs-dropdown-toggle{color:inherit}.bk-bs-panel-title{margin-top:0;margin-bottom:0;font-size:16px;color:inherit}.bk-bs-panel-title>a{color:inherit}.bk-bs-panel-footer{padding:10px 15px;background-color:#f5f5f5;border-top:1px solid #ddd;border-bottom-right-radius:3px;border-bottom-left-radius:3px}.bk-bs-panel>.bk-bs-list-group{margin-bottom:0}.bk-bs-panel>.bk-bs-list-group .bk-bs-list-group-item{border-width:1px 0;border-radius:0}.bk-bs-panel>.bk-bs-list-group:first-child .bk-bs-list-group-item:first-child{border-top:0;border-top-right-radius:3px;border-top-left-radius:3px}.bk-bs-panel>.bk-bs-list-group:last-child .bk-bs-list-group-item:last-child{border-bottom:0;border-bottom-right-radius:3px;border-bottom-left-radius:3px}.bk-bs-panel-heading+.bk-bs-list-group .bk-bs-list-group-item:first-child{border-top-width:0}.bk-bs-panel>.bk-bs-table,.bk-bs-panel>.bk-bs-table-responsive>.bk-bs-table{margin-bottom:0}.bk-bs-panel>.bk-bs-table:first-child,.bk-bs-panel>.bk-bs-table-responsive:first-child>.bk-bs-table:first-child{border-top-right-radius:3px;border-top-left-radius:3px}.bk-bs-panel>.bk-bs-table:first-child>thead:first-child>tr:first-child td:first-child,.bk-bs-panel>.bk-bs-table-responsive:first-child>.bk-bs-table:first-child>thead:first-child>tr:first-child td:first-child,.bk-bs-panel>.bk-bs-table:first-child>tbody:first-child>tr:first-child td:first-child,.bk-bs-panel>.bk-bs-table-responsive:first-child>.bk-bs-table:first-child>tbody:first-child>tr:first-child td:first-child,.bk-bs-panel>.bk-bs-table:first-child>thead:first-child>tr:first-child th:first-child,.bk-bs-panel>.bk-bs-table-responsive:first-child>.bk-bs-table:first-child>thead:first-child>tr:first-child th:first-child,.bk-bs-panel>.bk-bs-table:first-child>tbody:first-child>tr:first-child th:first-child,.bk-bs-panel>.bk-bs-table-responsive:first-child>.bk-bs-table:first-child>tbody:first-child>tr:first-child th:first-child{border-top-left-radius:3px}.bk-bs-panel>.bk-bs-table:first-child>thead:first-child>tr:first-child td:last-child,.bk-bs-panel>.bk-bs-table-responsive:first-child>.bk-bs-table:first-child>thead:first-child>tr:first-child td:last-child,.bk-bs-panel>.bk-bs-table:first-child>tbody:first-child>tr:first-child td:last-child,.bk-bs-panel>.bk-bs-table-responsive:first-child>.bk-bs-table:first-child>tbody:first-child>tr:first-child td:last-child,.bk-bs-panel>.bk-bs-table:first-child>thead:first-child>tr:first-child th:last-child,.bk-bs-panel>.bk-bs-table-responsive:first-child>.bk-bs-table:first-child>thead:first-child>tr:first-child th:last-child,.bk-bs-panel>.bk-bs-table:first-child>tbody:first-child>tr:first-child th:last-child,.bk-bs-panel>.bk-bs-table-responsive:first-child>.bk-bs-table:first-child>tbody:first-child>tr:first-child th:last-child{border-top-right-radius:3px}.bk-bs-panel>.bk-bs-table:last-child,.bk-bs-panel>.bk-bs-table-responsive:last-child>.bk-bs-table:last-child{border-bottom-right-radius:3px;border-bottom-left-radius:3px}.bk-bs-panel>.bk-bs-table:last-child>tbody:last-child>tr:last-child td:first-child,.bk-bs-panel>.bk-bs-table-responsive:last-child>.bk-bs-table:last-child>tbody:last-child>tr:last-child td:first-child,.bk-bs-panel>.bk-bs-table:last-child>tfoot:last-child>tr:last-child td:first-child,.bk-bs-panel>.bk-bs-table-responsive:last-child>.bk-bs-table:last-child>tfoot:last-child>tr:last-child td:first-child,.bk-bs-panel>.bk-bs-table:last-child>tbody:last-child>tr:last-child th:first-child,.bk-bs-panel>.bk-bs-table-responsive:last-child>.bk-bs-table:last-child>tbody:last-child>tr:last-child th:first-child,.bk-bs-panel>.bk-bs-table:last-child>tfoot:last-child>tr:last-child th:first-child,.bk-bs-panel>.bk-bs-table-responsive:last-child>.bk-bs-table:last-child>tfoot:last-child>tr:last-child th:first-child{border-bottom-left-radius:3px}.bk-bs-panel>.bk-bs-table:last-child>tbody:last-child>tr:last-child td:last-child,.bk-bs-panel>.bk-bs-table-responsive:last-child>.bk-bs-table:last-child>tbody:last-child>tr:last-child td:last-child,.bk-bs-panel>.bk-bs-table:last-child>tfoot:last-child>tr:last-child td:last-child,.bk-bs-panel>.bk-bs-table-responsive:last-child>.bk-bs-table:last-child>tfoot:last-child>tr:last-child td:last-child,.bk-bs-panel>.bk-bs-table:last-child>tbody:last-child>tr:last-child th:last-child,.bk-bs-panel>.bk-bs-table-responsive:last-child>.bk-bs-table:last-child>tbody:last-child>tr:last-child th:last-child,.bk-bs-panel>.bk-bs-table:last-child>tfoot:last-child>tr:last-child th:last-child,.bk-bs-panel>.bk-bs-table-responsive:last-child>.bk-bs-table:last-child>tfoot:last-child>tr:last-child th:last-child{border-bottom-right-radius:3px}.bk-bs-panel>.bk-bs-panel-body+.bk-bs-table,.bk-bs-panel>.bk-bs-panel-body+.bk-bs-table-responsive{border-top:1px solid #ddd}.bk-bs-panel>.bk-bs-table>tbody:first-child>tr:first-child th,.bk-bs-panel>.bk-bs-table>tbody:first-child>tr:first-child td{border-top:0}.bk-bs-panel>.bk-bs-table-bordered,.bk-bs-panel>.bk-bs-table-responsive>.bk-bs-table-bordered{border:0}.bk-bs-panel>.bk-bs-table-bordered>thead>tr>th:first-child,.bk-bs-panel>.bk-bs-table-responsive>.bk-bs-table-bordered>thead>tr>th:first-child,.bk-bs-panel>.bk-bs-table-bordered>tbody>tr>th:first-child,.bk-bs-panel>.bk-bs-table-responsive>.bk-bs-table-bordered>tbody>tr>th:first-child,.bk-bs-panel>.bk-bs-table-bordered>tfoot>tr>th:first-child,.bk-bs-panel>.bk-bs-table-responsive>.bk-bs-table-bordered>tfoot>tr>th:first-child,.bk-bs-panel>.bk-bs-table-bordered>thead>tr>td:first-child,.bk-bs-panel>.bk-bs-table-responsive>.bk-bs-table-bordered>thead>tr>td:first-child,.bk-bs-panel>.bk-bs-table-bordered>tbody>tr>td:first-child,.bk-bs-panel>.bk-bs-table-responsive>.bk-bs-table-bordered>tbody>tr>td:first-child,.bk-bs-panel>.bk-bs-table-bordered>tfoot>tr>td:first-child,.bk-bs-panel>.bk-bs-table-responsive>.bk-bs-table-bordered>tfoot>tr>td:first-child{border-left:0}.bk-bs-panel>.bk-bs-table-bordered>thead>tr>th:last-child,.bk-bs-panel>.bk-bs-table-responsive>.bk-bs-table-bordered>thead>tr>th:last-child,.bk-bs-panel>.bk-bs-table-bordered>tbody>tr>th:last-child,.bk-bs-panel>.bk-bs-table-responsive>.bk-bs-table-bordered>tbody>tr>th:last-child,.bk-bs-panel>.bk-bs-table-bordered>tfoot>tr>th:last-child,.bk-bs-panel>.bk-bs-table-responsive>.bk-bs-table-bordered>tfoot>tr>th:last-child,.bk-bs-panel>.bk-bs-table-bordered>thead>tr>td:last-child,.bk-bs-panel>.bk-bs-table-responsive>.bk-bs-table-bordered>thead>tr>td:last-child,.bk-bs-panel>.bk-bs-table-bordered>tbody>tr>td:last-child,.bk-bs-panel>.bk-bs-table-responsive>.bk-bs-table-bordered>tbody>tr>td:last-child,.bk-bs-panel>.bk-bs-table-bordered>tfoot>tr>td:last-child,.bk-bs-panel>.bk-bs-table-responsive>.bk-bs-table-bordered>tfoot>tr>td:last-child{border-right:0}.bk-bs-panel>.bk-bs-table-bordered>thead>tr:first-child>td,.bk-bs-panel>.bk-bs-table-responsive>.bk-bs-table-bordered>thead>tr:first-child>td,.bk-bs-panel>.bk-bs-table-bordered>tbody>tr:first-child>td,.bk-bs-panel>.bk-bs-table-responsive>.bk-bs-table-bordered>tbody>tr:first-child>td,.bk-bs-panel>.bk-bs-table-bordered>thead>tr:first-child>th,.bk-bs-panel>.bk-bs-table-responsive>.bk-bs-table-bordered>thead>tr:first-child>th,.bk-bs-panel>.bk-bs-table-bordered>tbody>tr:first-child>th,.bk-bs-panel>.bk-bs-table-responsive>.bk-bs-table-bordered>tbody>tr:first-child>th{border-bottom:0}.bk-bs-panel>.bk-bs-table-bordered>tbody>tr:last-child>td,.bk-bs-panel>.bk-bs-table-responsive>.bk-bs-table-bordered>tbody>tr:last-child>td,.bk-bs-panel>.bk-bs-table-bordered>tfoot>tr:last-child>td,.bk-bs-panel>.bk-bs-table-responsive>.bk-bs-table-bordered>tfoot>tr:last-child>td,.bk-bs-panel>.bk-bs-table-bordered>tbody>tr:last-child>th,.bk-bs-panel>.bk-bs-table-responsive>.bk-bs-table-bordered>tbody>tr:last-child>th,.bk-bs-panel>.bk-bs-table-bordered>tfoot>tr:last-child>th,.bk-bs-panel>.bk-bs-table-responsive>.bk-bs-table-bordered>tfoot>tr:last-child>th{border-bottom:0}.bk-bs-panel>.bk-bs-table-responsive{border:0;margin-bottom:0}.bk-bs-panel-group{margin-bottom:20px}.bk-bs-panel-group .bk-bs-panel{margin-bottom:0;border-radius:4px;overflow:hidden}.bk-bs-panel-group .bk-bs-panel+.bk-bs-panel{margin-top:5px}.bk-bs-panel-group .bk-bs-panel-heading{border-bottom:0}.bk-bs-panel-group .bk-bs-panel-heading+.bk-bs-panel-collapse .bk-bs-panel-body{border-top:1px solid #ddd}.bk-bs-panel-group .bk-bs-panel-footer{border-top:0}.bk-bs-panel-group .bk-bs-panel-footer+.bk-bs-panel-collapse .bk-bs-panel-body{border-bottom:1px solid #ddd}.bk-bs-panel-default{border-color:#ddd}.bk-bs-panel-default>.bk-bs-panel-heading{color:#333;background-color:#f5f5f5;border-color:#ddd}.bk-bs-panel-default>.bk-bs-panel-heading+.bk-bs-panel-collapse .bk-bs-panel-body{border-top-color:#ddd}.bk-bs-panel-default>.bk-bs-panel-footer+.bk-bs-panel-collapse .bk-bs-panel-body{border-bottom-color:#ddd}.bk-bs-panel-primary{border-color:#428bca}.bk-bs-panel-primary>.bk-bs-panel-heading{color:#fff;background-color:#428bca;border-color:#428bca}.bk-bs-panel-primary>.bk-bs-panel-heading+.bk-bs-panel-collapse .bk-bs-panel-body{border-top-color:#428bca}.bk-bs-panel-primary>.bk-bs-panel-footer+.bk-bs-panel-collapse .bk-bs-panel-body{border-bottom-color:#428bca}.bk-bs-panel-success{border-color:#d6e9c6}.bk-bs-panel-success>.bk-bs-panel-heading{color:#3c763d;background-color:#dff0d8;border-color:#d6e9c6}.bk-bs-panel-success>.bk-bs-panel-heading+.bk-bs-panel-collapse .bk-bs-panel-body{border-top-color:#d6e9c6}.bk-bs-panel-success>.bk-bs-panel-footer+.bk-bs-panel-collapse .bk-bs-panel-body{border-bottom-color:#d6e9c6}.bk-bs-panel-info{border-color:#bce8f1}.bk-bs-panel-info>.bk-bs-panel-heading{color:#31708f;background-color:#d9edf7;border-color:#bce8f1}.bk-bs-panel-info>.bk-bs-panel-heading+.bk-bs-panel-collapse .bk-bs-panel-body{border-top-color:#bce8f1}.bk-bs-panel-info>.bk-bs-panel-footer+.bk-bs-panel-collapse .bk-bs-panel-body{border-bottom-color:#bce8f1}.bk-bs-panel-warning{border-color:#faebcc}.bk-bs-panel-warning>.bk-bs-panel-heading{color:#8a6d3b;background-color:#fcf8e3;border-color:#faebcc}.bk-bs-panel-warning>.bk-bs-panel-heading+.bk-bs-panel-collapse .bk-bs-panel-body{border-top-color:#faebcc}.bk-bs-panel-warning>.bk-bs-panel-footer+.bk-bs-panel-collapse .bk-bs-panel-body{border-bottom-color:#faebcc}.bk-bs-panel-danger{border-color:#ebccd1}.bk-bs-panel-danger>.bk-bs-panel-heading{color:#a94442;background-color:#f2dede;border-color:#ebccd1}.bk-bs-panel-danger>.bk-bs-panel-heading+.bk-bs-panel-collapse .bk-bs-panel-body{border-top-color:#ebccd1}.bk-bs-panel-danger>.bk-bs-panel-footer+.bk-bs-panel-collapse .bk-bs-panel-body{border-bottom-color:#ebccd1}.bk-bs-close{float:right;font-size:21px;font-weight:bold;line-height:1;color:#000;text-shadow:0 1px 0 #fff;opacity:.2;filter:alpha(opacity=20)}.bk-bs-close:hover,.bk-bs-close:focus{color:#000;text-decoration:none;cursor:pointer;opacity:.5;filter:alpha(opacity=50)}button.bk-bs-close{padding:0;cursor:pointer;background:transparent;border:0;-webkit-appearance:none}.bk-bs-modal-open{overflow:hidden}.bk-bs-modal{display:none;overflow:auto;overflow-y:scroll;position:fixed;top:0;right:0;bottom:0;left:0;z-index:1050;-webkit-overflow-scrolling:touch;outline:0}.bk-bs-modal.bk-bs-fade .bk-bs-modal-dialog{-webkit-transform:translate(0,-25%);-ms-transform:translate(0,-25%);transform:translate(0,-25%);-webkit-transition:-webkit-transform .3s ease-out;-moz-transition:-moz-transform .3s ease-out;-o-transition:-o-transform .3s ease-out;transition:transform .3s ease-out}.bk-bs-modal.bk-bs-in .bk-bs-modal-dialog{-webkit-transform:translate(0,0);-ms-transform:translate(0,0);transform:translate(0,0)}.bk-bs-modal-dialog{position:relative;width:auto;margin:10px}.bk-bs-modal-content{position:relative;background-color:#fff;border:1px solid #999;border:1px solid rgba(0,0,0,0.2);border-radius:6px;-webkit-box-shadow:0 3px 9px rgba(0,0,0,0.5);box-shadow:0 3px 9px rgba(0,0,0,0.5);background-clip:padding-box;outline:0}.bk-bs-modal-backdrop{position:fixed;top:0;right:0;bottom:0;left:0;z-index:1040;background-color:#000}.bk-bs-modal-backdrop.bk-bs-fade{opacity:0;filter:alpha(opacity=0)}.bk-bs-modal-backdrop.bk-bs-in{opacity:.5;filter:alpha(opacity=50)}.bk-bs-modal-header{padding:15px;border-bottom:1px solid #e5e5e5;min-height:16.42857143px}.bk-bs-modal-header .bk-bs-close{margin-top:-2px}.bk-bs-modal-title{margin:0;line-height:1.42857143}.bk-bs-modal-body{position:relative;padding:20px}.bk-bs-modal-footer{margin-top:15px;padding:19px 20px 20px;text-align:right;border-top:1px solid #e5e5e5}.bk-bs-modal-footer .bk-bs-btn+.bk-bs-btn{margin-left:5px;margin-bottom:0}.bk-bs-modal-footer .bk-bs-btn-group .bk-bs-btn+.bk-bs-btn{margin-left:-1px}.bk-bs-modal-footer .bk-bs-btn-block+.bk-bs-btn-block{margin-left:0}@media(min-width:768px){.bk-bs-modal-dialog{width:600px;margin:30px auto}.bk-bs-modal-content{-webkit-box-shadow:0 5px 15px rgba(0,0,0,0.5);box-shadow:0 5px 15px rgba(0,0,0,0.5)}.bk-bs-modal-sm{width:300px}}@media(min-width:992px){.bk-bs-modal-lg{width:900px}}.bk-bs-clearfix:before,.bk-bs-clearfix:after,.bk-bs-container:before,.bk-bs-container:after,.bk-bs-container-fluid:before,.bk-bs-container-fluid:after,.bk-bs-row:before,.bk-bs-row:after,.bk-bs-form-horizontal .bk-bs-form-group:before,.bk-bs-form-horizontal .bk-bs-form-group:after,.bk-bs-btn-toolbar:before,.bk-bs-btn-toolbar:after,.bk-bs-btn-group-vertical>.bk-bs-btn-group:before,.bk-bs-btn-group-vertical>.bk-bs-btn-group:after,.bk-bs-nav:before,.bk-bs-nav:after,.bk-bs-panel-body:before,.bk-bs-panel-body:after,.bk-bs-modal-footer:before,.bk-bs-modal-footer:after{content:" ";display:table}.bk-bs-clearfix:after,.bk-bs-container:after,.bk-bs-container-fluid:after,.bk-bs-row:after,.bk-bs-form-horizontal .bk-bs-form-group:after,.bk-bs-btn-toolbar:after,.bk-bs-btn-group-vertical>.bk-bs-btn-group:after,.bk-bs-nav:after,.bk-bs-panel-body:after,.bk-bs-modal-footer:after{clear:both}.bk-bs-center-block{display:block;margin-left:auto;margin-right:auto}.bk-bs-pull-right{float:right !important}.bk-bs-pull-left{float:left !important}.bk-bs-hide{display:none !important}.bk-bs-show{display:block !important}.bk-bs-invisible{visibility:hidden}.bk-bs-text-hide{font:0/0 a;color:transparent;text-shadow:none;background-color:transparent;border:0}.bk-bs-hidden{display:none !important;visibility:hidden !important}.bk-bs-affix{position:fixed}.tableelem{padding:2px 10px;border:2px white;background-color:#e0e0e0}.tableheader{background-color:silver}#notebook .bk-plot-wrapper table{border:none !important}#notebook .bk-plot-wrapper table tr{border:none !important}#notebook .bk-plot-wrapper table tr td{border:none !important;padding:0 !important;margin:0 !important}#notebook .bk-plot-wrapper table tr td.bk-plot-above{border-bottom:2px solid #efefef !important}#notebook .bk-plot-wrapper table tr td.bk-plot-below{border-top:2px solid #efefef !important}#notebook .bk-plot-wrapper table tr td.bk-plot-left{border-right:2px solid #efefef !important}#notebook .bk-plot-wrapper table tr td.bk-plot-right{border-left:2px solid #efefef !important}.bk-table table tr td{padding:2px}.bk-table form table tr td{padding:2px}.bk-table form table tr td input{padding:0}.jsp:after,.bk-plot:after,.bk-canvas-wrapper:after,.bk-sidebar:after,.bk-box:after{content:" ";height:0;display:block;clear:both}.bk-canvas-wrapper .bk-resize-popup{position:absolute;left:0;top:0;width:40px;height:40px;overflow:hidden;background-image:url(data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAABkAAAAZCAYAAADE6YVjAAAABHNCSVQICAgIfAhkiAAAAAlwSFlzAAALEgAACxIB0t1+/AAAABx0RVh0U29mdHdhcmUAQWRvYmUgRmlyZXdvcmtzIENTNui8sowAAAEnSURBVEiJzZXBioQwDIb/XQuF6U3wCRSEvv8zeFOoB2++QD14aqHSOntYtthxdHesDptTk4Z+hKR/PqqquuNi+7wa8DYIWTplWSJN0yDBGAMhBJxzhyFBJY8AACCErGKv2u4L1lp0XRdVBfBLTwghuN1uUYBNiDHGn4uiQJZl50GmaYJSCm3bou/700BBT4QQAL57IaUEAOR57kEAMAxDHMRaG1wuQc45aK1fBqwgz+wHpJSCUuoayBJ01P6/djHG/jR1hzWDMQbOuZedvak7XAljzAMe/xGlFEmSeP9wJVv/SGsNzjmcc2iaJg6yBbLWghASqHf0dEkpAwl6thpOGWGl1O46iIZQSsE5Dxp9OsQ5h3meV/FxHP05erdaa1HX9W7OW2TlC31ceRWbb5+AAAAAAElFTkSuQmCC);background-position:bottom right;background-repeat:no-repeat;cursor:se-resize}.bk-canvas-wrapper:hover .bk-resize-popup{display:block}.bk-sidebar.bk-logo{margin:5px auto}.bk-logo{position:relative;display:block;background-repeat:no-repeat}.bk-logo.grey{filter:url("data:image/svg+xml;utf8,<svgxmlns=\'http://www.w3.org/2000/svg\'><filterid=\'grayscale\'><feColorMatrixtype=\'matrix\'values=\'0.33330.33330.3333000.33330.33330.3333000.33330.33330.33330000010\'/></filter></svg>#grayscale");filter:gray;-webkit-filter:grayscale(100%)}.bk-logo-notebook{display:inline-block;vertical-align:middle;margin-right:5px}.bk-banner>span{display:inline-block;vertical-align:middle}.bk-logo-small{width:20px;height:20px;background-image:url(data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAABQAAAAUCAYAAACNiR0NAAAABHNCSVQICAgIfAhkiAAAAAlwSFlzAAALEgAACxIB0t1+/AAAABx0RVh0U29mdHdhcmUAQWRvYmUgRmlyZXdvcmtzIENTNui8sowAAAOkSURBVDiNjZRtaJVlGMd/1/08zzln5zjP1LWcU9N0NkN8m2CYjpgQYQXqSs0I84OLIC0hkEKoPtiH3gmKoiJDU7QpLgoLjLIQCpEsNJ1vqUOdO7ppbuec5+V+rj4ctwzd8IIbbi6u+8f1539dt3A78eXC7QizUF7gyV1fD1Yqg4JWz84yffhm0qkFqBogB9rM8tZdtwVsPUhWhGcFJngGeWrPzHm5oaMmkfEg1usvLFyc8jLRqDOMru7AyC8saQr7GG7f5fvDeH7Ej8CM66nIF+8yngt6HWaKh7k49Soy9nXurCi1o3qUbS3zWfrYeQDTB/Qj6kX6Ybhw4B+bOYoLKCC9H3Nu/leUTZ1JdRWkkn2ldcCamzrcf47KKXdAJllSlxAOkRgyHsGC/zRday5Qld9DyoM4/q/rUoy/CXh3jzOu3bHUVZeU+DEn8FInkPBFlu3+nW3Nw0mk6vCDiWg8CeJaxEwuHS3+z5RgY+YBR6V1Z1nxSOfoaPa4LASWxxdNp+VWTk7+4vzaou8v8PN+xo+KY2xsw6une2frhw05CTYOmQvsEhjhWjn0bmXPjpE1+kplmmkP3suftwTubK9Vq22qKmrBhpY4jvd5afdRA3wGjFAgcnTK2s4hY0/GPNIb0nErGMCRxWOOX64Z8RAC4oCXdklmEvcL8o0BfkNK4lUg9HTl+oPlQxdNo3Mg4Nv175e/1LDGzZen30MEjRUtmXSfiTVu1kK8W4txyV6BMKlbgk3lMwYCiusNy9fVfvvwMxv8Ynl6vxoByANLTWplvuj/nF9m2+PDtt1eiHPBr1oIfhCChQMBw6Aw0UulqTKZdfVvfG7VcfIqLG9bcldL/+pdWTLxLUy8Qq38heUIjh4XlzZxzQm19lLFlr8vdQ97rjZVOLf8nclzckbcD4wxXMidpX30sFd37Fv/GtwwhzhxGVAprjbg0gCAEeIgwCZyTV2Z1REEW8O4py0wsjeloKoMr6iCY6dP92H6Vw/oTyICIthibxjm/DfN9lVz8IqtqKYLUXfoKVMVQVVJOElGjrnnUt9T9wbgp8AyYKaGlqingHZU/uG2NTZSVqwHQTWkx9hxjkpWDaCg6Ckj5qebgBVbT3V3NNXMSiWSDdGV3hrtzla7J+duwPOToIg42ChPQOQjspnSlp1V+Gjdged7+8UN5CRAV7a5EdFNwCjEaBR27b3W890TE7g24NAP/mMDXRWrGoFPQI9ls/MWO2dWFAar/xcOIImbbpA3zgAAAABJRU5ErkJggg==)}.bk-logo-medium{width:35px;height:35px;background-image:url(data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAACMAAAAjCAYAAAAe2bNZAAAABHNCSVQICAgIfAhkiAAAAAlwSFlzAAAK6wAACusBgosNWgAAABx0RVh0U29mdHdhcmUAQWRvYmUgRmlyZXdvcmtzIENTNui8sowAAAf9SURBVFiFvZh7cFTVHcc/59y7793sJiFAwkvAYDRqFWwdraLVlj61diRYsDjqCFbFKrYo0CltlSq1tLaC2GprGIriGwqjFu10OlrGv8RiK/IICYECSWBDkt3s695zTv9IAtlHeOn0O7Mzu797z+/3Ob/z+p0VfBq9doNFljuABwAXw2PcvGHt6bgwxhz7Ls4YZNVXxxANLENwE2D1W9PAGmAhszZ0/X9gll5yCbHoOirLzmaQs0F6F8QMZq1v/8xgNm7DYwwjgXJLYL4witQ16+sv/U9HdDmV4WrKw6B06cZC/RMrM4MZ7xz61DAbtzEXmAvUAX4pMOVecg9/MFFu3j3Gz7gQBLygS2RGumBkL0cubiFRsR3LzVBV1UMk3IrW73PT9C2lYOwhQB4ClhX1AuKpjLcV27oEjyUpNUJCg1CvcejykWTCXyQgzic2HIIBjg3pS6+uRLKAhumZvD4U+tq0jTrgkVKQQtLekfTtxIPAkhTNF6G7kZm7aPp6M9myKVQEoaYaIhEQYvD781DML/RfBGNZXAl4irJiwBa07e/y7cQnBaJghIX6ENl2GR/fGCBoz6cm5qeyEqQA5ZYA5x5eeiV0Qph4gjFAUSwAr6QllQgcxS/Jm25Cr2Tmpsk03XI9NfI31FTZBEOgVOk51adqDBNPCNPSRlkiDXbBEwOU2WxH+I7itQZ62g56OjM33suq1YsZHVtGZSUI2QdyYgkgOthQNIF7BIGDnRAJgJSgj69cUx1gB8PkOGwL4E1gPrM27gIg7NlGKLQApc7BmEnAxP5g/rw4YqBrCDB5xHkw5rdR/1qTrN/hKNo6YUwVDNpFsnjYS8RbidBPcPXFP6R6yfExuOXmN4A3jv1+8ZUwgY9D2OWjUZE6lO88jDwHI8ZixGiMKSeYTBamCoDk6kDAb6y1OcH1a6KpD/fZesoFw5FlIXAVCIiH4PxrV+p2npVDToTBmtjY8t1swh2V61E9KqWiyuPEjM8dbfxuvfa49Zayf9R136Wr8mBSf/T7bNteA8zwaGEUbFpckWwq95n59dUIywKl2fbOIS5e8bWSu0tJ1a5redAYfqkdjesodFajcgaVNWhXo1C9SrkN3Usmv3UMJrc6/DDwkwEntkEJLe67tSLhvyzK8rHDQWleve5CGk4VZEB1r+5bg2E2si+Y0QatDK6jUVkX5eg2YYlp++ZM+rfMNYamAj8Y7MAVWFqaR1f/t2xzU4IHjybBtthzuiAASqv7jTF7jOqDMAakFHgDNsFyP+FhwZHBmH9F7cutIYkQCylYYv1AZSqsn1/+bX51OMMjPSl2nAnM7hnjOx2v53YgNWAzHM9Q/9l0lQWPSCBSyokAtOBC1Rj+w/1Xs+STDp4/E5g7Rs2zm2+oeVd7PUuHKDf6A4r5EsPT5K3gfCnBXNUYnvGzb+KcCczYYWOnLpy4eOXuG2oec0PBN8XQQAnpvS35AvAykr56rWhPBiV4MvtceGLxk5Mr6A1O8IfK7rl7xJ0r9kyumuP4fa0lMqTBLJIAJqEf1J3qE92lMBndlyfRD2YBghHC4hlny7ASqCeWo5zaoDdIWfnIefNGTb9fC73QDfhyBUCNOxrGPSUBfPem9us253YTV+3mcBbdkUYfzmHiLqZbYdIGHHON2ZlemXouaJUOO6TqtdHEQuXYY8Yt+EbDgmlS6RdzkaDTv2P9A3gICiq93sWhb5mc5wVhuU3Y7m5hOc3So7qFT3SLgOXHb/cyOfMn7xROegoC/PTcn3v8gbKPgDopJFk3R/uBPWQiwQ+2/GJevRMObLUzqe/saJjQUQTTftEVMW9tWxPgAocwcj9abNcZe7s+6t2R2xXZG7zyYLp8Q1PiRBBHym5bYuXi8Qt+/LvGu9f/5YDAxABsaRNPH6Xr4D4Sk87a897SOy9v/fKwjoF2eQel95yDESGEF6gEMwKhLwKus3wOVjTtes7qzgLdXTMnNCNoEpbcrtNuq6N7Xh/+eqcbj94xQkp7mdKpW5XbtbR8Z26kgMCAf2UU5YEovRUVRHbu2b3vK1UdDFkDCyMRQxbpdv8nhKAGIa7QaQedzT07fFPny53R738JoVYBdVrnsNx9XZ9v33UeGO+AA2MMUkgqQ5UcdDLZSFeVgONnXeHqSAC5Ew1BXwko0D1Zct3dT1duOjS3MzZnEUJtBuoQAq3SGOLR4ekjn9NC5nVOaYXf9lETrUkmOJy3pOz8OKIb2A1cWhJCCEzOxU2mUPror+2/L3yyM3pkM7jTjr1nBOgkGeyQ7erxpdJsMAS9wb2F9rzMxNY1K2PMU0WtZV82VU8Wp6vbKJVo9Lx/+4cydORdxCCQ/kDGTZCWsRpLu7VD7bfKqL8V2orKTp/PtzaXy42jr6TwAuisi+7JolUG4wY+8vyrISCMtRrLKWpvjAOqx/QGhp0rjRo5xD3x98CWQuOQN8qumRMmI7jKZPUEpzNVZsj4Zbaq1to5tZZsKIydLWojhIXrJnES79EaOzv3du2NytKuxzJKAA6wF8xqEE8s2jo/1wd/khslQGxd81Zg62Bbp31XBH+iETt7Y3ELA0iU6iGDlQ5mexe0VEx4a3x8V1AaYwFJgTiwaOsDmeK2J8nMUOqsnB1A+dcA04ucCYt0urkjmflk9iT2v30q/gZn5rQPvor4n9Ou634PeBzoznes/iot/7WnClKoM/+zCIjH5kwT8ChQjTHPIPTjFV3PpU/Hx+DM/A9U3IXI4SPCYAAAAABJRU5ErkJggg==)}.bk-logo-large{width:75px;height:75px;background-image:url(data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAAEsAAABLCAYAAAA4TnrqAAAABHNCSVQICAgIfAhkiAAAAAlwSFlzAAALEgAACxIB0t1+/AAAABx0RVh0U29mdHdhcmUAQWRvYmUgRmlyZXdvcmtzIENTNui8sowAABNHSURBVHiczZx5nFxVlce/576q6uqq6q7e0t0habIRgQScfEBAJ4MLo4gogY9CAkkIApElCqOCI8IAKriMg6MwoqiBgERMIJEECCoIKKIYWcImS9KEJCxJOr2kt1rfu2f+eN2d7qS7tu4Efp9Pf7rqvvvOOfV759577r3nPuG9hF/PmQXmZEQ/CkwEtqLyV8Q+yPz7nn03TFLVgc/ybhiwD+469WiQLwOfAmqHqbEb1bWo/JCFa148kKa9t8hafuqVGLkWCBVQuxvVy1mw9hf726x+vDfIOqMxzCdm3U4sPK/oe1V/wIK1X98PVg2j6t0m63uzx2O95YyLn0BlFKwtRcrPmb/morE2bW8MJsvsb2X74PsfmoToAwScEwgFAM17ywi4kLtO+78xtCwvDixZPzjuGMQ8CRxFwIFgALRksgC+xF2n/WyMrMuLMWmG9zxLRQjGIYxjGFc5tJLsOX8+/Ij1LbX/g2gDVqE8BI3VYEdFVj9uYf6ai8dC0N4Y3AwDpQpZs4GZYjgVmCNwEBAFIsPV3ZXFa4wmo2QDEMr6hcGSVe8L8S7il0sitM66l2Ci1S/UXqz2kEgYKmKdTJray+lzukejpmiLVz9NNBjgOoRLASdffQGs0tOTDbyO0WkDF8rGiCwnk6FnQged0xcRTC4aKPcdopfaWkNNdRtepouVq5pRfQbVFZw1d2Oxqorqs377DNXBIL9H+AoFENVvc9CwvTVV9iLSN+oZ6euvijV3L5hsM11Nt9F+pIs6DAi0FkSgujpK/bhygsGJwAxgDiLfwpiXWLnqTn5z96Si1BVacd0GAo7DauDfilEAINDd3FnxGkb9Dj3ggFMQ1yPDZO6h9cjraX//SYidgFhftrVQXg4NDVAV90nbdxAJAgsx5klWrPpowSoLregKF4vwsULrD4Zj2NKbLOtA1H/4AQeMKXUk9Ah2XcimT19BYuJ1ONnJgE+SMVBTA/XjIFzml+XWMR7ht6y458hCFBdE1r0bKBfhskLqDocywyuo+LpUIRQAp6SBeBOBnuP5+9V3E3bvx7hNA94UiUBjA8QrfW8qPNCtRmQZd63MO90qiCwHPgYU1b4HI2R4hUxwC4r/9EuJr1RvoXzzUTx30RYan38O0RkD3lTb503BYCHeNByOxphT8lUqrBkaPl2s9sEYF2ETGccftgUIOsV07i2ozmfB2ot5+gaoeesh0ElDvKmy0q85qgBXFuarURBZIkwZhRWp2nq2EvKCoOAU41n6R6x+hAVrf8MNP6qi+uUHsfYIHGcsvGkohKNZsbw8V5W8ZH34mwQzLvVSeqy/E2gj4AZR/P4qv7As8A3a3E+ycO2r/PDGCpQ/YPV4YrE93qQ6epL2oAYNTc9VIW9kOLOJirdaiU+q952iBNteF8Hlu24YFEJBvymOLOdFVJewYO0TACy5KkA2u5RQ6FiqqiAW9WuVtlKRC2WINOSqkNezGuLYzl681i4/liwWqmz1NXm9gBJ0GHlKqrcAsweIOuPcIJNqfkUsNpfGBqiIjbU3DUYAqMlXISdCIeJA3a5OqIxAtAy8Ih6qgL8M7Ho7CQVdAk5wnx+r+g7IpSxYu3qg7MabY4isJBY9mVjMLxt7b9obORXk9SwRykWIWgs7OsDT4pYqFPw5mKcOASME9oncH/Y78TWrh5QKh1MVL6eiYiOqqf3kTUUhr2dpX3dsDHQloLUTGqvALewhewov+Z8sOI7imP5lmTToNSTlBs6/b19pl37xKeAElq+qxrF1iEwGZoAciXA40AjUAZUFWTIGyEvWG0HKJmcJO/h9VstuiIUhEs7fKlR5Wyy7AD9iLws6fReeBZawYO36vBYuPL0D6AA2AQ8PlC//dTmB0DRgKiJTQN8HchjooSCNFDjRLwZ5yTq/I3Tc47EMGfG1uxa2d8DUhpHmqHsgwitzjiLha3KEsqBB9Zdk7OWce3/XqCxfuCCJ77UvDZQtXSZURCpQUw8cBnIEwmHAwYP+giMLzd3Wc5KVWhaZH0o5N0UwyTWxlDUQdQx0JWFXFzRUg+flUN3fXwGEAq9izGc4a826XDpHhcXnKtDV99cMPDDk+jNX19E2oZHuimNxAx9AZCb+wmUVSB0BN5NL/IhkpZZFFojIHR7qHJkKJjyrq1dVpY+PWaYYgZZOqCz3V4e9kZ/HawOfvvbEDmC/EHXQT1+emOnNzPLSWqtWRVURI2qMGHHMeFQCoiYrv5OsqHFVbJ2KdYCXbTDb7oYyh9hAts64gY92wX0j6RmWrNSyyJlGzJ2AKJAVjcxKBz+yrce7fkPEvb5MaHA92L4bJtePGGMqyhtjQ0duROJl4Ug8fKebtlVu2sXLeHiuxctarKeIKCoW6V/pGDSciw0QSgZADYj9cu33n9vYdsWsW4bTs0/okFoWWdBP1ODyrGHSnN7yU7Ien3LhZcdAZy+0dvmR/TDoPlBkNS+Y1uyEnEvClSFi4yLEJ1RQNaGCqgkx4uOjRGvKCUWCmIAgBtQq1lPUKqqKiqLGQ0UF5KfV39swZzg9QwhJLYvMM2LuIkf8FczKFytmdq+8so1/oEw1BqaPh7LQPqPj5myGmZ87htRYEFIIDl259XaEc6Av3hEZcHtVnxwvY3EzLm7a4mU9vIz/fwiEXuvZE3f/11F/G3ZHOn179GhB/gLknHkDGsw4x5w+uXPnB9I86lmmxyMwpWHorpYqT5w6i+NH+fuLwrTlr9cEQoH1wCEj1RERMH0bKZ5irWJdi5tyyaY9vIyHdS3W1R29HcmTkv993PP99xqA5K3hqKgsIz9RAJINeatXvVjX3g3HO8ITnYl9m6MIL5T6o0vF6wuntVu155Njmq6qqOc3QwQcxxAMByivClPZGKW6qZKqCRXED4o1jp9R99tD79l2UP+9BsAxznkIBa1D92FStib1s++ewk5VTjPCUzt3QzLjR/q+VRS91TQW2DRvyuOKfqegyoOap1qfRLWKE3QIRYOUxUJTQX/aX92kbi2vQuS8oq0SFqWXRRddeSptBj6RdvndO+0MPFPlwHtWP1Kkvqmqfyn1/sEECnLqoXdvXQhgjGOOB95filARuTF1W2z6FXPo3LaLOZ29rGjtBseQQXmrVGNHi21zD/Osp58Hdo+FPBG57JCVWyoMyL9SeoJIlRGWJW6NOUu/gHv1HSxo6+bB7iTJujitY2FoqWieP2Wzqn51jMTNckTOMKo0jUqMMNsxXAvAvdi3Wjm9tZPzurpIjoWVo8HGeZOXKXrnGImbJ+llsZtFWDIqMUpW0U+Undv75zEybMww7e7N1QGcZ2BUmy4Au4zAk6O2SAiKyM2p2yoO2NpSLnx1+lUnXdB0aRPA63OndljPfp48q6AFYJxR1T8B20YpCGCmiD2gmXgjoSZas7giXPH4/PrzDgfYdNaUx1X1+tFJ1S2m7LzetzTHTLsYiMiizO2x+WMhq1R8+7DrTNAJ1MXL45MrwhX3LWq8cBpAWtPXKTxRqlyFJ/rzD24CesfEWuVHqdvKp46JrBIQCEerrdpJZaEyqiPVh4QCwYcumHjJkVvPPNRV9EL8ta5i4aGy3ACUndu7yaq9CsEPIkaTPCnUG+Ms7bmtesyXdQs0oB5ostZSFa0iWhadaoXHvtB0yfGb5k5+2ap+pViJCus8Yx4ZiK/C5yZu1KS9R3usvx/cT1z/n1AMiR8LSuaA5KnvDRXbRN/6uyDUxGpwjKkVZM1F4y/60KZ5k29TkbuLEJlVa7/VfPpEd0gwmlqX+LzX4v7R25HFbs9iW1x0t4cmFXXVH0+MFESeiHw7fXvsgK469On9YP9nq5ZwIEx1eTWK1miw7KEl9ReeuvGMg+epSHMh8hS9fOO8yc/CXpF7/H4S6tqFePYFTSnaY7HtHnaAvCy21UW7PDRpwWMPaYM90Icjyi8SS8MHNJwQlSH5CopSFamiPFSOVRvzwuFV/xmcd+LbH2yai0g2j7ibXjtj0k39X/aZ5kSvd3ci+jkMu4YQ4OF7WKeLbfWwLS7eO1mfyA4P7bJoSsFlMHmHOcHATXvr2K8Qjhj8VVUREWqiNRg/ny7Q2djw4JKDP9vYW199lY6Qk6Dw89fmTvqPwWXDzgnLr3GbrbWfVHQXjgWjezzHkT13eYqmFN3tYXe52B1ZvB1ZvO1ZbKuH9lhI23NSS6N5c5/GAtf8y3fiDHOqzKqlPFhOdaQaqxZEnJ7xB635zJnfkGyk/A9qhtKg6E82zp20z1GXEXd3ot90N/ReFb5I02W/wrFtUpZ9XkLuDsAiqhjdLtCDNSG1EkQlgJIm472JxdFeD/XJDWrUvD56KvIj7JS/D6gf7pqqEo/ESWQSJLNJCARCgXTmO8f+4Pa7/vG1c94MpDNNxvUAfr5x7uRLhpORc9+w9+EjHg5N6ehEqEKZoWg9Vl5Q2KiJsiczOyu2jn96/bu2FNMSO3OWZ3sC4xMPPA0gKk0IZcPVVRQjhppYDdt3b0dR3HAoMO6lTXNnLF+3c9PnPo4XcH7W/NmJI86Tc5IVnLIrjHEEqESoFJiG0eMEoDJFeWWK3YdMfRNhm8JWVDcBL4C+qso7mUymu/Hed3Jsw5aO9qoLL7Re7w3JTMs3gacBVPRQyTFE9zfHqkgV7T3tiBGy5eHwwX96elKoJ3H/vfd/MeeCQu7teykguhKagCaB2Xsy+sSCdoTD4X/uPmtKM8g/VfUtYKOqt6Vm5baSF+VaK89vMMb8WOBMqxlct71i0OWcmXvge1g8EieRTpByUxgx2GCApseeeizfvWN4gGYIjIjUAh8G+TD07apAj0igveOsqTtR3Qg8L8Ir1upWYGfNyi0tuYS2Vy4+ESO3AFMQg+t1ASoA35v1Y+MnheSGquKIQ02shh2dO1AUQUhVV7bTk/ve/UXWSIgBMYGDETkGWABgjICwbffcKW96vdk7ate99cvBN7VULg4GhOsR+RoDni5Ym0Dpa+WiFcBhhRhh1RItixIvj9OeaMcRBwrg4kCTNTyMoGnvYLc3Hde099TgS23xxYeLyE+AE/aUCqoWz0tC3xaJhzvBIZAzzXEwrPpzx0QmQdpNI8g7ec3Mc33/Hgvu6+O8rjSZtsTr6fbEcfWPtjzXf7ktvvhiEfk7Q4jym7S1CaxNIQSMX2ZmFaNaVXGMQ22sFhFB0bz9aD7PSgBjP5r1ba1rxsXrzOBl3Dc81z1p4vrOZoC2qsUHifK/iIxw2NzB2hTWpkCc8SgIganFLoZaa4mEIsTL49rS1ZJ30yZnheoVW7sZo+2kAYiAgtedJtuWxGay3cDZBz3R4RNVef6JgvxpZKIAFGsT+ORI2JfLzFLMsWqpjlRnmmoO7shXt4CcUn1VkJIMGYI+b7IpF68ng2ZcUDoQTmt4bNdf34wsCkWCoWtF5Mr8wiyu1wsYHPUyAKo6odQ+w4jpriyL5T3lWsh+4SMl2jDYGt+butK47UmfKJGUBjKfqX+s9fG2qsVHRUNljxZGlN/feF4XIDh42VPmrI+P5siMopvTmeSOvD8jryTLOqC0s8VC30jnkm1L4HWn+z3Mw0kvaPhj59/a4ouX9GXvzC5UqGra79xFcHDTkzvfrkMZV5KNgCrPXv3SFfmWa/KTVb3yjW2qOmwmXE70901dKbJtCTTr9R/RyGhFy8k8vHtde+UF94jIzYxwEH14sQbP60HV9b+j1Q3d22YiMuycsDCZ+qtC6hW0be9lvWsV/WthmgEj2Ey/N2X6ksoEjEUj7fND913dFohf8AJGTy9I5hDxBtfrHSDLwxkX8tIfKPlIgfKHbzz71YL2Tgsiq271tqRm9GRFH80tTcAqbmfK75v2eBM4bkKtXBt85OIZWrlzPcL7CtE9FIJisV7vwHeLMSpOMelSg9FmxSs4H6LghJCa1Vu6rJc9GfQaVPdN+hDBJl0/HOjJDJQB4GRTdDTeF1w/bzah9LcRLXHnR7CaxbMp+s8EKMb05boXizbgs1c9e/nLhWsvAe1nTKoXx5wuIv8OTEWp83rStV5v1oJGh5wnFIvZNSVhNh/biZMd7+c/lwa/v0rR1f0U4GFU6Q3FNj4wc/5E1wQjRgsLSlV5CLVXXPncZRvy1x3Dtxy1njW13HSn49nWRFzKjMWYGCJRQGwo3eC0Ns1wmj/0rb7U6VHpEnHIZHbR0/s8IgEC6tISG88j00/rdU3AGLXD/R5L/0EC1ccVXX3lhst+X6jOMXm9Sj/qfrM5CSSBEeKUTtoqZjeL4y0v5Ahrbgie103/M/bEIZxJfNfDrnUFDQxHluJa67599fNf3zk63ft7ojwIbfHF80XkDkbxgESC9PQ+TyazE5GAp/Clafpq8WFNEXhX3p9V27n0LlV7dukSBNUsnpcASKnaM/Y3UXvjgL4/q7bz1hVW9WxKWMnoD0atzWxX7EnT2HjvfjAxJw74m9nqOpcuV9VFQLq4OxVV3exp4tOHsPldyTA88K+xw2+SKB/HP+ZWEKz1VgSdcbOn80be4X5/4V199eau+OJaB5YgcjbD78x4oH9W1VtqO2+950DbB++Ft0nuhfbKL0yyhk8JepIohwApFZ5BdVUy2f34xMzdRTbZscNgsv4fCI1BY5O1DJEAAAAASUVORK5CYII=)}.bk-sidebar{box-sizing:border-box}.bk-button-bar .bk-bs-dropdown{padding:10px 10px 0 5px}.bk-button-bar .bk-bs-dropdown a{color:transparent;font-size:0;display:block;float:left;width:13px;height:13px;margin:5px;background-image:url(data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAAA0AAAANCAYAAABy6+R8AAAABHNCSVQICAgIfAhkiAAAAAlwSFlzAAALEgAACxIB0t1+/AAAABx0RVh0U29mdHdhcmUAQWRvYmUgRmlyZXdvcmtzIENTNui8sowAAAGdSURBVCiRXZJPSxtRFMV/dyYTunBREASRIkKwddPSjYIL+x2K9plR+owR7K7L7vsZutIkZqhO8jZ+h25aQilUkbaUgv2zEOpGpAsZnbld+BJC7ubAvYd77uEeYaTaB25bhJqi44Kcq9LYWDOt/lxVkQG542ZFaQP3gB6wBLwDFoGfKtQ2qua7qhIAJB1XEeUE9CtSPLCxWQbObGxWEL0P+k2Uk3barQBImrrgWvmE8NnGpgaQpO4OcArM2Nhc+d4e8DCUcL6UwZYIk0WYzw9Zy4CnHm+9hNcvJI/+3GhuJUndR+A3yCpoCGQ2NkWfnKQuAMqo5Ih2gakScBeYBj32vE3gw5DqAtBCFGAcuCwBF8CRIFX1SiNf6AGPQXLwSgq7Aq+zMGPLrPdNB16h50+92uvul4MiWlJ4FZShAZxFebQztL0MHHoEICiiBvCrRJiI31wBvii8zQt9WV9f/Zek7sjG5lFzvzsWBvJGYA2Ye1599mOQiKTjZlVpCcwA74En/UQonIqwaX0ihJFKUlcH6sAE8Bdo2tg0B/9S5T8JNaZ11wlT0wAAAABJRU5ErkJggg==)}.bk-button-bar .bk-button-bar-list{margin:0;padding:0}.bk-button-bar-list>li{list-style-type:none;float:left;padding:0;margin:0;position:relative;display:block;overflow:visible;background-color:transparent}.bk-button-bar-list>li:last-child:after{content:"|";font-size:90%;color:lightgray;display:inline-block;float:left;height:28px;line-height:28px;padding:0 3px}.bk-button-bar-list.bk-bs-dropdown:after{content:"|";font-size:90%;color:lightgray;display:inline-block;float:left;height:28px;line-height:28px;padding:0 3px}.bk-button-bar-list[type='help'] li:after{content:"" !important;display:none}.bk-button-bar-list>a:after{content:"|";font-size:90%;color:lightgray;display:inline-block;float:left;height:28px;line-height:28px;padding:0 3px}.bk-button-bar .bk-button-bar-list .bk-bs-dropdown-menu{padding:10px 8px}.bk-button-bar .bk-button-bar-list .bk-bs-dropdown-menu li{float:none;clear:both;font-family:Helvetica,sans-serif;line-height:1.5em}.bk-button-bar .bk-button-bar-list .bk-bs-dropdown-menu li input{margin-right:8px}.bk-button-bar-list .bk-toolbar-button{width:30px;height:28px;padding:5px;border:0;border-radius:0 !important;-moz-border-radius:0 !important;-webkit-border-radius:0 !important;background:transparent !important}.bk-button-bar-list .bk-toolbar-button .bk-btn-icon{display:block;position:relative;height:16px;margin:0;border:0;background-size:contain;background-color:transparent;background-repeat:no-repeat;background-position:center center}.bk-button-bar-list .bk-toolbar-button span.tip{display:none}.bk-button-bar-list .bk-toolbar-button span.tip:before{display:none;content:" ";position:relative;width:100%;background-position:top left;background-repeat:no-repeat}.bk-button-bar-list li::hover .bk-toolbar-button{cursor:pointer;background:transparent !important}.bk-button-bar-list li:hover .bk-toolbar-button span.tip:before{display:inline-block}.bk-button-bar-list li:hover .bk-toolbar-button span.tip{z-index:100;font-size:100%;color:#fff;font-family:'Open Sans',sans-serif;white-space:nowrap;background-color:#818789;border-radius:3px !important;-moz-border-radius:3px !important;-webkit-border-radius:3px !important;display:inline-block;position:relative;top:25px;padding:3px 5px;transition:all .6s ease;-webkit-transition:all .6s ease;-moz-transition:all .6s ease;-o-transition:all .6s ease}.bk-button-bar-list li:hover .bk-toolbar-button span.tip>*{display:block;text-align:left}.bk-button-bar-list li:hover .bk-toolbar-button span.tip span{width:200px;white-space:normal}.bk-button-bar-list .bk-toolbar-button.active{background:#fff;-box-shadow:none !important;-webkit-box-shadow:none !important;-moz-box-shadow:none !important;outline:none !important;border-bottom:2px solid #26aae1}.bk-button-bar>.bk-toolbar-button.active{border-bottom:2px solid #26aae1}.bk-plot-above.bk-toolbar-active{border-bottom:2px solid #e5e5e5}.bk-plot-below.bk-toolbar-active{border-top:2px solid #e5e5e5;padding-bottom:45px}.bk-plot-above.bk-toolbar-active,.bk-plot-below.bk-toolbar-active{height:30px}.bk-plot-above.bk-toolbar-active .bk-logo,.bk-plot-below.bk-toolbar-active .bk-logo{float:left;top:5px;margin:5px 0}.bk-plot-above.bk-toolbar-active .bk-button-bar,.bk-plot-below.bk-toolbar-active .bk-button-bar{float:right;position:relative;top:5px}.bk-plot-above.bk-toolbar-active .bk-button-bar .bk-button-bar-list,.bk-plot-below.bk-toolbar-active .bk-button-bar .bk-button-bar-list{float:left}.bk-plot-above.bk-toolbar-active .bk-button-bar .bk-button-bar-list.bk-bs-dropdown,.bk-plot-below.bk-toolbar-active .bk-button-bar .bk-button-bar-list.bk-bs-dropdown{margin-right:20px}.bk-plot-above.bk-toolbar-active .bk-button-bar .bk-button-bar-list.bk-bs-dropdown:before,.bk-plot-below.bk-toolbar-active .bk-button-bar .bk-button-bar-list.bk-bs-dropdown:before{right:-6px}.bk-plot-above.bk-toolbar-active .bk-button-bar .bk-button-bar-list.bk-bs-dropdown:after,.bk-plot-below.bk-toolbar-active .bk-button-bar .bk-button-bar-list.bk-bs-dropdown:after{right:-12px;position:absolute}.bk-plot-above.bk-toolbar-active .bk-button-bar .bk-button-bar-list .bk-bs-dropdown-menu:after,.bk-plot-below.bk-toolbar-active .bk-button-bar .bk-button-bar-list .bk-bs-dropdown-menu:after{content:""}.bk-plot-above.bk-toolbar-active .bk-button-bar .bk-toolbar-button,.bk-plot-below.bk-toolbar-active .bk-button-bar .bk-toolbar-button{float:left}.bk-plot-above.bk-toolbar-active .bk-button-bar .bk-toolbar-button.help,.bk-plot-below.bk-toolbar-active .bk-button-bar .bk-toolbar-button.help{float:right}.bk-plot-above.bk-toolbar-active .bk-button-bar .bk-toolbar-button.help span.tip,.bk-plot-below.bk-toolbar-active .bk-button-bar .bk-toolbar-button.help span.tip{right:0;text-align:left;width:200px;white-space:normal}.bk-plot-above.bk-toolbar-active .bk-button-bar .bk-toolbar-button.help span.tip>*,.bk-plot-below.bk-toolbar-active .bk-button-bar .bk-toolbar-button.help span.tip>*{margin-left:0;margin-right:0}.bk-plot-above.bk-toolbar-active .bk-button-bar .bk-toolbar-button span.tip,.bk-plot-below.bk-toolbar-active .bk-button-bar .bk-toolbar-button span.tip{top:41px;left:0;z-index:100;position:absolute;width:auto;padding:0 10px 5px 10px}.bk-plot-above.bk-toolbar-active .bk-button-bar .bk-toolbar-button span.tip:before,.bk-plot-below.bk-toolbar-active .bk-button-bar .bk-toolbar-button span.tip:before{top:-7px;left:-5px;width:100%;height:9px;padding:0 10px;background-image:url(data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAAAwAAAAJCAYAAAAGuM1UAAAAGXRFWHRTb2Z0d2FyZQBBZG9iZSBJbWFnZVJlYWR5ccllPAAAA2hpVFh0WE1MOmNvbS5hZG9iZS54bXAAAAAAADw/eHBhY2tldCBiZWdpbj0i77u/IiBpZD0iVzVNME1wQ2VoaUh6cmVTek5UY3prYzlkIj8+IDx4OnhtcG1ldGEgeG1sbnM6eD0iYWRvYmU6bnM6bWV0YS8iIHg6eG1wdGs9IkFkb2JlIFhNUCBDb3JlIDUuNS1jMDE0IDc5LjE1MTQ4MSwgMjAxMy8wMy8xMy0xMjowOToxNSAgICAgICAgIj4gPHJkZjpSREYgeG1sbnM6cmRmPSJodHRwOi8vd3d3LnczLm9yZy8xOTk5LzAyLzIyLXJkZi1zeW50YXgtbnMjIj4gPHJkZjpEZXNjcmlwdGlvbiByZGY6YWJvdXQ9IiIgeG1sbnM6eG1wTU09Imh0dHA6Ly9ucy5hZG9iZS5jb20veGFwLzEuMC9tbS8iIHhtbG5zOnN0UmVmPSJodHRwOi8vbnMuYWRvYmUuY29tL3hhcC8xLjAvc1R5cGUvUmVzb3VyY2VSZWYjIiB4bWxuczp4bXA9Imh0dHA6Ly9ucy5hZG9iZS5jb20veGFwLzEuMC8iIHhtcE1NOk9yaWdpbmFsRG9jdW1lbnRJRD0ieG1wLmRpZDo3NzIwRUFGMDYyMjE2ODExOTdBNUNBNjVEQTY5OTRDRSIgeG1wTU06RG9jdW1lbnRJRD0ieG1wLmRpZDpDQjA4MDBGRDQ3NjExMUU0QjI1NEVEQTlCODRBRDIyNiIgeG1wTU06SW5zdGFuY2VJRD0ieG1wLmlpZDpDQjA4MDBGQzQ3NjExMUU0QjI1NEVEQTlCODRBRDIyNiIgeG1wOkNyZWF0b3JUb29sPSJBZG9iZSBQaG90b3Nob3AgQ1M1LjEgTWFjaW50b3NoIj4gPHhtcE1NOkRlcml2ZWRGcm9tIHN0UmVmOmluc3RhbmNlSUQ9InhtcC5paWQ6N0Y0M0E0Nzk5NDIyNjgxMTk3QTVDQTY1REE2OTk0Q0UiIHN0UmVmOmRvY3VtZW50SUQ9InhtcC5kaWQ6NzcyMEVBRjA2MjIxNjgxMTk3QTVDQTY1REE2OTk0Q0UiLz4gPC9yZGY6RGVzY3JpcHRpb24+IDwvcmRmOlJERj4gPC94OnhtcG1ldGE+IDw/eHBhY2tldCBlbmQ9InIiPz4te1g5AAAAk0lEQVR42mL8//8/AymApamjC5dcJRBPBOJvyIJM2FQCbS0GUm1APAddDkPDv3//3BgZGTuh3Eig5lKcGv78+aPKxMS0HMhkhokBNbcDDfHApoGHmZl5HZAWQrOUGWQIyDBkDYxAqxcBTdPBEQACQMM2AGk+Jqgn64CKA/EFJ1BeC2QoE9B9AUBOPTFxAFTnDxBgAI5eL2ABBdyaAAAAAElFTkSuQmCC);display:block !important}.bk-plot-left.bk-toolbar-active{border-right:2px solid #e5e5e5}.bk-plot-right.bk-toolbar-active{border-left:2px solid #e5e5e5}.bk-plot-left.bk-toolbar-active,.bk-plot-right.bk-toolbar-active{display:block;margin:45px 0 0 0}.bk-plot-left.bk-toolbar-active .bk-logo,.bk-plot-right.bk-toolbar-active .bk-logo{left:6px;margin-bottom:20px}.bk-plot-left.bk-toolbar-active .bk-button-bar,.bk-plot-right.bk-toolbar-active .bk-button-bar{position:relative;left:3px}.bk-plot-left.bk-toolbar-active .bk-button-bar:before,.bk-plot-right.bk-toolbar-active .bk-button-bar:before,.bk-plot-left.bk-toolbar-active .bk-button-bar:after,.bk-plot-right.bk-toolbar-active .bk-button-bar:after{content:" ";display:block;height:0;clear:both}.bk-plot-left.bk-toolbar-active .bk-button-bar .bk-button-bar-list:after,.bk-plot-right.bk-toolbar-active .bk-button-bar .bk-button-bar-list:after{content:" ";height:0;display:block;clear:both}.bk-plot-left.bk-toolbar-active .bk-button-bar .bk-button-bar-list.bk-bs-dropdown:before,.bk-plot-right.bk-toolbar-active .bk-button-bar .bk-button-bar-list.bk-bs-dropdown:before{top:}.bk-plot-left.bk-toolbar-active .bk-button-bar .bk-button-bar-list.bk-bs-dropdown:after,.bk-plot-right.bk-toolbar-active .bk-button-bar .bk-button-bar-list.bk-bs-dropdown:after{content:" \2014";float:none;clear:both;display:block;width:30px;height:8px;line-height:8px;padding:3px 0;text-align:center}.bk-plot-left.bk-toolbar-active .bk-button-bar .bk-button-bar-list>li,.bk-plot-right.bk-toolbar-active .bk-button-bar .bk-button-bar-list>li{clear:both}.bk-plot-left.bk-toolbar-active .bk-button-bar .bk-button-bar-list>li:last-child:after,.bk-plot-right.bk-toolbar-active .bk-button-bar .bk-button-bar-list>li:last-child:after{content:" \2014";float:none;clear:both;display:block;width:30px;height:8px;line-height:8px;padding:3px 0;text-align:center}.bk-plot-left.bk-toolbar-active .bk-button-bar .bk-button-bar-list>li .bk-toolbar-button.active,.bk-plot-right.bk-toolbar-active .bk-button-bar .bk-button-bar-list>li .bk-toolbar-button.active{border-bottom:0;border-right:2px solid #26aae1}.bk-plot-left.bk-toolbar-active .bk-button-bar .bk-button-bar-list>li .bk-toolbar-button.help span.tip:before,.bk-plot-right.bk-toolbar-active .bk-button-bar .bk-button-bar-list>li .bk-toolbar-button.help span.tip:before{left:-57%}.bk-plot-left.bk-toolbar-active .bk-button-bar .bk-button-bar-list>li .bk-toolbar-button span.tip,.bk-plot-right.bk-toolbar-active .bk-button-bar .bk-button-bar-list>li .bk-toolbar-button span.tip{position:absolute;top:4px;left:40px;padding:5px 10px 5px 10px}.bk-plot-left.bk-toolbar-active .bk-button-bar .bk-button-bar-list>li .bk-toolbar-button span.tip:before,.bk-plot-right.bk-toolbar-active .bk-button-bar .bk-button-bar-list>li .bk-toolbar-button span.tip:before{top:2px;left:-19px;width:9px;height:15px;background-image:url(data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAAAkAAAAPCAMAAAABFhU/AAAAA3NCSVQICAjb4U/gAAAAY1BMVEX////////8/Pz5+fn39/f19fX09PTv8fHv7+/t7e7s7Ozp6enn6Onm5ubj4+Ph4eHf39/X2drW1tfMzMzAw8S+wMGusbKorK6orK2nq6ufo6WcoaGYnZ+RlpiJj5GGjI6Bh4n1ho2QAAAAIXRSTlMA//////////////////////////////////////////9G9E6kAAAACXBIWXMAAAsSAAALEgHS3X78AAAAHHRFWHRTb2Z0d2FyZQBBZG9iZSBGaXJld29ya3MgQ1M26LyyjAAAABR0RVh0Q3JlYXRpb24gVGltZQA5LzUvMTTY+fXxAAAAUklEQVQImTXN2xZAIABE0VQUIfdLwvz/V1rL1DztpzOi4EoIQoekNoIaH1AL8EvvoExEUkBWfWZZvyWVzq/vL6kbP9/sKdtPF8vKdMPBN1m5AR+0BAnD6uP50QAAAABJRU5ErkJggg==)}.bk-bs-caret{color:lightgray;display:inline-block;width:0;height:0;position:relative;left:11px;top:3px;vertical-align:middle;border-top:4px solid;border-right:4px solid transparent;border-left:4px solid transparent}.bk-hbox{display:-webkit-box;-webkit-box-orient:horizontal;-webkit-box-align:stretch;display:-moz-box;-moz-box-orient:horizontal;-moz-box-align:stretch;display:-ms-flexbox;display:box;box-orient:horizontal;box-align:stretch;display:flex;display:-webkit-flex;flex-direction:row;flex-wrap:nowrap}.bk-vbox{display:-webkit-box;-webkit-box-orient:vertical;-webkit-box-align:stretch;display:-moz-box;-moz-box-orient:vertical;-moz-box-align:stretch;display:box;box-orient:vertical;box-align:stretch;width:100%;width:auto}.bk-hbox-spacer{margin-right:40px}.bk-vbox-spacer{margin-bottom:auto}.bk-button-bar{margin-top:0;margin-bottom:0;padding-top:0;padding-bottom:2px;position:relative;display:inline-block;vertical-align:middle}.bk-button-bar>.bk-bs-btn{position:relative;float:left}.bk-button-bar>.bk-bs-btn:hover,.bk-button-bar>.bk-bs-btn:focus,.bk-button-bar>.bk-bs-btn:active,.bk-button-bar>.bk-bs-btn.bk-bs-active{z-index:2}.bk-button-bar>.bk-bs-btn:focus{outline:0}.bk-button-bar .bk-bs-btn+.bk-bs-btn,.bk-button-bar .bk-bs-btn+.bk-bs-btn-group,.bk-button-bar .bk-bs-btn-group+.bk-bs-btn,.bk-button-bar .bk-bs-btn-group+.bk-bs-btn-group{margin-left:-1px}.bk-toolbar-button{display:inline-block;margin-bottom:0;font-weight:normal;text-align:center;vertical-align:middle;cursor:pointer;background-image:none;border:1px solid transparent;white-space:nowrap;padding:6px 12px;font-size:14px;line-height:1.42857143;border-radius:4px;-webkit-user-select:none;-moz-user-select:none;-ms-user-select:none;user-select:none;padding:5px 10px;font-size:12px;line-height:1.5;border-radius:3px;color:#333;background-color:#fff;border-color:#ccc}.bk-toolbar-button:focus,.bk-toolbar-button:active:focus,.bk-toolbar-button.bk-bs-active:focus{outline:thin dotted;outline:5px auto -webkit-focus-ring-color;outline-offset:-2px}.bk-toolbar-button:hover,.bk-toolbar-button:focus{color:#333;text-decoration:none}.bk-toolbar-button:active,.bk-toolbar-button.bk-bs-active{outline:0;background-image:none;-webkit-box-shadow:inset 0 3px 5px rgba(0,0,0,0.125);box-shadow:inset 0 3px 5px rgba(0,0,0,0.125)}.bk-toolbar-button.bk-bs-disabled,.bk-toolbar-button[disabled],fieldset[disabled] .bk-toolbar-button{cursor:not-allowed;pointer-events:none;opacity:.65;filter:alpha(opacity=65);-webkit-box-shadow:none;box-shadow:none}.bk-toolbar-button:hover,.bk-toolbar-button:focus,.bk-toolbar-button:active,.bk-toolbar-button.bk-bs-active,.bk-bs-open .bk-bs-dropdown-toggle.bk-toolbar-button{color:#333;background-color:#ebebeb;border-color:#adadad}.bk-toolbar-button:active,.bk-toolbar-button.bk-bs-active,.bk-bs-open .bk-bs-dropdown-toggle.bk-toolbar-button{background-image:none}.bk-toolbar-button.bk-bs-disabled,.bk-toolbar-button[disabled],fieldset[disabled] .bk-toolbar-button,.bk-toolbar-button.bk-bs-disabled:hover,.bk-toolbar-button[disabled]:hover,fieldset[disabled] .bk-toolbar-button:hover,.bk-toolbar-button.bk-bs-disabled:focus,.bk-toolbar-button[disabled]:focus,fieldset[disabled] .bk-toolbar-button:focus,.bk-toolbar-button.bk-bs-disabled:active,.bk-toolbar-button[disabled]:active,fieldset[disabled] .bk-toolbar-button:active,.bk-toolbar-button.bk-bs-disabled.bk-bs-active,.bk-toolbar-button[disabled].bk-bs-active,fieldset[disabled] .bk-toolbar-button.bk-bs-active{background-color:#fff;border-color:#ccc}.bk-toolbar-button .bk-bs-badge{color:#fff;background-color:#333}.bk-canvas-wrapper{position:relative;font-size:12pt;float:left}.bk-canvas{clear:both;position:absolute;font-size:12pt}.bk-canvas-wrapper .bk-canvas-map{position:absolute !important;z-index:-5}.bk-tooltip{position:absolute;padding:5px;border:1px solid #1e4b6c;background-color:#1e4b6c;border-radius:5px;pointer-events:none}.bk-tooltip.bk-left::before{position:absolute;margin:-7px 0 0 0;top:50%;width:0;height:0;border-style:solid;border-width:7px 0 7px 0;border-color:transparent;content:" ";display:block;left:-10px;border-right-width:10px;border-right-color:#1e4b6c}.bk-tooltip.bk-right::after{position:absolute;margin:-7px 0 0 0;top:50%;width:0;height:0;border-style:solid;border-width:7px 0 7px 0;border-color:transparent;content:" ";display:block;right:-10px;border-left-width:10px;border-left-color:#1e4b6c}.bk-tooltip.bk-tooltip-custom.bk-left::before{border-right-color:black}.bk-tooltip.bk-tooltip-custom.bk-right::after{border-left-color:black}.bk-tooltip.bk-tooltip-custom{border-color:black;background-color:white}.bk-tooltip-row-label{color:#9ab9b1;font-family:Helvetica,sans-serif;text-align:right}.bk-tooltip-row-value{color:#e2ddbd;font-family:Helvetica,sans-serif}.bk-tooltip-color-block{width:12px;height:12px;margin-left:5px;margin-right:5px;outline:#ddd solid 1px;display:inline-block}.bk-canvas-map{position:absolute;border:0;z-index:-5}.shading{position:absolute;display:block;border:1px dashed green;z-index:100}.gridplot_container{position:relative}.gridplot_container .gp_plotwrapper{position:absolute}.table_wrap table{display:block;margin:5px;height:300px;overflow-y:scroll}.bk-table{overflow:auto}.bokehdelete{float:right}.plottitle{padding-left:50px;padding-bottom:10px}.bk-toolbar-button.hover:focus{outline:0}.bk-tool-icon-box-select{background-image:url("data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAACQAAAAgCAYAAAB6kdqOAAAAGXRFWHRTb2Z0d2FyZQBBZG9iZSBJbWFnZVJlYWR5ccllPAAAAyRpVFh0WE1MOmNvbS5hZG9iZS54bXAAAAAAADw/eHBhY2tldCBiZWdpbj0i77u/IiBpZD0iVzVNME1wQ2VoaUh6cmVTek5UY3prYzlkIj8+IDx4OnhtcG1ldGEgeG1sbnM6eD0iYWRvYmU6bnM6bWV0YS8iIHg6eG1wdGs9IkFkb2JlIFhNUCBDb3JlIDUuMC1jMDYxIDY0LjE0MDk0OSwgMjAxMC8xMi8wNy0xMDo1NzowMSAgICAgICAgIj4gPHJkZjpSREYgeG1sbnM6cmRmPSJodHRwOi8vd3d3LnczLm9yZy8xOTk5LzAyLzIyLXJkZi1zeW50YXgtbnMjIj4gPHJkZjpEZXNjcmlwdGlvbiByZGY6YWJvdXQ9IiIgeG1sbnM6eG1wPSJodHRwOi8vbnMuYWRvYmUuY29tL3hhcC8xLjAvIiB4bWxuczp4bXBNTT0iaHR0cDovL25zLmFkb2JlLmNvbS94YXAvMS4wL21tLyIgeG1sbnM6c3RSZWY9Imh0dHA6Ly9ucy5hZG9iZS5jb20veGFwLzEuMC9zVHlwZS9SZXNvdXJjZVJlZiMiIHhtcDpDcmVhdG9yVG9vbD0iQWRvYmUgUGhvdG9zaG9wIENTNS4xIE1hY2ludG9zaCIgeG1wTU06SW5zdGFuY2VJRD0ieG1wLmlpZDpBODVDNDBCRjIwQjMxMUU0ODREQUYzNzM5QTM2MjBCRSIgeG1wTU06RG9jdW1lbnRJRD0ieG1wLmRpZDpBODVDNDBDMDIwQjMxMUU0ODREQUYzNzM5QTM2MjBCRSI+IDx4bXBNTTpEZXJpdmVkRnJvbSBzdFJlZjppbnN0YW5jZUlEPSJ4bXAuaWlkOkE4NUM0MEJEMjBCMzExRTQ4NERBRjM3MzlBMzYyMEJFIiBzdFJlZjpkb2N1bWVudElEPSJ4bXAuZGlkOkE4NUM0MEJFMjBCMzExRTQ4NERBRjM3MzlBMzYyMEJFIi8+IDwvcmRmOkRlc2NyaXB0aW9uPiA8L3JkZjpSREY+IDwveDp4bXBtZXRhPiA8P3hwYWNrZXQgZW5kPSJyIj8+hdQ7dQAAAJdJREFUeNpiXLhs5X8GBPgIxAJQNjZxfiD+wIAKGCkUZ0SWZGIYZIAF3YVoPkEHH6kojhUMyhD6jydEaAlgaWnwh9BAgf9DKpfxDxYHjeay0Vw2bHMZw2guG81lwyXKRnMZWlt98JdDTFAX/x9NQwPkIH6kGMAVEyjyo7lstC4jouc69Moh9L42rlyBTZyYXDS00xBAgAEAqsguPe03+cYAAAAASUVORK5CYII=")}.bk-tool-icon-box-zoom{background-image:url("data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAACIAAAAgCAYAAAB3j6rJAAAAGXRFWHRTb2Z0d2FyZQBBZG9iZSBJbWFnZVJlYWR5ccllPAAAAyRpVFh0WE1MOmNvbS5hZG9iZS54bXAAAAAAADw/eHBhY2tldCBiZWdpbj0i77u/IiBpZD0iVzVNME1wQ2VoaUh6cmVTek5UY3prYzlkIj8+IDx4OnhtcG1ldGEgeG1sbnM6eD0iYWRvYmU6bnM6bWV0YS8iIHg6eG1wdGs9IkFkb2JlIFhNUCBDb3JlIDUuMC1jMDYxIDY0LjE0MDk0OSwgMjAxMC8xMi8wNy0xMDo1NzowMSAgICAgICAgIj4gPHJkZjpSREYgeG1sbnM6cmRmPSJodHRwOi8vd3d3LnczLm9yZy8xOTk5LzAyLzIyLXJkZi1zeW50YXgtbnMjIj4gPHJkZjpEZXNjcmlwdGlvbiByZGY6YWJvdXQ9IiIgeG1sbnM6eG1wPSJodHRwOi8vbnMuYWRvYmUuY29tL3hhcC8xLjAvIiB4bWxuczp4bXBNTT0iaHR0cDovL25zLmFkb2JlLmNvbS94YXAvMS4wL21tLyIgeG1sbnM6c3RSZWY9Imh0dHA6Ly9ucy5hZG9iZS5jb20veGFwLzEuMC9zVHlwZS9SZXNvdXJjZVJlZiMiIHhtcDpDcmVhdG9yVG9vbD0iQWRvYmUgUGhvdG9zaG9wIENTNS4xIE1hY2ludG9zaCIgeG1wTU06SW5zdGFuY2VJRD0ieG1wLmlpZDozMjFERDhEMjIwQjIxMUU0ODREQUYzNzM5QTM2MjBCRSIgeG1wTU06RG9jdW1lbnRJRD0ieG1wLmRpZDozMjFERDhEMzIwQjIxMUU0ODREQUYzNzM5QTM2MjBCRSI+IDx4bXBNTTpEZXJpdmVkRnJvbSBzdFJlZjppbnN0YW5jZUlEPSJ4bXAuaWlkOjMyMUREOEQwMjBCMjExRTQ4NERBRjM3MzlBMzYyMEJFIiBzdFJlZjpkb2N1bWVudElEPSJ4bXAuZGlkOjMyMUREOEQxMjBCMjExRTQ4NERBRjM3MzlBMzYyMEJFIi8+IDwvcmRmOkRlc2NyaXB0aW9uPiA8L3JkZjpSREY+IDwveDp4bXBtZXRhPiA8P3hwYWNrZXQgZW5kPSJyIj8+a2Q0KAAAAmVJREFUeNq8V19EpFEUvzOtmKfpJSJKDL2WiLJExKaUEq0eeikiaolZLT2lVUpPydqHqIlIo1ilFOmphxj1miKWWHppnobIt7+zeyZ3jjvz/bnf9OPHd8/9d77z3XN+94ts7ew6SqksWKX+w1GFiLjYdVSAfeAQ2Ag2sf0GvAXT4C/wle1x3lt9UOGBNk6BrYa+FuYIeAWOsmNviGqe6W+q081OmAGvizgh0cpjZ3RjGBFZBpMG+xn4wM8NYJfWFwNXwXrwS96RiIUTwwYn6AxMgb+FvQ5c4zOUxzR4Ce5GLZyo5LfSsQP2G5xQbKO+bWFfoLWinA1OAEcoM2rFRpMe5sloJWgtm4j0iPZcPhVdkOWxBWvZONIi2uc+5sqxbTaO1Ij2o4+5T6JdGy1SF4Kg2mLsi01E/oh2l4+5HTKaNlmTEe0ka40XyNqTsYnIkWiTwC16rMRNci0bR0hJ7w1veizqy9uB5D4ZDZKBtI3WvLCCJoT9E3jHny4j1DdmWOcbrWWjNYuGoqaL2kdmKayTztio7yzTJprz4A/9PuI3a8YMh5IKVC9fetxAY5rB79pNzXdESMJ/GrSjm8/DCTjAgpjQZCDDh5I+w4HuQBBHOsE9USty4KB2KF85m9J+v5XX9KXr3T7fQZS26WefYlcU+ayJlxhDIT40jBnn21hQOPrfgFtEqAhdGETqK7gZ4h/Av4g4Jf5TUoYquQSuqJDhFpEJca3b4EoYOtyyhrSkHTzlcj4R4t4FZ9NL+j6yMzlT/ocZES9aky3D3r6y5t2gaw3xWXgs7XFhdyzsgSpr2fFXgAEAmp2J9DuX/WgAAAAASUVORK5CYII=")}.bk-tool-icon-help{background-image:url("data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAACAAAAAgCAIAAAD8GO2jAAAAGXRFWHRTb2Z0d2FyZQBBZG9iZSBJbWFnZVJlYWR5ccllPAAAAyRpVFh0WE1MOmNvbS5hZG9iZS54bXAAAAAAADw/eHBhY2tldCBiZWdpbj0i77u/IiBpZD0iVzVNME1wQ2VoaUh6cmVTek5UY3prYzlkIj8+IDx4OnhtcG1ldGEgeG1sbnM6eD0iYWRvYmU6bnM6bWV0YS8iIHg6eG1wdGs9IkFkb2JlIFhNUCBDb3JlIDUuMC1jMDYxIDY0LjE0MDk0OSwgMjAxMC8xMi8wNy0xMDo1NzowMSAgICAgICAgIj4gPHJkZjpSREYgeG1sbnM6cmRmPSJodHRwOi8vd3d3LnczLm9yZy8xOTk5LzAyLzIyLXJkZi1zeW50YXgtbnMjIj4gPHJkZjpEZXNjcmlwdGlvbiByZGY6YWJvdXQ9IiIgeG1sbnM6eG1wPSJodHRwOi8vbnMuYWRvYmUuY29tL3hhcC8xLjAvIiB4bWxuczp4bXBNTT0iaHR0cDovL25zLmFkb2JlLmNvbS94YXAvMS4wL21tLyIgeG1sbnM6c3RSZWY9Imh0dHA6Ly9ucy5hZG9iZS5jb20veGFwLzEuMC9zVHlwZS9SZXNvdXJjZVJlZiMiIHhtcDpDcmVhdG9yVG9vbD0iQWRvYmUgUGhvdG9zaG9wIENTNS4xIE1hY2ludG9zaCIgeG1wTU06SW5zdGFuY2VJRD0ieG1wLmlpZDpBODVDNDBDMzIwQjMxMUU0ODREQUYzNzM5QTM2MjBCRSIgeG1wTU06RG9jdW1lbnRJRD0ieG1wLmRpZDpBODVDNDBDNDIwQjMxMUU0ODREQUYzNzM5QTM2MjBCRSI+IDx4bXBNTTpEZXJpdmVkRnJvbSBzdFJlZjppbnN0YW5jZUlEPSJ4bXAuaWlkOkE4NUM0MEMxMjBCMzExRTQ4NERBRjM3MzlBMzYyMEJFIiBzdFJlZjpkb2N1bWVudElEPSJ4bXAuZGlkOkE4NUM0MEMyMjBCMzExRTQ4NERBRjM3MzlBMzYyMEJFIi8+IDwvcmRmOkRlc2NyaXB0aW9uPiA8L3JkZjpSREY+IDwveDp4bXBtZXRhPiA8P3hwYWNrZXQgZW5kPSJyIj8+mR+SmAAAA/BJREFUeNq8lulPU1kUwOnjCS2yL12pFZFRoBU1MYpCRVGD0cREo4kziX/ffDDxkzNmcEUUd0cBGYdhKYXSln0riyz+mNvceb6W15dxMvdDc3rfvb9z7rnn3HMsW1tbWTsPvkaisfFoND4xiTw9M8tkaUmxxWKxV5S7nE6304FsQLDspGB5ZeVDd89wKLz25YvB/pxdu/b6vA0Bv81qNatgY2PjY++nvs/96+vr/C0pLvJWetxOZ3Z2NrYzwzlYE4lGw6NjM7NzzKiqWnug5lB9HWsyKMDwRx2dk1PTyD5v5ZFDgcLCAoMTzM8vvP/YHQqPIpeXlZ4JNumO8o0CTIO+lEjk5+9ubjxRUV6WZW5MTE51POtiY57NdvZ0szioXgG2373XnlhedtgrWppP5ebkpLoukVguKMhPq2N1be3x02ex+AQ6LrWdl+dIKtjc3Lx3/yGecTrs51qCiqLInajs7ftjYHBY3nZRUeE+n++HmmqdEUDaHz1BB75qO3dWQJIK8GN3bx/WXTzfas3N1TrtweMODpdqMhcbPNVY6XZpJ1dWV3/57f7i4lKgvpb7Y0YRNhIzCE0njmvp6H7a9UJL1zqXGMPv7NUqYDsQBIDi07aCnk99rCZmdLdKis3NzQsZS3+6ce1y24Ufr1/1uJxSx+BQSHcyEhAUn8BuK0DqHxhCEifSDhJYyseOHhExjmeOHm6Q8zOzs6neEyiwwJXwWITwIHJS431pKSEEAkMbPDbbP5Ge9p0ABRAscDUWjzPlcblS151uOpk2IkMjo1IuKS5OuwYg4QRcEbluPqeGR8Kv372XD9H+6qq0ywQQuLqwsChC2wz9c//Ayzdvk46yWltbgqn5KHOFX+CqSB9syUgfCo28evtOerk12LxTVksgcDXL9CCfRVZysSSqNmMMhiJVZVzKQySdk5EuHaOIY/LqZlTAMyDzK+NiAQSuUE+QqIhZ/+kQQOCqw27/869BypO/7qDxnls3b5hXEPn7FQCueD1u3gCSwoyXTA5Q0VgcLHCeFnX/vr0E+O/dPTy/Bpf28+07Qt6dl3ftymUDBaD4ramuAr79mgbq65BI0anpme83n6oFCqC/rjb5XPOW0RMgdDzvovJ9D51Io4QgAASbVMCg4yC5yewnnc+pfP+OzkZKEBBQAPVFnw+/tj/AhJ2KvvGQRZ8cpO7KV+SbtoU7oG2h1PE5eLKxrLTEfNvS+eIlJuIWWiPtRqPGq8q353DAb/CiiXPTMHCrphov2f986OmlaiPwF3O8HsqwQ9c6jkdj4bExEXh84lYb/PWZW8f/o/nVBQa9RWR8HC/r2ndqltvlon3Xdmmp46sAAwDlJz2CuiavpwAAAABJRU5ErkJggg==")}.bk-tool-icon-inspector{background-image:url("data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAACAAAAAgCAYAAABzenr0AAADEUlEQVRYR81XXVIaQRCeHqug8CXmBNETaE4gniDwIgpVspxAbxC9ATkBkCpQ8gKeQDiB5AQxNyAvUlrldr7eHxyGXZi1rMJ5opbp7m++7un+htSGF204vsoMoNXrlzSpfWa1oxQfhAegCZGaEtPorHo8znIoJwCt6+td8uk7ApUQCIHTF4BNAWzImq8ap6cP68CsBdDp9i9ZqXM7ML79g/EnCWD+jgMKENKqWT+tXK0CkQqgNRjs0OxpQIqKhoMxaG6/6JeRnK7T6yO2UvVqhYSlLX+ryORfgKn9ORDFIy7ky41yGcwsr0QAQfDH5zucOswx819fs4egI9OFCcD8DjBF7VNbEX0JzdWEt3NHSSASAcCxBDqMgt/623kvyTgNgNjJIfTjk4D4FqaJR1715MjmYAmA5Bx3AwUXQL+t105KaTlcBSC26XRvhjEIoLiq1yqXpr8FAGG16/ug4IT27fxBWu7EiQuAiImJpEMKE6nYM30uAIDDttSUOPfJP7JzbjPhAiBIh9QE67vIvoOi9WJfCwDavf40ulpjbCqmUf+W753ezURuh7Dg1SqflwAEHU6pgfyBq9Y4qx0LG++2fnZ/eUzcstmdM2AWH+jfc+liWdBJfSENf8Lifi3GVwC9mybOfi5dzatWVrbbLIHNva8p5h/16gkaFiLGGxbufkoE6XguwePiXLF3XmMfCUCUAqtKXU7sumd1CowOuJEi3Pg1FBpjitIGhyvVSfvmjci6ZR+rFQfDiPVE2jFYeICQ+PoewwjC5h7CZld6DBdyu6nDSKgzOyIMhmhK5TTqXYbRorZYM46TmpKAAOrGWwSJJekSB1yqJNOzp1Gs7YJ0EDeySDIMtJbQHh6Kf/uFfNFZkolJICRmz0P8DKWZuIG2g1hpok+Mk0Qphs0h9lzMtWRoNvYLuVImUWrmPJDlBKeRBDfATGOpHkhw670QSHWGLLckmF1PTsMlYqMJpyUbiO0weiMMceqLVTcotnMCYAYJJbcuQrVgZFP0NOOJYpr62pf3AmrHfWUG4O7abefGAfwH7EXSMJafOlYAAAAASUVORK5CYII=")}.bk-tool-icon-lasso-select{background-image:url("data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAACQAAAAgCAYAAAB6kdqOAAAAGXRFWHRTb2Z0d2FyZQBBZG9iZSBJbWFnZVJlYWR5ccllPAAAA2hpVFh0WE1MOmNvbS5hZG9iZS54bXAAAAAAADw/eHBhY2tldCBiZWdpbj0i77u/IiBpZD0iVzVNME1wQ2VoaUh6cmVTek5UY3prYzlkIj8+IDx4OnhtcG1ldGEgeG1sbnM6eD0iYWRvYmU6bnM6bWV0YS8iIHg6eG1wdGs9IkFkb2JlIFhNUCBDb3JlIDUuMC1jMDYxIDY0LjE0MDk0OSwgMjAxMC8xMi8wNy0xMDo1NzowMSAgICAgICAgIj4gPHJkZjpSREYgeG1sbnM6cmRmPSJodHRwOi8vd3d3LnczLm9yZy8xOTk5LzAyLzIyLXJkZi1zeW50YXgtbnMjIj4gPHJkZjpEZXNjcmlwdGlvbiByZGY6YWJvdXQ9IiIgeG1sbnM6eG1wTU09Imh0dHA6Ly9ucy5hZG9iZS5jb20veGFwLzEuMC9tbS8iIHhtbG5zOnN0UmVmPSJodHRwOi8vbnMuYWRvYmUuY29tL3hhcC8xLjAvc1R5cGUvUmVzb3VyY2VSZWYjIiB4bWxuczp4bXA9Imh0dHA6Ly9ucy5hZG9iZS5jb20veGFwLzEuMC8iIHhtcE1NOk9yaWdpbmFsRG9jdW1lbnRJRD0ieG1wLmRpZDo3NzIwRUFGMDYyMjE2ODExOTdBNUNBNjVEQTY5OTRDRSIgeG1wTU06RG9jdW1lbnRJRD0ieG1wLmRpZDo1ODBEQzAzNDQ0RTMxMUU0QTE0ODk2NTE1M0M0MkZENCIgeG1wTU06SW5zdGFuY2VJRD0ieG1wLmlpZDo1ODBEQzAzMzQ0RTMxMUU0QTE0ODk2NTE1M0M0MkZENCIgeG1wOkNyZWF0b3JUb29sPSJBZG9iZSBQaG90b3Nob3AgQ1M1LjEgTWFjaW50b3NoIj4gPHhtcE1NOkRlcml2ZWRGcm9tIHN0UmVmOmluc3RhbmNlSUQ9InhtcC5paWQ6OTU0QzIwMUM1RjIxNjgxMUE3QkFFMzhGRjc2NTI3MjgiIHN0UmVmOmRvY3VtZW50SUQ9InhtcC5kaWQ6NzcyMEVBRjA2MjIxNjgxMTk3QTVDQTY1REE2OTk0Q0UiLz4gPC9yZGY6RGVzY3JpcHRpb24+IDwvcmRmOlJERj4gPC94OnhtcG1ldGE+IDw/eHBhY2tldCBlbmQ9InIiPz7r0xDwAAAC9klEQVR42sSXb2hNcRjHz50rt1aslNQitSimq6VESW6SFMvFyJ+UknnhhVhkRIkX/iRbSPMnyt95sblZFvMC02patEKtaE3Km1taqWlxfZ/6Hj39+p17zr3nHJ76dO4953d+53ue5/k9v+ck2jseORHYRDAXpHmcDSar84McNwLegwHQa5soGULENFAPMmApH+5laXVcw9/fwA1wDYyFEbQI7FITl2vTQTPYDnaCj3KyooQJVoNu0BmBGG0zQc71YhAPzQEnGRY/+8R8+QGGVCjcXEqBZQy3tkrQBpYnfRL1EGgEEzzGSB48AT2gT+eCj8nLbQCbDU9lk0USto35Ytov0MWE7C8zTL3kKbiiFsQqWw7VcaBNzD2wGOwJIUabePeB+l9tCloI2i0xlnCsBAfAVyda69Pe1yGbBW4ywVwbB2fBRSc+0y8/5AqSpL0KpqqLo2BHRKHxMnnuFvW/xxUkD65VF76DBpb5OG0vy8rfFVtBrzQbA/f9AzFZ0KT+t0iKiKCNRt7kuMriNAlTq6pvkti33Eq9whh8N0YhUqlPcP9ybRjs1pvrfEv5j8NkyzgFatS5PNjKo+NurinjxtqIhcgedh3cN8SIZ9by6GhBI8YEkuBVHpNXlyAkQyHP2SloG7CJcQW9tOzu3VwFlVyFl8Bn8AZ8AMctnk1RxFHwDtyxCBG7DNbrMGlLoIWVXfaVR8f3ExQsDxf7wpeZwp067eMxaUsOg7fFBiUZsiPgjOX6pCL3zgDbAvZIp8HjIHF2K/VturDVqElhrJ8tShdbFqcUQW4rIK3FfrCpTGHS47wGHZbFEsjM9iPP8M3j/pYPOI+smgV8kZZyxRRr8sfZlh4LOI/0UReiiLPfV4e4/pwlB3571J3GsIKCfHWcp7cyLIzyNfGCHqkzxjaxzR0tV1CiUChYLzzszPndKx3mM0vyH+SqdRrW1UfnIT2Zh7hhtilZ4/wSV1AcOeRntmJXE2dS+9mg5VzV/xRkq1NjYSb8I8AAdTOa+zQjMmsAAAAASUVORK5CYII=")}.bk-tool-icon-pan{background-image:url("data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAACAAAAAgCAYAAABzenr0AAAAGXRFWHRTb2Z0d2FyZQBBZG9iZSBJbWFnZVJlYWR5ccllPAAAAyRpVFh0WE1MOmNvbS5hZG9iZS54bXAAAAAAADw/eHBhY2tldCBiZWdpbj0i77u/IiBpZD0iVzVNME1wQ2VoaUh6cmVTek5UY3prYzlkIj8+IDx4OnhtcG1ldGEgeG1sbnM6eD0iYWRvYmU6bnM6bWV0YS8iIHg6eG1wdGs9IkFkb2JlIFhNUCBDb3JlIDUuMC1jMDYxIDY0LjE0MDk0OSwgMjAxMC8xMi8wNy0xMDo1NzowMSAgICAgICAgIj4gPHJkZjpSREYgeG1sbnM6cmRmPSJodHRwOi8vd3d3LnczLm9yZy8xOTk5LzAyLzIyLXJkZi1zeW50YXgtbnMjIj4gPHJkZjpEZXNjcmlwdGlvbiByZGY6YWJvdXQ9IiIgeG1sbnM6eG1wPSJodHRwOi8vbnMuYWRvYmUuY29tL3hhcC8xLjAvIiB4bWxuczp4bXBNTT0iaHR0cDovL25zLmFkb2JlLmNvbS94YXAvMS4wL21tLyIgeG1sbnM6c3RSZWY9Imh0dHA6Ly9ucy5hZG9iZS5jb20veGFwLzEuMC9zVHlwZS9SZXNvdXJjZVJlZiMiIHhtcDpDcmVhdG9yVG9vbD0iQWRvYmUgUGhvdG9zaG9wIENTNS4xIE1hY2ludG9zaCIgeG1wTU06SW5zdGFuY2VJRD0ieG1wLmlpZDpCRTI5MDhEODIwQjUxMUU0ODREQUYzNzM5QTM2MjBCRSIgeG1wTU06RG9jdW1lbnRJRD0ieG1wLmRpZDpCRTI5MDhEOTIwQjUxMUU0ODREQUYzNzM5QTM2MjBCRSI+IDx4bXBNTTpEZXJpdmVkRnJvbSBzdFJlZjppbnN0YW5jZUlEPSJ4bXAuaWlkOkJFMjkwOEQ2MjBCNTExRTQ4NERBRjM3MzlBMzYyMEJFIiBzdFJlZjpkb2N1bWVudElEPSJ4bXAuZGlkOkJFMjkwOEQ3MjBCNTExRTQ4NERBRjM3MzlBMzYyMEJFIi8+IDwvcmRmOkRlc2NyaXB0aW9uPiA8L3JkZjpSREY+IDwveDp4bXBtZXRhPiA8P3hwYWNrZXQgZW5kPSJyIj8+OXzPwwAAAKNJREFUeNrsVsEKgCAM3cyj0f8fuwT9XdEHrLyVIOKYY4kPPDim0+fenF+3HZi4nhFec+Rs4oCPAALwjDVUsKMWA6DNAFX6YXcMYIERdRWIYBzAZbKYGsSKex6mVUAK8Za0TphgoFTbpSvlx3/I0EQOILO2i/ibegLk/mgVONM4JvuBVizgkGH3XTGrR/xlV0ycbO8qCeMN54wdtVQwSTFwCzAATqEZUn8W8W4AAAAASUVORK5CYII=")}.bk-tool-icon-polygon-select{background-image:url("data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAACQAAAAgCAYAAAB6kdqOAAAAGXRFWHRTb2Z0d2FyZQBBZG9iZSBJbWFnZVJlYWR5ccllPAAAAyRpVFh0WE1MOmNvbS5hZG9iZS54bXAAAAAAADw/eHBhY2tldCBiZWdpbj0i77u/IiBpZD0iVzVNME1wQ2VoaUh6cmVTek5UY3prYzlkIj8+IDx4OnhtcG1ldGEgeG1sbnM6eD0iYWRvYmU6bnM6bWV0YS8iIHg6eG1wdGs9IkFkb2JlIFhNUCBDb3JlIDUuMC1jMDYxIDY0LjE0MDk0OSwgMjAxMC8xMi8wNy0xMDo1NzowMSAgICAgICAgIj4gPHJkZjpSREYgeG1sbnM6cmRmPSJodHRwOi8vd3d3LnczLm9yZy8xOTk5LzAyLzIyLXJkZi1zeW50YXgtbnMjIj4gPHJkZjpEZXNjcmlwdGlvbiByZGY6YWJvdXQ9IiIgeG1sbnM6eG1wPSJodHRwOi8vbnMuYWRvYmUuY29tL3hhcC8xLjAvIiB4bWxuczp4bXBNTT0iaHR0cDovL25zLmFkb2JlLmNvbS94YXAvMS4wL21tLyIgeG1sbnM6c3RSZWY9Imh0dHA6Ly9ucy5hZG9iZS5jb20veGFwLzEuMC9zVHlwZS9SZXNvdXJjZVJlZiMiIHhtcDpDcmVhdG9yVG9vbD0iQWRvYmUgUGhvdG9zaG9wIENTNS4xIE1hY2ludG9zaCIgeG1wTU06SW5zdGFuY2VJRD0ieG1wLmlpZDpFMzNBREIxOTQ0MUExMUU0QTE0ODk2NTE1M0M0MkZENCIgeG1wTU06RG9jdW1lbnRJRD0ieG1wLmRpZDpFMzNBREIxQTQ0MUExMUU0QTE0ODk2NTE1M0M0MkZENCI+IDx4bXBNTTpEZXJpdmVkRnJvbSBzdFJlZjppbnN0YW5jZUlEPSJ4bXAuaWlkOkUzM0FEQjE3NDQxQTExRTRBMTQ4OTY1MTUzQzQyRkQ0IiBzdFJlZjpkb2N1bWVudElEPSJ4bXAuZGlkOkUzM0FEQjE4NDQxQTExRTRBMTQ4OTY1MTUzQzQyRkQ0Ii8+IDwvcmRmOkRlc2NyaXB0aW9uPiA8L3JkZjpSREY+IDwveDp4bXBtZXRhPiA8P3hwYWNrZXQgZW5kPSJyIj8+xB9jgwAAAe5JREFUeNrsmL1LAzEYxu9KUVDBW8RBhRscXNSCoyA6uIl0kYqIXFcXBRdBoYpuDi7iYEFbkFZPpX6sin+BtAhODloHRZTaSkEUUZ/A23rUer275mjFBn40hJA8eZI3ea+iGjn4FL5LCkigHiQ5trM5HEPuQaFQcQhlVpy0GoFWpF2hmKe/lfaUWUHZYsRSM2Vn/9CSQ5LNu2Bq/LI7Qw6KgqSNc5gavywdqgiqRFklyv7doS7q7flrUbYImkG61FvmAU9gBvhLHWUrYIucfwdxM6kNL4fqwBzV18AHOAaNYJo1BsOqDFyiKAp68BA0Cx6BD4yDc8ql+0FC008Gp4HQtttOh6JgAVSDF/BM7WmdZyQCUct6giSTkdYCpqjup+0JghqwaXCMSYhibknFOFQFwnRIl0AbWKXtUSy42wuuIMplNcoewDB9XdyB2gLbYzQTiEKUYtShHjBK9RM6JxOgCZxxvCo2IIohOX/pwMJ1D3STCBWMgTeCZyYQI+I/3jKNmFuNe5d0zyRsSt68yojnOl+UeUEXuAc3dLew67WTs5gYzZUpvtxD3UEurINdam8HDeCIsyNMTB8cCeA344qCsyNrBbFOrfQPxQWHyCkkJhPR8/lcYoJe6XJj98GAXXkIE6IRI+S4lHXoS4ABAP0ljy6tE4wBAAAAAElFTkSuQmCC")}.bk-tool-icon-redo{background-image:url("data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAACgAAAAgCAYAAABgrToAAAAABmJLR0QA/wD/AP+gvaeTAAAACXBIWXMAAAsTAAALEwEAmpwYAAAAB3RJTUUH3wwGEDEBYlsi0wAAAYBJREFUWMPtl71Lw0AYxn9ppVAodKoUBGfHDtJJR0FRFAc5uMEbBFcdBcXi4G5Hhw5ZAkFQHASho07i0L+hUCi4KBSKQsHlLYSS0iQ0rcI9EMjHfTz3e58LCVhZWf1vOVEbup6fBTbkWAOyQEUet4AB8Ao0gabRajATg67nl4ErQAHFiON+AT5QM1p1UzHoen4eOAdOgELC8XtAHbg2WvWnZlCoPQLVKUXpDdhLQtMJMVcRc8sh7TvAA/AEfEj2kCyWgG1gH1ga03fHaNVKbFDIvYdM0AVqQGNS+GUzHUluyyEmV+OQdAID54CXkLI+AwdGq16clbueXwDugM2Qcq8brX6ijLMQOL8MMVc3Wp0mCZ0saMv1/BvZaENVZa6Lqb4Hk0pKfg/sjuzuFaNVZ1L/TNoGJbOHkr+hCsDZnyAYIHkM3AZu9YHFSdnOMDs1gHbgOj9S9tkTdD2/CHzGjIQzL4Lpfs2kTXKUnCU4hmQO+I5Cbl4ES/YfwcrKyiqefgEvB2gLTkQWKgAAAABJRU5ErkJggg==")}.bk-tool-icon-reset{background-image:url("data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAACgAAAAgCAYAAABgrToAAAAAGXRFWHRTb2Z0d2FyZQBBZG9iZSBJbWFnZVJlYWR5ccllPAAAAyRpVFh0WE1MOmNvbS5hZG9iZS54bXAAAAAAADw/eHBhY2tldCBiZWdpbj0i77u/IiBpZD0iVzVNME1wQ2VoaUh6cmVTek5UY3prYzlkIj8+IDx4OnhtcG1ldGEgeG1sbnM6eD0iYWRvYmU6bnM6bWV0YS8iIHg6eG1wdGs9IkFkb2JlIFhNUCBDb3JlIDUuMC1jMDYxIDY0LjE0MDk0OSwgMjAxMC8xMi8wNy0xMDo1NzowMSAgICAgICAgIj4gPHJkZjpSREYgeG1sbnM6cmRmPSJodHRwOi8vd3d3LnczLm9yZy8xOTk5LzAyLzIyLXJkZi1zeW50YXgtbnMjIj4gPHJkZjpEZXNjcmlwdGlvbiByZGY6YWJvdXQ9IiIgeG1sbnM6eG1wPSJodHRwOi8vbnMuYWRvYmUuY29tL3hhcC8xLjAvIiB4bWxuczp4bXBNTT0iaHR0cDovL25zLmFkb2JlLmNvbS94YXAvMS4wL21tLyIgeG1sbnM6c3RSZWY9Imh0dHA6Ly9ucy5hZG9iZS5jb20veGFwLzEuMC9zVHlwZS9SZXNvdXJjZVJlZiMiIHhtcDpDcmVhdG9yVG9vbD0iQWRvYmUgUGhvdG9zaG9wIENTNS4xIE1hY2ludG9zaCIgeG1wTU06SW5zdGFuY2VJRD0ieG1wLmlpZDpCRTI5MDhFMDIwQjUxMUU0ODREQUYzNzM5QTM2MjBCRSIgeG1wTU06RG9jdW1lbnRJRD0ieG1wLmRpZDoyOUMzNDE3NDIwQkIxMUU0ODREQUYzNzM5QTM2MjBCRSI+IDx4bXBNTTpEZXJpdmVkRnJvbSBzdFJlZjppbnN0YW5jZUlEPSJ4bXAuaWlkOkJFMjkwOERFMjBCNTExRTQ4NERBRjM3MzlBMzYyMEJFIiBzdFJlZjpkb2N1bWVudElEPSJ4bXAuZGlkOkJFMjkwOERGMjBCNTExRTQ4NERBRjM3MzlBMzYyMEJFIi8+IDwvcmRmOkRlc2NyaXB0aW9uPiA8L3JkZjpSREY+IDwveDp4bXBtZXRhPiA8P3hwYWNrZXQgZW5kPSJyIj8+kFHGtQAAAm1JREFUeNrMmE9ExFEQx3+7ZYmlLrEsUUTHaEV0iESJVqduXaJr1xKlFB1bdYqoQ9GlFBFdikgpIhLd0rLqUsQqrW2G7+YZr+2993vaHT6H3583M795897M+0U2t3cCR6kh+kA3rtvx7IYoEGfEMSi4GIk4OJgg5ogRot5wzBvBhmaJnI2xqMW7dcQC8UCMWzgX4N1xjF2ALq8OctROiGkiHrhLHDpOoNOLg5xXF0Sn5lmWWCUGiBRRC1K4t4p3pLCuKyVnnXMwAUVJcT+HfFo3SH5ePGPI24TmA1Pl8rJcBGPEvsa5I6KVWDNcmQW824qxqiRhI+bi4IxmWjOYuneH/HvH2Ixmumd8bjNhhad8lxgSzrfp8jUa/L/wlI8KZ3h1T4bdB30Kb9zz4t6YbgurlIMBdoBHUQiGTBx8JYoKPqVe0ftFNInnW8J20SSCjRWM8k8E1S+TNfbZYyQ59yJEg0kjw1QyB42k1iI6ReXLfEWSK8iHJnJVsYqN8jtammuFc/FOr3juU7Ia+39uM7fiuq8aVrEqp+J6BPWzahw8IPLKdTPKUNU4yJ3Fhqb1inu0y7qeRNVYsWkWFkXPl0QZ8iVbohFmW0s2DmY1jSUX8mUPzi1rmoLML2eXsvsgR/FO3JtAix53nNZ96FDlDrasW35eKGniRRPJeywck9VdOjTdayL3Ahv5MC1/xy+Hp1Iq7BGHMHatjOEqMUgMlxmbVsaEOpMk4GSnp0VyCedyLtuMTlhRD1ZaPoRjeejoMf1HE7VUPkW04Jz7Ztm9rGHslM1Hhjl2xlCn+4muQP/77RyHdf799uli5FuAAQC+l5Sj5nEBdwAAAABJRU5ErkJggg==")}.bk-tool-icon-resize{background-image:url("data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAACIAAAAgCAYAAAB3j6rJAAAAGXRFWHRTb2Z0d2FyZQBBZG9iZSBJbWFnZVJlYWR5ccllPAAAAyRpVFh0WE1MOmNvbS5hZG9iZS54bXAAAAAAADw/eHBhY2tldCBiZWdpbj0i77u/IiBpZD0iVzVNME1wQ2VoaUh6cmVTek5UY3prYzlkIj8+IDx4OnhtcG1ldGEgeG1sbnM6eD0iYWRvYmU6bnM6bWV0YS8iIHg6eG1wdGs9IkFkb2JlIFhNUCBDb3JlIDUuMC1jMDYxIDY0LjE0MDk0OSwgMjAxMC8xMi8wNy0xMDo1NzowMSAgICAgICAgIj4gPHJkZjpSREYgeG1sbnM6cmRmPSJodHRwOi8vd3d3LnczLm9yZy8xOTk5LzAyLzIyLXJkZi1zeW50YXgtbnMjIj4gPHJkZjpEZXNjcmlwdGlvbiByZGY6YWJvdXQ9IiIgeG1sbnM6eG1wPSJodHRwOi8vbnMuYWRvYmUuY29tL3hhcC8xLjAvIiB4bWxuczp4bXBNTT0iaHR0cDovL25zLmFkb2JlLmNvbS94YXAvMS4wL21tLyIgeG1sbnM6c3RSZWY9Imh0dHA6Ly9ucy5hZG9iZS5jb20veGFwLzEuMC9zVHlwZS9SZXNvdXJjZVJlZiMiIHhtcDpDcmVhdG9yVG9vbD0iQWRvYmUgUGhvdG9zaG9wIENTNS4xIE1hY2ludG9zaCIgeG1wTU06SW5zdGFuY2VJRD0ieG1wLmlpZDpBODVDNDBCQjIwQjMxMUU0ODREQUYzNzM5QTM2MjBCRSIgeG1wTU06RG9jdW1lbnRJRD0ieG1wLmRpZDpBODVDNDBCQzIwQjMxMUU0ODREQUYzNzM5QTM2MjBCRSI+IDx4bXBNTTpEZXJpdmVkRnJvbSBzdFJlZjppbnN0YW5jZUlEPSJ4bXAuaWlkOjMyMUREOEQ4MjBCMjExRTQ4NERBRjM3MzlBMzYyMEJFIiBzdFJlZjpkb2N1bWVudElEPSJ4bXAuZGlkOkE4NUM0MEJBMjBCMzExRTQ4NERBRjM3MzlBMzYyMEJFIi8+IDwvcmRmOkRlc2NyaXB0aW9uPiA8L3JkZjpSREY+IDwveDp4bXBtZXRhPiA8P3hwYWNrZXQgZW5kPSJyIj8+nIbQ0AAAAIJJREFUeNpiXLhs5X8G7ICRgTYAq31MDIMEwBzyERoCyJhWAN2ej4MqRFiIjUMahczgSyMsNE4PxACBQZlrcAFsuYkcLECpQwZNiIw6ZNQhow4ZdcioQ0YdMuoQerRZkQE/vdqwgypqQD7+MIBuANn9f1CnEcbRXIMjd4zM0QCAAAMAbdAPQaze1JcAAAAASUVORK5CYII=")}.bk-tool-icon-save{background-image:url("data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAACAAAAAgCAYAAABzenr0AAAAGXRFWHRTb2Z0d2FyZQBBZG9iZSBJbWFnZVJlYWR5ccllPAAAAyRpVFh0WE1MOmNvbS5hZG9iZS54bXAAAAAAADw/eHBhY2tldCBiZWdpbj0i77u/IiBpZD0iVzVNME1wQ2VoaUh6cmVTek5UY3prYzlkIj8+IDx4OnhtcG1ldGEgeG1sbnM6eD0iYWRvYmU6bnM6bWV0YS8iIHg6eG1wdGs9IkFkb2JlIFhNUCBDb3JlIDUuMC1jMDYxIDY0LjE0MDk0OSwgMjAxMC8xMi8wNy0xMDo1NzowMSAgICAgICAgIj4gPHJkZjpSREYgeG1sbnM6cmRmPSJodHRwOi8vd3d3LnczLm9yZy8xOTk5LzAyLzIyLXJkZi1zeW50YXgtbnMjIj4gPHJkZjpEZXNjcmlwdGlvbiByZGY6YWJvdXQ9IiIgeG1sbnM6eG1wPSJodHRwOi8vbnMuYWRvYmUuY29tL3hhcC8xLjAvIiB4bWxuczp4bXBNTT0iaHR0cDovL25zLmFkb2JlLmNvbS94YXAvMS4wL21tLyIgeG1sbnM6c3RSZWY9Imh0dHA6Ly9ucy5hZG9iZS5jb20veGFwLzEuMC9zVHlwZS9SZXNvdXJjZVJlZiMiIHhtcDpDcmVhdG9yVG9vbD0iQWRvYmUgUGhvdG9zaG9wIENTNS4xIE1hY2ludG9zaCIgeG1wTU06SW5zdGFuY2VJRD0ieG1wLmlpZDozMjFERDhENjIwQjIxMUU0ODREQUYzNzM5QTM2MjBCRSIgeG1wTU06RG9jdW1lbnRJRD0ieG1wLmRpZDozMjFERDhENzIwQjIxMUU0ODREQUYzNzM5QTM2MjBCRSI+IDx4bXBNTTpEZXJpdmVkRnJvbSBzdFJlZjppbnN0YW5jZUlEPSJ4bXAuaWlkOjMyMUREOEQ0MjBCMjExRTQ4NERBRjM3MzlBMzYyMEJFIiBzdFJlZjpkb2N1bWVudElEPSJ4bXAuZGlkOjMyMUREOEQ1MjBCMjExRTQ4NERBRjM3MzlBMzYyMEJFIi8+IDwvcmRmOkRlc2NyaXB0aW9uPiA8L3JkZjpSREY+IDwveDp4bXBtZXRhPiA8P3hwYWNrZXQgZW5kPSJyIj8+h5hT8AAAAKBJREFUeNpiWbhs5QcGBgZ+hgECTAwDDGAO+AjEjGj4Lw5xUrAAkl3ocr8IhQAzjT3PRu0o+I+EHw65NDDqgJHrABYC8t9JMIuRmiHACS2IKC0LOKH0X1JDAOTzs0BsBs3XlIKz5KSBRCA+RQXLjwNxNDlp4BoQm9Mo7fGPZsNRB4w6YNQBI94BfwfaAV9G08CoA9DbA/xUavkMvRAACDAAaPgYViexODkAAAAASUVORK5CYII=")}.bk-tool-icon-tap-select{background-image:url("data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAACAAAAAgCAYAAABzenr0AAAAGXRFWHRTb2Z0d2FyZQBBZG9iZSBJbWFnZVJlYWR5ccllPAAAA2hpVFh0WE1MOmNvbS5hZG9iZS54bXAAAAAAADw/eHBhY2tldCBiZWdpbj0i77u/IiBpZD0iVzVNME1wQ2VoaUh6cmVTek5UY3prYzlkIj8+IDx4OnhtcG1ldGEgeG1sbnM6eD0iYWRvYmU6bnM6bWV0YS8iIHg6eG1wdGs9IkFkb2JlIFhNUCBDb3JlIDUuMC1jMDYxIDY0LjE0MDk0OSwgMjAxMC8xMi8wNy0xMDo1NzowMSAgICAgICAgIj4gPHJkZjpSREYgeG1sbnM6cmRmPSJodHRwOi8vd3d3LnczLm9yZy8xOTk5LzAyLzIyLXJkZi1zeW50YXgtbnMjIj4gPHJkZjpEZXNjcmlwdGlvbiByZGY6YWJvdXQ9IiIgeG1sbnM6eG1wTU09Imh0dHA6Ly9ucy5hZG9iZS5jb20veGFwLzEuMC9tbS8iIHhtbG5zOnN0UmVmPSJodHRwOi8vbnMuYWRvYmUuY29tL3hhcC8xLjAvc1R5cGUvUmVzb3VyY2VSZWYjIiB4bWxuczp4bXA9Imh0dHA6Ly9ucy5hZG9iZS5jb20veGFwLzEuMC8iIHhtcE1NOk9yaWdpbmFsRG9jdW1lbnRJRD0ieG1wLmRpZDo3NzIwRUFGMDYyMjE2ODExOTdBNUNBNjVEQTY5OTRDRSIgeG1wTU06RG9jdW1lbnRJRD0ieG1wLmRpZDpCOTJBQzE0RDQ0RDUxMUU0QTE0ODk2NTE1M0M0MkZENCIgeG1wTU06SW5zdGFuY2VJRD0ieG1wLmlpZDpCOTJBQzE0QzQ0RDUxMUU0QTE0ODk2NTE1M0M0MkZENCIgeG1wOkNyZWF0b3JUb29sPSJBZG9iZSBQaG90b3Nob3AgQ1M1LjEgTWFjaW50b3NoIj4gPHhtcE1NOkRlcml2ZWRGcm9tIHN0UmVmOmluc3RhbmNlSUQ9InhtcC5paWQ6OTQ0QzIwMUM1RjIxNjgxMUE3QkFFMzhGRjc2NTI3MjgiIHN0UmVmOmRvY3VtZW50SUQ9InhtcC5kaWQ6NzcyMEVBRjA2MjIxNjgxMTk3QTVDQTY1REE2OTk0Q0UiLz4gPC9yZGY6RGVzY3JpcHRpb24+IDwvcmRmOlJERj4gPC94OnhtcG1ldGE+IDw/eHBhY2tldCBlbmQ9InIiPz6eYZ88AAADLklEQVR42rSXf2TUYRzHv7tuGcfE6Vwb5zLSSjEj7Y9KWqfEmFZJP+yPMdKKmUrrn0iUfjhWlLFi6YfNrF+StBoTo39iYkTGco4xxxG59P7k/T2PT8/37nu3bx9ezvPj+zyf5/PreS78bGLS8SmrwE6yje3NHJsDBTALpknBz6JhH3NiYAB0gHqPOVv52wJ6QQ48BzdAttTioRJjdeA8mAHHS2xuk3p+M8M16ipVQE49Ds6CiFO9RLjGONf05QLx6wPQaBlbBlPgJVgkP0ETiIJ2sB/E1XfimjfgBOOlKDUqCGOcqBcQnw6BYW5YTo4wbvQhMmCfGRemC2rBiGXzWUb+kM/NRZ6CHWBM9ce5R61NgX6ayhSJ5EPlItlDRNkz4JbFHf06BkSzHjXxM+gDv1S/mPUo2AXWgt9UUHL/IVhS8yUV1/EbV3o4N+NaoE9Fu/i827K5pNYHnqAVJECShWmAaddpscYFFXwR7vnXBRGlnUN/L6kqKJlxnRUuDbaDBiL+vst5d4gpcpBrqk/2jIgCKVUolhntplzivHmwh4stGOPfwBWwl/2dpp8p7xjQZqFLiQJtauKkivYm+kzccpK57yXfOUe+P23JqAnVbhMFmlXntCWnxbT31am9ZJ4BJifsUmNTqt0cYhA5ypympPg7VkEKunPbVb8cIG+0kyHLJZNR7fUMooUKFHAPkfQo58VLK+RzwRDd4FdWG9mjpaAXzqkJa1R7kQttqEABWXMjOOxxVRfnhRm5URX1prk/0pQHwNcKlchZ+jdpC+hFdVqO0my9Hj5dkYgCn1Rfh/KdlNDHrJhPqlDih+IfBd6qwpOgEqYMsorJ2HtWxtagLJDn/W3KRfPOZhoeBJfZPgVeGKeKrkQBh5dLXl25Ny3pc4/1fkTdbvFqFQgbxWeYD0hXulhQ0pYiM1jG547fcbMQpVnHTZEn9W3ljsCzwHxCdVteNHIZvQa7/7cC7nV6zHIfyFP9EXjFa7YxKAVqPP4bxhhoLWW+z9JyCb6M/MREg59/RlmmXbmneIybB+YC/ay+yrffqEddDzwGvKxxDmzhc0tc80XVgblqFfgjwAAPubcGjAOl1wAAAABJRU5ErkJggg==")}.bk-tool-icon-undo{background-image:url("data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAACgAAAAgCAYAAABgrToAAAAABmJLR0QA/wD/AP+gvaeTAAAACXBIWXMAAAsTAAALEwEAmpwYAAAAB3RJTUUH3wwGEAgO/GCy+AAAAXlJREFUWMPtlr1LQzEUxX+1ohQKuhQK/Sc6SCcdBUVQFCSQwQwOjjoKisXB3a5Ch7c8CA6iKAgddRKHjs6FQtGpUBCEoksK5RE179FPyIEs+bg59+TcJODh4THdSA0qUBDqNLBq2jKQBopmuA50gWegBtSUFN2REAxCnQfOAQEsOC5rAxooKylaQyEYhDoDnACHQDZhmA5QAS6UFJ8DI2hUuwVKA3LIC7BlUzOVgFwRuAcKluEmcAM8AB/Gexgv5oANYPuXtQ1Dsp6YoFHu1bJBCygD1f/Mb4pp3/g2b0lwqV/JVAxyc8CT5VgfgV0lRSdmslngGlizHPeKkuILYDZGzDMLuYqS4iiJ6UxC60GoL02h9VAye506KxiEugC8Rar1Dthxvc+SYsZx3nGEXBPYGzY5JwWNV96BTF/3gZLiahRPnYuCmxFyDaA6trc4CPV3zBiLSor2uD04eb8ZByWHqtz0K/iHkvO9W35SqjiKnP/ne3h4eIwOP9GxagtPmsh6AAAAAElFTkSuQmCC")}.bk-tool-icon-wheel-zoom{background-image:url("data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAACwAAAAgCAYAAABpRpp6AAAAGXRFWHRTb2Z0d2FyZQBBZG9iZSBJbWFnZVJlYWR5ccllPAAAAyRpVFh0WE1MOmNvbS5hZG9iZS54bXAAAAAAADw/eHBhY2tldCBiZWdpbj0i77u/IiBpZD0iVzVNME1wQ2VoaUh6cmVTek5UY3prYzlkIj8+IDx4OnhtcG1ldGEgeG1sbnM6eD0iYWRvYmU6bnM6bWV0YS8iIHg6eG1wdGs9IkFkb2JlIFhNUCBDb3JlIDUuMC1jMDYxIDY0LjE0MDk0OSwgMjAxMC8xMi8wNy0xMDo1NzowMSAgICAgICAgIj4gPHJkZjpSREYgeG1sbnM6cmRmPSJodHRwOi8vd3d3LnczLm9yZy8xOTk5LzAyLzIyLXJkZi1zeW50YXgtbnMjIj4gPHJkZjpEZXNjcmlwdGlvbiByZGY6YWJvdXQ9IiIgeG1sbnM6eG1wPSJodHRwOi8vbnMuYWRvYmUuY29tL3hhcC8xLjAvIiB4bWxuczp4bXBNTT0iaHR0cDovL25zLmFkb2JlLmNvbS94YXAvMS4wL21tLyIgeG1sbnM6c3RSZWY9Imh0dHA6Ly9ucy5hZG9iZS5jb20veGFwLzEuMC9zVHlwZS9SZXNvdXJjZVJlZiMiIHhtcDpDcmVhdG9yVG9vbD0iQWRvYmUgUGhvdG9zaG9wIENTNS4xIE1hY2ludG9zaCIgeG1wTU06SW5zdGFuY2VJRD0ieG1wLmlpZDpCRTI5MDhEQzIwQjUxMUU0ODREQUYzNzM5QTM2MjBCRSIgeG1wTU06RG9jdW1lbnRJRD0ieG1wLmRpZDpCRTI5MDhERDIwQjUxMUU0ODREQUYzNzM5QTM2MjBCRSI+IDx4bXBNTTpEZXJpdmVkRnJvbSBzdFJlZjppbnN0YW5jZUlEPSJ4bXAuaWlkOkJFMjkwOERBMjBCNTExRTQ4NERBRjM3MzlBMzYyMEJFIiBzdFJlZjpkb2N1bWVudElEPSJ4bXAuZGlkOkJFMjkwOERCMjBCNTExRTQ4NERBRjM3MzlBMzYyMEJFIi8+IDwvcmRmOkRlc2NyaXB0aW9uPiA8L3JkZjpSREY+IDwveDp4bXBtZXRhPiA8P3hwYWNrZXQgZW5kPSJyIj8+sFLapAAAA8xJREFUeNq8WH9k1VEU/+67ecTYxKM8xlJiifKIMUqUKMvy1CqbEmUxJZbSlGXTLBuJpYi18dpqStOzacT+WcTXpkiRUjziETEeY9bnzHm5O53vj/te7fDx3r3fc+/9fM/3nHPPvWWP0mOOIlVAC3AQqOc2SRZ4A9Cg58CSNrj1+FEnSIYfPynHTyOQArYCO/jRPPAJGAcmMM9f87vKfG3AF+AucMAgS5LgRZ4CH/mFrARkieAs8Aw4ASSBckaS++jZLOv6El4HjAKDwPoIa28GXgLdFmQv4WcO2BVBnXTmeIxK+D5wzLGXa8D1CGT78NPPhjFlGnjAmBbPSLefx65IBf+eZZ81hfznIfsr+W0eaACa2G3MhbuAt8CUD1kyRIfongDa4affhW4Nu2Oj0d2Bfg+6Y2UIukr2x4ShkAMOMQlNyLcmgVqj7z2wk17UDDosFOOYMOdPQ+dkyBcZFkb8DGxz2ckTwrKHA8g6HMn7gQWjbzsHqZSUmJ8sej6Cq7WzrhkzKVeYnmSEXSBM6I17RZ+WNWRfJ6z7K2xy1umUc7lGDizIkDL+AsNRXs6U3YpOUrRfWwS01K2noIuLzg+iTcFSiFLKlQPi8+aNAIwri24QlstaEM6JdoIsHBOdiyJl9RntfiXazUljEdJb3IKw1F10Q/Krtin0KaSD5Ido77MYK10sG0S4ByjzwW2LRT3pYlxLRBFpGM91/r9kRJuC/FbEnVEmhEwQYRqw7IMuC8LjnAKllSeBhEI0Qc8U636luWinWxYPqoFCnuxmX16VR9ldCvINqOH/NK5alpe8NY8qL5Nnl/GMFJhU6g2SZtqaw1xCkrss2pGEFhLp0CxuGow83+BDdoDn+FP8hJFeYusNlODL9LI/ubKLRRxDKfamuaNWRBx4o9TI49NDD9yjSdn9NKFa5jTGrdrIKpw1FJCtU8h6Rp/HwbVyBNOOSGtKGHJKtGdAao/NBO4aWrecS9mwQiuU8KLoi1nOEfepQ6TsFXVxnnO0NWFZEdVZjK8RaSgXoHtGbihwh4ViCM+LvhaL8VJ3xscdqnwOCk4xhDNKYNRHPOZfCakbzGOS+SWyloX8KsIj4lNScLwIuTsgsq+ASnFkmor4JdJayopKeEHZGOJ8OzMoatIkF0XvxIm5cGhcUtyhVqlrh4rNNoU8fI+jOCUs3cYIk14L63py9yo2D7fyBZ+t3AGuWgTmiFOCuCIvHuHFo6QbCpxm4GLIxZ+880j/K8Lm593EVZqnXF9N8UXIFt7zgwoeunDZCJzju44M+nKlEP4twAAD1RclkNDukAAAAABJRU5ErkJggg==")}
2 /*# sourceMappingURL=bokeh.min.css.map */
1 NO CONTENT: new file 100644
The requested commit or file is too big and content was truncated. Show full diff
1 NO CONTENT: new file 100644
The requested commit or file is too big and content was truncated. Show full diff
1 NO CONTENT: new file 100644
The requested commit or file is too big and content was truncated. Show full diff
1 NO CONTENT: new file 100644
The requested commit or file is too big and content was truncated. Show full diff
1 NO CONTENT: new file 100644
The requested commit or file is too big and content was truncated. Show full diff
1 NO CONTENT: new file 100644
The requested commit or file is too big and content was truncated. Show full diff
1 NO CONTENT: new file 100644
The requested commit or file is too big and content was truncated. Show full diff
1 NO CONTENT: new file 100644
The requested commit or file is too big and content was truncated. Show full diff
1 NO CONTENT: new file 100644
The requested commit or file is too big and content was truncated. Show full diff
1 NO CONTENT: new file 100644
The requested commit or file is too big and content was truncated. Show full diff
@@ -1,240 +1,251
1 1 import ast
2 2 import json
3 3 import requests
4 4 import base64
5 5 import struct
6 6 from struct import pack
7 7 import time
8 8 from django.contrib import messages
9 9 from django.db import models
10 10 from django.urls import reverse
11 11 from django.core.validators import MinValueValidator, MaxValueValidator
12 12
13 13 from apps.main.models import Configuration
14 14
15 SELECTOR_VALUE = (
16 (0, 'disable'),
17 (1, 'enable')
18 )
19
15 20 class GeneratorConfiguration(Configuration):
16 21
17 periode = models.FloatField(
22 periode = models.IntegerField(
18 23 verbose_name='Periode',
19 24 blank=False,
20 25 null=False
21 26 )
22 27
23 delay = models.FloatField(
28 delay = models.IntegerField(
24 29 verbose_name='Delay',
25 30 blank=False,
26 31 null=False
27 32 )
28 33
29 width = models.FloatField(
34 width = models.IntegerField(
30 35 verbose_name='Width',
31 36 blank=False,
32 37 null=False
33 38 )
34 39
35 enable = models.BooleanField(
36 verbose_name='Enable',
40 selector = models.IntegerField(
41 verbose_name='Selector',
42 choices=SELECTOR_VALUE,
37 43 blank=False,
38 44 null=False
39 45 )
40 46
41 47 class Meta:
42 48 db_table = 'generator_configurations'
43 49
44 50 def __str__(self):
45 51 return str(self.label)
46 52
47 53 def get_absolute_url_plot(self):
48 54 return reverse('url_plot_generator_pulses', args=[str(self.id)])
49 55
50 56 def request(self, cmd, method='get', **kwargs):
51 57
52 58 req = getattr(requests, method)(self.device.url(cmd), **kwargs)
53 59 payload = req.json()
54 60
55 61 return payload
56 62
57 63 def status_device(self):
58 64
59 65 try:
60 self.device.status = 0
61 payload = self.request('status')
62 if payload['status']=='enable':
63 self.device.status = 3
66 #self.device.status = 0
67 #payload = self.request('status')
68 payload = requests.get(self.device.url())
69 print(payload)
70 if payload:
71 self.device.status = 1
64 72 elif payload['status']=='disable':
65 73 self.device.status = 2
66 74 else:
67 75 self.device.status = 1
68 76 self.device.save()
69 77 self.message = 'Generator status: {}'.format(payload['status'])
70 78 return False
71 79 except Exception as e:
72 80 if 'No route to host' not in str(e):
73 81 self.device.status = 4
74 82 self.device.save()
75 83 self.message = 'Generator status: {}'.format(str(e))
76 84 return False
77 85
78 86 self.device.save()
79 87 return True
80 88
81 89 def reset_device(self):
82 90
83 91 try:
84 92 payload = self.request('reset', 'post')
85 93 if payload['reset']=='ok':
86 94 self.message = 'Generator restarted OK'
87 95 self.device.status = 2
88 96 self.device.save()
89 97 else:
90 98 self.message = 'Generator restart fail'
91 99 self.device.status = 4
92 100 self.device.save()
93 101 except Exception as e:
94 102 self.message = 'Generator reset: {}'.format(str(e))
95 103 return False
96 104
97 105 return True
98 106
99 107 def stop_device(self):
100 108
101 109 try:
102 110 command = self.device.url() + "stop"
103 111 r = requests.get(command)
104 112 if r:
105 113 self.device.status = 4
106 114 self.device.save()
107 115 self.message = 'Generator stopped'
108 116 else:
109 117 self.device.status = 4
110 118 self.device.save()
111 119 return False
112 120 except Exception as e:
113 121 if 'No route to host' not in str(e):
114 122 self.device.status = 4
115 123 else:
116 124 self.device.status = 0
117 self.message = 'Generator stop: {}'.format(str(e))
125 #self.message = 'Generator stop: {}'.format(str(e))
126 self.message = "Generator can't start, please check network/device connection or IP address/port configuration"
118 127 self.device.save()
119 128 return False
120 129
121 130 return True
122 131
123 132 def start_device(self):
124 print("Entró al start")
133
125 134 try:
126 135 generator = GeneratorConfiguration.objects.get(pk=self)
127 136 print(generator)
128 json_trmode = json.dumps({"periode": generator.periode, "delay": generator.delay, "width": generator.width, "enable": generator.enable})
137 json_trmode = json.dumps({"periode": generator.periode, "delay": generator.delay, "width": generator.width, "selector": generator.selector})
138 print(json_trmode)
129 139 base64_trmode = base64.urlsafe_b64encode(json_trmode.encode('ascii'))
130 140 print(base64_trmode)
131 141 trmode_url = self.device.url() + "trmode?params="
132 142 complete_url_trmode = trmode_url + base64_trmode.decode('ascii')
133 143 print(complete_url_trmode)
134 144 r = requests.get(complete_url_trmode)
135 145
136 146 if r:
137 147 self.device.status = 3
138 148 self.device.save()
139 149 self.message = 'Generator configured and started'
140 150 else:
141 151 return False
142 152 except Exception as e:
143 153 if 'No route to host' not in str(e):
144 154 self.device.status = 4
145 155 else:
146 156 self.device.status = 0
147 self.message = 'Generator start: {}'.format(str(e))
157 #self.message = 'Generator start: {}'.format(str(e))
158 self.message = "Generator can't start, please check network/device connection or IP address/port configuration"
148 159 self.device.save()
149 160 return False
150 161
151 162 return True
152 163
153 164 #def write_device(self, raw=False):
154 165
155 166 if not raw:
156 167 clock = RCClock.objects.get(rc_configuration=self)
157 168 print(clock)
158 169 if clock.mode:
159 170 data = {'default': clock.frequency}
160 171 else:
161 172 data = {'manual': [clock.multiplier, clock.divisor, clock.reference]}
162 173 payload = self.request('setfreq', 'post', data=json.dumps(data))
163 174 print(payload)
164 175 if payload['command'] != 'ok':
165 176 self.message = 'Generator write: {}'.format(payload['command'])
166 177 else:
167 178 self.message = payload['programming']
168 179 if payload['programming'] == 'fail':
169 180 self.message = 'Generator write: error programming CGS chip'
170 181
171 182 values = []
172 183 for pulse, delay in zip(self.get_pulses(), self.get_delays()):
173 184 while delay>65536:
174 185 values.append((pulse, 65535))
175 186 delay -= 65536
176 187 values.append((pulse, delay-1))
177 188 data = bytearray()
178 189 #reset
179 190 data.extend((128, 0))
180 191 #disable
181 192 data.extend((129, 0))
182 193 #SW switch
183 194 if self.control_sw:
184 195 data.extend((130, 2))
185 196 else:
186 197 data.extend((130, 0))
187 198 #divider
188 199 data.extend((131, self.clock_divider-1))
189 200 #enable writing
190 201 data.extend((139, 62))
191 202
192 203 last = 0
193 204 for tup in values:
194 205 vals = pack('<HH', last^tup[0], tup[1])
195 206 last = tup[0]
196 207 data.extend((133, vals[1], 132, vals[0], 133, vals[3], 132, vals[2]))
197 208
198 209 #enable
199 210 data.extend((129, 1))
200 211
201 212 if raw:
202 213 return b64encode(data)
203 214
204 215 try:
205 216 payload = self.request('stop', 'post')
206 217 payload = self.request('reset', 'post')
207 218 #payload = self.request('divider', 'post', data={'divider': self.clock_divider-1})
208 219 #payload = self.request('write', 'post', data=b64encode(bytearray((139, 62))), timeout=20)
209 220 n = len(data)
210 221 x = 0
211 222 #while x < n:
212 223 payload = self.request('write', 'post', data=b64encode(data))
213 224 # x += 1024
214 225
215 226 if payload['write']=='ok':
216 227 self.device.status = 3
217 228 self.device.save()
218 229 self.message = 'Generator configured and started'
219 230 else:
220 231 self.device.status = 1
221 232 self.device.save()
222 233 self.message = 'Generator write: {}'.format(payload['write'])
223 234 return False
224 235
225 236 #payload = self.request('start', 'post')
226 237
227 238 except Exception as e:
228 239 if 'No route to host' not in str(e):
229 240 self.device.status = 4
230 241 else:
231 242 self.device.status = 0
232 243 self.message = 'Generator write: {}'.format(str(e))
233 244 self.device.save()
234 245 return False
235 246
236 247 return True
237 248
238 249
239 250 def get_absolute_url_import(self):
240 251 return reverse('url_import_generator_conf', args=[str(self.id)])
@@ -1,30 +1,30
1 1 {% extends "dev_conf.html" %}
2 2 {% load static %}
3 3 {% load bootstrap4 %}
4 4 {% load main_tags %}
5 5
6 6 {% block content-detail %}
7 7
8 <h2>Pedestal</h2>
8 <h2>Generator</h2>
9 9 <table class="table table-bordered">
10 10 <tr>
11 11 <th>Status</th>
12 12 <td class="text-{{dev_conf.device.status_color}}"><strong>{{dev_conf.device.get_status_display}}</strong></td>
13 13 </tr>
14 14
15 15 {% for key in dev_conf_keys %}
16 16 <tr>
17 17 <th>{% get_verbose_field_name dev_conf key %}</th>
18 18 <td>{{dev_conf|attr:key}}</td>
19 19 </tr>
20 20 {% endfor %}
21 21 </table>
22 22 {% endblock %}
23 23
24 24 {% block extra-js%}
25 25 <script type="text/javascript">
26 26 $("#bt_toggle").click(function() {
27 27 $(".panel-collapse").collapse('toggle')
28 28 });
29 29 </script>
30 30 {% endblock %} No newline at end of file
@@ -1,27 +1,27
1 1 {% extends "dev_conf_edit.html" %}
2 2 {% load bootstrap4 %}
3 3 {% load static %}
4 4
5 5 {% block extra-head %}
6 6 <style type="text/css">
7 7 /* show the move cursor as the user moves the mouse over the panel header.*/
8 8 .panel-default { cursor: move; }
9 9 </style>
10 10 <script src="{% static 'js/jquery-ui.min.js' %}"></script>
11 11
12 12 {% endblock %}
13 13
14 14 {% block content %}
15 15 <form class="form" method="post">
16 16 {% csrf_token %}
17 <h2>Pedestal</h2>
17 <h2>Generator</h2>
18 18 {% bootstrap_form form layout='horizontal' size='medium' %}
19 19 <div style="clear: both;"></div>
20 20 <br>
21 21 <div class="pull-right">
22 22 <button type="button" class="btn btn-primary" onclick="{% if previous %}window.location.replace('{{ previous }}');{% else %}history.go(-1);{% endif %}">Cancel</button>
23 23 <button type="submit" class="btn btn-primary">{{button}}</button>
24 24 </div>
25 25 </form>
26 26 {% endblock %}
27 27 No newline at end of file
@@ -1,136 +1,139
1 1
2 2 import json
3 3
4 4 from django.contrib import messages
5 5 from django.utils.safestring import mark_safe
6 6 from django.shortcuts import render, redirect, get_object_or_404, HttpResponse
7 7 from django.contrib.auth.decorators import login_required
8 8
9 9 from apps.main.models import Experiment, Device
10 10 from apps.main.views import sidebar
11 11
12 12 from .models import GeneratorConfiguration
13 13 from .forms import GeneratorConfigurationForm, GeneratorImportForm
14 14
15 15
16 16 def conf(request, conf_id):
17 17
18 18 conf = get_object_or_404(GeneratorConfiguration, pk=conf_id)
19 19
20 20 kwargs = {}
21 21 kwargs['dev_conf'] = conf
22 kwargs['dev_conf_keys'] = ['periode', 'delay', 'width']
22 kwargs['dev_conf_keys'] = ['periode', 'delay', 'width', 'selector']
23 23
24 24 kwargs['title'] = 'Configuration'
25 25 kwargs['suptitle'] = 'Detail'
26 26
27 27 kwargs['button'] = 'Edit Configuration'
28
29 conf.status_device()
30
28 31 ###### SIDEBAR ######
29 32 kwargs.update(sidebar(conf=conf))
30 33
31 34 return render(request, 'generator_conf.html', kwargs)
32 35
33 36 @login_required
34 37 def conf_edit(request, conf_id):
35 38
36 39 conf = get_object_or_404(GeneratorConfiguration, pk=conf_id)
37 40 print(conf)
38 41 #print("fin de carga de params")
39 42 if request.method=='GET':
40 43 print("GET case")
41 44 form = GeneratorConfigurationForm(instance=conf)
42 45 print(form)
43 46
44 47 elif request.method=='POST':
45 48 #print("ingreso a post conf edit")
46 49 line_data = {}
47 50 conf_data = {}
48 51 clock_data = {}
49 52 extras = []
50 53 print("Inicio impresion POST#####")
51 54 print(request.POST.items)
52 55 print("Fin impresion de POST items#####")
53 56 #classified post fields
54 57 for label,value in request.POST.items():
55 58 if label=='csrfmiddlewaretoken':
56 59 continue
57 60
58 61 if label.count('|')==0:
59 62 if label in ('mode', 'multiplier', 'divisor', 'reference', 'frequency'):
60 63 clock_data[label] = value
61 64 else:
62 65 conf_data[label] = value
63 66 continue
64 67
65 68 elif label.split('|')[0]!='-1':
66 69 extras.append(label)
67 70 continue
68 71
69 72 #print(label)
70 73 x, pk, name = label.split('|')
71 74
72 75 if name=='codes':
73 76 value = [s for s in value.split('\r\n') if s]
74 77
75 78 if pk in line_data:
76 79 line_data[pk][name] = value
77 80 else:
78 81 line_data[pk] = {name:value}
79 82 #print(line_data[pk])
80 83 #update conf
81 84
82 85 form = GeneratorConfigurationForm(conf_data, instance=conf)
83 86
84 87 #print(request.POST.items())
85 88
86 89 if form.is_valid():
87 90 form.save()
88 91
89 92 messages.success(request, 'Generator configuration successfully updated')
90 93
91 94 return redirect(conf.get_absolute_url())
92 95
93 96 kwargs = {}
94 97 kwargs['dev_conf'] = conf
95 98 kwargs['form'] = form
96 99 kwargs['edit'] = True
97 100
98 101 kwargs['title'] = 'Generator Configuration'
99 102 kwargs['suptitle'] = 'Edit'
100 103 kwargs['button'] = 'Update'
101 104
102 105 print(kwargs)
103 106 print(form)
104 107 return render(request, 'generator_conf_edit.html', kwargs)
105 108
106 109 def import_file(request, conf_id):
107 110
108 111 conf = get_object_or_404(GeneratorConfiguration, pk=conf_id)
109 112 if request.method=='POST':
110 113 form = GeneratorImportForm(request.POST, request.FILES)
111 114 if form.is_valid():
112 115 try:
113 116 data = conf.import_from_file(request.FILES['file_name'])
114 117 conf.dict_to_parms(data)
115 118 messages.success(request, 'Configuration "%s" loaded succesfully' % request.FILES['file_name'])
116 119 return redirect(conf.get_absolute_url_edit())
117 120
118 121 except Exception as e:
119 122 messages.error(request, 'Error parsing file: "%s" - %s' % (request.FILES['file_name'], repr(e)))
120 123 else:
121 124 messages.warning(request, 'Your current configuration will be replaced')
122 125 form = GeneratorImportForm()
123 126
124 127 kwargs = {}
125 128 kwargs['form'] = form
126 129 kwargs['title'] = 'Generator Configuration'
127 130 kwargs['suptitle'] = 'Import file'
128 131 kwargs['button'] = 'Upload'
129 132 kwargs['previous'] = conf.get_absolute_url()
130 133
131 134 return render(request, 'generator_import.html', kwargs)
132 135
133 136 def conf_raw(request, conf_id):
134 137 conf = get_object_or_404(GeneratorConfiguration, pk=conf_id)
135 138 raw = conf.write_device(raw=True)
136 139 return HttpResponse(raw, content_type='application/json') No newline at end of file
@@ -1,731 +1,733
1 1
2 2 import os
3 3 import json
4 4 import requests
5 5 import time
6 6 from datetime import datetime
7 7
8 8 try:
9 9 from polymorphic.models import PolymorphicModel
10 10 except:
11 11 from polymorphic import PolymorphicModel
12 12
13 13 from django.template.base import kwarg_re
14 14 from django.db import models
15 15 from django.urls import reverse
16 16 from django.core.validators import MinValueValidator, MaxValueValidator
17 17 from django.shortcuts import get_object_or_404
18 18 from django.contrib.auth.models import User
19 19 from django.db.models.signals import post_save
20 20 from django.dispatch import receiver
21 21
22 22 from apps.main.utils import Params
23 23
24 24
25 25 DEV_PORTS = {
26 26 'pedestal' : 80,
27 'pedestal_dev' : 80,
27 28 'generator' : 80,
28 29 'usrp_rx' : 2000,
29 30 'usrp_tx' : 2000,
30 31 }
31 32
32 33 RADAR_STATES = (
33 34 (0, 'No connected'),
34 35 (1, 'Connected'),
35 36 (2, 'Configured'),
36 37 (3, 'Running'),
37 38 (4, 'Scheduled'),
38 39 )
39 40
40 41 EXPERIMENT_TYPE = (
41 42 (0, 'RAW_DATA'),
42 43 (1, 'PDATA'),
43 44 )
44 45
45 46 DECODE_TYPE = (
46 47 (0, 'None'),
47 48 (1, 'TimeDomain'),
48 49 (2, 'FreqDomain'),
49 50 (3, 'InvFreqDomain'),
50 51 )
51 52
52 53 DEV_STATES = (
53 (0, 'No connected'),
54 (0, 'Unknown'),
54 55 (1, 'Connected'),
55 56 (2, 'Configured'),
56 57 (3, 'Running'),
57 (4, 'Unknown'),
58 (4, 'Offline'),
58 59 )
59 60
60 61 DEV_TYPES = (
61 62 ('', 'Select a device type'),
62 63 ('pedestal', 'Pedestal Controller'),
64 ('pedestal_dev', 'Pedestal Controller Dev Mode'),
63 65 ('generator', 'Pulse Generator'),
64 66 ('usrp_rx', 'Universal Software Radio Peripheral Rx'),
65 67 ('usrp_tx', 'Universal Software Radio Peripheral Tx'),
66 68 )
67 69
68 70 EXP_STATES = (
69 71 (0,'Error'), #RED
70 72 (1,'Cancelled'), #YELLOW
71 73 (2,'Running'), #GREEN
72 74 (3,'Scheduled'), #BLUE
73 75 (4,'Unknown'), #WHITE
74 76 )
75 77
76 78 CONF_TYPES = (
77 79 (0, 'Active'),
78 80 (1, 'Historical'),
79 81 )
80 82
81 83 class Profile(models.Model):
82 84 user = models.OneToOneField(User, on_delete=models.CASCADE)
83 85 theme = models.CharField(max_length=30, default='spacelab')
84 86
85 87
86 88 @receiver(post_save, sender=User)
87 89 def create_user_profile(sender, instance, created, **kwargs):
88 90 if created:
89 91 Profile.objects.create(user=instance)
90 92
91 93 @receiver(post_save, sender=User)
92 94 def save_user_profile(sender, instance, **kwargs):
93 95 instance.profile.save()
94 96
95 97
96 98 class Location(models.Model):
97 99
98 100 name = models.CharField(max_length = 30)
99 101 description = models.TextField(blank=True, null=True)
100 102
101 103 class Meta:
102 104 db_table = 'db_location'
103 105
104 106 def __str__(self):
105 107 return u'%s' % self.name
106 108
107 109 def get_absolute_url(self):
108 110 return reverse('url_location', args=[str(self.id)])
109 111
110 112
111 113 class DeviceType(models.Model):
112 114
113 name = models.CharField(max_length = 10, choices = DEV_TYPES, default = 'pedestal')
115 name = models.CharField(max_length = 15, choices = DEV_TYPES, default = 'pedestal')
114 116 sequence = models.PositiveSmallIntegerField(default=55)
115 117 description = models.TextField(blank=True, null=True)
116 118
117 119 class Meta:
118 120 db_table = 'db_device_types'
119 121
120 122 def __str__(self):
121 123 return u'%s' % self.name.title()
122 124
123 125 class Device(models.Model):
124 126
125 127 device_type = models.ForeignKey('DeviceType', on_delete=models.CASCADE)
126 128 location = models.ForeignKey('Location', on_delete=models.CASCADE)
127 129 ip_address = models.GenericIPAddressField(verbose_name = 'IP address', protocol='IPv4', default='0.0.0.0')
128 130 port_address = models.PositiveSmallIntegerField(default=2000)
129 131 description = models.TextField(blank=True, null=True)
130 132 status = models.PositiveSmallIntegerField(default=4, choices=DEV_STATES)
131 133 conf_active = models.PositiveIntegerField(default=0, verbose_name='Current configuration')
132 134
133 135 class Meta:
134 136 db_table = 'db_devices'
135 137
136 138 def __str__(self):
137 139 ret = self.device_type
138 140 #ret = u'{} [{}]'.format(self.device_type.name.upper(), self.location.name)
139 141 return str(ret)
140 142
141 143 @property
142 144 def name(self):
143 145 return str(self)
144 146
145 147 def get_status(self):
146 148 return self.status
147 149
148 150 @property
149 151 def status_color(self):
150 152 color = 'muted'
151 153 if self.status == 0:
152 154 color = "danger"
153 155 elif self.status == 1:
154 156 color = "warning"
155 157 elif self.status == 2:
156 158 color = "info"
157 159 elif self.status == 3:
158 160 color = "success"
159 161
160 162 return color
161 163
162 164 def url(self, path=None):
163 165
164 166 if path:
165 167 return 'http://{}:{}/{}/'.format(self.ip_address, self.port_address, path)
166 168 else:
167 169 return 'http://{}:{}/'.format(self.ip_address, self.port_address)
168 170
169 171 def get_absolute_url(self):
170 172 return reverse('url_device', args=[str(self.id)])
171 173
172 174 def get_absolute_url_edit(self):
173 175 return reverse('url_edit_device', args=[str(self.id)])
174 176
175 177 def get_absolute_url_delete(self):
176 178 return reverse('url_delete_device', args=[str(self.id)])
177 179
178 180 def change_ip(self, ip_address, mask, gateway, dns, **kwargs):
179 181
180 182 if self.device_type.name=='pedestal':
181 183 headers = {'content-type': "application/json",
182 184 'cache-control': "no-cache"}
183 185
184 186 ip = [int(x) for x in ip_address.split('.')]
185 187 dns = [int(x) for x in dns.split('.')]
186 188 gateway = [int(x) for x in gateway.split('.')]
187 189 subnet = [int(x) for x in mask.split('.')]
188 190
189 191 payload = {
190 192 "ip": ip,
191 193 "dns": dns,
192 194 "gateway": gateway,
193 195 "subnet": subnet
194 196 }
195 197
196 198 req = requests.post(self.url('changeip'), data=json.dumps(payload), headers=headers)
197 199 try:
198 200 answer = req.json()
199 201 if answer['changeip']=='ok':
200 202 self.message = '25|IP succesfully changed'
201 203 self.ip_address = ip_address
202 204 self.save()
203 205 else:
204 206 self.message = '30|An error ocuur when changing IP'
205 207 except Exception as e:
206 208 self.message = '40|{}'.format(str(e))
207 209 else:
208 210 self.message = 'Not implemented'
209 211 return False
210 212
211 213 return True
212 214
213 215
214 216 class Campaign(models.Model):
215 217
216 218 template = models.BooleanField(default=False)
217 219 name = models.CharField(max_length=60, unique=True)
218 220 start_date = models.DateTimeField(blank=True, null=True)
219 221 end_date = models.DateTimeField(blank=True, null=True)
220 222 tags = models.CharField(max_length=40, blank=True, null=True)
221 223 description = models.TextField(blank=True, null=True)
222 224 experiments = models.ManyToManyField('Experiment', blank=True)
223 225 author = models.ForeignKey(User, null=True, blank=True,on_delete=models.CASCADE)
224 226
225 227 class Meta:
226 228 db_table = 'db_campaigns'
227 229 ordering = ('name',)
228 230
229 231 def __str__(self):
230 232 if self.template:
231 233 return u'{} (template)'.format(self.name)
232 234 else:
233 235 return u'{}'.format(self.name)
234 236
235 237 def jsonify(self):
236 238
237 239 data = {}
238 240
239 241 ignored = ('template')
240 242
241 243 for field in self._meta.fields:
242 244 if field.name in ignored:
243 245 continue
244 246 data[field.name] = field.value_from_object(self)
245 247
246 248 data['start_date'] = data['start_date'].strftime('%Y-%m-%d')
247 249 data['end_date'] = data['end_date'].strftime('%Y-%m-%d')
248 250
249 251 return data
250 252
251 253 def parms_to_dict(self):
252 254
253 255 params = Params({})
254 256 params.add(self.jsonify(), 'campaigns')
255 257
256 258 for exp in Experiment.objects.filter(campaign = self):
257 259 params.add(exp.jsonify(), 'experiments')
258 260 configurations = Configuration.objects.filter(experiment=exp, type=0)
259 261
260 262 for conf in configurations:
261 263 params.add(conf.jsonify(), 'configurations')
262 264
263 265 return params.data
264 266
265 267 def dict_to_parms(self, parms, CONF_MODELS):
266 268
267 269 experiments = Experiment.objects.filter(campaign = self)
268 270
269 271 if experiments:
270 272 for experiment in experiments:
271 273 experiment.delete()
272 274
273 275 for id_exp in parms['experiments']['allIds']:
274 276 exp_parms = parms['experiments']['byId'][id_exp]
275 277 dum = (datetime.now() - datetime(1970, 1, 1)).total_seconds()
276 278 exp = Experiment(name='{}'.format(dum))
277 279 exp.save()
278 280 exp.dict_to_parms(parms, CONF_MODELS, id_exp=id_exp)
279 281 self.experiments.add(exp)
280 282
281 283 camp_parms = parms['campaigns']['byId'][parms['campaigns']['allIds'][0]]
282 284
283 285 self.name = '{}-{}'.format(camp_parms['name'], datetime.now().strftime('%y%m%d'))
284 286 self.start_date = camp_parms['start_date']
285 287 self.end_date = camp_parms['end_date']
286 288 self.tags = camp_parms['tags']
287 289 self.save()
288 290
289 291 return self
290 292
291 293 def get_experiments_by_radar(self, radar=None):
292 294
293 295 ret = []
294 296 if radar:
295 297 locations = Location.objects.filter(pk=radar)
296 298 else:
297 299 locations = set([e.location for e in self.experiments.all()])
298 300
299 301 for loc in locations:
300 302 dum = {}
301 303 dum['name'] = loc.name
302 304 dum['id'] = loc.pk
303 305 dum['experiments'] = [e for e in self.experiments.all() if e.location==loc]
304 306 ret.append(dum)
305 307
306 308 return ret
307 309
308 310 def get_absolute_url(self):
309 311 return reverse('url_campaign', args=[str(self.id)])
310 312
311 313 def get_absolute_url_edit(self):
312 314 return reverse('url_edit_campaign', args=[str(self.id)])
313 315
314 316 def get_absolute_url_delete(self):
315 317 return reverse('url_delete_campaign', args=[str(self.id)])
316 318
317 319 def get_absolute_url_export(self):
318 320 return reverse('url_export_campaign', args=[str(self.id)])
319 321
320 322 def get_absolute_url_import(self):
321 323 return reverse('url_import_campaign', args=[str(self.id)])
322 324
323 325
324 326 class RunningExperiment(models.Model):
325 327 radar = models.OneToOneField('Location', on_delete=models.CASCADE)
326 328 running_experiment = models.ManyToManyField('Experiment', blank = True)
327 329 status = models.PositiveSmallIntegerField(default=0, choices=RADAR_STATES)
328 330
329 331
330 332 class Experiment(models.Model):
331 333
332 334 template = models.BooleanField(default=False)
333 335 name = models.CharField(max_length=40, default='', unique=True)
334 336 location = models.ForeignKey('Location', null=False, blank=False, on_delete=models.CASCADE, default='')
335 337 #freq = models.FloatField(verbose_name='Operating Freq. (MHz)', validators=[MinValueValidator(1), MaxValueValidator(10000)], default=49.9200)
336 338 start_time = models.TimeField(default='00:00:00')
337 339 end_time = models.TimeField(default='23:59:59')
338 340 task = models.CharField(max_length=36, default='', blank=True, null=True)
339 341 status = models.PositiveSmallIntegerField(default=4, choices=EXP_STATES)
340 342 author = models.ForeignKey(User, null=True, blank=True,on_delete=models.CASCADE)
341 343 hash = models.CharField(default='', max_length=64, null=True, blank=True)
342 344
343 345 class Meta:
344 346 db_table = 'db_experiments'
345 347 ordering = ('template', 'name')
346 348
347 349 def __str__(self):
348 350 if self.template:
349 351 return u'%s (template)' % (self.name)
350 352 else:
351 353 return u'%s' % (self.name)
352 354
353 355 def jsonify(self):
354 356
355 357 data = {}
356 358
357 359 ignored = ('template')
358 360
359 361 for field in self._meta.fields:
360 362 if field.name in ignored:
361 363 continue
362 364 data[field.name] = field.value_from_object(self)
363 365
364 366 data['start_time'] = data['start_time'].strftime('%H:%M:%S')
365 367 data['end_time'] = data['end_time'].strftime('%H:%M:%S')
366 368 data['location'] = self.location.name
367 369 data['configurations'] = ['{}'.format(conf.pk) for
368 370 conf in Configuration.objects.filter(experiment=self, type=0)]
369 371
370 372 return data
371 373
372 374 @property
373 375 def radar_system(self):
374 376 return self.location
375 377
376 378 def clone(self, **kwargs):
377 379
378 380 confs = Configuration.objects.filter(experiment=self, type=0)
379 381 self.pk = None
380 382 self.name = '{}_{:%y%m%d}'.format(self.name, datetime.now())
381 383 for attr, value in kwargs.items():
382 384 setattr(self, attr, value)
383 385
384 386 self.save()
385 387
386 388 for conf in confs:
387 389 conf.clone(experiment=self, template=False)
388 390
389 391 return self
390 392
391 393 def start(self):
392 394 '''
393 395 Configure and start experiments's devices
394 396 '''
395 397
396 398 confs = []
397 399 allconfs = Configuration.objects.filter(experiment=self, type = 0).order_by('-device__device_type__sequence')
398 400 confs = allconfs
399 401
400 402 try:
401 403 for conf in confs:
402 404 conf.stop_device()
403 405 print("OK")
404 406 #conf.write_device()
405 407 conf.device.conf_active = conf.pk
406 408 conf.device.save()
407 409 conf.start_device()
408 410 print("OK")
409 411 time.sleep(1)
410 412 except:
411 413 return 0
412 414 return 2
413 415
414 416
415 417 def stop(self):
416 418 '''
417 419 Stop experiments's devices
418 420 PEDESTAL, PULSE GENERATOR & USRP's
419 421 '''
420 422
421 423 confs = Configuration.objects.filter(experiment=self, type = 0).order_by('device__device_type__sequence')
422 424 try:
423 425 for conf in confs:
424 426 conf.stop_device()
425 427 except:
426 428 return 0
427 429 return 1
428 430
429 431 def get_status(self):
430 432
431 433 if self.status == 3:
432 434 return
433 435
434 436 confs = Configuration.objects.filter(experiment=self, type=0)
435 437
436 438 for conf in confs:
437 439 conf.status_device()
438 440
439 441 total = confs.aggregate(models.Sum('device__status'))['device__status__sum']
440 442
441 443 if total==2*confs.count():
442 444 status = 1
443 445 elif total == 3*confs.count():
444 446 status = 2
445 447 else:
446 448 status = 0
447 449
448 450 self.status = status
449 451 self.save()
450 452
451 453 def status_color(self):
452 454 color = 'muted'
453 455 if self.status == 0:
454 456 color = "danger"
455 457 elif self.status == 1:
456 458 color = "warning"
457 459 elif self.status == 2:
458 460 color = "success"
459 461 elif self.status == 3:
460 462 color = "info"
461 463
462 464 return color
463 465
464 466 def parms_to_dict(self):
465 467
466 468 params = Params({})
467 469 params.add(self.jsonify(), 'experiments')
468 470
469 471 configurations = Configuration.objects.filter(experiment=self, type=0)
470 472
471 473 for conf in configurations:
472 474 params.add(conf.jsonify(), 'configurations')
473 475
474 476 return params.data
475 477
476 478 def dict_to_parms(self, parms, CONF_MODELS, id_exp=None):
477 479
478 480 configurations = Configuration.objects.filter(experiment=self)
479 481
480 482 if id_exp is not None:
481 483 exp_parms = parms['experiments']['byId'][id_exp]
482 484 else:
483 485 exp_parms = parms['experiments']['byId'][parms['experiments']['allIds'][0]]
484 486
485 487 if configurations:
486 488 for configuration in configurations:
487 489 configuration.delete()
488 490
489 491 for id_conf in exp_parms['configurations']:
490 492 conf_parms = parms['configurations']['byId'][id_conf]
491 493 device = Device.objects.filter(device_type__name=conf_parms['device_type'])[0]
492 494 model = CONF_MODELS[conf_parms['device_type']]
493 495 conf = model(
494 496 experiment = self,
495 497 device = device,
496 498 )
497 499 conf.dict_to_parms(parms, id=id_conf)
498 500
499 501
500 502 location, created = Location.objects.get_or_create(name=exp_parms['location'])
501 503 self.name = '{}-{}'.format(exp_parms['name'], datetime.now().strftime('%y%m%d'))
502 504 self.location = location
503 505 self.start_time = exp_parms['start_time']
504 506 self.end_time = exp_parms['end_time']
505 507 self.save()
506 508
507 509 return self
508 510
509 511 def get_absolute_url(self):
510 512 return reverse('url_experiment', args=[str(self.id)])
511 513
512 514 def get_absolute_url_edit(self):
513 515 return reverse('url_edit_experiment', args=[str(self.id)])
514 516
515 517 def get_absolute_url_delete(self):
516 518 return reverse('url_delete_experiment', args=[str(self.id)])
517 519
518 520 def get_absolute_url_import(self):
519 521 return reverse('url_import_experiment', args=[str(self.id)])
520 522
521 523 def get_absolute_url_export(self):
522 524 return reverse('url_export_experiment', args=[str(self.id)])
523 525
524 526 def get_absolute_url_start(self):
525 527 return reverse('url_start_experiment', args=[str(self.id)])
526 528
527 529 def get_absolute_url_stop(self):
528 530 return reverse('url_stop_experiment', args=[str(self.id)])
529 531
530 532
531 533 class Configuration(PolymorphicModel):
532 534
533 535 id = models.AutoField(primary_key=True)
534 536 template = models.BooleanField(default=False)
535 537 # name = models.CharField(verbose_name="Configuration Name", max_length=40, default='')
536 538 device = models.ForeignKey('Device', verbose_name='Device', null=True, on_delete=models.CASCADE)
537 539 label = models.CharField(verbose_name="Label", max_length=40, default='', blank=True, null=True)
538 540 experiment = models.ForeignKey('Experiment', verbose_name='Experiment', null=True, blank=True, on_delete=models.CASCADE)
539 541 type = models.PositiveSmallIntegerField(default=0, choices=CONF_TYPES)
540 542 created_date = models.DateTimeField(auto_now_add=True)
541 543 programmed_date = models.DateTimeField(auto_now=True)
542 544 parameters = models.TextField(default='{}')
543 545 author = models.ForeignKey(User, null=True, blank=True,on_delete=models.CASCADE)
544 546 hash = models.CharField(default='', max_length=64, null=True, blank=True)
545 547 message = ""
546 548
547 549 class Meta:
548 550 db_table = 'db_configurations'
549 551 ordering = ('device__device_type__name',)
550 552
551 553 def __str__(self):
552 554
553 555 ret = u'{} '.format(self.device.device_type.name.upper())
554 556
555 557 if 'mix' in [f.name for f in self._meta.get_fields()]:
556 558 if self.mix:
557 559 ret = '{} MIX '.format(self.device.device_type.name.upper())
558 560
559 561 if 'label' in [f.name for f in self._meta.get_fields()]:
560 562 ret += '{}'.format(self.label)
561 563
562 564 if self.template:
563 565 ret += ' (template)'
564 566
565 567 return ret
566 568
567 569 @property
568 570 def name(self):
569 571
570 572 return str(self)
571 573
572 574 def jsonify(self):
573 575
574 576 data = {}
575 577
576 578 ignored = ('type', 'polymorphic_ctype', 'configuration_ptr',
577 579 'created_date', 'programmed_date', 'template', 'device',
578 580 'experiment')
579 581
580 582 for field in self._meta.fields:
581 583 if field.name in ignored:
582 584 continue
583 585 data[field.name] = field.value_from_object(self)
584 586
585 587 data['device_type'] = self.device.device_type.name
586 588 return data
587 589
588 590 def clone(self, **kwargs):
589 591
590 592 self.pk = None
591 593 self.id = None
592 594 for attr, value in kwargs.items():
593 595 setattr(self, attr, value)
594 596
595 597 self.save()
596 598
597 599 return self
598 600
599 601 def parms_to_dict(self):
600 602
601 603 params = Params({})
602 604 params.add(self.jsonify(), 'configurations')
603 605 return params.data
604 606
605 607 def parms_to_text(self):
606 608
607 609 raise NotImplementedError("This method should be implemented in %s Configuration model" %str(self.device.device_type.name).upper())
608 610
609 611
610 612 def parms_to_binary(self):
611 613
612 614 raise NotImplementedError("This method should be implemented in %s Configuration model" %str(self.device.device_type.name).upper())
613 615
614 616
615 617 def dict_to_parms(self, parameters, id=None):
616 618
617 619 params = Params(parameters)
618 620
619 621 if id:
620 622 data = params.get_conf(id_conf=id)
621 623 else:
622 624 data = params.get_conf(dtype=self.device.device_type.name)
623 625
624 626 for key, value in data.items():
625 627 if key not in ('id', 'device_type'):
626 628 setattr(self, key, value)
627 629
628 630 self.save()
629 631
630 632
631 633 def export_to_file(self, format="json"):
632 634
633 635 content_type = ''
634 636
635 637 if format == 'racp':
636 638 content_type = 'text/plain'
637 639 filename = '%s_%s.%s' %(self.device.device_type.name, self.name, 'racp')
638 640 content = self.parms_to_text(file_format = 'racp')
639 641
640 642 if format == 'text':
641 643 content_type = 'text/plain'
642 644 filename = '%s_%s.%s' %(self.device.device_type.name, self.name, self.device.device_type.name)
643 645 content = self.parms_to_text()
644 646
645 647 if format == 'binary':
646 648 content_type = 'application/octet-stream'
647 649 filename = '%s_%s.bin' %(self.device.device_type.name, self.name)
648 650 content = self.parms_to_binary()
649 651
650 652 if not content_type:
651 653 content_type = 'application/json'
652 654 filename = '%s_%s.json' %(self.device.device_type.name, self.name)
653 655 content = json.dumps(self.parms_to_dict(), indent=2)
654 656
655 657 fields = {'content_type':content_type,
656 658 'filename':filename,
657 659 'content':content
658 660 }
659 661
660 662 return fields
661 663
662 664 def import_from_file(self, fp):
663 665
664 666 parms = {}
665 667
666 668 path, ext = os.path.splitext(fp.name)
667 669
668 670 if ext == '.json':
669 671 parms = json.load(fp)
670 672
671 673 return parms
672 674
673 675 def status_device(self):
674 676
675 677 self.message = 'Function not implemented'
676 678 return False
677 679
678 680
679 681 def stop_device(self):
680 682
681 683 self.message = 'Function not implemented'
682 684 return False
683 685
684 686
685 687 def start_device(self):
686 688
687 689 self.message = 'Function not implemented'
688 690 return False
689 691
690 692
691 693 def write_device(self):
692 694
693 695 self.message = 'Function not implemented'
694 696 return False
695 697
696 698
697 699 def read_device(self):
698 700
699 701 self.message = 'Function not implemented'
700 702 return False
701 703
702 704
703 705 def get_absolute_url(self):
704 706 return reverse('url_%s_conf' % self.device.device_type.name, args=[str(self.id)])
705 707
706 708 def get_absolute_url_edit(self):
707 709 return reverse('url_edit_%s_conf' % self.device.device_type.name, args=[str(self.id)])
708 710
709 711 def get_absolute_url_delete(self):
710 712 return reverse('url_delete_dev_conf', args=[str(self.id)])
711 713
712 714 def get_absolute_url_import(self):
713 715 return reverse('url_import_dev_conf', args=[str(self.id)])
714 716
715 717 def get_absolute_url_export(self):
716 718 return reverse('url_export_dev_conf', args=[str(self.id)])
717 719
718 720 def get_absolute_url_write(self):
719 721 return reverse('url_write_dev_conf', args=[str(self.id)])
720 722
721 723 def get_absolute_url_read(self):
722 724 return reverse('url_read_dev_conf', args=[str(self.id)])
723 725
724 726 def get_absolute_url_start(self):
725 727 return reverse('url_start_dev_conf', args=[str(self.id)])
726 728
727 729 def get_absolute_url_stop(self):
728 730 return reverse('url_stop_dev_conf', args=[str(self.id)])
729 731
730 732 def get_absolute_url_status(self):
731 733 return reverse('url_status_dev_conf', args=[str(self.id)])
@@ -1,1917 +1,1920
1 1 import ast
2 2 import json
3 3 import hashlib
4 4 from datetime import datetime, timedelta
5 5
6 6 from django.shortcuts import render, redirect, get_object_or_404, HttpResponse
7 7 from django.utils.safestring import mark_safe
8 8 from django.http import HttpResponseRedirect
9 9 from django.urls import reverse
10 10 from django.db.models import Q
11 11 from django.core.paginator import Paginator, EmptyPage, PageNotAnInteger
12 12 from django.contrib import messages
13 13 from django.http.request import QueryDict
14 14 from django.contrib.auth.decorators import login_required, user_passes_test
15 15
16 16 from django.utils.timezone import is_aware
17 17
18 18 try:
19 19 from urllib.parse import urlencode
20 20 except ImportError:
21 21 from urllib import urlencode
22 22
23 23 from .forms import CampaignForm, ExperimentForm, DeviceForm, ConfigurationForm, LocationForm, UploadFileForm, DownloadFileForm, OperationForm, NewForm
24 24 from .forms import OperationSearchForm, FilterForm, ChangeIpForm
25 25
26 26 from apps.pedestal.forms import PedestalConfigurationForm
27 from apps.pedestal_dev.forms import PedestalDevConfigurationForm
27 28 from apps.generator.forms import GeneratorConfigurationForm
28 29 from apps.usrp_rx.forms import USRPRXConfigurationForm
29 30 from apps.usrp_tx.forms import USRPTXConfigurationForm
30 31 from .utils import Params
31 32
32 33 from .models import Campaign, Experiment, Device, Configuration, Location, RunningExperiment, DEV_STATES
33 34 from apps.pedestal.models import PedestalConfiguration
35 from apps.pedestal_dev.models import PedestalDevConfiguration
34 36 from apps.generator.models import GeneratorConfiguration
35 37 from apps.usrp_rx.models import USRPRXConfiguration
36 38 from apps.usrp_tx.models import USRPTXConfiguration
37 39
38 40
39 41 #comentario test
40 42 CONF_FORMS = {
41 43 'pedestal': PedestalConfigurationForm,
44 'pedestal_dev': PedestalDevConfigurationForm,
42 45 'generator': GeneratorConfigurationForm,
43 46 'usrp_rx': USRPRXConfigurationForm,
44 47 'usrp_tx': USRPTXConfigurationForm,
45 48 }
46 49
47 50 CONF_MODELS = {
48 51 'pedestal': PedestalConfiguration,
52 'pedestal_dev': PedestalDevConfiguration,
49 53 'generator': GeneratorConfiguration,
50 54 'usrp_rx': USRPRXConfiguration,
51 55 'usrp_tx': USRPTXConfiguration,
52 56 }
53 57
54 58 MIX_MODES = {
55 59 '0': 'P',
56 60 '1': 'S',
57 61 }
58 62
59 63 MIX_OPERATIONS = {
60 64 '0': 'OR',
61 65 '1': 'XOR',
62 66 '2': 'AND',
63 67 '3': 'NAND',
64 68 }
65 69
66 70
67 71 def is_developer(user):
68 72
69 73 groups = [str(g.name) for g in user.groups.all()]
70 74 return 'Developer' in groups or user.is_staff
71 75
72 76
73 77 def is_operator(user):
74 78
75 79 groups = [str(g.name) for g in user.groups.all()]
76 80 return 'Operator' in groups or user.is_staff
77 81
78 82
79 83 def has_been_modified(model):
80 84
81 85 prev_hash = model.hash
82 86 new_hash = hashlib.sha256(str(model.parms_to_dict).encode()).hexdigest()
83 87 if prev_hash != new_hash:
84 88 model.hash = new_hash
85 89 model.save()
86 90 return True
87 91 return False
88 92
89 93
90 94 def index(request):
91 95 kwargs = {'no_sidebar': True}
92 96
93 97 return render(request, 'index.html', kwargs)
94 98
95 99
96 100 def locations(request):
97 101
98 102 page = request.GET.get('page')
99 103 order = ('name',)
100 104
101 105 kwargs = get_paginator(Location, page, order)
102 106
103 107 kwargs['keys'] = ['name', 'description']
104 108 kwargs['title'] = 'Radar System'
105 109 kwargs['suptitle'] = 'List'
106 110 kwargs['no_sidebar'] = True
107 111
108 112 return render(request, 'base_list.html', kwargs)
109 113
110 114
111 115 def location(request, id_loc):
112 116
113 117 location = get_object_or_404(Location, pk=id_loc)
114 118
115 119 kwargs = {}
116 120 kwargs['location'] = location
117 121 kwargs['location_keys'] = ['name', 'description']
118 122
119 123 kwargs['title'] = 'Location'
120 124 kwargs['suptitle'] = 'Details'
121 125
122 126 return render(request, 'location.html', kwargs)
123 127
124 128
125 129 @login_required
126 130 def location_new(request):
127 131
128 132 if request.method == 'GET':
129 133 form = LocationForm()
130 134
131 135 if request.method == 'POST':
132 136 form = LocationForm(request.POST)
133 137
134 138 if form.is_valid():
135 139 form.save()
136 140 return redirect('url_locations')
137 141
138 142 kwargs = {}
139 143 kwargs['form'] = form
140 144 kwargs['title'] = 'Radar System'
141 145 kwargs['suptitle'] = 'New'
142 146 kwargs['button'] = 'Create'
143 147
144 148 return render(request, 'base_edit.html', kwargs)
145 149
146 150
147 151 @login_required
148 152 def location_edit(request, id_loc):
149 153
150 154 location = get_object_or_404(Location, pk=id_loc)
151 155
152 156 if request.method == 'GET':
153 157 form = LocationForm(instance=location)
154 158
155 159 if request.method == 'POST':
156 160 form = LocationForm(request.POST, instance=location)
157 161
158 162 if form.is_valid():
159 163 form.save()
160 164 return redirect('url_locations')
161 165
162 166 kwargs = {}
163 167 kwargs['form'] = form
164 168 kwargs['title'] = 'Location'
165 169 kwargs['suptitle'] = 'Edit'
166 170 kwargs['button'] = 'Update'
167 171
168 172 return render(request, 'base_edit.html', kwargs)
169 173
170 174
171 175 @login_required
172 176 def location_delete(request, id_loc):
173 177
174 178 location = get_object_or_404(Location, pk=id_loc)
175 179
176 180 if request.method == 'POST':
177 181
178 182 if is_developer(request.user):
179 183 location.delete()
180 184 return redirect('url_locations')
181 185
182 186 messages.error(request, 'Not enough permission to delete this object')
183 187 return redirect(location.get_absolute_url())
184 188
185 189 kwargs = {
186 190 'title': 'Delete',
187 191 'suptitle': 'Location',
188 192 'object': location,
189 193 'delete': True
190 194 }
191 195
192 196 return render(request, 'confirm.html', kwargs)
193 197
194 198
195 199 def devices(request):
196 200
197 201 page = request.GET.get('page')
198 202 order = ('location', 'device_type')
199 203
200 204 filters = request.GET.copy()
201 205 kwargs = get_paginator(Device, page, order, filters)
202 206 form = FilterForm(initial=request.GET, extra_fields=['tags'])
203 207
204 208 kwargs['keys'] = ['device_type', 'location',
205 209 'ip_address', 'port_address', 'actions']
206 210 kwargs['title'] = 'Device'
207 211 kwargs['suptitle'] = 'List'
208 212 kwargs['no_sidebar'] = True
209 213 kwargs['form'] = form
210 214 kwargs['add_url'] = reverse('url_add_device')
211 215 filters.pop('page', None)
212 216 kwargs['q'] = urlencode(filters)
213 217 kwargs['menu_devices'] = 'active'
214 218 return render(request, 'base_list.html', kwargs)
215 219
216 220
217 221 def device(request, id_dev):
218 222
219 223 device = get_object_or_404(Device, pk=id_dev)
220 224
221 225 kwargs = {}
222 226 kwargs['device'] = device
223 227 kwargs['device_keys'] = ['device_type',
224 228 'ip_address', 'port_address', 'description']
225 229
226 230 kwargs['title'] = 'Device'
227 231 kwargs['suptitle'] = 'Details'
228 232 kwargs['menu_devices'] = 'active'
229 233
230 234 return render(request, 'device.html', kwargs)
231 235
232 236
233 237 @login_required
234 238 def device_new(request):
235 239
236 240 if request.method == 'GET':
237 241 form = DeviceForm()
238 242
239 243 if request.method == 'POST':
240 244 form = DeviceForm(request.POST)
241 245
242 246 if form.is_valid():
243 247 form.save()
244 248 return redirect('url_devices')
245 249
246 250 kwargs = {}
247 251 kwargs['form'] = form
248 252 kwargs['title'] = 'Device'
249 253 kwargs['suptitle'] = 'New'
250 254 kwargs['button'] = 'Create'
251 255 kwargs['menu_devices'] = 'active'
252 256
253 257 return render(request, 'base_edit.html', kwargs)
254 258
255 259
256 260 @login_required
257 261 def device_edit(request, id_dev):
258 262
259 263 device = get_object_or_404(Device, pk=id_dev)
260 264
261 265 if request.method == 'GET':
262 266 form = DeviceForm(instance=device)
263 267
264 268 if request.method == 'POST':
265 269 form = DeviceForm(request.POST, instance=device)
266 270
267 271 if form.is_valid():
268 272 form.save()
269 273 return redirect(device.get_absolute_url())
270 274
271 275 kwargs = {}
272 276 kwargs['form'] = form
273 277 kwargs['title'] = 'Device'
274 278 kwargs['suptitle'] = 'Edit'
275 279 kwargs['button'] = 'Update'
276 280 kwargs['menu_devices'] = 'active'
277 281
278 282 return render(request, 'base_edit.html', kwargs)
279 283
280 284
281 285 @login_required
282 286 def device_delete(request, id_dev):
283 287
284 288 device = get_object_or_404(Device, pk=id_dev)
285 289
286 290 if request.method == 'POST':
287 291
288 292 if is_developer(request.user):
289 293 device.delete()
290 294 return redirect('url_devices')
291 295
292 296 messages.error(request, 'Not enough permission to delete this object')
293 297 return redirect(device.get_absolute_url())
294 298
295 299 kwargs = {
296 300 'title': 'Delete',
297 301 'suptitle': 'Device',
298 302 'object': device,
299 303 'delete': True
300 304 }
301 305 kwargs['menu_devices'] = 'active'
302 306
303 307 return render(request, 'confirm.html', kwargs)
304 308
305 309
306 310 @login_required
307 311 def device_change_ip(request, id_dev):
308 312
309 313 device = get_object_or_404(Device, pk=id_dev)
310 314
311 315 if request.method == 'POST':
312 316
313 317 if is_developer(request.user):
314 318 device.change_ip(**request.POST.dict())
315 319 level, message = device.message.split('|')
316 320 messages.add_message(request, level, message)
317 321 else:
318 322 messages.error(
319 323 request, 'Not enough permission to delete this object')
320 324 return redirect(device.get_absolute_url())
321 325
322 326 kwargs = {
323 327 'title': 'Device',
324 328 'suptitle': 'Change IP',
325 329 'object': device,
326 330 'previous': device.get_absolute_url(),
327 331 'form': ChangeIpForm(initial={'ip_address': device.ip_address}),
328 332 'message': ' ',
329 333 }
330 334 kwargs['menu_devices'] = 'active'
331 335
332 336 return render(request, 'confirm.html', kwargs)
333 337
334 338
335 339 def campaigns(request):
336 340
337 341 page = request.GET.get('page')
338 342 order = ('-start_date',)
339 343 filters = request.GET.copy()
340 344
341 345 kwargs = get_paginator(Campaign, page, order, filters)
342 346
343 347 form = FilterForm(initial=request.GET, extra_fields=[
344 348 'range_date', 'tags', 'template'])
345 349 kwargs['keys'] = ['name', 'start_date', 'end_date', 'actions']
346 350 kwargs['title'] = 'Campaign'
347 351 kwargs['suptitle'] = 'List'
348 352 kwargs['no_sidebar'] = True
349 353 kwargs['form'] = form
350 354 kwargs['add_url'] = reverse('url_add_campaign')
351 355 filters.pop('page', None)
352 356 kwargs['q'] = urlencode(filters)
353 357 kwargs['menu_campaigns'] = 'active'
354 358
355 359 return render(request, 'base_list.html', kwargs)
356 360
357 361
358 362 def campaign(request, id_camp):
359 363
360 364 campaign = get_object_or_404(Campaign, pk=id_camp)
361 365 experiments = Experiment.objects.filter(campaign=campaign)
362 366
363 367 form = CampaignForm(instance=campaign)
364 368
365 369 kwargs = {}
366 370 kwargs['campaign'] = campaign
367 371 kwargs['campaign_keys'] = ['template', 'name',
368 372 'start_date', 'end_date', 'tags', 'description']
369 373
370 374 kwargs['experiments'] = experiments
371 375 kwargs['experiment_keys'] = [
372 376 'name', 'radar_system', 'start_time', 'end_time']
373 377
374 378 kwargs['title'] = 'Campaign'
375 379 kwargs['suptitle'] = 'Details'
376 380
377 381 kwargs['form'] = form
378 382 kwargs['button'] = 'Add Experiment'
379 383 kwargs['menu_campaigns'] = 'active'
380 384
381 385 return render(request, 'campaign.html', kwargs)
382 386
383 387
384 388 @login_required
385 389 def campaign_new(request):
386 390
387 391 kwargs = {}
388 392
389 393 if request.method == 'GET':
390 394
391 395 if 'template' in request.GET:
392 396 if request.GET['template'] == '0':
393 397 form = NewForm(initial={'create_from': 2},
394 398 template_choices=Campaign.objects.filter(template=True).values_list('id', 'name'))
395 399 else:
396 400 kwargs['button'] = 'Create'
397 401 kwargs['experiments'] = Configuration.objects.filter(
398 402 experiment=request.GET['template'])
399 403 kwargs['experiment_keys'] = ['name', 'start_time', 'end_time']
400 404 camp = Campaign.objects.get(pk=request.GET['template'])
401 405 form = CampaignForm(instance=camp,
402 406 initial={'name': '{}_{:%Y%m%d}'.format(camp.name, datetime.now()),
403 407 'template': False})
404 408 elif 'blank' in request.GET:
405 409 kwargs['button'] = 'Create'
406 410 form = CampaignForm()
407 411 else:
408 412 form = NewForm()
409 413
410 414 if request.method == 'POST':
411 415 kwargs['button'] = 'Create'
412 416 post = request.POST.copy()
413 417 experiments = []
414 418
415 419 for id_exp in post.getlist('experiments'):
416 420 exp = Experiment.objects.get(pk=id_exp)
417 421 new_exp = exp.clone(template=False)
418 422 experiments.append(new_exp)
419 423
420 424 post.setlist('experiments', [])
421 425
422 426 form = CampaignForm(post)
423 427
424 428 if form.is_valid():
425 429 campaign = form.save(commit=False)
426 430 campaign.author = request.user
427 431 for exp in experiments:
428 432 campaign.experiments.add(exp)
429 433 campaign.save()
430 434 return redirect('url_campaign', id_camp=campaign.id)
431 435
432 436 kwargs['form'] = form
433 437 kwargs['title'] = 'Campaign'
434 438 kwargs['suptitle'] = 'New'
435 439 kwargs['menu_campaigns'] = 'active'
436 440
437 441 return render(request, 'campaign_edit.html', kwargs)
438 442
439 443
440 444 @login_required
441 445 def campaign_edit(request, id_camp):
442 446
443 447 campaign = get_object_or_404(Campaign, pk=id_camp)
444 448
445 449 if request.method == 'GET':
446 450 form = CampaignForm(instance=campaign)
447 451
448 452 if request.method == 'POST':
449 453 exps = campaign.experiments.all().values_list('pk', flat=True)
450 454 post = request.POST.copy()
451 455 new_exps = post.getlist('experiments')
452 456 post.setlist('experiments', [])
453 457 form = CampaignForm(post, instance=campaign)
454 458
455 459 if form.is_valid():
456 460 camp = form.save()
457 461 for id_exp in new_exps:
458 462 if int(id_exp) in exps:
459 463 exps.pop(id_exp)
460 464 else:
461 465 exp = Experiment.objects.get(pk=id_exp)
462 466 if exp.template:
463 467 camp.experiments.add(exp.clone(template=False))
464 468 else:
465 469 camp.experiments.add(exp)
466 470
467 471 for id_exp in exps:
468 472 camp.experiments.remove(Experiment.objects.get(pk=id_exp))
469 473
470 474 return redirect('url_campaign', id_camp=id_camp)
471 475
472 476 kwargs = {}
473 477 kwargs['form'] = form
474 478 kwargs['title'] = 'Campaign'
475 479 kwargs['suptitle'] = 'Edit'
476 480 kwargs['button'] = 'Update'
477 481 kwargs['menu_campaigns'] = 'active'
478 482
479 483 return render(request, 'campaign_edit.html', kwargs)
480 484
481 485
482 486 @login_required
483 487 def campaign_delete(request, id_camp):
484 488
485 489 campaign = get_object_or_404(Campaign, pk=id_camp)
486 490
487 491 if request.method == 'POST':
488 492 if is_developer(request.user):
489 493
490 494 for exp in campaign.experiments.all():
491 495 for conf in Configuration.objects.filter(experiment=exp):
492 496 conf.delete()
493 497 exp.delete()
494 498 campaign.delete()
495 499
496 500 return redirect('url_campaigns')
497 501
498 502 messages.error(request, 'Not enough permission to delete this object')
499 503 return redirect(campaign.get_absolute_url())
500 504
501 505 kwargs = {
502 506 'title': 'Delete',
503 507 'suptitle': 'Campaign',
504 508 'object': campaign,
505 509 'delete': True
506 510 }
507 511 kwargs['menu_campaigns'] = 'active'
508 512
509 513 return render(request, 'confirm.html', kwargs)
510 514
511 515
512 516 @login_required
513 517 def campaign_export(request, id_camp):
514 518
515 519 campaign = get_object_or_404(Campaign, pk=id_camp)
516 520 content = campaign.parms_to_dict()
517 521 content_type = 'application/json'
518 522 filename = '%s_%s.json' % (campaign.name, campaign.id)
519 523
520 524 response = HttpResponse(content_type=content_type)
521 525 response['Content-Disposition'] = 'attachment; filename="%s"' % filename
522 526 response.write(json.dumps(content, indent=2))
523 527
524 528 return response
525 529
526 530
527 531 @login_required
528 532 def campaign_import(request, id_camp):
529 533
530 534 campaign = get_object_or_404(Campaign, pk=id_camp)
531 535
532 536 if request.method == 'GET':
533 537 file_form = UploadFileForm()
534 538
535 539 if request.method == 'POST':
536 540 file_form = UploadFileForm(request.POST, request.FILES)
537 541
538 542 if file_form.is_valid():
539 543 new_camp = campaign.dict_to_parms(
540 544 json.load(request.FILES['file']), CONF_MODELS)
541 545 messages.success(
542 546 request, "Parameters imported from: '%s'." % request.FILES['file'].name)
543 547 return redirect(new_camp.get_absolute_url_edit())
544 548
545 549 messages.error(request, "Could not import parameters from file")
546 550
547 551 kwargs = {}
548 552 kwargs['title'] = 'Campaign'
549 553 kwargs['form'] = file_form
550 554 kwargs['suptitle'] = 'Importing file'
551 555 kwargs['button'] = 'Import'
552 556 kwargs['menu_campaigns'] = 'active'
553 557
554 558 return render(request, 'campaign_import.html', kwargs)
555 559
556 560
557 561 def experiments(request):
558 562
559 563 page = request.GET.get('page')
560 564 order = ('location',)
561 565 filters = request.GET.copy()
562 566
563 567 if 'my experiments' in filters:
564 568 filters.pop('my experiments', None)
565 569 filters['mine'] = request.user.id
566 570
567 571 kwargs = get_paginator(Experiment, page, order, filters)
568 572
569 573 fields = ['tags', 'template']
570 574 if request.user.is_authenticated:
571 575 fields.append('my experiments')
572 576
573 577 form = FilterForm(initial=request.GET, extra_fields=fields)
574 578
575 579 kwargs['keys'] = ['name', 'radar_system',
576 580 'start_time', 'end_time', 'actions']
577 581 kwargs['title'] = 'Experiment'
578 582 kwargs['suptitle'] = 'List'
579 583 kwargs['no_sidebar'] = True
580 584 kwargs['form'] = form
581 585 kwargs['add_url'] = reverse('url_add_experiment')
582 586 filters = request.GET.copy()
583 587 filters.pop('page', None)
584 588 kwargs['q'] = urlencode(filters)
585 589 kwargs['menu_experiments'] = 'active'
586 590
587 591 return render(request, 'base_list.html', kwargs)
588 592
589 593
590 594 def experiment(request, id_exp):
591 595
592 596 experiment = get_object_or_404(Experiment, pk=id_exp)
593 597
594 598 configurations = Configuration.objects.filter(
595 599 experiment=experiment, type=0)
596 600
597 601 kwargs = {}
598 602
599 603 kwargs['experiment_keys'] = ['template', 'radar_system',
600 604 'name', 'start_time', 'end_time']
601 605 kwargs['experiment'] = experiment
602 606 kwargs['configuration_keys'] = ['name', 'device__ip_address',
603 607 'device__port_address', 'device__status']
604 608 kwargs['configurations'] = configurations
605 609 kwargs['title'] = 'Experiment'
606 610 kwargs['suptitle'] = 'Details'
607 611 kwargs['button'] = 'Add Configuration'
608 612 kwargs['menu_experiments'] = 'active'
609 613
610 614 ###### SIDEBAR ######
611 615 kwargs.update(sidebar(experiment=experiment))
612 616
613 617 return render(request, 'experiment.html', kwargs)
614 618
615 619
616 620 @login_required
617 621 def experiment_new(request, id_camp=None):
618 622
619 623 if not is_developer(request.user):
620 624 messages.error(
621 625 request, 'Developer required, to create new Experiments')
622 626 return redirect('index')
623 627 kwargs = {}
624 628
625 629 if request.method == 'GET':
626 630 if 'template' in request.GET:
627 631 if request.GET['template'] == '0':
628 632 form = NewForm(initial={'create_from': 2},
629 633 template_choices=Experiment.objects.filter(template=True).values_list('id', 'name'))
630 634 else:
631 635 kwargs['button'] = 'Create'
632 636 kwargs['configurations'] = Configuration.objects.filter(
633 637 experiment=request.GET['template'])
634 638 kwargs['configuration_keys'] = ['name', 'device__name',
635 639 'device__ip_address', 'device__port_address']
636 640 exp = Experiment.objects.get(pk=request.GET['template'])
637 641 form = ExperimentForm(instance=exp,
638 642 initial={'name': '{}_{:%y%m%d}'.format(exp.name, datetime.now()),
639 643 'template': False})
640 644 elif 'blank' in request.GET:
641 645 kwargs['button'] = 'Create'
642 646 form = ExperimentForm()
643 647 else:
644 648 form = NewForm()
645 649
646 650 if request.method == 'POST':
647 651 form = ExperimentForm(request.POST)
648 652 if form.is_valid():
649 653 experiment = form.save(commit=False)
650 654 experiment.author = request.user
651 655 experiment.save()
652 656
653 657 if 'template' in request.GET:
654 658 configurations = Configuration.objects.filter(
655 659 experiment=request.GET['template'], type=0)
656 660 for conf in configurations:
657 661 conf.clone(experiment=experiment, template=False)
658 662
659 663 return redirect('url_experiment', id_exp=experiment.id)
660 664
661 665 kwargs['form'] = form
662 666 kwargs['title'] = 'Experiment'
663 667 kwargs['suptitle'] = 'New'
664 668 kwargs['menu_experiments'] = 'active'
665 669
666 670 return render(request, 'experiment_edit.html', kwargs)
667 671
668 672
669 673 @login_required
670 674 def experiment_edit(request, id_exp):
671 675
672 676 experiment = get_object_or_404(Experiment, pk=id_exp)
673 677
674 678 if request.method == 'GET':
675 679 form = ExperimentForm(instance=experiment)
676 680
677 681 if request.method == 'POST':
678 682 form = ExperimentForm(request.POST, instance=experiment)
679 683
680 684 if form.is_valid():
681 685 experiment = form.save()
682 686 return redirect('url_experiment', id_exp=experiment.id)
683 687
684 688 kwargs = {}
685 689 kwargs['form'] = form
686 690 kwargs['title'] = 'Experiment'
687 691 kwargs['suptitle'] = 'Edit'
688 692 kwargs['button'] = 'Update'
689 693 kwargs['menu_experiments'] = 'active'
690 694
691 695 return render(request, 'experiment_edit.html', kwargs)
692 696
693 697
694 698 @login_required
695 699 def experiment_delete(request, id_exp):
696 700
697 701 experiment = get_object_or_404(Experiment, pk=id_exp)
698 702
699 703 if request.method == 'POST':
700 704 if is_developer(request.user):
701 705 for conf in Configuration.objects.filter(experiment=experiment):
702 706 conf.delete()
703 707 experiment.delete()
704 708 return redirect('url_experiments')
705 709
706 710 messages.error(request, 'Not enough permission to delete this object')
707 711 return redirect(experiment.get_absolute_url())
708 712
709 713 kwargs = {
710 714 'title': 'Delete',
711 715 'suptitle': 'Experiment',
712 716 'object': experiment,
713 717 'delete': True
714 718 }
715 719
716 720 return render(request, 'confirm.html', kwargs)
717 721
718 722
719 723 @login_required
720 724 def experiment_export(request, id_exp):
721 725
722 726 experiment = get_object_or_404(Experiment, pk=id_exp)
723 727 content = experiment.parms_to_dict()
724 728 content_type = 'application/json'
725 729 filename = '%s_%s.json' % (experiment.name, experiment.id)
726 730
727 731 response = HttpResponse(content_type=content_type)
728 732 response['Content-Disposition'] = 'attachment; filename="%s"' % filename
729 733 response.write(json.dumps(content, indent=2))
730 734
731 735 return response
732 736
733 737
734 738 @login_required
735 739 def experiment_import(request, id_exp):
736 740
737 741 experiment = get_object_or_404(Experiment, pk=id_exp)
738 742 configurations = Configuration.objects.filter(experiment=experiment)
739 743
740 744 if request.method == 'GET':
741 745 file_form = UploadFileForm()
742 746
743 747 if request.method == 'POST':
744 748 file_form = UploadFileForm(request.POST, request.FILES)
745 749
746 750 if file_form.is_valid():
747 751 new_exp = experiment.dict_to_parms(
748 752 json.load(request.FILES['file']), CONF_MODELS)
749 753 messages.success(
750 754 request, "Parameters imported from: '%s'." % request.FILES['file'].name)
751 755 return redirect(new_exp.get_absolute_url_edit())
752 756
753 757 messages.error(request, "Could not import parameters from file")
754 758
755 759 kwargs = {}
756 760 kwargs['title'] = 'Experiment'
757 761 kwargs['form'] = file_form
758 762 kwargs['suptitle'] = 'Importing file'
759 763 kwargs['button'] = 'Import'
760 764 kwargs['menu_experiments'] = 'active'
761 765
762 766 kwargs.update(sidebar(experiment=experiment))
763 767
764 768 return render(request, 'experiment_import.html', kwargs)
765 769
766 770
767 771 @login_required
768 772 def experiment_start(request, id_exp):
769 773
770 774 exp = get_object_or_404(Experiment, pk=id_exp)
771 775
772 776 if exp.status == 2:
773 777 messages.warning(request, 'Experiment {} already runnnig'.format(exp))
774 778 else:
775 779 exp.status = exp.start()
776 780 if exp.status == 0:
777 781 messages.error(request, 'Experiment {} not start'.format(exp))
778 782 if exp.status == 2:
779 783 messages.success(request, 'Experiment {} started'.format(exp))
780 784
781 785 exp.save()
782 786
783 787 return redirect(exp.get_absolute_url())
784 788
785 789
786 790 @login_required
787 791 def experiment_stop(request, id_exp):
788 792
789 793 exp = get_object_or_404(Experiment, pk=id_exp)
790 794
791 795 if exp.status == 2:
792 796 exp.status = exp.stop()
793 797 exp.save()
794 798 messages.success(request, 'Experiment {} stopped'.format(exp))
795 799 else:
796 800 messages.error(request, 'Experiment {} not running'.format(exp))
797 801
798 802 return redirect(exp.get_absolute_url())
799 803
800 804
801 805 def experiment_status(request, id_exp):
802 806
803 807 exp = get_object_or_404(Experiment, pk=id_exp)
804 808
805 809 exp.get_status()
806 810
807 811 return redirect(exp.get_absolute_url())
808 812
809 813
810 814 @login_required
811 815 def experiment_mix(request, id_exp):
812 816
813 817 experiment = get_object_or_404(Experiment, pk=id_exp)
814 818 rc_confs = [conf for conf in PedestalConfiguration.objects.filter(
815 819 experiment=id_exp,
816 820 type=0,
817 821 mix=False)]
818 822
819 823 if len(rc_confs) < 2:
820 824 messages.warning(
821 825 request, 'You need at least two RC Configurations to make a mix')
822 826 return redirect(experiment.get_absolute_url())
823 827
824 828 mix_confs = PedestalConfiguration.objects.filter(experiment=id_exp, mix=True, type=0)
825 829
826 830 if mix_confs:
827 831 mix = mix_confs[0]
828 832 else:
829 833 mix = PedestalConfiguration(experiment=experiment,
830 834 device=rc_confs[0].device,
831 835 ipp=rc_confs[0].ipp,
832 836 clock_in=rc_confs[0].clock_in,
833 837 clock_divider=rc_confs[0].clock_divider,
834 838 mix=True,
835 839 parameters='')
836 840 mix.save()
837 841
838 842 line_type = RCLineType.objects.get(name='mix')
839 843 print("VIew obteniendo len getlines")
840 844 print(len(rc_confs[0].get_lines()))
841 845 for i in range(len(rc_confs[0].get_lines())):
842 846 line = RCLine(rc_configuration=mix, line_type=line_type, channel=i)
843 847 line.save()
844 848
845 849 initial = {'name': mix.name,
846 850 'result': parse_mix_result(mix.parameters),
847 851 'delay': 0,
848 852 'mask': [0, 1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13, 14, 15]
849 853 }
850 854
851 855 if request.method == 'GET':
852 856 form = RCMixConfigurationForm(confs=rc_confs, initial=initial)
853 857
854 858 if request.method == 'POST':
855 859 result = mix.parameters
856 860
857 861 if '{}|'.format(request.POST['experiment']) in result:
858 862 messages.error(request, 'Configuration already added')
859 863 else:
860 864 if 'operation' in request.POST:
861 865 operation = MIX_OPERATIONS[request.POST['operation']]
862 866 else:
863 867 operation = ' '
864 868
865 869 mode = MIX_MODES[request.POST['mode']]
866 870
867 871 if result:
868 872 result = '{}-{}|{}|{}|{}|{}'.format(mix.parameters,
869 873 request.POST['experiment'],
870 874 mode,
871 875 operation,
872 876 float(
873 877 request.POST['delay']),
874 878 parse_mask(
875 879 request.POST.getlist('mask'))
876 880 )
877 881 else:
878 882 result = '{}|{}|{}|{}|{}'.format(request.POST['experiment'],
879 883 mode,
880 884 operation,
881 885 float(request.POST['delay']),
882 886 parse_mask(
883 887 request.POST.getlist('mask'))
884 888 )
885 889
886 890 mix.parameters = result
887 891 mix.save()
888 892 mix.update_pulses()
889 893
890 894 initial['result'] = parse_mix_result(result)
891 895 initial['name'] = mix.name
892 896
893 897 form = RCMixConfigurationForm(initial=initial, confs=rc_confs)
894 898
895 899 kwargs = {
896 900 'title': 'Experiment',
897 901 'suptitle': 'Mix Configurations',
898 902 'form': form,
899 903 'extra_button': 'Delete',
900 904 'button': 'Add',
901 905 'cancel': 'Back',
902 906 'previous': experiment.get_absolute_url(),
903 907 'id_exp': id_exp,
904 908
905 909 }
906 910 kwargs['menu_experiments'] = 'active'
907 911
908 912 return render(request, 'experiment_mix.html', kwargs)
909 913
910 914
911 915 @login_required
912 916 def experiment_mix_delete(request, id_exp):
913 917
914 918 conf = PedestalConfiguration.objects.get(experiment=id_exp, mix=True, type=0)
915 919 values = conf.parameters.split('-')
916 920 conf.parameters = '-'.join(values[:-1])
917 921 conf.save()
918 922
919 923 return redirect('url_mix_experiment', id_exp=id_exp)
920 924
921 925
922 926 def experiment_summary(request, id_exp):
923 927
924 928 experiment = get_object_or_404(Experiment, pk=id_exp)
925 929 configurations = Configuration.objects.filter(
926 930 experiment=experiment, type=0)
927 931
928 932 kwargs = {}
929 933 kwargs['experiment_keys'] = ['radar_system',
930 934 'name', 'freq', 'start_time', 'end_time']
931 935 kwargs['experiment'] = experiment
932 936 kwargs['configurations'] = []
933 937 kwargs['title'] = 'Experiment Summary'
934 938 kwargs['suptitle'] = 'Details'
935 939 kwargs['button'] = 'Verify Parameters'
936 940
937 941 c_vel = 3.0*(10**8) # m/s
938 942 ope_freq = experiment.freq*(10**6) # 1/s
939 943 radar_lambda = c_vel/ope_freq # m
940 944 kwargs['radar_lambda'] = radar_lambda
941 945
942 946 ipp = None
943 947 nsa = 1
944 948 code_id = 0
945 949 tx_line = {}
946 950
947 951 for configuration in configurations.filter(device__device_type__name = 'pedestal'):
948 952
949 953 if configuration.mix:
950 954 continue
951 955 conf = {'conf': configuration}
952 956 conf['keys'] = []
953 957 conf['NTxs'] = configuration.ntx
954 958 conf['keys'].append('NTxs')
955 959 ipp = configuration.ipp
956 960 conf['IPP'] = ipp
957 961 conf['keys'].append('IPP')
958 962 lines = configuration.get_lines(line_type__name='tx')
959 963
960 964 for tx_line in lines:
961 965 tx_params = json.loads(tx_line.params)
962 966 conf[tx_line.get_name()] = '{} Km'.format(tx_params['pulse_width'])
963 967 conf['keys'].append(tx_line.get_name())
964 968 delays = tx_params['delays']
965 969 if delays not in ('', '0'):
966 970 n = len(delays.split(','))
967 971 taus = '{} Taus: {}'.format(n, delays)
968 972 else:
969 973 taus = '-'
970 974 conf['Taus ({})'.format(tx_line.get_name())] = taus
971 975 conf['keys'].append('Taus ({})'.format(tx_line.get_name()))
972 976 for code_line in configuration.get_lines(line_type__name='codes'):
973 977 code_params = json.loads(code_line.params)
974 978 code_id = code_params['code']
975 979 if tx_line.pk == int(code_params['TX_ref']):
976 980 conf['Code ({})'.format(tx_line.get_name())] = '{}:{}'.format(RCLineCode.objects.get(pk=code_params['code']),
977 981 '-'.join(code_params['codes']))
978 982 conf['keys'].append('Code ({})'.format(tx_line.get_name()))
979 983
980 984 for windows_line in configuration.get_lines(line_type__name='windows'):
981 985 win_params = json.loads(windows_line.params)
982 986 if tx_line.pk == int(win_params['TX_ref']):
983 987 windows = ''
984 988 nsa = win_params['params'][0]['number_of_samples']
985 989 for i, params in enumerate(win_params['params']):
986 990 windows += 'W{}: Ho={first_height} km DH={resolution} km NSA={number_of_samples}<br>'.format(
987 991 i, **params)
988 992 conf['Window'] = mark_safe(windows)
989 993 conf['keys'].append('Window')
990 994
991 995 kwargs['configurations'].append(conf)
992 996
993 997 for configuration in configurations.filter(device__device_type__name = 'jars'):
994 998
995 999 conf = {'conf': configuration}
996 1000 conf['keys'] = []
997 1001 conf['Type of Data'] = EXPERIMENT_TYPE[configuration.exp_type][1]
998 1002 conf['keys'].append('Type of Data')
999 1003 channels_number = configuration.channels_number
1000 1004 exp_type = configuration.exp_type
1001 1005 fftpoints = configuration.fftpoints
1002 1006 filter_parms = json.loads(configuration.filter_parms)
1003 1007 spectral_number = configuration.spectral_number
1004 1008 acq_profiles = configuration.acq_profiles
1005 1009 cohe_integr = configuration.cohe_integr
1006 1010 profiles_block = configuration.profiles_block
1007 1011
1008 1012 conf['Num of Profiles'] = acq_profiles
1009 1013 conf['keys'].append('Num of Profiles')
1010 1014
1011 1015 conf['Prof per Block'] = profiles_block
1012 1016 conf['keys'].append('Prof per Block')
1013 1017
1014 1018 conf['Blocks per File'] = configuration.raw_data_blocks
1015 1019 conf['keys'].append('Blocks per File')
1016 1020
1017 1021 if exp_type == 0: # Short
1018 1022 bytes_ = 2
1019 1023 b = nsa*2*bytes_*channels_number
1020 1024 else: # Float
1021 1025 bytes_ = 4
1022 1026 channels = channels_number + spectral_number
1023 1027 b = nsa*2*bytes_*fftpoints*channels
1024 1028
1025 1029 codes_num = 7
1026 1030 if code_id == 2:
1027 1031 codes_num = 7
1028 1032 elif code_id == 12:
1029 1033 codes_num = 15
1030 1034
1031 1035 #Jars filter values:
1032 1036
1033 1037 clock = float(filter_parms['clock'])
1034 1038 filter_2 = int(filter_parms['cic_2'])
1035 1039 filter_5 = int(filter_parms['cic_5'])
1036 1040 filter_fir = int(filter_parms['fir'])
1037 1041 Fs_MHz = clock/(filter_2*filter_5*filter_fir)
1038 1042
1039 1043 #Jars values:
1040 1044 if ipp is not None:
1041 1045 IPP_units = ipp/0.15*Fs_MHz
1042 1046 IPP_us = IPP_units / Fs_MHz
1043 1047 IPP_s = IPP_units / (Fs_MHz * (10**6))
1044 1048 Ts = 1/(Fs_MHz*(10**6))
1045 1049
1046 1050 Va = radar_lambda/(4*Ts*cohe_integr)
1047 1051 rate_bh = ((nsa-codes_num)*channels_number*2 *
1048 1052 bytes_/IPP_us)*(36*(10**8)/cohe_integr)
1049 1053 rate_gh = rate_bh/(1024*1024*1024)
1050 1054
1051 1055 conf['Time per Block'] = IPP_s * profiles_block * cohe_integr
1052 1056 conf['keys'].append('Time per Block')
1053 1057 conf['Acq time'] = IPP_s * acq_profiles
1054 1058 conf['keys'].append('Acq time')
1055 1059 conf['Data rate'] = str(rate_gh)+" (GB/h)"
1056 1060 conf['keys'].append('Data rate')
1057 1061 conf['Va (m/s)'] = Va
1058 1062 conf['keys'].append('Va (m/s)')
1059 1063 conf['Vrange (m/s)'] = 3/(2*IPP_s*cohe_integr)
1060 1064 conf['keys'].append('Vrange (m/s)')
1061 1065
1062 1066 kwargs['configurations'].append(conf)
1063 1067 kwargs['menu_experiments'] = 'active'
1064 1068
1065 1069 ###### SIDEBAR ######
1066 1070 kwargs.update(sidebar(experiment=experiment))
1067 1071
1068 1072 return render(request, 'experiment_summary.html', kwargs)
1069 1073
1070 1074
1071 1075 @login_required
1072 1076 def experiment_verify(request, id_exp):
1073 1077
1074 1078 experiment = get_object_or_404(Experiment, pk=id_exp)
1075 1079 experiment_data = experiment.parms_to_dict()
1076 1080 configurations = Configuration.objects.filter(
1077 1081 experiment=experiment, type=0)
1078 1082
1079 1083 kwargs = {}
1080 1084
1081 1085 kwargs['experiment_keys'] = ['template',
1082 1086 'radar_system', 'name', 'start_time', 'end_time']
1083 1087 kwargs['experiment'] = experiment
1084 1088
1085 1089 kwargs['configuration_keys'] = ['name', 'device__ip_address',
1086 1090 'device__port_address', 'device__status']
1087 1091 kwargs['configurations'] = configurations
1088 1092 kwargs['experiment_data'] = experiment_data
1089 1093
1090 1094 kwargs['title'] = 'Verify Experiment'
1091 1095 kwargs['suptitle'] = 'Parameters'
1092 1096
1093 1097 kwargs['button'] = 'Update'
1094 1098
1095 1099 jars_conf = False
1096 1100 rc_conf = False
1097 1101 dds_conf = False
1098 1102
1099 1103 for configuration in configurations:
1100 1104 #-------------------- JARS -----------------------:
1101 1105 if configuration.device.device_type.name == 'jars':
1102 1106 jars_conf = True
1103 1107 jars = configuration
1104 1108 kwargs['jars_conf'] = jars_conf
1105 1109 filter_parms = json.loads(jars.filter_parms)
1106 1110 kwargs['filter_parms'] = filter_parms
1107 1111 #--Sampling Frequency
1108 1112 clock = filter_parms['clock']
1109 1113 filter_2 = filter_parms['cic_2']
1110 1114 filter_5 = filter_parms['cic_5']
1111 1115 filter_fir = filter_parms['fir']
1112 1116 samp_freq_jars = clock/filter_2/filter_5/filter_fir
1113 1117
1114 1118 kwargs['samp_freq_jars'] = samp_freq_jars
1115 1119 kwargs['jars'] = configuration
1116 1120
1117 1121 #--------------------- RC ----------------------:
1118 1122 if configuration.device.device_type.name == 'pedestal' and not configuration.mix:
1119 1123 rc_conf = True
1120 1124 rc = configuration
1121 1125
1122 1126 rc_parms = configuration.parms_to_dict()
1123 1127
1124 1128 win_lines = rc.get_lines(line_type__name='windows')
1125 1129 if win_lines:
1126 1130 dh = json.loads(win_lines[0].params)['params'][0]['resolution']
1127 1131 #--Sampling Frequency
1128 1132 samp_freq_rc = 0.15/dh
1129 1133 kwargs['samp_freq_rc'] = samp_freq_rc
1130 1134
1131 1135 kwargs['rc_conf'] = rc_conf
1132 1136 kwargs['rc'] = configuration
1133 1137
1134 1138 #-------------------- DDS ----------------------:
1135 1139 if configuration.device.device_type.name == 'dds':
1136 1140 dds_conf = True
1137 1141 dds = configuration
1138 1142 dds_parms = configuration.parms_to_dict()
1139 1143
1140 1144 kwargs['dds_conf'] = dds_conf
1141 1145 kwargs['dds'] = configuration
1142 1146
1143 1147 #------------Validation------------:
1144 1148 #Clock
1145 1149 if dds_conf and rc_conf and jars_conf:
1146 1150 if float(filter_parms['clock']) != float(rc_parms['configurations']['byId'][str(rc.pk)]['clock_in']) and float(rc_parms['configurations']['byId'][str(rc.pk)]['clock_in']) != float(dds_parms['configurations']['byId'][str(dds.pk)]['clock']):
1147 1151 messages.warning(request, "Devices don't have the same clock.")
1148 1152 elif rc_conf and jars_conf:
1149 1153 if float(filter_parms['clock']) != float(rc_parms['configurations']['byId'][str(rc.pk)]['clock_in']):
1150 1154 messages.warning(request, "Devices don't have the same clock.")
1151 1155 elif rc_conf and dds_conf:
1152 1156 if float(rc_parms['configurations']['byId'][str(rc.pk)]['clock_in']) != float(dds_parms['configurations']['byId'][str(dds.pk)]['clock']):
1153 1157 messages.warning(request, "Devices don't have the same clock.")
1154 1158 if float(samp_freq_rc) != float(dds_parms['configurations']['byId'][str(dds.pk)]['frequencyA']):
1155 1159 messages.warning(
1156 1160 request, "Devices don't have the same Frequency A.")
1157 1161
1158 1162 #------------POST METHOD------------:
1159 1163 if request.method == 'POST':
1160 1164 if request.POST['suggest_clock']:
1161 1165 try:
1162 1166 suggest_clock = float(request.POST['suggest_clock'])
1163 1167 except:
1164 1168 messages.warning(request, "Invalid value in CLOCK IN.")
1165 1169 return redirect('url_verify_experiment', id_exp=experiment.id)
1166 1170 else:
1167 1171 suggest_clock = ""
1168 1172 if suggest_clock:
1169 1173 if rc_conf:
1170 1174 rc.clock_in = suggest_clock
1171 1175 rc.save()
1172 1176 if jars_conf:
1173 1177 filter_parms = jars.filter_parms
1174 1178 filter_parms = ast.literal_eval(filter_parms)
1175 1179 filter_parms['clock'] = suggest_clock
1176 1180 jars.filter_parms = json.dumps(filter_parms)
1177 1181 jars.save()
1178 1182 kwargs['filter_parms'] = filter_parms
1179 1183 if dds_conf:
1180 1184 dds.clock = suggest_clock
1181 1185 dds.save()
1182 1186
1183 1187 if request.POST['suggest_frequencyA']:
1184 1188 try:
1185 1189 suggest_frequencyA = float(request.POST['suggest_frequencyA'])
1186 1190 except:
1187 1191 messages.warning(request, "Invalid value in FREQUENCY A.")
1188 1192 return redirect('url_verify_experiment', id_exp=experiment.id)
1189 1193 else:
1190 1194 suggest_frequencyA = ""
1191 1195 if suggest_frequencyA:
1192 1196 if jars_conf:
1193 1197 filter_parms = jars.filter_parms
1194 1198 filter_parms = ast.literal_eval(filter_parms)
1195 1199 filter_parms['fch'] = suggest_frequencyA
1196 1200 jars.filter_parms = json.dumps(filter_parms)
1197 1201 jars.save()
1198 1202 kwargs['filter_parms'] = filter_parms
1199 1203 if dds_conf:
1200 1204 dds.frequencyA_Mhz = request.POST['suggest_frequencyA']
1201 1205 dds.save()
1202 1206
1203 1207 kwargs['menu_experiments'] = 'active'
1204 1208 kwargs.update(sidebar(experiment=experiment))
1205 1209 return render(request, 'experiment_verify.html', kwargs)
1206 1210
1207 1211
1208 1212 def parse_mix_result(s):
1209 1213
1210 1214 values = s.split('-')
1211 1215 html = 'EXP MOD OPE DELAY MASK\r\n'
1212 1216
1213 1217 if not values or values[0] in ('', ' '):
1214 1218 return mark_safe(html)
1215 1219
1216 1220 for i, value in enumerate(values):
1217 1221 if not value:
1218 1222 continue
1219 1223 pk, mode, operation, delay, mask = value.split('|')
1220 1224 conf = PedestalConfiguration.objects.get(pk=pk)
1221 1225 if i == 0:
1222 1226 html += '{:20.18}{:3}{:4}{:9}km{:>6}\r\n'.format(
1223 1227 conf.name,
1224 1228 mode,
1225 1229 ' ',
1226 1230 delay,
1227 1231 mask)
1228 1232 else:
1229 1233 html += '{:20.18}{:3}{:4}{:9}km{:>6}\r\n'.format(
1230 1234 conf.name,
1231 1235 mode,
1232 1236 operation,
1233 1237 delay,
1234 1238 mask)
1235 1239
1236 1240 return mark_safe(html)
1237 1241
1238 1242
1239 1243 def parse_mask(l):
1240 1244
1241 1245 values = []
1242 1246
1243 1247 for x in range(16):
1244 1248 if '{}'.format(x) in l:
1245 1249 values.append(1)
1246 1250 else:
1247 1251 values.append(0)
1248 1252
1249 1253 values.reverse()
1250 1254
1251 1255 return int(''.join([str(x) for x in values]), 2)
1252 1256
1253 1257
1254 1258 def dev_confs(request):
1255 1259
1256 1260 page = request.GET.get('page')
1257 1261 order = ('-programmed_date', )
1258 1262 filters = request.GET.copy()
1259 1263 if 'my configurations' in filters:
1260 1264 filters.pop('my configurations', None)
1261 1265 filters['mine'] = request.user.id
1262 1266 kwargs = get_paginator(Configuration, page, order, filters)
1263 1267 fields = ['tags', 'template', 'historical']
1264 1268 if request.user.is_authenticated:
1265 1269 fields.append('my configurations')
1266 1270 form = FilterForm(initial=request.GET, extra_fields=fields)
1267 1271 kwargs['keys'] = ['name', 'device', 'experiment',
1268 1272 'type', 'programmed_date', 'actions']
1269 1273 kwargs['title'] = 'Configuration'
1270 1274 kwargs['suptitle'] = 'List'
1271 1275 kwargs['no_sidebar'] = True
1272 1276 kwargs['form'] = form
1273 1277 kwargs['add_url'] = reverse('url_add_dev_conf', args=[0])
1274 1278 filters = request.GET.copy()
1275 1279 filters.pop('page', None)
1276 1280 kwargs['q'] = urlencode(filters)
1277 1281 kwargs['menu_configurations'] = 'active'
1278 1282
1279 1283 return render(request, 'base_list.html', kwargs)
1280 1284
1281 1285
1282 1286 def dev_conf(request, id_conf):
1283 1287
1284 1288 conf = get_object_or_404(Configuration, pk=id_conf)
1285 1289
1286 1290 return redirect(conf.get_absolute_url())
1287 1291
1288 1292
1289 1293 @login_required
1290 1294 def dev_conf_new(request, id_exp=0, id_dev=0):
1291 1295
1292 1296 if not is_developer(request.user):
1293 1297 messages.error(
1294 1298 request, 'Developer required, to create new configurations')
1295 1299 return redirect('index')
1296 1300
1297 1301 initial = {}
1298 1302 kwargs = {}
1299 1303
1300 1304 if id_exp != 0:
1301 1305 initial['experiment'] = id_exp
1302 1306
1303 1307 if id_dev != 0:
1304 1308 initial['device'] = id_dev
1305 1309
1306 1310 if request.method == 'GET':
1307 1311
1308 1312 if id_dev:
1309 1313 kwargs['button'] = 'Create'
1310 1314 device = Device.objects.get(pk=id_dev)
1311 1315 DevConfForm = CONF_FORMS[device.device_type.name]
1312 1316 initial['name'] = request.GET['name']
1313 1317 form = DevConfForm(initial=initial)
1314 1318 else:
1315 1319 if 'template' in request.GET:
1316 1320 if request.GET['template'] == '0':
1317 1321 choices = [(conf.pk, '{}'.format(conf))
1318 1322 for conf in Configuration.objects.filter(template=True)]
1319 1323 form = NewForm(initial={'create_from': 2},
1320 1324 template_choices=choices)
1321 1325 else:
1322 1326 kwargs['button'] = 'Create'
1323 1327 conf = Configuration.objects.get(
1324 1328 pk=request.GET['template'])
1325 1329 id_dev = conf.device.pk
1326 1330 DevConfForm = CONF_FORMS[conf.device.device_type.name]
1327 1331 form = DevConfForm(instance=conf,
1328 1332 initial={'name': '{}_{:%y%m%d}'.format(conf.name, datetime.now()),
1329 1333 'template': False,
1330 1334 'experiment': id_exp})
1331 1335 elif 'blank' in request.GET:
1332 1336 kwargs['button'] = 'Create'
1333 1337 form = ConfigurationForm(initial=initial)
1334 1338 else:
1335 1339 form = NewForm()
1336 1340
1337 1341 if request.method == 'POST':
1338 1342
1339 1343 device = Device.objects.get(pk=request.POST['device'])
1340 1344 DevConfForm = CONF_FORMS[device.device_type.name]
1341 1345
1342 1346 form = DevConfForm(request.POST)
1343 1347 kwargs['button'] = 'Create'
1344 1348 if form.is_valid():
1345 1349 conf = form.save(commit=False)
1346 1350 conf.author = request.user
1347 1351 conf.save()
1348 1352 return redirect('url_dev_conf', id_conf=conf.pk)
1349 1353
1350 1354 kwargs['id_exp'] = id_exp
1351 1355 kwargs['form'] = form
1352 1356 kwargs['title'] = 'Configuration'
1353 1357 kwargs['suptitle'] = 'New'
1354 1358 kwargs['menu_configurations'] = 'active'
1355 1359
1356 1360 if id_dev != 0:
1357 1361 device = Device.objects.get(pk=id_dev)
1358 1362 kwargs['device'] = device.device_type.name
1359 1363 return render(request, 'dev_conf_edit.html', kwargs)
1360 1364
1361 1365
1362 1366 @login_required
1363 1367 def dev_conf_edit(request, id_conf):
1364 1368
1365 1369 conf = get_object_or_404(Configuration, pk=id_conf)
1366 1370
1367 1371 DevConfForm = CONF_FORMS[conf.device.device_type.name]
1368 1372
1369 1373 if request.method == 'GET':
1370 1374 form = DevConfForm(instance=conf)
1371 1375
1372 1376 if request.method == 'POST':
1373 1377 form = DevConfForm(request.POST, instance=conf)
1374 1378
1375 1379 if form.is_valid():
1376 1380 form.save()
1377 1381 return redirect('url_dev_conf', id_conf=id_conf)
1378 1382
1379 1383 kwargs = {}
1380 1384 kwargs['form'] = form
1381 1385 kwargs['title'] = 'Device Configuration'
1382 1386 kwargs['suptitle'] = 'Edit'
1383 1387 kwargs['button'] = 'Update'
1384 1388 kwargs['menu_configurations'] = 'active'
1385 1389
1386 1390 ###### SIDEBAR ######
1387 1391 kwargs.update(sidebar(conf=conf))
1388 1392
1389 1393 return render(request, '%s_conf_edit.html' % conf.device.device_type.name, kwargs)
1390 1394
1391 1395
1392 1396 @login_required
1393 1397 def dev_conf_start(request, id_conf):
1394 1398
1395 1399 conf = get_object_or_404(Configuration, pk=id_conf)
1396 1400
1397 1401 if conf.start_device():
1398 1402 messages.success(request, conf.message)
1399 1403 else:
1400 1404 messages.error(request, conf.message)
1401 1405
1402 1406 #conf.status_device()
1403 1407
1404 1408 return redirect(conf.get_absolute_url())
1405 1409
1406 1410
1407 1411 @login_required
1408 1412 def dev_conf_stop(request, id_conf):
1409 1413
1410 1414 conf = get_object_or_404(Configuration, pk=id_conf)
1411 1415
1412 1416 if conf.stop_device():
1413 1417 messages.success(request, conf.message)
1414 1418 else:
1415 1419 messages.error(request, conf.message)
1416 1420
1417 1421 #conf.status_device()
1418 1422
1419 1423 return redirect(conf.get_absolute_url())
1420 1424
1421 1425
1422 1426 @login_required
1423 1427 def dev_conf_status(request, id_conf):
1424 1428
1425 1429 conf = get_object_or_404(Configuration, pk=id_conf)
1426 1430
1427 1431 conf_active = Configuration.objects.filter(pk=conf.device.conf_active).first()
1428 1432 if conf_active!=conf:
1429 1433 url = '#' if conf_active is None else conf_active.get_absolute_url()
1430 1434 label = 'None' if conf_active is None else conf_active.label
1431 1435 messages.warning(
1432 1436 request,
1433 1437 mark_safe('The current configuration has not been written to device, the active configuration is <a href="{}">{}</a>'.format(
1434 1438 url,
1435 1439 label
1436 1440 ))
1437 1441 )
1438 1442
1439 1443 return redirect(conf.get_absolute_url())
1440 1444
1441 1445 if conf.status_device():
1442 1446 messages.success(request, conf.message)
1443 1447 else:
1444 1448 messages.error(request, conf.message)
1445 1449
1446 1450 return redirect(conf.get_absolute_url())
1447 1451
1448 1452
1449 1453 @login_required
1450 1454 def dev_conf_reset(request, id_conf):
1451 1455
1452 1456 conf = get_object_or_404(Configuration, pk=id_conf)
1453 1457
1454 1458 if conf.reset_device():
1455 1459 messages.success(request, conf.message)
1456 1460 else:
1457 1461 messages.error(request, conf.message)
1458 1462
1459 1463 return redirect(conf.get_absolute_url())
1460 1464
1461 1465
1462 1466 @login_required
1463 1467 def dev_conf_write(request, id_conf):
1464 1468
1465 1469 conf = get_object_or_404(Configuration, pk=id_conf)
1466 1470
1467 1471 if request.method == 'POST':
1468 1472 if conf.write_device():
1469 1473 conf.device.conf_active = conf.pk
1470 1474 conf.device.save()
1471 1475 messages.success(request, conf.message)
1472 1476 if has_been_modified(conf):
1473 1477 conf.clone(type=1, template=False)
1474 1478 else:
1475 1479 messages.error(request, conf.message)
1476 1480
1477 1481 return redirect(get_object_or_404(Configuration, pk=id_conf).get_absolute_url())
1478 1482
1479 1483 kwargs = {
1480 1484 'title': 'Write Configuration',
1481 1485 'suptitle': conf.label,
1482 1486 'message': 'Are you sure yo want to write this {} configuration?'.format(conf.device),
1483 1487 'delete': False
1484 1488 }
1485 1489 kwargs['menu_configurations'] = 'active'
1486 1490
1487 1491 return render(request, 'confirm.html', kwargs)
1488 1492
1489 1493
1490 1494 @login_required
1491 1495 def dev_conf_read(request, id_conf):
1492 1496
1493 1497 conf = get_object_or_404(Configuration, pk=id_conf)
1494 1498
1495 1499 DevConfForm = CONF_FORMS[conf.device.device_type.name]
1496 1500
1497 1501 if request.method == 'GET':
1498
1499 1502 parms = conf.read_device()
1500 1503 #conf.status_device()
1501 1504
1502 1505 if not parms:
1503 1506 messages.error(request, conf.message)
1504 1507 return redirect(conf.get_absolute_url())
1505 1508
1506 1509 form = DevConfForm(initial=parms, instance=conf)
1507 1510
1508 1511 if request.method == 'POST':
1509 1512 form = DevConfForm(request.POST, instance=conf)
1510 1513
1511 1514 if form.is_valid():
1512 1515 form.save()
1513 1516 return redirect(conf.get_absolute_url())
1514 1517
1515 1518 messages.error(request, "Parameters could not be saved")
1516 1519
1517 1520 kwargs = {}
1518 1521 kwargs['id_dev'] = conf.id
1519 1522 kwargs['form'] = form
1520 1523 kwargs['title'] = 'Device Configuration'
1521 1524 kwargs['suptitle'] = 'Parameters read from device'
1522 1525 kwargs['button'] = 'Save'
1523 1526
1524 1527 ###### SIDEBAR ######
1525 1528 kwargs.update(sidebar(conf=conf))
1526 1529
1527 1530 return render(request, '%s_conf_edit.html' % conf.device.device_type.name, kwargs)
1528 1531
1529 1532
1530 1533 @login_required
1531 1534 def dev_conf_import(request, id_conf):
1532 1535
1533 1536 conf = get_object_or_404(Configuration, pk=id_conf)
1534 1537 DevConfForm = CONF_FORMS[conf.device.device_type.name]
1535 1538
1536 1539 if request.method == 'GET':
1537 1540 file_form = UploadFileForm()
1538 1541
1539 1542 if request.method == 'POST':
1540 1543 file_form = UploadFileForm(request.POST, request.FILES)
1541 1544
1542 1545 if file_form.is_valid():
1543 1546
1544 1547 data = conf.import_from_file(request.FILES['file'])
1545 1548 parms = Params(data=data).get_conf(
1546 1549 dtype=conf.device.device_type.name)
1547 1550
1548 1551 if parms:
1549 1552
1550 1553 form = DevConfForm(initial=parms, instance=conf)
1551 1554
1552 1555 kwargs = {}
1553 1556 kwargs['id_dev'] = conf.id
1554 1557 kwargs['form'] = form
1555 1558 kwargs['title'] = 'Device Configuration'
1556 1559 kwargs['suptitle'] = 'Parameters imported'
1557 1560 kwargs['button'] = 'Save'
1558 1561 kwargs['action'] = conf.get_absolute_url_edit()
1559 1562 kwargs['previous'] = conf.get_absolute_url()
1560 1563
1561 1564 ###### SIDEBAR ######
1562 1565 kwargs.update(sidebar(conf=conf))
1563 1566
1564 1567 messages.success(
1565 1568 request, "Parameters imported from: '%s'." % request.FILES['file'].name)
1566 1569
1567 1570 return render(request, '%s_conf_edit.html' % conf.device.device_type.name, kwargs)
1568 1571
1569 1572 messages.error(request, "Could not import parameters from file")
1570 1573
1571 1574 kwargs = {}
1572 1575 kwargs['id_dev'] = conf.id
1573 1576 kwargs['title'] = 'Device Configuration'
1574 1577 kwargs['form'] = file_form
1575 1578 kwargs['suptitle'] = 'Importing file'
1576 1579 kwargs['button'] = 'Import'
1577 1580 kwargs['menu_configurations'] = 'active'
1578 1581
1579 1582 kwargs.update(sidebar(conf=conf))
1580 1583
1581 1584 return render(request, 'dev_conf_import.html', kwargs)
1582 1585
1583 1586
1584 1587 @login_required
1585 1588 def dev_conf_export(request, id_conf):
1586 1589
1587 1590 conf = get_object_or_404(Configuration, pk=id_conf)
1588 1591
1589 1592 if request.method == 'GET':
1590 1593 file_form = DownloadFileForm(conf.device.device_type.name)
1591 1594
1592 1595 if request.method == 'POST':
1593 1596 file_form = DownloadFileForm(
1594 1597 conf.device.device_type.name, request.POST)
1595 1598
1596 1599 if file_form.is_valid():
1597 1600 fields = conf.export_to_file(
1598 1601 format=file_form.cleaned_data['format'])
1599 1602 if not fields['content']:
1600 1603 messages.error(request, conf.message)
1601 1604 return redirect(conf.get_absolute_url_export())
1602 1605 response = HttpResponse(content_type=fields['content_type'])
1603 1606 response['Content-Disposition'] = 'attachment; filename="%s"' % fields['filename']
1604 1607 response.write(fields['content'])
1605 1608
1606 1609 return response
1607 1610
1608 1611 messages.error(request, "Could not export parameters")
1609 1612
1610 1613 kwargs = {}
1611 1614 kwargs['id_dev'] = conf.id
1612 1615 kwargs['title'] = 'Device Configuration'
1613 1616 kwargs['form'] = file_form
1614 1617 kwargs['suptitle'] = 'Exporting file'
1615 1618 kwargs['button'] = 'Export'
1616 1619 kwargs['menu_configurations'] = 'active'
1617 1620
1618 1621 return render(request, 'dev_conf_export.html', kwargs)
1619 1622
1620 1623
1621 1624 @login_required
1622 1625 def dev_conf_delete(request, id_conf):
1623 1626
1624 1627 conf = get_object_or_404(Configuration, pk=id_conf)
1625 1628
1626 1629 if request.method == 'POST':
1627 1630 if is_developer(request.user):
1628 1631 conf.delete()
1629 1632 return redirect('url_dev_confs')
1630 1633
1631 1634 messages.error(request, 'Not enough permission to delete this object')
1632 1635 return redirect(conf.get_absolute_url())
1633 1636
1634 1637 kwargs = {
1635 1638 'title': 'Delete',
1636 1639 'suptitle': 'Configuration',
1637 1640 'object': conf,
1638 1641 'delete': True
1639 1642 }
1640 1643 kwargs['menu_configurations'] = 'active'
1641 1644
1642 1645 return render(request, 'confirm.html', kwargs)
1643 1646
1644 1647
1645 1648 def sidebar(**kwargs):
1646 1649
1647 1650 side_data = {}
1648 1651
1649 1652 conf = kwargs.get('conf', None)
1650 1653 experiment = kwargs.get('experiment', None)
1651 1654
1652 1655 if not experiment:
1653 1656 experiment = conf.experiment
1654 1657
1655 1658 if experiment:
1656 1659 side_data['experiment'] = experiment
1657 1660 campaign = experiment.campaign_set.all()
1658 1661 if campaign:
1659 1662 side_data['campaign'] = campaign[0]
1660 1663 experiments = campaign[0].experiments.all().order_by('name')
1661 1664 else:
1662 1665 experiments = [experiment]
1663 1666 configurations = experiment.configuration_set.filter(type=0)
1664 1667 side_data['side_experiments'] = experiments
1665 1668 side_data['side_configurations'] = configurations.order_by(
1666 1669 'device__device_type__name')
1667 1670
1668 1671 return side_data
1669 1672
1670 1673
1671 1674 def get_paginator(model, page, order, filters={}, n=8):
1672 1675
1673 1676 kwargs = {}
1674 1677 query = Q()
1675 1678 if isinstance(filters, QueryDict):
1676 1679 filters = filters.dict()
1677 1680 [filters.pop(key) for key in list(filters) if filters[key] in ('', ' ')]
1678 1681 filters.pop('page', None)
1679 1682
1680 1683 fields = [f.name for f in model._meta.get_fields()]
1681 1684
1682 1685 if 'template' in filters:
1683 1686 filters['template'] = True
1684 1687 if 'historical' in filters:
1685 1688 filters.pop('historical')
1686 1689 filters['type'] = 1
1687 1690 elif 'type' in fields:
1688 1691 filters['type'] = 0
1689 1692 if 'start_date' in filters:
1690 1693 filters['start_date__gte'] = filters.pop('start_date')
1691 1694 if 'end_date' in filters:
1692 1695 filters['start_date__lte'] = filters.pop('end_date')
1693 1696 if 'tags' in filters:
1694 1697 tags = filters.pop('tags')
1695 1698 if 'tags' in fields:
1696 1699 query = query | Q(tags__icontains=tags)
1697 1700 if 'label' in fields:
1698 1701 query = query | Q(label__icontains=tags)
1699 1702 if 'location' in fields:
1700 1703 query = query | Q(location__name__icontains=tags)
1701 1704 if 'device' in fields:
1702 1705 query = query | Q(device__device_type__name__icontains=tags)
1703 1706 query = query | Q(device__location__name__icontains=tags)
1704 1707 if 'device_type' in fields:
1705 1708 query = query | Q(device_type__name__icontains=tags)
1706 1709
1707 1710 if 'mine' in filters:
1708 1711 filters['author_id'] = filters['mine']
1709 1712 filters.pop('mine')
1710 1713 object_list = model.objects.filter(query, **filters).order_by(*order)
1711 1714 paginator = Paginator(object_list, n)
1712 1715
1713 1716 try:
1714 1717 objects = paginator.page(page)
1715 1718 except PageNotAnInteger:
1716 1719 objects = paginator.page(1)
1717 1720 except EmptyPage:
1718 1721 objects = paginator.page(paginator.num_pages)
1719 1722
1720 1723 kwargs['objects'] = objects
1721 1724 kwargs['offset'] = (int(page)-1)*n if page else 0
1722 1725
1723 1726 return kwargs
1724 1727
1725 1728
1726 1729 def operation(request, id_camp=None):
1727 1730
1728 1731 kwargs = {}
1729 1732 kwargs['title'] = 'Radars Operation'
1730 1733 kwargs['no_sidebar'] = True
1731 1734 kwargs['menu_operation'] = 'active'
1732 1735 campaigns = Campaign.objects.filter(start_date__lte=datetime.now(),
1733 1736 end_date__gte=datetime.now()).order_by('-start_date')
1734 1737
1735 1738 if id_camp:
1736 1739 campaign = get_object_or_404(Campaign, pk=id_camp)
1737 1740 form = OperationForm(
1738 1741 initial={'campaign': campaign.id}, campaigns=campaigns)
1739 1742 kwargs['campaign'] = campaign
1740 1743 else:
1741 1744 # form = OperationForm(campaigns=campaigns)
1742 1745 kwargs['campaigns'] = campaigns
1743 1746 return render(request, 'operation.html', kwargs)
1744 1747
1745 1748 #---Experiment
1746 1749 keys = ['id', 'name', 'start_time', 'end_time', 'status']
1747 1750 kwargs['experiment_keys'] = keys[1:]
1748 1751 kwargs['experiments'] = experiments
1749 1752 #---Radar
1750 1753 kwargs['locations'] = campaign.get_experiments_by_radar()
1751 1754 kwargs['form'] = form
1752 1755
1753 1756 return render(request, 'operation.html', kwargs)
1754 1757
1755 1758
1756 1759 @login_required
1757 1760 def radar_start(request, id_camp, id_radar):
1758 1761
1759 1762 campaign = get_object_or_404(Campaign, pk=id_camp)
1760 1763 experiments = campaign.get_experiments_by_radar(id_radar)[0]['experiments']
1761 1764 now = datetime.now()
1762 1765
1763 1766 for exp in experiments:
1764 1767 #app.control.revoke(exp.task)
1765 1768 print(exp.status)
1766 1769 start = datetime.combine(datetime.now().date(), exp.start_time)
1767 1770 end = datetime.combine(datetime.now().date(), exp.end_time)
1768 1771 print(exp.start_time)
1769 1772 print(exp.end_time)
1770 1773
1771 1774 print(start)
1772 1775 print(end)
1773 1776 print(is_aware(start))
1774 1777 print(campaign.start_date)
1775 1778 print(campaign.end_date)
1776 1779 print(is_aware(campaign.start_date))
1777 1780 if end < start:
1778 1781 end += timedelta(1)
1779 1782
1780 1783 if exp.status == 2:
1781 1784 messages.warning(
1782 1785 request, 'Experiment {} already running'.format(exp))
1783 1786 continue
1784 1787
1785 1788 if exp.status == 3:
1786 1789 messages.warning(
1787 1790 request, 'Experiment {} already programmed'.format(exp))
1788 1791 continue
1789 1792
1790 1793 if start > campaign.end_date or start < campaign.start_date:
1791 1794 messages.warning(request, 'Experiment {} out of date'.format(exp))
1792 1795 continue
1793 1796
1794 1797 app.control.revoke(exp.task)
1795 1798 print("Llego luego del revoke")
1796 1799 if now > start and now <= end:
1797 1800 print("Caso now >start and <end")
1798 1801 task = task_start.delay(exp.id)
1799 1802 exp.status = task.wait()
1800 1803 if exp.status == 0:
1801 1804 messages.error(request, 'Experiment {} not start'.format(exp))
1802 1805 if exp.status == 2:
1803 1806 messages.success(request, 'Experiment {} started'.format(exp))
1804 1807 else:
1805 1808 print("Caso now < start o >end")
1806 1809 task = task_start.apply_async((exp.pk, ), eta=start)#start+timedelta(hours=5))
1807 1810 exp.task = task.id
1808 1811 exp.status = 3
1809 1812 messages.success(request, 'Experiment {} programmed to start at {}'.format(exp, start))
1810 1813
1811 1814 exp.save()
1812 1815
1813 1816 return HttpResponseRedirect(reverse('url_operation', args=[id_camp]))
1814 1817
1815 1818
1816 1819 @login_required
1817 1820 def radar_stop(request, id_camp, id_radar):
1818 1821
1819 1822 campaign = get_object_or_404(Campaign, pk=id_camp)
1820 1823 experiments = campaign.get_experiments_by_radar(id_radar)[0]['experiments']
1821 1824 print("Ingreso en stop radar_stop")
1822 1825 for exp in experiments:
1823 1826
1824 1827 if exp.task:
1825 1828 print("Ingreso antes de revoke stop")
1826 1829 app.control.revoke(exp.task)
1827 1830
1828 1831
1829 1832 if exp.status == 2: #status 2 es started
1830 1833 print("llama a exp.stop")
1831 1834 exp.stop()
1832 1835 messages.warning(request, 'Experiment {} stopped'.format(exp))
1833 1836 exp.status = 1
1834 1837 exp.save()
1835 1838
1836 1839 return HttpResponseRedirect(reverse('url_operation', args=[id_camp]))
1837 1840
1838 1841
1839 1842 @login_required
1840 1843 def radar_refresh(request, id_camp, id_radar):
1841 1844
1842 1845 campaign = get_object_or_404(Campaign, pk=id_camp)
1843 1846 experiments = campaign.get_experiments_by_radar(id_radar)[0]['experiments']
1844 1847
1845 1848 i = app.control.inspect()
1846 1849 print(i)
1847 1850 print(i.scheduled())
1848 1851 print(i.scheduled().values())
1849 1852 scheduled = list(i.scheduled().values())[0]
1850 1853 revoked = list(i.revoked().values())[0]
1851 1854
1852 1855 for exp in experiments:
1853 1856 if exp.task in revoked:
1854 1857 exp.status = 1
1855 1858 elif exp.task in [t['request']['id'] for t in scheduled if 'task_stop' in t['request']['name']]:
1856 1859 exp.status = 2
1857 1860 elif exp.task in [t['request']['id'] for t in scheduled if 'task_start' in t['request']['name']]:
1858 1861 exp.status = 3
1859 1862 else:
1860 1863 exp.status = 4
1861 1864 exp.save()
1862 1865 return HttpResponseRedirect(reverse('url_operation', args=[id_camp]))
1863 1866
1864 1867 @login_required
1865 1868 def revoke_tasks(request, id_camp):
1866 1869
1867 1870 i = app.control.inspect()
1868 1871 scheduled = list(i.scheduled().values())[0]
1869 1872 revoked = list(i.revoked().values())[0]
1870 1873
1871 1874 for t in scheduled:
1872 1875 if t['request']['id'] in revoked:
1873 1876 continue
1874 1877 app.control.revoke(t['request']['id'])
1875 1878 exp = Experiment.objects.get(pk=eval(str(t['request']['args']))[0])
1876 1879 eta = t['eta']
1877 1880 task = t['request']['name'].split('.')[-1]
1878 1881 messages.warning(request, 'Scheduled {} at {} for experiment {} revoked'.format(task, eta, exp.name))
1879 1882
1880 1883 return HttpResponseRedirect(reverse('url_operation', args=[id_camp]))
1881 1884
1882 1885 @login_required
1883 1886 def show_tasks(request, id_camp):
1884 1887
1885 1888 i = app.control.inspect()
1886 1889 scheduled = list(i.scheduled().values())[0]
1887 1890 revoked = list(i.revoked().values())[0]
1888 1891
1889 1892 for t in scheduled:
1890 1893 if t['request']['id'] in revoked:
1891 1894 continue
1892 1895 exp = Experiment.objects.get(pk=eval(str(t['request']['args']))[0])
1893 1896 eta = t['eta']
1894 1897 task = t['request']['name'].split('.')[-1]
1895 1898 messages.success(request, 'Task {} scheduled at {} for experiment {}'.format(task, eta, exp.name))
1896 1899
1897 1900 return HttpResponseRedirect(reverse('url_operation', args=[id_camp]))
1898 1901
1899 1902 def real_time(request):
1900 1903
1901 1904 graphic_path = "/home/fiorella/Pictures/catwbeanie.jpg"
1902 1905
1903 1906 kwargs = {}
1904 1907 kwargs['title'] = 'CLAIRE'
1905 1908 kwargs['suptitle'] = 'Real Time'
1906 1909 kwargs['no_sidebar'] = True
1907 1910 kwargs['graphic_path'] = graphic_path
1908 1911 kwargs['graphic1_path'] = 'http://www.bluemaize.net/im/girls-accessories/shark-beanie-11.jpg'
1909 1912
1910 1913 return render(request, 'real_time.html', kwargs)
1911 1914
1912 1915 def theme(request, theme):
1913 1916
1914 1917 user = request.user
1915 1918 user.profile.theme = theme
1916 1919 user.save()
1917 1920 return redirect('index')
@@ -1,291 +1,295
1 1 import ast
2 2 import json
3 3 import requests
4 4 import base64
5 5 import struct
6 6 from struct import pack
7 7 import time
8 8 from django.contrib import messages
9 9 from django.db import models
10 10 from django.urls import reverse
11 11 from django.core.validators import MinValueValidator, MaxValueValidator
12 12
13 13 from apps.main.models import Configuration
14 14
15 15 AXIS_VALUE = (
16 16 ('AZI', 'azimuth'),
17 17 ('ELE', 'elevation')
18 18 )
19 19
20 20 class PedestalConfiguration(Configuration):
21 21
22 22 axis = models.CharField(
23 23 verbose_name='Axis',
24 24 max_length=3,
25 25 choices=AXIS_VALUE,
26 26 null=False,
27 27 blank=False
28 28 )
29 29
30 30 speed = models.FloatField(
31 31 verbose_name='Speed',
32 32 validators=[MinValueValidator(-20), MaxValueValidator(20)],
33 33 blank=False,
34 34 null=False
35 35 )
36 36
37 37 table = models.CharField(
38 38 verbose_name="Table",
39 39 max_length=100,
40 40 default='',
41 41 blank=False,
42 42 null=False
43 43 )
44 44
45 45 class Meta:
46 46 db_table = 'pedestal_configurations'
47 47
48 48 def __str__(self):
49 49 return str(self.label)
50 50
51 51 def get_absolute_url_plot(self):
52 52 return reverse('url_plot_pedestal_pulses', args=[str(self.id)])
53 53
54 54 def request(self, cmd, method='get', **kwargs):
55 55
56 56 req = getattr(requests, method)(self.device.url(cmd), **kwargs)
57 57 payload = req.json()
58 58
59 59 return payload
60 60
61 61 def status_device(self):
62 62
63 63 try:
64 self.device.status = 0
65 payload = self.request('status')
66 if payload['status']=='enable':
67 self.device.status = 3
64 #self.device.status = 0
65 #payload = self.request('status')
66 payload = requests.get(self.device.url())
67 print(payload)
68 if payload:
69 self.device.status = 1
68 70 elif payload['status']=='disable':
69 71 self.device.status = 2
70 72 else:
71 73 self.device.status = 1
72 74 self.device.save()
73 75 self.message = 'Pedestal status: {}'.format(payload['status'])
74 76 return False
75 77 except Exception as e:
76 78 if 'No route to host' not in str(e):
77 79 self.device.status = 4
78 80 self.device.save()
79 81 self.message = 'Pedestal status: {}'.format(str(e))
80 82 return False
81 83
82 84 self.device.save()
83 85 return True
84 86
85 87 def reset_device(self):
86 88
87 89 try:
88 90 payload = self.request('reset', 'post')
89 91 if payload['reset']=='ok':
90 92 self.message = 'Pedestal restarted OK'
91 93 self.device.status = 2
92 94 self.device.save()
93 95 else:
94 96 self.message = 'Pedestal restart fail'
95 97 self.device.status = 4
96 98 self.device.save()
97 99 except Exception as e:
98 100 self.message = 'Pedestal reset: {}'.format(str(e))
99 101 return False
100 102
101 103 return True
102 104
103 105 def stop_device(self):
104 106
105 107 try:
106 108 command = self.device.url() + "stop"
107 109 r = requests.get(command)
108 110 if r:
109 111 self.device.status = 4
110 112 self.device.save()
111 113 self.message = 'Pedestal stopped'
112 114 else:
113 115 self.device.status = 4
114 116 self.device.save()
115 117 return False
116 118 except Exception as e:
117 119 if 'No route to host' not in str(e):
118 120 self.device.status = 4
119 121 else:
120 122 self.device.status = 0
121 self.message = 'Pedestal stop: {}'.format(str(e))
123 #self.message = 'Pedestal stop: {}'.format(str(e))
124 self.message = "Pedestal can't start, please check network/device connection or IP address/port configuration"
122 125 self.device.save()
123 126 return False
124 127
125 128 return True
126 129
127 130 def start_device(self):
128 print("Entró al start")
131
129 132 try:
130 133 pedestal = PedestalConfiguration.objects.get(pk=self)
131 134 print(pedestal)
132 135 pedestal_axis = pedestal.get_axis_display()
133 136 print(pedestal)
134 137 print(pedestal_axis)
135 138 table = pedestal.table
136 139 print(table)
137 140 li = list(table.split(", "))
138 141 print(li)
139 142 list_of_floats = []
140 143 for item in li:
141 144 list_of_floats.append(float(item))
142 145 print(list_of_floats)
143 146 byte_table = []
144 147 for x in list_of_floats:
145 148 temp = bytearray(struct.pack("f", x))
146 149 byte_table.append(temp[3])
147 150 byte_table.append(temp[2])
148 151 byte_table.append(temp[1])
149 152 byte_table.append(temp[0])
150 153 print(byte_table)
151 154 coded_table = base64.urlsafe_b64encode(bytes(byte_table))
152 155 coded_table_ascii = coded_table.decode('ascii')
153 156 print(coded_table_ascii)
154 157 data = {'axis': pedestal_axis, 'speed': pedestal.speed, 'table': coded_table_ascii}
155 158 print(data)
156 159 json_data = json.dumps(data)
157 160 print(json_data)
158 161 first_position = table[0]
159 162
160 163 if pedestal.axis=='azimuth':
161 164 json_az = json.dumps({"axis": 'azimuth', "position": 0.0})
162 165 json_el = json.dumps({"axis": 'elevation', "position": first_position})
163 166 else:
164 167 json_az = json.dumps({"axis": 'azimuth', "position": first_position})
165 168 json_el = json.dumps({"axis": 'elevation', "position": 0.0})
166 169
167 170 base64_table = base64.urlsafe_b64encode(json_data.encode('ascii'))
168 171 base64_az = base64.urlsafe_b64encode(json_az.encode('ascii'))
169 172 base64_el = base64.urlsafe_b64encode(json_el.encode('ascii'))
170 173
171 174 table_url = self.device.url() + "table?params="
172 175 az_url = self.device.url() + "position?params="
173 176 el_url = self.device.url() + "position?params="
174 177
175 178
176 179 complete_url = table_url + base64_table.decode('ascii')
177 180
178 181 az_url = az_url + base64_az.decode('ascii')
179 182 el_url = el_url + base64_el.decode('ascii')
180 183 print(complete_url)
181 184 print(az_url)
182 185 print(el_url)
183 186 r = requests.get(az_url)
184 187 r = requests.get(el_url)
185 188 #time.sleep(10)
186 189 r = requests.get(complete_url)
187 190 if r:
188 191 self.device.status = 3
189 192 self.device.save()
190 193 self.message = 'Pedestal configured and started'
191 194 else:
192 195 return False
193 196 except Exception as e:
194 197 if 'No route to host' not in str(e):
195 198 self.device.status = 4
196 199 else:
197 200 self.device.status = 0
198 self.message = 'Pedestal start: {}'.format(str(e))
201 #self.message = 'Pedestal start: {}'.format(str(e))
202 self.message = "Pedestal can't start, please check network/device connection or IP address/port configuration"
199 203 self.device.save()
200 204 return False
201 205
202 206 return True
203 207
204 208 #def write_device(self, raw=False):
205 209
206 210 if not raw:
207 211 clock = RCClock.objects.get(rc_configuration=self)
208 212 print(clock)
209 213 if clock.mode:
210 214 data = {'default': clock.frequency}
211 215 else:
212 216 data = {'manual': [clock.multiplier, clock.divisor, clock.reference]}
213 217 payload = self.request('setfreq', 'post', data=json.dumps(data))
214 218 print(payload)
215 219 if payload['command'] != 'ok':
216 220 self.message = 'Pedestal write: {}'.format(payload['command'])
217 221 else:
218 222 self.message = payload['programming']
219 223 if payload['programming'] == 'fail':
220 224 self.message = 'Pedestal write: error programming CGS chip'
221 225
222 226 values = []
223 227 for pulse, delay in zip(self.get_pulses(), self.get_delays()):
224 228 while delay>65536:
225 229 values.append((pulse, 65535))
226 230 delay -= 65536
227 231 values.append((pulse, delay-1))
228 232 data = bytearray()
229 233 #reset
230 234 data.extend((128, 0))
231 235 #disable
232 236 data.extend((129, 0))
233 237 #SW switch
234 238 if self.control_sw:
235 239 data.extend((130, 2))
236 240 else:
237 241 data.extend((130, 0))
238 242 #divider
239 243 data.extend((131, self.clock_divider-1))
240 244 #enable writing
241 245 data.extend((139, 62))
242 246
243 247 last = 0
244 248 for tup in values:
245 249 vals = pack('<HH', last^tup[0], tup[1])
246 250 last = tup[0]
247 251 data.extend((133, vals[1], 132, vals[0], 133, vals[3], 132, vals[2]))
248 252
249 253 #enable
250 254 data.extend((129, 1))
251 255
252 256 if raw:
253 257 return b64encode(data)
254 258
255 259 try:
256 260 payload = self.request('stop', 'post')
257 261 payload = self.request('reset', 'post')
258 262 #payload = self.request('divider', 'post', data={'divider': self.clock_divider-1})
259 263 #payload = self.request('write', 'post', data=b64encode(bytearray((139, 62))), timeout=20)
260 264 n = len(data)
261 265 x = 0
262 266 #while x < n:
263 267 payload = self.request('write', 'post', data=b64encode(data))
264 268 # x += 1024
265 269
266 270 if payload['write']=='ok':
267 271 self.device.status = 3
268 272 self.device.save()
269 273 self.message = 'Pedestal configured and started'
270 274 else:
271 275 self.device.status = 1
272 276 self.device.save()
273 277 self.message = 'Pedestal write: {}'.format(payload['write'])
274 278 return False
275 279
276 280 #payload = self.request('start', 'post')
277 281
278 282 except Exception as e:
279 283 if 'No route to host' not in str(e):
280 284 self.device.status = 4
281 285 else:
282 286 self.device.status = 0
283 287 self.message = 'Pedestal write: {}'.format(str(e))
284 288 self.device.save()
285 289 return False
286 290
287 291 return True
288 292
289 293
290 294 def get_absolute_url_import(self):
291 295 return reverse('url_import_pedestal_conf', args=[str(self.id)])
@@ -1,136 +1,139
1 1
2 2 import json
3 3
4 4 from django.contrib import messages
5 5 from django.utils.safestring import mark_safe
6 6 from django.shortcuts import render, redirect, get_object_or_404, HttpResponse
7 7 from django.contrib.auth.decorators import login_required
8 8
9 9 from apps.main.models import Experiment, Device
10 10 from apps.main.views import sidebar
11 11
12 12 from .models import PedestalConfiguration
13 13 from .forms import PedestalConfigurationForm, PedestalImportForm
14 14
15 15
16 16 def conf(request, conf_id):
17 17
18 18 conf = get_object_or_404(PedestalConfiguration, pk=conf_id)
19 19
20 20 kwargs = {}
21 21 kwargs['dev_conf'] = conf
22 22 kwargs['dev_conf_keys'] = ['axis', 'speed', 'table']
23 23
24 24 kwargs['title'] = 'Configuration'
25 25 kwargs['suptitle'] = 'Detail'
26 26
27 27 kwargs['button'] = 'Edit Configuration'
28
29 conf.status_device()
30
28 31 ###### SIDEBAR ######
29 32 kwargs.update(sidebar(conf=conf))
30 33
31 34 return render(request, 'pedestal_conf.html', kwargs)
32 35
33 36 @login_required
34 37 def conf_edit(request, conf_id):
35 38
36 39 conf = get_object_or_404(PedestalConfiguration, pk=conf_id)
37 40 print(conf)
38 41 #print("fin de carga de params")
39 42 if request.method=='GET':
40 43 print("GET case")
41 44 form = PedestalConfigurationForm(instance=conf)
42 45 print(form)
43 46
44 47 elif request.method=='POST':
45 48 #print("ingreso a post conf edit")
46 49 line_data = {}
47 50 conf_data = {}
48 51 clock_data = {}
49 52 extras = []
50 53 print("Inicio impresion POST#####")
51 54 print(request.POST.items)
52 55 print("Fin impresion de POST items#####")
53 56 #classified post fields
54 57 for label,value in request.POST.items():
55 58 if label=='csrfmiddlewaretoken':
56 59 continue
57 60
58 61 if label.count('|')==0:
59 62 if label in ('mode', 'multiplier', 'divisor', 'reference', 'frequency'):
60 63 clock_data[label] = value
61 64 else:
62 65 conf_data[label] = value
63 66 continue
64 67
65 68 elif label.split('|')[0]!='-1':
66 69 extras.append(label)
67 70 continue
68 71
69 72 #print(label)
70 73 x, pk, name = label.split('|')
71 74
72 75 if name=='codes':
73 76 value = [s for s in value.split('\r\n') if s]
74 77
75 78 if pk in line_data:
76 79 line_data[pk][name] = value
77 80 else:
78 81 line_data[pk] = {name:value}
79 82 #print(line_data[pk])
80 83 #update conf
81 84
82 85 form = PedestalConfigurationForm(conf_data, instance=conf)
83 86
84 87 #print(request.POST.items())
85 88
86 89 if form.is_valid():
87 90 form.save()
88 91
89 92 messages.success(request, 'Pedestal configuration successfully updated')
90 93
91 94 return redirect(conf.get_absolute_url())
92 95
93 96 kwargs = {}
94 97 kwargs['dev_conf'] = conf
95 98 kwargs['form'] = form
96 99 kwargs['edit'] = True
97 100
98 101 kwargs['title'] = 'Pedestal Configuration'
99 102 kwargs['suptitle'] = 'Edit'
100 103 kwargs['button'] = 'Update'
101 104
102 105 print(kwargs)
103 106 print(form)
104 107 return render(request, 'pedestal_conf_edit.html', kwargs)
105 108
106 109 def import_file(request, conf_id):
107 110
108 111 conf = get_object_or_404(PedestalConfiguration, pk=conf_id)
109 112 if request.method=='POST':
110 113 form = PedestalImportForm(request.POST, request.FILES)
111 114 if form.is_valid():
112 115 try:
113 116 data = conf.import_from_file(request.FILES['file_name'])
114 117 conf.dict_to_parms(data)
115 118 messages.success(request, 'Configuration "%s" loaded succesfully' % request.FILES['file_name'])
116 119 return redirect(conf.get_absolute_url_edit())
117 120
118 121 except Exception as e:
119 122 messages.error(request, 'Error parsing file: "%s" - %s' % (request.FILES['file_name'], repr(e)))
120 123 else:
121 124 messages.warning(request, 'Your current configuration will be replaced')
122 125 form = PedestalImportForm()
123 126
124 127 kwargs = {}
125 128 kwargs['form'] = form
126 129 kwargs['title'] = 'Pedestal Configuration'
127 130 kwargs['suptitle'] = 'Import file'
128 131 kwargs['button'] = 'Upload'
129 132 kwargs['previous'] = conf.get_absolute_url()
130 133
131 134 return render(request, 'pedestal_import.html', kwargs)
132 135
133 136 def conf_raw(request, conf_id):
134 137 conf = get_object_or_404(PedestalConfiguration, pk=conf_id)
135 138 raw = conf.write_device(raw=True)
136 139 return HttpResponse(raw, content_type='application/json') No newline at end of file
1 NO CONTENT: modified file
The requested commit or file is too big and content was truncated. Show full diff
1 NO CONTENT: modified file
The requested commit or file is too big and content was truncated. Show full diff
1 NO CONTENT: modified file
The requested commit or file is too big and content was truncated. Show full diff
1 NO CONTENT: modified file
The requested commit or file is too big and content was truncated. Show full diff
1 NO CONTENT: modified file
The requested commit or file is too big and content was truncated. Show full diff
1 NO CONTENT: modified file
The requested commit or file is too big and content was truncated. Show full diff
General Comments 0
You need to be logged in to leave comments. Login now