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