##// 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
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
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
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
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
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
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
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
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
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
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
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 import ast
1 import ast
2 import json
2 import json
3 import requests
3 import requests
4 import base64
4 import base64
5 import struct
5 import struct
6 from struct import pack
6 from struct import pack
7 import time
7 import time
8 from django.contrib import messages
8 from django.contrib import messages
9 from django.db import models
9 from django.db import models
10 from django.urls import reverse
10 from django.urls import reverse
11 from django.core.validators import MinValueValidator, MaxValueValidator
11 from django.core.validators import MinValueValidator, MaxValueValidator
12
12
13 from apps.main.models import Configuration
13 from apps.main.models import Configuration
14
14
15 SELECTOR_VALUE = (
16 (0, 'disable'),
17 (1, 'enable')
18 )
19
15 class GeneratorConfiguration(Configuration):
20 class GeneratorConfiguration(Configuration):
16
21
17 periode = models.FloatField(
22 periode = models.IntegerField(
18 verbose_name='Periode',
23 verbose_name='Periode',
19 blank=False,
24 blank=False,
20 null=False
25 null=False
21 )
26 )
22
27
23 delay = models.FloatField(
28 delay = models.IntegerField(
24 verbose_name='Delay',
29 verbose_name='Delay',
25 blank=False,
30 blank=False,
26 null=False
31 null=False
27 )
32 )
28
33
29 width = models.FloatField(
34 width = models.IntegerField(
30 verbose_name='Width',
35 verbose_name='Width',
31 blank=False,
36 blank=False,
32 null=False
37 null=False
33 )
38 )
34
39
35 enable = models.BooleanField(
40 selector = models.IntegerField(
36 verbose_name='Enable',
41 verbose_name='Selector',
42 choices=SELECTOR_VALUE,
37 blank=False,
43 blank=False,
38 null=False
44 null=False
39 )
45 )
40
46
41 class Meta:
47 class Meta:
42 db_table = 'generator_configurations'
48 db_table = 'generator_configurations'
43
49
44 def __str__(self):
50 def __str__(self):
45 return str(self.label)
51 return str(self.label)
46
52
47 def get_absolute_url_plot(self):
53 def get_absolute_url_plot(self):
48 return reverse('url_plot_generator_pulses', args=[str(self.id)])
54 return reverse('url_plot_generator_pulses', args=[str(self.id)])
49
55
50 def request(self, cmd, method='get', **kwargs):
56 def request(self, cmd, method='get', **kwargs):
51
57
52 req = getattr(requests, method)(self.device.url(cmd), **kwargs)
58 req = getattr(requests, method)(self.device.url(cmd), **kwargs)
53 payload = req.json()
59 payload = req.json()
54
60
55 return payload
61 return payload
56
62
57 def status_device(self):
63 def status_device(self):
58
64
59 try:
65 try:
60 self.device.status = 0
66 #self.device.status = 0
61 payload = self.request('status')
67 #payload = self.request('status')
62 if payload['status']=='enable':
68 payload = requests.get(self.device.url())
63 self.device.status = 3
69 print(payload)
70 if payload:
71 self.device.status = 1
64 elif payload['status']=='disable':
72 elif payload['status']=='disable':
65 self.device.status = 2
73 self.device.status = 2
66 else:
74 else:
67 self.device.status = 1
75 self.device.status = 1
68 self.device.save()
76 self.device.save()
69 self.message = 'Generator status: {}'.format(payload['status'])
77 self.message = 'Generator status: {}'.format(payload['status'])
70 return False
78 return False
71 except Exception as e:
79 except Exception as e:
72 if 'No route to host' not in str(e):
80 if 'No route to host' not in str(e):
73 self.device.status = 4
81 self.device.status = 4
74 self.device.save()
82 self.device.save()
75 self.message = 'Generator status: {}'.format(str(e))
83 self.message = 'Generator status: {}'.format(str(e))
76 return False
84 return False
77
85
78 self.device.save()
86 self.device.save()
79 return True
87 return True
80
88
81 def reset_device(self):
89 def reset_device(self):
82
90
83 try:
91 try:
84 payload = self.request('reset', 'post')
92 payload = self.request('reset', 'post')
85 if payload['reset']=='ok':
93 if payload['reset']=='ok':
86 self.message = 'Generator restarted OK'
94 self.message = 'Generator restarted OK'
87 self.device.status = 2
95 self.device.status = 2
88 self.device.save()
96 self.device.save()
89 else:
97 else:
90 self.message = 'Generator restart fail'
98 self.message = 'Generator restart fail'
91 self.device.status = 4
99 self.device.status = 4
92 self.device.save()
100 self.device.save()
93 except Exception as e:
101 except Exception as e:
94 self.message = 'Generator reset: {}'.format(str(e))
102 self.message = 'Generator reset: {}'.format(str(e))
95 return False
103 return False
96
104
97 return True
105 return True
98
106
99 def stop_device(self):
107 def stop_device(self):
100
108
101 try:
109 try:
102 command = self.device.url() + "stop"
110 command = self.device.url() + "stop"
103 r = requests.get(command)
111 r = requests.get(command)
104 if r:
112 if r:
105 self.device.status = 4
113 self.device.status = 4
106 self.device.save()
114 self.device.save()
107 self.message = 'Generator stopped'
115 self.message = 'Generator stopped'
108 else:
116 else:
109 self.device.status = 4
117 self.device.status = 4
110 self.device.save()
118 self.device.save()
111 return False
119 return False
112 except Exception as e:
120 except Exception as e:
113 if 'No route to host' not in str(e):
121 if 'No route to host' not in str(e):
114 self.device.status = 4
122 self.device.status = 4
115 else:
123 else:
116 self.device.status = 0
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 self.device.save()
127 self.device.save()
119 return False
128 return False
120
129
121 return True
130 return True
122
131
123 def start_device(self):
132 def start_device(self):
124 print("Entró al start")
133
125 try:
134 try:
126 generator = GeneratorConfiguration.objects.get(pk=self)
135 generator = GeneratorConfiguration.objects.get(pk=self)
127 print(generator)
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 base64_trmode = base64.urlsafe_b64encode(json_trmode.encode('ascii'))
139 base64_trmode = base64.urlsafe_b64encode(json_trmode.encode('ascii'))
130 print(base64_trmode)
140 print(base64_trmode)
131 trmode_url = self.device.url() + "trmode?params="
141 trmode_url = self.device.url() + "trmode?params="
132 complete_url_trmode = trmode_url + base64_trmode.decode('ascii')
142 complete_url_trmode = trmode_url + base64_trmode.decode('ascii')
133 print(complete_url_trmode)
143 print(complete_url_trmode)
134 r = requests.get(complete_url_trmode)
144 r = requests.get(complete_url_trmode)
135
145
136 if r:
146 if r:
137 self.device.status = 3
147 self.device.status = 3
138 self.device.save()
148 self.device.save()
139 self.message = 'Generator configured and started'
149 self.message = 'Generator configured and started'
140 else:
150 else:
141 return False
151 return False
142 except Exception as e:
152 except Exception as e:
143 if 'No route to host' not in str(e):
153 if 'No route to host' not in str(e):
144 self.device.status = 4
154 self.device.status = 4
145 else:
155 else:
146 self.device.status = 0
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 self.device.save()
159 self.device.save()
149 return False
160 return False
150
161
151 return True
162 return True
152
163
153 #def write_device(self, raw=False):
164 #def write_device(self, raw=False):
154
165
155 if not raw:
166 if not raw:
156 clock = RCClock.objects.get(rc_configuration=self)
167 clock = RCClock.objects.get(rc_configuration=self)
157 print(clock)
168 print(clock)
158 if clock.mode:
169 if clock.mode:
159 data = {'default': clock.frequency}
170 data = {'default': clock.frequency}
160 else:
171 else:
161 data = {'manual': [clock.multiplier, clock.divisor, clock.reference]}
172 data = {'manual': [clock.multiplier, clock.divisor, clock.reference]}
162 payload = self.request('setfreq', 'post', data=json.dumps(data))
173 payload = self.request('setfreq', 'post', data=json.dumps(data))
163 print(payload)
174 print(payload)
164 if payload['command'] != 'ok':
175 if payload['command'] != 'ok':
165 self.message = 'Generator write: {}'.format(payload['command'])
176 self.message = 'Generator write: {}'.format(payload['command'])
166 else:
177 else:
167 self.message = payload['programming']
178 self.message = payload['programming']
168 if payload['programming'] == 'fail':
179 if payload['programming'] == 'fail':
169 self.message = 'Generator write: error programming CGS chip'
180 self.message = 'Generator write: error programming CGS chip'
170
181
171 values = []
182 values = []
172 for pulse, delay in zip(self.get_pulses(), self.get_delays()):
183 for pulse, delay in zip(self.get_pulses(), self.get_delays()):
173 while delay>65536:
184 while delay>65536:
174 values.append((pulse, 65535))
185 values.append((pulse, 65535))
175 delay -= 65536
186 delay -= 65536
176 values.append((pulse, delay-1))
187 values.append((pulse, delay-1))
177 data = bytearray()
188 data = bytearray()
178 #reset
189 #reset
179 data.extend((128, 0))
190 data.extend((128, 0))
180 #disable
191 #disable
181 data.extend((129, 0))
192 data.extend((129, 0))
182 #SW switch
193 #SW switch
183 if self.control_sw:
194 if self.control_sw:
184 data.extend((130, 2))
195 data.extend((130, 2))
185 else:
196 else:
186 data.extend((130, 0))
197 data.extend((130, 0))
187 #divider
198 #divider
188 data.extend((131, self.clock_divider-1))
199 data.extend((131, self.clock_divider-1))
189 #enable writing
200 #enable writing
190 data.extend((139, 62))
201 data.extend((139, 62))
191
202
192 last = 0
203 last = 0
193 for tup in values:
204 for tup in values:
194 vals = pack('<HH', last^tup[0], tup[1])
205 vals = pack('<HH', last^tup[0], tup[1])
195 last = tup[0]
206 last = tup[0]
196 data.extend((133, vals[1], 132, vals[0], 133, vals[3], 132, vals[2]))
207 data.extend((133, vals[1], 132, vals[0], 133, vals[3], 132, vals[2]))
197
208
198 #enable
209 #enable
199 data.extend((129, 1))
210 data.extend((129, 1))
200
211
201 if raw:
212 if raw:
202 return b64encode(data)
213 return b64encode(data)
203
214
204 try:
215 try:
205 payload = self.request('stop', 'post')
216 payload = self.request('stop', 'post')
206 payload = self.request('reset', 'post')
217 payload = self.request('reset', 'post')
207 #payload = self.request('divider', 'post', data={'divider': self.clock_divider-1})
218 #payload = self.request('divider', 'post', data={'divider': self.clock_divider-1})
208 #payload = self.request('write', 'post', data=b64encode(bytearray((139, 62))), timeout=20)
219 #payload = self.request('write', 'post', data=b64encode(bytearray((139, 62))), timeout=20)
209 n = len(data)
220 n = len(data)
210 x = 0
221 x = 0
211 #while x < n:
222 #while x < n:
212 payload = self.request('write', 'post', data=b64encode(data))
223 payload = self.request('write', 'post', data=b64encode(data))
213 # x += 1024
224 # x += 1024
214
225
215 if payload['write']=='ok':
226 if payload['write']=='ok':
216 self.device.status = 3
227 self.device.status = 3
217 self.device.save()
228 self.device.save()
218 self.message = 'Generator configured and started'
229 self.message = 'Generator configured and started'
219 else:
230 else:
220 self.device.status = 1
231 self.device.status = 1
221 self.device.save()
232 self.device.save()
222 self.message = 'Generator write: {}'.format(payload['write'])
233 self.message = 'Generator write: {}'.format(payload['write'])
223 return False
234 return False
224
235
225 #payload = self.request('start', 'post')
236 #payload = self.request('start', 'post')
226
237
227 except Exception as e:
238 except Exception as e:
228 if 'No route to host' not in str(e):
239 if 'No route to host' not in str(e):
229 self.device.status = 4
240 self.device.status = 4
230 else:
241 else:
231 self.device.status = 0
242 self.device.status = 0
232 self.message = 'Generator write: {}'.format(str(e))
243 self.message = 'Generator write: {}'.format(str(e))
233 self.device.save()
244 self.device.save()
234 return False
245 return False
235
246
236 return True
247 return True
237
248
238
249
239 def get_absolute_url_import(self):
250 def get_absolute_url_import(self):
240 return reverse('url_import_generator_conf', args=[str(self.id)])
251 return reverse('url_import_generator_conf', args=[str(self.id)])
@@ -1,30 +1,30
1 {% extends "dev_conf.html" %}
1 {% extends "dev_conf.html" %}
2 {% load static %}
2 {% load static %}
3 {% load bootstrap4 %}
3 {% load bootstrap4 %}
4 {% load main_tags %}
4 {% load main_tags %}
5
5
6 {% block content-detail %}
6 {% block content-detail %}
7
7
8 <h2>Pedestal</h2>
8 <h2>Generator</h2>
9 <table class="table table-bordered">
9 <table class="table table-bordered">
10 <tr>
10 <tr>
11 <th>Status</th>
11 <th>Status</th>
12 <td class="text-{{dev_conf.device.status_color}}"><strong>{{dev_conf.device.get_status_display}}</strong></td>
12 <td class="text-{{dev_conf.device.status_color}}"><strong>{{dev_conf.device.get_status_display}}</strong></td>
13 </tr>
13 </tr>
14
14
15 {% for key in dev_conf_keys %}
15 {% for key in dev_conf_keys %}
16 <tr>
16 <tr>
17 <th>{% get_verbose_field_name dev_conf key %}</th>
17 <th>{% get_verbose_field_name dev_conf key %}</th>
18 <td>{{dev_conf|attr:key}}</td>
18 <td>{{dev_conf|attr:key}}</td>
19 </tr>
19 </tr>
20 {% endfor %}
20 {% endfor %}
21 </table>
21 </table>
22 {% endblock %}
22 {% endblock %}
23
23
24 {% block extra-js%}
24 {% block extra-js%}
25 <script type="text/javascript">
25 <script type="text/javascript">
26 $("#bt_toggle").click(function() {
26 $("#bt_toggle").click(function() {
27 $(".panel-collapse").collapse('toggle')
27 $(".panel-collapse").collapse('toggle')
28 });
28 });
29 </script>
29 </script>
30 {% endblock %} No newline at end of file
30 {% endblock %}
@@ -1,27 +1,27
1 {% extends "dev_conf_edit.html" %}
1 {% extends "dev_conf_edit.html" %}
2 {% load bootstrap4 %}
2 {% load bootstrap4 %}
3 {% load static %}
3 {% load static %}
4
4
5 {% block extra-head %}
5 {% block extra-head %}
6 <style type="text/css">
6 <style type="text/css">
7 /* show the move cursor as the user moves the mouse over the panel header.*/
7 /* show the move cursor as the user moves the mouse over the panel header.*/
8 .panel-default { cursor: move; }
8 .panel-default { cursor: move; }
9 </style>
9 </style>
10 <script src="{% static 'js/jquery-ui.min.js' %}"></script>
10 <script src="{% static 'js/jquery-ui.min.js' %}"></script>
11
11
12 {% endblock %}
12 {% endblock %}
13
13
14 {% block content %}
14 {% block content %}
15 <form class="form" method="post">
15 <form class="form" method="post">
16 {% csrf_token %}
16 {% csrf_token %}
17 <h2>Pedestal</h2>
17 <h2>Generator</h2>
18 {% bootstrap_form form layout='horizontal' size='medium' %}
18 {% bootstrap_form form layout='horizontal' size='medium' %}
19 <div style="clear: both;"></div>
19 <div style="clear: both;"></div>
20 <br>
20 <br>
21 <div class="pull-right">
21 <div class="pull-right">
22 <button type="button" class="btn btn-primary" onclick="{% if previous %}window.location.replace('{{ previous }}');{% else %}history.go(-1);{% endif %}">Cancel</button>
22 <button type="button" class="btn btn-primary" onclick="{% if previous %}window.location.replace('{{ previous }}');{% else %}history.go(-1);{% endif %}">Cancel</button>
23 <button type="submit" class="btn btn-primary">{{button}}</button>
23 <button type="submit" class="btn btn-primary">{{button}}</button>
24 </div>
24 </div>
25 </form>
25 </form>
26 {% endblock %}
26 {% endblock %}
27 No newline at end of file
27
@@ -1,136 +1,139
1
1
2 import json
2 import json
3
3
4 from django.contrib import messages
4 from django.contrib import messages
5 from django.utils.safestring import mark_safe
5 from django.utils.safestring import mark_safe
6 from django.shortcuts import render, redirect, get_object_or_404, HttpResponse
6 from django.shortcuts import render, redirect, get_object_or_404, HttpResponse
7 from django.contrib.auth.decorators import login_required
7 from django.contrib.auth.decorators import login_required
8
8
9 from apps.main.models import Experiment, Device
9 from apps.main.models import Experiment, Device
10 from apps.main.views import sidebar
10 from apps.main.views import sidebar
11
11
12 from .models import GeneratorConfiguration
12 from .models import GeneratorConfiguration
13 from .forms import GeneratorConfigurationForm, GeneratorImportForm
13 from .forms import GeneratorConfigurationForm, GeneratorImportForm
14
14
15
15
16 def conf(request, conf_id):
16 def conf(request, conf_id):
17
17
18 conf = get_object_or_404(GeneratorConfiguration, pk=conf_id)
18 conf = get_object_or_404(GeneratorConfiguration, pk=conf_id)
19
19
20 kwargs = {}
20 kwargs = {}
21 kwargs['dev_conf'] = conf
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 kwargs['title'] = 'Configuration'
24 kwargs['title'] = 'Configuration'
25 kwargs['suptitle'] = 'Detail'
25 kwargs['suptitle'] = 'Detail'
26
26
27 kwargs['button'] = 'Edit Configuration'
27 kwargs['button'] = 'Edit Configuration'
28
29 conf.status_device()
30
28 ###### SIDEBAR ######
31 ###### SIDEBAR ######
29 kwargs.update(sidebar(conf=conf))
32 kwargs.update(sidebar(conf=conf))
30
33
31 return render(request, 'generator_conf.html', kwargs)
34 return render(request, 'generator_conf.html', kwargs)
32
35
33 @login_required
36 @login_required
34 def conf_edit(request, conf_id):
37 def conf_edit(request, conf_id):
35
38
36 conf = get_object_or_404(GeneratorConfiguration, pk=conf_id)
39 conf = get_object_or_404(GeneratorConfiguration, pk=conf_id)
37 print(conf)
40 print(conf)
38 #print("fin de carga de params")
41 #print("fin de carga de params")
39 if request.method=='GET':
42 if request.method=='GET':
40 print("GET case")
43 print("GET case")
41 form = GeneratorConfigurationForm(instance=conf)
44 form = GeneratorConfigurationForm(instance=conf)
42 print(form)
45 print(form)
43
46
44 elif request.method=='POST':
47 elif request.method=='POST':
45 #print("ingreso a post conf edit")
48 #print("ingreso a post conf edit")
46 line_data = {}
49 line_data = {}
47 conf_data = {}
50 conf_data = {}
48 clock_data = {}
51 clock_data = {}
49 extras = []
52 extras = []
50 print("Inicio impresion POST#####")
53 print("Inicio impresion POST#####")
51 print(request.POST.items)
54 print(request.POST.items)
52 print("Fin impresion de POST items#####")
55 print("Fin impresion de POST items#####")
53 #classified post fields
56 #classified post fields
54 for label,value in request.POST.items():
57 for label,value in request.POST.items():
55 if label=='csrfmiddlewaretoken':
58 if label=='csrfmiddlewaretoken':
56 continue
59 continue
57
60
58 if label.count('|')==0:
61 if label.count('|')==0:
59 if label in ('mode', 'multiplier', 'divisor', 'reference', 'frequency'):
62 if label in ('mode', 'multiplier', 'divisor', 'reference', 'frequency'):
60 clock_data[label] = value
63 clock_data[label] = value
61 else:
64 else:
62 conf_data[label] = value
65 conf_data[label] = value
63 continue
66 continue
64
67
65 elif label.split('|')[0]!='-1':
68 elif label.split('|')[0]!='-1':
66 extras.append(label)
69 extras.append(label)
67 continue
70 continue
68
71
69 #print(label)
72 #print(label)
70 x, pk, name = label.split('|')
73 x, pk, name = label.split('|')
71
74
72 if name=='codes':
75 if name=='codes':
73 value = [s for s in value.split('\r\n') if s]
76 value = [s for s in value.split('\r\n') if s]
74
77
75 if pk in line_data:
78 if pk in line_data:
76 line_data[pk][name] = value
79 line_data[pk][name] = value
77 else:
80 else:
78 line_data[pk] = {name:value}
81 line_data[pk] = {name:value}
79 #print(line_data[pk])
82 #print(line_data[pk])
80 #update conf
83 #update conf
81
84
82 form = GeneratorConfigurationForm(conf_data, instance=conf)
85 form = GeneratorConfigurationForm(conf_data, instance=conf)
83
86
84 #print(request.POST.items())
87 #print(request.POST.items())
85
88
86 if form.is_valid():
89 if form.is_valid():
87 form.save()
90 form.save()
88
91
89 messages.success(request, 'Generator configuration successfully updated')
92 messages.success(request, 'Generator configuration successfully updated')
90
93
91 return redirect(conf.get_absolute_url())
94 return redirect(conf.get_absolute_url())
92
95
93 kwargs = {}
96 kwargs = {}
94 kwargs['dev_conf'] = conf
97 kwargs['dev_conf'] = conf
95 kwargs['form'] = form
98 kwargs['form'] = form
96 kwargs['edit'] = True
99 kwargs['edit'] = True
97
100
98 kwargs['title'] = 'Generator Configuration'
101 kwargs['title'] = 'Generator Configuration'
99 kwargs['suptitle'] = 'Edit'
102 kwargs['suptitle'] = 'Edit'
100 kwargs['button'] = 'Update'
103 kwargs['button'] = 'Update'
101
104
102 print(kwargs)
105 print(kwargs)
103 print(form)
106 print(form)
104 return render(request, 'generator_conf_edit.html', kwargs)
107 return render(request, 'generator_conf_edit.html', kwargs)
105
108
106 def import_file(request, conf_id):
109 def import_file(request, conf_id):
107
110
108 conf = get_object_or_404(GeneratorConfiguration, pk=conf_id)
111 conf = get_object_or_404(GeneratorConfiguration, pk=conf_id)
109 if request.method=='POST':
112 if request.method=='POST':
110 form = GeneratorImportForm(request.POST, request.FILES)
113 form = GeneratorImportForm(request.POST, request.FILES)
111 if form.is_valid():
114 if form.is_valid():
112 try:
115 try:
113 data = conf.import_from_file(request.FILES['file_name'])
116 data = conf.import_from_file(request.FILES['file_name'])
114 conf.dict_to_parms(data)
117 conf.dict_to_parms(data)
115 messages.success(request, 'Configuration "%s" loaded succesfully' % request.FILES['file_name'])
118 messages.success(request, 'Configuration "%s" loaded succesfully' % request.FILES['file_name'])
116 return redirect(conf.get_absolute_url_edit())
119 return redirect(conf.get_absolute_url_edit())
117
120
118 except Exception as e:
121 except Exception as e:
119 messages.error(request, 'Error parsing file: "%s" - %s' % (request.FILES['file_name'], repr(e)))
122 messages.error(request, 'Error parsing file: "%s" - %s' % (request.FILES['file_name'], repr(e)))
120 else:
123 else:
121 messages.warning(request, 'Your current configuration will be replaced')
124 messages.warning(request, 'Your current configuration will be replaced')
122 form = GeneratorImportForm()
125 form = GeneratorImportForm()
123
126
124 kwargs = {}
127 kwargs = {}
125 kwargs['form'] = form
128 kwargs['form'] = form
126 kwargs['title'] = 'Generator Configuration'
129 kwargs['title'] = 'Generator Configuration'
127 kwargs['suptitle'] = 'Import file'
130 kwargs['suptitle'] = 'Import file'
128 kwargs['button'] = 'Upload'
131 kwargs['button'] = 'Upload'
129 kwargs['previous'] = conf.get_absolute_url()
132 kwargs['previous'] = conf.get_absolute_url()
130
133
131 return render(request, 'generator_import.html', kwargs)
134 return render(request, 'generator_import.html', kwargs)
132
135
133 def conf_raw(request, conf_id):
136 def conf_raw(request, conf_id):
134 conf = get_object_or_404(GeneratorConfiguration, pk=conf_id)
137 conf = get_object_or_404(GeneratorConfiguration, pk=conf_id)
135 raw = conf.write_device(raw=True)
138 raw = conf.write_device(raw=True)
136 return HttpResponse(raw, content_type='application/json') No newline at end of file
139 return HttpResponse(raw, content_type='application/json')
@@ -1,731 +1,733
1
1
2 import os
2 import os
3 import json
3 import json
4 import requests
4 import requests
5 import time
5 import time
6 from datetime import datetime
6 from datetime import datetime
7
7
8 try:
8 try:
9 from polymorphic.models import PolymorphicModel
9 from polymorphic.models import PolymorphicModel
10 except:
10 except:
11 from polymorphic import PolymorphicModel
11 from polymorphic import PolymorphicModel
12
12
13 from django.template.base import kwarg_re
13 from django.template.base import kwarg_re
14 from django.db import models
14 from django.db import models
15 from django.urls import reverse
15 from django.urls import reverse
16 from django.core.validators import MinValueValidator, MaxValueValidator
16 from django.core.validators import MinValueValidator, MaxValueValidator
17 from django.shortcuts import get_object_or_404
17 from django.shortcuts import get_object_or_404
18 from django.contrib.auth.models import User
18 from django.contrib.auth.models import User
19 from django.db.models.signals import post_save
19 from django.db.models.signals import post_save
20 from django.dispatch import receiver
20 from django.dispatch import receiver
21
21
22 from apps.main.utils import Params
22 from apps.main.utils import Params
23
23
24
24
25 DEV_PORTS = {
25 DEV_PORTS = {
26 'pedestal' : 80,
26 'pedestal' : 80,
27 'generator' : 80,
27 'pedestal_dev' : 80,
28 'usrp_rx' : 2000,
28 'generator' : 80,
29 'usrp_tx' : 2000,
29 'usrp_rx' : 2000,
30 'usrp_tx' : 2000,
30 }
31 }
31
32
32 RADAR_STATES = (
33 RADAR_STATES = (
33 (0, 'No connected'),
34 (0, 'No connected'),
34 (1, 'Connected'),
35 (1, 'Connected'),
35 (2, 'Configured'),
36 (2, 'Configured'),
36 (3, 'Running'),
37 (3, 'Running'),
37 (4, 'Scheduled'),
38 (4, 'Scheduled'),
38 )
39 )
39
40
40 EXPERIMENT_TYPE = (
41 EXPERIMENT_TYPE = (
41 (0, 'RAW_DATA'),
42 (0, 'RAW_DATA'),
42 (1, 'PDATA'),
43 (1, 'PDATA'),
43 )
44 )
44
45
45 DECODE_TYPE = (
46 DECODE_TYPE = (
46 (0, 'None'),
47 (0, 'None'),
47 (1, 'TimeDomain'),
48 (1, 'TimeDomain'),
48 (2, 'FreqDomain'),
49 (2, 'FreqDomain'),
49 (3, 'InvFreqDomain'),
50 (3, 'InvFreqDomain'),
50 )
51 )
51
52
52 DEV_STATES = (
53 DEV_STATES = (
53 (0, 'No connected'),
54 (0, 'Unknown'),
54 (1, 'Connected'),
55 (1, 'Connected'),
55 (2, 'Configured'),
56 (2, 'Configured'),
56 (3, 'Running'),
57 (3, 'Running'),
57 (4, 'Unknown'),
58 (4, 'Offline'),
58 )
59 )
59
60
60 DEV_TYPES = (
61 DEV_TYPES = (
61 ('', 'Select a device type'),
62 ('', 'Select a device type'),
62 ('pedestal', 'Pedestal Controller'),
63 ('pedestal', 'Pedestal Controller'),
64 ('pedestal_dev', 'Pedestal Controller Dev Mode'),
63 ('generator', 'Pulse Generator'),
65 ('generator', 'Pulse Generator'),
64 ('usrp_rx', 'Universal Software Radio Peripheral Rx'),
66 ('usrp_rx', 'Universal Software Radio Peripheral Rx'),
65 ('usrp_tx', 'Universal Software Radio Peripheral Tx'),
67 ('usrp_tx', 'Universal Software Radio Peripheral Tx'),
66 )
68 )
67
69
68 EXP_STATES = (
70 EXP_STATES = (
69 (0,'Error'), #RED
71 (0,'Error'), #RED
70 (1,'Cancelled'), #YELLOW
72 (1,'Cancelled'), #YELLOW
71 (2,'Running'), #GREEN
73 (2,'Running'), #GREEN
72 (3,'Scheduled'), #BLUE
74 (3,'Scheduled'), #BLUE
73 (4,'Unknown'), #WHITE
75 (4,'Unknown'), #WHITE
74 )
76 )
75
77
76 CONF_TYPES = (
78 CONF_TYPES = (
77 (0, 'Active'),
79 (0, 'Active'),
78 (1, 'Historical'),
80 (1, 'Historical'),
79 )
81 )
80
82
81 class Profile(models.Model):
83 class Profile(models.Model):
82 user = models.OneToOneField(User, on_delete=models.CASCADE)
84 user = models.OneToOneField(User, on_delete=models.CASCADE)
83 theme = models.CharField(max_length=30, default='spacelab')
85 theme = models.CharField(max_length=30, default='spacelab')
84
86
85
87
86 @receiver(post_save, sender=User)
88 @receiver(post_save, sender=User)
87 def create_user_profile(sender, instance, created, **kwargs):
89 def create_user_profile(sender, instance, created, **kwargs):
88 if created:
90 if created:
89 Profile.objects.create(user=instance)
91 Profile.objects.create(user=instance)
90
92
91 @receiver(post_save, sender=User)
93 @receiver(post_save, sender=User)
92 def save_user_profile(sender, instance, **kwargs):
94 def save_user_profile(sender, instance, **kwargs):
93 instance.profile.save()
95 instance.profile.save()
94
96
95
97
96 class Location(models.Model):
98 class Location(models.Model):
97
99
98 name = models.CharField(max_length = 30)
100 name = models.CharField(max_length = 30)
99 description = models.TextField(blank=True, null=True)
101 description = models.TextField(blank=True, null=True)
100
102
101 class Meta:
103 class Meta:
102 db_table = 'db_location'
104 db_table = 'db_location'
103
105
104 def __str__(self):
106 def __str__(self):
105 return u'%s' % self.name
107 return u'%s' % self.name
106
108
107 def get_absolute_url(self):
109 def get_absolute_url(self):
108 return reverse('url_location', args=[str(self.id)])
110 return reverse('url_location', args=[str(self.id)])
109
111
110
112
111 class DeviceType(models.Model):
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 sequence = models.PositiveSmallIntegerField(default=55)
116 sequence = models.PositiveSmallIntegerField(default=55)
115 description = models.TextField(blank=True, null=True)
117 description = models.TextField(blank=True, null=True)
116
118
117 class Meta:
119 class Meta:
118 db_table = 'db_device_types'
120 db_table = 'db_device_types'
119
121
120 def __str__(self):
122 def __str__(self):
121 return u'%s' % self.name.title()
123 return u'%s' % self.name.title()
122
124
123 class Device(models.Model):
125 class Device(models.Model):
124
126
125 device_type = models.ForeignKey('DeviceType', on_delete=models.CASCADE)
127 device_type = models.ForeignKey('DeviceType', on_delete=models.CASCADE)
126 location = models.ForeignKey('Location', on_delete=models.CASCADE)
128 location = models.ForeignKey('Location', on_delete=models.CASCADE)
127 ip_address = models.GenericIPAddressField(verbose_name = 'IP address', protocol='IPv4', default='0.0.0.0')
129 ip_address = models.GenericIPAddressField(verbose_name = 'IP address', protocol='IPv4', default='0.0.0.0')
128 port_address = models.PositiveSmallIntegerField(default=2000)
130 port_address = models.PositiveSmallIntegerField(default=2000)
129 description = models.TextField(blank=True, null=True)
131 description = models.TextField(blank=True, null=True)
130 status = models.PositiveSmallIntegerField(default=4, choices=DEV_STATES)
132 status = models.PositiveSmallIntegerField(default=4, choices=DEV_STATES)
131 conf_active = models.PositiveIntegerField(default=0, verbose_name='Current configuration')
133 conf_active = models.PositiveIntegerField(default=0, verbose_name='Current configuration')
132
134
133 class Meta:
135 class Meta:
134 db_table = 'db_devices'
136 db_table = 'db_devices'
135
137
136 def __str__(self):
138 def __str__(self):
137 ret = self.device_type
139 ret = self.device_type
138 #ret = u'{} [{}]'.format(self.device_type.name.upper(), self.location.name)
140 #ret = u'{} [{}]'.format(self.device_type.name.upper(), self.location.name)
139 return str(ret)
141 return str(ret)
140
142
141 @property
143 @property
142 def name(self):
144 def name(self):
143 return str(self)
145 return str(self)
144
146
145 def get_status(self):
147 def get_status(self):
146 return self.status
148 return self.status
147
149
148 @property
150 @property
149 def status_color(self):
151 def status_color(self):
150 color = 'muted'
152 color = 'muted'
151 if self.status == 0:
153 if self.status == 0:
152 color = "danger"
154 color = "danger"
153 elif self.status == 1:
155 elif self.status == 1:
154 color = "warning"
156 color = "warning"
155 elif self.status == 2:
157 elif self.status == 2:
156 color = "info"
158 color = "info"
157 elif self.status == 3:
159 elif self.status == 3:
158 color = "success"
160 color = "success"
159
161
160 return color
162 return color
161
163
162 def url(self, path=None):
164 def url(self, path=None):
163
165
164 if path:
166 if path:
165 return 'http://{}:{}/{}/'.format(self.ip_address, self.port_address, path)
167 return 'http://{}:{}/{}/'.format(self.ip_address, self.port_address, path)
166 else:
168 else:
167 return 'http://{}:{}/'.format(self.ip_address, self.port_address)
169 return 'http://{}:{}/'.format(self.ip_address, self.port_address)
168
170
169 def get_absolute_url(self):
171 def get_absolute_url(self):
170 return reverse('url_device', args=[str(self.id)])
172 return reverse('url_device', args=[str(self.id)])
171
173
172 def get_absolute_url_edit(self):
174 def get_absolute_url_edit(self):
173 return reverse('url_edit_device', args=[str(self.id)])
175 return reverse('url_edit_device', args=[str(self.id)])
174
176
175 def get_absolute_url_delete(self):
177 def get_absolute_url_delete(self):
176 return reverse('url_delete_device', args=[str(self.id)])
178 return reverse('url_delete_device', args=[str(self.id)])
177
179
178 def change_ip(self, ip_address, mask, gateway, dns, **kwargs):
180 def change_ip(self, ip_address, mask, gateway, dns, **kwargs):
179
181
180 if self.device_type.name=='pedestal':
182 if self.device_type.name=='pedestal':
181 headers = {'content-type': "application/json",
183 headers = {'content-type': "application/json",
182 'cache-control': "no-cache"}
184 'cache-control': "no-cache"}
183
185
184 ip = [int(x) for x in ip_address.split('.')]
186 ip = [int(x) for x in ip_address.split('.')]
185 dns = [int(x) for x in dns.split('.')]
187 dns = [int(x) for x in dns.split('.')]
186 gateway = [int(x) for x in gateway.split('.')]
188 gateway = [int(x) for x in gateway.split('.')]
187 subnet = [int(x) for x in mask.split('.')]
189 subnet = [int(x) for x in mask.split('.')]
188
190
189 payload = {
191 payload = {
190 "ip": ip,
192 "ip": ip,
191 "dns": dns,
193 "dns": dns,
192 "gateway": gateway,
194 "gateway": gateway,
193 "subnet": subnet
195 "subnet": subnet
194 }
196 }
195
197
196 req = requests.post(self.url('changeip'), data=json.dumps(payload), headers=headers)
198 req = requests.post(self.url('changeip'), data=json.dumps(payload), headers=headers)
197 try:
199 try:
198 answer = req.json()
200 answer = req.json()
199 if answer['changeip']=='ok':
201 if answer['changeip']=='ok':
200 self.message = '25|IP succesfully changed'
202 self.message = '25|IP succesfully changed'
201 self.ip_address = ip_address
203 self.ip_address = ip_address
202 self.save()
204 self.save()
203 else:
205 else:
204 self.message = '30|An error ocuur when changing IP'
206 self.message = '30|An error ocuur when changing IP'
205 except Exception as e:
207 except Exception as e:
206 self.message = '40|{}'.format(str(e))
208 self.message = '40|{}'.format(str(e))
207 else:
209 else:
208 self.message = 'Not implemented'
210 self.message = 'Not implemented'
209 return False
211 return False
210
212
211 return True
213 return True
212
214
213
215
214 class Campaign(models.Model):
216 class Campaign(models.Model):
215
217
216 template = models.BooleanField(default=False)
218 template = models.BooleanField(default=False)
217 name = models.CharField(max_length=60, unique=True)
219 name = models.CharField(max_length=60, unique=True)
218 start_date = models.DateTimeField(blank=True, null=True)
220 start_date = models.DateTimeField(blank=True, null=True)
219 end_date = models.DateTimeField(blank=True, null=True)
221 end_date = models.DateTimeField(blank=True, null=True)
220 tags = models.CharField(max_length=40, blank=True, null=True)
222 tags = models.CharField(max_length=40, blank=True, null=True)
221 description = models.TextField(blank=True, null=True)
223 description = models.TextField(blank=True, null=True)
222 experiments = models.ManyToManyField('Experiment', blank=True)
224 experiments = models.ManyToManyField('Experiment', blank=True)
223 author = models.ForeignKey(User, null=True, blank=True,on_delete=models.CASCADE)
225 author = models.ForeignKey(User, null=True, blank=True,on_delete=models.CASCADE)
224
226
225 class Meta:
227 class Meta:
226 db_table = 'db_campaigns'
228 db_table = 'db_campaigns'
227 ordering = ('name',)
229 ordering = ('name',)
228
230
229 def __str__(self):
231 def __str__(self):
230 if self.template:
232 if self.template:
231 return u'{} (template)'.format(self.name)
233 return u'{} (template)'.format(self.name)
232 else:
234 else:
233 return u'{}'.format(self.name)
235 return u'{}'.format(self.name)
234
236
235 def jsonify(self):
237 def jsonify(self):
236
238
237 data = {}
239 data = {}
238
240
239 ignored = ('template')
241 ignored = ('template')
240
242
241 for field in self._meta.fields:
243 for field in self._meta.fields:
242 if field.name in ignored:
244 if field.name in ignored:
243 continue
245 continue
244 data[field.name] = field.value_from_object(self)
246 data[field.name] = field.value_from_object(self)
245
247
246 data['start_date'] = data['start_date'].strftime('%Y-%m-%d')
248 data['start_date'] = data['start_date'].strftime('%Y-%m-%d')
247 data['end_date'] = data['end_date'].strftime('%Y-%m-%d')
249 data['end_date'] = data['end_date'].strftime('%Y-%m-%d')
248
250
249 return data
251 return data
250
252
251 def parms_to_dict(self):
253 def parms_to_dict(self):
252
254
253 params = Params({})
255 params = Params({})
254 params.add(self.jsonify(), 'campaigns')
256 params.add(self.jsonify(), 'campaigns')
255
257
256 for exp in Experiment.objects.filter(campaign = self):
258 for exp in Experiment.objects.filter(campaign = self):
257 params.add(exp.jsonify(), 'experiments')
259 params.add(exp.jsonify(), 'experiments')
258 configurations = Configuration.objects.filter(experiment=exp, type=0)
260 configurations = Configuration.objects.filter(experiment=exp, type=0)
259
261
260 for conf in configurations:
262 for conf in configurations:
261 params.add(conf.jsonify(), 'configurations')
263 params.add(conf.jsonify(), 'configurations')
262
264
263 return params.data
265 return params.data
264
266
265 def dict_to_parms(self, parms, CONF_MODELS):
267 def dict_to_parms(self, parms, CONF_MODELS):
266
268
267 experiments = Experiment.objects.filter(campaign = self)
269 experiments = Experiment.objects.filter(campaign = self)
268
270
269 if experiments:
271 if experiments:
270 for experiment in experiments:
272 for experiment in experiments:
271 experiment.delete()
273 experiment.delete()
272
274
273 for id_exp in parms['experiments']['allIds']:
275 for id_exp in parms['experiments']['allIds']:
274 exp_parms = parms['experiments']['byId'][id_exp]
276 exp_parms = parms['experiments']['byId'][id_exp]
275 dum = (datetime.now() - datetime(1970, 1, 1)).total_seconds()
277 dum = (datetime.now() - datetime(1970, 1, 1)).total_seconds()
276 exp = Experiment(name='{}'.format(dum))
278 exp = Experiment(name='{}'.format(dum))
277 exp.save()
279 exp.save()
278 exp.dict_to_parms(parms, CONF_MODELS, id_exp=id_exp)
280 exp.dict_to_parms(parms, CONF_MODELS, id_exp=id_exp)
279 self.experiments.add(exp)
281 self.experiments.add(exp)
280
282
281 camp_parms = parms['campaigns']['byId'][parms['campaigns']['allIds'][0]]
283 camp_parms = parms['campaigns']['byId'][parms['campaigns']['allIds'][0]]
282
284
283 self.name = '{}-{}'.format(camp_parms['name'], datetime.now().strftime('%y%m%d'))
285 self.name = '{}-{}'.format(camp_parms['name'], datetime.now().strftime('%y%m%d'))
284 self.start_date = camp_parms['start_date']
286 self.start_date = camp_parms['start_date']
285 self.end_date = camp_parms['end_date']
287 self.end_date = camp_parms['end_date']
286 self.tags = camp_parms['tags']
288 self.tags = camp_parms['tags']
287 self.save()
289 self.save()
288
290
289 return self
291 return self
290
292
291 def get_experiments_by_radar(self, radar=None):
293 def get_experiments_by_radar(self, radar=None):
292
294
293 ret = []
295 ret = []
294 if radar:
296 if radar:
295 locations = Location.objects.filter(pk=radar)
297 locations = Location.objects.filter(pk=radar)
296 else:
298 else:
297 locations = set([e.location for e in self.experiments.all()])
299 locations = set([e.location for e in self.experiments.all()])
298
300
299 for loc in locations:
301 for loc in locations:
300 dum = {}
302 dum = {}
301 dum['name'] = loc.name
303 dum['name'] = loc.name
302 dum['id'] = loc.pk
304 dum['id'] = loc.pk
303 dum['experiments'] = [e for e in self.experiments.all() if e.location==loc]
305 dum['experiments'] = [e for e in self.experiments.all() if e.location==loc]
304 ret.append(dum)
306 ret.append(dum)
305
307
306 return ret
308 return ret
307
309
308 def get_absolute_url(self):
310 def get_absolute_url(self):
309 return reverse('url_campaign', args=[str(self.id)])
311 return reverse('url_campaign', args=[str(self.id)])
310
312
311 def get_absolute_url_edit(self):
313 def get_absolute_url_edit(self):
312 return reverse('url_edit_campaign', args=[str(self.id)])
314 return reverse('url_edit_campaign', args=[str(self.id)])
313
315
314 def get_absolute_url_delete(self):
316 def get_absolute_url_delete(self):
315 return reverse('url_delete_campaign', args=[str(self.id)])
317 return reverse('url_delete_campaign', args=[str(self.id)])
316
318
317 def get_absolute_url_export(self):
319 def get_absolute_url_export(self):
318 return reverse('url_export_campaign', args=[str(self.id)])
320 return reverse('url_export_campaign', args=[str(self.id)])
319
321
320 def get_absolute_url_import(self):
322 def get_absolute_url_import(self):
321 return reverse('url_import_campaign', args=[str(self.id)])
323 return reverse('url_import_campaign', args=[str(self.id)])
322
324
323
325
324 class RunningExperiment(models.Model):
326 class RunningExperiment(models.Model):
325 radar = models.OneToOneField('Location', on_delete=models.CASCADE)
327 radar = models.OneToOneField('Location', on_delete=models.CASCADE)
326 running_experiment = models.ManyToManyField('Experiment', blank = True)
328 running_experiment = models.ManyToManyField('Experiment', blank = True)
327 status = models.PositiveSmallIntegerField(default=0, choices=RADAR_STATES)
329 status = models.PositiveSmallIntegerField(default=0, choices=RADAR_STATES)
328
330
329
331
330 class Experiment(models.Model):
332 class Experiment(models.Model):
331
333
332 template = models.BooleanField(default=False)
334 template = models.BooleanField(default=False)
333 name = models.CharField(max_length=40, default='', unique=True)
335 name = models.CharField(max_length=40, default='', unique=True)
334 location = models.ForeignKey('Location', null=False, blank=False, on_delete=models.CASCADE, default='')
336 location = models.ForeignKey('Location', null=False, blank=False, on_delete=models.CASCADE, default='')
335 #freq = models.FloatField(verbose_name='Operating Freq. (MHz)', validators=[MinValueValidator(1), MaxValueValidator(10000)], default=49.9200)
337 #freq = models.FloatField(verbose_name='Operating Freq. (MHz)', validators=[MinValueValidator(1), MaxValueValidator(10000)], default=49.9200)
336 start_time = models.TimeField(default='00:00:00')
338 start_time = models.TimeField(default='00:00:00')
337 end_time = models.TimeField(default='23:59:59')
339 end_time = models.TimeField(default='23:59:59')
338 task = models.CharField(max_length=36, default='', blank=True, null=True)
340 task = models.CharField(max_length=36, default='', blank=True, null=True)
339 status = models.PositiveSmallIntegerField(default=4, choices=EXP_STATES)
341 status = models.PositiveSmallIntegerField(default=4, choices=EXP_STATES)
340 author = models.ForeignKey(User, null=True, blank=True,on_delete=models.CASCADE)
342 author = models.ForeignKey(User, null=True, blank=True,on_delete=models.CASCADE)
341 hash = models.CharField(default='', max_length=64, null=True, blank=True)
343 hash = models.CharField(default='', max_length=64, null=True, blank=True)
342
344
343 class Meta:
345 class Meta:
344 db_table = 'db_experiments'
346 db_table = 'db_experiments'
345 ordering = ('template', 'name')
347 ordering = ('template', 'name')
346
348
347 def __str__(self):
349 def __str__(self):
348 if self.template:
350 if self.template:
349 return u'%s (template)' % (self.name)
351 return u'%s (template)' % (self.name)
350 else:
352 else:
351 return u'%s' % (self.name)
353 return u'%s' % (self.name)
352
354
353 def jsonify(self):
355 def jsonify(self):
354
356
355 data = {}
357 data = {}
356
358
357 ignored = ('template')
359 ignored = ('template')
358
360
359 for field in self._meta.fields:
361 for field in self._meta.fields:
360 if field.name in ignored:
362 if field.name in ignored:
361 continue
363 continue
362 data[field.name] = field.value_from_object(self)
364 data[field.name] = field.value_from_object(self)
363
365
364 data['start_time'] = data['start_time'].strftime('%H:%M:%S')
366 data['start_time'] = data['start_time'].strftime('%H:%M:%S')
365 data['end_time'] = data['end_time'].strftime('%H:%M:%S')
367 data['end_time'] = data['end_time'].strftime('%H:%M:%S')
366 data['location'] = self.location.name
368 data['location'] = self.location.name
367 data['configurations'] = ['{}'.format(conf.pk) for
369 data['configurations'] = ['{}'.format(conf.pk) for
368 conf in Configuration.objects.filter(experiment=self, type=0)]
370 conf in Configuration.objects.filter(experiment=self, type=0)]
369
371
370 return data
372 return data
371
373
372 @property
374 @property
373 def radar_system(self):
375 def radar_system(self):
374 return self.location
376 return self.location
375
377
376 def clone(self, **kwargs):
378 def clone(self, **kwargs):
377
379
378 confs = Configuration.objects.filter(experiment=self, type=0)
380 confs = Configuration.objects.filter(experiment=self, type=0)
379 self.pk = None
381 self.pk = None
380 self.name = '{}_{:%y%m%d}'.format(self.name, datetime.now())
382 self.name = '{}_{:%y%m%d}'.format(self.name, datetime.now())
381 for attr, value in kwargs.items():
383 for attr, value in kwargs.items():
382 setattr(self, attr, value)
384 setattr(self, attr, value)
383
385
384 self.save()
386 self.save()
385
387
386 for conf in confs:
388 for conf in confs:
387 conf.clone(experiment=self, template=False)
389 conf.clone(experiment=self, template=False)
388
390
389 return self
391 return self
390
392
391 def start(self):
393 def start(self):
392 '''
394 '''
393 Configure and start experiments's devices
395 Configure and start experiments's devices
394 '''
396 '''
395
397
396 confs = []
398 confs = []
397 allconfs = Configuration.objects.filter(experiment=self, type = 0).order_by('-device__device_type__sequence')
399 allconfs = Configuration.objects.filter(experiment=self, type = 0).order_by('-device__device_type__sequence')
398 confs = allconfs
400 confs = allconfs
399
401
400 try:
402 try:
401 for conf in confs:
403 for conf in confs:
402 conf.stop_device()
404 conf.stop_device()
403 print("OK")
405 print("OK")
404 #conf.write_device()
406 #conf.write_device()
405 conf.device.conf_active = conf.pk
407 conf.device.conf_active = conf.pk
406 conf.device.save()
408 conf.device.save()
407 conf.start_device()
409 conf.start_device()
408 print("OK")
410 print("OK")
409 time.sleep(1)
411 time.sleep(1)
410 except:
412 except:
411 return 0
413 return 0
412 return 2
414 return 2
413
415
414
416
415 def stop(self):
417 def stop(self):
416 '''
418 '''
417 Stop experiments's devices
419 Stop experiments's devices
418 PEDESTAL, PULSE GENERATOR & USRP's
420 PEDESTAL, PULSE GENERATOR & USRP's
419 '''
421 '''
420
422
421 confs = Configuration.objects.filter(experiment=self, type = 0).order_by('device__device_type__sequence')
423 confs = Configuration.objects.filter(experiment=self, type = 0).order_by('device__device_type__sequence')
422 try:
424 try:
423 for conf in confs:
425 for conf in confs:
424 conf.stop_device()
426 conf.stop_device()
425 except:
427 except:
426 return 0
428 return 0
427 return 1
429 return 1
428
430
429 def get_status(self):
431 def get_status(self):
430
432
431 if self.status == 3:
433 if self.status == 3:
432 return
434 return
433
435
434 confs = Configuration.objects.filter(experiment=self, type=0)
436 confs = Configuration.objects.filter(experiment=self, type=0)
435
437
436 for conf in confs:
438 for conf in confs:
437 conf.status_device()
439 conf.status_device()
438
440
439 total = confs.aggregate(models.Sum('device__status'))['device__status__sum']
441 total = confs.aggregate(models.Sum('device__status'))['device__status__sum']
440
442
441 if total==2*confs.count():
443 if total==2*confs.count():
442 status = 1
444 status = 1
443 elif total == 3*confs.count():
445 elif total == 3*confs.count():
444 status = 2
446 status = 2
445 else:
447 else:
446 status = 0
448 status = 0
447
449
448 self.status = status
450 self.status = status
449 self.save()
451 self.save()
450
452
451 def status_color(self):
453 def status_color(self):
452 color = 'muted'
454 color = 'muted'
453 if self.status == 0:
455 if self.status == 0:
454 color = "danger"
456 color = "danger"
455 elif self.status == 1:
457 elif self.status == 1:
456 color = "warning"
458 color = "warning"
457 elif self.status == 2:
459 elif self.status == 2:
458 color = "success"
460 color = "success"
459 elif self.status == 3:
461 elif self.status == 3:
460 color = "info"
462 color = "info"
461
463
462 return color
464 return color
463
465
464 def parms_to_dict(self):
466 def parms_to_dict(self):
465
467
466 params = Params({})
468 params = Params({})
467 params.add(self.jsonify(), 'experiments')
469 params.add(self.jsonify(), 'experiments')
468
470
469 configurations = Configuration.objects.filter(experiment=self, type=0)
471 configurations = Configuration.objects.filter(experiment=self, type=0)
470
472
471 for conf in configurations:
473 for conf in configurations:
472 params.add(conf.jsonify(), 'configurations')
474 params.add(conf.jsonify(), 'configurations')
473
475
474 return params.data
476 return params.data
475
477
476 def dict_to_parms(self, parms, CONF_MODELS, id_exp=None):
478 def dict_to_parms(self, parms, CONF_MODELS, id_exp=None):
477
479
478 configurations = Configuration.objects.filter(experiment=self)
480 configurations = Configuration.objects.filter(experiment=self)
479
481
480 if id_exp is not None:
482 if id_exp is not None:
481 exp_parms = parms['experiments']['byId'][id_exp]
483 exp_parms = parms['experiments']['byId'][id_exp]
482 else:
484 else:
483 exp_parms = parms['experiments']['byId'][parms['experiments']['allIds'][0]]
485 exp_parms = parms['experiments']['byId'][parms['experiments']['allIds'][0]]
484
486
485 if configurations:
487 if configurations:
486 for configuration in configurations:
488 for configuration in configurations:
487 configuration.delete()
489 configuration.delete()
488
490
489 for id_conf in exp_parms['configurations']:
491 for id_conf in exp_parms['configurations']:
490 conf_parms = parms['configurations']['byId'][id_conf]
492 conf_parms = parms['configurations']['byId'][id_conf]
491 device = Device.objects.filter(device_type__name=conf_parms['device_type'])[0]
493 device = Device.objects.filter(device_type__name=conf_parms['device_type'])[0]
492 model = CONF_MODELS[conf_parms['device_type']]
494 model = CONF_MODELS[conf_parms['device_type']]
493 conf = model(
495 conf = model(
494 experiment = self,
496 experiment = self,
495 device = device,
497 device = device,
496 )
498 )
497 conf.dict_to_parms(parms, id=id_conf)
499 conf.dict_to_parms(parms, id=id_conf)
498
500
499
501
500 location, created = Location.objects.get_or_create(name=exp_parms['location'])
502 location, created = Location.objects.get_or_create(name=exp_parms['location'])
501 self.name = '{}-{}'.format(exp_parms['name'], datetime.now().strftime('%y%m%d'))
503 self.name = '{}-{}'.format(exp_parms['name'], datetime.now().strftime('%y%m%d'))
502 self.location = location
504 self.location = location
503 self.start_time = exp_parms['start_time']
505 self.start_time = exp_parms['start_time']
504 self.end_time = exp_parms['end_time']
506 self.end_time = exp_parms['end_time']
505 self.save()
507 self.save()
506
508
507 return self
509 return self
508
510
509 def get_absolute_url(self):
511 def get_absolute_url(self):
510 return reverse('url_experiment', args=[str(self.id)])
512 return reverse('url_experiment', args=[str(self.id)])
511
513
512 def get_absolute_url_edit(self):
514 def get_absolute_url_edit(self):
513 return reverse('url_edit_experiment', args=[str(self.id)])
515 return reverse('url_edit_experiment', args=[str(self.id)])
514
516
515 def get_absolute_url_delete(self):
517 def get_absolute_url_delete(self):
516 return reverse('url_delete_experiment', args=[str(self.id)])
518 return reverse('url_delete_experiment', args=[str(self.id)])
517
519
518 def get_absolute_url_import(self):
520 def get_absolute_url_import(self):
519 return reverse('url_import_experiment', args=[str(self.id)])
521 return reverse('url_import_experiment', args=[str(self.id)])
520
522
521 def get_absolute_url_export(self):
523 def get_absolute_url_export(self):
522 return reverse('url_export_experiment', args=[str(self.id)])
524 return reverse('url_export_experiment', args=[str(self.id)])
523
525
524 def get_absolute_url_start(self):
526 def get_absolute_url_start(self):
525 return reverse('url_start_experiment', args=[str(self.id)])
527 return reverse('url_start_experiment', args=[str(self.id)])
526
528
527 def get_absolute_url_stop(self):
529 def get_absolute_url_stop(self):
528 return reverse('url_stop_experiment', args=[str(self.id)])
530 return reverse('url_stop_experiment', args=[str(self.id)])
529
531
530
532
531 class Configuration(PolymorphicModel):
533 class Configuration(PolymorphicModel):
532
534
533 id = models.AutoField(primary_key=True)
535 id = models.AutoField(primary_key=True)
534 template = models.BooleanField(default=False)
536 template = models.BooleanField(default=False)
535 # name = models.CharField(verbose_name="Configuration Name", max_length=40, default='')
537 # name = models.CharField(verbose_name="Configuration Name", max_length=40, default='')
536 device = models.ForeignKey('Device', verbose_name='Device', null=True, on_delete=models.CASCADE)
538 device = models.ForeignKey('Device', verbose_name='Device', null=True, on_delete=models.CASCADE)
537 label = models.CharField(verbose_name="Label", max_length=40, default='', blank=True, null=True)
539 label = models.CharField(verbose_name="Label", max_length=40, default='', blank=True, null=True)
538 experiment = models.ForeignKey('Experiment', verbose_name='Experiment', null=True, blank=True, on_delete=models.CASCADE)
540 experiment = models.ForeignKey('Experiment', verbose_name='Experiment', null=True, blank=True, on_delete=models.CASCADE)
539 type = models.PositiveSmallIntegerField(default=0, choices=CONF_TYPES)
541 type = models.PositiveSmallIntegerField(default=0, choices=CONF_TYPES)
540 created_date = models.DateTimeField(auto_now_add=True)
542 created_date = models.DateTimeField(auto_now_add=True)
541 programmed_date = models.DateTimeField(auto_now=True)
543 programmed_date = models.DateTimeField(auto_now=True)
542 parameters = models.TextField(default='{}')
544 parameters = models.TextField(default='{}')
543 author = models.ForeignKey(User, null=True, blank=True,on_delete=models.CASCADE)
545 author = models.ForeignKey(User, null=True, blank=True,on_delete=models.CASCADE)
544 hash = models.CharField(default='', max_length=64, null=True, blank=True)
546 hash = models.CharField(default='', max_length=64, null=True, blank=True)
545 message = ""
547 message = ""
546
548
547 class Meta:
549 class Meta:
548 db_table = 'db_configurations'
550 db_table = 'db_configurations'
549 ordering = ('device__device_type__name',)
551 ordering = ('device__device_type__name',)
550
552
551 def __str__(self):
553 def __str__(self):
552
554
553 ret = u'{} '.format(self.device.device_type.name.upper())
555 ret = u'{} '.format(self.device.device_type.name.upper())
554
556
555 if 'mix' in [f.name for f in self._meta.get_fields()]:
557 if 'mix' in [f.name for f in self._meta.get_fields()]:
556 if self.mix:
558 if self.mix:
557 ret = '{} MIX '.format(self.device.device_type.name.upper())
559 ret = '{} MIX '.format(self.device.device_type.name.upper())
558
560
559 if 'label' in [f.name for f in self._meta.get_fields()]:
561 if 'label' in [f.name for f in self._meta.get_fields()]:
560 ret += '{}'.format(self.label)
562 ret += '{}'.format(self.label)
561
563
562 if self.template:
564 if self.template:
563 ret += ' (template)'
565 ret += ' (template)'
564
566
565 return ret
567 return ret
566
568
567 @property
569 @property
568 def name(self):
570 def name(self):
569
571
570 return str(self)
572 return str(self)
571
573
572 def jsonify(self):
574 def jsonify(self):
573
575
574 data = {}
576 data = {}
575
577
576 ignored = ('type', 'polymorphic_ctype', 'configuration_ptr',
578 ignored = ('type', 'polymorphic_ctype', 'configuration_ptr',
577 'created_date', 'programmed_date', 'template', 'device',
579 'created_date', 'programmed_date', 'template', 'device',
578 'experiment')
580 'experiment')
579
581
580 for field in self._meta.fields:
582 for field in self._meta.fields:
581 if field.name in ignored:
583 if field.name in ignored:
582 continue
584 continue
583 data[field.name] = field.value_from_object(self)
585 data[field.name] = field.value_from_object(self)
584
586
585 data['device_type'] = self.device.device_type.name
587 data['device_type'] = self.device.device_type.name
586 return data
588 return data
587
589
588 def clone(self, **kwargs):
590 def clone(self, **kwargs):
589
591
590 self.pk = None
592 self.pk = None
591 self.id = None
593 self.id = None
592 for attr, value in kwargs.items():
594 for attr, value in kwargs.items():
593 setattr(self, attr, value)
595 setattr(self, attr, value)
594
596
595 self.save()
597 self.save()
596
598
597 return self
599 return self
598
600
599 def parms_to_dict(self):
601 def parms_to_dict(self):
600
602
601 params = Params({})
603 params = Params({})
602 params.add(self.jsonify(), 'configurations')
604 params.add(self.jsonify(), 'configurations')
603 return params.data
605 return params.data
604
606
605 def parms_to_text(self):
607 def parms_to_text(self):
606
608
607 raise NotImplementedError("This method should be implemented in %s Configuration model" %str(self.device.device_type.name).upper())
609 raise NotImplementedError("This method should be implemented in %s Configuration model" %str(self.device.device_type.name).upper())
608
610
609
611
610 def parms_to_binary(self):
612 def parms_to_binary(self):
611
613
612 raise NotImplementedError("This method should be implemented in %s Configuration model" %str(self.device.device_type.name).upper())
614 raise NotImplementedError("This method should be implemented in %s Configuration model" %str(self.device.device_type.name).upper())
613
615
614
616
615 def dict_to_parms(self, parameters, id=None):
617 def dict_to_parms(self, parameters, id=None):
616
618
617 params = Params(parameters)
619 params = Params(parameters)
618
620
619 if id:
621 if id:
620 data = params.get_conf(id_conf=id)
622 data = params.get_conf(id_conf=id)
621 else:
623 else:
622 data = params.get_conf(dtype=self.device.device_type.name)
624 data = params.get_conf(dtype=self.device.device_type.name)
623
625
624 for key, value in data.items():
626 for key, value in data.items():
625 if key not in ('id', 'device_type'):
627 if key not in ('id', 'device_type'):
626 setattr(self, key, value)
628 setattr(self, key, value)
627
629
628 self.save()
630 self.save()
629
631
630
632
631 def export_to_file(self, format="json"):
633 def export_to_file(self, format="json"):
632
634
633 content_type = ''
635 content_type = ''
634
636
635 if format == 'racp':
637 if format == 'racp':
636 content_type = 'text/plain'
638 content_type = 'text/plain'
637 filename = '%s_%s.%s' %(self.device.device_type.name, self.name, 'racp')
639 filename = '%s_%s.%s' %(self.device.device_type.name, self.name, 'racp')
638 content = self.parms_to_text(file_format = 'racp')
640 content = self.parms_to_text(file_format = 'racp')
639
641
640 if format == 'text':
642 if format == 'text':
641 content_type = 'text/plain'
643 content_type = 'text/plain'
642 filename = '%s_%s.%s' %(self.device.device_type.name, self.name, self.device.device_type.name)
644 filename = '%s_%s.%s' %(self.device.device_type.name, self.name, self.device.device_type.name)
643 content = self.parms_to_text()
645 content = self.parms_to_text()
644
646
645 if format == 'binary':
647 if format == 'binary':
646 content_type = 'application/octet-stream'
648 content_type = 'application/octet-stream'
647 filename = '%s_%s.bin' %(self.device.device_type.name, self.name)
649 filename = '%s_%s.bin' %(self.device.device_type.name, self.name)
648 content = self.parms_to_binary()
650 content = self.parms_to_binary()
649
651
650 if not content_type:
652 if not content_type:
651 content_type = 'application/json'
653 content_type = 'application/json'
652 filename = '%s_%s.json' %(self.device.device_type.name, self.name)
654 filename = '%s_%s.json' %(self.device.device_type.name, self.name)
653 content = json.dumps(self.parms_to_dict(), indent=2)
655 content = json.dumps(self.parms_to_dict(), indent=2)
654
656
655 fields = {'content_type':content_type,
657 fields = {'content_type':content_type,
656 'filename':filename,
658 'filename':filename,
657 'content':content
659 'content':content
658 }
660 }
659
661
660 return fields
662 return fields
661
663
662 def import_from_file(self, fp):
664 def import_from_file(self, fp):
663
665
664 parms = {}
666 parms = {}
665
667
666 path, ext = os.path.splitext(fp.name)
668 path, ext = os.path.splitext(fp.name)
667
669
668 if ext == '.json':
670 if ext == '.json':
669 parms = json.load(fp)
671 parms = json.load(fp)
670
672
671 return parms
673 return parms
672
674
673 def status_device(self):
675 def status_device(self):
674
676
675 self.message = 'Function not implemented'
677 self.message = 'Function not implemented'
676 return False
678 return False
677
679
678
680
679 def stop_device(self):
681 def stop_device(self):
680
682
681 self.message = 'Function not implemented'
683 self.message = 'Function not implemented'
682 return False
684 return False
683
685
684
686
685 def start_device(self):
687 def start_device(self):
686
688
687 self.message = 'Function not implemented'
689 self.message = 'Function not implemented'
688 return False
690 return False
689
691
690
692
691 def write_device(self):
693 def write_device(self):
692
694
693 self.message = 'Function not implemented'
695 self.message = 'Function not implemented'
694 return False
696 return False
695
697
696
698
697 def read_device(self):
699 def read_device(self):
698
700
699 self.message = 'Function not implemented'
701 self.message = 'Function not implemented'
700 return False
702 return False
701
703
702
704
703 def get_absolute_url(self):
705 def get_absolute_url(self):
704 return reverse('url_%s_conf' % self.device.device_type.name, args=[str(self.id)])
706 return reverse('url_%s_conf' % self.device.device_type.name, args=[str(self.id)])
705
707
706 def get_absolute_url_edit(self):
708 def get_absolute_url_edit(self):
707 return reverse('url_edit_%s_conf' % self.device.device_type.name, args=[str(self.id)])
709 return reverse('url_edit_%s_conf' % self.device.device_type.name, args=[str(self.id)])
708
710
709 def get_absolute_url_delete(self):
711 def get_absolute_url_delete(self):
710 return reverse('url_delete_dev_conf', args=[str(self.id)])
712 return reverse('url_delete_dev_conf', args=[str(self.id)])
711
713
712 def get_absolute_url_import(self):
714 def get_absolute_url_import(self):
713 return reverse('url_import_dev_conf', args=[str(self.id)])
715 return reverse('url_import_dev_conf', args=[str(self.id)])
714
716
715 def get_absolute_url_export(self):
717 def get_absolute_url_export(self):
716 return reverse('url_export_dev_conf', args=[str(self.id)])
718 return reverse('url_export_dev_conf', args=[str(self.id)])
717
719
718 def get_absolute_url_write(self):
720 def get_absolute_url_write(self):
719 return reverse('url_write_dev_conf', args=[str(self.id)])
721 return reverse('url_write_dev_conf', args=[str(self.id)])
720
722
721 def get_absolute_url_read(self):
723 def get_absolute_url_read(self):
722 return reverse('url_read_dev_conf', args=[str(self.id)])
724 return reverse('url_read_dev_conf', args=[str(self.id)])
723
725
724 def get_absolute_url_start(self):
726 def get_absolute_url_start(self):
725 return reverse('url_start_dev_conf', args=[str(self.id)])
727 return reverse('url_start_dev_conf', args=[str(self.id)])
726
728
727 def get_absolute_url_stop(self):
729 def get_absolute_url_stop(self):
728 return reverse('url_stop_dev_conf', args=[str(self.id)])
730 return reverse('url_stop_dev_conf', args=[str(self.id)])
729
731
730 def get_absolute_url_status(self):
732 def get_absolute_url_status(self):
731 return reverse('url_status_dev_conf', args=[str(self.id)])
733 return reverse('url_status_dev_conf', args=[str(self.id)])
@@ -1,1917 +1,1920
1 import ast
1 import ast
2 import json
2 import json
3 import hashlib
3 import hashlib
4 from datetime import datetime, timedelta
4 from datetime import datetime, timedelta
5
5
6 from django.shortcuts import render, redirect, get_object_or_404, HttpResponse
6 from django.shortcuts import render, redirect, get_object_or_404, HttpResponse
7 from django.utils.safestring import mark_safe
7 from django.utils.safestring import mark_safe
8 from django.http import HttpResponseRedirect
8 from django.http import HttpResponseRedirect
9 from django.urls import reverse
9 from django.urls import reverse
10 from django.db.models import Q
10 from django.db.models import Q
11 from django.core.paginator import Paginator, EmptyPage, PageNotAnInteger
11 from django.core.paginator import Paginator, EmptyPage, PageNotAnInteger
12 from django.contrib import messages
12 from django.contrib import messages
13 from django.http.request import QueryDict
13 from django.http.request import QueryDict
14 from django.contrib.auth.decorators import login_required, user_passes_test
14 from django.contrib.auth.decorators import login_required, user_passes_test
15
15
16 from django.utils.timezone import is_aware
16 from django.utils.timezone import is_aware
17
17
18 try:
18 try:
19 from urllib.parse import urlencode
19 from urllib.parse import urlencode
20 except ImportError:
20 except ImportError:
21 from urllib import urlencode
21 from urllib import urlencode
22
22
23 from .forms import CampaignForm, ExperimentForm, DeviceForm, ConfigurationForm, LocationForm, UploadFileForm, DownloadFileForm, OperationForm, NewForm
23 from .forms import CampaignForm, ExperimentForm, DeviceForm, ConfigurationForm, LocationForm, UploadFileForm, DownloadFileForm, OperationForm, NewForm
24 from .forms import OperationSearchForm, FilterForm, ChangeIpForm
24 from .forms import OperationSearchForm, FilterForm, ChangeIpForm
25
25
26 from apps.pedestal.forms import PedestalConfigurationForm
26 from apps.pedestal.forms import PedestalConfigurationForm
27 from apps.pedestal_dev.forms import PedestalDevConfigurationForm
27 from apps.generator.forms import GeneratorConfigurationForm
28 from apps.generator.forms import GeneratorConfigurationForm
28 from apps.usrp_rx.forms import USRPRXConfigurationForm
29 from apps.usrp_rx.forms import USRPRXConfigurationForm
29 from apps.usrp_tx.forms import USRPTXConfigurationForm
30 from apps.usrp_tx.forms import USRPTXConfigurationForm
30 from .utils import Params
31 from .utils import Params
31
32
32 from .models import Campaign, Experiment, Device, Configuration, Location, RunningExperiment, DEV_STATES
33 from .models import Campaign, Experiment, Device, Configuration, Location, RunningExperiment, DEV_STATES
33 from apps.pedestal.models import PedestalConfiguration
34 from apps.pedestal.models import PedestalConfiguration
35 from apps.pedestal_dev.models import PedestalDevConfiguration
34 from apps.generator.models import GeneratorConfiguration
36 from apps.generator.models import GeneratorConfiguration
35 from apps.usrp_rx.models import USRPRXConfiguration
37 from apps.usrp_rx.models import USRPRXConfiguration
36 from apps.usrp_tx.models import USRPTXConfiguration
38 from apps.usrp_tx.models import USRPTXConfiguration
37
39
38
40
39 #comentario test
41 #comentario test
40 CONF_FORMS = {
42 CONF_FORMS = {
41 'pedestal': PedestalConfigurationForm,
43 'pedestal': PedestalConfigurationForm,
44 'pedestal_dev': PedestalDevConfigurationForm,
42 'generator': GeneratorConfigurationForm,
45 'generator': GeneratorConfigurationForm,
43 'usrp_rx': USRPRXConfigurationForm,
46 'usrp_rx': USRPRXConfigurationForm,
44 'usrp_tx': USRPTXConfigurationForm,
47 'usrp_tx': USRPTXConfigurationForm,
45 }
48 }
46
49
47 CONF_MODELS = {
50 CONF_MODELS = {
48 'pedestal': PedestalConfiguration,
51 'pedestal': PedestalConfiguration,
52 'pedestal_dev': PedestalDevConfiguration,
49 'generator': GeneratorConfiguration,
53 'generator': GeneratorConfiguration,
50 'usrp_rx': USRPRXConfiguration,
54 'usrp_rx': USRPRXConfiguration,
51 'usrp_tx': USRPTXConfiguration,
55 'usrp_tx': USRPTXConfiguration,
52 }
56 }
53
57
54 MIX_MODES = {
58 MIX_MODES = {
55 '0': 'P',
59 '0': 'P',
56 '1': 'S',
60 '1': 'S',
57 }
61 }
58
62
59 MIX_OPERATIONS = {
63 MIX_OPERATIONS = {
60 '0': 'OR',
64 '0': 'OR',
61 '1': 'XOR',
65 '1': 'XOR',
62 '2': 'AND',
66 '2': 'AND',
63 '3': 'NAND',
67 '3': 'NAND',
64 }
68 }
65
69
66
70
67 def is_developer(user):
71 def is_developer(user):
68
72
69 groups = [str(g.name) for g in user.groups.all()]
73 groups = [str(g.name) for g in user.groups.all()]
70 return 'Developer' in groups or user.is_staff
74 return 'Developer' in groups or user.is_staff
71
75
72
76
73 def is_operator(user):
77 def is_operator(user):
74
78
75 groups = [str(g.name) for g in user.groups.all()]
79 groups = [str(g.name) for g in user.groups.all()]
76 return 'Operator' in groups or user.is_staff
80 return 'Operator' in groups or user.is_staff
77
81
78
82
79 def has_been_modified(model):
83 def has_been_modified(model):
80
84
81 prev_hash = model.hash
85 prev_hash = model.hash
82 new_hash = hashlib.sha256(str(model.parms_to_dict).encode()).hexdigest()
86 new_hash = hashlib.sha256(str(model.parms_to_dict).encode()).hexdigest()
83 if prev_hash != new_hash:
87 if prev_hash != new_hash:
84 model.hash = new_hash
88 model.hash = new_hash
85 model.save()
89 model.save()
86 return True
90 return True
87 return False
91 return False
88
92
89
93
90 def index(request):
94 def index(request):
91 kwargs = {'no_sidebar': True}
95 kwargs = {'no_sidebar': True}
92
96
93 return render(request, 'index.html', kwargs)
97 return render(request, 'index.html', kwargs)
94
98
95
99
96 def locations(request):
100 def locations(request):
97
101
98 page = request.GET.get('page')
102 page = request.GET.get('page')
99 order = ('name',)
103 order = ('name',)
100
104
101 kwargs = get_paginator(Location, page, order)
105 kwargs = get_paginator(Location, page, order)
102
106
103 kwargs['keys'] = ['name', 'description']
107 kwargs['keys'] = ['name', 'description']
104 kwargs['title'] = 'Radar System'
108 kwargs['title'] = 'Radar System'
105 kwargs['suptitle'] = 'List'
109 kwargs['suptitle'] = 'List'
106 kwargs['no_sidebar'] = True
110 kwargs['no_sidebar'] = True
107
111
108 return render(request, 'base_list.html', kwargs)
112 return render(request, 'base_list.html', kwargs)
109
113
110
114
111 def location(request, id_loc):
115 def location(request, id_loc):
112
116
113 location = get_object_or_404(Location, pk=id_loc)
117 location = get_object_or_404(Location, pk=id_loc)
114
118
115 kwargs = {}
119 kwargs = {}
116 kwargs['location'] = location
120 kwargs['location'] = location
117 kwargs['location_keys'] = ['name', 'description']
121 kwargs['location_keys'] = ['name', 'description']
118
122
119 kwargs['title'] = 'Location'
123 kwargs['title'] = 'Location'
120 kwargs['suptitle'] = 'Details'
124 kwargs['suptitle'] = 'Details'
121
125
122 return render(request, 'location.html', kwargs)
126 return render(request, 'location.html', kwargs)
123
127
124
128
125 @login_required
129 @login_required
126 def location_new(request):
130 def location_new(request):
127
131
128 if request.method == 'GET':
132 if request.method == 'GET':
129 form = LocationForm()
133 form = LocationForm()
130
134
131 if request.method == 'POST':
135 if request.method == 'POST':
132 form = LocationForm(request.POST)
136 form = LocationForm(request.POST)
133
137
134 if form.is_valid():
138 if form.is_valid():
135 form.save()
139 form.save()
136 return redirect('url_locations')
140 return redirect('url_locations')
137
141
138 kwargs = {}
142 kwargs = {}
139 kwargs['form'] = form
143 kwargs['form'] = form
140 kwargs['title'] = 'Radar System'
144 kwargs['title'] = 'Radar System'
141 kwargs['suptitle'] = 'New'
145 kwargs['suptitle'] = 'New'
142 kwargs['button'] = 'Create'
146 kwargs['button'] = 'Create'
143
147
144 return render(request, 'base_edit.html', kwargs)
148 return render(request, 'base_edit.html', kwargs)
145
149
146
150
147 @login_required
151 @login_required
148 def location_edit(request, id_loc):
152 def location_edit(request, id_loc):
149
153
150 location = get_object_or_404(Location, pk=id_loc)
154 location = get_object_or_404(Location, pk=id_loc)
151
155
152 if request.method == 'GET':
156 if request.method == 'GET':
153 form = LocationForm(instance=location)
157 form = LocationForm(instance=location)
154
158
155 if request.method == 'POST':
159 if request.method == 'POST':
156 form = LocationForm(request.POST, instance=location)
160 form = LocationForm(request.POST, instance=location)
157
161
158 if form.is_valid():
162 if form.is_valid():
159 form.save()
163 form.save()
160 return redirect('url_locations')
164 return redirect('url_locations')
161
165
162 kwargs = {}
166 kwargs = {}
163 kwargs['form'] = form
167 kwargs['form'] = form
164 kwargs['title'] = 'Location'
168 kwargs['title'] = 'Location'
165 kwargs['suptitle'] = 'Edit'
169 kwargs['suptitle'] = 'Edit'
166 kwargs['button'] = 'Update'
170 kwargs['button'] = 'Update'
167
171
168 return render(request, 'base_edit.html', kwargs)
172 return render(request, 'base_edit.html', kwargs)
169
173
170
174
171 @login_required
175 @login_required
172 def location_delete(request, id_loc):
176 def location_delete(request, id_loc):
173
177
174 location = get_object_or_404(Location, pk=id_loc)
178 location = get_object_or_404(Location, pk=id_loc)
175
179
176 if request.method == 'POST':
180 if request.method == 'POST':
177
181
178 if is_developer(request.user):
182 if is_developer(request.user):
179 location.delete()
183 location.delete()
180 return redirect('url_locations')
184 return redirect('url_locations')
181
185
182 messages.error(request, 'Not enough permission to delete this object')
186 messages.error(request, 'Not enough permission to delete this object')
183 return redirect(location.get_absolute_url())
187 return redirect(location.get_absolute_url())
184
188
185 kwargs = {
189 kwargs = {
186 'title': 'Delete',
190 'title': 'Delete',
187 'suptitle': 'Location',
191 'suptitle': 'Location',
188 'object': location,
192 'object': location,
189 'delete': True
193 'delete': True
190 }
194 }
191
195
192 return render(request, 'confirm.html', kwargs)
196 return render(request, 'confirm.html', kwargs)
193
197
194
198
195 def devices(request):
199 def devices(request):
196
200
197 page = request.GET.get('page')
201 page = request.GET.get('page')
198 order = ('location', 'device_type')
202 order = ('location', 'device_type')
199
203
200 filters = request.GET.copy()
204 filters = request.GET.copy()
201 kwargs = get_paginator(Device, page, order, filters)
205 kwargs = get_paginator(Device, page, order, filters)
202 form = FilterForm(initial=request.GET, extra_fields=['tags'])
206 form = FilterForm(initial=request.GET, extra_fields=['tags'])
203
207
204 kwargs['keys'] = ['device_type', 'location',
208 kwargs['keys'] = ['device_type', 'location',
205 'ip_address', 'port_address', 'actions']
209 'ip_address', 'port_address', 'actions']
206 kwargs['title'] = 'Device'
210 kwargs['title'] = 'Device'
207 kwargs['suptitle'] = 'List'
211 kwargs['suptitle'] = 'List'
208 kwargs['no_sidebar'] = True
212 kwargs['no_sidebar'] = True
209 kwargs['form'] = form
213 kwargs['form'] = form
210 kwargs['add_url'] = reverse('url_add_device')
214 kwargs['add_url'] = reverse('url_add_device')
211 filters.pop('page', None)
215 filters.pop('page', None)
212 kwargs['q'] = urlencode(filters)
216 kwargs['q'] = urlencode(filters)
213 kwargs['menu_devices'] = 'active'
217 kwargs['menu_devices'] = 'active'
214 return render(request, 'base_list.html', kwargs)
218 return render(request, 'base_list.html', kwargs)
215
219
216
220
217 def device(request, id_dev):
221 def device(request, id_dev):
218
222
219 device = get_object_or_404(Device, pk=id_dev)
223 device = get_object_or_404(Device, pk=id_dev)
220
224
221 kwargs = {}
225 kwargs = {}
222 kwargs['device'] = device
226 kwargs['device'] = device
223 kwargs['device_keys'] = ['device_type',
227 kwargs['device_keys'] = ['device_type',
224 'ip_address', 'port_address', 'description']
228 'ip_address', 'port_address', 'description']
225
229
226 kwargs['title'] = 'Device'
230 kwargs['title'] = 'Device'
227 kwargs['suptitle'] = 'Details'
231 kwargs['suptitle'] = 'Details'
228 kwargs['menu_devices'] = 'active'
232 kwargs['menu_devices'] = 'active'
229
233
230 return render(request, 'device.html', kwargs)
234 return render(request, 'device.html', kwargs)
231
235
232
236
233 @login_required
237 @login_required
234 def device_new(request):
238 def device_new(request):
235
239
236 if request.method == 'GET':
240 if request.method == 'GET':
237 form = DeviceForm()
241 form = DeviceForm()
238
242
239 if request.method == 'POST':
243 if request.method == 'POST':
240 form = DeviceForm(request.POST)
244 form = DeviceForm(request.POST)
241
245
242 if form.is_valid():
246 if form.is_valid():
243 form.save()
247 form.save()
244 return redirect('url_devices')
248 return redirect('url_devices')
245
249
246 kwargs = {}
250 kwargs = {}
247 kwargs['form'] = form
251 kwargs['form'] = form
248 kwargs['title'] = 'Device'
252 kwargs['title'] = 'Device'
249 kwargs['suptitle'] = 'New'
253 kwargs['suptitle'] = 'New'
250 kwargs['button'] = 'Create'
254 kwargs['button'] = 'Create'
251 kwargs['menu_devices'] = 'active'
255 kwargs['menu_devices'] = 'active'
252
256
253 return render(request, 'base_edit.html', kwargs)
257 return render(request, 'base_edit.html', kwargs)
254
258
255
259
256 @login_required
260 @login_required
257 def device_edit(request, id_dev):
261 def device_edit(request, id_dev):
258
262
259 device = get_object_or_404(Device, pk=id_dev)
263 device = get_object_or_404(Device, pk=id_dev)
260
264
261 if request.method == 'GET':
265 if request.method == 'GET':
262 form = DeviceForm(instance=device)
266 form = DeviceForm(instance=device)
263
267
264 if request.method == 'POST':
268 if request.method == 'POST':
265 form = DeviceForm(request.POST, instance=device)
269 form = DeviceForm(request.POST, instance=device)
266
270
267 if form.is_valid():
271 if form.is_valid():
268 form.save()
272 form.save()
269 return redirect(device.get_absolute_url())
273 return redirect(device.get_absolute_url())
270
274
271 kwargs = {}
275 kwargs = {}
272 kwargs['form'] = form
276 kwargs['form'] = form
273 kwargs['title'] = 'Device'
277 kwargs['title'] = 'Device'
274 kwargs['suptitle'] = 'Edit'
278 kwargs['suptitle'] = 'Edit'
275 kwargs['button'] = 'Update'
279 kwargs['button'] = 'Update'
276 kwargs['menu_devices'] = 'active'
280 kwargs['menu_devices'] = 'active'
277
281
278 return render(request, 'base_edit.html', kwargs)
282 return render(request, 'base_edit.html', kwargs)
279
283
280
284
281 @login_required
285 @login_required
282 def device_delete(request, id_dev):
286 def device_delete(request, id_dev):
283
287
284 device = get_object_or_404(Device, pk=id_dev)
288 device = get_object_or_404(Device, pk=id_dev)
285
289
286 if request.method == 'POST':
290 if request.method == 'POST':
287
291
288 if is_developer(request.user):
292 if is_developer(request.user):
289 device.delete()
293 device.delete()
290 return redirect('url_devices')
294 return redirect('url_devices')
291
295
292 messages.error(request, 'Not enough permission to delete this object')
296 messages.error(request, 'Not enough permission to delete this object')
293 return redirect(device.get_absolute_url())
297 return redirect(device.get_absolute_url())
294
298
295 kwargs = {
299 kwargs = {
296 'title': 'Delete',
300 'title': 'Delete',
297 'suptitle': 'Device',
301 'suptitle': 'Device',
298 'object': device,
302 'object': device,
299 'delete': True
303 'delete': True
300 }
304 }
301 kwargs['menu_devices'] = 'active'
305 kwargs['menu_devices'] = 'active'
302
306
303 return render(request, 'confirm.html', kwargs)
307 return render(request, 'confirm.html', kwargs)
304
308
305
309
306 @login_required
310 @login_required
307 def device_change_ip(request, id_dev):
311 def device_change_ip(request, id_dev):
308
312
309 device = get_object_or_404(Device, pk=id_dev)
313 device = get_object_or_404(Device, pk=id_dev)
310
314
311 if request.method == 'POST':
315 if request.method == 'POST':
312
316
313 if is_developer(request.user):
317 if is_developer(request.user):
314 device.change_ip(**request.POST.dict())
318 device.change_ip(**request.POST.dict())
315 level, message = device.message.split('|')
319 level, message = device.message.split('|')
316 messages.add_message(request, level, message)
320 messages.add_message(request, level, message)
317 else:
321 else:
318 messages.error(
322 messages.error(
319 request, 'Not enough permission to delete this object')
323 request, 'Not enough permission to delete this object')
320 return redirect(device.get_absolute_url())
324 return redirect(device.get_absolute_url())
321
325
322 kwargs = {
326 kwargs = {
323 'title': 'Device',
327 'title': 'Device',
324 'suptitle': 'Change IP',
328 'suptitle': 'Change IP',
325 'object': device,
329 'object': device,
326 'previous': device.get_absolute_url(),
330 'previous': device.get_absolute_url(),
327 'form': ChangeIpForm(initial={'ip_address': device.ip_address}),
331 'form': ChangeIpForm(initial={'ip_address': device.ip_address}),
328 'message': ' ',
332 'message': ' ',
329 }
333 }
330 kwargs['menu_devices'] = 'active'
334 kwargs['menu_devices'] = 'active'
331
335
332 return render(request, 'confirm.html', kwargs)
336 return render(request, 'confirm.html', kwargs)
333
337
334
338
335 def campaigns(request):
339 def campaigns(request):
336
340
337 page = request.GET.get('page')
341 page = request.GET.get('page')
338 order = ('-start_date',)
342 order = ('-start_date',)
339 filters = request.GET.copy()
343 filters = request.GET.copy()
340
344
341 kwargs = get_paginator(Campaign, page, order, filters)
345 kwargs = get_paginator(Campaign, page, order, filters)
342
346
343 form = FilterForm(initial=request.GET, extra_fields=[
347 form = FilterForm(initial=request.GET, extra_fields=[
344 'range_date', 'tags', 'template'])
348 'range_date', 'tags', 'template'])
345 kwargs['keys'] = ['name', 'start_date', 'end_date', 'actions']
349 kwargs['keys'] = ['name', 'start_date', 'end_date', 'actions']
346 kwargs['title'] = 'Campaign'
350 kwargs['title'] = 'Campaign'
347 kwargs['suptitle'] = 'List'
351 kwargs['suptitle'] = 'List'
348 kwargs['no_sidebar'] = True
352 kwargs['no_sidebar'] = True
349 kwargs['form'] = form
353 kwargs['form'] = form
350 kwargs['add_url'] = reverse('url_add_campaign')
354 kwargs['add_url'] = reverse('url_add_campaign')
351 filters.pop('page', None)
355 filters.pop('page', None)
352 kwargs['q'] = urlencode(filters)
356 kwargs['q'] = urlencode(filters)
353 kwargs['menu_campaigns'] = 'active'
357 kwargs['menu_campaigns'] = 'active'
354
358
355 return render(request, 'base_list.html', kwargs)
359 return render(request, 'base_list.html', kwargs)
356
360
357
361
358 def campaign(request, id_camp):
362 def campaign(request, id_camp):
359
363
360 campaign = get_object_or_404(Campaign, pk=id_camp)
364 campaign = get_object_or_404(Campaign, pk=id_camp)
361 experiments = Experiment.objects.filter(campaign=campaign)
365 experiments = Experiment.objects.filter(campaign=campaign)
362
366
363 form = CampaignForm(instance=campaign)
367 form = CampaignForm(instance=campaign)
364
368
365 kwargs = {}
369 kwargs = {}
366 kwargs['campaign'] = campaign
370 kwargs['campaign'] = campaign
367 kwargs['campaign_keys'] = ['template', 'name',
371 kwargs['campaign_keys'] = ['template', 'name',
368 'start_date', 'end_date', 'tags', 'description']
372 'start_date', 'end_date', 'tags', 'description']
369
373
370 kwargs['experiments'] = experiments
374 kwargs['experiments'] = experiments
371 kwargs['experiment_keys'] = [
375 kwargs['experiment_keys'] = [
372 'name', 'radar_system', 'start_time', 'end_time']
376 'name', 'radar_system', 'start_time', 'end_time']
373
377
374 kwargs['title'] = 'Campaign'
378 kwargs['title'] = 'Campaign'
375 kwargs['suptitle'] = 'Details'
379 kwargs['suptitle'] = 'Details'
376
380
377 kwargs['form'] = form
381 kwargs['form'] = form
378 kwargs['button'] = 'Add Experiment'
382 kwargs['button'] = 'Add Experiment'
379 kwargs['menu_campaigns'] = 'active'
383 kwargs['menu_campaigns'] = 'active'
380
384
381 return render(request, 'campaign.html', kwargs)
385 return render(request, 'campaign.html', kwargs)
382
386
383
387
384 @login_required
388 @login_required
385 def campaign_new(request):
389 def campaign_new(request):
386
390
387 kwargs = {}
391 kwargs = {}
388
392
389 if request.method == 'GET':
393 if request.method == 'GET':
390
394
391 if 'template' in request.GET:
395 if 'template' in request.GET:
392 if request.GET['template'] == '0':
396 if request.GET['template'] == '0':
393 form = NewForm(initial={'create_from': 2},
397 form = NewForm(initial={'create_from': 2},
394 template_choices=Campaign.objects.filter(template=True).values_list('id', 'name'))
398 template_choices=Campaign.objects.filter(template=True).values_list('id', 'name'))
395 else:
399 else:
396 kwargs['button'] = 'Create'
400 kwargs['button'] = 'Create'
397 kwargs['experiments'] = Configuration.objects.filter(
401 kwargs['experiments'] = Configuration.objects.filter(
398 experiment=request.GET['template'])
402 experiment=request.GET['template'])
399 kwargs['experiment_keys'] = ['name', 'start_time', 'end_time']
403 kwargs['experiment_keys'] = ['name', 'start_time', 'end_time']
400 camp = Campaign.objects.get(pk=request.GET['template'])
404 camp = Campaign.objects.get(pk=request.GET['template'])
401 form = CampaignForm(instance=camp,
405 form = CampaignForm(instance=camp,
402 initial={'name': '{}_{:%Y%m%d}'.format(camp.name, datetime.now()),
406 initial={'name': '{}_{:%Y%m%d}'.format(camp.name, datetime.now()),
403 'template': False})
407 'template': False})
404 elif 'blank' in request.GET:
408 elif 'blank' in request.GET:
405 kwargs['button'] = 'Create'
409 kwargs['button'] = 'Create'
406 form = CampaignForm()
410 form = CampaignForm()
407 else:
411 else:
408 form = NewForm()
412 form = NewForm()
409
413
410 if request.method == 'POST':
414 if request.method == 'POST':
411 kwargs['button'] = 'Create'
415 kwargs['button'] = 'Create'
412 post = request.POST.copy()
416 post = request.POST.copy()
413 experiments = []
417 experiments = []
414
418
415 for id_exp in post.getlist('experiments'):
419 for id_exp in post.getlist('experiments'):
416 exp = Experiment.objects.get(pk=id_exp)
420 exp = Experiment.objects.get(pk=id_exp)
417 new_exp = exp.clone(template=False)
421 new_exp = exp.clone(template=False)
418 experiments.append(new_exp)
422 experiments.append(new_exp)
419
423
420 post.setlist('experiments', [])
424 post.setlist('experiments', [])
421
425
422 form = CampaignForm(post)
426 form = CampaignForm(post)
423
427
424 if form.is_valid():
428 if form.is_valid():
425 campaign = form.save(commit=False)
429 campaign = form.save(commit=False)
426 campaign.author = request.user
430 campaign.author = request.user
427 for exp in experiments:
431 for exp in experiments:
428 campaign.experiments.add(exp)
432 campaign.experiments.add(exp)
429 campaign.save()
433 campaign.save()
430 return redirect('url_campaign', id_camp=campaign.id)
434 return redirect('url_campaign', id_camp=campaign.id)
431
435
432 kwargs['form'] = form
436 kwargs['form'] = form
433 kwargs['title'] = 'Campaign'
437 kwargs['title'] = 'Campaign'
434 kwargs['suptitle'] = 'New'
438 kwargs['suptitle'] = 'New'
435 kwargs['menu_campaigns'] = 'active'
439 kwargs['menu_campaigns'] = 'active'
436
440
437 return render(request, 'campaign_edit.html', kwargs)
441 return render(request, 'campaign_edit.html', kwargs)
438
442
439
443
440 @login_required
444 @login_required
441 def campaign_edit(request, id_camp):
445 def campaign_edit(request, id_camp):
442
446
443 campaign = get_object_or_404(Campaign, pk=id_camp)
447 campaign = get_object_or_404(Campaign, pk=id_camp)
444
448
445 if request.method == 'GET':
449 if request.method == 'GET':
446 form = CampaignForm(instance=campaign)
450 form = CampaignForm(instance=campaign)
447
451
448 if request.method == 'POST':
452 if request.method == 'POST':
449 exps = campaign.experiments.all().values_list('pk', flat=True)
453 exps = campaign.experiments.all().values_list('pk', flat=True)
450 post = request.POST.copy()
454 post = request.POST.copy()
451 new_exps = post.getlist('experiments')
455 new_exps = post.getlist('experiments')
452 post.setlist('experiments', [])
456 post.setlist('experiments', [])
453 form = CampaignForm(post, instance=campaign)
457 form = CampaignForm(post, instance=campaign)
454
458
455 if form.is_valid():
459 if form.is_valid():
456 camp = form.save()
460 camp = form.save()
457 for id_exp in new_exps:
461 for id_exp in new_exps:
458 if int(id_exp) in exps:
462 if int(id_exp) in exps:
459 exps.pop(id_exp)
463 exps.pop(id_exp)
460 else:
464 else:
461 exp = Experiment.objects.get(pk=id_exp)
465 exp = Experiment.objects.get(pk=id_exp)
462 if exp.template:
466 if exp.template:
463 camp.experiments.add(exp.clone(template=False))
467 camp.experiments.add(exp.clone(template=False))
464 else:
468 else:
465 camp.experiments.add(exp)
469 camp.experiments.add(exp)
466
470
467 for id_exp in exps:
471 for id_exp in exps:
468 camp.experiments.remove(Experiment.objects.get(pk=id_exp))
472 camp.experiments.remove(Experiment.objects.get(pk=id_exp))
469
473
470 return redirect('url_campaign', id_camp=id_camp)
474 return redirect('url_campaign', id_camp=id_camp)
471
475
472 kwargs = {}
476 kwargs = {}
473 kwargs['form'] = form
477 kwargs['form'] = form
474 kwargs['title'] = 'Campaign'
478 kwargs['title'] = 'Campaign'
475 kwargs['suptitle'] = 'Edit'
479 kwargs['suptitle'] = 'Edit'
476 kwargs['button'] = 'Update'
480 kwargs['button'] = 'Update'
477 kwargs['menu_campaigns'] = 'active'
481 kwargs['menu_campaigns'] = 'active'
478
482
479 return render(request, 'campaign_edit.html', kwargs)
483 return render(request, 'campaign_edit.html', kwargs)
480
484
481
485
482 @login_required
486 @login_required
483 def campaign_delete(request, id_camp):
487 def campaign_delete(request, id_camp):
484
488
485 campaign = get_object_or_404(Campaign, pk=id_camp)
489 campaign = get_object_or_404(Campaign, pk=id_camp)
486
490
487 if request.method == 'POST':
491 if request.method == 'POST':
488 if is_developer(request.user):
492 if is_developer(request.user):
489
493
490 for exp in campaign.experiments.all():
494 for exp in campaign.experiments.all():
491 for conf in Configuration.objects.filter(experiment=exp):
495 for conf in Configuration.objects.filter(experiment=exp):
492 conf.delete()
496 conf.delete()
493 exp.delete()
497 exp.delete()
494 campaign.delete()
498 campaign.delete()
495
499
496 return redirect('url_campaigns')
500 return redirect('url_campaigns')
497
501
498 messages.error(request, 'Not enough permission to delete this object')
502 messages.error(request, 'Not enough permission to delete this object')
499 return redirect(campaign.get_absolute_url())
503 return redirect(campaign.get_absolute_url())
500
504
501 kwargs = {
505 kwargs = {
502 'title': 'Delete',
506 'title': 'Delete',
503 'suptitle': 'Campaign',
507 'suptitle': 'Campaign',
504 'object': campaign,
508 'object': campaign,
505 'delete': True
509 'delete': True
506 }
510 }
507 kwargs['menu_campaigns'] = 'active'
511 kwargs['menu_campaigns'] = 'active'
508
512
509 return render(request, 'confirm.html', kwargs)
513 return render(request, 'confirm.html', kwargs)
510
514
511
515
512 @login_required
516 @login_required
513 def campaign_export(request, id_camp):
517 def campaign_export(request, id_camp):
514
518
515 campaign = get_object_or_404(Campaign, pk=id_camp)
519 campaign = get_object_or_404(Campaign, pk=id_camp)
516 content = campaign.parms_to_dict()
520 content = campaign.parms_to_dict()
517 content_type = 'application/json'
521 content_type = 'application/json'
518 filename = '%s_%s.json' % (campaign.name, campaign.id)
522 filename = '%s_%s.json' % (campaign.name, campaign.id)
519
523
520 response = HttpResponse(content_type=content_type)
524 response = HttpResponse(content_type=content_type)
521 response['Content-Disposition'] = 'attachment; filename="%s"' % filename
525 response['Content-Disposition'] = 'attachment; filename="%s"' % filename
522 response.write(json.dumps(content, indent=2))
526 response.write(json.dumps(content, indent=2))
523
527
524 return response
528 return response
525
529
526
530
527 @login_required
531 @login_required
528 def campaign_import(request, id_camp):
532 def campaign_import(request, id_camp):
529
533
530 campaign = get_object_or_404(Campaign, pk=id_camp)
534 campaign = get_object_or_404(Campaign, pk=id_camp)
531
535
532 if request.method == 'GET':
536 if request.method == 'GET':
533 file_form = UploadFileForm()
537 file_form = UploadFileForm()
534
538
535 if request.method == 'POST':
539 if request.method == 'POST':
536 file_form = UploadFileForm(request.POST, request.FILES)
540 file_form = UploadFileForm(request.POST, request.FILES)
537
541
538 if file_form.is_valid():
542 if file_form.is_valid():
539 new_camp = campaign.dict_to_parms(
543 new_camp = campaign.dict_to_parms(
540 json.load(request.FILES['file']), CONF_MODELS)
544 json.load(request.FILES['file']), CONF_MODELS)
541 messages.success(
545 messages.success(
542 request, "Parameters imported from: '%s'." % request.FILES['file'].name)
546 request, "Parameters imported from: '%s'." % request.FILES['file'].name)
543 return redirect(new_camp.get_absolute_url_edit())
547 return redirect(new_camp.get_absolute_url_edit())
544
548
545 messages.error(request, "Could not import parameters from file")
549 messages.error(request, "Could not import parameters from file")
546
550
547 kwargs = {}
551 kwargs = {}
548 kwargs['title'] = 'Campaign'
552 kwargs['title'] = 'Campaign'
549 kwargs['form'] = file_form
553 kwargs['form'] = file_form
550 kwargs['suptitle'] = 'Importing file'
554 kwargs['suptitle'] = 'Importing file'
551 kwargs['button'] = 'Import'
555 kwargs['button'] = 'Import'
552 kwargs['menu_campaigns'] = 'active'
556 kwargs['menu_campaigns'] = 'active'
553
557
554 return render(request, 'campaign_import.html', kwargs)
558 return render(request, 'campaign_import.html', kwargs)
555
559
556
560
557 def experiments(request):
561 def experiments(request):
558
562
559 page = request.GET.get('page')
563 page = request.GET.get('page')
560 order = ('location',)
564 order = ('location',)
561 filters = request.GET.copy()
565 filters = request.GET.copy()
562
566
563 if 'my experiments' in filters:
567 if 'my experiments' in filters:
564 filters.pop('my experiments', None)
568 filters.pop('my experiments', None)
565 filters['mine'] = request.user.id
569 filters['mine'] = request.user.id
566
570
567 kwargs = get_paginator(Experiment, page, order, filters)
571 kwargs = get_paginator(Experiment, page, order, filters)
568
572
569 fields = ['tags', 'template']
573 fields = ['tags', 'template']
570 if request.user.is_authenticated:
574 if request.user.is_authenticated:
571 fields.append('my experiments')
575 fields.append('my experiments')
572
576
573 form = FilterForm(initial=request.GET, extra_fields=fields)
577 form = FilterForm(initial=request.GET, extra_fields=fields)
574
578
575 kwargs['keys'] = ['name', 'radar_system',
579 kwargs['keys'] = ['name', 'radar_system',
576 'start_time', 'end_time', 'actions']
580 'start_time', 'end_time', 'actions']
577 kwargs['title'] = 'Experiment'
581 kwargs['title'] = 'Experiment'
578 kwargs['suptitle'] = 'List'
582 kwargs['suptitle'] = 'List'
579 kwargs['no_sidebar'] = True
583 kwargs['no_sidebar'] = True
580 kwargs['form'] = form
584 kwargs['form'] = form
581 kwargs['add_url'] = reverse('url_add_experiment')
585 kwargs['add_url'] = reverse('url_add_experiment')
582 filters = request.GET.copy()
586 filters = request.GET.copy()
583 filters.pop('page', None)
587 filters.pop('page', None)
584 kwargs['q'] = urlencode(filters)
588 kwargs['q'] = urlencode(filters)
585 kwargs['menu_experiments'] = 'active'
589 kwargs['menu_experiments'] = 'active'
586
590
587 return render(request, 'base_list.html', kwargs)
591 return render(request, 'base_list.html', kwargs)
588
592
589
593
590 def experiment(request, id_exp):
594 def experiment(request, id_exp):
591
595
592 experiment = get_object_or_404(Experiment, pk=id_exp)
596 experiment = get_object_or_404(Experiment, pk=id_exp)
593
597
594 configurations = Configuration.objects.filter(
598 configurations = Configuration.objects.filter(
595 experiment=experiment, type=0)
599 experiment=experiment, type=0)
596
600
597 kwargs = {}
601 kwargs = {}
598
602
599 kwargs['experiment_keys'] = ['template', 'radar_system',
603 kwargs['experiment_keys'] = ['template', 'radar_system',
600 'name', 'start_time', 'end_time']
604 'name', 'start_time', 'end_time']
601 kwargs['experiment'] = experiment
605 kwargs['experiment'] = experiment
602 kwargs['configuration_keys'] = ['name', 'device__ip_address',
606 kwargs['configuration_keys'] = ['name', 'device__ip_address',
603 'device__port_address', 'device__status']
607 'device__port_address', 'device__status']
604 kwargs['configurations'] = configurations
608 kwargs['configurations'] = configurations
605 kwargs['title'] = 'Experiment'
609 kwargs['title'] = 'Experiment'
606 kwargs['suptitle'] = 'Details'
610 kwargs['suptitle'] = 'Details'
607 kwargs['button'] = 'Add Configuration'
611 kwargs['button'] = 'Add Configuration'
608 kwargs['menu_experiments'] = 'active'
612 kwargs['menu_experiments'] = 'active'
609
613
610 ###### SIDEBAR ######
614 ###### SIDEBAR ######
611 kwargs.update(sidebar(experiment=experiment))
615 kwargs.update(sidebar(experiment=experiment))
612
616
613 return render(request, 'experiment.html', kwargs)
617 return render(request, 'experiment.html', kwargs)
614
618
615
619
616 @login_required
620 @login_required
617 def experiment_new(request, id_camp=None):
621 def experiment_new(request, id_camp=None):
618
622
619 if not is_developer(request.user):
623 if not is_developer(request.user):
620 messages.error(
624 messages.error(
621 request, 'Developer required, to create new Experiments')
625 request, 'Developer required, to create new Experiments')
622 return redirect('index')
626 return redirect('index')
623 kwargs = {}
627 kwargs = {}
624
628
625 if request.method == 'GET':
629 if request.method == 'GET':
626 if 'template' in request.GET:
630 if 'template' in request.GET:
627 if request.GET['template'] == '0':
631 if request.GET['template'] == '0':
628 form = NewForm(initial={'create_from': 2},
632 form = NewForm(initial={'create_from': 2},
629 template_choices=Experiment.objects.filter(template=True).values_list('id', 'name'))
633 template_choices=Experiment.objects.filter(template=True).values_list('id', 'name'))
630 else:
634 else:
631 kwargs['button'] = 'Create'
635 kwargs['button'] = 'Create'
632 kwargs['configurations'] = Configuration.objects.filter(
636 kwargs['configurations'] = Configuration.objects.filter(
633 experiment=request.GET['template'])
637 experiment=request.GET['template'])
634 kwargs['configuration_keys'] = ['name', 'device__name',
638 kwargs['configuration_keys'] = ['name', 'device__name',
635 'device__ip_address', 'device__port_address']
639 'device__ip_address', 'device__port_address']
636 exp = Experiment.objects.get(pk=request.GET['template'])
640 exp = Experiment.objects.get(pk=request.GET['template'])
637 form = ExperimentForm(instance=exp,
641 form = ExperimentForm(instance=exp,
638 initial={'name': '{}_{:%y%m%d}'.format(exp.name, datetime.now()),
642 initial={'name': '{}_{:%y%m%d}'.format(exp.name, datetime.now()),
639 'template': False})
643 'template': False})
640 elif 'blank' in request.GET:
644 elif 'blank' in request.GET:
641 kwargs['button'] = 'Create'
645 kwargs['button'] = 'Create'
642 form = ExperimentForm()
646 form = ExperimentForm()
643 else:
647 else:
644 form = NewForm()
648 form = NewForm()
645
649
646 if request.method == 'POST':
650 if request.method == 'POST':
647 form = ExperimentForm(request.POST)
651 form = ExperimentForm(request.POST)
648 if form.is_valid():
652 if form.is_valid():
649 experiment = form.save(commit=False)
653 experiment = form.save(commit=False)
650 experiment.author = request.user
654 experiment.author = request.user
651 experiment.save()
655 experiment.save()
652
656
653 if 'template' in request.GET:
657 if 'template' in request.GET:
654 configurations = Configuration.objects.filter(
658 configurations = Configuration.objects.filter(
655 experiment=request.GET['template'], type=0)
659 experiment=request.GET['template'], type=0)
656 for conf in configurations:
660 for conf in configurations:
657 conf.clone(experiment=experiment, template=False)
661 conf.clone(experiment=experiment, template=False)
658
662
659 return redirect('url_experiment', id_exp=experiment.id)
663 return redirect('url_experiment', id_exp=experiment.id)
660
664
661 kwargs['form'] = form
665 kwargs['form'] = form
662 kwargs['title'] = 'Experiment'
666 kwargs['title'] = 'Experiment'
663 kwargs['suptitle'] = 'New'
667 kwargs['suptitle'] = 'New'
664 kwargs['menu_experiments'] = 'active'
668 kwargs['menu_experiments'] = 'active'
665
669
666 return render(request, 'experiment_edit.html', kwargs)
670 return render(request, 'experiment_edit.html', kwargs)
667
671
668
672
669 @login_required
673 @login_required
670 def experiment_edit(request, id_exp):
674 def experiment_edit(request, id_exp):
671
675
672 experiment = get_object_or_404(Experiment, pk=id_exp)
676 experiment = get_object_or_404(Experiment, pk=id_exp)
673
677
674 if request.method == 'GET':
678 if request.method == 'GET':
675 form = ExperimentForm(instance=experiment)
679 form = ExperimentForm(instance=experiment)
676
680
677 if request.method == 'POST':
681 if request.method == 'POST':
678 form = ExperimentForm(request.POST, instance=experiment)
682 form = ExperimentForm(request.POST, instance=experiment)
679
683
680 if form.is_valid():
684 if form.is_valid():
681 experiment = form.save()
685 experiment = form.save()
682 return redirect('url_experiment', id_exp=experiment.id)
686 return redirect('url_experiment', id_exp=experiment.id)
683
687
684 kwargs = {}
688 kwargs = {}
685 kwargs['form'] = form
689 kwargs['form'] = form
686 kwargs['title'] = 'Experiment'
690 kwargs['title'] = 'Experiment'
687 kwargs['suptitle'] = 'Edit'
691 kwargs['suptitle'] = 'Edit'
688 kwargs['button'] = 'Update'
692 kwargs['button'] = 'Update'
689 kwargs['menu_experiments'] = 'active'
693 kwargs['menu_experiments'] = 'active'
690
694
691 return render(request, 'experiment_edit.html', kwargs)
695 return render(request, 'experiment_edit.html', kwargs)
692
696
693
697
694 @login_required
698 @login_required
695 def experiment_delete(request, id_exp):
699 def experiment_delete(request, id_exp):
696
700
697 experiment = get_object_or_404(Experiment, pk=id_exp)
701 experiment = get_object_or_404(Experiment, pk=id_exp)
698
702
699 if request.method == 'POST':
703 if request.method == 'POST':
700 if is_developer(request.user):
704 if is_developer(request.user):
701 for conf in Configuration.objects.filter(experiment=experiment):
705 for conf in Configuration.objects.filter(experiment=experiment):
702 conf.delete()
706 conf.delete()
703 experiment.delete()
707 experiment.delete()
704 return redirect('url_experiments')
708 return redirect('url_experiments')
705
709
706 messages.error(request, 'Not enough permission to delete this object')
710 messages.error(request, 'Not enough permission to delete this object')
707 return redirect(experiment.get_absolute_url())
711 return redirect(experiment.get_absolute_url())
708
712
709 kwargs = {
713 kwargs = {
710 'title': 'Delete',
714 'title': 'Delete',
711 'suptitle': 'Experiment',
715 'suptitle': 'Experiment',
712 'object': experiment,
716 'object': experiment,
713 'delete': True
717 'delete': True
714 }
718 }
715
719
716 return render(request, 'confirm.html', kwargs)
720 return render(request, 'confirm.html', kwargs)
717
721
718
722
719 @login_required
723 @login_required
720 def experiment_export(request, id_exp):
724 def experiment_export(request, id_exp):
721
725
722 experiment = get_object_or_404(Experiment, pk=id_exp)
726 experiment = get_object_or_404(Experiment, pk=id_exp)
723 content = experiment.parms_to_dict()
727 content = experiment.parms_to_dict()
724 content_type = 'application/json'
728 content_type = 'application/json'
725 filename = '%s_%s.json' % (experiment.name, experiment.id)
729 filename = '%s_%s.json' % (experiment.name, experiment.id)
726
730
727 response = HttpResponse(content_type=content_type)
731 response = HttpResponse(content_type=content_type)
728 response['Content-Disposition'] = 'attachment; filename="%s"' % filename
732 response['Content-Disposition'] = 'attachment; filename="%s"' % filename
729 response.write(json.dumps(content, indent=2))
733 response.write(json.dumps(content, indent=2))
730
734
731 return response
735 return response
732
736
733
737
734 @login_required
738 @login_required
735 def experiment_import(request, id_exp):
739 def experiment_import(request, id_exp):
736
740
737 experiment = get_object_or_404(Experiment, pk=id_exp)
741 experiment = get_object_or_404(Experiment, pk=id_exp)
738 configurations = Configuration.objects.filter(experiment=experiment)
742 configurations = Configuration.objects.filter(experiment=experiment)
739
743
740 if request.method == 'GET':
744 if request.method == 'GET':
741 file_form = UploadFileForm()
745 file_form = UploadFileForm()
742
746
743 if request.method == 'POST':
747 if request.method == 'POST':
744 file_form = UploadFileForm(request.POST, request.FILES)
748 file_form = UploadFileForm(request.POST, request.FILES)
745
749
746 if file_form.is_valid():
750 if file_form.is_valid():
747 new_exp = experiment.dict_to_parms(
751 new_exp = experiment.dict_to_parms(
748 json.load(request.FILES['file']), CONF_MODELS)
752 json.load(request.FILES['file']), CONF_MODELS)
749 messages.success(
753 messages.success(
750 request, "Parameters imported from: '%s'." % request.FILES['file'].name)
754 request, "Parameters imported from: '%s'." % request.FILES['file'].name)
751 return redirect(new_exp.get_absolute_url_edit())
755 return redirect(new_exp.get_absolute_url_edit())
752
756
753 messages.error(request, "Could not import parameters from file")
757 messages.error(request, "Could not import parameters from file")
754
758
755 kwargs = {}
759 kwargs = {}
756 kwargs['title'] = 'Experiment'
760 kwargs['title'] = 'Experiment'
757 kwargs['form'] = file_form
761 kwargs['form'] = file_form
758 kwargs['suptitle'] = 'Importing file'
762 kwargs['suptitle'] = 'Importing file'
759 kwargs['button'] = 'Import'
763 kwargs['button'] = 'Import'
760 kwargs['menu_experiments'] = 'active'
764 kwargs['menu_experiments'] = 'active'
761
765
762 kwargs.update(sidebar(experiment=experiment))
766 kwargs.update(sidebar(experiment=experiment))
763
767
764 return render(request, 'experiment_import.html', kwargs)
768 return render(request, 'experiment_import.html', kwargs)
765
769
766
770
767 @login_required
771 @login_required
768 def experiment_start(request, id_exp):
772 def experiment_start(request, id_exp):
769
773
770 exp = get_object_or_404(Experiment, pk=id_exp)
774 exp = get_object_or_404(Experiment, pk=id_exp)
771
775
772 if exp.status == 2:
776 if exp.status == 2:
773 messages.warning(request, 'Experiment {} already runnnig'.format(exp))
777 messages.warning(request, 'Experiment {} already runnnig'.format(exp))
774 else:
778 else:
775 exp.status = exp.start()
779 exp.status = exp.start()
776 if exp.status == 0:
780 if exp.status == 0:
777 messages.error(request, 'Experiment {} not start'.format(exp))
781 messages.error(request, 'Experiment {} not start'.format(exp))
778 if exp.status == 2:
782 if exp.status == 2:
779 messages.success(request, 'Experiment {} started'.format(exp))
783 messages.success(request, 'Experiment {} started'.format(exp))
780
784
781 exp.save()
785 exp.save()
782
786
783 return redirect(exp.get_absolute_url())
787 return redirect(exp.get_absolute_url())
784
788
785
789
786 @login_required
790 @login_required
787 def experiment_stop(request, id_exp):
791 def experiment_stop(request, id_exp):
788
792
789 exp = get_object_or_404(Experiment, pk=id_exp)
793 exp = get_object_or_404(Experiment, pk=id_exp)
790
794
791 if exp.status == 2:
795 if exp.status == 2:
792 exp.status = exp.stop()
796 exp.status = exp.stop()
793 exp.save()
797 exp.save()
794 messages.success(request, 'Experiment {} stopped'.format(exp))
798 messages.success(request, 'Experiment {} stopped'.format(exp))
795 else:
799 else:
796 messages.error(request, 'Experiment {} not running'.format(exp))
800 messages.error(request, 'Experiment {} not running'.format(exp))
797
801
798 return redirect(exp.get_absolute_url())
802 return redirect(exp.get_absolute_url())
799
803
800
804
801 def experiment_status(request, id_exp):
805 def experiment_status(request, id_exp):
802
806
803 exp = get_object_or_404(Experiment, pk=id_exp)
807 exp = get_object_or_404(Experiment, pk=id_exp)
804
808
805 exp.get_status()
809 exp.get_status()
806
810
807 return redirect(exp.get_absolute_url())
811 return redirect(exp.get_absolute_url())
808
812
809
813
810 @login_required
814 @login_required
811 def experiment_mix(request, id_exp):
815 def experiment_mix(request, id_exp):
812
816
813 experiment = get_object_or_404(Experiment, pk=id_exp)
817 experiment = get_object_or_404(Experiment, pk=id_exp)
814 rc_confs = [conf for conf in PedestalConfiguration.objects.filter(
818 rc_confs = [conf for conf in PedestalConfiguration.objects.filter(
815 experiment=id_exp,
819 experiment=id_exp,
816 type=0,
820 type=0,
817 mix=False)]
821 mix=False)]
818
822
819 if len(rc_confs) < 2:
823 if len(rc_confs) < 2:
820 messages.warning(
824 messages.warning(
821 request, 'You need at least two RC Configurations to make a mix')
825 request, 'You need at least two RC Configurations to make a mix')
822 return redirect(experiment.get_absolute_url())
826 return redirect(experiment.get_absolute_url())
823
827
824 mix_confs = PedestalConfiguration.objects.filter(experiment=id_exp, mix=True, type=0)
828 mix_confs = PedestalConfiguration.objects.filter(experiment=id_exp, mix=True, type=0)
825
829
826 if mix_confs:
830 if mix_confs:
827 mix = mix_confs[0]
831 mix = mix_confs[0]
828 else:
832 else:
829 mix = PedestalConfiguration(experiment=experiment,
833 mix = PedestalConfiguration(experiment=experiment,
830 device=rc_confs[0].device,
834 device=rc_confs[0].device,
831 ipp=rc_confs[0].ipp,
835 ipp=rc_confs[0].ipp,
832 clock_in=rc_confs[0].clock_in,
836 clock_in=rc_confs[0].clock_in,
833 clock_divider=rc_confs[0].clock_divider,
837 clock_divider=rc_confs[0].clock_divider,
834 mix=True,
838 mix=True,
835 parameters='')
839 parameters='')
836 mix.save()
840 mix.save()
837
841
838 line_type = RCLineType.objects.get(name='mix')
842 line_type = RCLineType.objects.get(name='mix')
839 print("VIew obteniendo len getlines")
843 print("VIew obteniendo len getlines")
840 print(len(rc_confs[0].get_lines()))
844 print(len(rc_confs[0].get_lines()))
841 for i in range(len(rc_confs[0].get_lines())):
845 for i in range(len(rc_confs[0].get_lines())):
842 line = RCLine(rc_configuration=mix, line_type=line_type, channel=i)
846 line = RCLine(rc_configuration=mix, line_type=line_type, channel=i)
843 line.save()
847 line.save()
844
848
845 initial = {'name': mix.name,
849 initial = {'name': mix.name,
846 'result': parse_mix_result(mix.parameters),
850 'result': parse_mix_result(mix.parameters),
847 'delay': 0,
851 'delay': 0,
848 'mask': [0, 1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13, 14, 15]
852 'mask': [0, 1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13, 14, 15]
849 }
853 }
850
854
851 if request.method == 'GET':
855 if request.method == 'GET':
852 form = RCMixConfigurationForm(confs=rc_confs, initial=initial)
856 form = RCMixConfigurationForm(confs=rc_confs, initial=initial)
853
857
854 if request.method == 'POST':
858 if request.method == 'POST':
855 result = mix.parameters
859 result = mix.parameters
856
860
857 if '{}|'.format(request.POST['experiment']) in result:
861 if '{}|'.format(request.POST['experiment']) in result:
858 messages.error(request, 'Configuration already added')
862 messages.error(request, 'Configuration already added')
859 else:
863 else:
860 if 'operation' in request.POST:
864 if 'operation' in request.POST:
861 operation = MIX_OPERATIONS[request.POST['operation']]
865 operation = MIX_OPERATIONS[request.POST['operation']]
862 else:
866 else:
863 operation = ' '
867 operation = ' '
864
868
865 mode = MIX_MODES[request.POST['mode']]
869 mode = MIX_MODES[request.POST['mode']]
866
870
867 if result:
871 if result:
868 result = '{}-{}|{}|{}|{}|{}'.format(mix.parameters,
872 result = '{}-{}|{}|{}|{}|{}'.format(mix.parameters,
869 request.POST['experiment'],
873 request.POST['experiment'],
870 mode,
874 mode,
871 operation,
875 operation,
872 float(
876 float(
873 request.POST['delay']),
877 request.POST['delay']),
874 parse_mask(
878 parse_mask(
875 request.POST.getlist('mask'))
879 request.POST.getlist('mask'))
876 )
880 )
877 else:
881 else:
878 result = '{}|{}|{}|{}|{}'.format(request.POST['experiment'],
882 result = '{}|{}|{}|{}|{}'.format(request.POST['experiment'],
879 mode,
883 mode,
880 operation,
884 operation,
881 float(request.POST['delay']),
885 float(request.POST['delay']),
882 parse_mask(
886 parse_mask(
883 request.POST.getlist('mask'))
887 request.POST.getlist('mask'))
884 )
888 )
885
889
886 mix.parameters = result
890 mix.parameters = result
887 mix.save()
891 mix.save()
888 mix.update_pulses()
892 mix.update_pulses()
889
893
890 initial['result'] = parse_mix_result(result)
894 initial['result'] = parse_mix_result(result)
891 initial['name'] = mix.name
895 initial['name'] = mix.name
892
896
893 form = RCMixConfigurationForm(initial=initial, confs=rc_confs)
897 form = RCMixConfigurationForm(initial=initial, confs=rc_confs)
894
898
895 kwargs = {
899 kwargs = {
896 'title': 'Experiment',
900 'title': 'Experiment',
897 'suptitle': 'Mix Configurations',
901 'suptitle': 'Mix Configurations',
898 'form': form,
902 'form': form,
899 'extra_button': 'Delete',
903 'extra_button': 'Delete',
900 'button': 'Add',
904 'button': 'Add',
901 'cancel': 'Back',
905 'cancel': 'Back',
902 'previous': experiment.get_absolute_url(),
906 'previous': experiment.get_absolute_url(),
903 'id_exp': id_exp,
907 'id_exp': id_exp,
904
908
905 }
909 }
906 kwargs['menu_experiments'] = 'active'
910 kwargs['menu_experiments'] = 'active'
907
911
908 return render(request, 'experiment_mix.html', kwargs)
912 return render(request, 'experiment_mix.html', kwargs)
909
913
910
914
911 @login_required
915 @login_required
912 def experiment_mix_delete(request, id_exp):
916 def experiment_mix_delete(request, id_exp):
913
917
914 conf = PedestalConfiguration.objects.get(experiment=id_exp, mix=True, type=0)
918 conf = PedestalConfiguration.objects.get(experiment=id_exp, mix=True, type=0)
915 values = conf.parameters.split('-')
919 values = conf.parameters.split('-')
916 conf.parameters = '-'.join(values[:-1])
920 conf.parameters = '-'.join(values[:-1])
917 conf.save()
921 conf.save()
918
922
919 return redirect('url_mix_experiment', id_exp=id_exp)
923 return redirect('url_mix_experiment', id_exp=id_exp)
920
924
921
925
922 def experiment_summary(request, id_exp):
926 def experiment_summary(request, id_exp):
923
927
924 experiment = get_object_or_404(Experiment, pk=id_exp)
928 experiment = get_object_or_404(Experiment, pk=id_exp)
925 configurations = Configuration.objects.filter(
929 configurations = Configuration.objects.filter(
926 experiment=experiment, type=0)
930 experiment=experiment, type=0)
927
931
928 kwargs = {}
932 kwargs = {}
929 kwargs['experiment_keys'] = ['radar_system',
933 kwargs['experiment_keys'] = ['radar_system',
930 'name', 'freq', 'start_time', 'end_time']
934 'name', 'freq', 'start_time', 'end_time']
931 kwargs['experiment'] = experiment
935 kwargs['experiment'] = experiment
932 kwargs['configurations'] = []
936 kwargs['configurations'] = []
933 kwargs['title'] = 'Experiment Summary'
937 kwargs['title'] = 'Experiment Summary'
934 kwargs['suptitle'] = 'Details'
938 kwargs['suptitle'] = 'Details'
935 kwargs['button'] = 'Verify Parameters'
939 kwargs['button'] = 'Verify Parameters'
936
940
937 c_vel = 3.0*(10**8) # m/s
941 c_vel = 3.0*(10**8) # m/s
938 ope_freq = experiment.freq*(10**6) # 1/s
942 ope_freq = experiment.freq*(10**6) # 1/s
939 radar_lambda = c_vel/ope_freq # m
943 radar_lambda = c_vel/ope_freq # m
940 kwargs['radar_lambda'] = radar_lambda
944 kwargs['radar_lambda'] = radar_lambda
941
945
942 ipp = None
946 ipp = None
943 nsa = 1
947 nsa = 1
944 code_id = 0
948 code_id = 0
945 tx_line = {}
949 tx_line = {}
946
950
947 for configuration in configurations.filter(device__device_type__name = 'pedestal'):
951 for configuration in configurations.filter(device__device_type__name = 'pedestal'):
948
952
949 if configuration.mix:
953 if configuration.mix:
950 continue
954 continue
951 conf = {'conf': configuration}
955 conf = {'conf': configuration}
952 conf['keys'] = []
956 conf['keys'] = []
953 conf['NTxs'] = configuration.ntx
957 conf['NTxs'] = configuration.ntx
954 conf['keys'].append('NTxs')
958 conf['keys'].append('NTxs')
955 ipp = configuration.ipp
959 ipp = configuration.ipp
956 conf['IPP'] = ipp
960 conf['IPP'] = ipp
957 conf['keys'].append('IPP')
961 conf['keys'].append('IPP')
958 lines = configuration.get_lines(line_type__name='tx')
962 lines = configuration.get_lines(line_type__name='tx')
959
963
960 for tx_line in lines:
964 for tx_line in lines:
961 tx_params = json.loads(tx_line.params)
965 tx_params = json.loads(tx_line.params)
962 conf[tx_line.get_name()] = '{} Km'.format(tx_params['pulse_width'])
966 conf[tx_line.get_name()] = '{} Km'.format(tx_params['pulse_width'])
963 conf['keys'].append(tx_line.get_name())
967 conf['keys'].append(tx_line.get_name())
964 delays = tx_params['delays']
968 delays = tx_params['delays']
965 if delays not in ('', '0'):
969 if delays not in ('', '0'):
966 n = len(delays.split(','))
970 n = len(delays.split(','))
967 taus = '{} Taus: {}'.format(n, delays)
971 taus = '{} Taus: {}'.format(n, delays)
968 else:
972 else:
969 taus = '-'
973 taus = '-'
970 conf['Taus ({})'.format(tx_line.get_name())] = taus
974 conf['Taus ({})'.format(tx_line.get_name())] = taus
971 conf['keys'].append('Taus ({})'.format(tx_line.get_name()))
975 conf['keys'].append('Taus ({})'.format(tx_line.get_name()))
972 for code_line in configuration.get_lines(line_type__name='codes'):
976 for code_line in configuration.get_lines(line_type__name='codes'):
973 code_params = json.loads(code_line.params)
977 code_params = json.loads(code_line.params)
974 code_id = code_params['code']
978 code_id = code_params['code']
975 if tx_line.pk == int(code_params['TX_ref']):
979 if tx_line.pk == int(code_params['TX_ref']):
976 conf['Code ({})'.format(tx_line.get_name())] = '{}:{}'.format(RCLineCode.objects.get(pk=code_params['code']),
980 conf['Code ({})'.format(tx_line.get_name())] = '{}:{}'.format(RCLineCode.objects.get(pk=code_params['code']),
977 '-'.join(code_params['codes']))
981 '-'.join(code_params['codes']))
978 conf['keys'].append('Code ({})'.format(tx_line.get_name()))
982 conf['keys'].append('Code ({})'.format(tx_line.get_name()))
979
983
980 for windows_line in configuration.get_lines(line_type__name='windows'):
984 for windows_line in configuration.get_lines(line_type__name='windows'):
981 win_params = json.loads(windows_line.params)
985 win_params = json.loads(windows_line.params)
982 if tx_line.pk == int(win_params['TX_ref']):
986 if tx_line.pk == int(win_params['TX_ref']):
983 windows = ''
987 windows = ''
984 nsa = win_params['params'][0]['number_of_samples']
988 nsa = win_params['params'][0]['number_of_samples']
985 for i, params in enumerate(win_params['params']):
989 for i, params in enumerate(win_params['params']):
986 windows += 'W{}: Ho={first_height} km DH={resolution} km NSA={number_of_samples}<br>'.format(
990 windows += 'W{}: Ho={first_height} km DH={resolution} km NSA={number_of_samples}<br>'.format(
987 i, **params)
991 i, **params)
988 conf['Window'] = mark_safe(windows)
992 conf['Window'] = mark_safe(windows)
989 conf['keys'].append('Window')
993 conf['keys'].append('Window')
990
994
991 kwargs['configurations'].append(conf)
995 kwargs['configurations'].append(conf)
992
996
993 for configuration in configurations.filter(device__device_type__name = 'jars'):
997 for configuration in configurations.filter(device__device_type__name = 'jars'):
994
998
995 conf = {'conf': configuration}
999 conf = {'conf': configuration}
996 conf['keys'] = []
1000 conf['keys'] = []
997 conf['Type of Data'] = EXPERIMENT_TYPE[configuration.exp_type][1]
1001 conf['Type of Data'] = EXPERIMENT_TYPE[configuration.exp_type][1]
998 conf['keys'].append('Type of Data')
1002 conf['keys'].append('Type of Data')
999 channels_number = configuration.channels_number
1003 channels_number = configuration.channels_number
1000 exp_type = configuration.exp_type
1004 exp_type = configuration.exp_type
1001 fftpoints = configuration.fftpoints
1005 fftpoints = configuration.fftpoints
1002 filter_parms = json.loads(configuration.filter_parms)
1006 filter_parms = json.loads(configuration.filter_parms)
1003 spectral_number = configuration.spectral_number
1007 spectral_number = configuration.spectral_number
1004 acq_profiles = configuration.acq_profiles
1008 acq_profiles = configuration.acq_profiles
1005 cohe_integr = configuration.cohe_integr
1009 cohe_integr = configuration.cohe_integr
1006 profiles_block = configuration.profiles_block
1010 profiles_block = configuration.profiles_block
1007
1011
1008 conf['Num of Profiles'] = acq_profiles
1012 conf['Num of Profiles'] = acq_profiles
1009 conf['keys'].append('Num of Profiles')
1013 conf['keys'].append('Num of Profiles')
1010
1014
1011 conf['Prof per Block'] = profiles_block
1015 conf['Prof per Block'] = profiles_block
1012 conf['keys'].append('Prof per Block')
1016 conf['keys'].append('Prof per Block')
1013
1017
1014 conf['Blocks per File'] = configuration.raw_data_blocks
1018 conf['Blocks per File'] = configuration.raw_data_blocks
1015 conf['keys'].append('Blocks per File')
1019 conf['keys'].append('Blocks per File')
1016
1020
1017 if exp_type == 0: # Short
1021 if exp_type == 0: # Short
1018 bytes_ = 2
1022 bytes_ = 2
1019 b = nsa*2*bytes_*channels_number
1023 b = nsa*2*bytes_*channels_number
1020 else: # Float
1024 else: # Float
1021 bytes_ = 4
1025 bytes_ = 4
1022 channels = channels_number + spectral_number
1026 channels = channels_number + spectral_number
1023 b = nsa*2*bytes_*fftpoints*channels
1027 b = nsa*2*bytes_*fftpoints*channels
1024
1028
1025 codes_num = 7
1029 codes_num = 7
1026 if code_id == 2:
1030 if code_id == 2:
1027 codes_num = 7
1031 codes_num = 7
1028 elif code_id == 12:
1032 elif code_id == 12:
1029 codes_num = 15
1033 codes_num = 15
1030
1034
1031 #Jars filter values:
1035 #Jars filter values:
1032
1036
1033 clock = float(filter_parms['clock'])
1037 clock = float(filter_parms['clock'])
1034 filter_2 = int(filter_parms['cic_2'])
1038 filter_2 = int(filter_parms['cic_2'])
1035 filter_5 = int(filter_parms['cic_5'])
1039 filter_5 = int(filter_parms['cic_5'])
1036 filter_fir = int(filter_parms['fir'])
1040 filter_fir = int(filter_parms['fir'])
1037 Fs_MHz = clock/(filter_2*filter_5*filter_fir)
1041 Fs_MHz = clock/(filter_2*filter_5*filter_fir)
1038
1042
1039 #Jars values:
1043 #Jars values:
1040 if ipp is not None:
1044 if ipp is not None:
1041 IPP_units = ipp/0.15*Fs_MHz
1045 IPP_units = ipp/0.15*Fs_MHz
1042 IPP_us = IPP_units / Fs_MHz
1046 IPP_us = IPP_units / Fs_MHz
1043 IPP_s = IPP_units / (Fs_MHz * (10**6))
1047 IPP_s = IPP_units / (Fs_MHz * (10**6))
1044 Ts = 1/(Fs_MHz*(10**6))
1048 Ts = 1/(Fs_MHz*(10**6))
1045
1049
1046 Va = radar_lambda/(4*Ts*cohe_integr)
1050 Va = radar_lambda/(4*Ts*cohe_integr)
1047 rate_bh = ((nsa-codes_num)*channels_number*2 *
1051 rate_bh = ((nsa-codes_num)*channels_number*2 *
1048 bytes_/IPP_us)*(36*(10**8)/cohe_integr)
1052 bytes_/IPP_us)*(36*(10**8)/cohe_integr)
1049 rate_gh = rate_bh/(1024*1024*1024)
1053 rate_gh = rate_bh/(1024*1024*1024)
1050
1054
1051 conf['Time per Block'] = IPP_s * profiles_block * cohe_integr
1055 conf['Time per Block'] = IPP_s * profiles_block * cohe_integr
1052 conf['keys'].append('Time per Block')
1056 conf['keys'].append('Time per Block')
1053 conf['Acq time'] = IPP_s * acq_profiles
1057 conf['Acq time'] = IPP_s * acq_profiles
1054 conf['keys'].append('Acq time')
1058 conf['keys'].append('Acq time')
1055 conf['Data rate'] = str(rate_gh)+" (GB/h)"
1059 conf['Data rate'] = str(rate_gh)+" (GB/h)"
1056 conf['keys'].append('Data rate')
1060 conf['keys'].append('Data rate')
1057 conf['Va (m/s)'] = Va
1061 conf['Va (m/s)'] = Va
1058 conf['keys'].append('Va (m/s)')
1062 conf['keys'].append('Va (m/s)')
1059 conf['Vrange (m/s)'] = 3/(2*IPP_s*cohe_integr)
1063 conf['Vrange (m/s)'] = 3/(2*IPP_s*cohe_integr)
1060 conf['keys'].append('Vrange (m/s)')
1064 conf['keys'].append('Vrange (m/s)')
1061
1065
1062 kwargs['configurations'].append(conf)
1066 kwargs['configurations'].append(conf)
1063 kwargs['menu_experiments'] = 'active'
1067 kwargs['menu_experiments'] = 'active'
1064
1068
1065 ###### SIDEBAR ######
1069 ###### SIDEBAR ######
1066 kwargs.update(sidebar(experiment=experiment))
1070 kwargs.update(sidebar(experiment=experiment))
1067
1071
1068 return render(request, 'experiment_summary.html', kwargs)
1072 return render(request, 'experiment_summary.html', kwargs)
1069
1073
1070
1074
1071 @login_required
1075 @login_required
1072 def experiment_verify(request, id_exp):
1076 def experiment_verify(request, id_exp):
1073
1077
1074 experiment = get_object_or_404(Experiment, pk=id_exp)
1078 experiment = get_object_or_404(Experiment, pk=id_exp)
1075 experiment_data = experiment.parms_to_dict()
1079 experiment_data = experiment.parms_to_dict()
1076 configurations = Configuration.objects.filter(
1080 configurations = Configuration.objects.filter(
1077 experiment=experiment, type=0)
1081 experiment=experiment, type=0)
1078
1082
1079 kwargs = {}
1083 kwargs = {}
1080
1084
1081 kwargs['experiment_keys'] = ['template',
1085 kwargs['experiment_keys'] = ['template',
1082 'radar_system', 'name', 'start_time', 'end_time']
1086 'radar_system', 'name', 'start_time', 'end_time']
1083 kwargs['experiment'] = experiment
1087 kwargs['experiment'] = experiment
1084
1088
1085 kwargs['configuration_keys'] = ['name', 'device__ip_address',
1089 kwargs['configuration_keys'] = ['name', 'device__ip_address',
1086 'device__port_address', 'device__status']
1090 'device__port_address', 'device__status']
1087 kwargs['configurations'] = configurations
1091 kwargs['configurations'] = configurations
1088 kwargs['experiment_data'] = experiment_data
1092 kwargs['experiment_data'] = experiment_data
1089
1093
1090 kwargs['title'] = 'Verify Experiment'
1094 kwargs['title'] = 'Verify Experiment'
1091 kwargs['suptitle'] = 'Parameters'
1095 kwargs['suptitle'] = 'Parameters'
1092
1096
1093 kwargs['button'] = 'Update'
1097 kwargs['button'] = 'Update'
1094
1098
1095 jars_conf = False
1099 jars_conf = False
1096 rc_conf = False
1100 rc_conf = False
1097 dds_conf = False
1101 dds_conf = False
1098
1102
1099 for configuration in configurations:
1103 for configuration in configurations:
1100 #-------------------- JARS -----------------------:
1104 #-------------------- JARS -----------------------:
1101 if configuration.device.device_type.name == 'jars':
1105 if configuration.device.device_type.name == 'jars':
1102 jars_conf = True
1106 jars_conf = True
1103 jars = configuration
1107 jars = configuration
1104 kwargs['jars_conf'] = jars_conf
1108 kwargs['jars_conf'] = jars_conf
1105 filter_parms = json.loads(jars.filter_parms)
1109 filter_parms = json.loads(jars.filter_parms)
1106 kwargs['filter_parms'] = filter_parms
1110 kwargs['filter_parms'] = filter_parms
1107 #--Sampling Frequency
1111 #--Sampling Frequency
1108 clock = filter_parms['clock']
1112 clock = filter_parms['clock']
1109 filter_2 = filter_parms['cic_2']
1113 filter_2 = filter_parms['cic_2']
1110 filter_5 = filter_parms['cic_5']
1114 filter_5 = filter_parms['cic_5']
1111 filter_fir = filter_parms['fir']
1115 filter_fir = filter_parms['fir']
1112 samp_freq_jars = clock/filter_2/filter_5/filter_fir
1116 samp_freq_jars = clock/filter_2/filter_5/filter_fir
1113
1117
1114 kwargs['samp_freq_jars'] = samp_freq_jars
1118 kwargs['samp_freq_jars'] = samp_freq_jars
1115 kwargs['jars'] = configuration
1119 kwargs['jars'] = configuration
1116
1120
1117 #--------------------- RC ----------------------:
1121 #--------------------- RC ----------------------:
1118 if configuration.device.device_type.name == 'pedestal' and not configuration.mix:
1122 if configuration.device.device_type.name == 'pedestal' and not configuration.mix:
1119 rc_conf = True
1123 rc_conf = True
1120 rc = configuration
1124 rc = configuration
1121
1125
1122 rc_parms = configuration.parms_to_dict()
1126 rc_parms = configuration.parms_to_dict()
1123
1127
1124 win_lines = rc.get_lines(line_type__name='windows')
1128 win_lines = rc.get_lines(line_type__name='windows')
1125 if win_lines:
1129 if win_lines:
1126 dh = json.loads(win_lines[0].params)['params'][0]['resolution']
1130 dh = json.loads(win_lines[0].params)['params'][0]['resolution']
1127 #--Sampling Frequency
1131 #--Sampling Frequency
1128 samp_freq_rc = 0.15/dh
1132 samp_freq_rc = 0.15/dh
1129 kwargs['samp_freq_rc'] = samp_freq_rc
1133 kwargs['samp_freq_rc'] = samp_freq_rc
1130
1134
1131 kwargs['rc_conf'] = rc_conf
1135 kwargs['rc_conf'] = rc_conf
1132 kwargs['rc'] = configuration
1136 kwargs['rc'] = configuration
1133
1137
1134 #-------------------- DDS ----------------------:
1138 #-------------------- DDS ----------------------:
1135 if configuration.device.device_type.name == 'dds':
1139 if configuration.device.device_type.name == 'dds':
1136 dds_conf = True
1140 dds_conf = True
1137 dds = configuration
1141 dds = configuration
1138 dds_parms = configuration.parms_to_dict()
1142 dds_parms = configuration.parms_to_dict()
1139
1143
1140 kwargs['dds_conf'] = dds_conf
1144 kwargs['dds_conf'] = dds_conf
1141 kwargs['dds'] = configuration
1145 kwargs['dds'] = configuration
1142
1146
1143 #------------Validation------------:
1147 #------------Validation------------:
1144 #Clock
1148 #Clock
1145 if dds_conf and rc_conf and jars_conf:
1149 if dds_conf and rc_conf and jars_conf:
1146 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']):
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 messages.warning(request, "Devices don't have the same clock.")
1151 messages.warning(request, "Devices don't have the same clock.")
1148 elif rc_conf and jars_conf:
1152 elif rc_conf and jars_conf:
1149 if float(filter_parms['clock']) != float(rc_parms['configurations']['byId'][str(rc.pk)]['clock_in']):
1153 if float(filter_parms['clock']) != float(rc_parms['configurations']['byId'][str(rc.pk)]['clock_in']):
1150 messages.warning(request, "Devices don't have the same clock.")
1154 messages.warning(request, "Devices don't have the same clock.")
1151 elif rc_conf and dds_conf:
1155 elif rc_conf and dds_conf:
1152 if float(rc_parms['configurations']['byId'][str(rc.pk)]['clock_in']) != float(dds_parms['configurations']['byId'][str(dds.pk)]['clock']):
1156 if float(rc_parms['configurations']['byId'][str(rc.pk)]['clock_in']) != float(dds_parms['configurations']['byId'][str(dds.pk)]['clock']):
1153 messages.warning(request, "Devices don't have the same clock.")
1157 messages.warning(request, "Devices don't have the same clock.")
1154 if float(samp_freq_rc) != float(dds_parms['configurations']['byId'][str(dds.pk)]['frequencyA']):
1158 if float(samp_freq_rc) != float(dds_parms['configurations']['byId'][str(dds.pk)]['frequencyA']):
1155 messages.warning(
1159 messages.warning(
1156 request, "Devices don't have the same Frequency A.")
1160 request, "Devices don't have the same Frequency A.")
1157
1161
1158 #------------POST METHOD------------:
1162 #------------POST METHOD------------:
1159 if request.method == 'POST':
1163 if request.method == 'POST':
1160 if request.POST['suggest_clock']:
1164 if request.POST['suggest_clock']:
1161 try:
1165 try:
1162 suggest_clock = float(request.POST['suggest_clock'])
1166 suggest_clock = float(request.POST['suggest_clock'])
1163 except:
1167 except:
1164 messages.warning(request, "Invalid value in CLOCK IN.")
1168 messages.warning(request, "Invalid value in CLOCK IN.")
1165 return redirect('url_verify_experiment', id_exp=experiment.id)
1169 return redirect('url_verify_experiment', id_exp=experiment.id)
1166 else:
1170 else:
1167 suggest_clock = ""
1171 suggest_clock = ""
1168 if suggest_clock:
1172 if suggest_clock:
1169 if rc_conf:
1173 if rc_conf:
1170 rc.clock_in = suggest_clock
1174 rc.clock_in = suggest_clock
1171 rc.save()
1175 rc.save()
1172 if jars_conf:
1176 if jars_conf:
1173 filter_parms = jars.filter_parms
1177 filter_parms = jars.filter_parms
1174 filter_parms = ast.literal_eval(filter_parms)
1178 filter_parms = ast.literal_eval(filter_parms)
1175 filter_parms['clock'] = suggest_clock
1179 filter_parms['clock'] = suggest_clock
1176 jars.filter_parms = json.dumps(filter_parms)
1180 jars.filter_parms = json.dumps(filter_parms)
1177 jars.save()
1181 jars.save()
1178 kwargs['filter_parms'] = filter_parms
1182 kwargs['filter_parms'] = filter_parms
1179 if dds_conf:
1183 if dds_conf:
1180 dds.clock = suggest_clock
1184 dds.clock = suggest_clock
1181 dds.save()
1185 dds.save()
1182
1186
1183 if request.POST['suggest_frequencyA']:
1187 if request.POST['suggest_frequencyA']:
1184 try:
1188 try:
1185 suggest_frequencyA = float(request.POST['suggest_frequencyA'])
1189 suggest_frequencyA = float(request.POST['suggest_frequencyA'])
1186 except:
1190 except:
1187 messages.warning(request, "Invalid value in FREQUENCY A.")
1191 messages.warning(request, "Invalid value in FREQUENCY A.")
1188 return redirect('url_verify_experiment', id_exp=experiment.id)
1192 return redirect('url_verify_experiment', id_exp=experiment.id)
1189 else:
1193 else:
1190 suggest_frequencyA = ""
1194 suggest_frequencyA = ""
1191 if suggest_frequencyA:
1195 if suggest_frequencyA:
1192 if jars_conf:
1196 if jars_conf:
1193 filter_parms = jars.filter_parms
1197 filter_parms = jars.filter_parms
1194 filter_parms = ast.literal_eval(filter_parms)
1198 filter_parms = ast.literal_eval(filter_parms)
1195 filter_parms['fch'] = suggest_frequencyA
1199 filter_parms['fch'] = suggest_frequencyA
1196 jars.filter_parms = json.dumps(filter_parms)
1200 jars.filter_parms = json.dumps(filter_parms)
1197 jars.save()
1201 jars.save()
1198 kwargs['filter_parms'] = filter_parms
1202 kwargs['filter_parms'] = filter_parms
1199 if dds_conf:
1203 if dds_conf:
1200 dds.frequencyA_Mhz = request.POST['suggest_frequencyA']
1204 dds.frequencyA_Mhz = request.POST['suggest_frequencyA']
1201 dds.save()
1205 dds.save()
1202
1206
1203 kwargs['menu_experiments'] = 'active'
1207 kwargs['menu_experiments'] = 'active'
1204 kwargs.update(sidebar(experiment=experiment))
1208 kwargs.update(sidebar(experiment=experiment))
1205 return render(request, 'experiment_verify.html', kwargs)
1209 return render(request, 'experiment_verify.html', kwargs)
1206
1210
1207
1211
1208 def parse_mix_result(s):
1212 def parse_mix_result(s):
1209
1213
1210 values = s.split('-')
1214 values = s.split('-')
1211 html = 'EXP MOD OPE DELAY MASK\r\n'
1215 html = 'EXP MOD OPE DELAY MASK\r\n'
1212
1216
1213 if not values or values[0] in ('', ' '):
1217 if not values or values[0] in ('', ' '):
1214 return mark_safe(html)
1218 return mark_safe(html)
1215
1219
1216 for i, value in enumerate(values):
1220 for i, value in enumerate(values):
1217 if not value:
1221 if not value:
1218 continue
1222 continue
1219 pk, mode, operation, delay, mask = value.split('|')
1223 pk, mode, operation, delay, mask = value.split('|')
1220 conf = PedestalConfiguration.objects.get(pk=pk)
1224 conf = PedestalConfiguration.objects.get(pk=pk)
1221 if i == 0:
1225 if i == 0:
1222 html += '{:20.18}{:3}{:4}{:9}km{:>6}\r\n'.format(
1226 html += '{:20.18}{:3}{:4}{:9}km{:>6}\r\n'.format(
1223 conf.name,
1227 conf.name,
1224 mode,
1228 mode,
1225 ' ',
1229 ' ',
1226 delay,
1230 delay,
1227 mask)
1231 mask)
1228 else:
1232 else:
1229 html += '{:20.18}{:3}{:4}{:9}km{:>6}\r\n'.format(
1233 html += '{:20.18}{:3}{:4}{:9}km{:>6}\r\n'.format(
1230 conf.name,
1234 conf.name,
1231 mode,
1235 mode,
1232 operation,
1236 operation,
1233 delay,
1237 delay,
1234 mask)
1238 mask)
1235
1239
1236 return mark_safe(html)
1240 return mark_safe(html)
1237
1241
1238
1242
1239 def parse_mask(l):
1243 def parse_mask(l):
1240
1244
1241 values = []
1245 values = []
1242
1246
1243 for x in range(16):
1247 for x in range(16):
1244 if '{}'.format(x) in l:
1248 if '{}'.format(x) in l:
1245 values.append(1)
1249 values.append(1)
1246 else:
1250 else:
1247 values.append(0)
1251 values.append(0)
1248
1252
1249 values.reverse()
1253 values.reverse()
1250
1254
1251 return int(''.join([str(x) for x in values]), 2)
1255 return int(''.join([str(x) for x in values]), 2)
1252
1256
1253
1257
1254 def dev_confs(request):
1258 def dev_confs(request):
1255
1259
1256 page = request.GET.get('page')
1260 page = request.GET.get('page')
1257 order = ('-programmed_date', )
1261 order = ('-programmed_date', )
1258 filters = request.GET.copy()
1262 filters = request.GET.copy()
1259 if 'my configurations' in filters:
1263 if 'my configurations' in filters:
1260 filters.pop('my configurations', None)
1264 filters.pop('my configurations', None)
1261 filters['mine'] = request.user.id
1265 filters['mine'] = request.user.id
1262 kwargs = get_paginator(Configuration, page, order, filters)
1266 kwargs = get_paginator(Configuration, page, order, filters)
1263 fields = ['tags', 'template', 'historical']
1267 fields = ['tags', 'template', 'historical']
1264 if request.user.is_authenticated:
1268 if request.user.is_authenticated:
1265 fields.append('my configurations')
1269 fields.append('my configurations')
1266 form = FilterForm(initial=request.GET, extra_fields=fields)
1270 form = FilterForm(initial=request.GET, extra_fields=fields)
1267 kwargs['keys'] = ['name', 'device', 'experiment',
1271 kwargs['keys'] = ['name', 'device', 'experiment',
1268 'type', 'programmed_date', 'actions']
1272 'type', 'programmed_date', 'actions']
1269 kwargs['title'] = 'Configuration'
1273 kwargs['title'] = 'Configuration'
1270 kwargs['suptitle'] = 'List'
1274 kwargs['suptitle'] = 'List'
1271 kwargs['no_sidebar'] = True
1275 kwargs['no_sidebar'] = True
1272 kwargs['form'] = form
1276 kwargs['form'] = form
1273 kwargs['add_url'] = reverse('url_add_dev_conf', args=[0])
1277 kwargs['add_url'] = reverse('url_add_dev_conf', args=[0])
1274 filters = request.GET.copy()
1278 filters = request.GET.copy()
1275 filters.pop('page', None)
1279 filters.pop('page', None)
1276 kwargs['q'] = urlencode(filters)
1280 kwargs['q'] = urlencode(filters)
1277 kwargs['menu_configurations'] = 'active'
1281 kwargs['menu_configurations'] = 'active'
1278
1282
1279 return render(request, 'base_list.html', kwargs)
1283 return render(request, 'base_list.html', kwargs)
1280
1284
1281
1285
1282 def dev_conf(request, id_conf):
1286 def dev_conf(request, id_conf):
1283
1287
1284 conf = get_object_or_404(Configuration, pk=id_conf)
1288 conf = get_object_or_404(Configuration, pk=id_conf)
1285
1289
1286 return redirect(conf.get_absolute_url())
1290 return redirect(conf.get_absolute_url())
1287
1291
1288
1292
1289 @login_required
1293 @login_required
1290 def dev_conf_new(request, id_exp=0, id_dev=0):
1294 def dev_conf_new(request, id_exp=0, id_dev=0):
1291
1295
1292 if not is_developer(request.user):
1296 if not is_developer(request.user):
1293 messages.error(
1297 messages.error(
1294 request, 'Developer required, to create new configurations')
1298 request, 'Developer required, to create new configurations')
1295 return redirect('index')
1299 return redirect('index')
1296
1300
1297 initial = {}
1301 initial = {}
1298 kwargs = {}
1302 kwargs = {}
1299
1303
1300 if id_exp != 0:
1304 if id_exp != 0:
1301 initial['experiment'] = id_exp
1305 initial['experiment'] = id_exp
1302
1306
1303 if id_dev != 0:
1307 if id_dev != 0:
1304 initial['device'] = id_dev
1308 initial['device'] = id_dev
1305
1309
1306 if request.method == 'GET':
1310 if request.method == 'GET':
1307
1311
1308 if id_dev:
1312 if id_dev:
1309 kwargs['button'] = 'Create'
1313 kwargs['button'] = 'Create'
1310 device = Device.objects.get(pk=id_dev)
1314 device = Device.objects.get(pk=id_dev)
1311 DevConfForm = CONF_FORMS[device.device_type.name]
1315 DevConfForm = CONF_FORMS[device.device_type.name]
1312 initial['name'] = request.GET['name']
1316 initial['name'] = request.GET['name']
1313 form = DevConfForm(initial=initial)
1317 form = DevConfForm(initial=initial)
1314 else:
1318 else:
1315 if 'template' in request.GET:
1319 if 'template' in request.GET:
1316 if request.GET['template'] == '0':
1320 if request.GET['template'] == '0':
1317 choices = [(conf.pk, '{}'.format(conf))
1321 choices = [(conf.pk, '{}'.format(conf))
1318 for conf in Configuration.objects.filter(template=True)]
1322 for conf in Configuration.objects.filter(template=True)]
1319 form = NewForm(initial={'create_from': 2},
1323 form = NewForm(initial={'create_from': 2},
1320 template_choices=choices)
1324 template_choices=choices)
1321 else:
1325 else:
1322 kwargs['button'] = 'Create'
1326 kwargs['button'] = 'Create'
1323 conf = Configuration.objects.get(
1327 conf = Configuration.objects.get(
1324 pk=request.GET['template'])
1328 pk=request.GET['template'])
1325 id_dev = conf.device.pk
1329 id_dev = conf.device.pk
1326 DevConfForm = CONF_FORMS[conf.device.device_type.name]
1330 DevConfForm = CONF_FORMS[conf.device.device_type.name]
1327 form = DevConfForm(instance=conf,
1331 form = DevConfForm(instance=conf,
1328 initial={'name': '{}_{:%y%m%d}'.format(conf.name, datetime.now()),
1332 initial={'name': '{}_{:%y%m%d}'.format(conf.name, datetime.now()),
1329 'template': False,
1333 'template': False,
1330 'experiment': id_exp})
1334 'experiment': id_exp})
1331 elif 'blank' in request.GET:
1335 elif 'blank' in request.GET:
1332 kwargs['button'] = 'Create'
1336 kwargs['button'] = 'Create'
1333 form = ConfigurationForm(initial=initial)
1337 form = ConfigurationForm(initial=initial)
1334 else:
1338 else:
1335 form = NewForm()
1339 form = NewForm()
1336
1340
1337 if request.method == 'POST':
1341 if request.method == 'POST':
1338
1342
1339 device = Device.objects.get(pk=request.POST['device'])
1343 device = Device.objects.get(pk=request.POST['device'])
1340 DevConfForm = CONF_FORMS[device.device_type.name]
1344 DevConfForm = CONF_FORMS[device.device_type.name]
1341
1345
1342 form = DevConfForm(request.POST)
1346 form = DevConfForm(request.POST)
1343 kwargs['button'] = 'Create'
1347 kwargs['button'] = 'Create'
1344 if form.is_valid():
1348 if form.is_valid():
1345 conf = form.save(commit=False)
1349 conf = form.save(commit=False)
1346 conf.author = request.user
1350 conf.author = request.user
1347 conf.save()
1351 conf.save()
1348 return redirect('url_dev_conf', id_conf=conf.pk)
1352 return redirect('url_dev_conf', id_conf=conf.pk)
1349
1353
1350 kwargs['id_exp'] = id_exp
1354 kwargs['id_exp'] = id_exp
1351 kwargs['form'] = form
1355 kwargs['form'] = form
1352 kwargs['title'] = 'Configuration'
1356 kwargs['title'] = 'Configuration'
1353 kwargs['suptitle'] = 'New'
1357 kwargs['suptitle'] = 'New'
1354 kwargs['menu_configurations'] = 'active'
1358 kwargs['menu_configurations'] = 'active'
1355
1359
1356 if id_dev != 0:
1360 if id_dev != 0:
1357 device = Device.objects.get(pk=id_dev)
1361 device = Device.objects.get(pk=id_dev)
1358 kwargs['device'] = device.device_type.name
1362 kwargs['device'] = device.device_type.name
1359 return render(request, 'dev_conf_edit.html', kwargs)
1363 return render(request, 'dev_conf_edit.html', kwargs)
1360
1364
1361
1365
1362 @login_required
1366 @login_required
1363 def dev_conf_edit(request, id_conf):
1367 def dev_conf_edit(request, id_conf):
1364
1368
1365 conf = get_object_or_404(Configuration, pk=id_conf)
1369 conf = get_object_or_404(Configuration, pk=id_conf)
1366
1370
1367 DevConfForm = CONF_FORMS[conf.device.device_type.name]
1371 DevConfForm = CONF_FORMS[conf.device.device_type.name]
1368
1372
1369 if request.method == 'GET':
1373 if request.method == 'GET':
1370 form = DevConfForm(instance=conf)
1374 form = DevConfForm(instance=conf)
1371
1375
1372 if request.method == 'POST':
1376 if request.method == 'POST':
1373 form = DevConfForm(request.POST, instance=conf)
1377 form = DevConfForm(request.POST, instance=conf)
1374
1378
1375 if form.is_valid():
1379 if form.is_valid():
1376 form.save()
1380 form.save()
1377 return redirect('url_dev_conf', id_conf=id_conf)
1381 return redirect('url_dev_conf', id_conf=id_conf)
1378
1382
1379 kwargs = {}
1383 kwargs = {}
1380 kwargs['form'] = form
1384 kwargs['form'] = form
1381 kwargs['title'] = 'Device Configuration'
1385 kwargs['title'] = 'Device Configuration'
1382 kwargs['suptitle'] = 'Edit'
1386 kwargs['suptitle'] = 'Edit'
1383 kwargs['button'] = 'Update'
1387 kwargs['button'] = 'Update'
1384 kwargs['menu_configurations'] = 'active'
1388 kwargs['menu_configurations'] = 'active'
1385
1389
1386 ###### SIDEBAR ######
1390 ###### SIDEBAR ######
1387 kwargs.update(sidebar(conf=conf))
1391 kwargs.update(sidebar(conf=conf))
1388
1392
1389 return render(request, '%s_conf_edit.html' % conf.device.device_type.name, kwargs)
1393 return render(request, '%s_conf_edit.html' % conf.device.device_type.name, kwargs)
1390
1394
1391
1395
1392 @login_required
1396 @login_required
1393 def dev_conf_start(request, id_conf):
1397 def dev_conf_start(request, id_conf):
1394
1398
1395 conf = get_object_or_404(Configuration, pk=id_conf)
1399 conf = get_object_or_404(Configuration, pk=id_conf)
1396
1400
1397 if conf.start_device():
1401 if conf.start_device():
1398 messages.success(request, conf.message)
1402 messages.success(request, conf.message)
1399 else:
1403 else:
1400 messages.error(request, conf.message)
1404 messages.error(request, conf.message)
1401
1405
1402 #conf.status_device()
1406 #conf.status_device()
1403
1407
1404 return redirect(conf.get_absolute_url())
1408 return redirect(conf.get_absolute_url())
1405
1409
1406
1410
1407 @login_required
1411 @login_required
1408 def dev_conf_stop(request, id_conf):
1412 def dev_conf_stop(request, id_conf):
1409
1413
1410 conf = get_object_or_404(Configuration, pk=id_conf)
1414 conf = get_object_or_404(Configuration, pk=id_conf)
1411
1415
1412 if conf.stop_device():
1416 if conf.stop_device():
1413 messages.success(request, conf.message)
1417 messages.success(request, conf.message)
1414 else:
1418 else:
1415 messages.error(request, conf.message)
1419 messages.error(request, conf.message)
1416
1420
1417 #conf.status_device()
1421 #conf.status_device()
1418
1422
1419 return redirect(conf.get_absolute_url())
1423 return redirect(conf.get_absolute_url())
1420
1424
1421
1425
1422 @login_required
1426 @login_required
1423 def dev_conf_status(request, id_conf):
1427 def dev_conf_status(request, id_conf):
1424
1428
1425 conf = get_object_or_404(Configuration, pk=id_conf)
1429 conf = get_object_or_404(Configuration, pk=id_conf)
1426
1430
1427 conf_active = Configuration.objects.filter(pk=conf.device.conf_active).first()
1431 conf_active = Configuration.objects.filter(pk=conf.device.conf_active).first()
1428 if conf_active!=conf:
1432 if conf_active!=conf:
1429 url = '#' if conf_active is None else conf_active.get_absolute_url()
1433 url = '#' if conf_active is None else conf_active.get_absolute_url()
1430 label = 'None' if conf_active is None else conf_active.label
1434 label = 'None' if conf_active is None else conf_active.label
1431 messages.warning(
1435 messages.warning(
1432 request,
1436 request,
1433 mark_safe('The current configuration has not been written to device, the active configuration is <a href="{}">{}</a>'.format(
1437 mark_safe('The current configuration has not been written to device, the active configuration is <a href="{}">{}</a>'.format(
1434 url,
1438 url,
1435 label
1439 label
1436 ))
1440 ))
1437 )
1441 )
1438
1442
1439 return redirect(conf.get_absolute_url())
1443 return redirect(conf.get_absolute_url())
1440
1444
1441 if conf.status_device():
1445 if conf.status_device():
1442 messages.success(request, conf.message)
1446 messages.success(request, conf.message)
1443 else:
1447 else:
1444 messages.error(request, conf.message)
1448 messages.error(request, conf.message)
1445
1449
1446 return redirect(conf.get_absolute_url())
1450 return redirect(conf.get_absolute_url())
1447
1451
1448
1452
1449 @login_required
1453 @login_required
1450 def dev_conf_reset(request, id_conf):
1454 def dev_conf_reset(request, id_conf):
1451
1455
1452 conf = get_object_or_404(Configuration, pk=id_conf)
1456 conf = get_object_or_404(Configuration, pk=id_conf)
1453
1457
1454 if conf.reset_device():
1458 if conf.reset_device():
1455 messages.success(request, conf.message)
1459 messages.success(request, conf.message)
1456 else:
1460 else:
1457 messages.error(request, conf.message)
1461 messages.error(request, conf.message)
1458
1462
1459 return redirect(conf.get_absolute_url())
1463 return redirect(conf.get_absolute_url())
1460
1464
1461
1465
1462 @login_required
1466 @login_required
1463 def dev_conf_write(request, id_conf):
1467 def dev_conf_write(request, id_conf):
1464
1468
1465 conf = get_object_or_404(Configuration, pk=id_conf)
1469 conf = get_object_or_404(Configuration, pk=id_conf)
1466
1470
1467 if request.method == 'POST':
1471 if request.method == 'POST':
1468 if conf.write_device():
1472 if conf.write_device():
1469 conf.device.conf_active = conf.pk
1473 conf.device.conf_active = conf.pk
1470 conf.device.save()
1474 conf.device.save()
1471 messages.success(request, conf.message)
1475 messages.success(request, conf.message)
1472 if has_been_modified(conf):
1476 if has_been_modified(conf):
1473 conf.clone(type=1, template=False)
1477 conf.clone(type=1, template=False)
1474 else:
1478 else:
1475 messages.error(request, conf.message)
1479 messages.error(request, conf.message)
1476
1480
1477 return redirect(get_object_or_404(Configuration, pk=id_conf).get_absolute_url())
1481 return redirect(get_object_or_404(Configuration, pk=id_conf).get_absolute_url())
1478
1482
1479 kwargs = {
1483 kwargs = {
1480 'title': 'Write Configuration',
1484 'title': 'Write Configuration',
1481 'suptitle': conf.label,
1485 'suptitle': conf.label,
1482 'message': 'Are you sure yo want to write this {} configuration?'.format(conf.device),
1486 'message': 'Are you sure yo want to write this {} configuration?'.format(conf.device),
1483 'delete': False
1487 'delete': False
1484 }
1488 }
1485 kwargs['menu_configurations'] = 'active'
1489 kwargs['menu_configurations'] = 'active'
1486
1490
1487 return render(request, 'confirm.html', kwargs)
1491 return render(request, 'confirm.html', kwargs)
1488
1492
1489
1493
1490 @login_required
1494 @login_required
1491 def dev_conf_read(request, id_conf):
1495 def dev_conf_read(request, id_conf):
1492
1496
1493 conf = get_object_or_404(Configuration, pk=id_conf)
1497 conf = get_object_or_404(Configuration, pk=id_conf)
1494
1498
1495 DevConfForm = CONF_FORMS[conf.device.device_type.name]
1499 DevConfForm = CONF_FORMS[conf.device.device_type.name]
1496
1500
1497 if request.method == 'GET':
1501 if request.method == 'GET':
1498
1499 parms = conf.read_device()
1502 parms = conf.read_device()
1500 #conf.status_device()
1503 #conf.status_device()
1501
1504
1502 if not parms:
1505 if not parms:
1503 messages.error(request, conf.message)
1506 messages.error(request, conf.message)
1504 return redirect(conf.get_absolute_url())
1507 return redirect(conf.get_absolute_url())
1505
1508
1506 form = DevConfForm(initial=parms, instance=conf)
1509 form = DevConfForm(initial=parms, instance=conf)
1507
1510
1508 if request.method == 'POST':
1511 if request.method == 'POST':
1509 form = DevConfForm(request.POST, instance=conf)
1512 form = DevConfForm(request.POST, instance=conf)
1510
1513
1511 if form.is_valid():
1514 if form.is_valid():
1512 form.save()
1515 form.save()
1513 return redirect(conf.get_absolute_url())
1516 return redirect(conf.get_absolute_url())
1514
1517
1515 messages.error(request, "Parameters could not be saved")
1518 messages.error(request, "Parameters could not be saved")
1516
1519
1517 kwargs = {}
1520 kwargs = {}
1518 kwargs['id_dev'] = conf.id
1521 kwargs['id_dev'] = conf.id
1519 kwargs['form'] = form
1522 kwargs['form'] = form
1520 kwargs['title'] = 'Device Configuration'
1523 kwargs['title'] = 'Device Configuration'
1521 kwargs['suptitle'] = 'Parameters read from device'
1524 kwargs['suptitle'] = 'Parameters read from device'
1522 kwargs['button'] = 'Save'
1525 kwargs['button'] = 'Save'
1523
1526
1524 ###### SIDEBAR ######
1527 ###### SIDEBAR ######
1525 kwargs.update(sidebar(conf=conf))
1528 kwargs.update(sidebar(conf=conf))
1526
1529
1527 return render(request, '%s_conf_edit.html' % conf.device.device_type.name, kwargs)
1530 return render(request, '%s_conf_edit.html' % conf.device.device_type.name, kwargs)
1528
1531
1529
1532
1530 @login_required
1533 @login_required
1531 def dev_conf_import(request, id_conf):
1534 def dev_conf_import(request, id_conf):
1532
1535
1533 conf = get_object_or_404(Configuration, pk=id_conf)
1536 conf = get_object_or_404(Configuration, pk=id_conf)
1534 DevConfForm = CONF_FORMS[conf.device.device_type.name]
1537 DevConfForm = CONF_FORMS[conf.device.device_type.name]
1535
1538
1536 if request.method == 'GET':
1539 if request.method == 'GET':
1537 file_form = UploadFileForm()
1540 file_form = UploadFileForm()
1538
1541
1539 if request.method == 'POST':
1542 if request.method == 'POST':
1540 file_form = UploadFileForm(request.POST, request.FILES)
1543 file_form = UploadFileForm(request.POST, request.FILES)
1541
1544
1542 if file_form.is_valid():
1545 if file_form.is_valid():
1543
1546
1544 data = conf.import_from_file(request.FILES['file'])
1547 data = conf.import_from_file(request.FILES['file'])
1545 parms = Params(data=data).get_conf(
1548 parms = Params(data=data).get_conf(
1546 dtype=conf.device.device_type.name)
1549 dtype=conf.device.device_type.name)
1547
1550
1548 if parms:
1551 if parms:
1549
1552
1550 form = DevConfForm(initial=parms, instance=conf)
1553 form = DevConfForm(initial=parms, instance=conf)
1551
1554
1552 kwargs = {}
1555 kwargs = {}
1553 kwargs['id_dev'] = conf.id
1556 kwargs['id_dev'] = conf.id
1554 kwargs['form'] = form
1557 kwargs['form'] = form
1555 kwargs['title'] = 'Device Configuration'
1558 kwargs['title'] = 'Device Configuration'
1556 kwargs['suptitle'] = 'Parameters imported'
1559 kwargs['suptitle'] = 'Parameters imported'
1557 kwargs['button'] = 'Save'
1560 kwargs['button'] = 'Save'
1558 kwargs['action'] = conf.get_absolute_url_edit()
1561 kwargs['action'] = conf.get_absolute_url_edit()
1559 kwargs['previous'] = conf.get_absolute_url()
1562 kwargs['previous'] = conf.get_absolute_url()
1560
1563
1561 ###### SIDEBAR ######
1564 ###### SIDEBAR ######
1562 kwargs.update(sidebar(conf=conf))
1565 kwargs.update(sidebar(conf=conf))
1563
1566
1564 messages.success(
1567 messages.success(
1565 request, "Parameters imported from: '%s'." % request.FILES['file'].name)
1568 request, "Parameters imported from: '%s'." % request.FILES['file'].name)
1566
1569
1567 return render(request, '%s_conf_edit.html' % conf.device.device_type.name, kwargs)
1570 return render(request, '%s_conf_edit.html' % conf.device.device_type.name, kwargs)
1568
1571
1569 messages.error(request, "Could not import parameters from file")
1572 messages.error(request, "Could not import parameters from file")
1570
1573
1571 kwargs = {}
1574 kwargs = {}
1572 kwargs['id_dev'] = conf.id
1575 kwargs['id_dev'] = conf.id
1573 kwargs['title'] = 'Device Configuration'
1576 kwargs['title'] = 'Device Configuration'
1574 kwargs['form'] = file_form
1577 kwargs['form'] = file_form
1575 kwargs['suptitle'] = 'Importing file'
1578 kwargs['suptitle'] = 'Importing file'
1576 kwargs['button'] = 'Import'
1579 kwargs['button'] = 'Import'
1577 kwargs['menu_configurations'] = 'active'
1580 kwargs['menu_configurations'] = 'active'
1578
1581
1579 kwargs.update(sidebar(conf=conf))
1582 kwargs.update(sidebar(conf=conf))
1580
1583
1581 return render(request, 'dev_conf_import.html', kwargs)
1584 return render(request, 'dev_conf_import.html', kwargs)
1582
1585
1583
1586
1584 @login_required
1587 @login_required
1585 def dev_conf_export(request, id_conf):
1588 def dev_conf_export(request, id_conf):
1586
1589
1587 conf = get_object_or_404(Configuration, pk=id_conf)
1590 conf = get_object_or_404(Configuration, pk=id_conf)
1588
1591
1589 if request.method == 'GET':
1592 if request.method == 'GET':
1590 file_form = DownloadFileForm(conf.device.device_type.name)
1593 file_form = DownloadFileForm(conf.device.device_type.name)
1591
1594
1592 if request.method == 'POST':
1595 if request.method == 'POST':
1593 file_form = DownloadFileForm(
1596 file_form = DownloadFileForm(
1594 conf.device.device_type.name, request.POST)
1597 conf.device.device_type.name, request.POST)
1595
1598
1596 if file_form.is_valid():
1599 if file_form.is_valid():
1597 fields = conf.export_to_file(
1600 fields = conf.export_to_file(
1598 format=file_form.cleaned_data['format'])
1601 format=file_form.cleaned_data['format'])
1599 if not fields['content']:
1602 if not fields['content']:
1600 messages.error(request, conf.message)
1603 messages.error(request, conf.message)
1601 return redirect(conf.get_absolute_url_export())
1604 return redirect(conf.get_absolute_url_export())
1602 response = HttpResponse(content_type=fields['content_type'])
1605 response = HttpResponse(content_type=fields['content_type'])
1603 response['Content-Disposition'] = 'attachment; filename="%s"' % fields['filename']
1606 response['Content-Disposition'] = 'attachment; filename="%s"' % fields['filename']
1604 response.write(fields['content'])
1607 response.write(fields['content'])
1605
1608
1606 return response
1609 return response
1607
1610
1608 messages.error(request, "Could not export parameters")
1611 messages.error(request, "Could not export parameters")
1609
1612
1610 kwargs = {}
1613 kwargs = {}
1611 kwargs['id_dev'] = conf.id
1614 kwargs['id_dev'] = conf.id
1612 kwargs['title'] = 'Device Configuration'
1615 kwargs['title'] = 'Device Configuration'
1613 kwargs['form'] = file_form
1616 kwargs['form'] = file_form
1614 kwargs['suptitle'] = 'Exporting file'
1617 kwargs['suptitle'] = 'Exporting file'
1615 kwargs['button'] = 'Export'
1618 kwargs['button'] = 'Export'
1616 kwargs['menu_configurations'] = 'active'
1619 kwargs['menu_configurations'] = 'active'
1617
1620
1618 return render(request, 'dev_conf_export.html', kwargs)
1621 return render(request, 'dev_conf_export.html', kwargs)
1619
1622
1620
1623
1621 @login_required
1624 @login_required
1622 def dev_conf_delete(request, id_conf):
1625 def dev_conf_delete(request, id_conf):
1623
1626
1624 conf = get_object_or_404(Configuration, pk=id_conf)
1627 conf = get_object_or_404(Configuration, pk=id_conf)
1625
1628
1626 if request.method == 'POST':
1629 if request.method == 'POST':
1627 if is_developer(request.user):
1630 if is_developer(request.user):
1628 conf.delete()
1631 conf.delete()
1629 return redirect('url_dev_confs')
1632 return redirect('url_dev_confs')
1630
1633
1631 messages.error(request, 'Not enough permission to delete this object')
1634 messages.error(request, 'Not enough permission to delete this object')
1632 return redirect(conf.get_absolute_url())
1635 return redirect(conf.get_absolute_url())
1633
1636
1634 kwargs = {
1637 kwargs = {
1635 'title': 'Delete',
1638 'title': 'Delete',
1636 'suptitle': 'Configuration',
1639 'suptitle': 'Configuration',
1637 'object': conf,
1640 'object': conf,
1638 'delete': True
1641 'delete': True
1639 }
1642 }
1640 kwargs['menu_configurations'] = 'active'
1643 kwargs['menu_configurations'] = 'active'
1641
1644
1642 return render(request, 'confirm.html', kwargs)
1645 return render(request, 'confirm.html', kwargs)
1643
1646
1644
1647
1645 def sidebar(**kwargs):
1648 def sidebar(**kwargs):
1646
1649
1647 side_data = {}
1650 side_data = {}
1648
1651
1649 conf = kwargs.get('conf', None)
1652 conf = kwargs.get('conf', None)
1650 experiment = kwargs.get('experiment', None)
1653 experiment = kwargs.get('experiment', None)
1651
1654
1652 if not experiment:
1655 if not experiment:
1653 experiment = conf.experiment
1656 experiment = conf.experiment
1654
1657
1655 if experiment:
1658 if experiment:
1656 side_data['experiment'] = experiment
1659 side_data['experiment'] = experiment
1657 campaign = experiment.campaign_set.all()
1660 campaign = experiment.campaign_set.all()
1658 if campaign:
1661 if campaign:
1659 side_data['campaign'] = campaign[0]
1662 side_data['campaign'] = campaign[0]
1660 experiments = campaign[0].experiments.all().order_by('name')
1663 experiments = campaign[0].experiments.all().order_by('name')
1661 else:
1664 else:
1662 experiments = [experiment]
1665 experiments = [experiment]
1663 configurations = experiment.configuration_set.filter(type=0)
1666 configurations = experiment.configuration_set.filter(type=0)
1664 side_data['side_experiments'] = experiments
1667 side_data['side_experiments'] = experiments
1665 side_data['side_configurations'] = configurations.order_by(
1668 side_data['side_configurations'] = configurations.order_by(
1666 'device__device_type__name')
1669 'device__device_type__name')
1667
1670
1668 return side_data
1671 return side_data
1669
1672
1670
1673
1671 def get_paginator(model, page, order, filters={}, n=8):
1674 def get_paginator(model, page, order, filters={}, n=8):
1672
1675
1673 kwargs = {}
1676 kwargs = {}
1674 query = Q()
1677 query = Q()
1675 if isinstance(filters, QueryDict):
1678 if isinstance(filters, QueryDict):
1676 filters = filters.dict()
1679 filters = filters.dict()
1677 [filters.pop(key) for key in list(filters) if filters[key] in ('', ' ')]
1680 [filters.pop(key) for key in list(filters) if filters[key] in ('', ' ')]
1678 filters.pop('page', None)
1681 filters.pop('page', None)
1679
1682
1680 fields = [f.name for f in model._meta.get_fields()]
1683 fields = [f.name for f in model._meta.get_fields()]
1681
1684
1682 if 'template' in filters:
1685 if 'template' in filters:
1683 filters['template'] = True
1686 filters['template'] = True
1684 if 'historical' in filters:
1687 if 'historical' in filters:
1685 filters.pop('historical')
1688 filters.pop('historical')
1686 filters['type'] = 1
1689 filters['type'] = 1
1687 elif 'type' in fields:
1690 elif 'type' in fields:
1688 filters['type'] = 0
1691 filters['type'] = 0
1689 if 'start_date' in filters:
1692 if 'start_date' in filters:
1690 filters['start_date__gte'] = filters.pop('start_date')
1693 filters['start_date__gte'] = filters.pop('start_date')
1691 if 'end_date' in filters:
1694 if 'end_date' in filters:
1692 filters['start_date__lte'] = filters.pop('end_date')
1695 filters['start_date__lte'] = filters.pop('end_date')
1693 if 'tags' in filters:
1696 if 'tags' in filters:
1694 tags = filters.pop('tags')
1697 tags = filters.pop('tags')
1695 if 'tags' in fields:
1698 if 'tags' in fields:
1696 query = query | Q(tags__icontains=tags)
1699 query = query | Q(tags__icontains=tags)
1697 if 'label' in fields:
1700 if 'label' in fields:
1698 query = query | Q(label__icontains=tags)
1701 query = query | Q(label__icontains=tags)
1699 if 'location' in fields:
1702 if 'location' in fields:
1700 query = query | Q(location__name__icontains=tags)
1703 query = query | Q(location__name__icontains=tags)
1701 if 'device' in fields:
1704 if 'device' in fields:
1702 query = query | Q(device__device_type__name__icontains=tags)
1705 query = query | Q(device__device_type__name__icontains=tags)
1703 query = query | Q(device__location__name__icontains=tags)
1706 query = query | Q(device__location__name__icontains=tags)
1704 if 'device_type' in fields:
1707 if 'device_type' in fields:
1705 query = query | Q(device_type__name__icontains=tags)
1708 query = query | Q(device_type__name__icontains=tags)
1706
1709
1707 if 'mine' in filters:
1710 if 'mine' in filters:
1708 filters['author_id'] = filters['mine']
1711 filters['author_id'] = filters['mine']
1709 filters.pop('mine')
1712 filters.pop('mine')
1710 object_list = model.objects.filter(query, **filters).order_by(*order)
1713 object_list = model.objects.filter(query, **filters).order_by(*order)
1711 paginator = Paginator(object_list, n)
1714 paginator = Paginator(object_list, n)
1712
1715
1713 try:
1716 try:
1714 objects = paginator.page(page)
1717 objects = paginator.page(page)
1715 except PageNotAnInteger:
1718 except PageNotAnInteger:
1716 objects = paginator.page(1)
1719 objects = paginator.page(1)
1717 except EmptyPage:
1720 except EmptyPage:
1718 objects = paginator.page(paginator.num_pages)
1721 objects = paginator.page(paginator.num_pages)
1719
1722
1720 kwargs['objects'] = objects
1723 kwargs['objects'] = objects
1721 kwargs['offset'] = (int(page)-1)*n if page else 0
1724 kwargs['offset'] = (int(page)-1)*n if page else 0
1722
1725
1723 return kwargs
1726 return kwargs
1724
1727
1725
1728
1726 def operation(request, id_camp=None):
1729 def operation(request, id_camp=None):
1727
1730
1728 kwargs = {}
1731 kwargs = {}
1729 kwargs['title'] = 'Radars Operation'
1732 kwargs['title'] = 'Radars Operation'
1730 kwargs['no_sidebar'] = True
1733 kwargs['no_sidebar'] = True
1731 kwargs['menu_operation'] = 'active'
1734 kwargs['menu_operation'] = 'active'
1732 campaigns = Campaign.objects.filter(start_date__lte=datetime.now(),
1735 campaigns = Campaign.objects.filter(start_date__lte=datetime.now(),
1733 end_date__gte=datetime.now()).order_by('-start_date')
1736 end_date__gte=datetime.now()).order_by('-start_date')
1734
1737
1735 if id_camp:
1738 if id_camp:
1736 campaign = get_object_or_404(Campaign, pk=id_camp)
1739 campaign = get_object_or_404(Campaign, pk=id_camp)
1737 form = OperationForm(
1740 form = OperationForm(
1738 initial={'campaign': campaign.id}, campaigns=campaigns)
1741 initial={'campaign': campaign.id}, campaigns=campaigns)
1739 kwargs['campaign'] = campaign
1742 kwargs['campaign'] = campaign
1740 else:
1743 else:
1741 # form = OperationForm(campaigns=campaigns)
1744 # form = OperationForm(campaigns=campaigns)
1742 kwargs['campaigns'] = campaigns
1745 kwargs['campaigns'] = campaigns
1743 return render(request, 'operation.html', kwargs)
1746 return render(request, 'operation.html', kwargs)
1744
1747
1745 #---Experiment
1748 #---Experiment
1746 keys = ['id', 'name', 'start_time', 'end_time', 'status']
1749 keys = ['id', 'name', 'start_time', 'end_time', 'status']
1747 kwargs['experiment_keys'] = keys[1:]
1750 kwargs['experiment_keys'] = keys[1:]
1748 kwargs['experiments'] = experiments
1751 kwargs['experiments'] = experiments
1749 #---Radar
1752 #---Radar
1750 kwargs['locations'] = campaign.get_experiments_by_radar()
1753 kwargs['locations'] = campaign.get_experiments_by_radar()
1751 kwargs['form'] = form
1754 kwargs['form'] = form
1752
1755
1753 return render(request, 'operation.html', kwargs)
1756 return render(request, 'operation.html', kwargs)
1754
1757
1755
1758
1756 @login_required
1759 @login_required
1757 def radar_start(request, id_camp, id_radar):
1760 def radar_start(request, id_camp, id_radar):
1758
1761
1759 campaign = get_object_or_404(Campaign, pk=id_camp)
1762 campaign = get_object_or_404(Campaign, pk=id_camp)
1760 experiments = campaign.get_experiments_by_radar(id_radar)[0]['experiments']
1763 experiments = campaign.get_experiments_by_radar(id_radar)[0]['experiments']
1761 now = datetime.now()
1764 now = datetime.now()
1762
1765
1763 for exp in experiments:
1766 for exp in experiments:
1764 #app.control.revoke(exp.task)
1767 #app.control.revoke(exp.task)
1765 print(exp.status)
1768 print(exp.status)
1766 start = datetime.combine(datetime.now().date(), exp.start_time)
1769 start = datetime.combine(datetime.now().date(), exp.start_time)
1767 end = datetime.combine(datetime.now().date(), exp.end_time)
1770 end = datetime.combine(datetime.now().date(), exp.end_time)
1768 print(exp.start_time)
1771 print(exp.start_time)
1769 print(exp.end_time)
1772 print(exp.end_time)
1770
1773
1771 print(start)
1774 print(start)
1772 print(end)
1775 print(end)
1773 print(is_aware(start))
1776 print(is_aware(start))
1774 print(campaign.start_date)
1777 print(campaign.start_date)
1775 print(campaign.end_date)
1778 print(campaign.end_date)
1776 print(is_aware(campaign.start_date))
1779 print(is_aware(campaign.start_date))
1777 if end < start:
1780 if end < start:
1778 end += timedelta(1)
1781 end += timedelta(1)
1779
1782
1780 if exp.status == 2:
1783 if exp.status == 2:
1781 messages.warning(
1784 messages.warning(
1782 request, 'Experiment {} already running'.format(exp))
1785 request, 'Experiment {} already running'.format(exp))
1783 continue
1786 continue
1784
1787
1785 if exp.status == 3:
1788 if exp.status == 3:
1786 messages.warning(
1789 messages.warning(
1787 request, 'Experiment {} already programmed'.format(exp))
1790 request, 'Experiment {} already programmed'.format(exp))
1788 continue
1791 continue
1789
1792
1790 if start > campaign.end_date or start < campaign.start_date:
1793 if start > campaign.end_date or start < campaign.start_date:
1791 messages.warning(request, 'Experiment {} out of date'.format(exp))
1794 messages.warning(request, 'Experiment {} out of date'.format(exp))
1792 continue
1795 continue
1793
1796
1794 app.control.revoke(exp.task)
1797 app.control.revoke(exp.task)
1795 print("Llego luego del revoke")
1798 print("Llego luego del revoke")
1796 if now > start and now <= end:
1799 if now > start and now <= end:
1797 print("Caso now >start and <end")
1800 print("Caso now >start and <end")
1798 task = task_start.delay(exp.id)
1801 task = task_start.delay(exp.id)
1799 exp.status = task.wait()
1802 exp.status = task.wait()
1800 if exp.status == 0:
1803 if exp.status == 0:
1801 messages.error(request, 'Experiment {} not start'.format(exp))
1804 messages.error(request, 'Experiment {} not start'.format(exp))
1802 if exp.status == 2:
1805 if exp.status == 2:
1803 messages.success(request, 'Experiment {} started'.format(exp))
1806 messages.success(request, 'Experiment {} started'.format(exp))
1804 else:
1807 else:
1805 print("Caso now < start o >end")
1808 print("Caso now < start o >end")
1806 task = task_start.apply_async((exp.pk, ), eta=start)#start+timedelta(hours=5))
1809 task = task_start.apply_async((exp.pk, ), eta=start)#start+timedelta(hours=5))
1807 exp.task = task.id
1810 exp.task = task.id
1808 exp.status = 3
1811 exp.status = 3
1809 messages.success(request, 'Experiment {} programmed to start at {}'.format(exp, start))
1812 messages.success(request, 'Experiment {} programmed to start at {}'.format(exp, start))
1810
1813
1811 exp.save()
1814 exp.save()
1812
1815
1813 return HttpResponseRedirect(reverse('url_operation', args=[id_camp]))
1816 return HttpResponseRedirect(reverse('url_operation', args=[id_camp]))
1814
1817
1815
1818
1816 @login_required
1819 @login_required
1817 def radar_stop(request, id_camp, id_radar):
1820 def radar_stop(request, id_camp, id_radar):
1818
1821
1819 campaign = get_object_or_404(Campaign, pk=id_camp)
1822 campaign = get_object_or_404(Campaign, pk=id_camp)
1820 experiments = campaign.get_experiments_by_radar(id_radar)[0]['experiments']
1823 experiments = campaign.get_experiments_by_radar(id_radar)[0]['experiments']
1821 print("Ingreso en stop radar_stop")
1824 print("Ingreso en stop radar_stop")
1822 for exp in experiments:
1825 for exp in experiments:
1823
1826
1824 if exp.task:
1827 if exp.task:
1825 print("Ingreso antes de revoke stop")
1828 print("Ingreso antes de revoke stop")
1826 app.control.revoke(exp.task)
1829 app.control.revoke(exp.task)
1827
1830
1828
1831
1829 if exp.status == 2: #status 2 es started
1832 if exp.status == 2: #status 2 es started
1830 print("llama a exp.stop")
1833 print("llama a exp.stop")
1831 exp.stop()
1834 exp.stop()
1832 messages.warning(request, 'Experiment {} stopped'.format(exp))
1835 messages.warning(request, 'Experiment {} stopped'.format(exp))
1833 exp.status = 1
1836 exp.status = 1
1834 exp.save()
1837 exp.save()
1835
1838
1836 return HttpResponseRedirect(reverse('url_operation', args=[id_camp]))
1839 return HttpResponseRedirect(reverse('url_operation', args=[id_camp]))
1837
1840
1838
1841
1839 @login_required
1842 @login_required
1840 def radar_refresh(request, id_camp, id_radar):
1843 def radar_refresh(request, id_camp, id_radar):
1841
1844
1842 campaign = get_object_or_404(Campaign, pk=id_camp)
1845 campaign = get_object_or_404(Campaign, pk=id_camp)
1843 experiments = campaign.get_experiments_by_radar(id_radar)[0]['experiments']
1846 experiments = campaign.get_experiments_by_radar(id_radar)[0]['experiments']
1844
1847
1845 i = app.control.inspect()
1848 i = app.control.inspect()
1846 print(i)
1849 print(i)
1847 print(i.scheduled())
1850 print(i.scheduled())
1848 print(i.scheduled().values())
1851 print(i.scheduled().values())
1849 scheduled = list(i.scheduled().values())[0]
1852 scheduled = list(i.scheduled().values())[0]
1850 revoked = list(i.revoked().values())[0]
1853 revoked = list(i.revoked().values())[0]
1851
1854
1852 for exp in experiments:
1855 for exp in experiments:
1853 if exp.task in revoked:
1856 if exp.task in revoked:
1854 exp.status = 1
1857 exp.status = 1
1855 elif exp.task in [t['request']['id'] for t in scheduled if 'task_stop' in t['request']['name']]:
1858 elif exp.task in [t['request']['id'] for t in scheduled if 'task_stop' in t['request']['name']]:
1856 exp.status = 2
1859 exp.status = 2
1857 elif exp.task in [t['request']['id'] for t in scheduled if 'task_start' in t['request']['name']]:
1860 elif exp.task in [t['request']['id'] for t in scheduled if 'task_start' in t['request']['name']]:
1858 exp.status = 3
1861 exp.status = 3
1859 else:
1862 else:
1860 exp.status = 4
1863 exp.status = 4
1861 exp.save()
1864 exp.save()
1862 return HttpResponseRedirect(reverse('url_operation', args=[id_camp]))
1865 return HttpResponseRedirect(reverse('url_operation', args=[id_camp]))
1863
1866
1864 @login_required
1867 @login_required
1865 def revoke_tasks(request, id_camp):
1868 def revoke_tasks(request, id_camp):
1866
1869
1867 i = app.control.inspect()
1870 i = app.control.inspect()
1868 scheduled = list(i.scheduled().values())[0]
1871 scheduled = list(i.scheduled().values())[0]
1869 revoked = list(i.revoked().values())[0]
1872 revoked = list(i.revoked().values())[0]
1870
1873
1871 for t in scheduled:
1874 for t in scheduled:
1872 if t['request']['id'] in revoked:
1875 if t['request']['id'] in revoked:
1873 continue
1876 continue
1874 app.control.revoke(t['request']['id'])
1877 app.control.revoke(t['request']['id'])
1875 exp = Experiment.objects.get(pk=eval(str(t['request']['args']))[0])
1878 exp = Experiment.objects.get(pk=eval(str(t['request']['args']))[0])
1876 eta = t['eta']
1879 eta = t['eta']
1877 task = t['request']['name'].split('.')[-1]
1880 task = t['request']['name'].split('.')[-1]
1878 messages.warning(request, 'Scheduled {} at {} for experiment {} revoked'.format(task, eta, exp.name))
1881 messages.warning(request, 'Scheduled {} at {} for experiment {} revoked'.format(task, eta, exp.name))
1879
1882
1880 return HttpResponseRedirect(reverse('url_operation', args=[id_camp]))
1883 return HttpResponseRedirect(reverse('url_operation', args=[id_camp]))
1881
1884
1882 @login_required
1885 @login_required
1883 def show_tasks(request, id_camp):
1886 def show_tasks(request, id_camp):
1884
1887
1885 i = app.control.inspect()
1888 i = app.control.inspect()
1886 scheduled = list(i.scheduled().values())[0]
1889 scheduled = list(i.scheduled().values())[0]
1887 revoked = list(i.revoked().values())[0]
1890 revoked = list(i.revoked().values())[0]
1888
1891
1889 for t in scheduled:
1892 for t in scheduled:
1890 if t['request']['id'] in revoked:
1893 if t['request']['id'] in revoked:
1891 continue
1894 continue
1892 exp = Experiment.objects.get(pk=eval(str(t['request']['args']))[0])
1895 exp = Experiment.objects.get(pk=eval(str(t['request']['args']))[0])
1893 eta = t['eta']
1896 eta = t['eta']
1894 task = t['request']['name'].split('.')[-1]
1897 task = t['request']['name'].split('.')[-1]
1895 messages.success(request, 'Task {} scheduled at {} for experiment {}'.format(task, eta, exp.name))
1898 messages.success(request, 'Task {} scheduled at {} for experiment {}'.format(task, eta, exp.name))
1896
1899
1897 return HttpResponseRedirect(reverse('url_operation', args=[id_camp]))
1900 return HttpResponseRedirect(reverse('url_operation', args=[id_camp]))
1898
1901
1899 def real_time(request):
1902 def real_time(request):
1900
1903
1901 graphic_path = "/home/fiorella/Pictures/catwbeanie.jpg"
1904 graphic_path = "/home/fiorella/Pictures/catwbeanie.jpg"
1902
1905
1903 kwargs = {}
1906 kwargs = {}
1904 kwargs['title'] = 'CLAIRE'
1907 kwargs['title'] = 'CLAIRE'
1905 kwargs['suptitle'] = 'Real Time'
1908 kwargs['suptitle'] = 'Real Time'
1906 kwargs['no_sidebar'] = True
1909 kwargs['no_sidebar'] = True
1907 kwargs['graphic_path'] = graphic_path
1910 kwargs['graphic_path'] = graphic_path
1908 kwargs['graphic1_path'] = 'http://www.bluemaize.net/im/girls-accessories/shark-beanie-11.jpg'
1911 kwargs['graphic1_path'] = 'http://www.bluemaize.net/im/girls-accessories/shark-beanie-11.jpg'
1909
1912
1910 return render(request, 'real_time.html', kwargs)
1913 return render(request, 'real_time.html', kwargs)
1911
1914
1912 def theme(request, theme):
1915 def theme(request, theme):
1913
1916
1914 user = request.user
1917 user = request.user
1915 user.profile.theme = theme
1918 user.profile.theme = theme
1916 user.save()
1919 user.save()
1917 return redirect('index')
1920 return redirect('index')
@@ -1,291 +1,295
1 import ast
1 import ast
2 import json
2 import json
3 import requests
3 import requests
4 import base64
4 import base64
5 import struct
5 import struct
6 from struct import pack
6 from struct import pack
7 import time
7 import time
8 from django.contrib import messages
8 from django.contrib import messages
9 from django.db import models
9 from django.db import models
10 from django.urls import reverse
10 from django.urls import reverse
11 from django.core.validators import MinValueValidator, MaxValueValidator
11 from django.core.validators import MinValueValidator, MaxValueValidator
12
12
13 from apps.main.models import Configuration
13 from apps.main.models import Configuration
14
14
15 AXIS_VALUE = (
15 AXIS_VALUE = (
16 ('AZI', 'azimuth'),
16 ('AZI', 'azimuth'),
17 ('ELE', 'elevation')
17 ('ELE', 'elevation')
18 )
18 )
19
19
20 class PedestalConfiguration(Configuration):
20 class PedestalConfiguration(Configuration):
21
21
22 axis = models.CharField(
22 axis = models.CharField(
23 verbose_name='Axis',
23 verbose_name='Axis',
24 max_length=3,
24 max_length=3,
25 choices=AXIS_VALUE,
25 choices=AXIS_VALUE,
26 null=False,
26 null=False,
27 blank=False
27 blank=False
28 )
28 )
29
29
30 speed = models.FloatField(
30 speed = models.FloatField(
31 verbose_name='Speed',
31 verbose_name='Speed',
32 validators=[MinValueValidator(-20), MaxValueValidator(20)],
32 validators=[MinValueValidator(-20), MaxValueValidator(20)],
33 blank=False,
33 blank=False,
34 null=False
34 null=False
35 )
35 )
36
36
37 table = models.CharField(
37 table = models.CharField(
38 verbose_name="Table",
38 verbose_name="Table",
39 max_length=100,
39 max_length=100,
40 default='',
40 default='',
41 blank=False,
41 blank=False,
42 null=False
42 null=False
43 )
43 )
44
44
45 class Meta:
45 class Meta:
46 db_table = 'pedestal_configurations'
46 db_table = 'pedestal_configurations'
47
47
48 def __str__(self):
48 def __str__(self):
49 return str(self.label)
49 return str(self.label)
50
50
51 def get_absolute_url_plot(self):
51 def get_absolute_url_plot(self):
52 return reverse('url_plot_pedestal_pulses', args=[str(self.id)])
52 return reverse('url_plot_pedestal_pulses', args=[str(self.id)])
53
53
54 def request(self, cmd, method='get', **kwargs):
54 def request(self, cmd, method='get', **kwargs):
55
55
56 req = getattr(requests, method)(self.device.url(cmd), **kwargs)
56 req = getattr(requests, method)(self.device.url(cmd), **kwargs)
57 payload = req.json()
57 payload = req.json()
58
58
59 return payload
59 return payload
60
60
61 def status_device(self):
61 def status_device(self):
62
62
63 try:
63 try:
64 self.device.status = 0
64 #self.device.status = 0
65 payload = self.request('status')
65 #payload = self.request('status')
66 if payload['status']=='enable':
66 payload = requests.get(self.device.url())
67 self.device.status = 3
67 print(payload)
68 if payload:
69 self.device.status = 1
68 elif payload['status']=='disable':
70 elif payload['status']=='disable':
69 self.device.status = 2
71 self.device.status = 2
70 else:
72 else:
71 self.device.status = 1
73 self.device.status = 1
72 self.device.save()
74 self.device.save()
73 self.message = 'Pedestal status: {}'.format(payload['status'])
75 self.message = 'Pedestal status: {}'.format(payload['status'])
74 return False
76 return False
75 except Exception as e:
77 except Exception as e:
76 if 'No route to host' not in str(e):
78 if 'No route to host' not in str(e):
77 self.device.status = 4
79 self.device.status = 4
78 self.device.save()
80 self.device.save()
79 self.message = 'Pedestal status: {}'.format(str(e))
81 self.message = 'Pedestal status: {}'.format(str(e))
80 return False
82 return False
81
83
82 self.device.save()
84 self.device.save()
83 return True
85 return True
84
86
85 def reset_device(self):
87 def reset_device(self):
86
88
87 try:
89 try:
88 payload = self.request('reset', 'post')
90 payload = self.request('reset', 'post')
89 if payload['reset']=='ok':
91 if payload['reset']=='ok':
90 self.message = 'Pedestal restarted OK'
92 self.message = 'Pedestal restarted OK'
91 self.device.status = 2
93 self.device.status = 2
92 self.device.save()
94 self.device.save()
93 else:
95 else:
94 self.message = 'Pedestal restart fail'
96 self.message = 'Pedestal restart fail'
95 self.device.status = 4
97 self.device.status = 4
96 self.device.save()
98 self.device.save()
97 except Exception as e:
99 except Exception as e:
98 self.message = 'Pedestal reset: {}'.format(str(e))
100 self.message = 'Pedestal reset: {}'.format(str(e))
99 return False
101 return False
100
102
101 return True
103 return True
102
104
103 def stop_device(self):
105 def stop_device(self):
104
106
105 try:
107 try:
106 command = self.device.url() + "stop"
108 command = self.device.url() + "stop"
107 r = requests.get(command)
109 r = requests.get(command)
108 if r:
110 if r:
109 self.device.status = 4
111 self.device.status = 4
110 self.device.save()
112 self.device.save()
111 self.message = 'Pedestal stopped'
113 self.message = 'Pedestal stopped'
112 else:
114 else:
113 self.device.status = 4
115 self.device.status = 4
114 self.device.save()
116 self.device.save()
115 return False
117 return False
116 except Exception as e:
118 except Exception as e:
117 if 'No route to host' not in str(e):
119 if 'No route to host' not in str(e):
118 self.device.status = 4
120 self.device.status = 4
119 else:
121 else:
120 self.device.status = 0
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 self.device.save()
125 self.device.save()
123 return False
126 return False
124
127
125 return True
128 return True
126
129
127 def start_device(self):
130 def start_device(self):
128 print("Entró al start")
131
129 try:
132 try:
130 pedestal = PedestalConfiguration.objects.get(pk=self)
133 pedestal = PedestalConfiguration.objects.get(pk=self)
131 print(pedestal)
134 print(pedestal)
132 pedestal_axis = pedestal.get_axis_display()
135 pedestal_axis = pedestal.get_axis_display()
133 print(pedestal)
136 print(pedestal)
134 print(pedestal_axis)
137 print(pedestal_axis)
135 table = pedestal.table
138 table = pedestal.table
136 print(table)
139 print(table)
137 li = list(table.split(", "))
140 li = list(table.split(", "))
138 print(li)
141 print(li)
139 list_of_floats = []
142 list_of_floats = []
140 for item in li:
143 for item in li:
141 list_of_floats.append(float(item))
144 list_of_floats.append(float(item))
142 print(list_of_floats)
145 print(list_of_floats)
143 byte_table = []
146 byte_table = []
144 for x in list_of_floats:
147 for x in list_of_floats:
145 temp = bytearray(struct.pack("f", x))
148 temp = bytearray(struct.pack("f", x))
146 byte_table.append(temp[3])
149 byte_table.append(temp[3])
147 byte_table.append(temp[2])
150 byte_table.append(temp[2])
148 byte_table.append(temp[1])
151 byte_table.append(temp[1])
149 byte_table.append(temp[0])
152 byte_table.append(temp[0])
150 print(byte_table)
153 print(byte_table)
151 coded_table = base64.urlsafe_b64encode(bytes(byte_table))
154 coded_table = base64.urlsafe_b64encode(bytes(byte_table))
152 coded_table_ascii = coded_table.decode('ascii')
155 coded_table_ascii = coded_table.decode('ascii')
153 print(coded_table_ascii)
156 print(coded_table_ascii)
154 data = {'axis': pedestal_axis, 'speed': pedestal.speed, 'table': coded_table_ascii}
157 data = {'axis': pedestal_axis, 'speed': pedestal.speed, 'table': coded_table_ascii}
155 print(data)
158 print(data)
156 json_data = json.dumps(data)
159 json_data = json.dumps(data)
157 print(json_data)
160 print(json_data)
158 first_position = table[0]
161 first_position = table[0]
159
162
160 if pedestal.axis=='azimuth':
163 if pedestal.axis=='azimuth':
161 json_az = json.dumps({"axis": 'azimuth', "position": 0.0})
164 json_az = json.dumps({"axis": 'azimuth', "position": 0.0})
162 json_el = json.dumps({"axis": 'elevation', "position": first_position})
165 json_el = json.dumps({"axis": 'elevation', "position": first_position})
163 else:
166 else:
164 json_az = json.dumps({"axis": 'azimuth', "position": first_position})
167 json_az = json.dumps({"axis": 'azimuth', "position": first_position})
165 json_el = json.dumps({"axis": 'elevation', "position": 0.0})
168 json_el = json.dumps({"axis": 'elevation', "position": 0.0})
166
169
167 base64_table = base64.urlsafe_b64encode(json_data.encode('ascii'))
170 base64_table = base64.urlsafe_b64encode(json_data.encode('ascii'))
168 base64_az = base64.urlsafe_b64encode(json_az.encode('ascii'))
171 base64_az = base64.urlsafe_b64encode(json_az.encode('ascii'))
169 base64_el = base64.urlsafe_b64encode(json_el.encode('ascii'))
172 base64_el = base64.urlsafe_b64encode(json_el.encode('ascii'))
170
173
171 table_url = self.device.url() + "table?params="
174 table_url = self.device.url() + "table?params="
172 az_url = self.device.url() + "position?params="
175 az_url = self.device.url() + "position?params="
173 el_url = self.device.url() + "position?params="
176 el_url = self.device.url() + "position?params="
174
177
175
178
176 complete_url = table_url + base64_table.decode('ascii')
179 complete_url = table_url + base64_table.decode('ascii')
177
180
178 az_url = az_url + base64_az.decode('ascii')
181 az_url = az_url + base64_az.decode('ascii')
179 el_url = el_url + base64_el.decode('ascii')
182 el_url = el_url + base64_el.decode('ascii')
180 print(complete_url)
183 print(complete_url)
181 print(az_url)
184 print(az_url)
182 print(el_url)
185 print(el_url)
183 r = requests.get(az_url)
186 r = requests.get(az_url)
184 r = requests.get(el_url)
187 r = requests.get(el_url)
185 #time.sleep(10)
188 #time.sleep(10)
186 r = requests.get(complete_url)
189 r = requests.get(complete_url)
187 if r:
190 if r:
188 self.device.status = 3
191 self.device.status = 3
189 self.device.save()
192 self.device.save()
190 self.message = 'Pedestal configured and started'
193 self.message = 'Pedestal configured and started'
191 else:
194 else:
192 return False
195 return False
193 except Exception as e:
196 except Exception as e:
194 if 'No route to host' not in str(e):
197 if 'No route to host' not in str(e):
195 self.device.status = 4
198 self.device.status = 4
196 else:
199 else:
197 self.device.status = 0
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 self.device.save()
203 self.device.save()
200 return False
204 return False
201
205
202 return True
206 return True
203
207
204 #def write_device(self, raw=False):
208 #def write_device(self, raw=False):
205
209
206 if not raw:
210 if not raw:
207 clock = RCClock.objects.get(rc_configuration=self)
211 clock = RCClock.objects.get(rc_configuration=self)
208 print(clock)
212 print(clock)
209 if clock.mode:
213 if clock.mode:
210 data = {'default': clock.frequency}
214 data = {'default': clock.frequency}
211 else:
215 else:
212 data = {'manual': [clock.multiplier, clock.divisor, clock.reference]}
216 data = {'manual': [clock.multiplier, clock.divisor, clock.reference]}
213 payload = self.request('setfreq', 'post', data=json.dumps(data))
217 payload = self.request('setfreq', 'post', data=json.dumps(data))
214 print(payload)
218 print(payload)
215 if payload['command'] != 'ok':
219 if payload['command'] != 'ok':
216 self.message = 'Pedestal write: {}'.format(payload['command'])
220 self.message = 'Pedestal write: {}'.format(payload['command'])
217 else:
221 else:
218 self.message = payload['programming']
222 self.message = payload['programming']
219 if payload['programming'] == 'fail':
223 if payload['programming'] == 'fail':
220 self.message = 'Pedestal write: error programming CGS chip'
224 self.message = 'Pedestal write: error programming CGS chip'
221
225
222 values = []
226 values = []
223 for pulse, delay in zip(self.get_pulses(), self.get_delays()):
227 for pulse, delay in zip(self.get_pulses(), self.get_delays()):
224 while delay>65536:
228 while delay>65536:
225 values.append((pulse, 65535))
229 values.append((pulse, 65535))
226 delay -= 65536
230 delay -= 65536
227 values.append((pulse, delay-1))
231 values.append((pulse, delay-1))
228 data = bytearray()
232 data = bytearray()
229 #reset
233 #reset
230 data.extend((128, 0))
234 data.extend((128, 0))
231 #disable
235 #disable
232 data.extend((129, 0))
236 data.extend((129, 0))
233 #SW switch
237 #SW switch
234 if self.control_sw:
238 if self.control_sw:
235 data.extend((130, 2))
239 data.extend((130, 2))
236 else:
240 else:
237 data.extend((130, 0))
241 data.extend((130, 0))
238 #divider
242 #divider
239 data.extend((131, self.clock_divider-1))
243 data.extend((131, self.clock_divider-1))
240 #enable writing
244 #enable writing
241 data.extend((139, 62))
245 data.extend((139, 62))
242
246
243 last = 0
247 last = 0
244 for tup in values:
248 for tup in values:
245 vals = pack('<HH', last^tup[0], tup[1])
249 vals = pack('<HH', last^tup[0], tup[1])
246 last = tup[0]
250 last = tup[0]
247 data.extend((133, vals[1], 132, vals[0], 133, vals[3], 132, vals[2]))
251 data.extend((133, vals[1], 132, vals[0], 133, vals[3], 132, vals[2]))
248
252
249 #enable
253 #enable
250 data.extend((129, 1))
254 data.extend((129, 1))
251
255
252 if raw:
256 if raw:
253 return b64encode(data)
257 return b64encode(data)
254
258
255 try:
259 try:
256 payload = self.request('stop', 'post')
260 payload = self.request('stop', 'post')
257 payload = self.request('reset', 'post')
261 payload = self.request('reset', 'post')
258 #payload = self.request('divider', 'post', data={'divider': self.clock_divider-1})
262 #payload = self.request('divider', 'post', data={'divider': self.clock_divider-1})
259 #payload = self.request('write', 'post', data=b64encode(bytearray((139, 62))), timeout=20)
263 #payload = self.request('write', 'post', data=b64encode(bytearray((139, 62))), timeout=20)
260 n = len(data)
264 n = len(data)
261 x = 0
265 x = 0
262 #while x < n:
266 #while x < n:
263 payload = self.request('write', 'post', data=b64encode(data))
267 payload = self.request('write', 'post', data=b64encode(data))
264 # x += 1024
268 # x += 1024
265
269
266 if payload['write']=='ok':
270 if payload['write']=='ok':
267 self.device.status = 3
271 self.device.status = 3
268 self.device.save()
272 self.device.save()
269 self.message = 'Pedestal configured and started'
273 self.message = 'Pedestal configured and started'
270 else:
274 else:
271 self.device.status = 1
275 self.device.status = 1
272 self.device.save()
276 self.device.save()
273 self.message = 'Pedestal write: {}'.format(payload['write'])
277 self.message = 'Pedestal write: {}'.format(payload['write'])
274 return False
278 return False
275
279
276 #payload = self.request('start', 'post')
280 #payload = self.request('start', 'post')
277
281
278 except Exception as e:
282 except Exception as e:
279 if 'No route to host' not in str(e):
283 if 'No route to host' not in str(e):
280 self.device.status = 4
284 self.device.status = 4
281 else:
285 else:
282 self.device.status = 0
286 self.device.status = 0
283 self.message = 'Pedestal write: {}'.format(str(e))
287 self.message = 'Pedestal write: {}'.format(str(e))
284 self.device.save()
288 self.device.save()
285 return False
289 return False
286
290
287 return True
291 return True
288
292
289
293
290 def get_absolute_url_import(self):
294 def get_absolute_url_import(self):
291 return reverse('url_import_pedestal_conf', args=[str(self.id)])
295 return reverse('url_import_pedestal_conf', args=[str(self.id)])
@@ -1,136 +1,139
1
1
2 import json
2 import json
3
3
4 from django.contrib import messages
4 from django.contrib import messages
5 from django.utils.safestring import mark_safe
5 from django.utils.safestring import mark_safe
6 from django.shortcuts import render, redirect, get_object_or_404, HttpResponse
6 from django.shortcuts import render, redirect, get_object_or_404, HttpResponse
7 from django.contrib.auth.decorators import login_required
7 from django.contrib.auth.decorators import login_required
8
8
9 from apps.main.models import Experiment, Device
9 from apps.main.models import Experiment, Device
10 from apps.main.views import sidebar
10 from apps.main.views import sidebar
11
11
12 from .models import PedestalConfiguration
12 from .models import PedestalConfiguration
13 from .forms import PedestalConfigurationForm, PedestalImportForm
13 from .forms import PedestalConfigurationForm, PedestalImportForm
14
14
15
15
16 def conf(request, conf_id):
16 def conf(request, conf_id):
17
17
18 conf = get_object_or_404(PedestalConfiguration, pk=conf_id)
18 conf = get_object_or_404(PedestalConfiguration, pk=conf_id)
19
19
20 kwargs = {}
20 kwargs = {}
21 kwargs['dev_conf'] = conf
21 kwargs['dev_conf'] = conf
22 kwargs['dev_conf_keys'] = ['axis', 'speed', 'table']
22 kwargs['dev_conf_keys'] = ['axis', 'speed', 'table']
23
23
24 kwargs['title'] = 'Configuration'
24 kwargs['title'] = 'Configuration'
25 kwargs['suptitle'] = 'Detail'
25 kwargs['suptitle'] = 'Detail'
26
26
27 kwargs['button'] = 'Edit Configuration'
27 kwargs['button'] = 'Edit Configuration'
28
29 conf.status_device()
30
28 ###### SIDEBAR ######
31 ###### SIDEBAR ######
29 kwargs.update(sidebar(conf=conf))
32 kwargs.update(sidebar(conf=conf))
30
33
31 return render(request, 'pedestal_conf.html', kwargs)
34 return render(request, 'pedestal_conf.html', kwargs)
32
35
33 @login_required
36 @login_required
34 def conf_edit(request, conf_id):
37 def conf_edit(request, conf_id):
35
38
36 conf = get_object_or_404(PedestalConfiguration, pk=conf_id)
39 conf = get_object_or_404(PedestalConfiguration, pk=conf_id)
37 print(conf)
40 print(conf)
38 #print("fin de carga de params")
41 #print("fin de carga de params")
39 if request.method=='GET':
42 if request.method=='GET':
40 print("GET case")
43 print("GET case")
41 form = PedestalConfigurationForm(instance=conf)
44 form = PedestalConfigurationForm(instance=conf)
42 print(form)
45 print(form)
43
46
44 elif request.method=='POST':
47 elif request.method=='POST':
45 #print("ingreso a post conf edit")
48 #print("ingreso a post conf edit")
46 line_data = {}
49 line_data = {}
47 conf_data = {}
50 conf_data = {}
48 clock_data = {}
51 clock_data = {}
49 extras = []
52 extras = []
50 print("Inicio impresion POST#####")
53 print("Inicio impresion POST#####")
51 print(request.POST.items)
54 print(request.POST.items)
52 print("Fin impresion de POST items#####")
55 print("Fin impresion de POST items#####")
53 #classified post fields
56 #classified post fields
54 for label,value in request.POST.items():
57 for label,value in request.POST.items():
55 if label=='csrfmiddlewaretoken':
58 if label=='csrfmiddlewaretoken':
56 continue
59 continue
57
60
58 if label.count('|')==0:
61 if label.count('|')==0:
59 if label in ('mode', 'multiplier', 'divisor', 'reference', 'frequency'):
62 if label in ('mode', 'multiplier', 'divisor', 'reference', 'frequency'):
60 clock_data[label] = value
63 clock_data[label] = value
61 else:
64 else:
62 conf_data[label] = value
65 conf_data[label] = value
63 continue
66 continue
64
67
65 elif label.split('|')[0]!='-1':
68 elif label.split('|')[0]!='-1':
66 extras.append(label)
69 extras.append(label)
67 continue
70 continue
68
71
69 #print(label)
72 #print(label)
70 x, pk, name = label.split('|')
73 x, pk, name = label.split('|')
71
74
72 if name=='codes':
75 if name=='codes':
73 value = [s for s in value.split('\r\n') if s]
76 value = [s for s in value.split('\r\n') if s]
74
77
75 if pk in line_data:
78 if pk in line_data:
76 line_data[pk][name] = value
79 line_data[pk][name] = value
77 else:
80 else:
78 line_data[pk] = {name:value}
81 line_data[pk] = {name:value}
79 #print(line_data[pk])
82 #print(line_data[pk])
80 #update conf
83 #update conf
81
84
82 form = PedestalConfigurationForm(conf_data, instance=conf)
85 form = PedestalConfigurationForm(conf_data, instance=conf)
83
86
84 #print(request.POST.items())
87 #print(request.POST.items())
85
88
86 if form.is_valid():
89 if form.is_valid():
87 form.save()
90 form.save()
88
91
89 messages.success(request, 'Pedestal configuration successfully updated')
92 messages.success(request, 'Pedestal configuration successfully updated')
90
93
91 return redirect(conf.get_absolute_url())
94 return redirect(conf.get_absolute_url())
92
95
93 kwargs = {}
96 kwargs = {}
94 kwargs['dev_conf'] = conf
97 kwargs['dev_conf'] = conf
95 kwargs['form'] = form
98 kwargs['form'] = form
96 kwargs['edit'] = True
99 kwargs['edit'] = True
97
100
98 kwargs['title'] = 'Pedestal Configuration'
101 kwargs['title'] = 'Pedestal Configuration'
99 kwargs['suptitle'] = 'Edit'
102 kwargs['suptitle'] = 'Edit'
100 kwargs['button'] = 'Update'
103 kwargs['button'] = 'Update'
101
104
102 print(kwargs)
105 print(kwargs)
103 print(form)
106 print(form)
104 return render(request, 'pedestal_conf_edit.html', kwargs)
107 return render(request, 'pedestal_conf_edit.html', kwargs)
105
108
106 def import_file(request, conf_id):
109 def import_file(request, conf_id):
107
110
108 conf = get_object_or_404(PedestalConfiguration, pk=conf_id)
111 conf = get_object_or_404(PedestalConfiguration, pk=conf_id)
109 if request.method=='POST':
112 if request.method=='POST':
110 form = PedestalImportForm(request.POST, request.FILES)
113 form = PedestalImportForm(request.POST, request.FILES)
111 if form.is_valid():
114 if form.is_valid():
112 try:
115 try:
113 data = conf.import_from_file(request.FILES['file_name'])
116 data = conf.import_from_file(request.FILES['file_name'])
114 conf.dict_to_parms(data)
117 conf.dict_to_parms(data)
115 messages.success(request, 'Configuration "%s" loaded succesfully' % request.FILES['file_name'])
118 messages.success(request, 'Configuration "%s" loaded succesfully' % request.FILES['file_name'])
116 return redirect(conf.get_absolute_url_edit())
119 return redirect(conf.get_absolute_url_edit())
117
120
118 except Exception as e:
121 except Exception as e:
119 messages.error(request, 'Error parsing file: "%s" - %s' % (request.FILES['file_name'], repr(e)))
122 messages.error(request, 'Error parsing file: "%s" - %s' % (request.FILES['file_name'], repr(e)))
120 else:
123 else:
121 messages.warning(request, 'Your current configuration will be replaced')
124 messages.warning(request, 'Your current configuration will be replaced')
122 form = PedestalImportForm()
125 form = PedestalImportForm()
123
126
124 kwargs = {}
127 kwargs = {}
125 kwargs['form'] = form
128 kwargs['form'] = form
126 kwargs['title'] = 'Pedestal Configuration'
129 kwargs['title'] = 'Pedestal Configuration'
127 kwargs['suptitle'] = 'Import file'
130 kwargs['suptitle'] = 'Import file'
128 kwargs['button'] = 'Upload'
131 kwargs['button'] = 'Upload'
129 kwargs['previous'] = conf.get_absolute_url()
132 kwargs['previous'] = conf.get_absolute_url()
130
133
131 return render(request, 'pedestal_import.html', kwargs)
134 return render(request, 'pedestal_import.html', kwargs)
132
135
133 def conf_raw(request, conf_id):
136 def conf_raw(request, conf_id):
134 conf = get_object_or_404(PedestalConfiguration, pk=conf_id)
137 conf = get_object_or_404(PedestalConfiguration, pk=conf_id)
135 raw = conf.write_device(raw=True)
138 raw = conf.write_device(raw=True)
136 return HttpResponse(raw, content_type='application/json') No newline at end of file
139 return HttpResponse(raw, content_type='application/json')
1 NO CONTENT: modified file
NO CONTENT: modified file
The requested commit or file is too big and content was truncated. Show full diff
1 NO CONTENT: modified file
NO CONTENT: modified file
The requested commit or file is too big and content was truncated. Show full diff
1 NO CONTENT: modified file
NO CONTENT: modified file
The requested commit or file is too big and content was truncated. Show full diff
1 NO CONTENT: modified file
NO CONTENT: modified file
The requested commit or file is too big and content was truncated. Show full diff
1 NO CONTENT: modified file
NO CONTENT: modified file
The requested commit or file is too big and content was truncated. Show full diff
1 NO CONTENT: modified file
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