diff --git a/admin.py b/admin.py index 71199d1..c8cc9ee 100644 --- a/admin.py +++ b/admin.py @@ -1,3 +1,5 @@ +# pylint: disable=line-too-long, unused-argument + from django.contrib import admin from purple_robot_app.models import PurpleRobotConfiguration, PurpleRobotPayload, \ @@ -7,13 +9,15 @@ PurpleRobotDevice, PurpleRobotAlert, \ PurpleRobotDeviceNote + class PurpleRobotConfigurationAdmin(admin.ModelAdmin): list_display = ('name', 'slug', 'added') - list_filter = ['added',] + list_filter = ['added', ] search_fields = ['name', 'slug', 'contents'] admin.site.register(PurpleRobotConfiguration, PurpleRobotConfigurationAdmin) + class PurpleRobotPayloadAdmin(admin.ModelAdmin): list_display = ('user_id', 'added', 'process_tags',) list_filter = ['added', 'user_id'] @@ -21,6 +25,7 @@ class PurpleRobotPayloadAdmin(admin.ModelAdmin): admin.site.register(PurpleRobotPayload, PurpleRobotPayloadAdmin) + class PurpleRobotEventAdmin(admin.ModelAdmin): list_display = ('event', 'event_name', 'logged', 'user_id') list_filter = ['event', 'logged', 'user_id'] @@ -28,6 +33,7 @@ class PurpleRobotEventAdmin(admin.ModelAdmin): admin.site.register(PurpleRobotEvent, PurpleRobotEventAdmin) + class PurpleRobotReadingAdmin(admin.ModelAdmin): list_display = ('probe_name', 'user_id', 'guid', 'logged', 'size', 'attachment',) list_filter = ['probe', 'user_id', 'logged'] @@ -35,27 +41,31 @@ class PurpleRobotReadingAdmin(admin.ModelAdmin): admin.site.register(PurpleRobotReading, PurpleRobotReadingAdmin) + class PurpleRobotReportAdmin(admin.ModelAdmin): list_display = ('probe', 'user_id', 'generated', 'mime_type') list_filter = ['probe', 'user_id', 'generated', 'mime_type'] admin.site.register(PurpleRobotReport, PurpleRobotReportAdmin) + class PurpleRobotTestAdmin(admin.ModelAdmin): list_display = ('probe', 'user_id', 'slug', 'frequency', 'last_updated', 'active') list_filter = ['probe', 'user_id', 'last_updated', 'active'] - + admin.site.register(PurpleRobotTest, PurpleRobotTestAdmin) + class PurpleRobotExportJobAdmin(admin.ModelAdmin): list_display = ('start_date', 'end_date', 'destination', 'state') list_filter = ['start_date', 'end_date', 'state'] - + admin.site.register(PurpleRobotExportJob, PurpleRobotExportJobAdmin) + class PurpleRobotDeviceGroupAdmin(admin.ModelAdmin): - list_display = ('group_id', 'configuration',) - + list_display = ('name', 'group_id', 'configuration',) + admin.site.register(PurpleRobotDeviceGroup, PurpleRobotDeviceGroupAdmin) @@ -66,6 +76,7 @@ def clear_performance_metadata(modeladmin, request, queryset): clear_performance_metadata.description = 'Reset cached metadata' + def mute_alerts(modeladmin, request, queryset): for device in queryset: device.mute_alerts = True @@ -73,6 +84,7 @@ def mute_alerts(modeladmin, request, queryset): mute_alerts.description = 'Mute alerts' + def unmute_alerts(modeladmin, request, queryset): for device in queryset: device.mute_alerts = False @@ -81,21 +93,40 @@ def unmute_alerts(modeladmin, request, queryset): unmute_alerts.description = 'Unmute alerts' +def mark_testing(modeladmin, request, queryset): + for device in queryset: + device.test_device = True + device.save() + +mark_testing.description = 'Mark as test device' + + +def unmark_testing(modeladmin, request, queryset): + for device in queryset: + device.test_device = False + device.save() + +unmark_testing.description = 'Mark as regular device' + + class PurpleRobotDeviceAdmin(admin.ModelAdmin): - list_display = ('device_id', 'name', 'configuration', 'config_last_fetched', 'config_last_user_agent', 'hash_key', 'mute_alerts') - list_filter = ['device_group', 'configuration', 'mute_alerts'] - - actions = [ clear_performance_metadata, mute_alerts, unmute_alerts ] - + list_display = ('device_id', 'name', 'device_group', 'configuration', 'config_last_fetched', 'config_last_user_agent', 'hash_key', 'mute_alerts', 'test_device', ) # 'earliest_reading_date', 'latest_reading_date',) + list_filter = ['device_group', 'test_device', 'configuration', 'mute_alerts'] + search_fields = ['device_id', 'name', 'hash_key'] + + actions = [clear_performance_metadata, mute_alerts, unmute_alerts, mark_testing, unmark_testing] + admin.site.register(PurpleRobotDevice, PurpleRobotDeviceAdmin) + class PurpleRobotDeviceNoteAdmin(admin.ModelAdmin): list_display = ('device', 'added',) list_filter = ['added'] search_fields = ['note'] - + admin.site.register(PurpleRobotDeviceNote, PurpleRobotDeviceNoteAdmin) + class PurpleRobotAlertAdmin(admin.ModelAdmin): list_display = ('user_id', 'probe', 'tags', 'message', 'severity', 'generated', 'dismissed', 'manually_dismissed',) diff --git a/device_info.py b/device_info.py new file mode 100644 index 0000000..54afb94 --- /dev/null +++ b/device_info.py @@ -0,0 +1,45 @@ +# pylint: disable=line-too-long + +DEVICE_PROBES = { + 'LGE: LGLS740': { + 'name': 'LG Volt (LGLS740)', + 'missing_probes': [ + 'edu.northwestern.cbits.purple_robot_manager.probes.sensors.LightSensorProbe', + ] + }, + 'LGE: LGL34C': { + 'name': 'Optimus Fuel (LGL34C)', + 'missing_probes': [ + 'edu.northwestern.cbits.purple_robot_manager.probes.sensors.LightSensorProbe', + ] + }, + 'LGE: LGLS660': { + 'name': 'LG Tribute (LGLS660)', + 'missing_probes': [ + 'edu.northwestern.cbits.purple_robot_manager.probes.sensors.LightSensorProbe', + ] + }, + 'LGE: LGL41C': { + 'name': 'LG Ultimate 2 (LGL41C)', + 'missing_probes': [ + 'edu.northwestern.cbits.purple_robot_manager.probes.sensors.LightSensorProbe', + ] + } +} + + +def can_sense(manufacturer, model, probe): + if manufacturer is None or model is None or probe is None: + return None + + # Until contrary evidence surfaces, assume that all devices have all sensors. + + key = manufacturer + ': ' + model + + if key in DEVICE_PROBES: + device = DEVICE_PROBES[key] + + if probe in device['missing_probes']: + return False + + return True diff --git a/formatters/builtin_applicationlaunchprobe.py b/formatters/builtin_applicationlaunchprobe.py index 7523f01..28d533b 100644 --- a/formatters/builtin_applicationlaunchprobe.py +++ b/formatters/builtin_applicationlaunchprobe.py @@ -1,35 +1,37 @@ -import math +# pylint: disable=line-too-long, unused-argument + import json -from django.template.loader import render_to_string -def format(probe_name, json_payload): + +def format_reading(probe_name, json_payload): item = json.loads(json_payload) app = item['CURRENT_APP_NAME'] category = item['CURRENT_CATEGORY'] - + return app + ' (' + category + ')' + def visualize(probe_name, readings): return '' -''' - report = [] - - for reading in readings: - payload = json.loads(reading.payload) - - timestamp = payload['TIMESTAMP'] - device_active = payload['DEVICE_ACTIVE'] - - if device_active == True: - device_active = 1 - elif device_active == False: - device_active = 0 - - rep_dict = {} - rep_dict["y"] = device_active - rep_dict["x"] = timestamp - report.append(rep_dict) - - return render_to_string('visualization_device.html', { 'probe_name' : probe_name, 'readings' : readings, 'device_report' : json.dumps(report) }) -''' \ No newline at end of file + +# +# report = [] +# +# for reading in readings: +# payload = json.loads(reading.payload) +# +# timestamp = payload['TIMESTAMP'] +# device_active = payload['DEVICE_ACTIVE'] +# +# if device_active == True: +# device_active = 1 +# elif device_active == False: +# device_active = 0 +# +# rep_dict = {} +# rep_dict["y"] = device_active +# rep_dict["x"] = timestamp +# report.append(rep_dict) +# +# return render_to_string('visualization_device.html', { 'probe_name' : probe_name, 'readings' : readings, 'device_report' : json.dumps(report) }) diff --git a/formatters/builtin_batteryprobe.py b/formatters/builtin_batteryprobe.py index 1382ad8..e7a008d 100644 --- a/formatters/builtin_batteryprobe.py +++ b/formatters/builtin_batteryprobe.py @@ -1,20 +1,24 @@ +# pylint: disable=line-too-long, unused-argument + import json from django.template.loader import render_to_string -def format(probe_name, json_payload): + +def format_reading(probe_name, json_payload): item = json.loads(json_payload) - + status = 'Unknown' - + if item['status'] == 2: status = 'Charging' elif item['status'] == 3: status = 'Discharging' elif item['status'] == 5: status = 'Full' - + return str(item['level']) + '% (' + status + ')' + def visualize(probe_name, readings): - return render_to_string('visualization_probe.html', { 'probe_name': probe_name, 'readings': readings }) + return render_to_string('visualization_probe.html', {'probe_name': probe_name, 'readings': readings}) diff --git a/formatters/builtin_callstateprobe.py b/formatters/builtin_callstateprobe.py index a1bc5cf..ac013a7 100644 --- a/formatters/builtin_callstateprobe.py +++ b/formatters/builtin_callstateprobe.py @@ -1,33 +1,36 @@ -import math +# pylint: disable=line-too-long, unused-argument + import json from django.template.loader import render_to_string -def format(probe_name, json_payload): + +def format_reading(probe_name, json_payload): item = json.loads(json_payload) state = item['CALL_STATE'] - + return state + def visualize(probe_name, readings): report = [] - + for reading in readings: payload = json.loads(reading.payload) - + timestamp = payload['TIMESTAMP'] call_state = payload['CALL_STATE'] - + if call_state == "Off-Hook": call_state = 2 elif call_state == "Ringing": call_state = 1 elif call_state == "Idle": call_state = 0 - + rep_dict = {} rep_dict["y"] = call_state rep_dict["x"] = timestamp report.append(rep_dict) - - return render_to_string('visualization_callstate.html', { 'probe_name' : probe_name, 'readings' : readings, 'callstate_report' : json.dumps(report) }) + + return render_to_string('visualization_callstate.html', {'probe_name': probe_name, 'readings': readings, 'callstate_report': json.dumps(report)}) diff --git a/formatters/builtin_communicationeventprobe.py b/formatters/builtin_communicationeventprobe.py index db4f92e..d8073b7 100644 --- a/formatters/builtin_communicationeventprobe.py +++ b/formatters/builtin_communicationeventprobe.py @@ -1,71 +1,70 @@ +# pylint: disable=line-too-long, unused-argument + import datetime import json from django.template.loader import render_to_string -def format(probe_name, json_payload): + +def format_reading(probe_name, json_payload): item = json.loads(json_payload) - + comm_type = item["COMMUNICATION_TYPE"] comm_direction = item["COMMUNICATION_DIRECTION"] comm_timestamp = datetime.datetime.fromtimestamp((item["COMM_TIMESTAMP"]/1000)) - + if comm_type == "PHONE": duration = item["DURATION"] return "{0} - {1}: {2} - Duration: {3}".format(comm_timestamp, comm_direction.capitalize(), comm_type.capitalize(), duration) else: return "{0} - {1}: {2}".format(comm_timestamp, comm_direction.capitalize(), comm_type) -def visualize(probe_name, readings): + +def visualize(probe_name, readings): phone_out_report = [] phone_in_report = [] sms_out_report = [] sms_in_report = [] - + comm_subtype = 0 - + for reading in readings: payload = json.loads(reading.payload) - + timestamp = (payload["COMM_TIMESTAMP"])/1000 comm_type = payload["COMMUNICATION_TYPE"] comm_direct = payload["COMMUNICATION_DIRECTION"] - + subtype_dict = {} subtype_dict["x"] = timestamp - + if comm_type == "PHONE" and comm_direct == "OUTGOING": comm_type = 1 comm_subtype = 3 subtype_dict["y"] = comm_subtype phone_out_report.append(subtype_dict) - elif comm_type == "PHONE" and comm_direct == "INCOMING": comm_type = 1 comm_subtype = 2 subtype_dict["y"] = comm_subtype phone_in_report.append(subtype_dict) - elif comm_type == "SMS" and comm_direct == "OUTGOING": comm_type = 0 comm_subtype = 1 subtype_dict["y"] = comm_subtype sms_out_report.append(subtype_dict) - elif comm_type == "SMS" and comm_direct == "INCOMING": comm_type = 0 comm_subtype = 0 subtype_dict["y"] = comm_subtype sms_in_report.append(subtype_dict) - - return render_to_string( - 'visualization_communicationevent.html', - { - 'probe_name' : probe_name, - 'readings' : readings, - 'phone_out_report' : json.dumps(phone_out_report), - 'phone_in_report' : json.dumps(phone_in_report), - 'sms_out_report' : json.dumps(sms_out_report), - 'sms_in_report' : json.dumps(sms_in_report) - } - ) + + context = {'probe_name': probe_name, + 'readings': readings, + 'phone_out_report': json.dumps(phone_out_report), + 'phone_in_report': json.dumps(phone_in_report), + 'sms_out_report': json.dumps(sms_out_report), + 'sms_in_report': json.dumps(sms_in_report) + } + + return render_to_string('visualization_communicationevent.html', context) diff --git a/formatters/builtin_fusedlocationprobe.py b/formatters/builtin_fusedlocationprobe.py index 93ba18e..fa89c41 100644 --- a/formatters/builtin_fusedlocationprobe.py +++ b/formatters/builtin_fusedlocationprobe.py @@ -1,53 +1,57 @@ +# pylint: disable=line-too-long, unused-argument + import math import json from django.template.loader import render_to_string -def format(probe_name, json_payload): + +def format_reading(probe_name, json_payload): item = json.loads(json_payload) - + provider = item['PROVIDER'] - + if item['PROVIDER'] == 'fused': provider = 'Fused Provider' - + return provider + ': ' + str(item['LATITUDE']) + ', ' + str(item['LONGITUDE']) + def visualize(probe_name, readings): point_counter = {} - + for reading in readings: payload = json.loads(reading.payload) - + longitude = payload['LONGITUDE'] latitude = payload['LATITUDE'] - + key = str(round(longitude, 5)) + ',' + str(round(latitude, 5)) - + points = [] - + try: points = point_counter[key] except KeyError: pass - + points.append(payload) - + point_counter[key] = points - + report = [] - - for k,v in point_counter.iteritems(): - count = len(point_counter[k]) - latitude = round(point_counter[k][0]['LATITUDE'], 5) - longitude = round(point_counter[k][0]['LONGITUDE'], 5) - + + for key in point_counter.keys(): + count = len(point_counter[key]) + latitude = round(point_counter[key][0]['LATITUDE'], 5) + longitude = round(point_counter[key][0]['LONGITUDE'], 5) + report_item = {} - + report_item['count'] = 1 + math.log(count, 100) report_item['lat'] = latitude report_item['lng'] = longitude - + report.append(report_item) - - return render_to_string('visualization_fusedlocationprobe.html', { 'probe_name': probe_name, 'readings': readings, 'heat_report': json.dumps(report, indent=2)}) + + return render_to_string('visualization_fusedlocationprobe.html', {'probe_name': probe_name, 'readings': readings, 'heat_report': json.dumps(report, indent=2)}) diff --git a/formatters/builtin_locationprobe.py b/formatters/builtin_locationprobe.py index 5106b0d..1f98232 100644 --- a/formatters/builtin_locationprobe.py +++ b/formatters/builtin_locationprobe.py @@ -1,57 +1,61 @@ +# pylint: disable=line-too-long, unused-argument + import math import json from django.template.loader import render_to_string -def format(probe_name, json_payload): + +def format_reading(probe_name, json_payload): item = json.loads(json_payload) - + provider = item['PROVIDER'] - + if item['PROVIDER'] == 'fused': provider = 'Fused Provider' elif item['PROVIDER'] == 'network': provider = 'Network Provider' elif item['PROVIDER'] == 'gps': provider = 'GPS Provider' - + return provider + ': ' + str(item['LATITUDE']) + ', ' + str(item['LONGITUDE']) + def visualize(probe_name, readings): point_counter = {} - + for reading in readings: payload = json.loads(reading.payload) - + longitude = payload['LONGITUDE'] latitude = payload['LATITUDE'] - + key = str(round(longitude, 5)) + ',' + str(round(latitude, 5)) - + points = [] - + try: points = point_counter[key] except KeyError: pass - + points.append(payload) - + point_counter[key] = points - + report = [] - - for k,v in point_counter.iteritems(): - count = len(point_counter[k]) - latitude = round(point_counter[k][0]['LATITUDE'], 5) - longitude = round(point_counter[k][0]['LONGITUDE'], 5) - + + for key in point_counter.keys(): + count = len(point_counter[key]) + latitude = round(point_counter[key][0]['LATITUDE'], 5) + longitude = round(point_counter[key][0]['LONGITUDE'], 5) + report_item = {} - + report_item['count'] = 1 + math.log(count, 100) report_item['lat'] = latitude report_item['lng'] = longitude - + report.append(report_item) - - return render_to_string('visualization_fusedlocationprobe.html', { 'probe_name': probe_name, 'readings': readings, 'heat_report': json.dumps(report, indent=2)}) + + return render_to_string('visualization_fusedlocationprobe.html', {'probe_name': probe_name, 'readings': readings, 'heat_report': json.dumps(report, indent=2)}) diff --git a/formatters/builtin_rawlocationprobe.py b/formatters/builtin_rawlocationprobe.py index 93ba18e..fa89c41 100644 --- a/formatters/builtin_rawlocationprobe.py +++ b/formatters/builtin_rawlocationprobe.py @@ -1,53 +1,57 @@ +# pylint: disable=line-too-long, unused-argument + import math import json from django.template.loader import render_to_string -def format(probe_name, json_payload): + +def format_reading(probe_name, json_payload): item = json.loads(json_payload) - + provider = item['PROVIDER'] - + if item['PROVIDER'] == 'fused': provider = 'Fused Provider' - + return provider + ': ' + str(item['LATITUDE']) + ', ' + str(item['LONGITUDE']) + def visualize(probe_name, readings): point_counter = {} - + for reading in readings: payload = json.loads(reading.payload) - + longitude = payload['LONGITUDE'] latitude = payload['LATITUDE'] - + key = str(round(longitude, 5)) + ',' + str(round(latitude, 5)) - + points = [] - + try: points = point_counter[key] except KeyError: pass - + points.append(payload) - + point_counter[key] = points - + report = [] - - for k,v in point_counter.iteritems(): - count = len(point_counter[k]) - latitude = round(point_counter[k][0]['LATITUDE'], 5) - longitude = round(point_counter[k][0]['LONGITUDE'], 5) - + + for key in point_counter.keys(): + count = len(point_counter[key]) + latitude = round(point_counter[key][0]['LATITUDE'], 5) + longitude = round(point_counter[key][0]['LONGITUDE'], 5) + report_item = {} - + report_item['count'] = 1 + math.log(count, 100) report_item['lat'] = latitude report_item['lng'] = longitude - + report.append(report_item) - - return render_to_string('visualization_fusedlocationprobe.html', { 'probe_name': probe_name, 'readings': readings, 'heat_report': json.dumps(report, indent=2)}) + + return render_to_string('visualization_fusedlocationprobe.html', {'probe_name': probe_name, 'readings': readings, 'heat_report': json.dumps(report, indent=2)}) diff --git a/formatters/builtin_screenprobe.py b/formatters/builtin_screenprobe.py index 549779e..4200339 100644 --- a/formatters/builtin_screenprobe.py +++ b/formatters/builtin_screenprobe.py @@ -1,36 +1,39 @@ -import math +# pylint: disable=line-too-long, unused-argument + import json from django.template.loader import render_to_string -def format(probe_name, json_payload): + +def format_reading(probe_name, json_payload): item = json.loads(json_payload) status = item['SCREEN_ACTIVE'] - + if item['SCREEN_ACTIVE'] is True: status = "Active" elif item['SCREEN_ACTIVE'] is False: status = "Inactive" - + return status + def visualize(probe_name, readings): report = [] - + for reading in readings: payload = json.loads(reading.payload) - + timestamp = payload['TIMESTAMP'] screen_active = payload['SCREEN_ACTIVE'] - - if screen_active == True: + + if screen_active is True: screen_active = 1 - elif screen_active == False: + elif screen_active is False: screen_active = 0 - + rep_dict = {} rep_dict["y"] = screen_active rep_dict["x"] = timestamp report.append(rep_dict) - - return render_to_string('visualization_screen.html', { 'probe_name' : probe_name, 'readings' : readings, 'screen_report' : json.dumps(report) }) + + return render_to_string('visualization_screen.html', {'probe_name': probe_name, 'readings': readings, 'screen_report': json.dumps(report)}) diff --git a/formatters/features_deviceinusefeature.py b/formatters/features_deviceinusefeature.py index 8635ee4..509d8d8 100644 --- a/formatters/features_deviceinusefeature.py +++ b/formatters/features_deviceinusefeature.py @@ -1,36 +1,39 @@ -import math +# pylint: disable=line-too-long, unused-argument + import json from django.template.loader import render_to_string -def format(probe_name, json_payload): + +def format_reading(probe_name, json_payload): item = json.loads(json_payload) status = item['DEVICE_ACTIVE'] - + if item['DEVICE_ACTIVE'] is True: status = "Active" elif item['DEVICE_ACTIVE'] is False: status = "Inactive" - + return status + def visualize(probe_name, readings): report = [] - + for reading in readings: payload = json.loads(reading.payload) - + timestamp = payload['TIMESTAMP'] device_active = payload['DEVICE_ACTIVE'] - - if device_active == True: + + if device_active is True: device_active = 1 - elif device_active == False: + elif device_active is False: device_active = 0 - + rep_dict = {} rep_dict["y"] = device_active rep_dict["x"] = timestamp report.append(rep_dict) - - return render_to_string('visualization_device.html', { 'probe_name' : probe_name, 'readings' : readings, 'device_report' : json.dumps(report) }) + + return render_to_string('visualization_device.html', {'probe_name': probe_name, 'readings': readings, 'device_report': json.dumps(report)}) diff --git a/formatters/features_sunrisesunsetfeature.py b/formatters/features_sunrisesunsetfeature.py index 67ddd05..6990c9d 100644 --- a/formatters/features_sunrisesunsetfeature.py +++ b/formatters/features_sunrisesunsetfeature.py @@ -1,58 +1,60 @@ +# pylint: disable=line-too-long, unused-argument + import datetime import json from django.template.loader import render_to_string -def format(probe_name, json_payload): + +def format_reading(probe_name, json_payload): item = json.loads(json_payload) timestamp = datetime.datetime.fromtimestamp(item["TIMESTAMP"]) sunrise = datetime.datetime.fromtimestamp((item["SUNRISE"]/1000)) sunset = datetime.datetime.fromtimestamp((item["SUNSET"]/1000)) - + day_duration = (item["DAY_DURATION"]/1000)/3600.0 - + if item["IS_DAY"] == True: sunlight_hrs = (timestamp - sunrise).seconds / 3600.0 return "{0} -> {1}, Daylight hours: {2:.1f} - Hours after sunrise: {3:.1f}".format(str(sunrise)[10:], str(sunset)[10:], day_duration, sunlight_hrs) else: night_hrs = (timestamp - sunset).seconds / 3600.0 return "{0} -> {1}, Daylight hours: {2:.1f} - Hours after sunset: {3:.1f}".format(str(sunrise)[10:], str(sunset)[10:], day_duration, night_hrs) - + + def visualize(probe_name, readings): sun_report = [] daylight_report = [] - + for reading in readings: payload = json.loads(reading.payload) - + timestamp = payload["TIMESTAMP"] sunrise = datetime.datetime.fromtimestamp((payload["SUNRISE"]/1000)) sunset = datetime.datetime.fromtimestamp((payload["SUNSET"]/1000)) day_duration = (payload["DAY_DURATION"]/1000)/3600 - + sun_ref = 0 if payload["IS_DAY"] == True: sun_ref = (datetime.datetime.fromtimestamp(timestamp) - sunrise).seconds / 3600.0 else: sun_ref = -(datetime.datetime.fromtimestamp(timestamp) - sunset).seconds / 3600.0 - + sun_dict = {} sun_dict["y"] = sun_ref sun_dict["x"] = timestamp sun_report.append(sun_dict) - + daylight_dict = {} daylight_dict["y"] = day_duration daylight_dict["x"] = timestamp daylight_report.append(daylight_dict) - return render_to_string( - 'visualization_sunrisesunset.html', - { - 'probe_name' : probe_name, - 'readings' : readings, - 'sun_report' : json.dumps(sun_report), - 'daylight_report' : json.dumps(daylight_report) - } - ) + context = {'probe_name': probe_name, + 'readings': readings, + 'sun_report': json.dumps(sun_report), + 'daylight_report': json.dumps(daylight_report) + } + + return render_to_string('visualization_sunrisesunset.html', context) diff --git a/formatters/probe.py b/formatters/probe.py index 71deb90..9eb2516 100644 --- a/formatters/probe.py +++ b/formatters/probe.py @@ -1,14 +1,18 @@ +# pylint: disable=line-too-long + import json from django.template.loader import render_to_string -def format(probe_name, json_payload): + +def format_reading(probe_name, json_payload): item = json.loads(json_payload) - + if 'PROBE_DISPLAY_MESSAGE' in item: return item['PROBE_DISPLAY_MESSAGE'] - + return probe_name + ': TODO' + def visualize(probe_name, readings): - return render_to_string('visualization_probe.html', { 'probe_name': probe_name, 'readings': readings }) + return render_to_string('visualization_probe.html', {'probe_name': probe_name, 'readings': readings}) diff --git a/formatters/services_fitbitbetaprobe.py b/formatters/services_fitbitbetaprobe.py index 4eb25ea..5744c7b 100644 --- a/formatters/services_fitbitbetaprobe.py +++ b/formatters/services_fitbitbetaprobe.py @@ -1,17 +1,20 @@ +# pylint: disable=line-too-long, unused-argument + import json from django.template.loader import render_to_string -def format(probe_name, json_payload): + +def format_reading(probe_name, json_payload): item = json.loads(json_payload) - + heart = '?' steps = '?' distance = '?' calories = '?' floors = '?' elevation = '?' - + if 'HEART' in item: heart = str(len(item['HEART'])) @@ -30,7 +33,8 @@ def format(probe_name, json_payload): if 'ELEVATION' in item: elevation = str(len(item['ELEVATION'])) - return 'Heart: ' + heart + ' Elevation: ' + elevation + ' Distance: ' + distance + ' Steps: ' + steps + ' Floors: ' + floors + ' Calories: ' + calories + return 'Heart: ' + heart + ' Elevation: ' + elevation + ' Distance: ' + distance + ' Steps: ' + steps + ' Floors: ' + floors + ' Calories: ' + calories + def visualize(probe_name, readings): heart = [] @@ -39,44 +43,44 @@ def visualize(probe_name, readings): calories = [] floors = [] elevation = [] - + for reading in readings: item = json.loads(reading.payload) - + if 'HEART' in item: for i in range(0, len(item['HEART'])): - plot = { 'x': item['HEART_TIMESTAMPS'][i] / 1000, 'y': item['HEART'][i] } - + plot = {'x': item['HEART_TIMESTAMPS'][i] / 1000, 'y': item['HEART'][i]} + heart.append(plot) if 'STEPS' in item: for i in range(0, len(item['STEPS'])): - plot = { 'x': item['STEP_TIMESTAMPS'][i] / 1000, 'y': item['STEPS'][i] } - + plot = {'x': item['STEP_TIMESTAMPS'][i] / 1000, 'y': item['STEPS'][i]} + steps.append(plot) if 'FLOORS' in item: for i in range(0, len(item['FLOORS'])): - plot = { 'x': item['FLOORS_TIMESTAMPS'][i] / 1000, 'y': item['FLOORS'][i] } - + plot = {'x': item['FLOORS_TIMESTAMPS'][i] / 1000, 'y': item['FLOORS'][i]} + floors.append(plot) if 'ELEVATION' in item: for i in range(0, len(item['ELEVATION'])): - plot = { 'x': item['ELEVATION_TIMESTAMPS'][i] / 1000, 'y': item['ELEVATION'][i] } - + plot = {'x': item['ELEVATION_TIMESTAMPS'][i] / 1000, 'y': item['ELEVATION'][i]} + elevation.append(plot) if 'CALORIES' in item: for i in range(0, len(item['CALORIES'])): - plot = { 'x': item['CALORIES_TIMESTAMPS'][i] / 1000, 'y': item['CALORIES'][i] } - + plot = {'x': item['CALORIES_TIMESTAMPS'][i] / 1000, 'y': item['CALORIES'][i]} + calories.append(plot) if 'DISTANCE' in item: for i in range(0, len(item['DISTANCE'])): - plot = { 'x': item['DISTANCE_TIMESTAMPS'][i] / 1000, 'y': item['DISTANCE'][i] } - + plot = {'x': item['DISTANCE_TIMESTAMPS'][i] / 1000, 'y': item['DISTANCE'][i]} + distance.append(plot) heart.sort(key=lambda o: o['x']) @@ -85,5 +89,5 @@ def visualize(probe_name, readings): elevation.sort(key=lambda o: o['x']) calories.sort(key=lambda o: o['x']) distance.sort(key=lambda o: o['x']) - - return render_to_string('visualization_fitbit.html', { 'probe_name': probe_name, 'distance': json.dumps(distance), 'heart': json.dumps(heart), 'steps': json.dumps(steps), 'floors': json.dumps(floors), 'elevation': json.dumps(elevation), 'calories': json.dumps(calories) }) + + return render_to_string('visualization_fitbit.html', {'probe_name': probe_name, 'distance': json.dumps(distance), 'heart': json.dumps(heart), 'steps': json.dumps(steps), 'floors': json.dumps(floors), 'elevation': json.dumps(elevation), 'calories': json.dumps(calories)}) diff --git a/forms.py b/forms.py index 8b8d4d7..513d3f5 100644 --- a/forms.py +++ b/forms.py @@ -1,21 +1,23 @@ +# pylint: disable=line-too-long, no-member + from django import forms -from models import PurpleRobotReading +from purple_robot_app.models import PurpleRobotReading + class ExportJobForm(forms.Form): start_date = forms.DateTimeField() end_date = forms.DateTimeField() destination = forms.EmailField() - + def __init__(self, *args, **kwargs): super(ExportJobForm, self).__init__(*args, **kwargs) - + probes = [] - + for probe in PurpleRobotReading.objects.order_by('probe').values_list('probe', flat=True).distinct(): tokens = probe.split('.') - probes.append((probe, tokens[-1])) self.fields['probes'] = forms.MultipleChoiceField(choices=probes, widget=forms.CheckboxSelectMultiple()) diff --git a/management/commands/analytics/__init__.py b/management/commands/analytics/__init__.py new file mode 100644 index 0000000..e69de29 diff --git a/management/commands/analytics/generic_probe.py b/management/commands/analytics/generic_probe.py new file mode 100644 index 0000000..0d79585 --- /dev/null +++ b/management/commands/analytics/generic_probe.py @@ -0,0 +1,14 @@ +# pylint: disable=line-too-long + +import datetime + +from django.utils import timezone + +from purple_robot_app.performance import append_performance_sample + + +def log_reading(reading): + yesterday = timezone.now() - datetime.timedelta(days=1) + + if reading.logged > yesterday: + append_performance_sample(reading.user_id, reading.probe, reading.logged, {'sample_count': 1}) diff --git a/management/commands/check_user_data.py b/management/commands/check_user_data.py new file mode 100644 index 0000000..9e7462c --- /dev/null +++ b/management/commands/check_user_data.py @@ -0,0 +1,37 @@ +# pylint: disable=line-too-long, no-member + +import hashlib + +from django.core.management.base import BaseCommand + +from purple_robot_app.models import PurpleRobotReading + + +class Command(BaseCommand): + def handle(self, *args, **options): + user_file = open(args[0], 'r') + + for line in user_file: + user_id = line.strip() + + if len(user_id) > 0: + # readings = PurpleRobotReading.objects.filter(user_id=user_id) + # print(user_id + ': ' + str(readings.count())) + + md5_hash = hashlib.md5() + md5_hash.update(user_id) + + md5_id = md5_hash.hexdigest() + + readings = PurpleRobotReading.objects.filter(user_id=md5_id) + + print user_id + ' (MD5 - ' + md5_id + '): ' + str(readings.count()) + + # m = hashlib.sha256() + # m.update(user_id) + # + # sha256_id = m.hexdigest() + # + # readings = PurpleRobotReading.objects.filter(user_id=sha256_id) + # + # print(user_id + ' (SHA-256 - ' + sha256_id + '): ' + str(readings.count())) diff --git a/management/commands/clean_database.py b/management/commands/clean_database.py index 74325bc..8aede47 100644 --- a/management/commands/clean_database.py +++ b/management/commands/clean_database.py @@ -1,3 +1,5 @@ +# pylint: disable=line-too-long, no-member + import datetime from django.core.management.base import BaseCommand @@ -8,11 +10,12 @@ DAYS_KEPT = 21 + class Command(BaseCommand): def handle(self, *args, **options): now = timezone.now() start = now - datetime.timedelta(DAYS_KEPT) - + PurpleRobotReading.objects.filter(logged__lte=start).delete() PurpleRobotEvent.objects.filter(logged__lte=start).delete() PurpleRobotPayload.objects.filter(added__lte=start).delete() diff --git a/management/commands/clean_old_reports.py b/management/commands/clean_old_reports.py index f706231..c8895dd 100644 --- a/management/commands/clean_old_reports.py +++ b/management/commands/clean_old_reports.py @@ -1,3 +1,5 @@ +# pylint: disable=line-too-long, no-member + import datetime from django.core.management.base import BaseCommand @@ -7,9 +9,10 @@ PROBE_NAME = 'edu.northwestern.cbits.purple_robot_manager.probes.builtin.SignificantMotionProbe' + class Command(BaseCommand): def handle(self, *args, **options): now = timezone.now() start = now - datetime.timedelta(1) - + PurpleRobotReport.objects.filter(generated__lte=start).delete() diff --git a/management/commands/clear_payload_skip_tags.py b/management/commands/clear_payload_skip_tags.py new file mode 100644 index 0000000..343b715 --- /dev/null +++ b/management/commands/clear_payload_skip_tags.py @@ -0,0 +1,60 @@ +# pylint: disable=line-too-long, no-member + +import datetime +import requests +import os + +from django.core.management.base import BaseCommand + +from purple_robot_app.models import PurpleRobotPayload + +PRINT_PROGRESS = False + +SKIP_TAG = 'extracted_into_database_skip' + + +def touch(fname, mode=0o666): + flags = os.O_CREAT | os.O_APPEND + + if os.fdopen(os.open(fname, flags, mode)) is not None: + os.utime(fname, None) + + +class Command(BaseCommand): + def handle(self, *args, **options): + if os.access('/tmp/clear_payload_skip_tags.lock', os.R_OK): + timestamp = os.path.getmtime('/tmp/clear_payload_skip_tags.lock') + created = datetime.datetime.fromtimestamp(timestamp) + + if (datetime.datetime.now() - created).total_seconds() > 300: + print 'clear_payload_skip_tags: Stale lock - removing...' + os.remove('/tmp/clear_payload_skip_tags.lock') + else: + return + + touch('/tmp/clear_payload_skip_tags.lock') + + requests.packages.urllib3.disable_warnings() + + payloads = list(PurpleRobotPayload.objects.filter(process_tags__contains=SKIP_TAG).order_by('-added')[:250]) + + count = 0 + + while len(payloads) > 0 and count < 20: + count += 1 + + for payload in payloads: + payload.process_tags = payload.process_tags.replace(SKIP_TAG, '') + + while payload.process_tags.find(' ') != -1: + payload.process_tags = payload.process_tags.replace(' ', ' ') + + payload.process_tags = payload.process_tags.strip() + + touch('/tmp/clear_payload_skip_tags.lock') + + payload.save() + + payloads = list(PurpleRobotPayload.objects.filter(process_tags__contains=SKIP_TAG).order_by('-added')[:250]) + + os.remove('/tmp/clear_payload_skip_tags.lock') diff --git a/management/commands/compile_accelerometer_report.py b/management/commands/compile_accelerometer_report.py index 1db8130..e75a966 100644 --- a/management/commands/compile_accelerometer_report.py +++ b/management/commands/compile_accelerometer_report.py @@ -1,3 +1,5 @@ +# pylint: disable=line-too-long, no-member + import datetime import gzip import json @@ -14,10 +16,10 @@ PROBE_NAME = 'edu.northwestern.cbits.purple_robot_manager.probes.builtin.AccelerometerProbe' + class Command(BaseCommand): def handle(self, *args, **options): hashes = REPORT_DEVICES - # start = datetime.datetime.now() - datetime.timedelta(days=120) start_ts = datetime.datetime(2015, 7, 3, 5, 0, 0, 0, tzinfo=pytz.timezone('US/Central')) end_ts = start_ts + datetime.timedelta(hours=1) @@ -27,95 +29,95 @@ def handle(self, *args, **options): for user_hash in hashes: payloads = PurpleRobotReading.objects.filter(user_id=user_hash, probe=PROBE_NAME, logged__gte=start_ts, logged__lt=end_ts).order_by('logged') - + count = payloads.count() - + if count > 0: temp_file = tempfile.TemporaryFile() gzf = gzip.GzipFile(mode='wb', fileobj=temp_file) gzf.write('User ID\tSensor Timestamp\tNormalized Timestamp\tCPU Timestamp\tX\tY\tZ\n') - + index = 0 - + last_sensor = sys.maxint base_ts = 0 - + while index < count: end = index + 100 - + if end > count: end = count - + last_sensor = sys.maxint base_ts = 0 - + for payload in payloads[index:end]: reading_json = json.loads(payload.payload) - - ns = [] - ss = [] - ts = [] - xs = [] - ys = [] - zs = [] - + + normal_times = [] + sensor_times = [] + cpu_times = [] + x_readings = [] + y_readings = [] + z_readings = [] + has_sensor = False - + if 'SENSOR_TIMESTAMP' in reading_json: has_sensor = True - - for s in reading_json['SENSOR_TIMESTAMP']: - ss.append(s) - for t in reading_json['EVENT_TIMESTAMP']: - ts.append(t) - + for sensor_time in reading_json['SENSOR_TIMESTAMP']: + sensor_times.append(sensor_time) + + for event_time in reading_json['EVENT_TIMESTAMP']: + cpu_times.append(event_time) + if has_sensor is False: - ss.append(-1) - ns.append(-1) - + sensor_times.append(-1) + normal_times.append(-1) + if has_sensor: - for i in range(0, len(ss)): - sensor_ts = float(ss[i]) - - normalized_ts = sensor_ts / (1000 * 1000 * 1000) - + for i in range(0, len(sensor_times)): + sensor_ts = float(sensor_times[i]) + + normalized_ts = sensor_ts / (1000 * 1000 * 1000) + if normalized_ts < last_sensor: - cpu_time = ts[i] - + cpu_time = cpu_times[i] + base_ts = cpu_time - normalized_ts - - ns.append(base_ts + normalized_ts) - + + normal_times.append(base_ts + normalized_ts) + last_sensor = normalized_ts - - for x in reading_json['X']: - xs.append(x) - - for y in reading_json['Y']: - ys.append(y) - - for z in reading_json['Z']: - zs.append(z) - - for i in range(0, len(ts)): - x = xs[i] - y = ys[i] - z = zs[i] - t = ts[i] - s = ss[i] - n = ns[i] - - gzf.write(user_hash + '\t' + str(s) + '\t' + str(n) + '\t' + str(t) + '\t' + str(x) + '\t' + str(y) + '\t' + str(z) + '\n') - + + for x_reading in reading_json['X']: + x_readings.append(x_reading) + + for y_reading in reading_json['Y']: + y_readings.append(y_reading) + + for z_reading in reading_json['Z']: + z_readings.append(z_reading) + + for i in range(0, len(cpu_times)): + x_reading = x_readings[i] + y_reading = y_readings[i] + z_reading = z_readings[i] + cpu_time = cpu_times[i] + sensor_time = sensor_times[i] + normal_time = normal_times[i] + + gzf.write(user_hash + '\t' + str(sensor_time) + '\t' + str(normal_time) + '\t' + str(cpu_time) + '\t' + str(x_reading) + '\t' + str(y_reading) + '\t' + str(z_reading) + '\n') + index += 100 - + gzf.flush() gzf.close() - + temp_file.seek(0) - + report = PurpleRobotReport(generated=timezone.now(), mime_type='application/x-gzip', probe=PROBE_NAME, user_id=user_hash) report.save() report.report_file.save(user_hash + '-accelerometer.txt.gz', File(temp_file)) diff --git a/management/commands/compile_barometer_report.py b/management/commands/compile_barometer_report.py index 53da195..6ab902a 100644 --- a/management/commands/compile_barometer_report.py +++ b/management/commands/compile_barometer_report.py @@ -1,3 +1,5 @@ +# pylint: disable=line-too-long, no-member + import datetime import gzip import json @@ -14,9 +16,10 @@ PROBE_NAME = 'edu.northwestern.cbits.purple_robot_manager.probes.builtin.PressureProbe' + class Command(BaseCommand): def handle(self, *args, **options): - hashes = REPORT_DEVICES # PurpleRobotPayload.objects.order_by().values('user_id').distinct() + hashes = REPORT_DEVICES # PurpleRobotPayload.objects.order_by().values('user_id').distinct() # start = datetime.datetime.now() - datetime.timedelta(days=120) start_ts = datetime.datetime(2015, 7, 3, 5, 0, 0, 0, tzinfo=pytz.timezone('US/Central')) @@ -31,81 +34,81 @@ def handle(self, *args, **options): if count > 0: temp_file = tempfile.TemporaryFile() gzf = gzip.GzipFile(mode='wb', fileobj=temp_file) - + gzf.write('User ID\tSensor Timestamp\ttNormalized Timestamp\tCPU Timestamp\tAltitude\tPressure\n') - + index = 0 last_sensor = sys.maxint base_ts = 0 - + while index < count: end = index + 100 - + if end > count: end = count - + for payload in payloads[index:end]: reading_json = json.loads(payload.payload) - - ns = [] - ss = [] - ts = [] - xs = [] - ys = [] - + + normal_times = [] + sensor_times = [] + cpu_times = [] + x_readings = [] + y_readings = [] + has_sensor = False - + if 'SENSOR_TIMESTAMP' in reading_json: has_sensor = True - - for s in reading_json['SENSOR_TIMESTAMP']: - ss.append(s) - for t in reading_json['EVENT_TIMESTAMP']: - ts.append(t) - + for sensor_time in reading_json['SENSOR_TIMESTAMP']: + sensor_times.append(sensor_time) + + for event_time in reading_json['EVENT_TIMESTAMP']: + cpu_times.append(event_time) + if has_sensor is False: - ss.append(-1) - ns.append(-1) + sensor_times.append(-1) + normal_times.append(-1) if has_sensor: - for i in range(0, len(ss)): - sensor_ts = float(ss[i]) - - normalized_ts = sensor_ts / (1000 * 1000 * 1000) - + for i in range(0, len(sensor_times)): + sensor_ts = float(sensor_times[i]) + + normalized_ts = sensor_ts / (1000 * 1000 * 1000) + if normalized_ts < last_sensor: - cpu_time = ts[i] - + cpu_time = cpu_times[i] + base_ts = cpu_time - normalized_ts - - ns.append(base_ts + normalized_ts) - + + normal_times.append(base_ts + normalized_ts) + last_sensor = normalized_ts - - for x in reading_json['ALTITUDE']: - xs.append(x) - - for y in reading_json['PRESSURE']: - ys.append(y) - - for i in range(0, len(ts)): - x = xs[i] - y = ys[i] - t = ts[i] - s = ss[i] - n = ns[i] - - gzf.write(user_hash + '\t' + str(s) + '\t' + str(n) + '\t' + str(t) + '\t' + str(x) + '\t' + str(y) + '\n') - + + for x_reading in reading_json['ALTITUDE']: + x_readings.append(x_reading) + + for y_reading in reading_json['PRESSURE']: + y_readings.append(y_reading) + + for i in range(0, len(cpu_times)): + x_reading = x_readings[i] + y_reading = y_readings[i] + cpu_time = cpu_times[i] + sensor_time = sensor_times[i] + normal_time = normal_times[i] + + gzf.write(user_hash + '\t' + str(sensor_time) + '\t' + str(normal_time) + '\t' + str(cpu_time) + '\t' + str(x_reading) + '\t' + str(y_reading) + '\n') + index += 100 - + gzf.flush() gzf.close() - + temp_file.seek(0) - + report = PurpleRobotReport(generated=timezone.now(), mime_type='application/x-gzip', probe=PROBE_NAME, user_id=user_hash) report.save() report.report_file.save(user_hash + '-barometer.txt.gz', File(temp_file)) diff --git a/management/commands/compile_gyroscope_report.py b/management/commands/compile_gyroscope_report.py index 28b561d..e339b7f 100644 --- a/management/commands/compile_gyroscope_report.py +++ b/management/commands/compile_gyroscope_report.py @@ -1,3 +1,5 @@ +# pylint: disable=line-too-long, no-member + import datetime import gzip import json @@ -14,6 +16,7 @@ PROBE_NAME = 'edu.northwestern.cbits.purple_robot_manager.probes.builtin.GyroscopeProbe' + class Command(BaseCommand): def handle(self, *args, **options): hashes = REPORT_DEVICES # PurpleRobotPayload.objects.order_by().values('user_id').distinct() @@ -26,92 +29,90 @@ def handle(self, *args, **options): # hash = hash['user_id'] payloads = PurpleRobotReading.objects.filter(user_id=user_hash, probe=PROBE_NAME, logged__gte=start_ts, logged__lt=end_ts).order_by('logged') - count = payloads.count() if count > 0: temp_file = tempfile.TemporaryFile() gzf = gzip.GzipFile(mode='wb', fileobj=temp_file) gzf.write('User ID\tSensor Timestamp\tNormalized Timestamp\tCPU Timestamp\tX\tY\tZ\n') - + index = 0 - + last_sensor = sys.maxint base_ts = 0 while index < count: end = index + 100 - + if end > count: end = count - - + for payload in payloads[index:end]: reading_json = json.loads(payload.payload) - - ns = [] - ss = [] - ts = [] - xs = [] - ys = [] - zs = [] - + + normal_times = [] + sensor_times = [] + cpu_times = [] + x_readings = [] + y_readings = [] + z_readings = [] + has_sensor = False - + if 'SENSOR_TIMESTAMP' in reading_json: has_sensor = True - - for s in reading_json['SENSOR_TIMESTAMP']: - ss.append(s) - for t in reading_json['EVENT_TIMESTAMP']: - ts.append(t) - + for sensor_time in reading_json['SENSOR_TIMESTAMP']: + sensor_times.append(sensor_times) + + for event_time in reading_json['EVENT_TIMESTAMP']: + cpu_times.append(event_time) + if has_sensor is False: - ss.append(-1) - ns.append(-1) - + sensor_times.append(-1) + normal_times.append(-1) + if has_sensor: - for i in range(0, len(ss)): - sensor_ts = float(ss[i]) - - normalized_ts = sensor_ts / (1000 * 1000 * 1000) - + for i in range(0, len(sensor_times)): + sensor_ts = float(sensor_times[i]) + + normalized_ts = sensor_ts / (1000 * 1000 * 1000) + if normalized_ts < last_sensor: - cpu_time = ts[i] - + cpu_time = cpu_times[i] + base_ts = cpu_time - normalized_ts - - ns.append(base_ts + normalized_ts) - + + normal_times.append(base_ts + normalized_ts) + last_sensor = normalized_ts - - for x in reading_json['X']: - xs.append(x) - - for y in reading_json['Y']: - ys.append(y) - - for z in reading_json['Z']: - zs.append(z) - - for i in range(0, len(ts)): - x = xs[i] - y = ys[i] - z = zs[i] - t = ts[i] - s = ss[i] - n = ns[i] - - gzf.write(user_hash + '\t' + str(s) + '\t' + str(n) + '\t' + str(t) + '\t' + str(x) + '\t' + str(y) + '\t' + str(z) + '\n') - + + for x_reading in reading_json['X']: + x_readings.append(x_reading) + + for y_reading in reading_json['Y']: + y_readings.append(y_reading) + + for z_reading in reading_json['Z']: + z_readings.append(z_reading) + + for i in range(0, len(cpu_times)): + x_reading = x_readings[i] + y_reading = y_readings[i] + z_reading = z_readings[i] + cpu_time = cpu_times[i] + sensor_time = sensor_times[i] + normal_time = normal_times[i] + + gzf.write(user_hash + '\t' + str(sensor_time) + '\t' + str(normal_time) + '\t' + str(cpu_time) + '\t' + str(x_reading) + '\t' + str(y_reading) + '\t' + str(z_reading) + '\n') + index += 100 - + gzf.flush() gzf.close() - + temp_file.seek(0) - + report = PurpleRobotReport(generated=timezone.now(), mime_type='application/x-gzip', probe=PROBE_NAME, user_id=user_hash) report.save() report.report_file.save(user_hash + '-gyroscope.txt.gz', File(temp_file)) diff --git a/management/commands/compile_labels_report.py b/management/commands/compile_labels_report.py index 43b872f..f9de932 100644 --- a/management/commands/compile_labels_report.py +++ b/management/commands/compile_labels_report.py @@ -1,3 +1,5 @@ +# pylint: disable=line-too-long, no-member + import datetime import gzip import json @@ -10,25 +12,23 @@ from django.utils.text import slugify from purple_robot_app.models import PurpleRobotReading, PurpleRobotReport - from purple_robot.settings import REPORT_DEVICES + class Command(BaseCommand): def handle(self, *args, **options): - hashes = REPORT_DEVICES # PurpleRobotPayload.objects.order_by().values('user_id').distinct() + hashes = REPORT_DEVICES # PurpleRobotPayload.objects.order_by().values('user_id').distinct() # start = datetime.datetime.now() - datetime.timedelta(days=120) start_ts = datetime.datetime(2015, 11, 10, 0, 0, 0, 0, tzinfo=pytz.timezone('US/Central')) end_ts = start_ts + datetime.timedelta(days=1) labels = PurpleRobotReading.objects.exclude(probe__startswith='edu.northwestern').values('probe').distinct() - + for user_hash in hashes: for label in labels: slug_label = slugify(label['probe']) - payloads = PurpleRobotReading.objects.filter(user_id=user_hash, probe=label['probe'], logged__gte=start_ts, logged__lt=end_ts).order_by('logged') - count = payloads.count() if count > 0: @@ -36,27 +36,27 @@ def handle(self, *args, **options): gzf = gzip.GzipFile(mode='wb', fileobj=temp_file) gzf.write('User ID\tTimestamp\tValue\n') - + index = 0 - + while index < count: end = index + 100 - + if end > count: end = count - + for payload in payloads[index:end]: reading_json = json.loads(payload.payload) gzf.write(user_hash + '\t' + str(reading_json['TIMESTAMP']) + '\t' + reading_json['FEATURE_VALUE'] + '\n') - + index += 100 - + gzf.flush() gzf.close() - + temp_file.seek(0) - + report = PurpleRobotReport(generated=timezone.now(), mime_type='application/x-gzip', probe=slug_label, user_id=hash) report.save() report.report_file.save(user_hash + '-' + slug_label + '.txt.gz', File(temp_file)) diff --git a/management/commands/compile_significant_motion_report.py b/management/commands/compile_significant_motion_report.py index e1bdee2..8eb1d2d 100644 --- a/management/commands/compile_significant_motion_report.py +++ b/management/commands/compile_significant_motion_report.py @@ -1,3 +1,5 @@ +# pylint: disable=line-too-long, no-member + import datetime import gzip import json @@ -13,6 +15,7 @@ PROBE_NAME = 'edu.northwestern.cbits.purple_robot_manager.probes.builtin.SignificantMotionProbe' + class Command(BaseCommand): def handle(self, *args, **options): hashes = REPORT_DEVICES # PurpleRobotPayload.objects.order_by().values('user_id').distinct() @@ -28,29 +31,29 @@ def handle(self, *args, **options): if count > 0: temp_file = tempfile.TemporaryFile() gzf = gzip.GzipFile(mode='wb', fileobj=temp_file) - + gzf.write('User ID\tTimestamp\n') - + index = 0 - + while index < count: end = index + 100 - + if end > count: end = count - + for payload in payloads[index:end]: reading_json = json.loads(payload.payload) - + gzf.write(hash + '\t' + str(reading_json['TIMESTAMP']) + '\n') - + index += 100 - + gzf.flush() gzf.close() - + temp_file.seek(0) - + report = PurpleRobotReport(generated=timezone.now(), mime_type='application/x-gzip', probe=PROBE_NAME, user_id=user_hash) report.save() report.report_file.save(hash + '-significant-motion.txt.gz', File(temp_file)) diff --git a/management/commands/delete_duplicate_readings.py b/management/commands/delete_duplicate_readings.py index 7201e75..b1712a1 100644 --- a/management/commands/delete_duplicate_readings.py +++ b/management/commands/delete_duplicate_readings.py @@ -1,48 +1,45 @@ +# pylint: disable=line-too-long, no-member + import datetime -import json import os from django.core.management.base import BaseCommand from purple_robot_app.models import PurpleRobotReading, PurpleRobotDevice -def touch(fname, mode=0o666, dir_fd=None, **kwargs): + +def touch(fname, mode=0o666): flags = os.O_CREAT | os.O_APPEND - - with os.fdopen(os.open(fname, flags, mode)) as f: + + if os.fdopen(os.open(fname, flags, mode)) is not None: os.utime(fname, None) + class Command(BaseCommand): def handle(self, *args, **options): if True: - return # Disable this command! - + return # Disable this command! + if os.access('/tmp/delete_duplicate_readings.lock', os.R_OK): - t = os.path.getmtime('/tmp/delete_duplicate_readings.lock') - created = datetime.datetime.fromtimestamp(t) - + timestamp = os.path.getmtime('/tmp/delete_duplicate_readings.lock') + created = datetime.datetime.fromtimestamp(timestamp) + if (datetime.datetime.now() - created).total_seconds() > 120: - print('delete_duplicate_readings: Stale lock - removing...') + print 'delete_duplicate_readings: Stale lock - removing...' os.remove('/tmp/delete_duplicate_readings.lock') else: return touch('/tmp/delete_duplicate_readings.lock') - - print('1') + for device in PurpleRobotDevice.objects.all().order_by('device_id'): - print('2 ' + device.device_id) guids = PurpleRobotReading.objects.filter(user_id=device.hash_key).order_by('guid').values_list('guid', flat=True).distinct() - print('3 ' + device.device_id + ' -- ' + str(len(guids))) - for guid in guids: - if guid != None: + if guid is not None: count = PurpleRobotReading.objects.filter(guid=guid, user_id=device.hash_key).count() - - if count > 1: - print('4 ' + guid + ' -- ' + str(count)) + if count > 1: for match in PurpleRobotReading.objects.filter(guid=guid)[1:]: match.delete() diff --git a/management/commands/delete_older_data.py b/management/commands/delete_older_data.py index 8730833..2b11bc2 100644 --- a/management/commands/delete_older_data.py +++ b/management/commands/delete_older_data.py @@ -1,35 +1,38 @@ +# pylint: disable=line-too-long, no-member + import datetime -import json import os from django.core.management.base import BaseCommand from purple_robot_app.models import PurpleRobotReading, PurpleRobotPayload, PurpleRobotEvent -def touch(fname, mode=0o666, dir_fd=None, **kwargs): + +def touch(fname, mode=0o666): flags = os.O_CREAT | os.O_APPEND - - with os.fdopen(os.open(fname, flags, mode)) as f: + + if os.fdopen(os.open(fname, flags, mode)) is not None: os.utime(fname, None) + class Command(BaseCommand): def handle(self, *args, **options): if os.access('/tmp/delete_older_data.lock', os.R_OK): - t = os.path.getmtime('/tmp/delete_older_data.lock') - created = datetime.datetime.fromtimestamp(t) - + timestamp = os.path.getmtime('/tmp/delete_older_data.lock') + created = datetime.datetime.fromtimestamp(timestamp) + if (datetime.datetime.now() - created).total_seconds() > 120: - print('delete_older_data: Stale lock - removing...') + print 'delete_older_data: Stale lock - removing...' os.remove('/tmp/delete_older_data.lock') else: return touch('/tmp/delete_older_data.lock') - + end = datetime.datetime.now() - datetime.timedelta(days=14) - + PurpleRobotPayload.objects.filter(added__lte=end).delete() PurpleRobotReading.objects.filter(logged__lte=end).delete() PurpleRobotEvent.objects.filter(logged__lte=end).delete() - + os.remove('/tmp/delete_older_data.lock') diff --git a/management/commands/extract_guids.py b/management/commands/extract_guids.py index cdc1425..db2e77a 100644 --- a/management/commands/extract_guids.py +++ b/management/commands/extract_guids.py @@ -1,13 +1,16 @@ +# pylint: disable=line-too-long, no-member + import json from django.core.management.base import BaseCommand from purple_robot_app.models import PurpleRobotReading + class Command(BaseCommand): def handle(self, *args, **options): readings = PurpleRobotReading.objects.filter(guid=None)[:5000] - + for reading in readings: payload = json.loads(reading.payload) reading.guid = payload['GUID'] diff --git a/management/commands/extract_into_database.py b/management/commands/extract_into_database.py index ddfc172..b70ee7e 100644 --- a/management/commands/extract_into_database.py +++ b/management/commands/extract_into_database.py @@ -1,3 +1,5 @@ +# pylint: disable=line-too-long, no-member + import datetime import json import importlib @@ -5,19 +7,26 @@ from django.conf import settings from django.core.management.base import BaseCommand +from django.utils import timezone from django.utils.text import slugify -from purple_robot_app.models import PurpleRobotReading, PurpleRobotPayload +from purple_robot_app.models import PurpleRobotPayload +from purple_robot_app.performance import append_performance_sample + +EXTRACTORS = {} + def my_slugify(str_obj): return slugify(str_obj.replace('.', ' ')).replace('-', '_') -def touch(fname, mode=0o666, dir_fd=None, **kwargs): + +def touch(fname, mode=0o666): flags = os.O_CREAT | os.O_APPEND - - with os.fdopen(os.open(fname, flags, mode)) as f: + + if os.fdopen(os.open(fname, flags, mode)) is not None: os.utime(fname, None) + class Command(BaseCommand): def handle(self, *args, **options): try: @@ -26,105 +35,187 @@ def handle(self, *args, **options): return if os.access('/tmp/extract_into_database.lock', os.R_OK): - t = os.path.getmtime('/tmp/extract_into_database.lock') - created = datetime.datetime.fromtimestamp(t) - - if (datetime.datetime.now() - created).total_seconds() > 60 * 60 * 3: - print('extract_into_database: Stale lock - removing...') + timestamp = os.path.getmtime('/tmp/extract_into_database.lock') + created = datetime.datetime.fromtimestamp(timestamp) + + if (datetime.datetime.now() - created).total_seconds() > 60 * 60 * 8: + print 'extract_into_database: Stale lock - removing...' os.remove('/tmp/extract_into_database.lock') else: return - + touch('/tmp/extract_into_database.lock') tag = 'extracted_into_database' skip_tag = 'extracted_into_database_skip' - - payloads = PurpleRobotPayload.objects.exclude(process_tags__contains=tag).order_by('-added')[:100] - - index = 0 - - while payloads.count() > 0 and index < 50: - index += 1 - + + start = timezone.now() + payloads = list(PurpleRobotPayload.objects.exclude(process_tags__contains=tag).exclude(process_tags__contains=skip_tag).order_by('-added')[:250]) + end = timezone.now() + + query_time = (end - start).total_seconds() + + extractor_times = {} + extractor_counts = {} + + local_db = 0.0 + remote_db = 0.0 + local_app = 0.0 + + while len(payloads) > 0: + touch('/tmp/extract_into_database.lock') + + extractor_times = {} + extractor_counts = {} + + local_db = query_time + remote_db = 0.0 + local_app = 0.0 + + start = timezone.now() + for payload in payloads: - # print('PAYLOAD ' + str(payload.added) + ' / ' + str(payload.pk)) - + cpu_start = datetime.datetime.now() + items = json.loads(payload.payload) - + has_all_extractors = True missing_extractors = [] for item in items: if 'PROBE' in item and 'GUID' in item: probe_name = my_slugify(item['PROBE']).replace('edu_northwestern_cbits_purple_robot_manager_probes_', '') - + found = False - - for app in settings.INSTALLED_APPS: - try: - importlib.import_module(app + '.management.commands.extractors.' + probe_name) - + + if (probe_name in EXTRACTORS) is True: + if EXTRACTORS[probe_name] is not None: found = True - except ImportError: - pass - - if found == False: + else: + found = False + else: + EXTRACTORS[probe_name] = None + + for app in settings.INSTALLED_APPS: + try: + probe = importlib.import_module(app + '.management.commands.extractors.' + probe_name) + + EXTRACTORS[probe_name] = probe + + found = True + except ImportError: + pass + + if found is False: has_all_extractors = False - + if (probe_name in missing_extractors) == False: missing_extractors.append(probe_name) else: has_all_extractors = False missing_extractors.append('Unknown Probe') - + if has_all_extractors: for item in items: if 'PROBE' in item and 'GUID' in item: - probe = None - probe_name = my_slugify(item['PROBE']).replace('edu_northwestern_cbits_purple_robot_manager_probes_', '') - - for app in settings.INSTALLED_APPS: - try: - if probe == None: - probe = importlib.import_module(app + '.management.commands.extractors.' + probe_name) - - found = True - except ImportError: - pass - - if probe != None and probe.exists(settings.PURPLE_ROBOT_FLAT_MIRROR, payload.user_id, item) == False: - # print('PROBE: ' + probe_name) - probe.insert(settings.PURPLE_ROBOT_FLAT_MIRROR, payload.user_id, item) + + probe = EXTRACTORS[probe_name] + + write_start = datetime.datetime.now() + local_app += (write_start - cpu_start).total_seconds() + + exists = True + + if settings.PURPLE_ROBOT_DISABLE_DATA_CHECKS: + exists = False + else: + exists = probe.exists(settings.PURPLE_ROBOT_FLAT_MIRROR, payload.user_id, item) + + cpu_start = datetime.datetime.now() + remote_db += (cpu_start - write_start).total_seconds() + + if EXTRACTORS[probe_name] is not None and exists is False: + write_start = datetime.datetime.now() + local_app += (write_start - cpu_start).total_seconds() + + EXTRACTORS[probe_name].insert(settings.PURPLE_ROBOT_FLAT_MIRROR, payload.user_id, item, check_exists=(settings.PURPLE_ROBOT_DISABLE_DATA_CHECKS is False)) + + cpu_start = datetime.datetime.now() + remote_db += (cpu_start - write_start).total_seconds() + + duration = 0.0 + + if probe_name in extractor_times: + duration = extractor_times[probe_name] + + duration += (cpu_start - write_start).total_seconds() + + extractor_times[probe_name] = duration + + count = 0.0 + + if probe_name in extractor_counts: + count = extractor_counts[probe_name] + + count += 1 + + extractor_counts[probe_name] = count tags = payload.process_tags - + if tags is None or tags.find(tag) == -1: if tags is None or len(tags) == 0: tags = tag else: tags += ' ' + tag - + payload.process_tags = tags - + + read_start = datetime.datetime.now() + local_app += (read_start - cpu_start).total_seconds() + payload.save() + + cpu_start = datetime.datetime.now() + local_db += (cpu_start - read_start).total_seconds() else: tags = payload.process_tags - + if tags is None or tags.find(skip_tag) == -1: if tags is None or len(tags) == 0: tags = skip_tag else: tags += ' ' + skip_tag - + payload.process_tags = tags - + + read_start = datetime.datetime.now() + local_app += (read_start - cpu_start).total_seconds() + payload.save() - + + cpu_start = datetime.datetime.now() + local_db += (cpu_start - read_start).total_seconds() + if len(missing_extractors) > 0: - print('MISSING EXTRACTORS: ' + str(missing_extractors)) - - payloads = PurpleRobotPayload.objects.exclude(process_tags__contains=tag).exclude(process_tags__contains=skip_tag).order_by('-added')[:100] - + print 'MISSING EXTRACTORS: ' + str(missing_extractors) + + end = timezone.now() + + perf_values = {} + perf_values['num_mirrored'] = len(payloads) + perf_values['query_time'] = query_time + perf_values['local_db'] = local_db + perf_values['remote_db'] = remote_db + perf_values['local_app'] = local_app + perf_values['extraction_time'] = (end - start).total_seconds() + + append_performance_sample('system', 'reading_mirror_performance', end, perf_values) + + start = timezone.now() + payloads = list(PurpleRobotPayload.objects.exclude(process_tags__contains=tag).exclude(process_tags__contains=skip_tag).order_by('-added')[:250]) + end = timezone.now() + query_time = (end - start).total_seconds() + os.remove('/tmp/extract_into_database.lock') diff --git a/management/commands/extract_readings.py b/management/commands/extract_readings.py index de3e15b..7167512 100644 --- a/management/commands/extract_readings.py +++ b/management/commands/extract_readings.py @@ -1,26 +1,30 @@ +# pylint: disable=line-too-long, no-member + import datetime -import json import os -import pytz from django.core.management.base import BaseCommand +from django.utils import timezone from purple_robot_app.models import PurpleRobotReading, PurpleRobotPayload +from purple_robot_app.performance import append_performance_sample + -def touch(fname, mode=0o666, dir_fd=None, **kwargs): +def touch(fname, mode=0o666): flags = os.O_CREAT | os.O_APPEND - - with os.fdopen(os.open(fname, flags, mode)) as f: + + if os.fdopen(os.open(fname, flags, mode)) is not None: os.utime(fname, None) + class Command(BaseCommand): def handle(self, *args, **options): if os.access('/tmp/extract_readings.lock', os.R_OK): - t = os.path.getmtime('/tmp/extract_readings.lock') - created = datetime.datetime.fromtimestamp(t) - + timestamp = os.path.getmtime('/tmp/extract_readings.lock') + created = datetime.datetime.fromtimestamp(timestamp) + if (datetime.datetime.now() - created).total_seconds() > 4 * 60 * 60: - print('extract_readings: Stale lock - removing...') + print 'extract_readings: Stale lock - removing...' os.remove('/tmp/extract_readings.lock') else: return @@ -28,18 +32,40 @@ def handle(self, *args, **options): touch('/tmp/extract_readings.lock') tag = 'extracted_readings' - - payloads = PurpleRobotPayload.objects.exclude(process_tags__contains=tag).order_by('-added')[:250] - - while payloads.count() > 0: + + start = timezone.now() + payloads = list(PurpleRobotPayload.objects.exclude(process_tags__contains=tag).exclude(process_tags__contains='ingest_error')[:500]) + end = timezone.now() + + query_time = (end - start).total_seconds() + + while len(payloads) > 0: + touch('/tmp/extract_readings.lock') + + start = timezone.now() + for payload in payloads: payload.ingest_readings() - - payloads = PurpleRobotPayload.objects.exclude(process_tags__contains=tag).exclude(process_tags__contains='ingest_error').order_by('-added')[:250] - + + end = timezone.now() + + perf_values = {} + perf_values['num_extracted'] = len(payloads) + perf_values['query_time'] = query_time + perf_values['extraction_time'] = (end - start).total_seconds() + + append_performance_sample('system', 'reading_ingestion_performance', end, perf_values) + + start = timezone.now() + payloads = list(PurpleRobotPayload.objects.exclude(process_tags__contains=tag).exclude(process_tags__contains='ingest_error')[:500]) + end = timezone.now() + query_time = (end - start).total_seconds() + readings = PurpleRobotReading.objects.filter(guid=None)[:250] - + for reading in readings: reading.update_guid() + readings = PurpleRobotReading.objects.filter(guid=None)[:250] + os.remove('/tmp/extract_readings.lock') diff --git a/management/commands/extractors/builtin_accelerometerprobe.py b/management/commands/extractors/builtin_accelerometerprobe.py index ab026d3..411806b 100644 --- a/management/commands/extractors/builtin_accelerometerprobe.py +++ b/management/commands/extractors/builtin_accelerometerprobe.py @@ -1,5 +1,6 @@ +# pylint: disable=line-too-long + import datetime -import json import psycopg2 import pytz @@ -13,63 +14,65 @@ CREATE_READING_READING_ID_INDEX = 'CREATE INDEX ON builtin_accelerometerprobe_reading(reading_id);' CREATE_READING_UTC_LOGGED_INDEX = 'CREATE INDEX ON builtin_accelerometerprobe_reading(utc_logged);' + def exists(connection_str, user_id, reading): conn = psycopg2.connect(connection_str) cursor = conn.cursor() - if probe_table_exists(conn) == False or reading_table_exists(conn) == False: + if probe_table_exists(conn) is False or reading_table_exists(conn) is False: conn.close() return False cursor.execute('SELECT id FROM builtin_accelerometerprobe WHERE (user_id = %s AND guid = %s);', (user_id, reading['GUID'])) - - exists = (cursor.rowcount > 0) - + + row_exists = (cursor.rowcount > 0) + cursor.close() conn.close() - - return exists + + return row_exists + def probe_table_exists(conn): cursor = conn.cursor() cursor.execute('SELECT table_name FROM information_schema.tables WHERE (table_schema = \'public\' AND table_name = \'builtin_accelerometerprobe\')') - - exists = (cursor.rowcount > 0) - + + table_exists = (cursor.rowcount > 0) + cursor.close() - - return exists + + return table_exists + def reading_table_exists(conn): cursor = conn.cursor() cursor.execute('SELECT table_name FROM information_schema.tables WHERE (table_schema = \'public\' AND table_name = \'builtin_accelerometerprobe_reading\')') - - exists = (cursor.rowcount > 0) - + + table_exists = (cursor.rowcount > 0) + cursor.close() - - return exists -def insert(connection_str, user_id, reading): -# print(json.dumps(reading, indent=2)) - + return table_exists + + +def insert(connection_str, user_id, reading, check_exists=True): conn = psycopg2.connect(connection_str) cursor = conn.cursor() - - if probe_table_exists(conn) == False: + + if check_exists and probe_table_exists(conn) is False: cursor.execute(CREATE_PROBE_TABLE_SQL) cursor.execute(CREATE_PROBE_USER_ID_INDEX) cursor.execute(CREATE_PROBE_GUID_INDEX) cursor.execute(CREATE_PROBE_UTC_LOGGED_INDEX) - - if reading_table_exists(conn) == False: + + if check_exists and reading_table_exists(conn) is False: cursor.execute(CREATE_READING_TABLE_SQL) cursor.execute(CREATE_READING_USER_ID_INDEX) cursor.execute(CREATE_READING_READING_ID_INDEX) cursor.execute(CREATE_READING_UTC_LOGGED_INDEX) - + conn.commit() - + reading_cmd = 'INSERT INTO builtin_accelerometerprobe(user_id, ' + \ 'guid, ' + \ 'timestamp, ' + \ @@ -81,29 +84,28 @@ def insert(connection_str, user_id, reading): 'sensor_version, ' + \ 'sensor_resolution, ' + \ 'sensor_maximum_range) VALUES (%s, %s, %s, %s, %s, %s, %s, %s, %s, %s, %s) RETURNING id;' - - cursor.execute(reading_cmd, (user_id, \ - reading['GUID'], \ - reading['TIMESTAMP'], \ - datetime.datetime.fromtimestamp(reading['TIMESTAMP'], tz=pytz.utc), \ - reading['SENSOR']['VENDOR'], \ - reading['SENSOR']['NAME'], \ - reading['SENSOR']['POWER'], \ - reading['SENSOR']['TYPE'], \ - reading['SENSOR']['VERSION'], \ - reading['SENSOR']['RESOLUTION'], \ + cursor.execute(reading_cmd, (user_id, + reading['GUID'], + reading['TIMESTAMP'], + datetime.datetime.fromtimestamp(reading['TIMESTAMP'], tz=pytz.utc), + reading['SENSOR']['VENDOR'], + reading['SENSOR']['NAME'], + reading['SENSOR']['POWER'], + reading['SENSOR']['TYPE'], + reading['SENSOR']['VERSION'], + reading['SENSOR']['RESOLUTION'], reading['SENSOR']['MAXIMUM_RANGE'])) - + for row in cursor.fetchall(): reading_id = row[0] - + readings_len = len(reading['EVENT_TIMESTAMP']) - + has_sensor = ('SENSOR_TIMESTAMP' in reading) has_normalized = ('NORMALIZED_TIMESTAMP' in reading) - + reading_cursor = conn.cursor() - + for i in range(0, readings_len): reading_cmd = 'INSERT INTO builtin_accelerometerprobe_reading(user_id, ' + \ 'reading_id, ' + \ @@ -118,15 +120,15 @@ def insert(connection_str, user_id, reading): 'y, ' + \ 'z, ' + \ 'accuracy) VALUES (%s, %s, %s, %s, %s, %s, %s, %s, %s, %s, %s, %s, %s);' - - values = [ user_id, reading_id, datetime.datetime.fromtimestamp(reading['TIMESTAMP'], tz=pytz.utc) ] - + + values = [user_id, reading_id, datetime.datetime.fromtimestamp(reading['TIMESTAMP'], tz=pytz.utc)] + values.append(reading['EVENT_TIMESTAMP'][i]) values.append(datetime.datetime.fromtimestamp(reading['EVENT_TIMESTAMP'][i], tz=pytz.utc)) - + if has_sensor: values.append(reading['SENSOR_TIMESTAMP'][i]) - + try: values.append(datetime.datetime.fromtimestamp((reading['SENSOR_TIMESTAMP'][i] / 1000), tz=pytz.utc)) except ValueError: @@ -141,15 +143,15 @@ def insert(connection_str, user_id, reading): else: values.append(None) values.append(None) - + values.append(reading['X'][i]) values.append(reading['Y'][i]) values.append(reading['Z'][i]) values.append(reading['ACCURACY'][i]) - + reading_cursor.execute(reading_cmd, values) conn.commit() - + cursor.close() conn.close() diff --git a/management/commands/extractors/builtin_activitydetectionprobe.py b/management/commands/extractors/builtin_activitydetectionprobe.py index 5c465a4..d7eba00 100644 --- a/management/commands/extractors/builtin_activitydetectionprobe.py +++ b/management/commands/extractors/builtin_activitydetectionprobe.py @@ -1,5 +1,6 @@ +# pylint: disable=line-too-long + import datetime -import json import psycopg2 import pytz @@ -13,77 +14,79 @@ CREATE_ACTIVITY_READING_ID_INDEX = 'CREATE INDEX ON builtin_activitydetectionprobe_activity(reading_id);' CREATE_ACTIVITY_UTC_LOGGED_INDEX = 'CREATE INDEX ON builtin_activitydetectionprobe_activity(utc_logged);' + def exists(connection_str, user_id, reading): conn = psycopg2.connect(connection_str) - if probe_table_exists(conn) == False or activity_table_exists(conn) == False: + if probe_table_exists(conn) is False or activity_table_exists(conn) is False: conn.close() return False cursor = conn.cursor() cursor.execute('SELECT id FROM builtin_activitydetectionprobe WHERE (user_id = %s AND guid = %s);', (user_id, reading['GUID'])) - - exists = (cursor.rowcount > 0) - + + row_exists = (cursor.rowcount > 0) + cursor.close() conn.close() - - return exists + + return row_exists + def probe_table_exists(conn): cursor = conn.cursor() cursor.execute('SELECT table_name FROM information_schema.tables WHERE (table_schema = \'public\' AND table_name = \'builtin_activitydetectionprobe\')') - - probe_table_exists = (cursor.rowcount > 0) - + + table_exists = (cursor.rowcount > 0) + cursor.close() - - return probe_table_exists + + return table_exists + def activity_table_exists(conn): cursor = conn.cursor() cursor.execute('SELECT table_name FROM information_schema.tables WHERE (table_schema = \'public\' AND table_name = \'builtin_activitydetectionprobe_activity\')') - + activities_table_exists = (cursor.rowcount > 0) - + cursor.close() - + return activities_table_exists -def insert(connection_str, user_id, reading): -# print(json.dumps(reading, indent=2)) - + +def insert(connection_str, user_id, reading, check_exists=True): conn = psycopg2.connect(connection_str) cursor = conn.cursor() - - if probe_table_exists(conn) == False: + + if check_exists and probe_table_exists(conn) is False: cursor.execute(CREATE_PROBE_TABLE_SQL) cursor.execute(CREATE_PROBE_USER_ID_INDEX) cursor.execute(CREATE_PROBE_GUID_INDEX) cursor.execute(CREATE_PROBE_UTC_LOGGED_INDEX) - - if activity_table_exists(conn) == False: + + if check_exists and activity_table_exists(conn) is False: cursor.execute(CREATE_ACTIVITY_TABLE_SQL) cursor.execute(CREATE_ACTIVITY_USER_ID_INDEX) cursor.execute(CREATE_ACTIVITY_READING_ID_INDEX) cursor.execute(CREATE_ACTIVITY_UTC_LOGGED_INDEX) - + conn.commit() - + reading_cmd = 'INSERT INTO builtin_activitydetectionprobe(user_id, guid, timestamp, utc_logged, activity_count, most_probable_activity, most_probable_confidence) VALUES (%s, %s, %s, %s, %s, %s, %s) RETURNING id;' - + cursor.execute(reading_cmd, (user_id, reading['GUID'], reading['TIMESTAMP'], datetime.datetime.fromtimestamp(reading['TIMESTAMP'], tz=pytz.utc), reading['ACTIVITY_COUNT'], reading['MOST_PROBABLE_ACTIVITY'], reading['MOST_PROBABLE_CONFIDENCE'])) - + for row in cursor.fetchall(): reading_id = row[0] - + for activity in reading['ACTIVITIES']: activity_cmd = 'INSERT INTO builtin_activitydetectionprobe_activity(user_id, reading_id, utc_logged, activity, confidence) VALUES (%s, %s, %s, %s, %s);' - + cursor.execute(activity_cmd, (user_id, reading_id, datetime.datetime.fromtimestamp(reading['TIMESTAMP'], tz=pytz.utc), activity['ACTIVITY_TYPE'], activity['ACTIVITY_CONFIDENCE'])) - + conn.commit() - + cursor.close() conn.close() diff --git a/management/commands/extractors/builtin_applicationlaunchprobe.py b/management/commands/extractors/builtin_applicationlaunchprobe.py index 8a19e33..effe99c 100644 --- a/management/commands/extractors/builtin_applicationlaunchprobe.py +++ b/management/commands/extractors/builtin_applicationlaunchprobe.py @@ -1,5 +1,6 @@ +# pylint: disable=line-too-long + import datetime -import json import psycopg2 import pytz @@ -8,48 +9,49 @@ CREATE_PROBE_GUID_INDEX = 'CREATE INDEX ON builtin_applicationlaunchprobe(guid);' CREATE_PROBE_UTC_LOGGED_INDEX = 'CREATE INDEX ON builtin_applicationlaunchprobe(utc_logged);' + def exists(connection_str, user_id, reading): conn = psycopg2.connect(connection_str) - if probe_table_exists(conn) == False: + if probe_table_exists(conn) is False: conn.close() return False cursor = conn.cursor() cursor.execute('SELECT id FROM builtin_applicationlaunchprobe WHERE (user_id = %s AND guid = %s);', (user_id, reading['GUID'])) - - exists = (cursor.rowcount > 0) - + + row_exists = (cursor.rowcount > 0) + cursor.close() conn.close() - - return exists + + return row_exists + def probe_table_exists(conn): cursor = conn.cursor() cursor.execute('SELECT table_name FROM information_schema.tables WHERE (table_schema = \'public\' AND table_name = \'builtin_applicationlaunchprobe\')') - - probe_table_exists = (cursor.rowcount > 0) - + + table_exists = (cursor.rowcount > 0) + cursor.close() - - return probe_table_exists -def insert(connection_str, user_id, reading): -# print(json.dumps(reading, indent=2)) - + return table_exists + + +def insert(connection_str, user_id, reading, check_exists=True): conn = psycopg2.connect(connection_str) cursor = conn.cursor() - - if probe_table_exists(conn) == False: + + if check_exists and probe_table_exists(conn) is False: cursor.execute(CREATE_PROBE_TABLE_SQL) cursor.execute(CREATE_PROBE_USER_ID_INDEX) cursor.execute(CREATE_PROBE_GUID_INDEX) cursor.execute(CREATE_PROBE_UTC_LOGGED_INDEX) - + conn.commit() - + reading_cmd = 'INSERT INTO builtin_applicationlaunchprobe(user_id, ' + \ 'guid, ' + \ 'timestamp, ' + \ @@ -57,16 +59,14 @@ def insert(connection_str, user_id, reading): 'current_app_pkg, ' + \ 'current_app_name, ' + \ 'current_category) VALUES (%s, %s, %s, %s, %s, %s, %s) RETURNING id;' - - cursor.execute(reading_cmd, (user_id, \ - reading['GUID'], \ - reading['TIMESTAMP'], \ - datetime.datetime.fromtimestamp(reading['TIMESTAMP'], tz=pytz.utc), \ - reading['CURRENT_APP_PKG'], \ - reading['CURRENT_APP_NAME'], \ + cursor.execute(reading_cmd, (user_id, + reading['GUID'], + reading['TIMESTAMP'], + datetime.datetime.fromtimestamp(reading['TIMESTAMP'], tz=pytz.utc), + reading['CURRENT_APP_PKG'], + reading['CURRENT_APP_NAME'], reading['CURRENT_CATEGORY'])) - conn.commit() - + cursor.close() conn.close() diff --git a/management/commands/extractors/builtin_audiofeaturesprobe.py b/management/commands/extractors/builtin_audiofeaturesprobe.py index 7376bb6..6f9d0c6 100644 --- a/management/commands/extractors/builtin_audiofeaturesprobe.py +++ b/management/commands/extractors/builtin_audiofeaturesprobe.py @@ -1,5 +1,6 @@ +# pylint: disable=line-too-long + import datetime -import json import psycopg2 import pytz @@ -20,49 +21,49 @@ CREATE_PROBE_GUID_INDEX = 'CREATE INDEX ON builtin_audiofeaturesprobe(guid);' CREATE_PROBE_UTC_LOGGED_INDEX = 'CREATE INDEX ON builtin_audiofeaturesprobe(utc_logged);' + def exists(connection_str, user_id, reading): conn = psycopg2.connect(connection_str) - if probe_table_exists(conn) == False: + if probe_table_exists(conn) is False: conn.close() return False cursor = conn.cursor() cursor.execute('SELECT id FROM builtin_audiofeaturesprobe WHERE (user_id = %s AND guid = %s);', (user_id, reading['GUID'])) - - exists = (cursor.rowcount > 0) - + + row_exists = (cursor.rowcount > 0) + cursor.close() conn.close() - - return exists + + return row_exists + def probe_table_exists(conn): cursor = conn.cursor() cursor.execute('SELECT table_name FROM information_schema.tables WHERE (table_schema = \'public\' AND table_name = \'builtin_audiofeaturesprobe\')') - - exists = (cursor.rowcount > 0) - + + table_exists = (cursor.rowcount > 0) + cursor.close() - - return exists -def insert(connection_str, user_id, reading): -# print(json.dumps(reading, indent=2)) - + return table_exists + + +def insert(connection_str, user_id, reading, check_exists=True): conn = psycopg2.connect(connection_str) cursor = conn.cursor() - - if probe_table_exists(conn) == False: + + if check_exists and probe_table_exists(conn) is False: cursor.execute(CREATE_PROBE_TABLE_SQL) cursor.execute(CREATE_PROBE_USER_ID_INDEX) cursor.execute(CREATE_PROBE_GUID_INDEX) cursor.execute(CREATE_PROBE_UTC_LOGGED_INDEX) - - + conn.commit() - + reading_cmd = 'INSERT INTO builtin_audiofeaturesprobe(user_id, ' + \ 'guid, ' + \ 'timestamp, ' + \ @@ -73,21 +74,20 @@ def insert(connection_str, user_id, reading): 'power, ' + \ 'frequency, ' + \ 'normalized_avg_magnitude) VALUES (%s, %s, %s, %s, %s, %s, %s, %s, %s, %s) RETURNING id;' - - data = (user_id, \ - reading['GUID'], \ - reading['TIMESTAMP'], \ - datetime.datetime.fromtimestamp(reading['TIMESTAMP'], tz=pytz.utc), \ - reading['SAMPLES_RECORDED'], \ - reading['SAMPLE_RATE'], \ - reading['SAMPLE_BUFFER_SIZE'], \ - reading['POWER'], \ - reading['FREQUENCY'], \ + data = (user_id, + reading['GUID'], + reading['TIMESTAMP'], + datetime.datetime.fromtimestamp(reading['TIMESTAMP'], tz=pytz.utc), + reading['SAMPLES_RECORDED'], + reading['SAMPLE_RATE'], + reading['SAMPLE_BUFFER_SIZE'], + reading['POWER'], + reading['FREQUENCY'], reading['NORMALIZED_AVG_MAGNITUDE']) - + cursor.execute(reading_cmd, data) - + conn.commit() - + cursor.close() - conn.close() \ No newline at end of file + conn.close() diff --git a/management/commands/extractors/builtin_batteryprobe.py b/management/commands/extractors/builtin_batteryprobe.py index 7341581..8ae36fb 100644 --- a/management/commands/extractors/builtin_batteryprobe.py +++ b/management/commands/extractors/builtin_batteryprobe.py @@ -1,5 +1,6 @@ +# pylint: disable=line-too-long + import datetime -import json import psycopg2 import pytz @@ -8,48 +9,49 @@ CREATE_PROBE_GUID_INDEX = 'CREATE INDEX ON builtin_batteryprobe(guid);' CREATE_PROBE_UTC_LOGGED_INDEX = 'CREATE INDEX ON builtin_batteryprobe(utc_logged);' + def exists(connection_str, user_id, reading): conn = psycopg2.connect(connection_str) - if probe_table_exists(conn) == False: + if probe_table_exists(conn) is False: conn.close() return False cursor = conn.cursor() cursor.execute('SELECT id FROM builtin_batteryprobe WHERE (user_id = %s AND guid = %s);', (user_id, reading['GUID'])) - - exists = (cursor.rowcount > 0) - + + row_exists = (cursor.rowcount > 0) + cursor.close() conn.close() - - return exists + + return row_exists + def probe_table_exists(conn): cursor = conn.cursor() cursor.execute('SELECT table_name FROM information_schema.tables WHERE (table_schema = \'public\' AND table_name = \'builtin_batteryprobe\')') - - probe_table_exists = (cursor.rowcount > 0) - + + table_exists = (cursor.rowcount > 0) + cursor.close() - - return probe_table_exists -def insert(connection_str, user_id, reading): -# print(json.dumps(reading, indent=2)) - + return table_exists + + +def insert(connection_str, user_id, reading, check_exists=True): conn = psycopg2.connect(connection_str) cursor = conn.cursor() - - if probe_table_exists(conn) == False: + + if check_exists and probe_table_exists(conn) is False: cursor.execute(CREATE_PROBE_TABLE_SQL) cursor.execute(CREATE_PROBE_USER_ID_INDEX) cursor.execute(CREATE_PROBE_GUID_INDEX) cursor.execute(CREATE_PROBE_UTC_LOGGED_INDEX) - + conn.commit() - + reading_cmd = 'INSERT INTO builtin_batteryprobe(user_id, ' + \ 'guid, ' + \ 'timestamp, ' + \ @@ -64,23 +66,21 @@ def insert(connection_str, user_id, reading): 'technology, ' + \ 'present, ' + \ 'temperature) VALUES (%s, %s, %s, %s, %s, %s, %s, %s, %s, %s, %s, %s, %s, %s) RETURNING id;' - - cursor.execute(reading_cmd, (user_id, \ - reading['GUID'], \ - reading['TIMESTAMP'], \ - datetime.datetime.fromtimestamp(reading['TIMESTAMP'], tz=pytz.utc), \ - reading['status'], \ - reading['scale'], \ - reading['invalid_charger'], \ - reading['level'], \ - reading['plugged'], \ - reading['health'], \ - reading['voltage'], \ - reading['technology'], \ - reading['present'], \ + cursor.execute(reading_cmd, (user_id, + reading['GUID'], + reading['TIMESTAMP'], + datetime.datetime.fromtimestamp(reading['TIMESTAMP'], tz=pytz.utc), + reading['status'], + reading['scale'], + reading['invalid_charger'], + reading['level'], + reading['plugged'], + reading['health'], + reading['voltage'], + reading['technology'], + reading['present'], reading['temperature'])) - conn.commit() - + cursor.close() conn.close() diff --git a/management/commands/extractors/builtin_bluetoothdevicesprobe.py b/management/commands/extractors/builtin_bluetoothdevicesprobe.py new file mode 100644 index 0000000..c521ebe --- /dev/null +++ b/management/commands/extractors/builtin_bluetoothdevicesprobe.py @@ -0,0 +1,141 @@ +# pylint: disable=line-too-long + +import datetime +import psycopg2 +import pytz + +CREATE_PROBE_TABLE_SQL = 'CREATE TABLE builtin_bluetoothdevicesprobe(id SERIAL PRIMARY KEY, user_id TEXT, guid TEXT, timestamp BIGINT, utc_logged TIMESTAMP, device_count BIGINT);' +CREATE_PROBE_USER_ID_INDEX = 'CREATE INDEX ON builtin_bluetoothdevicesprobe(user_id);' +CREATE_PROBE_GUID_INDEX = 'CREATE INDEX ON builtin_bluetoothdevicesprobe(guid);' +CREATE_PROBE_UTC_LOGGED_INDEX = 'CREATE INDEX ON builtin_bluetoothdevicesprobe(utc_logged);' + +CREATE_DEVICE_TABLE_SQL = 'CREATE TABLE builtin_bluetoothdevicesprobe_device(id SERIAL PRIMARY KEY, user_id TEXT, reading_id BIGINT, utc_logged TIMESTAMP, name TEXT, major_class TEXT, minor_class TEXT, bond_state TEXT, address TEXT);' +CREATE_DEVICE_USER_ID_INDEX = 'CREATE INDEX ON builtin_bluetoothdevicesprobe_device(user_id);' +CREATE_DEVICE_READING_ID_INDEX = 'CREATE INDEX ON builtin_bluetoothdevicesprobe_device(reading_id);' +CREATE_DEVICE_UTC_LOGGED_INDEX = 'CREATE INDEX ON builtin_bluetoothdevicesprobe_device(utc_logged);' + + +def exists(connection_str, user_id, reading): + conn = psycopg2.connect(connection_str) + + if probe_table_exists(conn) is False or device_table_exists(conn) is False: + conn.close() + return False + + cursor = conn.cursor() + + cursor.execute('SELECT id FROM builtin_bluetoothdevicesprobe WHERE (user_id = %s AND guid = %s);', (user_id, reading['GUID'])) + + row_exists = (cursor.rowcount > 0) + + cursor.close() + conn.close() + + return row_exists + + +def probe_table_exists(conn): + cursor = conn.cursor() + cursor.execute('SELECT table_name FROM information_schema.tables WHERE (table_schema = \'public\' AND table_name = \'builtin_bluetoothdevicesprobe\')') + + table_exists = (cursor.rowcount > 0) + + cursor.close() + + return table_exists + + +def device_table_exists(conn): + cursor = conn.cursor() + cursor.execute('SELECT table_name FROM information_schema.tables WHERE (table_schema = \'public\' AND table_name = \'builtin_bluetoothdevicesprobe_device\')') + + table_exists = (cursor.rowcount > 0) + + cursor.close() + + return table_exists + + +def insert(connection_str, user_id, reading, check_exists=True): + check_exists = True + + conn = psycopg2.connect(connection_str) + cursor = conn.cursor() + + if check_exists and probe_table_exists(conn) is False: + cursor.execute(CREATE_PROBE_TABLE_SQL) + cursor.execute(CREATE_PROBE_USER_ID_INDEX) + cursor.execute(CREATE_PROBE_GUID_INDEX) + cursor.execute(CREATE_PROBE_UTC_LOGGED_INDEX) + + if check_exists and device_table_exists(conn) is False: + cursor.execute(CREATE_DEVICE_TABLE_SQL) + cursor.execute(CREATE_DEVICE_USER_ID_INDEX) + cursor.execute(CREATE_DEVICE_READING_ID_INDEX) + cursor.execute(CREATE_DEVICE_UTC_LOGGED_INDEX) + + conn.commit() + + reading_cmd = 'INSERT INTO builtin_bluetoothdevicesprobe(user_id, ' + \ + 'guid, ' + \ + 'timestamp, ' + \ + 'utc_logged, ' + \ + 'device_count) VALUES (%s, %s, %s, %s, %s) RETURNING id;' + device_count = None + + if 'DEVICE_COUNT' in reading: + device_count = reading['DEVICE_COUNT'] + + cursor.execute(reading_cmd, (user_id, + reading['GUID'], + reading['TIMESTAMP'], + datetime.datetime.fromtimestamp(reading['TIMESTAMP'], tz=pytz.utc), + device_count)) + + for row in cursor.fetchall(): + reading_id = row[0] + + device_cursor = conn.cursor() + + for device in reading['DEVICES']: + device_cmd = 'INSERT INTO builtin_bluetoothdevicesprobe_device(user_id, ' + \ + 'reading_id, ' + \ + 'utc_logged, ' + \ + 'name, ' + \ + 'major_class, ' + \ + 'minor_class, ' + \ + 'bond_state, ' + \ + 'address) VALUES (%s, %s, %s, %s, %s, %s, %s, %s);' + major = None + minor = None + name = None + bond = None + address = None + + if 'DEVICE MAJOR CLASS' in device: + major = device['DEVICE MAJOR CLASS'] + + if 'DEVICE MINOR CLASS' in device: + minor = device['DEVICE MINOR CLASS'] + + if 'BLUETOOTH_NAME' in device: + name = device['BLUETOOTH_NAME'] + + if 'BOND_STATE' in device: + bond = device['BOND_STATE'] + + if 'BLUETOOTH_ADDRESS' in device: + address = device['BLUETOOTH_ADDRESS'] + + device_cursor.execute(device_cmd, (user_id, + reading_id, + datetime.datetime.fromtimestamp(reading['TIMESTAMP'], tz=pytz.utc), + name, + major, + minor, + bond, + address)) + conn.commit() + + cursor.close() + conn.close() diff --git a/management/commands/extractors/builtin_callstateprobe.py b/management/commands/extractors/builtin_callstateprobe.py index 5ec7772..c2daacd 100644 --- a/management/commands/extractors/builtin_callstateprobe.py +++ b/management/commands/extractors/builtin_callstateprobe.py @@ -1,5 +1,6 @@ +# pylint: disable=line-too-long + import datetime -import json import psycopg2 import pytz @@ -8,59 +9,60 @@ CREATE_PROBE_GUID_INDEX = 'CREATE INDEX ON builtin_callstateprobe(guid);' CREATE_PROBE_UTC_LOGGED_INDEX = 'CREATE INDEX ON builtin_callstateprobe(utc_logged);' + def exists(connection_str, user_id, reading): conn = psycopg2.connect(connection_str) - - if probe_table_exists(conn) == False: + + if probe_table_exists(conn) is False: conn.close() return False cursor = conn.cursor() - + cursor.execute('SELECT id FROM builtin_callstateprobe WHERE (user_id = %s AND guid = %s);', (user_id, reading['GUID'])) - - exists = (cursor.rowcount > 0) - + + row_exists = (cursor.rowcount > 0) + cursor.close() conn.close() - - return exists + + return row_exists + def probe_table_exists(conn): cursor = conn.cursor() cursor.execute('SELECT table_name FROM information_schema.tables WHERE (table_schema = \'public\' AND table_name = \'builtin_callstateprobe\')') - - probe_table_exists = (cursor.rowcount > 0) - + + table_exists = (cursor.rowcount > 0) + cursor.close() - - return probe_table_exists -def insert(connection_str, user_id, reading): -# print(json.dumps(reading, indent=2)) - + return table_exists + + +def insert(connection_str, user_id, reading, check_exists=True): conn = psycopg2.connect(connection_str) cursor = conn.cursor() - - if probe_table_exists(conn) == False: + + if check_exists and probe_table_exists(conn) is False: cursor.execute(CREATE_PROBE_TABLE_SQL) cursor.execute(CREATE_PROBE_USER_ID_INDEX) cursor.execute(CREATE_PROBE_GUID_INDEX) cursor.execute(CREATE_PROBE_UTC_LOGGED_INDEX) - + conn.commit() - + reading_cmd = 'INSERT INTO builtin_callstateprobe(user_id, ' + \ 'guid, ' + \ 'timestamp, ' + \ 'utc_logged, ' + \ 'call_state) VALUES (%s, %s, %s, %s, %s) RETURNING id;' - cursor.execute(reading_cmd, (user_id, \ - reading['GUID'], \ - reading['TIMESTAMP'], \ - datetime.datetime.fromtimestamp(reading['TIMESTAMP'], tz=pytz.utc), \ + cursor.execute(reading_cmd, (user_id, + reading['GUID'], + reading['TIMESTAMP'], + datetime.datetime.fromtimestamp(reading['TIMESTAMP'], tz=pytz.utc), reading['CALL_STATE'])) conn.commit() - + cursor.close() conn.close() diff --git a/management/commands/extractors/builtin_communicationeventprobe.py b/management/commands/extractors/builtin_communicationeventprobe.py index e9f3e1a..12e572e 100644 --- a/management/commands/extractors/builtin_communicationeventprobe.py +++ b/management/commands/extractors/builtin_communicationeventprobe.py @@ -1,5 +1,6 @@ +# pylint: disable=line-too-long + import datetime -import json import psycopg2 import pytz @@ -8,49 +9,50 @@ CREATE_PROBE_GUID_INDEX = 'CREATE INDEX ON builtin_communicationeventprobe(guid);' CREATE_PROBE_UTC_LOGGED_INDEX = 'CREATE INDEX ON builtin_communicationeventprobe(utc_logged);' + def exists(connection_str, user_id, reading): conn = psycopg2.connect(connection_str) - - if probe_table_exists(conn) == False: + + if probe_table_exists(conn) is False: conn.close() - + return False - + cursor = conn.cursor() cursor.execute('SELECT id FROM builtin_communicationeventprobe WHERE (user_id = %s AND guid = %s);', (user_id, reading['GUID'])) - - exists = (cursor.rowcount > 0) - + + row_exists = (cursor.rowcount > 0) + cursor.close() conn.close() - - return exists + + return row_exists + def probe_table_exists(conn): cursor = conn.cursor() cursor.execute('SELECT table_name FROM information_schema.tables WHERE (table_schema = \'public\' AND table_name = \'builtin_communicationeventprobe\')') - - probe_table_exists = (cursor.rowcount > 0) - + + table_exists = (cursor.rowcount > 0) + cursor.close() - - return probe_table_exists -def insert(connection_str, user_id, reading): -# print(json.dumps(reading, indent=2)) - + return table_exists + + +def insert(connection_str, user_id, reading, check_exists=True): conn = psycopg2.connect(connection_str) cursor = conn.cursor() - - if probe_table_exists(conn) == False: + + if check_exists and probe_table_exists(conn) is False: cursor.execute(CREATE_PROBE_TABLE_SQL) cursor.execute(CREATE_PROBE_USER_ID_INDEX) cursor.execute(CREATE_PROBE_GUID_INDEX) cursor.execute(CREATE_PROBE_UTC_LOGGED_INDEX) - + conn.commit() - + reading_cmd = 'INSERT INTO builtin_communicationeventprobe(user_id, ' + \ 'guid, ' + \ 'timestamp, ' + \ @@ -62,18 +64,18 @@ def insert(connection_str, user_id, reading): 'comm_timestamp, ' + \ 'comm_timestamp_ts) VALUES (%s, %s, %s, %s, %s, %s, %s, %s, %s, %s) RETURNING id;' - cursor.execute(reading_cmd, (user_id, \ - reading['GUID'], \ - reading['TIMESTAMP'], \ - datetime.datetime.fromtimestamp(reading['TIMESTAMP'], tz=pytz.utc), \ - reading['NAME'], \ - reading['NUMBER'], \ - reading['COMMUNICATION_TYPE'], \ - reading['COMMUNICATION_DIRECTION'], \ - reading['COMM_TIMESTAMP'], \ + cursor.execute(reading_cmd, (user_id, + reading['GUID'], + reading['TIMESTAMP'], + datetime.datetime.fromtimestamp(reading['TIMESTAMP'], tz=pytz.utc), + reading['NAME'], + reading['NUMBER'], + reading['COMMUNICATION_TYPE'], + reading['COMMUNICATION_DIRECTION'], + reading['COMM_TIMESTAMP'], datetime.datetime.fromtimestamp((reading['COMM_TIMESTAMP'] / 1000), tz=pytz.utc))) conn.commit() - + cursor.close() conn.close() diff --git a/management/commands/extractors/builtin_communicationlogprobe.py b/management/commands/extractors/builtin_communicationlogprobe.py index 082aa29..8406de8 100644 --- a/management/commands/extractors/builtin_communicationlogprobe.py +++ b/management/commands/extractors/builtin_communicationlogprobe.py @@ -1,5 +1,6 @@ +# pylint: disable=line-too-long + import datetime -import json import psycopg2 import pytz @@ -13,64 +14,66 @@ CREATE_CALL_READING_ID_INDEX = 'CREATE INDEX ON builtin_communicationlogprobe_call(reading_id);' CREATE_CALL_UTC_LOGGED_INDEX = 'CREATE INDEX ON builtin_communicationlogprobe_call(utc_logged);' + def exists(connection_str, user_id, reading): conn = psycopg2.connect(connection_str) - - if probe_table_exists(conn) == False or call_table_exists(conn) == False: + + if probe_table_exists(conn) is False or call_table_exists(conn) is False: conn.close() return False cursor = conn.cursor() cursor.execute('SELECT id FROM builtin_communicationlogprobe WHERE (user_id = %s AND guid = %s);', (user_id, reading['GUID'])) - - exists = (cursor.rowcount > 0) - + + row_exists = (cursor.rowcount > 0) + cursor.close() conn.close() - - return exists + + return row_exists + def probe_table_exists(conn): cursor = conn.cursor() cursor.execute('SELECT table_name FROM information_schema.tables WHERE (table_schema = \'public\' AND table_name = \'builtin_communicationlogprobe\')') - - probe_table_exists = (cursor.rowcount > 0) - + + table_exists = (cursor.rowcount > 0) + cursor.close() - - return probe_table_exists + + return table_exists + def call_table_exists(conn): cursor = conn.cursor() cursor.execute('SELECT table_name FROM information_schema.tables WHERE (table_schema = \'public\' AND table_name = \'builtin_communicationlogprobe_call\')') - - activities_table_exists = (cursor.rowcount > 0) - + + table_exists = (cursor.rowcount > 0) + cursor.close() - - return activities_table_exists -def insert(connection_str, user_id, reading): -# print(json.dumps(reading, indent=2)) - + return table_exists + + +def insert(connection_str, user_id, reading, check_exists=True): conn = psycopg2.connect(connection_str) cursor = conn.cursor() - - if probe_table_exists(conn) == False: + + if check_exists and probe_table_exists(conn) is False: cursor.execute(CREATE_PROBE_TABLE_SQL) cursor.execute(CREATE_PROBE_USER_ID_INDEX) cursor.execute(CREATE_PROBE_GUID_INDEX) cursor.execute(CREATE_PROBE_UTC_LOGGED_INDEX) - - if call_table_exists(conn) == False: + + if check_exists and call_table_exists(conn) is False: cursor.execute(CREATE_CALL_TABLE_SQL) cursor.execute(CREATE_CALL_USER_ID_INDEX) cursor.execute(CREATE_CALL_READING_ID_INDEX) cursor.execute(CREATE_CALL_UTC_LOGGED_INDEX) - + conn.commit() - + reading_cmd = 'INSERT INTO builtin_communicationlogprobe(user_id, ' + \ 'guid, ' + \ 'timestamp, ' + \ @@ -85,65 +88,58 @@ def insert(connection_str, user_id, reading): 'recent_time_utc, ' + \ 'sms_outgoing_count, ' + \ 'sms_incoming_count) VALUES (%s, %s, %s, %s, %s, %s, %s, %s, %s, %s, %s, %s, %s, %s) RETURNING id;' - recent_caller = None recent_number = None recent_time = None recent_datetime = None - + if 'RECENT_CALLER' in reading: recent_caller = reading['RECENT_CALLER'] recent_number = reading['RECENT_NUMBER'] recent_time = reading['RECENT_TIME'] recent_datetime = datetime.datetime.fromtimestamp((reading['RECENT_TIME'] / 1000), tz=pytz.utc) - - cursor.execute(reading_cmd, (user_id, \ - reading['GUID'], \ - reading['TIMESTAMP'], \ - datetime.datetime.fromtimestamp(reading['TIMESTAMP'], tz=pytz.utc), \ - reading['CALL_TOTAL_COUNT'], \ - reading['CALL_INCOMING_COUNT'], \ - reading['CALL_OUTGOING_COUNT'], \ - reading['CALL_MISSED_COUNT'], \ - recent_caller, \ - recent_number, \ - recent_time, \ - recent_datetime, \ - reading['SMS_OUTGOING_COUNT'], \ + + cursor.execute(reading_cmd, (user_id, + reading['GUID'], + reading['TIMESTAMP'], + datetime.datetime.fromtimestamp(reading['TIMESTAMP'], tz=pytz.utc), + reading['CALL_TOTAL_COUNT'], + reading['CALL_INCOMING_COUNT'], + reading['CALL_OUTGOING_COUNT'], + reading['CALL_MISSED_COUNT'], + recent_caller, + recent_number, + recent_time, + recent_datetime, + reading['SMS_OUTGOING_COUNT'], reading['SMS_INCOMING_COUNT'])) - + for row in cursor.fetchall(): reading_id = row[0] - + for call in reading['PHONE_CALLS']: - existing_query = 'SELECT id FROM builtin_communicationlogprobe_call WHERE (user_id = %s AND number_name = %s AND call_timestamp = %s);' - values = (user_id, call['NUMBER_NAME'], call['CALL_TIMESTAMP']) - call_cursor = conn.cursor() - call_cursor.execute(existing_query, values) - - if call_cursor.rowcount == 0: - call_cmd = 'INSERT INTO builtin_communicationlogprobe_call(user_id, ' + \ - 'reading_id, ' + \ - 'utc_logged, ' + \ - 'number_name, ' + \ - 'number_label, ' + \ - 'call_duration, ' + \ - 'number_type, ' + \ - 'call_timestamp, ' + \ - 'call_timestamp_utc) VALUES (%s, %s, %s, %s, %s, %s, %s, %s, %s);' - - call_cursor.execute(call_cmd, (user_id, \ - reading_id, - datetime.datetime.fromtimestamp(reading['TIMESTAMP'], tz=pytz.utc), - call['NUMBER_NAME'], - call['NUMBER_LABEL'], - call['CALL_DURATION'], - call['NUMBER_TYPE'], - call['CALL_TIMESTAMP'], - datetime.datetime.fromtimestamp((call['CALL_TIMESTAMP'] / 1000), tz=pytz.utc))) + call_cmd = 'INSERT INTO builtin_communicationlogprobe_call(user_id, ' + \ + 'reading_id, ' + \ + 'utc_logged, ' + \ + 'number_name, ' + \ + 'number_label, ' + \ + 'call_duration, ' + \ + 'number_type, ' + \ + 'call_timestamp, ' + \ + 'call_timestamp_utc) VALUES (%s, %s, %s, %s, %s, %s, %s, %s, %s);' + + call_cursor.execute(call_cmd, (user_id, + reading_id, + datetime.datetime.fromtimestamp(reading['TIMESTAMP'], tz=pytz.utc), + call['NUMBER_NAME'], + call['NUMBER_LABEL'], + call['CALL_DURATION'], + call['NUMBER_TYPE'], + call['CALL_TIMESTAMP'], + datetime.datetime.fromtimestamp((call['CALL_TIMESTAMP'] / 1000), tz=pytz.utc))) conn.commit() - + cursor.close() conn.close() diff --git a/management/commands/extractors/builtin_datecalendarprobe.py b/management/commands/extractors/builtin_datecalendarprobe.py index 7623676..71e842d 100644 --- a/management/commands/extractors/builtin_datecalendarprobe.py +++ b/management/commands/extractors/builtin_datecalendarprobe.py @@ -1,5 +1,6 @@ +# pylint: disable=line-too-long + import datetime -import json import psycopg2 import pytz @@ -8,48 +9,49 @@ CREATE_PROBE_GUID_INDEX = 'CREATE INDEX ON builtin_datecalendarprobe(guid);' CREATE_PROBE_UTC_LOGGED_INDEX = 'CREATE INDEX ON builtin_datecalendarprobe(utc_logged);' + def exists(connection_str, user_id, reading): conn = psycopg2.connect(connection_str) - - if probe_table_exists(conn) == False: + + if probe_table_exists(conn) is False: conn.close() return False cursor = conn.cursor() - + cursor.execute('SELECT id FROM builtin_datecalendarprobe WHERE (user_id = %s AND guid = %s);', (user_id, reading['GUID'])) - - exists = (cursor.rowcount > 0) - + + row_exists = (cursor.rowcount > 0) + cursor.close() conn.close() - - return exists + + return row_exists + def probe_table_exists(conn): cursor = conn.cursor() cursor.execute('SELECT table_name FROM information_schema.tables WHERE (table_schema = \'public\' AND table_name = \'builtin_datecalendarprobe\')') - - probe_table_exists = (cursor.rowcount > 0) - + + table_exists = (cursor.rowcount > 0) + cursor.close() - - return probe_table_exists -def insert(connection_str, user_id, reading): -# print(json.dumps(reading, indent=2)) - + return table_exists + + +def insert(connection_str, user_id, reading, check_exists=True): conn = psycopg2.connect(connection_str) cursor = conn.cursor() - - if probe_table_exists(conn) == False: + + if check_exists and probe_table_exists(conn) is False: cursor.execute(CREATE_PROBE_TABLE_SQL) cursor.execute(CREATE_PROBE_USER_ID_INDEX) cursor.execute(CREATE_PROBE_GUID_INDEX) cursor.execute(CREATE_PROBE_UTC_LOGGED_INDEX) - + conn.commit() - + reading_cmd = 'INSERT INTO builtin_datecalendarprobe(user_id, ' + \ 'guid, ' + \ 'timestamp, ' + \ @@ -64,22 +66,21 @@ def insert(connection_str, user_id, reading): 'hour_of_day, ' + \ 'minute, ' + \ 'dst_offset) VALUES (%s, %s, %s, %s, %s, %s, %s, %s, %s, %s, %s, %s, %s, %s) RETURNING id;' - - cursor.execute(reading_cmd, (user_id, \ - reading['GUID'], \ - reading['TIMESTAMP'], \ - datetime.datetime.fromtimestamp(reading['TIMESTAMP'], tz=pytz.utc), \ - reading['MONTH'], \ - reading['DAY_OF_YEAR'], \ - reading['WEEK_OF_YEAR'], \ - reading['WEEK_OF_MONTH'], \ - reading['DAY_OF_MONTH'], \ - reading['DAY_OF_WEEK_IN_MONTH'], \ - reading['DAY_OF_WEEK'], \ - reading['HOUR_OF_DAY'], \ - reading['MINUTE'], \ + cursor.execute(reading_cmd, (user_id, + reading['GUID'], + reading['TIMESTAMP'], + datetime.datetime.fromtimestamp(reading['TIMESTAMP'], tz=pytz.utc), + reading['MONTH'], + reading['DAY_OF_YEAR'], + reading['WEEK_OF_YEAR'], + reading['WEEK_OF_MONTH'], + reading['DAY_OF_MONTH'], + reading['DAY_OF_WEEK_IN_MONTH'], + reading['DAY_OF_WEEK'], + reading['HOUR_OF_DAY'], + reading['MINUTE'], reading['DST_OFFSET'])) conn.commit() - + cursor.close() conn.close() diff --git a/management/commands/extractors/builtin_default.py b/management/commands/extractors/builtin_default.py index 1487fe2..8cb6d9c 100644 --- a/management/commands/extractors/builtin_default.py +++ b/management/commands/extractors/builtin_default.py @@ -1,10 +1,11 @@ -import datetime +# pylint: disable=line-too-long, unused-argument + import json -import psycopg2 -import pytz -def exists(connection_str, user_id, reading): + +def exists(connection_str, user_id, reading, check_exists=True): return False + def insert(connection_str, user_id, reading): - print(json.dumps(reading, indent=2)) + print json.dumps(reading, indent=2) diff --git a/management/commands/extractors/builtin_fusedlocationprobe.py b/management/commands/extractors/builtin_fusedlocationprobe.py index 2b5229b..f542bf0 100644 --- a/management/commands/extractors/builtin_fusedlocationprobe.py +++ b/management/commands/extractors/builtin_fusedlocationprobe.py @@ -1,5 +1,6 @@ +# pylint: disable=line-too-long + import datetime -import json import psycopg2 import pytz @@ -8,48 +9,49 @@ CREATE_PROBE_GUID_INDEX = 'CREATE INDEX ON builtin_fusedlocationprobe(guid);' CREATE_PROBE_UTC_LOGGED_INDEX = 'CREATE INDEX ON builtin_fusedlocationprobe(utc_logged);' + def exists(connection_str, user_id, reading): conn = psycopg2.connect(connection_str) - if probe_table_exists(conn) == False: + if probe_table_exists(conn) is False: conn.close() return False cursor = conn.cursor() cursor.execute('SELECT id FROM builtin_fusedlocationprobe WHERE (user_id = %s AND guid = %s);', (user_id, reading['GUID'])) - - exists = (cursor.rowcount > 0) - + + row_exists = (cursor.rowcount > 0) + cursor.close() conn.close() - - return exists + + return row_exists + def probe_table_exists(conn): cursor = conn.cursor() cursor.execute('SELECT table_name FROM information_schema.tables WHERE (table_schema = \'public\' AND table_name = \'builtin_fusedlocationprobe\')') - - probe_table_exists = (cursor.rowcount > 0) - + + table_exists = (cursor.rowcount > 0) + cursor.close() - - return probe_table_exists -def insert(connection_str, user_id, reading): -# print(json.dumps(reading, indent=2)) - + return table_exists + + +def insert(connection_str, user_id, reading, check_exists=True): conn = psycopg2.connect(connection_str) cursor = conn.cursor() - - if probe_table_exists(conn) == False: + + if check_exists and probe_table_exists(conn) is False: cursor.execute(CREATE_PROBE_TABLE_SQL) cursor.execute(CREATE_PROBE_USER_ID_INDEX) cursor.execute(CREATE_PROBE_GUID_INDEX) cursor.execute(CREATE_PROBE_UTC_LOGGED_INDEX) - + conn.commit() - + reading_cmd = 'INSERT INTO builtin_fusedlocationprobe(user_id, ' + \ 'guid, ' + \ 'timestamp, ' + \ @@ -59,13 +61,12 @@ def insert(connection_str, user_id, reading): 'altitude, ' + \ 'accuracy, ' + \ 'provider) VALUES (%s, %s, %s, %s, %s, %s, %s, %s, %s) RETURNING id;' - latitude = None longitude = None timestamp = None provider = None accuracy = None - + if 'LATITUDE' in reading: latitude = reading['LATITUDE'] @@ -74,29 +75,29 @@ def insert(connection_str, user_id, reading): if 'TIMESTAMP' in reading: timestamp = reading['TIMESTAMP'] - + if 'PROVIDER' in reading: provider = reading['PROVIDER'] if 'ACCURACY' in reading: accuracy = reading['ACCURACY'] - - if timestamp != None: - values = [ user_id, reading['GUID'], timestamp, datetime.datetime.fromtimestamp(timestamp, tz=pytz.utc), latitude, longitude ] + + if timestamp is not None: + values = [user_id, reading['GUID'], timestamp, datetime.datetime.fromtimestamp(timestamp, tz=pytz.utc), latitude, longitude] else: - values = [ user_id, reading['GUID'], None, None, latitude, longitude ] - + values = [user_id, reading['GUID'], None, None, latitude, longitude] + if 'ALTITUDE' in reading: values.append(reading['ALTITUDE']) else: values.append(None) - + values.append(accuracy) values.append(provider) - + cursor.execute(reading_cmd, values) conn.commit() - + cursor.close() conn.close() diff --git a/management/commands/extractors/builtin_gyroscopeprobe.py b/management/commands/extractors/builtin_gyroscopeprobe.py index f1e1f6a..8a31b70 100644 --- a/management/commands/extractors/builtin_gyroscopeprobe.py +++ b/management/commands/extractors/builtin_gyroscopeprobe.py @@ -1,5 +1,6 @@ +# pylint: disable=line-too-long + import datetime -import json import psycopg2 import pytz @@ -13,64 +14,66 @@ CREATE_READING_READING_ID_INDEX = 'CREATE INDEX ON builtin_gyroscopeprobe_reading(reading_id);' CREATE_READING_UTC_LOGGED_INDEX = 'CREATE INDEX ON builtin_gyroscopeprobe_reading(utc_logged);' + def exists(connection_str, user_id, reading): conn = psycopg2.connect(connection_str) - - if probe_table_exists(conn) == False: + + if probe_table_exists(conn) is False: conn.close() return False cursor = conn.cursor() cursor.execute('SELECT id FROM builtin_gyroscopeprobe WHERE (user_id = %s AND guid = %s);', (user_id, reading['GUID'])) - - exists = (cursor.rowcount > 0) - + + row_exists = (cursor.rowcount > 0) + cursor.close() conn.close() - - return exists + + return row_exists + def probe_table_exists(conn): cursor = conn.cursor() cursor.execute('SELECT table_name FROM information_schema.tables WHERE (table_schema = \'public\' AND table_name = \'builtin_gyroscopeprobe\')') - - exists = (cursor.rowcount > 0) - + + table_exists = (cursor.rowcount > 0) + cursor.close() - - return exists + + return table_exists + def reading_table_exists(conn): cursor = conn.cursor() cursor.execute('SELECT table_name FROM information_schema.tables WHERE (table_schema = \'public\' AND table_name = \'builtin_gyroscopeprobe_reading\')') - - exists = (cursor.rowcount > 0) - + + table_exists = (cursor.rowcount > 0) + cursor.close() - - return exists -def insert(connection_str, user_id, reading): -# print(json.dumps(reading, indent=2)) - + return table_exists + + +def insert(connection_str, user_id, reading, check_exists=True): conn = psycopg2.connect(connection_str) cursor = conn.cursor() - - if probe_table_exists(conn) == False: + + if check_exists and probe_table_exists(conn) is False: cursor.execute(CREATE_PROBE_TABLE_SQL) cursor.execute(CREATE_PROBE_USER_ID_INDEX) cursor.execute(CREATE_PROBE_GUID_INDEX) cursor.execute(CREATE_PROBE_UTC_LOGGED_INDEX) - - if reading_table_exists(conn) == False: + + if check_exists and reading_table_exists(conn) is False: cursor.execute(CREATE_READING_TABLE_SQL) cursor.execute(CREATE_READING_USER_ID_INDEX) cursor.execute(CREATE_READING_READING_ID_INDEX) cursor.execute(CREATE_READING_UTC_LOGGED_INDEX) - + conn.commit() - + reading_cmd = 'INSERT INTO builtin_gyroscopeprobe(user_id, ' + \ 'guid, ' + \ 'timestamp, ' + \ @@ -82,29 +85,28 @@ def insert(connection_str, user_id, reading): 'sensor_version, ' + \ 'sensor_resolution, ' + \ 'sensor_maximum_range) VALUES (%s, %s, %s, %s, %s, %s, %s, %s, %s, %s, %s) RETURNING id;' - - cursor.execute(reading_cmd, (user_id, \ - reading['GUID'], \ - reading['TIMESTAMP'], \ - datetime.datetime.fromtimestamp(reading['TIMESTAMP'], tz=pytz.utc), \ - reading['SENSOR']['VENDOR'], \ - reading['SENSOR']['NAME'], \ - reading['SENSOR']['POWER'], \ - reading['SENSOR']['TYPE'], \ - reading['SENSOR']['VERSION'], \ - reading['SENSOR']['RESOLUTION'], \ + cursor.execute(reading_cmd, (user_id, + reading['GUID'], + reading['TIMESTAMP'], + datetime.datetime.fromtimestamp(reading['TIMESTAMP'], tz=pytz.utc), + reading['SENSOR']['VENDOR'], + reading['SENSOR']['NAME'], + reading['SENSOR']['POWER'], + reading['SENSOR']['TYPE'], + reading['SENSOR']['VERSION'], + reading['SENSOR']['RESOLUTION'], reading['SENSOR']['MAXIMUM_RANGE'])) - + for row in cursor.fetchall(): reading_id = row[0] - + readings_len = len(reading['EVENT_TIMESTAMP']) - + has_sensor = ('SENSOR_TIMESTAMP' in reading) has_normalized = ('NORMALIZED_TIMESTAMP' in reading) - + reading_cursor = conn.cursor() - + for i in range(0, readings_len): reading_cmd = 'INSERT INTO builtin_gyroscopeprobe_reading(user_id, ' + \ 'reading_id, ' + \ @@ -119,15 +121,15 @@ def insert(connection_str, user_id, reading): 'y, ' + \ 'z, ' + \ 'accuracy) VALUES (%s, %s, %s, %s, %s, %s, %s, %s, %s, %s, %s, %s, %s);' - - values = [ user_id, reading_id, datetime.datetime.fromtimestamp(reading['TIMESTAMP'], tz=pytz.utc) ] - + + values = [user_id, reading_id, datetime.datetime.fromtimestamp(reading['TIMESTAMP'], tz=pytz.utc)] + values.append(reading['EVENT_TIMESTAMP'][i]) values.append(datetime.datetime.fromtimestamp(reading['EVENT_TIMESTAMP'][i], tz=pytz.utc)) - + if has_sensor: values.append(reading['SENSOR_TIMESTAMP'][i]) - + try: values.append(datetime.datetime.fromtimestamp((reading['SENSOR_TIMESTAMP'][i] / 1000), tz=pytz.utc)) except ValueError: @@ -142,15 +144,15 @@ def insert(connection_str, user_id, reading): else: values.append(None) values.append(None) - + values.append(reading['X'][i]) values.append(reading['Y'][i]) values.append(reading['Z'][i]) values.append(reading['ACCURACY'][i]) - + reading_cursor.execute(reading_cmd, values) conn.commit() - + cursor.close() conn.close() diff --git a/management/commands/extractors/builtin_hardwareinformationprobe.py b/management/commands/extractors/builtin_hardwareinformationprobe.py index e9d32aa..6c83d8b 100644 --- a/management/commands/extractors/builtin_hardwareinformationprobe.py +++ b/management/commands/extractors/builtin_hardwareinformationprobe.py @@ -1,5 +1,6 @@ +# pylint: disable=line-too-long + import datetime -import json import psycopg2 import pytz @@ -8,49 +9,49 @@ CREATE_PROBE_GUID_INDEX = 'CREATE INDEX ON builtin_hardwareinformationprobe(guid);' CREATE_PROBE_UTC_LOGGED_INDEX = 'CREATE INDEX ON builtin_hardwareinformationprobe(utc_logged);' + def exists(connection_str, user_id, reading): conn = psycopg2.connect(connection_str) - - if probe_table_exists(conn) == False: + + if probe_table_exists(conn) is False: conn.close() - return False - + cursor = conn.cursor() cursor.execute('SELECT id FROM builtin_hardwareinformationprobe WHERE (user_id = %s AND guid = %s);', (user_id, reading['GUID'])) - - exists = (cursor.rowcount > 0) - + + row_exists = (cursor.rowcount > 0) + cursor.close() conn.close() - - return exists + + return row_exists + def probe_table_exists(conn): cursor = conn.cursor() cursor.execute('SELECT table_name FROM information_schema.tables WHERE (table_schema = \'public\' AND table_name = \'builtin_hardwareinformationprobe\')') - - probe_table_exists = (cursor.rowcount > 0) - + + table_exists = (cursor.rowcount > 0) + cursor.close() - - return probe_table_exists -def insert(connection_str, user_id, reading): -# print(json.dumps(reading, indent=2)) - + return table_exists + + +def insert(connection_str, user_id, reading, check_exists=True): conn = psycopg2.connect(connection_str) cursor = conn.cursor() - - if probe_table_exists(conn) == False: + + if check_exists and probe_table_exists(conn) is False: cursor.execute(CREATE_PROBE_TABLE_SQL) cursor.execute(CREATE_PROBE_USER_ID_INDEX) cursor.execute(CREATE_PROBE_GUID_INDEX) cursor.execute(CREATE_PROBE_UTC_LOGGED_INDEX) - + conn.commit() - + reading_cmd = 'INSERT INTO builtin_hardwareinformationprobe(user_id, ' + \ 'guid, ' + \ 'timestamp, ' + \ @@ -70,42 +71,40 @@ def insert(connection_str, user_id, reading): 'display, ' + \ 'device_id, ' + \ 'manufacturer) VALUES (%s, %s, %s, %s, %s, %s, %s, %s, %s, %s, %s, %s, %s, %s, %s, %s, %s, %s, %s) RETURNING id;' - mobile_id = None bluetooth_mac = None - + if 'MOBILE_ID' in reading: mobile_id = reading['MOBILE_ID'] if 'BLUETOOTH_MAC' in reading: bluetooth_mac = reading['BLUETOOTH_MAC'] - + wifi_mac = None - + if 'WIFI_MAC' in reading: wifi_mac = reading['WIFI_MAC'] - - cursor.execute(reading_cmd, (user_id, \ - reading['GUID'], \ - reading['TIMESTAMP'], \ - datetime.datetime.fromtimestamp(reading['TIMESTAMP'], tz=pytz.utc), \ - reading['PRODUCT'], \ - reading['BOOTLOADER'], \ - reading['BRAND'], \ - mobile_id, \ - reading['HARDWARE'], \ - reading['HOST'], \ - bluetooth_mac, \ - reading['BOARD'], \ - reading['FINGERPRINT'], \ - reading['DEVICE'], \ - reading['MODEL'], \ - wifi_mac, \ - reading['DISPLAY'], \ - reading['ID'], \ - reading['MANUFACTURER'])) + cursor.execute(reading_cmd, (user_id, + reading['GUID'], + reading['TIMESTAMP'], + datetime.datetime.fromtimestamp(reading['TIMESTAMP'], tz=pytz.utc), + reading['PRODUCT'], + reading['BOOTLOADER'], + reading['BRAND'], + mobile_id, + reading['HARDWARE'], + reading['HOST'], + bluetooth_mac, + reading['BOARD'], + reading['FINGERPRINT'], + reading['DEVICE'], + reading['MODEL'], + wifi_mac, + reading['DISPLAY'], + reading['ID'], + reading['MANUFACTURER'])) conn.commit() - + cursor.close() conn.close() diff --git a/management/commands/extractors/builtin_lightprobe.py b/management/commands/extractors/builtin_lightprobe.py index 3a19ef2..fa71b18 100644 --- a/management/commands/extractors/builtin_lightprobe.py +++ b/management/commands/extractors/builtin_lightprobe.py @@ -1,5 +1,6 @@ +# pylint: disable=line-too-long + import datetime -import json import psycopg2 import pytz @@ -13,64 +14,66 @@ CREATE_READING_READING_ID_INDEX = 'CREATE INDEX ON builtin_lightprobe_reading(reading_id);' CREATE_READING_UTC_LOGGED_INDEX = 'CREATE INDEX ON builtin_lightprobe_reading(utc_logged);' + def exists(connection_str, user_id, reading): conn = psycopg2.connect(connection_str) - - if probe_table_exists(conn) == False: + + if probe_table_exists(conn) is False: conn.close() return False - + cursor = conn.cursor() cursor.execute('SELECT id FROM builtin_lightprobe WHERE (user_id = %s AND guid = %s);', (user_id, reading['GUID'])) - - exists = (cursor.rowcount > 0) - + + row_exists = (cursor.rowcount > 0) + cursor.close() conn.close() - - return exists + + return row_exists + def probe_table_exists(conn): cursor = conn.cursor() cursor.execute('SELECT table_name FROM information_schema.tables WHERE (table_schema = \'public\' AND table_name = \'builtin_lightprobe\')') - - exists = (cursor.rowcount > 0) - + + table_exists = (cursor.rowcount > 0) + cursor.close() - - return exists + + return table_exists + def reading_table_exists(conn): cursor = conn.cursor() cursor.execute('SELECT table_name FROM information_schema.tables WHERE (table_schema = \'public\' AND table_name = \'builtin_lightprobe_reading\')') - - exists = (cursor.rowcount > 0) - + + table_exists = (cursor.rowcount > 0) + cursor.close() - - return exists -def insert(connection_str, user_id, reading): -# print(json.dumps(reading, indent=2)) - + return table_exists + + +def insert(connection_str, user_id, reading, check_exists=True): conn = psycopg2.connect(connection_str) cursor = conn.cursor() - - if probe_table_exists(conn) == False: + + if check_exists and probe_table_exists(conn) is False: cursor.execute(CREATE_PROBE_TABLE_SQL) cursor.execute(CREATE_PROBE_USER_ID_INDEX) cursor.execute(CREATE_PROBE_GUID_INDEX) cursor.execute(CREATE_PROBE_UTC_LOGGED_INDEX) - - if reading_table_exists(conn) == False: + + if check_exists and reading_table_exists(conn) is False: cursor.execute(CREATE_READING_TABLE_SQL) cursor.execute(CREATE_READING_USER_ID_INDEX) cursor.execute(CREATE_READING_READING_ID_INDEX) cursor.execute(CREATE_READING_UTC_LOGGED_INDEX) - + conn.commit() - + reading_cmd = 'INSERT INTO builtin_lightprobe(user_id, ' + \ 'guid, ' + \ 'timestamp, ' + \ @@ -82,29 +85,28 @@ def insert(connection_str, user_id, reading): 'sensor_version, ' + \ 'sensor_resolution, ' + \ 'sensor_maximum_range) VALUES (%s, %s, %s, %s, %s, %s, %s, %s, %s, %s, %s) RETURNING id;' - - cursor.execute(reading_cmd, (user_id, \ - reading['GUID'], \ - reading['TIMESTAMP'], \ - datetime.datetime.fromtimestamp(reading['TIMESTAMP'], tz=pytz.utc), \ - reading['SENSOR']['VENDOR'], \ - reading['SENSOR']['NAME'], \ - reading['SENSOR']['POWER'], \ - reading['SENSOR']['TYPE'], \ - reading['SENSOR']['VERSION'], \ - reading['SENSOR']['RESOLUTION'], \ + cursor.execute(reading_cmd, (user_id, + reading['GUID'], + reading['TIMESTAMP'], + datetime.datetime.fromtimestamp(reading['TIMESTAMP'], tz=pytz.utc), + reading['SENSOR']['VENDOR'], + reading['SENSOR']['NAME'], + reading['SENSOR']['POWER'], + reading['SENSOR']['TYPE'], + reading['SENSOR']['VERSION'], + reading['SENSOR']['RESOLUTION'], reading['SENSOR']['MAXIMUM_RANGE'])) - + for row in cursor.fetchall(): reading_id = row[0] - + readings_len = len(reading['EVENT_TIMESTAMP']) - + has_sensor = ('SENSOR_TIMESTAMP' in reading) has_normalized = ('NORMALIZED_TIMESTAMP' in reading) - + reading_cursor = conn.cursor() - + for i in range(0, readings_len): reading_cmd = 'INSERT INTO builtin_lightprobe_reading(user_id, ' + \ 'reading_id, ' + \ @@ -117,15 +119,15 @@ def insert(connection_str, user_id, reading): 'normalized_timestamp_utc, ' + \ 'lux, ' + \ 'accuracy) VALUES (%s, %s, %s, %s, %s, %s, %s, %s, %s, %s, %s);' - - values = [ user_id, reading_id, datetime.datetime.fromtimestamp(reading['TIMESTAMP'], tz=pytz.utc) ] - + + values = [user_id, reading_id, datetime.datetime.fromtimestamp(reading['TIMESTAMP'], tz=pytz.utc)] + values.append(reading['EVENT_TIMESTAMP'][i]) values.append(datetime.datetime.fromtimestamp(reading['EVENT_TIMESTAMP'][i], tz=pytz.utc)) - + if has_sensor: values.append(reading['SENSOR_TIMESTAMP'][i]) - + try: values.append(datetime.datetime.fromtimestamp((reading['SENSOR_TIMESTAMP'][i] / 1000), tz=pytz.utc)) except ValueError: @@ -140,13 +142,13 @@ def insert(connection_str, user_id, reading): else: values.append(None) values.append(None) - + values.append(reading['LUX'][i]) values.append(reading['ACCURACY'][i]) - + reading_cursor.execute(reading_cmd, values) conn.commit() - + cursor.close() conn.close() diff --git a/management/commands/extractors/builtin_locationprobe.py b/management/commands/extractors/builtin_locationprobe.py index 6564d62..17c4183 100644 --- a/management/commands/extractors/builtin_locationprobe.py +++ b/management/commands/extractors/builtin_locationprobe.py @@ -1,5 +1,6 @@ +# pylint: disable=line-too-long + import datetime -import json import psycopg2 import pytz @@ -8,48 +9,49 @@ CREATE_PROBE_GUID_INDEX = 'CREATE INDEX ON builtin_locationprobe(guid);' CREATE_PROBE_UTC_LOGGED_INDEX = 'CREATE INDEX ON builtin_locationprobe(utc_logged);' + def exists(connection_str, user_id, reading): conn = psycopg2.connect(connection_str) - if probe_table_exists(conn) == False: + if probe_table_exists(conn) is False: conn.close() return False cursor = conn.cursor() cursor.execute('SELECT id FROM builtin_locationprobe WHERE (user_id = %s AND guid = %s);', (user_id, reading['GUID'])) - - exists = (cursor.rowcount > 0) - + + row_exists = (cursor.rowcount > 0) + cursor.close() conn.close() - - return exists + + return row_exists + def probe_table_exists(conn): cursor = conn.cursor() cursor.execute('SELECT table_name FROM information_schema.tables WHERE (table_schema = \'public\' AND table_name = \'builtin_locationprobe\')') - - probe_table_exists = (cursor.rowcount > 0) - + + table_exists = (cursor.rowcount > 0) + cursor.close() - - return probe_table_exists -def insert(connection_str, user_id, reading): -# print(json.dumps(reading, indent=2)) - + return table_exists + + +def insert(connection_str, user_id, reading, check_exists=True): conn = psycopg2.connect(connection_str) cursor = conn.cursor() - - if probe_table_exists(conn) == False: + + if check_exists and probe_table_exists(conn) is False: cursor.execute(CREATE_PROBE_TABLE_SQL) cursor.execute(CREATE_PROBE_USER_ID_INDEX) cursor.execute(CREATE_PROBE_GUID_INDEX) cursor.execute(CREATE_PROBE_UTC_LOGGED_INDEX) - + conn.commit() - + reading_cmd = 'INSERT INTO builtin_locationprobe(user_id, ' + \ 'guid, ' + \ 'timestamp, ' + \ @@ -62,14 +64,14 @@ def insert(connection_str, user_id, reading): 'network_available, ' + \ 'gps_available, ' + \ 'cluster) VALUES (%s, %s, %s, %s, %s, %s, %s, %s, %s, %s, %s, %s) RETURNING id;' - - values = [ user_id, reading['GUID'], reading['TIMESTAMP'], datetime.datetime.fromtimestamp(reading['TIMESTAMP'], tz=pytz.utc), reading['LATITUDE'], reading['LONGITUDE'] ] - + + values = [user_id, reading['GUID'], reading['TIMESTAMP'], datetime.datetime.fromtimestamp(reading['TIMESTAMP'], tz=pytz.utc), reading['LATITUDE'], reading['LONGITUDE']] + if 'ALTITUDE' in reading: values.append(reading['ALTITUDE']) else: values.append(None) - + values.append(reading['ACCURACY']) values.append(reading['PROVIDER']) values.append(reading['NETWORK_AVAILABLE']) @@ -79,10 +81,10 @@ def insert(connection_str, user_id, reading): values.append(reading['CLUSTER']) else: values.append(None) - + cursor.execute(reading_cmd, values) conn.commit() - + cursor.close() conn.close() diff --git a/management/commands/extractors/builtin_networkprobe.py b/management/commands/extractors/builtin_networkprobe.py index af14c82..3b7dc18 100644 --- a/management/commands/extractors/builtin_networkprobe.py +++ b/management/commands/extractors/builtin_networkprobe.py @@ -1,5 +1,6 @@ +# pylint: disable=line-too-long + import datetime -import json import psycopg2 import pytz @@ -8,49 +9,49 @@ CREATE_PROBE_GUID_INDEX = 'CREATE INDEX ON builtin_networkprobe(guid);' CREATE_PROBE_UTC_LOGGED_INDEX = 'CREATE INDEX ON builtin_networkprobe(utc_logged);' + def exists(connection_str, user_id, reading): conn = psycopg2.connect(connection_str) - - if probe_table_exists(conn) == False: + + if probe_table_exists(conn) is False: conn.close() return False cursor = conn.cursor() - + cursor.execute('SELECT id FROM builtin_networkprobe WHERE (user_id = %s AND guid = %s);', (user_id, reading['GUID'])) - - exists = (cursor.rowcount > 0) - + + row_exists = (cursor.rowcount > 0) + cursor.close() conn.close() - - return exists + + return row_exists + def probe_table_exists(conn): cursor = conn.cursor() cursor.execute('SELECT table_name FROM information_schema.tables WHERE (table_schema = \'public\' AND table_name = \'builtin_networkprobe\')') - - probe_table_exists = (cursor.rowcount > 0) - + + table_exists = (cursor.rowcount > 0) + cursor.close() - - return probe_table_exists -def insert(connection_str, user_id, reading): -# print('USER: '+ user_id) -# print(json.dumps(reading, indent=2)) - + return table_exists + + +def insert(connection_str, user_id, reading, check_exists=True): conn = psycopg2.connect(connection_str) cursor = conn.cursor() - - if probe_table_exists(conn) == False: + + if check_exists and probe_table_exists(conn) is False: cursor.execute(CREATE_PROBE_TABLE_SQL) cursor.execute(CREATE_PROBE_USER_ID_INDEX) cursor.execute(CREATE_PROBE_GUID_INDEX) cursor.execute(CREATE_PROBE_UTC_LOGGED_INDEX) - + conn.commit() - + reading_cmd = 'INSERT INTO builtin_networkprobe(user_id, ' + \ 'guid, ' + \ 'timestamp, ' + \ @@ -59,36 +60,36 @@ def insert(connection_str, user_id, reading): 'interface_name, ' + \ 'ip_address, ' + \ 'interface_display) VALUES (%s, %s, %s, %s, %s, %s, %s, %s) RETURNING id;' - + interface_name = None - + if 'INTERFACE_NAME' in reading: interface_name = reading['INTERFACE_NAME'] ip_address = None - + if 'IP_ADDRESS' in reading: ip_address = reading['IP_ADDRESS'] hostname = None - + if 'HOSTNAME' in reading: hostname = reading['HOSTNAME'] interface_display = None - + if 'INTERFACE_DISPLAY' in reading: interface_display = reading['INTERFACE_DISPLAY'] - - cursor.execute(reading_cmd, (user_id, \ - reading['GUID'], \ - reading['TIMESTAMP'], \ - datetime.datetime.fromtimestamp(reading['TIMESTAMP'], tz=pytz.utc), \ - hostname, \ - interface_name, \ - ip_address, \ + + cursor.execute(reading_cmd, (user_id, + reading['GUID'], + reading['TIMESTAMP'], + datetime.datetime.fromtimestamp(reading['TIMESTAMP'], tz=pytz.utc), + hostname, + interface_name, + ip_address, interface_display)) conn.commit() - + cursor.close() conn.close() diff --git a/management/commands/extractors/builtin_pressureprobe.py b/management/commands/extractors/builtin_pressureprobe.py index b244986..dbe3842 100644 --- a/management/commands/extractors/builtin_pressureprobe.py +++ b/management/commands/extractors/builtin_pressureprobe.py @@ -1,5 +1,6 @@ +# pylint: disable=line-too-long + import datetime -import json import psycopg2 import pytz @@ -13,64 +14,66 @@ CREATE_READING_READING_ID_INDEX = 'CREATE INDEX ON builtin_pressureprobe_reading(reading_id);' CREATE_READING_UTC_LOGGED_INDEX = 'CREATE INDEX ON builtin_pressureprobe_reading(utc_logged);' + def exists(connection_str, user_id, reading): conn = psycopg2.connect(connection_str) - - if probe_table_exists(conn) == False: + + if probe_table_exists(conn) is False: conn.close() return False - + cursor = conn.cursor() cursor.execute('SELECT id FROM builtin_pressureprobe WHERE (user_id = %s AND guid = %s);', (user_id, reading['GUID'])) - - exists = (cursor.rowcount > 0) - + + row_exists = (cursor.rowcount > 0) + cursor.close() conn.close() - - return exists + + return row_exists + def probe_table_exists(conn): cursor = conn.cursor() cursor.execute('SELECT table_name FROM information_schema.tables WHERE (table_schema = \'public\' AND table_name = \'builtin_pressureprobe\')') - - exists = (cursor.rowcount > 0) - + + table_exists = (cursor.rowcount > 0) + cursor.close() - - return exists + + return table_exists + def reading_table_exists(conn): cursor = conn.cursor() cursor.execute('SELECT table_name FROM information_schema.tables WHERE (table_schema = \'public\' AND table_name = \'builtin_pressureprobe_reading\')') - - exists = (cursor.rowcount > 0) - + + table_exists = (cursor.rowcount > 0) + cursor.close() - - return exists -def insert(connection_str, user_id, reading): -# print(json.dumps(reading, indent=2)) - + return table_exists + + +def insert(connection_str, user_id, reading, check_exists=True): conn = psycopg2.connect(connection_str) cursor = conn.cursor() - - if probe_table_exists(conn) == False: + + if check_exists and probe_table_exists(conn) is False: cursor.execute(CREATE_PROBE_TABLE_SQL) cursor.execute(CREATE_PROBE_USER_ID_INDEX) cursor.execute(CREATE_PROBE_GUID_INDEX) cursor.execute(CREATE_PROBE_UTC_LOGGED_INDEX) - - if reading_table_exists(conn) == False: + + if check_exists and reading_table_exists(conn) is False: cursor.execute(CREATE_READING_TABLE_SQL) cursor.execute(CREATE_READING_USER_ID_INDEX) cursor.execute(CREATE_READING_READING_ID_INDEX) cursor.execute(CREATE_READING_UTC_LOGGED_INDEX) - + conn.commit() - + reading_cmd = 'INSERT INTO builtin_pressureprobe(user_id, ' + \ 'guid, ' + \ 'timestamp, ' + \ @@ -82,29 +85,28 @@ def insert(connection_str, user_id, reading): 'sensor_version, ' + \ 'sensor_resolution, ' + \ 'sensor_maximum_range) VALUES (%s, %s, %s, %s, %s, %s, %s, %s, %s, %s, %s) RETURNING id;' - - cursor.execute(reading_cmd, (user_id, \ - reading['GUID'], \ - reading['TIMESTAMP'], \ - datetime.datetime.fromtimestamp(reading['TIMESTAMP'], tz=pytz.utc), \ - reading['SENSOR']['VENDOR'], \ - reading['SENSOR']['NAME'], \ - reading['SENSOR']['POWER'], \ - reading['SENSOR']['TYPE'], \ - reading['SENSOR']['VERSION'], \ - reading['SENSOR']['RESOLUTION'], \ + cursor.execute(reading_cmd, (user_id, + reading['GUID'], + reading['TIMESTAMP'], + datetime.datetime.fromtimestamp(reading['TIMESTAMP'], tz=pytz.utc), + reading['SENSOR']['VENDOR'], + reading['SENSOR']['NAME'], + reading['SENSOR']['POWER'], + reading['SENSOR']['TYPE'], + reading['SENSOR']['VERSION'], + reading['SENSOR']['RESOLUTION'], reading['SENSOR']['MAXIMUM_RANGE'])) - + for row in cursor.fetchall(): reading_id = row[0] - + readings_len = len(reading['EVENT_TIMESTAMP']) - + has_sensor = ('SENSOR_TIMESTAMP' in reading) has_normalized = ('NORMALIZED_TIMESTAMP' in reading) - + reading_cursor = conn.cursor() - + for i in range(0, readings_len): reading_cmd = 'INSERT INTO builtin_pressureprobe_reading(user_id, ' + \ 'reading_id, ' + \ @@ -118,15 +120,15 @@ def insert(connection_str, user_id, reading): 'pressure, ' + \ 'altitude, ' + \ 'accuracy) VALUES (%s, %s, %s, %s, %s, %s, %s, %s, %s, %s, %s, %s);' - - values = [ user_id, reading_id, datetime.datetime.fromtimestamp(reading['TIMESTAMP'], tz=pytz.utc) ] - + + values = [user_id, reading_id, datetime.datetime.fromtimestamp(reading['TIMESTAMP'], tz=pytz.utc)] + values.append(reading['EVENT_TIMESTAMP'][i]) values.append(datetime.datetime.fromtimestamp(reading['EVENT_TIMESTAMP'][i], tz=pytz.utc)) - + if has_sensor: values.append(reading['SENSOR_TIMESTAMP'][i]) - + try: values.append(datetime.datetime.fromtimestamp((reading['SENSOR_TIMESTAMP'][i] / 1000), tz=pytz.utc)) except ValueError: @@ -141,14 +143,14 @@ def insert(connection_str, user_id, reading): else: values.append(None) values.append(None) - + values.append(reading['PRESSURE'][i]) values.append(reading['ALTITUDE'][i]) values.append(reading['ACCURACY'][i]) - + reading_cursor.execute(reading_cmd, values) conn.commit() - + cursor.close() conn.close() diff --git a/management/commands/extractors/builtin_rawlocationprobe.py b/management/commands/extractors/builtin_rawlocationprobe.py index e4dfb13..dade607 100644 --- a/management/commands/extractors/builtin_rawlocationprobe.py +++ b/management/commands/extractors/builtin_rawlocationprobe.py @@ -1,5 +1,6 @@ +# pylint: disable=line-too-long + import datetime -import json import psycopg2 import pytz @@ -8,48 +9,49 @@ CREATE_PROBE_GUID_INDEX = 'CREATE INDEX ON builtin_rawlocationprobe(guid);' CREATE_PROBE_UTC_LOGGED_INDEX = 'CREATE INDEX ON builtin_rawlocationprobe(utc_logged);' + def exists(connection_str, user_id, reading): conn = psycopg2.connect(connection_str) - if probe_table_exists(conn) == False: + if probe_table_exists(conn) is False: conn.close() return False cursor = conn.cursor() cursor.execute('SELECT id FROM builtin_rawlocationprobe WHERE (user_id = %s AND guid = %s);', (user_id, reading['GUID'])) - - exists = (cursor.rowcount > 0) - + + row_exists = (cursor.rowcount > 0) + cursor.close() conn.close() - - return exists + + return row_exists + def probe_table_exists(conn): cursor = conn.cursor() cursor.execute('SELECT table_name FROM information_schema.tables WHERE (table_schema = \'public\' AND table_name = \'builtin_rawlocationprobe\')') - - probe_table_exists = (cursor.rowcount > 0) - + + table_exists = (cursor.rowcount > 0) + cursor.close() - - return probe_table_exists -def insert(connection_str, user_id, reading): -# print(json.dumps(reading, indent=2)) - + return table_exists + + +def insert(connection_str, user_id, reading, check_exists=True): conn = psycopg2.connect(connection_str) cursor = conn.cursor() - - if probe_table_exists(conn) == False: + + if check_exists and probe_table_exists(conn) is False: cursor.execute(CREATE_PROBE_TABLE_SQL) cursor.execute(CREATE_PROBE_USER_ID_INDEX) cursor.execute(CREATE_PROBE_GUID_INDEX) cursor.execute(CREATE_PROBE_UTC_LOGGED_INDEX) - + conn.commit() - + reading_cmd = 'INSERT INTO builtin_rawlocationprobe(user_id, ' + \ 'guid, ' + \ 'timestamp, ' + \ @@ -61,17 +63,17 @@ def insert(connection_str, user_id, reading): 'provider, ' + \ 'network_available, ' + \ 'gps_available) VALUES (%s, %s, %s, %s, %s, %s, %s, %s, %s, %s, %s) RETURNING id;' - - values = [ user_id, reading['GUID'], reading['TIMESTAMP'], datetime.datetime.fromtimestamp(reading['TIMESTAMP'], tz=pytz.utc), reading['LATITUDE'], reading['LONGITUDE'] ] - + + values = [user_id, reading['GUID'], reading['TIMESTAMP'], datetime.datetime.fromtimestamp(reading['TIMESTAMP'], tz=pytz.utc), reading['LATITUDE'], reading['LONGITUDE']] + if 'ALTITUDE' in reading: values.append(reading['ALTITUDE']) else: values.append(None) - + values.append(reading['ACCURACY']) values.append(reading['PROVIDER']) - + if 'NETWORK_AVAILABLE' in reading: values.append(reading['NETWORK_AVAILABLE']) else: @@ -81,10 +83,10 @@ def insert(connection_str, user_id, reading): values.append(reading['GPS_AVAILABLE']) else: values.append(None) - + cursor.execute(reading_cmd, values) conn.commit() - + cursor.close() conn.close() diff --git a/management/commands/extractors/builtin_rawlocationprobeeventlog.py b/management/commands/extractors/builtin_rawlocationprobeeventlog.py index 0f5ca55..932df27 100644 --- a/management/commands/extractors/builtin_rawlocationprobeeventlog.py +++ b/management/commands/extractors/builtin_rawlocationprobeeventlog.py @@ -1,5 +1,6 @@ +# pylint: disable=line-too-long + import datetime -import json import psycopg2 import pytz @@ -8,49 +9,50 @@ CREATE_PROBE_GUID_INDEX = 'CREATE INDEX ON builtin_rawlocationprobeeventlog(guid);' CREATE_PROBE_UTC_LOGGED_INDEX = 'CREATE INDEX ON builtin_rawlocationprobeeventlog(utc_logged);' + def exists(connection_str, user_id, reading): conn = psycopg2.connect(connection_str) - - if probe_table_exists(conn) == False: + + if probe_table_exists(conn) is False: conn.close() - + return False - + cursor = conn.cursor() cursor.execute('SELECT id FROM builtin_rawlocationprobeeventlog WHERE (user_id = %s AND guid = %s);', (user_id, reading['GUID'])) - - exists = (cursor.rowcount > 0) - + + row_exists = (cursor.rowcount > 0) + cursor.close() conn.close() - - return exists + + return row_exists + def probe_table_exists(conn): cursor = conn.cursor() cursor.execute('SELECT table_name FROM information_schema.tables WHERE (table_schema = \'public\' AND table_name = \'builtin_rawlocationprobeeventlog\')') - - probe_table_exists = (cursor.rowcount > 0) - + + table_exists = (cursor.rowcount > 0) + cursor.close() - - return probe_table_exists -def insert(connection_str, user_id, reading): -# print(json.dumps(reading, indent=2)) - + return table_exists + + +def insert(connection_str, user_id, reading, check_exists=True): conn = psycopg2.connect(connection_str) cursor = conn.cursor() - - if probe_table_exists(conn) == False: + + if check_exists and probe_table_exists(conn) is False: cursor.execute(CREATE_PROBE_TABLE_SQL) cursor.execute(CREATE_PROBE_USER_ID_INDEX) cursor.execute(CREATE_PROBE_GUID_INDEX) cursor.execute(CREATE_PROBE_UTC_LOGGED_INDEX) - + conn.commit() - + reading_cmd = 'INSERT INTO builtin_rawlocationprobeeventlog(user_id, ' + \ 'guid, ' + \ 'timestamp, ' + \ @@ -60,22 +62,21 @@ def insert(connection_str, user_id, reading): 'satellites) VALUES (%s, %s, %s, %s, %s, %s, %s) RETURNING id;' provider_status = None satellites = None - + if 'PROVIDER_STATUS' in reading: provider_status = reading['PROVIDER_STATUS'] if 'satellites' in reading: satellites = reading['satellites'] - - cursor.execute(reading_cmd, (user_id, \ - reading['GUID'], \ - reading['TIMESTAMP'], \ - datetime.datetime.fromtimestamp(reading['TIMESTAMP'], tz=pytz.utc), \ - provider_status, \ - reading['LOG_EVENT'], \ - satellites)) + cursor.execute(reading_cmd, (user_id, + reading['GUID'], + reading['TIMESTAMP'], + datetime.datetime.fromtimestamp(reading['TIMESTAMP'], tz=pytz.utc), + provider_status, + reading['LOG_EVENT'], + satellites)) conn.commit() - + cursor.close() conn.close() diff --git a/management/commands/extractors/builtin_robothealthprobe.py b/management/commands/extractors/builtin_robothealthprobe.py index 81f1982..4c757a4 100644 --- a/management/commands/extractors/builtin_robothealthprobe.py +++ b/management/commands/extractors/builtin_robothealthprobe.py @@ -1,3 +1,5 @@ +# pylint: disable=line-too-long + import datetime import json import psycopg2 @@ -54,80 +56,83 @@ CREATE_TRIGGER_READING_ID_INDEX = 'CREATE INDEX ON builtin_robothealthprobe_trigger(reading_id);' CREATE_TRIGGER_UTC_LOGGED_INDEX = 'CREATE INDEX ON builtin_robothealthprobe_trigger(utc_logged);' + def exists(connection_str, user_id, reading): conn = psycopg2.connect(connection_str) - if probe_table_exists(conn) == False or warning_table_exists(conn) == False or trigger_table_exists(conn) == False or trigger_table_exists(conn) == False: + if probe_table_exists(conn) is False or warning_table_exists(conn) is False or trigger_table_exists(conn) is False or trigger_table_exists(conn) is False: conn.close() return False cursor = conn.cursor() cursor.execute('SELECT id FROM builtin_robothealthprobe WHERE (user_id = %s AND guid = %s);', (user_id, reading['GUID'])) - - exists = (cursor.rowcount > 0) - + + row_exists = (cursor.rowcount > 0) + cursor.close() conn.close() - - return exists + + return row_exists + def probe_table_exists(conn): cursor = conn.cursor() cursor.execute('SELECT table_name FROM information_schema.tables WHERE (table_schema = \'public\' AND table_name = \'builtin_robothealthprobe\')') - - exists = (cursor.rowcount > 0) - + + table_exists = (cursor.rowcount > 0) + cursor.close() - - return exists + + return table_exists + def warning_table_exists(conn): cursor = conn.cursor() cursor.execute('SELECT table_name FROM information_schema.tables WHERE (table_schema = \'public\' AND table_name = \'builtin_robothealthprobe_warning\')') - - exists = (cursor.rowcount > 0) - + + table_exists = (cursor.rowcount > 0) + cursor.close() - - return exists + + return table_exists + def trigger_table_exists(conn): cursor = conn.cursor() cursor.execute('SELECT table_name FROM information_schema.tables WHERE (table_schema = \'public\' AND table_name = \'builtin_robothealthprobe_trigger\')') - - exists = (cursor.rowcount > 0) - + + table_exists = (cursor.rowcount > 0) + cursor.close() - - return exists -def insert(connection_str, user_id, reading): -# print(json.dumps(reading, indent=2)) - + return table_exists + + +def insert(connection_str, user_id, reading, check_exists=True): conn = psycopg2.connect(connection_str) cursor = conn.cursor() - - if probe_table_exists(conn) == False: + + if check_exists and probe_table_exists(conn) is False: cursor.execute(CREATE_PROBE_TABLE_SQL) cursor.execute(CREATE_PROBE_USER_ID_INDEX) cursor.execute(CREATE_PROBE_GUID_INDEX) cursor.execute(CREATE_PROBE_UTC_LOGGED_INDEX) - - if warning_table_exists(conn) == False: + + if check_exists and warning_table_exists(conn) is False: cursor.execute(CREATE_WARNING_TABLE_SQL) cursor.execute(CREATE_WARNING_USER_ID_INDEX) cursor.execute(CREATE_WARNING_READING_ID_INDEX) cursor.execute(CREATE_WARNING_UTC_LOGGED_INDEX) - if trigger_table_exists(conn) == False: + if check_exists and trigger_table_exists(conn) is False: cursor.execute(CREATE_TRIGGER_TABLE_SQL) cursor.execute(CREATE_TRIGGER_USER_ID_INDEX) cursor.execute(CREATE_TRIGGER_READING_ID_INDEX) cursor.execute(CREATE_TRIGGER_UTC_LOGGED_INDEX) - + conn.commit() - + reading_cmd = 'INSERT INTO builtin_robothealthprobe(user_id, ' + \ 'guid, ' + \ 'timestamp, ' + \ @@ -150,57 +155,56 @@ def insert(connection_str, user_id, reading): 'app_version_name, ' + \ 'json_config, ' + \ 'scheme_config) VALUES (%s, %s, %s, %s, %s, %s, %s, %s, %s, %s, %s, %s, %s, %s, %s, %s, %s, %s, %s, %s, %s, %s) RETURNING id;' - external_total = None - + if 'EXTERNAL_TOTAL'in reading: external_total = reading['EXTERNAL_TOTAL'] external_free = None - + if 'EXTERNAL_FREE'in reading: external_free = reading['EXTERNAL_FREE'] - data = (user_id, \ - reading['GUID'], \ - reading['TIMESTAMP'], \ - datetime.datetime.fromtimestamp(reading['TIMESTAMP'], tz=pytz.utc), \ - reading['LAST_BOOT'], \ - reading['LAST_HALT'], \ - reading['ACTIVE_RUNTIME'], \ - reading['ROOT_TOTAL'], \ - reading['ROOT_FREE'], \ - external_total, \ - external_free, \ - reading['PENDING_SIZE'], \ - reading['PENDING_COUNT'], \ - reading['CLEAR_TIME'], \ - reading['TIME_OFFSET_MS'], \ - reading['THROUGHPUT'], \ - reading['CPU_USAGE'], \ - reading['MEASURE_TIME'], \ - str(reading['APP_VERSION_CODE']), \ - reading['APP_VERSION_NAME'], \ - reading['JSON_CONFIG'], \ + data = (user_id, + reading['GUID'], + reading['TIMESTAMP'], + datetime.datetime.fromtimestamp(reading['TIMESTAMP'], tz=pytz.utc), + reading['LAST_BOOT'], + reading['LAST_HALT'], + reading['ACTIVE_RUNTIME'], + reading['ROOT_TOTAL'], + reading['ROOT_FREE'], + external_total, + external_free, + reading['PENDING_SIZE'], + reading['PENDING_COUNT'], + reading['CLEAR_TIME'], + reading['TIME_OFFSET_MS'], + reading['THROUGHPUT'], + reading['CPU_USAGE'], + reading['MEASURE_TIME'], + str(reading['APP_VERSION_CODE']), + reading['APP_VERSION_NAME'], + reading['JSON_CONFIG'], reading['SCHEME_CONFIG']) - + cursor.execute(reading_cmd, data) - + for row in cursor.fetchall(): reading_id = row[0] - + for trigger in reading['TRIGGERS']: trigger_cmd = 'INSERT INTO builtin_robothealthprobe_trigger(user_id, reading_id, utc_logged, trigger_json) VALUES (%s, %s, %s, %s);' - + cursor.execute(trigger_cmd, (user_id, reading_id, datetime.datetime.fromtimestamp(reading['TIMESTAMP'], tz=pytz.utc), json.dumps(trigger))) - + if 'CHECK_WARNINGS' in reading: for warning in reading['CHECK_WARNINGS']: warning_cmd = 'INSERT INTO builtin_robothealthprobe_warning(user_id, reading_id, utc_logged, warning) VALUES (%s, %s, %s, %s);' - + cursor.execute(warning_cmd, (user_id, reading_id, datetime.datetime.fromtimestamp(reading['TIMESTAMP'], tz=pytz.utc), warning)) - + conn.commit() - + cursor.close() - conn.close() \ No newline at end of file + conn.close() diff --git a/management/commands/extractors/builtin_runningsoftwareprobe.py b/management/commands/extractors/builtin_runningsoftwareprobe.py index ef9ad04..8246441 100644 --- a/management/commands/extractors/builtin_runningsoftwareprobe.py +++ b/management/commands/extractors/builtin_runningsoftwareprobe.py @@ -1,5 +1,6 @@ +# pylint: disable=line-too-long + import datetime -import json import psycopg2 import pytz @@ -13,86 +14,88 @@ CREATE_TASK_READING_ID_INDEX = 'CREATE INDEX ON builtin_runningsoftwareprobe_runningtask(reading_id);' CREATE_TASK_UTC_LOGGED_INDEX = 'CREATE INDEX ON builtin_runningsoftwareprobe_runningtask(utc_logged);' + def exists(connection_str, user_id, reading): conn = psycopg2.connect(connection_str) - if probe_table_exists(conn) == False: + if probe_table_exists(conn) is False: conn.close() return False cursor = conn.cursor() cursor.execute('SELECT id FROM builtin_runningsoftwareprobe WHERE (user_id = %s AND guid = %s);', (user_id, reading['GUID'])) - - exists = (cursor.rowcount > 0) - + + row_exists = (cursor.rowcount > 0) + cursor.close() conn.close() - - return exists + + return row_exists + def probe_table_exists(conn): cursor = conn.cursor() cursor.execute('SELECT table_name FROM information_schema.tables WHERE (table_schema = \'public\' AND table_name = \'builtin_runningsoftwareprobe\')') - - exists = (cursor.rowcount > 0) - + + table_exists = (cursor.rowcount > 0) + cursor.close() - - return exists + + return table_exists + def task_table_exists(conn): cursor = conn.cursor() cursor.execute('SELECT table_name FROM information_schema.tables WHERE (table_schema = \'public\' AND table_name = \'builtin_runningsoftwareprobe_runningtask\')') - - exists = (cursor.rowcount > 0) - + + table_exists = (cursor.rowcount > 0) + cursor.close() - - return exists -def insert(connection_str, user_id, reading): -# print(json.dumps(reading, indent=2)) - + return table_exists + + +def insert(connection_str, user_id, reading, check_exists=True): conn = psycopg2.connect(connection_str) cursor = conn.cursor() - - if probe_table_exists(conn) == False: + + if check_exists and probe_table_exists(conn) is False: cursor.execute(CREATE_PROBE_TABLE_SQL) cursor.execute(CREATE_PROBE_USER_ID_INDEX) cursor.execute(CREATE_PROBE_GUID_INDEX) cursor.execute(CREATE_PROBE_UTC_LOGGED_INDEX) - - if task_table_exists(conn) == False: + + if check_exists and task_table_exists(conn) is False: cursor.execute(CREATE_TASK_TABLE_SQL) cursor.execute(CREATE_TASK_USER_ID_INDEX) cursor.execute(CREATE_TASK_READING_ID_INDEX) cursor.execute(CREATE_TASK_UTC_LOGGED_INDEX) - + conn.commit() - + reading_cmd = 'INSERT INTO builtin_runningsoftwareprobe(user_id, ' + \ 'guid, ' + \ 'timestamp, ' + \ 'utc_logged, ' + \ 'running_task_count) VALUES (%s, %s, %s, %s, %s) RETURNING id;' running_count = -1 - + if 'RUNNING_TASK_COUNT' in reading: running_count = reading['RUNNING_TASK_COUNT'] - - cursor.execute(reading_cmd, (user_id, \ - reading['GUID'], \ - reading['TIMESTAMP'], \ - datetime.datetime.fromtimestamp(reading['TIMESTAMP'], tz=pytz.utc), \ + + cursor.execute(reading_cmd, (user_id, + reading['GUID'], + reading['TIMESTAMP'], + datetime.datetime.fromtimestamp(reading['TIMESTAMP'], tz=pytz.utc), running_count)) - + if 'RUNNING_TASKS' in reading: for row in cursor.fetchall(): reading_id = row[0] - + task_cursor = conn.cursor() - + for task in reading['RUNNING_TASKS']: task_cmd = 'INSERT INTO builtin_runningsoftwareprobe_runningtask(user_id, ' + \ 'reading_id, ' + \ @@ -100,16 +103,15 @@ def insert(connection_str, user_id, reading): 'package_name, ' + \ 'package_category, ' + \ 'task_stack_index) VALUES (%s, %s, %s, %s, %s, %s);' - - task_cursor.execute(task_cmd, (user_id, \ - reading_id, - datetime.datetime.fromtimestamp(reading['TIMESTAMP'], tz=pytz.utc), - task['PACKAGE_NAME'], - task['PACKAGE_CATEGORY'], + task_cursor.execute(task_cmd, (user_id, + reading_id, + datetime.datetime.fromtimestamp(reading['TIMESTAMP'], tz=pytz.utc), + task['PACKAGE_NAME'], + task['PACKAGE_CATEGORY'], task['TASK_STACK_INDEX'])) task_cursor.close() conn.commit() - + cursor.close() conn.close() diff --git a/management/commands/extractors/builtin_screenprobe.py b/management/commands/extractors/builtin_screenprobe.py index 87061ce..0535e3c 100644 --- a/management/commands/extractors/builtin_screenprobe.py +++ b/management/commands/extractors/builtin_screenprobe.py @@ -1,5 +1,6 @@ +# pylint: disable=line-too-long + import datetime -import json import psycopg2 import pytz @@ -8,59 +9,60 @@ CREATE_PROBE_GUID_INDEX = 'CREATE INDEX ON builtin_screenprobe(guid);' CREATE_PROBE_UTC_LOGGED_INDEX = 'CREATE INDEX ON builtin_screenprobe(utc_logged);' + def exists(connection_str, user_id, reading): conn = psycopg2.connect(connection_str) - - if probe_table_exists(conn) == False: + + if probe_table_exists(conn) is False: conn.close() return False cursor = conn.cursor() - + cursor.execute('SELECT id FROM builtin_screenprobe WHERE (user_id = %s AND guid = %s);', (user_id, reading['GUID'])) - - exists = (cursor.rowcount > 0) - + + row_exists = (cursor.rowcount > 0) + cursor.close() conn.close() - - return exists + + return row_exists + def probe_table_exists(conn): cursor = conn.cursor() cursor.execute('SELECT table_name FROM information_schema.tables WHERE (table_schema = \'public\' AND table_name = \'builtin_screenprobe\')') - - probe_table_exists = (cursor.rowcount > 0) - + + table_exists = (cursor.rowcount > 0) + cursor.close() - - return probe_table_exists -def insert(connection_str, user_id, reading): -# print(json.dumps(reading, indent=2)) - + return table_exists + + +def insert(connection_str, user_id, reading, check_exists=True): conn = psycopg2.connect(connection_str) cursor = conn.cursor() - - if probe_table_exists(conn) == False: + + if check_exists and probe_table_exists(conn) is False: cursor.execute(CREATE_PROBE_TABLE_SQL) cursor.execute(CREATE_PROBE_USER_ID_INDEX) cursor.execute(CREATE_PROBE_GUID_INDEX) cursor.execute(CREATE_PROBE_UTC_LOGGED_INDEX) - + conn.commit() - + reading_cmd = 'INSERT INTO builtin_screenprobe(user_id, ' + \ 'guid, ' + \ 'timestamp, ' + \ 'utc_logged, ' + \ 'screen_active) VALUES (%s, %s, %s, %s, %s) RETURNING id;' - cursor.execute(reading_cmd, (user_id, \ - reading['GUID'], \ - reading['TIMESTAMP'], \ - datetime.datetime.fromtimestamp(reading['TIMESTAMP'], tz=pytz.utc), \ + cursor.execute(reading_cmd, (user_id, + reading['GUID'], + reading['TIMESTAMP'], + datetime.datetime.fromtimestamp(reading['TIMESTAMP'], tz=pytz.utc), reading['SCREEN_ACTIVE'])) conn.commit() - + cursor.close() conn.close() diff --git a/management/commands/extractors/builtin_softwareinformationprobe.py b/management/commands/extractors/builtin_softwareinformationprobe.py index 81da979..e9e5f88 100644 --- a/management/commands/extractors/builtin_softwareinformationprobe.py +++ b/management/commands/extractors/builtin_softwareinformationprobe.py @@ -1,5 +1,6 @@ +# pylint: disable=line-too-long + import datetime -import json import psycopg2 import pytz @@ -13,65 +14,66 @@ CREATE_APP_READING_ID_INDEX = 'CREATE INDEX ON builtin_softwareinformationprobe_app(reading_id);' CREATE_APP_UTC_LOGGED_INDEX = 'CREATE INDEX ON builtin_softwareinformationprobe_app(utc_logged);' + def exists(connection_str, user_id, reading): conn = psycopg2.connect(connection_str) - - if probe_table_exists(conn) == False: + + if probe_table_exists(conn) is False: conn.close() - return False - + cursor = conn.cursor() cursor.execute('SELECT id FROM builtin_softwareinformationprobe WHERE (user_id = %s AND guid = %s);', (user_id, reading['GUID'])) - - exists = (cursor.rowcount > 0) - + + row_exists = (cursor.rowcount > 0) + cursor.close() conn.close() - - return exists + + return row_exists + def probe_table_exists(conn): cursor = conn.cursor() cursor.execute('SELECT table_name FROM information_schema.tables WHERE (table_schema = \'public\' AND table_name = \'builtin_softwareinformationprobe\')') - - probe_table_exists = (cursor.rowcount > 0) - + + table_exists = (cursor.rowcount > 0) + cursor.close() - - return probe_table_exists + + return table_exists + def app_point_table_exists(conn): cursor = conn.cursor() cursor.execute('SELECT table_name FROM information_schema.tables WHERE (table_schema = \'public\' AND table_name = \'builtin_softwareinformationprobe_app\')') - - activities_table_exists = (cursor.rowcount > 0) - + + table_exists = (cursor.rowcount > 0) + cursor.close() - - return activities_table_exists -def insert(connection_str, user_id, reading): -# print(json.dumps(reading, indent=2)) - + return table_exists + + +def insert(connection_str, user_id, reading, check_exists=True): conn = psycopg2.connect(connection_str) cursor = conn.cursor() - - if probe_table_exists(conn) == False: + + if check_exists and probe_table_exists(conn) is False: cursor.execute(CREATE_PROBE_TABLE_SQL) cursor.execute(CREATE_PROBE_USER_ID_INDEX) cursor.execute(CREATE_PROBE_GUID_INDEX) cursor.execute(CREATE_PROBE_UTC_LOGGED_INDEX) - - if app_point_table_exists(conn) == False: + + if check_exists and app_point_table_exists(conn) is False: cursor.execute(CREATE_APP_TABLE_SQL) cursor.execute(CREATE_APP_USER_ID_INDEX) cursor.execute(CREATE_APP_READING_ID_INDEX) cursor.execute(CREATE_APP_UTC_LOGGED_INDEX) - + conn.commit() - + reading_cmd = 'INSERT INTO builtin_softwareinformationprobe(user_id, ' + \ 'guid, ' + \ 'timestamp, ' + \ @@ -81,22 +83,21 @@ def insert(connection_str, user_id, reading): 'incremental, ' + \ 'codename, ' + \ 'installed_app_count) VALUES (%s, %s, %s, %s, %s, %s, %s, %s, %s) RETURNING id;' - - cursor.execute(reading_cmd, (user_id, \ - reading['GUID'], \ - reading['TIMESTAMP'], \ - datetime.datetime.fromtimestamp(reading['TIMESTAMP'], tz=pytz.utc), \ - reading['SDK_INT'], \ - reading['RELEASE'], \ - reading['INCREMENTAL'], \ - reading['CODENAME'], \ + cursor.execute(reading_cmd, (user_id, + reading['GUID'], + reading['TIMESTAMP'], + datetime.datetime.fromtimestamp(reading['TIMESTAMP'], tz=pytz.utc), + reading['SDK_INT'], + reading['RELEASE'], + reading['INCREMENTAL'], + reading['CODENAME'], reading['INSTALLED_APP_COUNT'])) - + for row in cursor.fetchall(): reading_id = row[0] - + app_cursor = conn.cursor() - + for app in reading['INSTALLED_APPS']: app_cmd = 'INSERT INTO builtin_softwareinformationprobe_app(user_id, ' + \ 'reading_id, ' + \ @@ -104,14 +105,14 @@ def insert(connection_str, user_id, reading): 'name, ' + \ 'package) VALUES (%s, %s, %s, %s, %s);' - app_cursor.execute(app_cmd, (user_id, \ - reading_id, - datetime.datetime.fromtimestamp(reading['TIMESTAMP'], tz=pytz.utc), - app['APP_NAME'], - app['PACKAGE_NAME'])) + app_cursor.execute(app_cmd, (user_id, + reading_id, + datetime.datetime.fromtimestamp(reading['TIMESTAMP'], tz=pytz.utc), + app['APP_NAME'], + app['PACKAGE_NAME'])) app_cursor.close() conn.commit() - + cursor.close() conn.close() diff --git a/management/commands/extractors/builtin_telephonyprobe.py b/management/commands/extractors/builtin_telephonyprobe.py index b418b33..18c523e 100644 --- a/management/commands/extractors/builtin_telephonyprobe.py +++ b/management/commands/extractors/builtin_telephonyprobe.py @@ -1,5 +1,6 @@ +# pylint: disable=line-too-long + import datetime -import json import psycopg2 import pytz @@ -8,48 +9,49 @@ CREATE_PROBE_GUID_INDEX = 'CREATE INDEX ON builtin_telephonyprobe(guid);' CREATE_PROBE_UTC_LOGGED_INDEX = 'CREATE INDEX ON builtin_telephonyprobe(utc_logged);' + def exists(connection_str, user_id, reading): conn = psycopg2.connect(connection_str) - - if probe_table_exists(conn) == False: + + if probe_table_exists(conn) is False: conn.close() return False cursor = conn.cursor() - + cursor.execute('SELECT id FROM builtin_telephonyprobe WHERE (user_id = %s AND guid = %s);', (user_id, reading['GUID'])) - - exists = (cursor.rowcount > 0) - + + row_exists = (cursor.rowcount > 0) + cursor.close() conn.close() - - return exists + + return row_exists + def probe_table_exists(conn): cursor = conn.cursor() cursor.execute('SELECT table_name FROM information_schema.tables WHERE (table_schema = \'public\' AND table_name = \'builtin_telephonyprobe\')') - - probe_table_exists = (cursor.rowcount > 0) - + + table_exists = (cursor.rowcount > 0) + cursor.close() - - return probe_table_exists -def insert(connection_str, user_id, reading): -# print(json.dumps(reading, indent=2)) - + return table_exists + + +def insert(connection_str, user_id, reading, check_exists=True): conn = psycopg2.connect(connection_str) cursor = conn.cursor() - - if probe_table_exists(conn) == False: + + if check_exists and probe_table_exists(conn) is False: cursor.execute(CREATE_PROBE_TABLE_SQL) cursor.execute(CREATE_PROBE_USER_ID_INDEX) cursor.execute(CREATE_PROBE_GUID_INDEX) cursor.execute(CREATE_PROBE_UTC_LOGGED_INDEX) - + conn.commit() - + reading_cmd = 'INSERT INTO builtin_telephonyprobe(user_id, ' + \ 'guid, ' + \ 'timestamp, ' + \ @@ -95,28 +97,28 @@ def insert(connection_str, user_id, reading): if 'GSM_ERROR_RATE' in reading: gsm_error = reading['GSM_ERROR_RATE'] - cursor.execute(reading_cmd, (user_id, \ - reading['GUID'], \ - reading['TIMESTAMP'], \ - datetime.datetime.fromtimestamp(reading['TIMESTAMP'], tz=pytz.utc), \ - reading['NETWORK_COUNTRY_ISO'], \ - reading['PHONE_TYPE'], \ - reading['SIM_STATE'], \ - cid, \ - reading['NETWORK_OPERATOR'], \ - reading['HAS_ICC_CARD'], \ - reading['SIM_COUNTRY_ISO'], \ - lac, \ - reading['CALL_STATE'], \ - gsm_strength, \ - reading['SIM_OPERATOR_NAME'], \ - psc, \ - device_software_version, \ - gsm_error, \ - reading['NETWORK_TYPE'], \ - reading['SERVICE_STATE'], \ + cursor.execute(reading_cmd, (user_id, + reading['GUID'], + reading['TIMESTAMP'], + datetime.datetime.fromtimestamp(reading['TIMESTAMP'], tz=pytz.utc), + reading['NETWORK_COUNTRY_ISO'], + reading['PHONE_TYPE'], + reading['SIM_STATE'], + cid, + reading['NETWORK_OPERATOR'], + reading['HAS_ICC_CARD'], + reading['SIM_COUNTRY_ISO'], + lac, + reading['CALL_STATE'], + gsm_strength, + reading['SIM_OPERATOR_NAME'], + psc, + device_software_version, + gsm_error, + reading['NETWORK_TYPE'], + reading['SERVICE_STATE'], reading['IS_FORWARDING'])) conn.commit() - + cursor.close() conn.close() diff --git a/management/commands/extractors/builtin_toucheventsprobe.py b/management/commands/extractors/builtin_toucheventsprobe.py index 12189e0..b14247c 100644 --- a/management/commands/extractors/builtin_toucheventsprobe.py +++ b/management/commands/extractors/builtin_toucheventsprobe.py @@ -1,5 +1,6 @@ +# pylint: disable=line-too-long + import datetime -import json import psycopg2 import pytz @@ -8,61 +9,62 @@ CREATE_PROBE_GUID_INDEX = 'CREATE INDEX ON builtin_toucheventsprobe(guid);' CREATE_PROBE_UTC_LOGGED_INDEX = 'CREATE INDEX ON builtin_toucheventsprobe(utc_logged);' + def exists(connection_str, user_id, reading): conn = psycopg2.connect(connection_str) - - if probe_table_exists(conn) == False: + + if probe_table_exists(conn) is False: conn.close() return False cursor = conn.cursor() - + cursor.execute('SELECT id FROM builtin_toucheventsprobe WHERE (user_id = %s AND guid = %s);', (user_id, reading['GUID'])) - - exists = (cursor.rowcount > 0) - + + row_exists = (cursor.rowcount > 0) + cursor.close() conn.close() - - return exists + + return row_exists + def probe_table_exists(conn): cursor = conn.cursor() cursor.execute('SELECT table_name FROM information_schema.tables WHERE (table_schema = \'public\' AND table_name = \'builtin_toucheventsprobe\')') - - probe_table_exists = (cursor.rowcount > 0) - + + table_exists = (cursor.rowcount > 0) + cursor.close() - - return probe_table_exists -def insert(connection_str, user_id, reading): -# print(json.dumps(reading, indent=2)) - + return table_exists + + +def insert(connection_str, user_id, reading, check_exists=True): conn = psycopg2.connect(connection_str) cursor = conn.cursor() - - if probe_table_exists(conn) == False: + + if check_exists and probe_table_exists(conn) is False: cursor.execute(CREATE_PROBE_TABLE_SQL) cursor.execute(CREATE_PROBE_USER_ID_INDEX) cursor.execute(CREATE_PROBE_GUID_INDEX) cursor.execute(CREATE_PROBE_UTC_LOGGED_INDEX) - + conn.commit() - + reading_cmd = 'INSERT INTO builtin_toucheventsprobe(user_id, ' + \ 'guid, ' + \ 'timestamp, ' + \ 'utc_logged, ' + \ 'last_touch_delay, ' + \ 'touch_count) VALUES (%s, %s, %s, %s, %s, %s) RETURNING id;' - cursor.execute(reading_cmd, (user_id, \ - reading['GUID'], \ - reading['TIMESTAMP'], \ - datetime.datetime.fromtimestamp(reading['TIMESTAMP'], tz=pytz.utc), \ - reading['LAST_TOUCH_DELAY'], \ + cursor.execute(reading_cmd, (user_id, + reading['GUID'], + reading['TIMESTAMP'], + datetime.datetime.fromtimestamp(reading['TIMESTAMP'], tz=pytz.utc), + reading['LAST_TOUCH_DELAY'], reading['TOUCH_COUNT'])) conn.commit() - + cursor.close() conn.close() diff --git a/management/commands/extractors/builtin_wakelockinformationprobe.py b/management/commands/extractors/builtin_wakelockinformationprobe.py index feec454..f0ac6ff 100644 --- a/management/commands/extractors/builtin_wakelockinformationprobe.py +++ b/management/commands/extractors/builtin_wakelockinformationprobe.py @@ -1,3 +1,5 @@ +# pylint: disable=line-too-long + import datetime import json import psycopg2 @@ -8,49 +10,49 @@ CREATE_PROBE_GUID_INDEX = 'CREATE INDEX ON builtin_wakelockinformationprobe(guid);' CREATE_PROBE_UTC_LOGGED_INDEX = 'CREATE INDEX ON builtin_wakelockinformationprobe(utc_logged);' + def exists(connection_str, user_id, reading): conn = psycopg2.connect(connection_str) - - if probe_table_exists(conn) == False: + + if probe_table_exists(conn) is False: conn.close() return False cursor = conn.cursor() - + cursor.execute('SELECT id FROM builtin_wakelockinformationprobe WHERE (user_id = %s AND guid = %s);', (user_id, reading['GUID'])) - - exists = (cursor.rowcount > 0) - + + row_exists = (cursor.rowcount > 0) + cursor.close() conn.close() - - return exists + + return row_exists + def probe_table_exists(conn): cursor = conn.cursor() cursor.execute('SELECT table_name FROM information_schema.tables WHERE (table_schema = \'public\' AND table_name = \'builtin_wakelockinformationprobe\')') - - probe_table_exists = (cursor.rowcount > 0) - + + table_exists = (cursor.rowcount > 0) + cursor.close() - - return probe_table_exists -def insert(connection_str, user_id, reading): -# print('USER: '+ user_id) -# print(json.dumps(reading, indent=2)) - + return table_exists + + +def insert(connection_str, user_id, reading, check_exists=True): conn = psycopg2.connect(connection_str) cursor = conn.cursor() - - if probe_table_exists(conn) == False: + + if check_exists and probe_table_exists(conn) is False: cursor.execute(CREATE_PROBE_TABLE_SQL) cursor.execute(CREATE_PROBE_USER_ID_INDEX) cursor.execute(CREATE_PROBE_GUID_INDEX) cursor.execute(CREATE_PROBE_UTC_LOGGED_INDEX) - + conn.commit() - + reading_cmd = 'INSERT INTO builtin_wakelockinformationprobe(user_id, ' + \ 'guid, ' + \ 'timestamp, ' + \ @@ -63,20 +65,19 @@ def insert(connection_str, user_id, reading): 'dim_count, ' + \ 'partial_locks, ' + \ 'partial_count) VALUES (%s, %s, %s, %s, %s, %s, %s, %s, %s, %s, %s, %s) RETURNING id;' - - cursor.execute(reading_cmd, (user_id, \ - reading['GUID'], \ - reading['TIMESTAMP'], \ - datetime.datetime.fromtimestamp(reading['TIMESTAMP'], tz=pytz.utc), \ - json.dumps(reading['FULL_LOCKS']), \ - reading['FULL_COUNT'], \ - json.dumps(reading['BRIGHT_LOCKS']), \ - reading['BRIGHT_COUNT'], \ - json.dumps(reading['DIM_LOCKS']), \ - reading['DIM_COUNT'], \ - json.dumps(reading['PARTIAL_LOCKS']), \ + cursor.execute(reading_cmd, (user_id, + reading['GUID'], + reading['TIMESTAMP'], + datetime.datetime.fromtimestamp(reading['TIMESTAMP'], tz=pytz.utc), + json.dumps(reading['FULL_LOCKS']), + reading['FULL_COUNT'], + json.dumps(reading['BRIGHT_LOCKS']), + reading['BRIGHT_COUNT'], + json.dumps(reading['DIM_LOCKS']), + reading['DIM_COUNT'], + json.dumps(reading['PARTIAL_LOCKS']), reading['PARTIAL_COUNT'])) conn.commit() - + cursor.close() conn.close() diff --git a/management/commands/extractors/builtin_wifiaccesspointsprobe.py b/management/commands/extractors/builtin_wifiaccesspointsprobe.py index d40e939..2016287 100644 --- a/management/commands/extractors/builtin_wifiaccesspointsprobe.py +++ b/management/commands/extractors/builtin_wifiaccesspointsprobe.py @@ -1,5 +1,6 @@ +# pylint: disable=line-too-long + import datetime -import json import psycopg2 import pytz @@ -13,64 +14,66 @@ CREATE_ACCESS_POINT_READING_ID_INDEX = 'CREATE INDEX ON builtin_wifiaccesspointsprobe_accesspoint(reading_id);' CREATE_ACCESS_POINT_UTC_LOGGED_INDEX = 'CREATE INDEX ON builtin_wifiaccesspointsprobe_accesspoint(utc_logged);' + def exists(connection_str, user_id, reading): conn = psycopg2.connect(connection_str) - if probe_table_exists(conn) == False or access_point_table_exists(conn) == False: + if probe_table_exists(conn) is False or access_point_table_exists(conn) is False: conn.close() return False cursor = conn.cursor() cursor.execute('SELECT id FROM builtin_wifiaccesspointsprobe WHERE (user_id = %s AND guid = %s);', (user_id, reading['GUID'])) - - exists = (cursor.rowcount > 0) - + + row_exists = (cursor.rowcount > 0) + cursor.close() conn.close() - - return exists + + return row_exists + def probe_table_exists(conn): cursor = conn.cursor() cursor.execute('SELECT table_name FROM information_schema.tables WHERE (table_schema = \'public\' AND table_name = \'builtin_wifiaccesspointsprobe\')') - - probe_table_exists = (cursor.rowcount > 0) - + + table_exists = (cursor.rowcount > 0) + cursor.close() - - return probe_table_exists + + return table_exists + def access_point_table_exists(conn): cursor = conn.cursor() cursor.execute('SELECT table_name FROM information_schema.tables WHERE (table_schema = \'public\' AND table_name = \'builtin_wifiaccesspointsprobe_accesspoint\')') - - activities_table_exists = (cursor.rowcount > 0) - + + table_exists = (cursor.rowcount > 0) + cursor.close() - - return activities_table_exists -def insert(connection_str, user_id, reading): -# print(json.dumps(reading, indent=2)) - + return table_exists + + +def insert(connection_str, user_id, reading, check_exists=True): conn = psycopg2.connect(connection_str) cursor = conn.cursor() - - if probe_table_exists(conn) == False: + + if check_exists and probe_table_exists(conn) is False: cursor.execute(CREATE_PROBE_TABLE_SQL) cursor.execute(CREATE_PROBE_USER_ID_INDEX) cursor.execute(CREATE_PROBE_GUID_INDEX) cursor.execute(CREATE_PROBE_UTC_LOGGED_INDEX) - - if access_point_table_exists(conn) == False: + + if check_exists and access_point_table_exists(conn) is False: cursor.execute(CREATE_ACCESS_POINT_TABLE_SQL) cursor.execute(CREATE_ACCESS_POINT_USER_ID_INDEX) cursor.execute(CREATE_ACCESS_POINT_READING_ID_INDEX) cursor.execute(CREATE_ACCESS_POINT_UTC_LOGGED_INDEX) - + conn.commit() - + reading_cmd = 'INSERT INTO builtin_wifiaccesspointsprobe(user_id, ' + \ 'guid, ' + \ 'timestamp, ' + \ @@ -80,39 +83,38 @@ def insert(connection_str, user_id, reading): 'current_rssi, ' + \ 'current_link_speed, ' + \ 'access_point_count) VALUES (%s, %s, %s, %s, %s, %s, %s, %s, %s) RETURNING id;' - current_ssid = None current_bssid = None current_link_speed = None current_rssi = None - + if 'CURRENT_SSID' in reading: current_ssid = reading['CURRENT_SSID'] - + if 'CURRENT_BSSID' in reading: current_bssid = reading['CURRENT_BSSID'] - + if 'CURRENT_LINK_SPEED' in reading: current_link_speed = reading['CURRENT_LINK_SPEED'] if 'CURRENT_RSSI' in reading: current_rssi = reading['CURRENT_RSSI'] - - cursor.execute(reading_cmd, (user_id, \ - reading['GUID'], \ - reading['TIMESTAMP'], \ - datetime.datetime.fromtimestamp(reading['TIMESTAMP'], tz=pytz.utc), \ - current_ssid, \ - current_bssid, \ - current_rssi, \ - current_link_speed, \ + + cursor.execute(reading_cmd, (user_id, + reading['GUID'], + reading['TIMESTAMP'], + datetime.datetime.fromtimestamp(reading['TIMESTAMP'], tz=pytz.utc), + current_ssid, + current_bssid, + current_rssi, + current_link_speed, reading['ACCESS_POINT_COUNT'])) - + for row in cursor.fetchall(): reading_id = row[0] - + ap_cursor = conn.cursor() - + for point in reading['ACCESS_POINTS']: point_cmd = 'INSERT INTO builtin_wifiaccesspointsprobe_accesspoint(user_id, ' + \ 'reading_id, ' + \ @@ -122,17 +124,15 @@ def insert(connection_str, user_id, reading): 'capabilities, ' + \ 'frequency, ' + \ 'level) VALUES (%s, %s, %s, %s, %s, %s, %s, %s);' - - ap_cursor.execute(point_cmd, (user_id, \ - reading_id, - datetime.datetime.fromtimestamp(reading['TIMESTAMP'], tz=pytz.utc), - point['SSID'], - point['BSSID'], - point['CAPABILITIES'], - point['FREQUENCY'], + ap_cursor.execute(point_cmd, (user_id, + reading_id, + datetime.datetime.fromtimestamp(reading['TIMESTAMP'], tz=pytz.utc), + point['SSID'], + point['BSSID'], + point['CAPABILITIES'], + point['FREQUENCY'], point['LEVEL'])) - conn.commit() - + cursor.close() conn.close() diff --git a/management/commands/extractors/features_callhistoryfeature.py b/management/commands/extractors/features_callhistoryfeature.py index f54a9ad..ea614df 100644 --- a/management/commands/extractors/features_callhistoryfeature.py +++ b/management/commands/extractors/features_callhistoryfeature.py @@ -1,5 +1,6 @@ +# pylint: disable=line-too-long + import datetime -import json import psycopg2 import pytz @@ -8,6 +9,7 @@ CREATE_PROBE_GUID_INDEX = 'CREATE INDEX ON features_callhistoryfeature(guid);' CREATE_PROBE_UTC_LOGGED_INDEX = 'CREATE INDEX ON features_callhistoryfeature(utc_logged);' + def exists(connection_str, user_id, reading): conn = psycopg2.connect(connection_str) @@ -18,38 +20,38 @@ def exists(connection_str, user_id, reading): cursor = conn.cursor() cursor.execute('SELECT id FROM features_callhistoryfeature WHERE (user_id = %s AND guid = %s);', (user_id, reading['GUID'])) - - exists = (cursor.rowcount > 0) - + + row_exists = (cursor.rowcount > 0) + cursor.close() conn.close() - - return exists + + return row_exists + def probe_table_exists(conn): cursor = conn.cursor() cursor.execute('SELECT table_name FROM information_schema.tables WHERE (table_schema = \'public\' AND table_name = \'features_callhistoryfeature\')') - - exists = (cursor.rowcount > 0) - + + table_exists = (cursor.rowcount > 0) + cursor.close() - - return exists -def insert(connection_str, user_id, reading): -# print(json.dumps(reading, indent=2)) - + return table_exists + + +def insert(connection_str, user_id, reading, check_exists=True): conn = psycopg2.connect(connection_str) cursor = conn.cursor() - - if probe_table_exists(conn) == False: + + if check_exists and probe_table_exists(conn) == False: cursor.execute(CREATE_PROBE_TABLE_SQL) cursor.execute(CREATE_PROBE_USER_ID_INDEX) cursor.execute(CREATE_PROBE_GUID_INDEX) cursor.execute(CREATE_PROBE_UTC_LOGGED_INDEX) - + conn.commit() - + reading_cmd = 'INSERT INTO features_callhistoryfeature(user_id, ' + \ 'guid, ' + \ 'timestamp, ' + \ @@ -70,31 +72,29 @@ def insert(connection_str, user_id, reading): 'acquiantance_count, ' + \ 'stranger_count, ' + \ 'acquiantance_ratio) VALUES (%s, %s, %s, %s, %s, %s, %s, %s, %s, %s, %s, %s, %s, %s, %s, %s, %s, %s, %s, %s) RETURNING id;' - - for window in reading['WINDOWS']: - cursor.execute(reading_cmd, (user_id, \ - reading['GUID'], - reading['TIMESTAMP'], - datetime.datetime.fromtimestamp(reading['TIMESTAMP'], tz=pytz.utc), - window['WINDOW_SIZE'], - window['INCOMING_COUNT'], - window['OUTGOING_COUNT'], - window['TOTAL'], - window['TOTAL_DURATION'], - window['MIN_DURATION'], - window['MAX_DURATION'], - window['STD_DEVIATION'], - window['AVG_DURATION'], - window['INCOMING_RATIO'], - window['ACK_RATIO'], - window['NEW_COUNT'], - window['ACK_COUNT'], - window['ACQUIANTANCE_COUNT'], - window['STRANGER_COUNT'], + cursor.execute(reading_cmd, (user_id, + reading['GUID'], + reading['TIMESTAMP'], + datetime.datetime.fromtimestamp(reading['TIMESTAMP'], tz=pytz.utc), + window['WINDOW_SIZE'], + window['INCOMING_COUNT'], + window['OUTGOING_COUNT'], + window['TOTAL'], + window['TOTAL_DURATION'], + window['MIN_DURATION'], + window['MAX_DURATION'], + window['STD_DEVIATION'], + window['AVG_DURATION'], + window['INCOMING_RATIO'], + window['ACK_RATIO'], + window['NEW_COUNT'], + window['ACK_COUNT'], + window['ACQUIANTANCE_COUNT'], + window['STRANGER_COUNT'], window['ACQUAINTANCE_RATIO'])) conn.commit() - + cursor.close() conn.close() diff --git a/management/commands/extractors/features_deviceinusefeature.py b/management/commands/extractors/features_deviceinusefeature.py index 24901c5..58458c0 100644 --- a/management/commands/extractors/features_deviceinusefeature.py +++ b/management/commands/extractors/features_deviceinusefeature.py @@ -1,5 +1,6 @@ +# pylint: disable=line-too-long + import datetime -import json import psycopg2 import pytz @@ -8,59 +9,60 @@ CREATE_PROBE_GUID_INDEX = 'CREATE INDEX ON features_deviceinusefeature(guid);' CREATE_PROBE_UTC_LOGGED_INDEX = 'CREATE INDEX ON features_deviceinusefeature(utc_logged);' + def exists(connection_str, user_id, reading): conn = psycopg2.connect(connection_str) - + if probe_table_exists(conn) == False: conn.close() return False cursor = conn.cursor() - + cursor.execute('SELECT id FROM features_deviceinusefeature WHERE (user_id = %s AND guid = %s);', (user_id, reading['GUID'])) - - exists = (cursor.rowcount > 0) - + + row_exists = (cursor.rowcount > 0) + cursor.close() conn.close() - - return exists + + return row_exists + def probe_table_exists(conn): cursor = conn.cursor() cursor.execute('SELECT table_name FROM information_schema.tables WHERE (table_schema = \'public\' AND table_name = \'features_deviceinusefeature\')') - - probe_table_exists = (cursor.rowcount > 0) - + + table_exists = (cursor.rowcount > 0) + cursor.close() - - return probe_table_exists -def insert(connection_str, user_id, reading): -# print(json.dumps(reading, indent=2)) - + return table_exists + + +def insert(connection_str, user_id, reading, check_exists=True): conn = psycopg2.connect(connection_str) cursor = conn.cursor() - - if probe_table_exists(conn) == False: + + if check_exists and probe_table_exists(conn) == False: cursor.execute(CREATE_PROBE_TABLE_SQL) cursor.execute(CREATE_PROBE_USER_ID_INDEX) cursor.execute(CREATE_PROBE_GUID_INDEX) cursor.execute(CREATE_PROBE_UTC_LOGGED_INDEX) - + conn.commit() - + reading_cmd = 'INSERT INTO features_deviceinusefeature(user_id, ' + \ 'guid, ' + \ 'timestamp, ' + \ 'utc_logged, ' + \ 'device_active) VALUES (%s, %s, %s, %s, %s) RETURNING id;' - cursor.execute(reading_cmd, (user_id, \ - reading['GUID'], \ - reading['TIMESTAMP'], \ - datetime.datetime.fromtimestamp(reading['TIMESTAMP'], tz=pytz.utc), \ + cursor.execute(reading_cmd, (user_id, + reading['GUID'], + reading['TIMESTAMP'], + datetime.datetime.fromtimestamp(reading['TIMESTAMP'], tz=pytz.utc), reading['DEVICE_ACTIVE'])) conn.commit() - + cursor.close() conn.close() diff --git a/management/commands/extractors/features_sunrisesunsetfeature.py b/management/commands/extractors/features_sunrisesunsetfeature.py index 59dd745..1a100cc 100644 --- a/management/commands/extractors/features_sunrisesunsetfeature.py +++ b/management/commands/extractors/features_sunrisesunsetfeature.py @@ -1,5 +1,6 @@ +# pylint: disable=line-too-long + import datetime -import json import psycopg2 import pytz @@ -8,49 +9,49 @@ CREATE_PROBE_GUID_INDEX = 'CREATE INDEX ON features_sunrisesunsetfeature(guid);' CREATE_PROBE_UTC_LOGGED_INDEX = 'CREATE INDEX ON features_sunrisesunsetfeature(utc_logged);' + def exists(connection_str, user_id, reading): conn = psycopg2.connect(connection_str) - - if probe_table_exists(conn) == False: + + if probe_table_exists(conn) is False: conn.close() return False cursor = conn.cursor() - + cursor.execute('SELECT id FROM features_sunrisesunsetfeature WHERE (user_id = %s AND guid = %s);', (user_id, reading['GUID'])) - - exists = (cursor.rowcount > 0) - + + row_exists = (cursor.rowcount > 0) + cursor.close() conn.close() - - return exists + + return row_exists + def probe_table_exists(conn): cursor = conn.cursor() cursor.execute('SELECT table_name FROM information_schema.tables WHERE (table_schema = \'public\' AND table_name = \'features_sunrisesunsetfeature\')') - - probe_table_exists = (cursor.rowcount > 0) - + + table_exists = (cursor.rowcount > 0) + cursor.close() - - return probe_table_exists -def insert(connection_str, user_id, reading): -# print('USER: '+ user_id) -# print(json.dumps(reading, indent=2)) - + return table_exists + + +def insert(connection_str, user_id, reading, check_exists=True): conn = psycopg2.connect(connection_str) cursor = conn.cursor() - - if probe_table_exists(conn) == False: + + if check_exists and probe_table_exists(conn) is False: cursor.execute(CREATE_PROBE_TABLE_SQL) cursor.execute(CREATE_PROBE_USER_ID_INDEX) cursor.execute(CREATE_PROBE_GUID_INDEX) cursor.execute(CREATE_PROBE_UTC_LOGGED_INDEX) - + conn.commit() - + reading_cmd = 'INSERT INTO features_sunrisesunsetfeature(user_id, ' + \ 'guid, ' + \ 'timestamp, ' + \ @@ -63,19 +64,19 @@ def insert(connection_str, user_id, reading): 'sunrise_distance, ' + \ 'sunset_distance, ' + \ 'is_day) VALUES (%s, %s, %s, %s, %s, %s, %s, %s, %s, %s, %s, %s) RETURNING id;' - cursor.execute(reading_cmd, (user_id, \ - reading['GUID'], \ - reading['TIMESTAMP'], \ - datetime.datetime.fromtimestamp(reading['TIMESTAMP'], tz=pytz.utc), \ - reading['LATITUDE'], \ - reading['LONGITUDE'], \ - reading['SUNRISE'], \ - reading['SUNSET'], \ - reading['DAY_DURATION'], \ - reading['SUNRISE_DISTANCE'], \ - reading['SUNSET_DISTANCE'], \ + cursor.execute(reading_cmd, (user_id, + reading['GUID'], + reading['TIMESTAMP'], + datetime.datetime.fromtimestamp(reading['TIMESTAMP'], tz=pytz.utc), + reading['LATITUDE'], + reading['LONGITUDE'], + reading['SUNRISE'], + reading['SUNSET'], + reading['DAY_DURATION'], + reading['SUNRISE_DISTANCE'], + reading['SUNSET_DISTANCE'], reading['IS_DAY'])) conn.commit() - + cursor.close() conn.close() diff --git a/management/commands/extractors/features_weatherundergroundfeature.py b/management/commands/extractors/features_weatherundergroundfeature.py index 8ffca12..817f6f6 100644 --- a/management/commands/extractors/features_weatherundergroundfeature.py +++ b/management/commands/extractors/features_weatherundergroundfeature.py @@ -1,5 +1,6 @@ +# pylint: disable=line-too-long + import datetime -import json import psycopg2 import pytz @@ -8,48 +9,49 @@ CREATE_PROBE_GUID_INDEX = 'CREATE INDEX ON features_weatherundergroundfeature(guid);' CREATE_PROBE_UTC_LOGGED_INDEX = 'CREATE INDEX ON features_weatherundergroundfeature(utc_logged);' + def exists(connection_str, user_id, reading): conn = psycopg2.connect(connection_str) - - if probe_table_exists(conn) == False: + + if probe_table_exists(conn) is False: conn.close() return False cursor = conn.cursor() - + cursor.execute('SELECT id FROM features_weatherundergroundfeature WHERE (user_id = %s AND guid = %s);', (user_id, reading['GUID'])) - - exists = (cursor.rowcount > 0) - + + row_exists = (cursor.rowcount > 0) + cursor.close() conn.close() - - return exists + + return row_exists + def probe_table_exists(conn): cursor = conn.cursor() cursor.execute('SELECT table_name FROM information_schema.tables WHERE (table_schema = \'public\' AND table_name = \'features_weatherundergroundfeature\')') - - probe_table_exists = (cursor.rowcount > 0) - + + table_exists = (cursor.rowcount > 0) + cursor.close() - - return probe_table_exists -def insert(connection_str, user_id, reading): -# print(json.dumps(reading, indent=2)) - + return table_exists + + +def insert(connection_str, user_id, reading, check_exists=True): conn = psycopg2.connect(connection_str) cursor = conn.cursor() - - if probe_table_exists(conn) == False: + + if check_exists and probe_table_exists(conn) is False: cursor.execute(CREATE_PROBE_TABLE_SQL) cursor.execute(CREATE_PROBE_USER_ID_INDEX) cursor.execute(CREATE_PROBE_GUID_INDEX) cursor.execute(CREATE_PROBE_UTC_LOGGED_INDEX) - + conn.commit() - + reading_cmd = 'INSERT INTO features_weatherundergroundfeature(user_id, ' + \ 'guid, ' + \ 'timestamp, ' + \ @@ -69,25 +71,25 @@ def insert(connection_str, user_id, reading): 'visibility, ' + \ 'wind_dir) VALUES (%s, %s, %s, %s, %s, %s, %s, %s, %s, %s, %s, %s, %s, %s, %s, %s, %s, %s) RETURNING id;' - cursor.execute(reading_cmd, (user_id, \ - reading['GUID'], \ - reading['TIMESTAMP'], \ - datetime.datetime.fromtimestamp(reading['TIMESTAMP'], tz=pytz.utc), \ - reading['WEATHER'], \ - reading['LOCATION'], \ - reading['STATION_ID'], \ - reading['OBS_TIMESTAMP'], \ - datetime.datetime.fromtimestamp(reading['OBS_TIMESTAMP'], tz=pytz.utc), \ - reading['TEMPERATURE'], \ - reading['PRESSURE'], \ - reading['PRESSURE_TREND'], \ - reading['WIND_SPEED'], \ - reading['GUST_SPEED'], \ - reading['WIND_DEGREES'], \ - reading['DEWPOINT'], \ - reading['VISIBILITY'], \ + cursor.execute(reading_cmd, (user_id, + reading['GUID'], + reading['TIMESTAMP'], + datetime.datetime.fromtimestamp(reading['TIMESTAMP'], tz=pytz.utc), + reading['WEATHER'], + reading['LOCATION'], + reading['STATION_ID'], + reading['OBS_TIMESTAMP'], + datetime.datetime.fromtimestamp(reading['OBS_TIMESTAMP'], tz=pytz.utc), + reading['TEMPERATURE'], + reading['PRESSURE'], + reading['PRESSURE_TREND'], + reading['WIND_SPEED'], + reading['GUST_SPEED'], + reading['WIND_DEGREES'], + reading['DEWPOINT'], + reading['VISIBILITY'], reading['WIND_DIR'])) conn.commit() - + cursor.close() conn.close() diff --git a/management/commands/extractors/media_audiocaptureprobe.py b/management/commands/extractors/media_audiocaptureprobe.py index 293b5d6..53308dc 100644 --- a/management/commands/extractors/media_audiocaptureprobe.py +++ b/management/commands/extractors/media_audiocaptureprobe.py @@ -1,11 +1,9 @@ +# pylint: disable=line-too-long + import datetime -import json -import msgpack import psycopg2 import pytz -from purple_robot_app.models import PurpleRobotReading - CREATE_PROBE_TABLE_SQL = 'CREATE TABLE media_audiocaptureprobe(id SERIAL PRIMARY KEY, user_id TEXT, guid TEXT, timestamp DOUBLE PRECISION, utc_logged TIMESTAMP, media_content_type TEXT, media_url TEXT, recording_duration BIGINT);' CREATE_PROBE_USER_ID_INDEX = 'CREATE INDEX ON media_audiocaptureprobe(user_id);' CREATE_PROBE_GUID_INDEX = 'CREATE INDEX ON media_audiocaptureprobe(guid);' @@ -16,43 +14,43 @@ def exists(connection_str, user_id, reading): conn = psycopg2.connect(connection_str) cursor = conn.cursor() - if probe_table_exists(conn) == False: + if probe_table_exists(conn) is False: conn.close() return False cursor.execute('SELECT id FROM media_audiocaptureprobe WHERE (user_id = %s AND guid = %s);', (user_id, reading['GUID'])) - - exists = (cursor.rowcount > 0) - + + row_exists = (cursor.rowcount > 0) + cursor.close() conn.close() - - return exists + + return row_exists + def probe_table_exists(conn): cursor = conn.cursor() cursor.execute('SELECT table_name FROM information_schema.tables WHERE (table_schema = \'public\' AND table_name = \'media_audiocaptureprobe\')') - - exists = (cursor.rowcount > 0) - + + table_exists = (cursor.rowcount > 0) + cursor.close() - - return exists - -def insert(connection_str, user_id, reading): -# print(json.dumps(reading, indent=2)) - + + return table_exists + + +def insert(connection_str, user_id, reading, check_exists=True): conn = psycopg2.connect(connection_str) cursor = conn.cursor() - - if probe_table_exists(conn) == False: + + if check_exists and probe_table_exists(conn) is False: cursor.execute(CREATE_PROBE_TABLE_SQL) cursor.execute(CREATE_PROBE_USER_ID_INDEX) cursor.execute(CREATE_PROBE_GUID_INDEX) cursor.execute(CREATE_PROBE_UTC_LOGGED_INDEX) - + conn.commit() - + reading_cmd = 'INSERT INTO media_audiocaptureprobe(user_id, ' + \ 'guid, ' + \ 'timestamp, ' + \ @@ -60,15 +58,15 @@ def insert(connection_str, user_id, reading): 'media_content_type, ' + \ 'media_url, ' + \ 'recording_duration) VALUES (%s, %s, %s, %s, %s, %s, %s) RETURNING id;' - - cursor.execute(reading_cmd, (user_id, \ - reading['GUID'], \ - reading['TIMESTAMP'], \ - datetime.datetime.fromtimestamp(reading['TIMESTAMP'], tz=pytz.utc), \ - reading['media_content_type'], \ - reading['media_url'], \ + + cursor.execute(reading_cmd, (user_id, + reading['GUID'], + reading['TIMESTAMP'], + datetime.datetime.fromtimestamp(reading['TIMESTAMP'], tz=pytz.utc), + reading['media_content_type'], + reading['media_url'], reading['RECORDING_DURATION'])) conn.commit() - + cursor.close() conn.close() diff --git a/management/commands/extractors/sensors_accelerometersensorprobe.py b/management/commands/extractors/sensors_accelerometersensorprobe.py index dac37bc..c235d12 100644 --- a/management/commands/extractors/sensors_accelerometersensorprobe.py +++ b/management/commands/extractors/sensors_accelerometersensorprobe.py @@ -1,5 +1,6 @@ +# pylint: disable=line-too-long, no-member + import datetime -import json import msgpack import psycopg2 import pytz @@ -16,67 +17,69 @@ CREATE_READING_READING_ID_INDEX = 'CREATE INDEX ON sensors_accelerometersensorprobe_reading(reading_id);' CREATE_READING_UTC_LOGGED_INDEX = 'CREATE INDEX ON sensors_accelerometersensorprobe_reading(utc_logged);' + def exists(connection_str, user_id, reading): conn = psycopg2.connect(connection_str) cursor = conn.cursor() - if probe_table_exists(conn) == False: + if probe_table_exists(conn) is False: conn.close() return False - if reading_table_exists(conn) == False: + if reading_table_exists(conn) is False: conn.close() return False cursor.execute('SELECT id FROM sensors_accelerometersensorprobe WHERE (user_id = %s AND guid = %s);', (user_id, reading['GUID'])) - - exists = (cursor.rowcount > 0) - + + row_exists = (cursor.rowcount > 0) + cursor.close() conn.close() - - return exists + + return row_exists + def probe_table_exists(conn): cursor = conn.cursor() cursor.execute('SELECT table_name FROM information_schema.tables WHERE (table_schema = \'public\' AND table_name = \'sensors_accelerometersensorprobe\')') - - exists = (cursor.rowcount > 0) - + + table_exists = (cursor.rowcount > 0) + cursor.close() - - return exists + + return table_exists + def reading_table_exists(conn): cursor = conn.cursor() cursor.execute('SELECT table_name FROM information_schema.tables WHERE (table_schema = \'public\' AND table_name = \'sensors_accelerometersensorprobe_reading\')') - - exists = (cursor.rowcount > 0) - + + table_exists = (cursor.rowcount > 0) + cursor.close() - - return exists -def insert(connection_str, user_id, reading): -# print(json.dumps(reading, indent=2)) - + return table_exists + + +def insert(connection_str, user_id, reading, check_exists=True): conn = psycopg2.connect(connection_str) cursor = conn.cursor() - - if probe_table_exists(conn) == False: + + if check_exists and probe_table_exists(conn) is False: cursor.execute(CREATE_PROBE_TABLE_SQL) cursor.execute(CREATE_PROBE_USER_ID_INDEX) cursor.execute(CREATE_PROBE_GUID_INDEX) cursor.execute(CREATE_PROBE_UTC_LOGGED_INDEX) - - if reading_table_exists(conn) == False: + + if check_exists and reading_table_exists(conn) is False: cursor.execute(CREATE_READING_TABLE_SQL) cursor.execute(CREATE_READING_USER_ID_INDEX) cursor.execute(CREATE_READING_READING_ID_INDEX) cursor.execute(CREATE_READING_UTC_LOGGED_INDEX) - + conn.commit() - + reading_cmd = 'INSERT INTO sensors_accelerometersensorprobe(user_id, ' + \ 'guid, ' + \ 'timestamp, ' + \ @@ -88,27 +91,27 @@ def insert(connection_str, user_id, reading): 'sensor_version, ' + \ 'sensor_resolution, ' + \ 'sensor_maximum_range) VALUES (%s, %s, %s, %s, %s, %s, %s, %s, %s, %s, %s) RETURNING id;' - - cursor.execute(reading_cmd, (user_id, \ - reading['GUID'], \ - reading['TIMESTAMP'], \ - datetime.datetime.fromtimestamp(reading['TIMESTAMP'], tz=pytz.utc), \ - reading['SENSOR']['VENDOR'], \ - reading['SENSOR']['NAME'], \ - reading['SENSOR']['POWER'], \ - reading['SENSOR']['TYPE'], \ - reading['SENSOR']['VERSION'], \ - reading['SENSOR']['RESOLUTION'], \ + + cursor.execute(reading_cmd, (user_id, + reading['GUID'], + reading['TIMESTAMP'], + datetime.datetime.fromtimestamp(reading['TIMESTAMP'], tz=pytz.utc), + reading['SENSOR']['VENDOR'], + reading['SENSOR']['NAME'], + reading['SENSOR']['POWER'], + reading['SENSOR']['TYPE'], + reading['SENSOR']['VERSION'], + reading['SENSOR']['RESOLUTION'], reading['SENSOR']['MAXIMUM_RANGE'])) - + for row in cursor.fetchall(): reading_id = row[0] - + reading_cursor = conn.cursor() pr_reading = PurpleRobotReading.objects.filter(guid=reading['GUID']).first() - - if pr_reading != None and pr_reading.attachment != None: + + if pr_reading is not None and pr_reading.attachment is not None: reading_cmd = 'INSERT INTO sensors_accelerometersensorprobe_reading(user_id, ' + \ 'reading_id, ' + \ 'utc_logged, ' + \ @@ -122,18 +125,18 @@ def insert(connection_str, user_id, reading): 'y, ' + \ 'z, ' + \ 'accuracy) VALUES (%s, %s, %s, %s, %s, %s, %s, %s, %s, %s, %s, %s, %s);' - + readings_file = None - + try: readings_file = pr_reading.attachment except ValueError: readings_file = None - - if readings_file != None: + + if readings_file is not None: try: content = list(msgpack.Unpacker(readings_file)) - + if len(content) == 8: time_buffer = content[1] sensor_time_buffer = content[2] @@ -142,13 +145,13 @@ def insert(connection_str, user_id, reading): x_buffer = content[5] y_buffer = content[6] z_buffer = content[7] - + for i in range(0, len(time_buffer)): - values = [ user_id, reading_id, datetime.datetime.fromtimestamp(reading['TIMESTAMP'], tz=pytz.utc) ] - + values = [user_id, reading_id, datetime.datetime.fromtimestamp(reading['TIMESTAMP'], tz=pytz.utc)] + values.append(time_buffer[i]) values.append(datetime.datetime.fromtimestamp(time_buffer[i], tz=pytz.utc)) - + values.append(sensor_time_buffer[i]) try: values.append(datetime.datetime.fromtimestamp((sensor_time_buffer[i] / 1000), tz=pytz.utc)) @@ -161,12 +164,12 @@ def insert(connection_str, user_id, reading): values.append(y_buffer[i]) values.append(z_buffer[i]) values.append(accuracy_buffer[i]) - + reading_cursor.execute(reading_cmd, values) except ValueError: pass conn.commit() - + cursor.close() conn.close() diff --git a/management/commands/extractors/sensors_lightsensorprobe.py b/management/commands/extractors/sensors_lightsensorprobe.py index ef08c6a..e0c02cc 100644 --- a/management/commands/extractors/sensors_lightsensorprobe.py +++ b/management/commands/extractors/sensors_lightsensorprobe.py @@ -1,5 +1,6 @@ +# pylint: disable=line-too-long, no-member + import datetime -import json import msgpack import psycopg2 import pytz @@ -16,67 +17,69 @@ CREATE_READING_READING_ID_INDEX = 'CREATE INDEX ON sensors_lightsensorprobe_reading(reading_id);' CREATE_READING_UTC_LOGGED_INDEX = 'CREATE INDEX ON sensors_lightsensorprobe_reading(utc_logged);' + def exists(connection_str, user_id, reading): conn = psycopg2.connect(connection_str) cursor = conn.cursor() - if probe_table_exists(conn) == False: + if probe_table_exists(conn) is False: conn.close() return False - if reading_table_exists(conn) == False: + if reading_table_exists(conn) is False: conn.close() return False cursor.execute('SELECT id FROM sensors_lightsensorprobe WHERE (user_id = %s AND guid = %s);', (user_id, reading['GUID'])) - - exists = (cursor.rowcount > 0) - + + row_exists = (cursor.rowcount > 0) + cursor.close() conn.close() - - return exists + + return row_exists + def probe_table_exists(conn): cursor = conn.cursor() cursor.execute('SELECT table_name FROM information_schema.tables WHERE (table_schema = \'public\' AND table_name = \'sensors_lightsensorprobe\')') - - exists = (cursor.rowcount > 0) - + + table_exists = (cursor.rowcount > 0) + cursor.close() - - return exists + + return table_exists + def reading_table_exists(conn): cursor = conn.cursor() cursor.execute('SELECT table_name FROM information_schema.tables WHERE (table_schema = \'public\' AND table_name = \'sensors_lightsensorprobe_reading\')') - - exists = (cursor.rowcount > 0) - + + table_exists = (cursor.rowcount > 0) + cursor.close() - - return exists -def insert(connection_str, user_id, reading): -# print(json.dumps(reading, indent=2)) - + return table_exists + + +def insert(connection_str, user_id, reading, check_exists=True): conn = psycopg2.connect(connection_str) cursor = conn.cursor() - - if probe_table_exists(conn) == False: + + if check_exists and probe_table_exists(conn) is False: cursor.execute(CREATE_PROBE_TABLE_SQL) cursor.execute(CREATE_PROBE_USER_ID_INDEX) cursor.execute(CREATE_PROBE_GUID_INDEX) cursor.execute(CREATE_PROBE_UTC_LOGGED_INDEX) - - if reading_table_exists(conn) == False: + + if check_exists and reading_table_exists(conn) is False: cursor.execute(CREATE_READING_TABLE_SQL) cursor.execute(CREATE_READING_USER_ID_INDEX) cursor.execute(CREATE_READING_READING_ID_INDEX) cursor.execute(CREATE_READING_UTC_LOGGED_INDEX) - + conn.commit() - + reading_cmd = 'INSERT INTO sensors_lightsensorprobe(user_id, ' + \ 'guid, ' + \ 'timestamp, ' + \ @@ -88,27 +91,27 @@ def insert(connection_str, user_id, reading): 'sensor_version, ' + \ 'sensor_resolution, ' + \ 'sensor_maximum_range) VALUES (%s, %s, %s, %s, %s, %s, %s, %s, %s, %s, %s) RETURNING id;' - - cursor.execute(reading_cmd, (user_id, \ - reading['GUID'], \ - reading['TIMESTAMP'], \ - datetime.datetime.fromtimestamp(reading['TIMESTAMP'], tz=pytz.utc), \ - reading['SENSOR']['VENDOR'], \ - reading['SENSOR']['NAME'], \ - reading['SENSOR']['POWER'], \ - reading['SENSOR']['TYPE'], \ - reading['SENSOR']['VERSION'], \ - reading['SENSOR']['RESOLUTION'], \ + + cursor.execute(reading_cmd, (user_id, + reading['GUID'], + reading['TIMESTAMP'], + datetime.datetime.fromtimestamp(reading['TIMESTAMP'], tz=pytz.utc), + reading['SENSOR']['VENDOR'], + reading['SENSOR']['NAME'], + reading['SENSOR']['POWER'], + reading['SENSOR']['TYPE'], + reading['SENSOR']['VERSION'], + reading['SENSOR']['RESOLUTION'], reading['SENSOR']['MAXIMUM_RANGE'])) - + for row in cursor.fetchall(): reading_id = row[0] - + reading_cursor = conn.cursor() pr_reading = PurpleRobotReading.objects.filter(guid=reading['GUID']).first() - - if pr_reading != None and pr_reading.attachment != None: + + if pr_reading is not None and pr_reading.attachment is not None: reading_cmd = 'INSERT INTO sensors_lightsensorprobe_reading(user_id, ' + \ 'reading_id, ' + \ 'utc_logged, ' + \ @@ -120,32 +123,33 @@ def insert(connection_str, user_id, reading): 'normalized_timestamp_utc, ' + \ 'lux, ' + \ 'accuracy) VALUES (%s, %s, %s, %s, %s, %s, %s, %s, %s, %s, %s);' - + readings_file = None - + try: readings_file = pr_reading.attachment except ValueError: readings_file = None - - if readings_file != None: + + if readings_file is not None: try: content = list(msgpack.Unpacker(readings_file)) - + if len(content) == 6: time_buffer = content[1] sensor_time_buffer = content[2] normal_time_buffer = content[3] accuracy_buffer = content[4] lux_buffer = content[5] - + for i in range(0, len(time_buffer)): - values = [ user_id, reading_id, datetime.datetime.fromtimestamp(reading['TIMESTAMP'], tz=pytz.utc) ] - + values = [user_id, reading_id, datetime.datetime.fromtimestamp(reading['TIMESTAMP'], tz=pytz.utc)] + values.append(time_buffer[i]) values.append(datetime.datetime.fromtimestamp(time_buffer[i], tz=pytz.utc)) - + values.append(sensor_time_buffer[i]) + try: values.append(datetime.datetime.fromtimestamp((sensor_time_buffer[i] / 1000), tz=pytz.utc)) except ValueError: @@ -155,12 +159,13 @@ def insert(connection_str, user_id, reading): values.append(datetime.datetime.fromtimestamp(normal_time_buffer[i], tz=pytz.utc)) values.append(lux_buffer[i]) values.append(accuracy_buffer[i]) - + reading_cursor.execute(reading_cmd, values) except ValueError: pass - + except TypeError: + pass conn.commit() - + cursor.close() conn.close() diff --git a/management/commands/extractors/services_fitbitbetaprobe copy.py b/management/commands/extractors/services_fitbitbetaprobe copy.py index ba36ec0..4c7f60a 100644 --- a/management/commands/extractors/services_fitbitbetaprobe copy.py +++ b/management/commands/extractors/services_fitbitbetaprobe copy.py @@ -1,5 +1,6 @@ +# pylint: disable=line-too-long + import datetime -import json import psycopg2 import pytz @@ -38,163 +39,169 @@ CREATE_ELEVATION_ID_INDEX = 'CREATE INDEX ON services_fitbitbetaprobe_elevation(reading_id);' CREATE_ELEVATION_UTC_LOGGED_INDEX = 'CREATE INDEX ON services_fitbitbetaprobe_elevation(utc_logged);' + def exists(connection_str, user_id, reading): conn = psycopg2.connect(connection_str) cursor = conn.cursor() - if probe_table_exists(conn) == False or heart_table_exists(conn) == False or distance_table_exists(conn) == False or calories_table_exists(conn) == False or steps_table_exists(conn) == False or floors_table_exists(conn) == False or elevation_table_exists(conn) == False: + if probe_table_exists(conn) is False or heart_table_exists(conn) is False or distance_table_exists(conn) is False or calories_table_exists(conn) is False or steps_table_exists(conn) is False or floors_table_exists(conn) is False or elevation_table_exists(conn) is False: conn.close() return False cursor.execute('SELECT id FROM services_fitbitbetaprobe WHERE (user_id = %s AND guid = %s);', (user_id, reading['GUID'])) - - exists = (cursor.rowcount > 0) - + + row_exists = (cursor.rowcount > 0) + cursor.close() conn.close() - - return exists + + return row_exists + def probe_table_exists(conn): cursor = conn.cursor() cursor.execute('SELECT table_name FROM information_schema.tables WHERE (table_schema = \'public\' AND table_name = \'services_fitbitbetaprobe\')') - - exists = (cursor.rowcount > 0) - + + table_exists = (cursor.rowcount > 0) + cursor.close() - - return exists + + return table_exists + def heart_table_exists(conn): cursor = conn.cursor() cursor.execute('SELECT table_name FROM information_schema.tables WHERE (table_schema = \'public\' AND table_name = \'services_fitbitbetaprobe_heart\')') - - exists = (cursor.rowcount > 0) - + + table_exists = (cursor.rowcount > 0) + cursor.close() - - return exists + + return table_exists + def steps_table_exists(conn): cursor = conn.cursor() cursor.execute('SELECT table_name FROM information_schema.tables WHERE (table_schema = \'public\' AND table_name = \'services_fitbitbetaprobe_steps\')') - - exists = (cursor.rowcount > 0) - + + table_exists = (cursor.rowcount > 0) + cursor.close() - - return exists + + return table_exists + def distance_table_exists(conn): cursor = conn.cursor() cursor.execute('SELECT table_name FROM information_schema.tables WHERE (table_schema = \'public\' AND table_name = \'services_fitbitbetaprobe_distance\')') - - exists = (cursor.rowcount > 0) - + + table_exists = (cursor.rowcount > 0) + cursor.close() - - return exists + + return table_exists + def calories_table_exists(conn): cursor = conn.cursor() cursor.execute('SELECT table_name FROM information_schema.tables WHERE (table_schema = \'public\' AND table_name = \'services_fitbitbetaprobe_calories\')') - - exists = (cursor.rowcount > 0) - + + table_exists = (cursor.rowcount > 0) + cursor.close() - - return exists + + return table_exists + def floors_table_exists(conn): cursor = conn.cursor() cursor.execute('SELECT table_name FROM information_schema.tables WHERE (table_schema = \'public\' AND table_name = \'services_fitbitbetaprobe_floors\')') - - exists = (cursor.rowcount > 0) - + + table_exists = (cursor.rowcount > 0) + cursor.close() - - return exists + + return table_exists + def elevation_table_exists(conn): cursor = conn.cursor() cursor.execute('SELECT table_name FROM information_schema.tables WHERE (table_schema = \'public\' AND table_name = \'services_fitbitbetaprobe_elevation\')') - - exists = (cursor.rowcount > 0) - + + table_exists = (cursor.rowcount > 0) + cursor.close() - - return exists + + return table_exists -def insert(connection_str, user_id, reading): -# print(json.dumps(reading, indent=2)) - +def insert(connection_str, user_id, reading, check_exists=True): conn = psycopg2.connect(connection_str) cursor = conn.cursor() - - if probe_table_exists(conn) == False: + + if check_exists and probe_table_exists(conn) is False: cursor.execute(CREATE_PROBE_TABLE_SQL) cursor.execute(CREATE_PROBE_USER_ID_INDEX) cursor.execute(CREATE_PROBE_GUID_INDEX) cursor.execute(CREATE_PROBE_UTC_LOGGED_INDEX) - - if heart_table_exists(conn) == False: + + if check_exists and heart_table_exists(conn) is False: cursor.execute(CREATE_HEART_TABLE_SQL) cursor.execute(CREATE_HEART_USER_ID_INDEX) cursor.execute(CREATE_HEART_ID_INDEX) cursor.execute(CREATE_HEART_UTC_LOGGED_INDEX) - - if distance_table_exists(conn) == False: + + if check_exists and distance_table_exists(conn) is False: cursor.execute(CREATE_DISTANCE_TABLE_SQL) cursor.execute(CREATE_DISTANCE_USER_ID_INDEX) cursor.execute(CREATE_DISTANCE_ID_INDEX) cursor.execute(CREATE_DISTANCE_UTC_LOGGED_INDEX) - - if calories_table_exists(conn) == False: + + if check_exists and calories_table_exists(conn) is False: cursor.execute(CREATE_CALORIES_TABLE_SQL) cursor.execute(CREATE_CALORIES_USER_ID_INDEX) cursor.execute(CREATE_CALORIES_ID_INDEX) cursor.execute(CREATE_CALORIES_UTC_LOGGED_INDEX) - - if steps_table_exists(conn) == False: + + if check_exists and steps_table_exists(conn) is False: cursor.execute(CREATE_STEPS_TABLE_SQL) cursor.execute(CREATE_STEPS_USER_ID_INDEX) cursor.execute(CREATE_STEPS_ID_INDEX) cursor.execute(CREATE_STEPS_UTC_LOGGED_INDEX) - - if floors_table_exists(conn) == False: + + if check_exists and floors_table_exists(conn) is False: cursor.execute(CREATE_FLOORS_TABLE_SQL) cursor.execute(CREATE_FLOORS_USER_ID_INDEX) cursor.execute(CREATE_FLOORS_ID_INDEX) cursor.execute(CREATE_FLOORS_UTC_LOGGED_INDEX) - - if elevation_table_exists(conn) == False: + + if check_exists and elevation_table_exists(conn) is False: cursor.execute(CREATE_ELEVATION_TABLE_SQL) cursor.execute(CREATE_ELEVATION_USER_ID_INDEX) cursor.execute(CREATE_ELEVATION_ID_INDEX) cursor.execute(CREATE_ELEVATION_UTC_LOGGED_INDEX) - + conn.commit() - + reading_cmd = 'INSERT INTO services_fitbitbetaprobe(user_id, ' + \ 'guid, ' + \ 'timestamp, ' + \ 'utc_logged) VALUES (%s, %s, %s, %s) RETURNING id;' - - cursor.execute(reading_cmd, (user_id, \ - reading['GUID'], \ - reading['TIMESTAMP'], \ + + cursor.execute(reading_cmd, (user_id, + reading['GUID'], + reading['TIMESTAMP'], datetime.datetime.fromtimestamp(reading['TIMESTAMP'], tz=pytz.utc))) - + for row in cursor.fetchall(): reading_id = row[0] - + reading_cursor = conn.cursor() - + if 'DISTANCE' in reading: for i in range(0, len(reading['DISTANCE'])): value = reading['DISTANCE'][i] - ts = reading['DISTANCE_TIMESTAMPS'][i] + timestamp = reading['DISTANCE_TIMESTAMPS'][i] reading_cmd = 'INSERT INTO services_fitbitbetaprobe_distance(user_id, ' + \ 'reading_id, ' + \ @@ -202,31 +209,31 @@ def insert(connection_str, user_id, reading): 'sensor_timestamp, ' + \ 'sensor_timestamp_utc, ' + \ 'distance) VALUES (%s, %s, %s, %s, %s, %s);' - - values = [ user_id, reading_id, datetime.datetime.fromtimestamp(reading['TIMESTAMP'], tz=pytz.utc), ts, datetime.datetime.fromtimestamp(ts / 1000, tz=pytz.utc), value ] - + + values = [user_id, reading_id, datetime.datetime.fromtimestamp(reading['TIMESTAMP'], tz=pytz.utc), timestamp, datetime.datetime.fromtimestamp(timestamp / 1000, tz=pytz.utc), value] + reading_cursor.execute(reading_cmd, values) if 'CALORIES' in reading: - for i in range(0, len(reading['CALORIES'])): - value = reading['CALORIES'][i] - ts = reading['CALORIES_TIMESTAMPS'][i] - - reading_cmd = 'INSERT INTO services_fitbitbetaprobe_calories(user_id, ' + \ - 'reading_id, ' + \ - 'utc_logged, ' + \ - 'sensor_timestamp, ' + \ - 'sensor_timestamp_utc, ' + \ - 'calories) VALUES (%s, %s, %s, %s, %s, %s);' - - values = [ user_id, reading_id, datetime.datetime.fromtimestamp(reading['TIMESTAMP'], tz=pytz.utc), ts, datetime.datetime.fromtimestamp(ts / 1000, tz=pytz.utc), value ] - - reading_cursor.execute(reading_cmd, values) - + for i in range(0, len(reading['CALORIES'])): + value = reading['CALORIES'][i] + timestamp = reading['CALORIES_TIMESTAMPS'][i] + + reading_cmd = 'INSERT INTO services_fitbitbetaprobe_calories(user_id, ' + \ + 'reading_id, ' + \ + 'utc_logged, ' + \ + 'sensor_timestamp, ' + \ + 'sensor_timestamp_utc, ' + \ + 'calories) VALUES (%s, %s, %s, %s, %s, %s);' + + values = [user_id, reading_id, datetime.datetime.fromtimestamp(reading['TIMESTAMP'], tz=pytz.utc), timestamp, datetime.datetime.fromtimestamp(timestamp / 1000, tz=pytz.utc), value] + + reading_cursor.execute(reading_cmd, values) + if 'STEPS' in reading: for i in range(0, len(reading['STEPS'])): value = reading['STEPS'][i] - ts = reading['STEP_TIMESTAMPS'][i] + timestamp = reading['STEP_TIMESTAMPS'][i] reading_cmd = 'INSERT INTO services_fitbitbetaprobe_steps(user_id, ' + \ 'reading_id, ' + \ @@ -234,15 +241,15 @@ def insert(connection_str, user_id, reading): 'sensor_timestamp, ' + \ 'sensor_timestamp_utc, ' + \ 'steps) VALUES (%s, %s, %s, %s, %s, %s);' - - values = [ user_id, reading_id, datetime.datetime.fromtimestamp(reading['TIMESTAMP'], tz=pytz.utc), ts, datetime.datetime.fromtimestamp(ts / 1000, tz=pytz.utc), value ] - + + values = [user_id, reading_id, datetime.datetime.fromtimestamp(reading['TIMESTAMP'], tz=pytz.utc), timestamp, datetime.datetime.fromtimestamp(timestamp / 1000, tz=pytz.utc), value] + reading_cursor.execute(reading_cmd, values) - + if 'FLOORS' in reading: for i in range(0, len(reading['FLOORS'])): value = reading['FLOORS'][i] - ts = reading['FLOORS_TIMESTAMPS'][i] + timestamp = reading['FLOORS_TIMESTAMPS'][i] reading_cmd = 'INSERT INTO services_fitbitbetaprobe_floors(user_id, ' + \ 'reading_id, ' + \ @@ -250,46 +257,46 @@ def insert(connection_str, user_id, reading): 'sensor_timestamp, ' + \ 'sensor_timestamp_utc, ' + \ 'floors) VALUES (%s, %s, %s, %s, %s, %s);' - - values = [ user_id, reading_id, datetime.datetime.fromtimestamp(reading['TIMESTAMP'], tz=pytz.utc), ts, datetime.datetime.fromtimestamp(ts / 1000, tz=pytz.utc), value ] - + + values = [user_id, reading_id, datetime.datetime.fromtimestamp(reading['TIMESTAMP'], tz=pytz.utc), timestamp, datetime.datetime.fromtimestamp(timestamp / 1000, tz=pytz.utc), value] + reading_cursor.execute(reading_cmd, values) if 'HEART' in reading: - for i in range(0, len(reading['HEART'])): - value = reading['HEART'][i] - ts = reading['HEART_TIMESTAMPS'][i] - - reading_cmd = 'INSERT INTO services_fitbitbetaprobe_heart(user_id, ' + \ - 'reading_id, ' + \ - 'utc_logged, ' + \ - 'sensor_timestamp, ' + \ - 'sensor_timestamp_utc, ' + \ - 'avg_heartrate) VALUES (%s, %s, %s, %s, %s, %s);' - - values = [ user_id, reading_id, datetime.datetime.fromtimestamp(reading['TIMESTAMP'], tz=pytz.utc), ts, datetime.datetime.fromtimestamp(ts / 1000, tz=pytz.utc), value ] - - reading_cursor.execute(reading_cmd, values) - + for i in range(0, len(reading['HEART'])): + value = reading['HEART'][i] + timestamp = reading['HEART_TIMESTAMPS'][i] + + reading_cmd = 'INSERT INTO services_fitbitbetaprobe_heart(user_id, ' + \ + 'reading_id, ' + \ + 'utc_logged, ' + \ + 'sensor_timestamp, ' + \ + 'sensor_timestamp_utc, ' + \ + 'avg_heartrate) VALUES (%s, %s, %s, %s, %s, %s);' + + values = [user_id, reading_id, datetime.datetime.fromtimestamp(reading['TIMESTAMP'], tz=pytz.utc), timestamp, datetime.datetime.fromtimestamp(timestamp / 1000, tz=pytz.utc), value] + + reading_cursor.execute(reading_cmd, values) + if 'ELEVATION' in reading: - for i in range(0, len(reading['ELEVATION'])): - value = reading['ELEVATION'][i] - ts = reading['ELEVATION_TIMESTAMPS'][i] - - reading_cmd = 'INSERT INTO services_fitbitbetaprobe_elevation(user_id, ' + \ - 'reading_id, ' + \ - 'utc_logged, ' + \ - 'sensor_timestamp, ' + \ - 'sensor_timestamp_utc, ' + \ - 'elevation) VALUES (%s, %s, %s, %s, %s, %s);' - - values = [ user_id, reading_id, datetime.datetime.fromtimestamp(reading['TIMESTAMP'], tz=pytz.utc), ts, datetime.datetime.fromtimestamp(ts / 1000, tz=pytz.utc), value ] - - reading_cursor.execute(reading_cmd, values) - + for i in range(0, len(reading['ELEVATION'])): + value = reading['ELEVATION'][i] + timestamp = reading['ELEVATION_TIMESTAMPS'][i] + + reading_cmd = 'INSERT INTO services_fitbitbetaprobe_elevation(user_id, ' + \ + 'reading_id, ' + \ + 'utc_logged, ' + \ + 'sensor_timestamp, ' + \ + 'sensor_timestamp_utc, ' + \ + 'elevation) VALUES (%s, %s, %s, %s, %s, %s);' + + values = [user_id, reading_id, datetime.datetime.fromtimestamp(reading['TIMESTAMP'], tz=pytz.utc), timestamp, datetime.datetime.fromtimestamp(timestamp / 1000, tz=pytz.utc), value] + + reading_cursor.execute(reading_cmd, values) + reading_cursor.close() conn.commit() - + cursor.close() conn.close() diff --git a/management/commands/extractors/services_googleplacesprobe.py b/management/commands/extractors/services_googleplacesprobe.py index 9347e0d..d1f88ca 100644 --- a/management/commands/extractors/services_googleplacesprobe.py +++ b/management/commands/extractors/services_googleplacesprobe.py @@ -1,3 +1,5 @@ +# pylint: disable=line-too-long + import datetime import json import psycopg2 @@ -8,54 +10,55 @@ CREATE_PROBE_GUID_INDEX = 'CREATE INDEX ON services_googleplacesprobe(guid);' CREATE_PROBE_UTC_LOGGED_INDEX = 'CREATE INDEX ON services_googleplacesprobe(utc_logged);' + def exists(connection_str, user_id, reading): conn = psycopg2.connect(connection_str) - + if probe_table_exists(conn) == False: conn.close() return False cursor = conn.cursor() - + cursor.execute('SELECT id FROM services_googleplacesprobe WHERE (user_id = %s AND guid = %s);', (user_id, reading['GUID'])) - - exists = (cursor.rowcount > 0) - + + row_exists = (cursor.rowcount > 0) + cursor.close() conn.close() - - return exists + + return row_exists + def probe_table_exists(conn): cursor = conn.cursor() cursor.execute('SELECT table_name FROM information_schema.tables WHERE (table_schema = \'public\' AND table_name = \'services_googleplacesprobe\')') - - probe_table_exists = (cursor.rowcount > 0) - + + table_exists = (cursor.rowcount > 0) + cursor.close() - - return probe_table_exists -def insert(connection_str, user_id, reading): -# print(json.dumps(reading, indent=2)) - + return table_exists + + +def insert(connection_str, user_id, reading, check_exists=True): conn = psycopg2.connect(connection_str) cursor = conn.cursor() - - if probe_table_exists(conn) == False: + + if check_exists and probe_table_exists(conn) == False: cursor.execute(CREATE_PROBE_TABLE_SQL) cursor.execute(CREATE_PROBE_USER_ID_INDEX) cursor.execute(CREATE_PROBE_GUID_INDEX) cursor.execute(CREATE_PROBE_UTC_LOGGED_INDEX) - + conn.commit() - + places_only = {} - - for k,v in reading.iteritems(): - if k != 'MOST_LIKELY_PLACE_ID' and k != 'MOST_LIKELY_PLACE' and k != 'MOST_LIKELY_PLACE_LIKELIHOOD' and k != 'GUID' and k != 'TIMESTAMP' and k != 'PROBE': - places_only[k] = v - + + for key, value in reading.iteritems(): + if key != 'MOST_LIKELY_PLACE_ID' and key != 'MOST_LIKELY_PLACE' and key != 'MOST_LIKELY_PLACE_LIKELIHOOD' and key != 'GUID' and key != 'TIMESTAMP' and key != 'PROBE': + places_only[key] = value + reading_cmd = 'INSERT INTO services_googleplacesprobe(user_id, ' + \ 'guid, ' + \ 'timestamp, ' + \ @@ -64,31 +67,31 @@ def insert(connection_str, user_id, reading): 'most_likely_place, ' + \ 'most_likely_place_likelihood, ' + \ 'places_json) VALUES (%s, %s, %s, %s, %s, %s, %s, %s) RETURNING id;' - + most_likely_id = None - + if 'MOST_LIKELY_PLACE_ID' in reading: - most_likely_id = reading['MOST_LIKELY_PLACE_ID'] + most_likely_id = reading['MOST_LIKELY_PLACE_ID'] most_likely_place = None - + if 'MOST_LIKELY_PLACE' in reading: - most_likely_place = reading['MOST_LIKELY_PLACE'] + most_likely_place = reading['MOST_LIKELY_PLACE'] most_likely_likelihood = None - + if 'MOST_LIKELY_PLACE_LIKELIHOOD' in reading: - most_likely_likelihood = reading['MOST_LIKELY_PLACE_LIKELIHOOD'] - - cursor.execute(reading_cmd, (user_id, \ - reading['GUID'], \ - reading['TIMESTAMP'], \ - datetime.datetime.fromtimestamp(reading['TIMESTAMP'], tz=pytz.utc), \ - most_likely_id, \ - most_likely_place, \ - most_likely_likelihood, \ + most_likely_likelihood = reading['MOST_LIKELY_PLACE_LIKELIHOOD'] + + cursor.execute(reading_cmd, (user_id, + reading['GUID'], + reading['TIMESTAMP'], + datetime.datetime.fromtimestamp(reading['TIMESTAMP'], tz=pytz.utc), + most_likely_id, + most_likely_place, + most_likely_likelihood, json.dumps(places_only))) conn.commit() - + cursor.close() conn.close() diff --git a/management/commands/fitbit_beta_check.py b/management/commands/fitbit_beta_check.py index 8dda689..7e8bfda 100644 --- a/management/commands/fitbit_beta_check.py +++ b/management/commands/fitbit_beta_check.py @@ -1,37 +1,36 @@ +# pylint: disable=line-too-long, no-member + import json from django.core.management.base import BaseCommand -from django.conf import settings -from django.utils import timezone from purple_robot_app.models import PurpleRobotReading + class Command(BaseCommand): def handle(self, *args, **options): seen = {} - - keys = [ 'HEART', 'FLOORS', 'ELEVATION', 'DISTANCE', 'CALORIES', 'STEPS' ] - + + keys = ['HEART', 'FLOORS', 'ELEVATION', 'DISTANCE', 'CALORIES', 'STEPS'] + for reading in PurpleRobotReading.objects.filter(user_id='073adb668d74a6aaa7d452eb9f804c99', probe='edu.northwestern.cbits.purple_robot_manager.probes.services.FitbitBetaProbe').order_by('logged'): payload = json.loads(reading.payload) - + for key in keys: values = payload[key] - + if key == 'STEPS': key = 'STEP' - + times = payload[key + '_TIMESTAMPS'] - + for i in range(0, len(values)): value = values[i] time = times[i] - + seen_key = key + '_' + str(time) - + if seen_key in seen: - print('DUPE VALUE: ' + seen_key + ' - O: ' + str(seen[seen_key]) + ' N: ' + str(value) + ' --> ' + reading.guid) - + print 'DUPE VALUE: ' + seen_key + ' - O: ' + str(seen[seen_key]) + ' N: ' + str(value) + ' --> ' + reading.guid + seen[seen_key] = value - - diff --git a/management/commands/gather_statistics.py b/management/commands/gather_statistics.py new file mode 100644 index 0000000..eb332e0 --- /dev/null +++ b/management/commands/gather_statistics.py @@ -0,0 +1,129 @@ +# pylint: disable=line-too-long, no-member + +import arrow +import datetime +import os +import pytz + +from django.core.management.base import BaseCommand +from django.conf import settings +from django.utils import timezone + +from purple_robot_app.models import PurpleRobotPayload +from purple_robot_app.performance import append_performance_sample + + +def touch(fname, mode=0o666): + flags = os.O_CREAT | os.O_APPEND + + if os.fdopen(os.open(fname, flags, mode)) is not None: + os.utime(fname, None) + + +class Command(BaseCommand): + def handle(self, *args, **options): + if os.access('/tmp/gather_statistics.lock', os.R_OK): + timestamp = os.path.getmtime('/tmp/gather_statistics.lock') + created = datetime.datetime.fromtimestamp(timestamp) + + if (datetime.datetime.now() - created).total_seconds() > 6 * 60 * 60: + print 'gather_statistics: Stale lock - removing...' + os.remove('/tmp/gather_statistics.lock') + else: + return + + touch('/tmp/gather_statistics.lock') + + here_tz = pytz.timezone(settings.TIME_ZONE) + now = arrow.get(timezone.now().astimezone(here_tz)) + + start_today = now.replace(hour=0, minute=0, second=0, microsecond=0).datetime + start_hour = now.datetime - datetime.timedelta(hours=1) + + count = PurpleRobotPayload.objects.filter(added__gte=start_hour).count() + append_performance_sample('system', 'uploads_hour', timezone.now(), {'count': count}) + + count = PurpleRobotPayload.objects.filter(added__gte=start_today).count() + append_performance_sample('system', 'uploads_today', timezone.now(), {'count': count}) + + tag = 'extracted_into_database' + skip_tag = 'extracted_into_database_skip' + + count = PurpleRobotPayload.objects.exclude(process_tags__contains=tag).exclude(process_tags__contains=skip_tag).count() + append_performance_sample('system', 'pending_mirror_payloads', timezone.now(), {'count': count}) + + count = PurpleRobotPayload.objects.filter(process_tags__contains=skip_tag).count() + append_performance_sample('system', 'skipped_mirror_payloads', timezone.now(), {'count': count}) + + week = now.datetime - datetime.timedelta(days=7) + day = now.datetime - datetime.timedelta(days=1) + half_day = now.datetime - datetime.timedelta(hours=12) + quarter_day = now.datetime - datetime.timedelta(hours=6) + hour = now.datetime - datetime.timedelta(hours=1) + + week_count = PurpleRobotPayload.objects.filter(added__lt=week).exclude(process_tags__contains=tag).exclude(process_tags__contains=skip_tag).count() + day_count = PurpleRobotPayload.objects.filter(added__gte=week, added__lt=day).exclude(process_tags__contains=tag).exclude(process_tags__contains=skip_tag).count() + half_day_count = PurpleRobotPayload.objects.filter(added__gte=day, added__lt=half_day).exclude(process_tags__contains=tag).exclude(process_tags__contains=skip_tag).count() + quarter_day_count = PurpleRobotPayload.objects.filter(added__gte=half_day, added__lt=quarter_day).exclude(process_tags__contains=tag).exclude(process_tags__contains=skip_tag).count() + hour_count = PurpleRobotPayload.objects.filter(added__gte=quarter_day, added__lt=hour).exclude(process_tags__contains=tag).exclude(process_tags__contains=skip_tag).count() + less_hour_count = PurpleRobotPayload.objects.filter(added__gte=hour).exclude(process_tags__contains=tag).exclude(process_tags__contains=skip_tag).count() + + append_performance_sample('system', 'pending_mirror_ages', timezone.now(), { + 'week_count': week_count, + 'day_count': day_count, + 'half_day_count': half_day_count, + 'quarter_day_count': quarter_day_count, + 'hour_count': hour_count, + 'less_hour_count': less_hour_count + }) + + tag = 'extracted_readings' + skip_tag = 'ingest_error' + + count = PurpleRobotPayload.objects.exclude(process_tags__contains=tag).exclude(process_tags__contains=skip_tag).count() + append_performance_sample('system', 'pending_ingest_payloads', timezone.now(), {'count': count}) + + count = PurpleRobotPayload.objects.filter(process_tags__contains=skip_tag).count() + append_performance_sample('system', 'skipped_ingest_payloads', timezone.now(), {'count': count}) + + week_count = PurpleRobotPayload.objects.filter(added__lt=week).exclude(process_tags__contains=tag).exclude(process_tags__contains=skip_tag).count() + day_count = PurpleRobotPayload.objects.filter(added__gte=week, added__lt=day).exclude(process_tags__contains=tag).exclude(process_tags__contains=skip_tag).count() + half_day_count = PurpleRobotPayload.objects.filter(added__gte=day, added__lt=half_day).exclude(process_tags__contains=tag).exclude(process_tags__contains=skip_tag).count() + quarter_day_count = PurpleRobotPayload.objects.filter(added__gte=half_day, added__lt=quarter_day).exclude(process_tags__contains=tag).exclude(process_tags__contains=skip_tag).count() + hour_count = PurpleRobotPayload.objects.filter(added__gte=quarter_day, added__lt=hour).exclude(process_tags__contains=tag).exclude(process_tags__contains=skip_tag).count() + less_hour_count = PurpleRobotPayload.objects.filter(added__gte=hour).exclude(process_tags__contains=tag).exclude(process_tags__contains=skip_tag).count() + + append_performance_sample('system', 'pending_ingest_ages', timezone.now(), { + 'week_count': week_count, + 'day_count': day_count, + 'half_day_count': half_day_count, + 'quarter_day_count': quarter_day_count, + 'hour_count': hour_count, + 'less_hour_count': less_hour_count + }) + + uptime = os.popen("/usr/bin/uptime").read() + uptime = uptime.split('load average: ')[1].split(" ") + + load_minute = float(uptime[0].replace(',', '').strip()) + load_five = float(uptime[1].replace(',', '').strip()) + load_fifteen = float(uptime[2].replace(',', '').strip()) + + append_performance_sample('system', 'server_performance', timezone.now(), {'load_minute': load_minute, 'load_five': load_five, 'load_fifteen': load_fifteen}) + + counts = [] + + index_time = start_today + + while (index_time + datetime.timedelta(seconds=(15 * 60))) < timezone.now(): + end = index_time + datetime.timedelta(seconds=(30 * 60)) + + plot = index_time + datetime.timedelta(seconds=(15 * 60)) + + counts.append({'date': plot.isoformat(), 'count': PurpleRobotPayload.objects.filter(added__gte=index_time, added__lt=end).count()}) + + index_time = end + + append_performance_sample('system', 'payload_uploads', timezone.now(), {'counts': counts}) + + os.remove('/tmp/gather_statistics.lock') diff --git a/management/commands/generate_md5_hash.py b/management/commands/generate_md5_hash.py new file mode 100644 index 0000000..fba1722 --- /dev/null +++ b/management/commands/generate_md5_hash.py @@ -0,0 +1,15 @@ +# pylint: disable=line-too-long + +import hashlib + +from django.core.management.base import BaseCommand + + +class Command(BaseCommand): + def handle(self, *args, **options): + md5_hash = hashlib.md5() + md5_hash.update(args[0]) + + user_id = md5_hash.hexdigest() + + print 'Hash for ' + args[0] + ': ' + user_id diff --git a/management/commands/generate_probe_tests.py b/management/commands/generate_probe_tests.py index 24126d7..f3a4e03 100644 --- a/management/commands/generate_probe_tests.py +++ b/management/commands/generate_probe_tests.py @@ -1,3 +1,5 @@ +# pylint: disable=line-too-long, no-member + from django.core.management.base import BaseCommand from django.utils import timezone from django.utils.text import slugify @@ -6,15 +8,16 @@ REPORT_DAYS = 1 + class Command(BaseCommand): def handle(self, *args, **options): for device in PurpleRobotDevice.objects.all(): user_id = device.user_hash() - + for reading in device.last_readings(omit_readings=True): probe = reading['full_probe_name'] - + if PurpleRobotTest.objects.filter(user_id=user_id, probe=probe).count() == 0: PurpleRobotTest(user_id=user_id, probe=probe, active=True, last_updated=timezone.now(), slug=slugify(device.device_id + '-' + reading['name'])).save() - - print('Added test ' + device.device_id + '/' + reading['name']) + + print 'Added test ' + device.device_id + '/' + reading['name'] diff --git a/management/commands/migrate_user.py b/management/commands/migrate_user.py new file mode 100644 index 0000000..ce4f88c --- /dev/null +++ b/management/commands/migrate_user.py @@ -0,0 +1,18 @@ +# pylint: disable=line-too-long, no-member + +from django.core.management.base import BaseCommand + +from purple_robot_app.models import PurpleRobotReading + + +class Command(BaseCommand): + def handle(self, *args, **options): + print 'Rename ' + args[0] + ' to ' + args[1] + '...' + + readings = PurpleRobotReading.objects.filter(user_id=args[0]) + + print 'Original reading count: ' + str(readings.count()) + + updated = readings.update(user_id=args[1]) + + print 'Updated reading count: ' + str(updated) diff --git a/management/commands/pr_status_check_device_payload_reading_gap.py b/management/commands/off_pr_status_check_device_payload_reading_gap.py similarity index 65% rename from management/commands/pr_status_check_device_payload_reading_gap.py rename to management/commands/off_pr_status_check_device_payload_reading_gap.py index f17638e..b396d2f 100644 --- a/management/commands/pr_status_check_device_payload_reading_gap.py +++ b/management/commands/off_pr_status_check_device_payload_reading_gap.py @@ -1,33 +1,35 @@ +# pylint: disable=line-too-long, no-member + import datetime from django.core.management.base import BaseCommand -from django.utils import timezone from purple_robot_app.models import PurpleRobotDevice, PurpleRobotPayload, PurpleRobotReading from purple_robot_app.management.commands.pr_check_status import log_alert, cancel_alert START_DAYS = 7 + class Command(BaseCommand): def handle(self, *args, **options): start = datetime.datetime.now() - datetime.timedelta(days=START_DAYS) - + for device in PurpleRobotDevice.objects.filter(mute_alerts=False): payload = PurpleRobotPayload.objects.filter(user_id=device.hash_key, added__gte=start).order_by('-added').first() reading = PurpleRobotReading.objects.filter(user_id=device.hash_key, logged__gte=start).order_by('-logged').first() - - if payload != None and reading != None: + + if payload is not None and reading is not None: diff = payload.added - reading.logged - + seconds = diff.total_seconds() - - if seconds < (10 * 60): + + if seconds < (10 * 60): cancel_alert(tags='device_payload_reading_gap', user_id=device.hash_key) - elif seconds < (30 * 60): + elif seconds < (30 * 60): log_alert(message='{0:.2f}'.format(seconds / 60) + ' minute gap between payload and reading.', severity=1, tags='device_payload_reading_gap', user_id=device.hash_key) else: log_alert(message='{0:.2f}'.format(seconds / 60) + ' minute gap between payload and reading.', severity=2, tags='device_payload_reading_gap', user_id=device.hash_key) - elif payload == None: - log_alert(message='Unable to determine reading-payload gap. No payloads uploaded in the last week.', severity=2, tags='device_payload_reading_gap', user_id=device.hash_key) - elif reading == None: - log_alert(message='Unable to determine reading-payload gap. No readings uploaded in the last week.', severity=2, tags='device_payload_reading_gap', user_id=device.hash_key) + elif payload is None: + log_alert(message='Unable to determine reading-payload gap. No payloads uploaded in the last week.', severity=2, tags='device_payload_reading_gap', user_id=device.hash_key) + elif reading is None: + log_alert(message='Unable to determine reading-payload gap. No readings uploaded in the last week.', severity=2, tags='device_payload_reading_gap', user_id=device.hash_key) diff --git a/management/commands/pr_check_status.py b/management/commands/pr_check_status.py index 0b71435..bcf4215 100644 --- a/management/commands/pr_check_status.py +++ b/management/commands/pr_check_status.py @@ -1,7 +1,9 @@ +# pylint: disable=line-too-long, no-member + import os import datetime -from django.core.management import call_command, find_management_module, find_commands, load_command_class +from django.core.management import call_command, find_management_module, find_commands from django.core.management.base import BaseCommand from django.conf import settings from django.db.models import Q @@ -9,50 +11,53 @@ from purple_robot_app.models import PurpleRobotAlert, PurpleRobotDevice -def touch(fname, mode=0o666, dir_fd=None, **kwargs): + +def touch(fname, mode=0o666): flags = os.O_CREAT | os.O_APPEND - - with os.fdopen(os.open(fname, flags, mode)) as f: + + if os.fdopen(os.open(fname, flags, mode)) is not None: os.utime(fname, None) + def fetch_query(message=None, severity=None, tags=None, user_id=None, probe=None, dismissed=False): - q = Q(dismissed=None) - + query = Q(dismissed=None) + if dismissed: - q = Q(dismissed__lte=timezone.now()) - - if message != None: - q = q & Q(message=message) - - if severity != None: - q = q & Q(severity=severity) - - if tags != None: - q = q & Q(tags__contains=tags) - - if user_id != None: - q = q & Q(user_id=user_id) - - if probe != None: - q = q & Q(probe=probe) - - return q + query = Q(dismissed__lte=timezone.now()) + + if message is not None: + query = query & Q(message=message) + + if severity is not None: + query = query & Q(severity=severity) + + if tags is not None: + query = query & Q(tags__contains=tags) + + if user_id is not None: + query = query & Q(user_id=user_id) + + if probe is not None: + query = query & Q(probe=probe) + + return query + def log_alert(message=None, severity=None, tags=None, user_id=None, probe=None): - q = fetch_query(None, None, tags, user_id, probe) - + query = fetch_query(None, None, tags, user_id, probe) + now = timezone.now() - - alerts = PurpleRobotAlert.objects.filter(q) - + + alerts = PurpleRobotAlert.objects.filter(query) + if alerts.count() > 0: for alert in alerts: - if message != None: + if message is not None: alert.message = message - - if severity != None: + + if severity is not None: alert.severity = severity - + alert.save() else: alert = PurpleRobotAlert(message=message, severity=severity, tags=tags, probe=probe, user_id=user_id, generated=now) @@ -60,47 +65,47 @@ def log_alert(message=None, severity=None, tags=None, user_id=None, probe=None): def cancel_alert(message=None, severity=None, tags=None, user_id=None, probe=None): - q = fetch_query(message, severity, tags, user_id, probe) - + query = fetch_query(message, severity, tags, user_id, probe) + now = timezone.now() - - for alert in PurpleRobotAlert.objects.filter(q): + + for alert in PurpleRobotAlert.objects.filter(query): alert.dismissed = now alert.manually_dismissed = True - + alert.save() + class Command(BaseCommand): def handle(self, *args, **options): if os.access('/tmp/check_status.lock', os.R_OK): - t = os.path.getmtime('/tmp/check_status.lock') - created = datetime.datetime.fromtimestamp(t) - - if (datetime.datetime.now() - created).total_seconds() > 60 * 60: - print('check_status: Stale lock - removing...') + timestamp = os.path.getmtime('/tmp/check_status.lock') + created = datetime.datetime.fromtimestamp(timestamp) + + if (datetime.datetime.now() - created).total_seconds() > 6 * 60 * 60: + print 'check_status: Stale lock - removing...' os.remove('/tmp/check_status.lock') else: return - + touch('/tmp/check_status.lock') for app in settings.INSTALLED_APPS: if app.startswith('django') == False: - try: + try: command_names = find_commands(find_management_module(app)) - + for command_name in command_names: if command_name.startswith('pr_status_check_'): - # print('Running: ' + command_name) call_command(command_name) - + touch('/tmp/check_status.lock') except ImportError: pass - + for device in PurpleRobotDevice.objects.filter(mute_alerts=True): for alert in PurpleRobotAlert.objects.filter(user_id=device.hash_key, dismissed=None): - alert.dismissed = timezone.now() - alert.save() + alert.dismissed = timezone.now() + alert.save() os.remove('/tmp/check_status.lock') diff --git a/management/commands/pr_status_check_consistent_hardware.py b/management/commands/pr_status_check_consistent_hardware.py index 79b5ee9..a3defcd 100644 --- a/management/commands/pr_status_check_consistent_hardware.py +++ b/management/commands/pr_status_check_consistent_hardware.py @@ -1,32 +1,36 @@ +# pylint: disable=line-too-long, no-member + import datetime import json from django.core.management.base import BaseCommand from django.utils import timezone -from purple_robot_app.models import PurpleRobotDevice, PurpleRobotReading, my_slugify +from purple_robot_app.models import PurpleRobotDevice, PurpleRobotReading from purple_robot_app.management.commands.pr_check_status import log_alert, cancel_alert HARDWARE_PROBE = 'edu.northwestern.cbits.purple_robot_manager.probes.builtin.HardwareInformationProbe' TAG = 'device_hardware_changed' + class Command(BaseCommand): def handle(self, *args, **options): for device in PurpleRobotDevice.objects.filter(mute_alerts=False): start = timezone.now() - datetime.timedelta(days=3) - + mac = None same_mac = True - + for reading in PurpleRobotReading.objects.filter(user_id=device.hash_key, probe=HARDWARE_PROBE, logged__gte=start): payload = json.loads(reading.payload) - - if mac == None: - mac = payload['WIFI_MAC'] - elif mac != payload['WIFI_MAC']: - same_mac = False - + + if 'WIFI_MAC' in payload: + if mac is None: + mac = payload['WIFI_MAC'] + elif mac is not payload['WIFI_MAC']: + same_mac = False + if same_mac: cancel_alert(tags=TAG, user_id=device.hash_key) else: diff --git a/management/commands/pr_status_check_consistent_hardware_sensors.py b/management/commands/pr_status_check_consistent_hardware_sensors.py index 6d0b23e..762bba1 100644 --- a/management/commands/pr_status_check_consistent_hardware_sensors.py +++ b/management/commands/pr_status_check_consistent_hardware_sensors.py @@ -1,3 +1,5 @@ +# pylint: disable=line-too-long, no-member + import datetime import json @@ -10,32 +12,33 @@ HARDWARE_SENSORS = ( u'edu.northwestern.cbits.purple_robot_manager.probes.builtin.AccelerometerProbe', u'edu.northwestern.cbits.purple_robot_manager.probes.builtin.GyroscopeProbe', -# u'edu.northwestern.cbits.purple_robot_manager.probes.builtin.HardwareInformationProbe', u'edu.northwestern.cbits.purple_robot_manager.probes.builtin.LightProbe', u'edu.northwestern.cbits.purple_robot_manager.probes.builtin.PressureProbe', u'edu.northwestern.cbits.purple_robot_manager.probes.builtin.ProximityProbe', -# u'edu.northwestern.cbits.purple_robot_manager.probes.builtin.SoftwareInformationProbe', u'edu.northwestern.cbits.purple_robot_manager.probes.sensors.AccelerometerSensorProbe', u'edu.northwestern.cbits.purple_robot_manager.probes.sensors.LightSensorProbe', + # u'edu.northwestern.cbits.purple_robot_manager.probes.builtin.HardwareInformationProbe', + # u'edu.northwestern.cbits.purple_robot_manager.probes.builtin.SoftwareInformationProbe', ) + class Command(BaseCommand): def handle(self, *args, **options): for device in PurpleRobotDevice.objects.filter(mute_alerts=False): for sensor in HARDWARE_SENSORS: last = device.most_recent_reading(sensor) - - if last != None: + + if last is not None: hardwares = set() - + start = timezone.now() - datetime.timedelta(days=3) - + for reading in PurpleRobotReading.objects.filter(user_id=device.hash_key, probe=sensor, logged__gte=start): payload = json.loads(reading.payload) - + if 'SENSOR' in payload: hardwares.add(payload['SENSOR']['NAME']) - + if len(hardwares) < 2: cancel_alert(tags='device_sensor_changed_' + my_slugify(sensor.replace('edu.northwestern.cbits.purple_robot_manager.', '')), user_id=device.hash_key) else: diff --git a/management/commands/pr_status_check_device_last_battery.py b/management/commands/pr_status_check_device_last_battery.py index 13c1df4..56b25c7 100644 --- a/management/commands/pr_status_check_device_last_battery.py +++ b/management/commands/pr_status_check_device_last_battery.py @@ -1,17 +1,19 @@ +# pylint: disable=line-too-long, no-member + from django.core.management.base import BaseCommand -from django.utils import timezone from purple_robot_app.models import PurpleRobotDevice from purple_robot_app.management.commands.pr_check_status import log_alert, cancel_alert + class Command(BaseCommand): def handle(self, *args, **options): for device in PurpleRobotDevice.objects.filter(mute_alerts=False): level = device.last_battery() - - if level >= 33: + + if level >= 33: cancel_alert(tags='device_last_battery', user_id=device.hash_key) - elif level >= 25: + elif level >= 25: log_alert(message='Battery level is less than 33%.', severity=1, tags='device_last_battery', user_id=device.hash_key) else: log_alert(message='Battery level is less than 25%.', severity=2, tags='device_last_battery', user_id=device.hash_key) diff --git a/management/commands/pr_status_check_device_last_upload.py b/management/commands/pr_status_check_device_last_upload.py index 0d619a7..1c0e50a 100644 --- a/management/commands/pr_status_check_device_last_upload.py +++ b/management/commands/pr_status_check_device_last_upload.py @@ -1,17 +1,20 @@ +# pylint: disable=line-too-long, no-member + from django.core.management.base import BaseCommand from django.utils import timezone from purple_robot_app.models import PurpleRobotDevice, PurpleRobotPayload from purple_robot_app.management.commands.pr_check_status import log_alert, cancel_alert + class Command(BaseCommand): def handle(self, *args, **options): for device in PurpleRobotDevice.objects.filter(mute_alerts=False): payload = PurpleRobotPayload.objects.filter(user_id=device.hash_key).order_by('-added').first() - + now = timezone.now() - - if payload == None: + + if payload is None: log_alert(message='No payloads have been uploaded yet.', severity=1, tags='device_last_upload', user_id=device.hash_key) elif (now - payload.added).total_seconds() > (60 * 60 * 12): log_alert(message='No payloads have been uploaded in the last 12 hours.', severity=2, tags='device_last_upload', user_id=device.hash_key) diff --git a/management/commands/pr_status_check_device_missing_probe.py b/management/commands/pr_status_check_device_missing_probe.py index a1d7700..a6420f2 100644 --- a/management/commands/pr_status_check_device_missing_probe.py +++ b/management/commands/pr_status_check_device_missing_probe.py @@ -1,26 +1,32 @@ +# pylint: disable=line-too-long, no-member + import datetime import os -from sexpdata import * +from sexpdata import Symbol, Quoted, car, cdr, loads from django.core.management.base import BaseCommand from django.utils import timezone -from purple_robot_app.models import PurpleRobotDevice, PurpleRobotConfiguration, PurpleRobotReading +from purple_robot_app.models import PurpleRobotDevice, PurpleRobotConfiguration from purple_robot_app.management.commands.pr_check_status import log_alert, cancel_alert +from purple_robot_app.device_info import can_sense + TAG = 'expected_probe_missing' START_DAYS = 7 -def touch(fname, mode=0o666, dir_fd=None, **kwargs): + +def touch(fname, mode=0o666): flags = os.O_CREAT | os.O_APPEND - - with os.fdopen(os.open(fname, flags, mode)) as f: + + if os.fdopen(os.open(fname, flags, mode)) is not None: os.utime(fname, None) + def enabled_probes(contents): probes = [] - + if isinstance(contents, Symbol): pass elif isinstance(contents, basestring): @@ -32,77 +38,94 @@ def enabled_probes(contents): elif car(contents) == Symbol('pr-update-probe'): probe_name = None probe_enabled = False - + for item in cdr(contents): if isinstance(item, Quoted): item = item.value() - + for child in item: if car(child) == 'name': probe_name = cdr(child) elif car(child) == 'enabled': probe_enabled = cdr(child) - - if probe_name != None and probe_enabled and (probe_name in probes) == False: + + if probe_name is not None and probe_enabled and (probe_name in probes) is False: probes.append(probe_name) else: for item in contents: found_probes = enabled_probes(item) - + for found in found_probes: - if (found in probes) == False: + if (found in probes) is False: probes.append(found) - + return probes + class Command(BaseCommand): def handle(self, *args, **options): if os.access('/tmp/expected_probe_missing_check.lock', os.R_OK): - t = os.path.getmtime('/tmp/expected_probe_missing_check.lock') - created = datetime.datetime.fromtimestamp(t) - + timestamp = os.path.getmtime('/tmp/expected_probe_missing_check.lock') + created = datetime.datetime.fromtimestamp(timestamp) + if (datetime.datetime.now() - created).total_seconds() > 60 * 60 * 4: - print('expected_probe_missing_check: Stale lock - removing...') + print 'expected_probe_missing_check: Stale lock - removing...' os.remove('/tmp/expected_probe_missing_check.lock') else: return - + touch('/tmp/expected_probe_missing_check.lock') start = timezone.now() - datetime.timedelta(days=START_DAYS) - + for device in PurpleRobotDevice.objects.filter(mute_alerts=False).order_by('device_id'): + model = device.last_model() + mfgr = device.last_manufacturer() + config = None - + default = PurpleRobotConfiguration.objects.filter(slug='default').first() - - if device.configuration != None: + + if device.configuration is not None: config = device.configuration - elif device.device_group != None and device.device_group.configuration != None: + elif device.device_group is not None and device.device_group.configuration is not None: config = device.device_group.configuration - elif config == None: + elif config is None: config = default - - if config == None: + + if config is None: log_alert(message='No configuration associated with ' + device.device_id + '.', severity=2, tags=TAG, user_id=device.hash_key) else: config_probes = enabled_probes(loads(config.contents, true='#t', false='#f')) - + + touch('/tmp/expected_probe_missing_check.lock') + missing_probes = [] - + for probe in config_probes: - found = device.most_recent_reading(probe) - - if found == None or found.logged < start: - missing_probes.append(probe.split('.')[-1]) - -# if found == None: -# print(device.device_id + ' ' + str(config) + ': ' + probe) - - + if can_sense(mfgr, model, probe): + found = device.most_recent_reading(probe) + + if found is None or found.logged < start: + missing_probes.append(probe.split('.')[-1]) + + platform = device.last_platform() + + if platform is not None and platform.startswith('Android 5'): + if 'ApplicationLaunchProbe' in missing_probes: + missing_probes.remove('ApplicationLaunchProbe') + + if 'RunningSoftwareProbe' in missing_probes: + missing_probes.remove('RunningSoftwareProbe') + if len(missing_probes) == 0: cancel_alert(tags=TAG, user_id=device.hash_key) else: - log_alert(message='Missing data from ' + str(len(missing_probes)) + ' probe(s). Absent probes: ' + ', '.join(missing_probes), severity=2, tags=TAG, user_id=device.hash_key) + missing_probes_str = ', '.join(missing_probes[:4]) + + if len(missing_probes) > 4: + missing_probes_str = missing_probes_str + ', and ' + str(len(missing_probes) - 4) + ' more' + + log_alert(message='Missing data from ' + str(len(missing_probes)) + ' probe(s). Absent probes: ' + missing_probes_str, severity=2, tags=TAG, user_id=device.hash_key) os.remove('/tmp/expected_probe_missing_check.lock') diff --git a/management/commands/pr_status_check_device_pending_files.py b/management/commands/pr_status_check_device_pending_files.py index d8a9dc9..b951187 100644 --- a/management/commands/pr_status_check_device_pending_files.py +++ b/management/commands/pr_status_check_device_pending_files.py @@ -1,14 +1,16 @@ +# pylint: disable=line-too-long, no-member + from django.core.management.base import BaseCommand -from django.utils import timezone from purple_robot_app.models import PurpleRobotDevice from purple_robot_app.management.commands.pr_check_status import log_alert, cancel_alert + class Command(BaseCommand): def handle(self, *args, **options): for device in PurpleRobotDevice.objects.filter(mute_alerts=False): pending = device.last_pending_count() - + if pending < 250: cancel_alert(tags='device_pending_files', user_id=device.hash_key) elif pending < 1000: diff --git a/management/commands/pr_status_check_device_projected_battery_life.py b/management/commands/pr_status_check_device_projected_battery_life.py index 8e031a7..74a1bbf 100644 --- a/management/commands/pr_status_check_device_projected_battery_life.py +++ b/management/commands/pr_status_check_device_projected_battery_life.py @@ -1,19 +1,26 @@ +# pylint: disable=line-too-long, no-member + from django.core.management.base import BaseCommand -from django.utils import timezone from purple_robot_app.models import PurpleRobotDevice -from purple_robot_app.management.commands.pr_check_status import log_alert, cancel_alert +from purple_robot_app.management.commands.pr_check_status import cancel_alert + class Command(BaseCommand): def handle(self, *args, **options): for device in PurpleRobotDevice.objects.filter(mute_alerts=False): - lifetime = device.projected_battery_lifetime() / (60 * 60) - - if lifetime == -1: - log_alert(message='Unable to project device lifetime.', severity=1, tags='device_projected_lifetime', user_id=device.hash_key) - elif lifetime > 8: - cancel_alert(tags='device_projected_lifetime', user_id=device.hash_key) - elif lifetime > 6: - log_alert(message='Device lifetime is estimated to be {0:.2f} hours.'.format(lifetime), severity=1, tags='device_projected_lifetime', user_id=device.hash_key) - else: - log_alert(message='Device lifetime is estimated to be {0:.2f} hours.'.format(lifetime), severity=2, tags='device_projected_lifetime', user_id=device.hash_key) + cancel_alert(tags='device_projected_lifetime', user_id=device.hash_key) + +# lifetime = device.projected_battery_lifetime() / (60 * 60) +# +# if lifetime == -1: +# log_alert(message='Unable to project device lifetime.', severity=1, +# tags='device_projected_lifetime', user_id=device.hash_key) +# elif lifetime > 8: +# cancel_alert(tags='device_projected_lifetime', user_id=device.hash_key) +# elif lifetime > 6: +# log_alert(message='Device lifetime is estimated to be {0:.2f} hours.'.format(lifetime), +# severity=1, tags='device_projected_lifetime', user_id=device.hash_key) +# else: +# log_alert(message='Device lifetime is estimated to be {0:.2f} hours.'.format(lifetime), +# severity=2, tags='device_projected_lifetime', user_id=device.hash_key) diff --git a/management/commands/pr_status_check_latest_version.py b/management/commands/pr_status_check_latest_version.py index 1deaf55..3473e0b 100644 --- a/management/commands/pr_status_check_latest_version.py +++ b/management/commands/pr_status_check_latest_version.py @@ -1,57 +1,54 @@ -import datetime -import json +# pylint: disable=line-too-long, no-member + import requests from BeautifulSoup import BeautifulSoup from django.core.management.base import BaseCommand -from django.utils import timezone -from purple_robot_app.models import PurpleRobotDevice, PurpleRobotReading, my_slugify +from purple_robot_app.models import PurpleRobotDevice from purple_robot_app.management.commands.pr_check_status import log_alert, cancel_alert TAG = 'running_latest_version' + class Command(BaseCommand): def handle(self, *args, **options): play_url = 'https://play.google.com/store/apps/details?id=edu.northwestern.cbits.purple_robot_manager' - - r = requests.get(play_url) - - soup = BeautifulSoup(r.text) - - if soup != None: + + request = requests.get(play_url) + + soup = BeautifulSoup(request.text) + + if soup is not None: changelog = '' - - changes = soup.findAll('div', { 'class': 'recent-change' }) - + + changes = soup.findAll('div', {'class': 'recent-change'}) + for change in changes: if len(changelog) > 0: changelog += '\n' - + changelog += change.contents[0].strip() - - version_html = soup.find('div', { 'class': 'content', 'itemprop': 'softwareVersion' }) - + + version_html = soup.find('div', {'class': 'content', 'itemprop': 'softwareVersion'}) + version = version_html.contents[0].strip() - - version_code = None - version_code_html = soup.findAll('button', { 'class': 'dropdown-child' }) - - for div in version_code_html: - if div.contents[0].strip() == 'Latest Version': - version_code = float(div['data-dropdown-value']) + + # version_code_html = soup.findAll('button', {'class': 'dropdown-child'}) + + # for div in version_code_html: + # if div.contents[0].strip() == 'Latest Version': + # version_code = float(div['data-dropdown-value']) for device in PurpleRobotDevice.objects.filter(mute_alerts=False): device_version = device.config_last_user_agent - - if device_version == None: + + if device_version is None: log_alert(message='Unable to determine installed version.', severity=1, tags=TAG, user_id=device.hash_key) elif device_version.endswith(str(version)): cancel_alert(tags=TAG, user_id=device.hash_key) else: log_alert(message='Running an older version on Purple Robot: ' + device_version + '.', severity=1, tags=TAG, user_id=device.hash_key) else: - print('Unable to fetch Play Store metadata.') - - + print 'Unable to fetch Play Store metadata.' diff --git a/management/commands/reflect_payloads.py b/management/commands/reflect_payloads.py index 66e9bd2..cb6b0fe 100644 --- a/management/commands/reflect_payloads.py +++ b/management/commands/reflect_payloads.py @@ -1,16 +1,15 @@ +# pylint: disable=line-too-long, no-member + import datetime import hashlib import json import requests import os -import sys -import time from requests.exceptions import ConnectionError, ReadTimeout from django.conf import settings from django.core.management.base import BaseCommand -from django.db import transaction from purple_robot_app.models import PurpleRobotPayload, PurpleRobotReading @@ -18,110 +17,115 @@ # Via http://stackoverflow.com/questions/1158076/implement-touch-using-python -def touch(fname, mode=0o666, dir_fd=None, **kwargs): + +def touch(fname, mode=0o666): flags = os.O_CREAT | os.O_APPEND - - with os.fdopen(os.open(fname, flags, mode)) as f: + + if os.fdopen(os.open(fname, flags, mode)) is not None: os.utime(fname, None) + class Command(BaseCommand): def handle(self, *args, **options): try: settings.PR_REDIRECT_ENDPOINT_MAP except AttributeError: print 'PR_REDIRECT_ENDPOINT_MAP not defined in settings.py. Exiting...' - + return if os.access('/tmp/reflected_payload.lock', os.R_OK): - t = os.path.getmtime('/tmp/reflected_payload.lock') - created = datetime.datetime.fromtimestamp(t) - - if (datetime.datetime.now() - created).total_seconds() > 120: - print('reflect_payloads: Stale lock - removing...') + timestamp = os.path.getmtime('/tmp/reflected_payload.lock') + created = datetime.datetime.fromtimestamp(timestamp) + + if (datetime.datetime.now() - created).total_seconds() > 60 * 60: + print 'reflect_payloads: Stale lock - removing...' os.remove('/tmp/reflected_payload.lock') else: return - + touch('/tmp/reflected_payload.lock') - + tag = 'reflected_payload' requests.packages.urllib3.disable_warnings() - - reflected_payloads = [] - + for config in settings.PR_REDIRECT_ENDPOINT_MAP: - payloads = PurpleRobotPayload.objects.filter(user_id=config['hash']).exclude(process_tags__contains=tag)[:50] - - if payloads.count() > 0: + payloads = list(PurpleRobotPayload.objects.filter(user_id=config['hash']).exclude(process_tags__contains=tag)[:50]) + + while len(payloads) > 0: + reflected_payloads = [] + if PRINT_PROGRESS: - print('') - + print 'COUNT: ' + str(len(payloads)) + + index = 0 + for pr_payload in payloads: + index += 1 + if PRINT_PROGRESS: + print ' PAYLOAD: ' + str(index) + ' -- ' + str(pr_payload.pk) + payload = {} payload['Payload'] = pr_payload.payload payload['Operation'] = 'SubmitProbes' - m = hashlib.md5() - m.update(config['new_id'].encode('utf-8')) - payload['UserHash'] = m.hexdigest() - - m = hashlib.md5() - m.update((payload['UserHash'] + payload['Operation'] + payload['Payload']).encode('utf-8')) - payload['Checksum'] = m.hexdigest() + md5_hash = hashlib.md5() + md5_hash.update(config['new_id'].encode('utf-8')) + payload['UserHash'] = md5_hash.hexdigest() + + md5_hash = hashlib.md5() + md5_hash.update((payload['UserHash'] + payload['Operation'] + payload['Payload']).encode('utf-8')) + payload['Checksum'] = md5_hash.hexdigest() + + data = {'json': json.dumps(payload, indent=2)} - data = { 'json': json.dumps(payload, indent=2) } - files = {} - + if 'media_url' in pr_payload.payload: readings = json.loads(pr_payload.payload) - + for reading in readings: if 'media_url' in reading: pr_reading = PurpleRobotReading.objects.filter(guid=reading['GUID']).first() - - if pr_reading != None and pr_reading.attachment != None: + + if pr_reading is not None and pr_reading.attachment is not None: reading_json = json.loads(pr_reading.payload) - + filename = pr_reading.attachment.name.split('/')[-1] files[pr_reading.guid] = (filename, pr_reading.attachment, reading_json['media_content_type'],) - try: response = requests.post(config['endpoint'], data=data, verify=False, timeout=120.0, files=files) - + response_obj = json.loads(response.text) - + if response_obj['Status'] == 'success': reflected_payloads.append(pr_payload.pk) - - touch('/tmp/reflected_payload.lock') except ValueError: # Missing attachment error - continue on... - reflected_payloads.append(pr_payload.pk) - - touch('/tmp/reflected_payload.lock') except ConnectionError: pass except ReadTimeout: pass - for pk in reflected_payloads: - pr_payload = PurpleRobotPayload.objects.get(pk=pk) - - tags = pr_payload.process_tags - - if tags is None or tags.find(tag) == -1: - if tags is None or len(tags) == 0: - tags = tag - else: - tags += ' ' + tag - - pr_payload.process_tags = tags - - pr_payload.save() - + touch('/tmp/reflected_payload.lock') + + for primary_key in reflected_payloads: + pr_payload = PurpleRobotPayload.objects.get(pk=primary_key) + + tags = pr_payload.process_tags + + if tags is None or tags.find(tag) == -1: + if tags is None or len(tags) == 0: + tags = tag + else: + tags += ' ' + tag + + pr_payload.process_tags = tags + + pr_payload.save() + + payloads = list(PurpleRobotPayload.objects.filter(user_id=config['hash']).exclude(process_tags__contains=tag)[:50]) + os.remove('/tmp/reflected_payload.lock') - diff --git a/management/commands/refresh_device_pages.py b/management/commands/refresh_device_pages.py index 8ea6161..3d26eb3 100644 --- a/management/commands/refresh_device_pages.py +++ b/management/commands/refresh_device_pages.py @@ -1,7 +1,6 @@ -import datetime -import json +# pylint: disable=line-too-long, no-member, invalid-name + import os -import pytz from collections import namedtuple @@ -11,20 +10,21 @@ from purple_robot_app.models import PurpleRobotDevice from purple_robot_app.views import pr_device + class Command(BaseCommand): def handle(self, *args, **options): if os.access('/tmp/refresh_device_pages.lock', os.R_OK): return - - open('/tmp/refresh_device_pages.lock', 'wa').close() - + + open('/tmp/refresh_device_pages.lock', 'w').close() + FakeRequest = namedtuple('FakeRequest', ['is_active', 'is_staff']) - + factory = RequestFactory() - + request = factory.get('/pr/devices/foobar') request.user = FakeRequest(is_active=True, is_staff=True) - + for device in PurpleRobotDevice.objects.all(): pr_device(request, device.device_id) diff --git a/management/commands/run_export_jobs.py b/management/commands/run_export_jobs.py index 041b188..e441789 100644 --- a/management/commands/run_export_jobs.py +++ b/management/commands/run_export_jobs.py @@ -18,6 +18,7 @@ from ...models import PurpleRobotExportJob, PurpleRobotReading + class Command(BaseCommand): def handle(self, *args, **options): processing = PurpleRobotExportJob.objects.filter(state='processing') diff --git a/management/commands/send_alert_digest.py b/management/commands/send_alert_digest.py index 2eed086..84e47d0 100644 --- a/management/commands/send_alert_digest.py +++ b/management/commands/send_alert_digest.py @@ -1,3 +1,5 @@ +# pylint: disable=line-too-long, no-member + from django.conf import settings from django.contrib.auth.models import Group from django.core.mail import send_mail @@ -7,43 +9,45 @@ from purple_robot_app.models import PurpleRobotAlert, PurpleRobotDevice + class Command(BaseCommand): def handle(self, *args, **options): group = Group.objects.filter(name=settings.PURPLE_ROBOT_ADMIN_GROUP).first() - - if group != None: + + if group is not None: users = group.user_set.all() - + recipient_list = [] - + for user in users: recipient_list.append(user.email) - + alerts = PurpleRobotAlert.objects.filter(dismissed=None).order_by('user_id', '-severity') - - c = Context() - c['alerts'] = alerts + + context = Context() + context['alerts'] = alerts if alerts.count() > 0 and len(recipient_list) > 0: from_addr = settings.PURPLE_ROBOT_EMAIL_SENDER - + devices = {} - + for alert in alerts: - device = PurpleRobotDevice.objects.get(hash_key=alert.user_id) - - device_alerts = [] - - if device.device_id in devices: - device_alerts = devices[device.device_id] - else: - devices[device.device_id] = device_alerts - - device_alerts.append([alert, device]) - - c['devices'] = devices - - message = render_to_string('email_admin_alert_content.txt', c) - subject = render_to_string('email_admin_alert_subject.txt', c) + device = PurpleRobotDevice.objects.filter(hash_key=alert.user_id).first() + + if device is not None: + device_alerts = [] + + if device.device_id in devices: + device_alerts = devices[device.device_id] + else: + devices[device.device_id] = device_alerts + + device_alerts.append([alert, device]) + + context['devices'] = devices + + message = render_to_string('email_admin_alert_content.txt', context) + subject = render_to_string('email_admin_alert_subject.txt', context) send_mail(subject, message, from_addr, recipient_list, fail_silently=False) diff --git a/management/commands/size_readings.py b/management/commands/size_readings.py index c940e0b..02d1a5d 100644 --- a/management/commands/size_readings.py +++ b/management/commands/size_readings.py @@ -1,9 +1,8 @@ +# pylint: disable=line-too-long, no-member + import datetime -import hashlib import json -import requests import os -import sys import time from django.core.management.base import BaseCommand @@ -12,53 +11,71 @@ from purple_robot_app.models import PurpleRobotReading, PurpleRobotDevice -def touch(fname, mode=0o666, dir_fd=None, **kwargs): + +def touch(fname, mode=0o666): flags = os.O_CREAT | os.O_APPEND - - with os.fdopen(os.open(fname, flags, mode)) as f: + + if os.fdopen(os.open(fname, flags, mode)) is not None: os.utime(fname, None) + class Command(BaseCommand): def handle(self, *args, **options): if os.access('/tmp/size_readings.lock', os.R_OK): - t = os.path.getmtime('/tmp/size_readings.lock') - created = datetime.datetime.fromtimestamp(t) - + timestamp = os.path.getmtime('/tmp/size_readings.lock') + created = datetime.datetime.fromtimestamp(timestamp) + if (datetime.datetime.now() - created).total_seconds() > 4 * 60 * 60: - print('size_readings: Stale lock - removing...') + print 'size_readings: Stale lock - removing...' os.remove('/tmp/size_readings.lock') else: return touch('/tmp/size_readings.lock') - - readings = PurpleRobotReading.objects.filter(size=0).order_by('-logged')[:2000] - - for reading in readings: - reading.size = len(reading.payload) - - if reading.attachment != None: - try: - reading.size += reading.attachment.size - except ValueError: - pass # No attachment... - - reading.save() - + + readings = list(PurpleRobotReading.objects.filter(size=0)[:1000]) + count = PurpleRobotReading.objects.filter(size=0).count() + + while count > 0: + for reading in readings: + reading.size = len(reading.payload) + + if reading.attachment is not None: + try: + reading.size += reading.attachment.size + except ValueError: + pass # No attachment... + + reading.save() + touch('/tmp/size_readings.lock') - - for device in PurpleRobotDevice.objects.all(): - total_size = PurpleRobotReading.objects.filter(user_id=device.hash_key).aggregate(Sum('size')) - + count -= len(readings) + readings = list(PurpleRobotReading.objects.filter(size=0)[:1000]) + + for device in PurpleRobotDevice.objects.order_by('device_id'): + now = timezone.now() + metadata = json.loads(device.performance_metadata) - - metadata['total_readings_size'] = total_size['size__sum'] - + + last_readings_size = 0 + + if 'last_readings_size' in metadata: + last_readings_size = metadata['last_readings_size'] + + last_readings_size = datetime.datetime.fromtimestamp(last_readings_size, tz=now.tzinfo) + + if (now - last_readings_size).total_seconds() > 60 * 60 * 4: + if 'total_readings_size' in metadata: + del metadata['total_readings_size'] + + if ('total_readings_size' in metadata) == False: + total_size = PurpleRobotReading.objects.filter(user_id=device.hash_key).aggregate(Sum('size')) + metadata['total_readings_size'] = total_size['size__sum'] + + metadata['last_readings_size'] = time.mktime(now.timetuple()) + device.performance_metadata = json.dumps(metadata, indent=2) - device.save() - touch('/tmp/size_readings.lock') - os.remove('/tmp/size_readings.lock') diff --git a/management/commands/update_test_reports.py b/management/commands/update_test_reports.py index 1c1b15e..ca8dd22 100644 --- a/management/commands/update_test_reports.py +++ b/management/commands/update_test_reports.py @@ -1,10 +1,14 @@ +# pylint: disable=line-too-long + from django.core.management.base import BaseCommand -from purple_robot_app.models import PurpleRobotTest +# from purple_robot_app.models import PurpleRobotTest REPORT_DAYS = 1 + class Command(BaseCommand): def handle(self, *args, **options): - for test in PurpleRobotTest.objects.all().order_by('last_updated')[:1]: - test.update(days=REPORT_DAYS) + pass + # test = PurpleRobotTest.objects.all().order_by('last_updated').first() + # test.update(days=REPORT_DAYS) diff --git a/management/commands/verify_user_data_present.py b/management/commands/verify_user_data_present.py new file mode 100644 index 0000000..d1a8462 --- /dev/null +++ b/management/commands/verify_user_data_present.py @@ -0,0 +1,29 @@ +# pylint: disable=line-too-long, no-member + +import hashlib + +from django.core.management.base import BaseCommand + +from purple_robot_app.models import PurpleRobotReading + + +class Command(BaseCommand): + def handle(self, *args, **options): + md5_hash = hashlib.md5() + md5_hash.update(args[0]) + + user_id = md5_hash.hexdigest() + + count = PurpleRobotReading.objects.filter(user_id=user_id).count() + + print 'Readings for ' + args[0] + ': ' + str(count) + + if count == 0: + md5_hash = hashlib.md5() + md5_hash.update(args[0].lower()) + + user_id = md5_hash.hexdigest() + + count = PurpleRobotReading.objects.filter(user_id=user_id).count() + + print 'Readings for ' + args[0].lower() + ': ' + str(count) diff --git a/migrations/0036_auto__add_field_purplerobotdevice_test_device.py b/migrations/0036_auto__add_field_purplerobotdevice_test_device.py new file mode 100644 index 0000000..8e17f9e --- /dev/null +++ b/migrations/0036_auto__add_field_purplerobotdevice_test_device.py @@ -0,0 +1,136 @@ +# -*- coding: utf-8 -*- +from south.utils import datetime_utils as datetime +from south.db import db +from south.v2 import SchemaMigration +from django.db import models + + +class Migration(SchemaMigration): + + def forwards(self, orm): + # Adding field 'PurpleRobotDevice.test_device' + db.add_column(u'purple_robot_app_purplerobotdevice', 'test_device', + self.gf('django.db.models.fields.BooleanField')(default=False), + keep_default=False) + + + def backwards(self, orm): + # Deleting field 'PurpleRobotDevice.test_device' + db.delete_column(u'purple_robot_app_purplerobotdevice', 'test_device') + + + models = { + u'purple_robot_app.purplerobotalert': { + 'Meta': {'object_name': 'PurpleRobotAlert'}, + 'action_url': ('django.db.models.fields.URLField', [], {'max_length': '1024', 'null': 'True', 'blank': 'True'}), + 'dismissed': ('django.db.models.fields.DateTimeField', [], {'null': 'True', 'blank': 'True'}), + 'generated': ('django.db.models.fields.DateTimeField', [], {}), + u'id': ('django.db.models.fields.AutoField', [], {'primary_key': 'True'}), + 'manually_dismissed': ('django.db.models.fields.BooleanField', [], {'default': 'False'}), + 'message': ('django.db.models.fields.CharField', [], {'max_length': '2048'}), + 'probe': ('django.db.models.fields.CharField', [], {'max_length': '1024', 'null': 'True', 'blank': 'True'}), + 'severity': ('django.db.models.fields.IntegerField', [], {'default': '0'}), + 'tags': ('django.db.models.fields.CharField', [], {'max_length': '2048', 'null': 'True', 'blank': 'True'}), + 'user_id': ('django.db.models.fields.CharField', [], {'max_length': '1024', 'null': 'True', 'blank': 'True'}) + }, + u'purple_robot_app.purplerobotconfiguration': { + 'Meta': {'object_name': 'PurpleRobotConfiguration'}, + 'added': ('django.db.models.fields.DateTimeField', [], {}), + 'contents': ('django.db.models.fields.TextField', [], {'max_length': '1048576'}), + u'id': ('django.db.models.fields.AutoField', [], {'primary_key': 'True'}), + 'name': ('django.db.models.fields.CharField', [], {'max_length': '1024'}), + 'slug': ('django.db.models.fields.SlugField', [], {'unique': 'True', 'max_length': '1024'}) + }, + u'purple_robot_app.purplerobotdevice': { + 'Meta': {'object_name': 'PurpleRobotDevice'}, + 'config_last_fetched': ('django.db.models.fields.DateTimeField', [], {'null': 'True', 'blank': 'True'}), + 'config_last_user_agent': ('django.db.models.fields.CharField', [], {'max_length': '1024', 'null': 'True', 'blank': 'True'}), + 'configuration': ('django.db.models.fields.related.ForeignKey', [], {'blank': 'True', 'related_name': "'devices'", 'null': 'True', 'to': u"orm['purple_robot_app.PurpleRobotConfiguration']"}), + 'description': ('django.db.models.fields.TextField', [], {'max_length': '1048576', 'null': 'True', 'blank': 'True'}), + 'device_group': ('django.db.models.fields.related.ForeignKey', [], {'blank': 'True', 'related_name': "'devices'", 'null': 'True', 'to': u"orm['purple_robot_app.PurpleRobotDeviceGroup']"}), + 'device_id': ('django.db.models.fields.CharField', [], {'unique': 'True', 'max_length': '256', 'db_index': 'True'}), + 'hash_key': ('django.db.models.fields.CharField', [], {'max_length': '128', 'null': 'True', 'blank': 'True'}), + u'id': ('django.db.models.fields.AutoField', [], {'primary_key': 'True'}), + 'mute_alerts': ('django.db.models.fields.BooleanField', [], {'default': 'False'}), + 'name': ('django.db.models.fields.CharField', [], {'max_length': '1024'}), + 'performance_metadata': ('django.db.models.fields.TextField', [], {'default': "'{}'", 'max_length': '1048576'}), + 'test_device': ('django.db.models.fields.BooleanField', [], {'default': 'False'}) + }, + u'purple_robot_app.purplerobotdevicegroup': { + 'Meta': {'object_name': 'PurpleRobotDeviceGroup'}, + 'configuration': ('django.db.models.fields.related.ForeignKey', [], {'blank': 'True', 'related_name': "'groups'", 'null': 'True', 'to': u"orm['purple_robot_app.PurpleRobotConfiguration']"}), + 'description': ('django.db.models.fields.TextField', [], {'max_length': '1048576', 'null': 'True', 'blank': 'True'}), + 'group_id': ('django.db.models.fields.SlugField', [], {'unique': 'True', 'max_length': '256'}), + u'id': ('django.db.models.fields.AutoField', [], {'primary_key': 'True'}), + 'name': ('django.db.models.fields.CharField', [], {'max_length': '1024'}) + }, + u'purple_robot_app.purplerobotdevicenote': { + 'Meta': {'object_name': 'PurpleRobotDeviceNote'}, + 'added': ('django.db.models.fields.DateTimeField', [], {}), + 'device': ('django.db.models.fields.related.ForeignKey', [], {'related_name': "'notes'", 'to': u"orm['purple_robot_app.PurpleRobotDevice']"}), + u'id': ('django.db.models.fields.AutoField', [], {'primary_key': 'True'}), + 'note': ('django.db.models.fields.TextField', [], {'max_length': '1024'}) + }, + u'purple_robot_app.purplerobotevent': { + 'Meta': {'object_name': 'PurpleRobotEvent'}, + 'event': ('django.db.models.fields.CharField', [], {'max_length': '1024'}), + u'id': ('django.db.models.fields.AutoField', [], {'primary_key': 'True'}), + 'logged': ('django.db.models.fields.DateTimeField', [], {}), + 'name': ('django.db.models.fields.CharField', [], {'db_index': 'True', 'max_length': '1024', 'null': 'True', 'blank': 'True'}), + 'payload': ('django.db.models.fields.TextField', [], {'max_length': '8388608', 'null': 'True', 'blank': 'True'}), + 'user_id': ('django.db.models.fields.CharField', [], {'max_length': '1024', 'db_index': 'True'}) + }, + u'purple_robot_app.purplerobotexportjob': { + 'Meta': {'object_name': 'PurpleRobotExportJob'}, + 'destination': ('django.db.models.fields.EmailField', [], {'max_length': '75', 'null': 'True', 'blank': 'True'}), + 'end_date': ('django.db.models.fields.DateField', [], {}), + 'export_file': ('django.db.models.fields.files.FileField', [], {'max_length': '100', 'blank': 'True'}), + u'id': ('django.db.models.fields.AutoField', [], {'primary_key': 'True'}), + 'probes': ('django.db.models.fields.TextField', [], {'max_length': '8196', 'null': 'True', 'blank': 'True'}), + 'start_date': ('django.db.models.fields.DateField', [], {}), + 'state': ('django.db.models.fields.CharField', [], {'default': "'pending'", 'max_length': '512'}), + 'users': ('django.db.models.fields.TextField', [], {'max_length': '8196', 'null': 'True', 'blank': 'True'}) + }, + u'purple_robot_app.purplerobotpayload': { + 'Meta': {'object_name': 'PurpleRobotPayload'}, + 'added': ('django.db.models.fields.DateTimeField', [], {'auto_now_add': 'True', 'blank': 'True'}), + 'errors': ('django.db.models.fields.TextField', [], {'max_length': '65536', 'null': 'True', 'blank': 'True'}), + u'id': ('django.db.models.fields.AutoField', [], {'primary_key': 'True'}), + 'payload': ('django.db.models.fields.TextField', [], {'max_length': '8388608'}), + 'process_tags': ('django.db.models.fields.CharField', [], {'db_index': 'True', 'max_length': '1024', 'null': 'True', 'blank': 'True'}), + 'user_id': ('django.db.models.fields.CharField', [], {'max_length': '1024', 'db_index': 'True'}) + }, + u'purple_robot_app.purplerobotreading': { + 'Meta': {'object_name': 'PurpleRobotReading', 'index_together': "[['probe', 'user_id'], ['logged', 'user_id'], ['probe', 'logged', 'user_id']]"}, + 'attachment': ('django.db.models.fields.files.FileField', [], {'max_length': '100', 'null': 'True', 'blank': 'True'}), + 'guid': ('django.db.models.fields.CharField', [], {'db_index': 'True', 'max_length': '1024', 'null': 'True', 'blank': 'True'}), + u'id': ('django.db.models.fields.AutoField', [], {'primary_key': 'True'}), + 'logged': ('django.db.models.fields.DateTimeField', [], {'db_index': 'True'}), + 'payload': ('django.db.models.fields.TextField', [], {'max_length': '8388608'}), + 'probe': ('django.db.models.fields.CharField', [], {'db_index': 'True', 'max_length': '1024', 'null': 'True', 'blank': 'True'}), + 'size': ('django.db.models.fields.IntegerField', [], {'default': '0', 'db_index': 'True'}), + 'user_id': ('django.db.models.fields.CharField', [], {'max_length': '1024', 'db_index': 'True'}) + }, + u'purple_robot_app.purplerobotreport': { + 'Meta': {'object_name': 'PurpleRobotReport'}, + 'generated': ('django.db.models.fields.DateTimeField', [], {}), + u'id': ('django.db.models.fields.AutoField', [], {'primary_key': 'True'}), + 'mime_type': ('django.db.models.fields.CharField', [], {'max_length': '1024'}), + 'probe': ('django.db.models.fields.CharField', [], {'max_length': '1024', 'null': 'True', 'blank': 'True'}), + 'report_file': ('django.db.models.fields.files.FileField', [], {'max_length': '100', 'blank': 'True'}), + 'user_id': ('django.db.models.fields.CharField', [], {'max_length': '1024'}) + }, + u'purple_robot_app.purplerobottest': { + 'Meta': {'object_name': 'PurpleRobotTest'}, + 'active': ('django.db.models.fields.BooleanField', [], {'default': 'False'}), + 'frequency': ('django.db.models.fields.FloatField', [], {'default': '1.0'}), + u'id': ('django.db.models.fields.AutoField', [], {'primary_key': 'True'}), + 'last_updated': ('django.db.models.fields.DateTimeField', [], {}), + 'probe': ('django.db.models.fields.CharField', [], {'max_length': '1024', 'null': 'True', 'blank': 'True'}), + 'report': ('django.db.models.fields.TextField', [], {'default': "'{}'"}), + 'slug': ('django.db.models.fields.SlugField', [], {'unique': 'True', 'max_length': '50'}), + 'user_id': ('django.db.models.fields.CharField', [], {'max_length': '1024'}) + } + } + + complete_apps = ['purple_robot_app'] \ No newline at end of file diff --git a/migrations/0037_auto__add_field_purplerobotdevice_first_reading_timestamp.py b/migrations/0037_auto__add_field_purplerobotdevice_first_reading_timestamp.py new file mode 100644 index 0000000..6b597c5 --- /dev/null +++ b/migrations/0037_auto__add_field_purplerobotdevice_first_reading_timestamp.py @@ -0,0 +1,137 @@ +# -*- coding: utf-8 -*- +from south.utils import datetime_utils as datetime +from south.db import db +from south.v2 import SchemaMigration +from django.db import models + + +class Migration(SchemaMigration): + + def forwards(self, orm): + # Adding field 'PurpleRobotDevice.first_reading_timestamp' + db.add_column(u'purple_robot_app_purplerobotdevice', 'first_reading_timestamp', + self.gf('django.db.models.fields.BigIntegerField')(default=0), + keep_default=False) + + + def backwards(self, orm): + # Deleting field 'PurpleRobotDevice.first_reading_timestamp' + db.delete_column(u'purple_robot_app_purplerobotdevice', 'first_reading_timestamp') + + + models = { + u'purple_robot_app.purplerobotalert': { + 'Meta': {'object_name': 'PurpleRobotAlert'}, + 'action_url': ('django.db.models.fields.URLField', [], {'max_length': '1024', 'null': 'True', 'blank': 'True'}), + 'dismissed': ('django.db.models.fields.DateTimeField', [], {'null': 'True', 'blank': 'True'}), + 'generated': ('django.db.models.fields.DateTimeField', [], {}), + u'id': ('django.db.models.fields.AutoField', [], {'primary_key': 'True'}), + 'manually_dismissed': ('django.db.models.fields.BooleanField', [], {'default': 'False'}), + 'message': ('django.db.models.fields.CharField', [], {'max_length': '2048'}), + 'probe': ('django.db.models.fields.CharField', [], {'max_length': '1024', 'null': 'True', 'blank': 'True'}), + 'severity': ('django.db.models.fields.IntegerField', [], {'default': '0'}), + 'tags': ('django.db.models.fields.CharField', [], {'max_length': '2048', 'null': 'True', 'blank': 'True'}), + 'user_id': ('django.db.models.fields.CharField', [], {'max_length': '1024', 'null': 'True', 'blank': 'True'}) + }, + u'purple_robot_app.purplerobotconfiguration': { + 'Meta': {'object_name': 'PurpleRobotConfiguration'}, + 'added': ('django.db.models.fields.DateTimeField', [], {}), + 'contents': ('django.db.models.fields.TextField', [], {'max_length': '1048576'}), + u'id': ('django.db.models.fields.AutoField', [], {'primary_key': 'True'}), + 'name': ('django.db.models.fields.CharField', [], {'max_length': '1024'}), + 'slug': ('django.db.models.fields.SlugField', [], {'unique': 'True', 'max_length': '1024'}) + }, + u'purple_robot_app.purplerobotdevice': { + 'Meta': {'object_name': 'PurpleRobotDevice'}, + 'config_last_fetched': ('django.db.models.fields.DateTimeField', [], {'null': 'True', 'blank': 'True'}), + 'config_last_user_agent': ('django.db.models.fields.CharField', [], {'max_length': '1024', 'null': 'True', 'blank': 'True'}), + 'configuration': ('django.db.models.fields.related.ForeignKey', [], {'blank': 'True', 'related_name': "'devices'", 'null': 'True', 'to': u"orm['purple_robot_app.PurpleRobotConfiguration']"}), + 'description': ('django.db.models.fields.TextField', [], {'max_length': '1048576', 'null': 'True', 'blank': 'True'}), + 'device_group': ('django.db.models.fields.related.ForeignKey', [], {'blank': 'True', 'related_name': "'devices'", 'null': 'True', 'to': u"orm['purple_robot_app.PurpleRobotDeviceGroup']"}), + 'device_id': ('django.db.models.fields.CharField', [], {'unique': 'True', 'max_length': '256', 'db_index': 'True'}), + 'first_reading_timestamp': ('django.db.models.fields.BigIntegerField', [], {'default': '0'}), + 'hash_key': ('django.db.models.fields.CharField', [], {'max_length': '128', 'null': 'True', 'blank': 'True'}), + u'id': ('django.db.models.fields.AutoField', [], {'primary_key': 'True'}), + 'mute_alerts': ('django.db.models.fields.BooleanField', [], {'default': 'False'}), + 'name': ('django.db.models.fields.CharField', [], {'max_length': '1024'}), + 'performance_metadata': ('django.db.models.fields.TextField', [], {'default': "'{}'", 'max_length': '1048576'}), + 'test_device': ('django.db.models.fields.BooleanField', [], {'default': 'False'}) + }, + u'purple_robot_app.purplerobotdevicegroup': { + 'Meta': {'object_name': 'PurpleRobotDeviceGroup'}, + 'configuration': ('django.db.models.fields.related.ForeignKey', [], {'blank': 'True', 'related_name': "'groups'", 'null': 'True', 'to': u"orm['purple_robot_app.PurpleRobotConfiguration']"}), + 'description': ('django.db.models.fields.TextField', [], {'max_length': '1048576', 'null': 'True', 'blank': 'True'}), + 'group_id': ('django.db.models.fields.SlugField', [], {'unique': 'True', 'max_length': '256'}), + u'id': ('django.db.models.fields.AutoField', [], {'primary_key': 'True'}), + 'name': ('django.db.models.fields.CharField', [], {'max_length': '1024'}) + }, + u'purple_robot_app.purplerobotdevicenote': { + 'Meta': {'object_name': 'PurpleRobotDeviceNote'}, + 'added': ('django.db.models.fields.DateTimeField', [], {}), + 'device': ('django.db.models.fields.related.ForeignKey', [], {'related_name': "'notes'", 'to': u"orm['purple_robot_app.PurpleRobotDevice']"}), + u'id': ('django.db.models.fields.AutoField', [], {'primary_key': 'True'}), + 'note': ('django.db.models.fields.TextField', [], {'max_length': '1024'}) + }, + u'purple_robot_app.purplerobotevent': { + 'Meta': {'object_name': 'PurpleRobotEvent'}, + 'event': ('django.db.models.fields.CharField', [], {'max_length': '1024'}), + u'id': ('django.db.models.fields.AutoField', [], {'primary_key': 'True'}), + 'logged': ('django.db.models.fields.DateTimeField', [], {}), + 'name': ('django.db.models.fields.CharField', [], {'db_index': 'True', 'max_length': '1024', 'null': 'True', 'blank': 'True'}), + 'payload': ('django.db.models.fields.TextField', [], {'max_length': '8388608', 'null': 'True', 'blank': 'True'}), + 'user_id': ('django.db.models.fields.CharField', [], {'max_length': '1024', 'db_index': 'True'}) + }, + u'purple_robot_app.purplerobotexportjob': { + 'Meta': {'object_name': 'PurpleRobotExportJob'}, + 'destination': ('django.db.models.fields.EmailField', [], {'max_length': '75', 'null': 'True', 'blank': 'True'}), + 'end_date': ('django.db.models.fields.DateField', [], {}), + 'export_file': ('django.db.models.fields.files.FileField', [], {'max_length': '100', 'blank': 'True'}), + u'id': ('django.db.models.fields.AutoField', [], {'primary_key': 'True'}), + 'probes': ('django.db.models.fields.TextField', [], {'max_length': '8196', 'null': 'True', 'blank': 'True'}), + 'start_date': ('django.db.models.fields.DateField', [], {}), + 'state': ('django.db.models.fields.CharField', [], {'default': "'pending'", 'max_length': '512'}), + 'users': ('django.db.models.fields.TextField', [], {'max_length': '8196', 'null': 'True', 'blank': 'True'}) + }, + u'purple_robot_app.purplerobotpayload': { + 'Meta': {'object_name': 'PurpleRobotPayload'}, + 'added': ('django.db.models.fields.DateTimeField', [], {'auto_now_add': 'True', 'blank': 'True'}), + 'errors': ('django.db.models.fields.TextField', [], {'max_length': '65536', 'null': 'True', 'blank': 'True'}), + u'id': ('django.db.models.fields.AutoField', [], {'primary_key': 'True'}), + 'payload': ('django.db.models.fields.TextField', [], {'max_length': '8388608'}), + 'process_tags': ('django.db.models.fields.CharField', [], {'db_index': 'True', 'max_length': '1024', 'null': 'True', 'blank': 'True'}), + 'user_id': ('django.db.models.fields.CharField', [], {'max_length': '1024', 'db_index': 'True'}) + }, + u'purple_robot_app.purplerobotreading': { + 'Meta': {'object_name': 'PurpleRobotReading', 'index_together': "[['probe', 'user_id'], ['logged', 'user_id'], ['probe', 'logged', 'user_id']]"}, + 'attachment': ('django.db.models.fields.files.FileField', [], {'max_length': '100', 'null': 'True', 'blank': 'True'}), + 'guid': ('django.db.models.fields.CharField', [], {'db_index': 'True', 'max_length': '1024', 'null': 'True', 'blank': 'True'}), + u'id': ('django.db.models.fields.AutoField', [], {'primary_key': 'True'}), + 'logged': ('django.db.models.fields.DateTimeField', [], {'db_index': 'True'}), + 'payload': ('django.db.models.fields.TextField', [], {'max_length': '8388608'}), + 'probe': ('django.db.models.fields.CharField', [], {'db_index': 'True', 'max_length': '1024', 'null': 'True', 'blank': 'True'}), + 'size': ('django.db.models.fields.IntegerField', [], {'default': '0', 'db_index': 'True'}), + 'user_id': ('django.db.models.fields.CharField', [], {'max_length': '1024', 'db_index': 'True'}) + }, + u'purple_robot_app.purplerobotreport': { + 'Meta': {'object_name': 'PurpleRobotReport'}, + 'generated': ('django.db.models.fields.DateTimeField', [], {}), + u'id': ('django.db.models.fields.AutoField', [], {'primary_key': 'True'}), + 'mime_type': ('django.db.models.fields.CharField', [], {'max_length': '1024'}), + 'probe': ('django.db.models.fields.CharField', [], {'max_length': '1024', 'null': 'True', 'blank': 'True'}), + 'report_file': ('django.db.models.fields.files.FileField', [], {'max_length': '100', 'blank': 'True'}), + 'user_id': ('django.db.models.fields.CharField', [], {'max_length': '1024'}) + }, + u'purple_robot_app.purplerobottest': { + 'Meta': {'object_name': 'PurpleRobotTest'}, + 'active': ('django.db.models.fields.BooleanField', [], {'default': 'False'}), + 'frequency': ('django.db.models.fields.FloatField', [], {'default': '1.0'}), + u'id': ('django.db.models.fields.AutoField', [], {'primary_key': 'True'}), + 'last_updated': ('django.db.models.fields.DateTimeField', [], {}), + 'probe': ('django.db.models.fields.CharField', [], {'max_length': '1024', 'null': 'True', 'blank': 'True'}), + 'report': ('django.db.models.fields.TextField', [], {'default': "'{}'"}), + 'slug': ('django.db.models.fields.SlugField', [], {'unique': 'True', 'max_length': '50'}), + 'user_id': ('django.db.models.fields.CharField', [], {'max_length': '1024'}) + } + } + + complete_apps = ['purple_robot_app'] \ No newline at end of file diff --git a/migrations/0038_auto__add_index_purplerobotevent_logged.py b/migrations/0038_auto__add_index_purplerobotevent_logged.py new file mode 100644 index 0000000..c9b7153 --- /dev/null +++ b/migrations/0038_auto__add_index_purplerobotevent_logged.py @@ -0,0 +1,135 @@ +# -*- coding: utf-8 -*- +from south.utils import datetime_utils as datetime +from south.db import db +from south.v2 import SchemaMigration +from django.db import models + + +class Migration(SchemaMigration): + + def forwards(self, orm): + # Adding index on 'PurpleRobotEvent', fields ['logged'] + db.create_index(u'purple_robot_app_purplerobotevent', ['logged']) + + + def backwards(self, orm): + # Removing index on 'PurpleRobotEvent', fields ['logged'] + db.delete_index(u'purple_robot_app_purplerobotevent', ['logged']) + + + models = { + u'purple_robot_app.purplerobotalert': { + 'Meta': {'object_name': 'PurpleRobotAlert'}, + 'action_url': ('django.db.models.fields.URLField', [], {'max_length': '1024', 'null': 'True', 'blank': 'True'}), + 'dismissed': ('django.db.models.fields.DateTimeField', [], {'null': 'True', 'blank': 'True'}), + 'generated': ('django.db.models.fields.DateTimeField', [], {}), + u'id': ('django.db.models.fields.AutoField', [], {'primary_key': 'True'}), + 'manually_dismissed': ('django.db.models.fields.BooleanField', [], {'default': 'False'}), + 'message': ('django.db.models.fields.CharField', [], {'max_length': '2048'}), + 'probe': ('django.db.models.fields.CharField', [], {'max_length': '1024', 'null': 'True', 'blank': 'True'}), + 'severity': ('django.db.models.fields.IntegerField', [], {'default': '0'}), + 'tags': ('django.db.models.fields.CharField', [], {'max_length': '2048', 'null': 'True', 'blank': 'True'}), + 'user_id': ('django.db.models.fields.CharField', [], {'max_length': '1024', 'null': 'True', 'blank': 'True'}) + }, + u'purple_robot_app.purplerobotconfiguration': { + 'Meta': {'object_name': 'PurpleRobotConfiguration'}, + 'added': ('django.db.models.fields.DateTimeField', [], {}), + 'contents': ('django.db.models.fields.TextField', [], {'max_length': '1048576'}), + u'id': ('django.db.models.fields.AutoField', [], {'primary_key': 'True'}), + 'name': ('django.db.models.fields.CharField', [], {'max_length': '1024'}), + 'slug': ('django.db.models.fields.SlugField', [], {'unique': 'True', 'max_length': '1024'}) + }, + u'purple_robot_app.purplerobotdevice': { + 'Meta': {'object_name': 'PurpleRobotDevice'}, + 'config_last_fetched': ('django.db.models.fields.DateTimeField', [], {'null': 'True', 'blank': 'True'}), + 'config_last_user_agent': ('django.db.models.fields.CharField', [], {'max_length': '1024', 'null': 'True', 'blank': 'True'}), + 'configuration': ('django.db.models.fields.related.ForeignKey', [], {'blank': 'True', 'related_name': "'devices'", 'null': 'True', 'to': u"orm['purple_robot_app.PurpleRobotConfiguration']"}), + 'description': ('django.db.models.fields.TextField', [], {'max_length': '1048576', 'null': 'True', 'blank': 'True'}), + 'device_group': ('django.db.models.fields.related.ForeignKey', [], {'blank': 'True', 'related_name': "'devices'", 'null': 'True', 'to': u"orm['purple_robot_app.PurpleRobotDeviceGroup']"}), + 'device_id': ('django.db.models.fields.CharField', [], {'unique': 'True', 'max_length': '256', 'db_index': 'True'}), + 'first_reading_timestamp': ('django.db.models.fields.BigIntegerField', [], {'default': '0'}), + 'hash_key': ('django.db.models.fields.CharField', [], {'max_length': '128', 'null': 'True', 'blank': 'True'}), + u'id': ('django.db.models.fields.AutoField', [], {'primary_key': 'True'}), + 'mute_alerts': ('django.db.models.fields.BooleanField', [], {'default': 'False'}), + 'name': ('django.db.models.fields.CharField', [], {'max_length': '1024'}), + 'performance_metadata': ('django.db.models.fields.TextField', [], {'default': "'{}'", 'max_length': '1048576'}), + 'test_device': ('django.db.models.fields.BooleanField', [], {'default': 'False'}) + }, + u'purple_robot_app.purplerobotdevicegroup': { + 'Meta': {'object_name': 'PurpleRobotDeviceGroup'}, + 'configuration': ('django.db.models.fields.related.ForeignKey', [], {'blank': 'True', 'related_name': "'groups'", 'null': 'True', 'to': u"orm['purple_robot_app.PurpleRobotConfiguration']"}), + 'description': ('django.db.models.fields.TextField', [], {'max_length': '1048576', 'null': 'True', 'blank': 'True'}), + 'group_id': ('django.db.models.fields.SlugField', [], {'unique': 'True', 'max_length': '256'}), + u'id': ('django.db.models.fields.AutoField', [], {'primary_key': 'True'}), + 'name': ('django.db.models.fields.CharField', [], {'max_length': '1024'}) + }, + u'purple_robot_app.purplerobotdevicenote': { + 'Meta': {'object_name': 'PurpleRobotDeviceNote'}, + 'added': ('django.db.models.fields.DateTimeField', [], {}), + 'device': ('django.db.models.fields.related.ForeignKey', [], {'related_name': "'notes'", 'to': u"orm['purple_robot_app.PurpleRobotDevice']"}), + u'id': ('django.db.models.fields.AutoField', [], {'primary_key': 'True'}), + 'note': ('django.db.models.fields.TextField', [], {'max_length': '1024'}) + }, + u'purple_robot_app.purplerobotevent': { + 'Meta': {'object_name': 'PurpleRobotEvent'}, + 'event': ('django.db.models.fields.CharField', [], {'max_length': '1024'}), + u'id': ('django.db.models.fields.AutoField', [], {'primary_key': 'True'}), + 'logged': ('django.db.models.fields.DateTimeField', [], {'db_index': 'True'}), + 'name': ('django.db.models.fields.CharField', [], {'db_index': 'True', 'max_length': '1024', 'null': 'True', 'blank': 'True'}), + 'payload': ('django.db.models.fields.TextField', [], {'max_length': '8388608', 'null': 'True', 'blank': 'True'}), + 'user_id': ('django.db.models.fields.CharField', [], {'max_length': '1024', 'db_index': 'True'}) + }, + u'purple_robot_app.purplerobotexportjob': { + 'Meta': {'object_name': 'PurpleRobotExportJob'}, + 'destination': ('django.db.models.fields.EmailField', [], {'max_length': '75', 'null': 'True', 'blank': 'True'}), + 'end_date': ('django.db.models.fields.DateField', [], {}), + 'export_file': ('django.db.models.fields.files.FileField', [], {'max_length': '100', 'blank': 'True'}), + u'id': ('django.db.models.fields.AutoField', [], {'primary_key': 'True'}), + 'probes': ('django.db.models.fields.TextField', [], {'max_length': '8196', 'null': 'True', 'blank': 'True'}), + 'start_date': ('django.db.models.fields.DateField', [], {}), + 'state': ('django.db.models.fields.CharField', [], {'default': "'pending'", 'max_length': '512'}), + 'users': ('django.db.models.fields.TextField', [], {'max_length': '8196', 'null': 'True', 'blank': 'True'}) + }, + u'purple_robot_app.purplerobotpayload': { + 'Meta': {'object_name': 'PurpleRobotPayload'}, + 'added': ('django.db.models.fields.DateTimeField', [], {'auto_now_add': 'True', 'blank': 'True'}), + 'errors': ('django.db.models.fields.TextField', [], {'max_length': '65536', 'null': 'True', 'blank': 'True'}), + u'id': ('django.db.models.fields.AutoField', [], {'primary_key': 'True'}), + 'payload': ('django.db.models.fields.TextField', [], {'max_length': '8388608'}), + 'process_tags': ('django.db.models.fields.CharField', [], {'db_index': 'True', 'max_length': '1024', 'null': 'True', 'blank': 'True'}), + 'user_id': ('django.db.models.fields.CharField', [], {'max_length': '1024', 'db_index': 'True'}) + }, + u'purple_robot_app.purplerobotreading': { + 'Meta': {'object_name': 'PurpleRobotReading', 'index_together': "[['probe', 'user_id'], ['logged', 'user_id'], ['probe', 'logged', 'user_id']]"}, + 'attachment': ('django.db.models.fields.files.FileField', [], {'max_length': '100', 'null': 'True', 'blank': 'True'}), + 'guid': ('django.db.models.fields.CharField', [], {'db_index': 'True', 'max_length': '1024', 'null': 'True', 'blank': 'True'}), + u'id': ('django.db.models.fields.AutoField', [], {'primary_key': 'True'}), + 'logged': ('django.db.models.fields.DateTimeField', [], {'db_index': 'True'}), + 'payload': ('django.db.models.fields.TextField', [], {'max_length': '8388608'}), + 'probe': ('django.db.models.fields.CharField', [], {'db_index': 'True', 'max_length': '1024', 'null': 'True', 'blank': 'True'}), + 'size': ('django.db.models.fields.IntegerField', [], {'default': '0', 'db_index': 'True'}), + 'user_id': ('django.db.models.fields.CharField', [], {'max_length': '1024', 'db_index': 'True'}) + }, + u'purple_robot_app.purplerobotreport': { + 'Meta': {'object_name': 'PurpleRobotReport'}, + 'generated': ('django.db.models.fields.DateTimeField', [], {}), + u'id': ('django.db.models.fields.AutoField', [], {'primary_key': 'True'}), + 'mime_type': ('django.db.models.fields.CharField', [], {'max_length': '1024'}), + 'probe': ('django.db.models.fields.CharField', [], {'max_length': '1024', 'null': 'True', 'blank': 'True'}), + 'report_file': ('django.db.models.fields.files.FileField', [], {'max_length': '100', 'blank': 'True'}), + 'user_id': ('django.db.models.fields.CharField', [], {'max_length': '1024'}) + }, + u'purple_robot_app.purplerobottest': { + 'Meta': {'object_name': 'PurpleRobotTest'}, + 'active': ('django.db.models.fields.BooleanField', [], {'default': 'False'}), + 'frequency': ('django.db.models.fields.FloatField', [], {'default': '1.0'}), + u'id': ('django.db.models.fields.AutoField', [], {'primary_key': 'True'}), + 'last_updated': ('django.db.models.fields.DateTimeField', [], {}), + 'probe': ('django.db.models.fields.CharField', [], {'max_length': '1024', 'null': 'True', 'blank': 'True'}), + 'report': ('django.db.models.fields.TextField', [], {'default': "'{}'"}), + 'slug': ('django.db.models.fields.SlugField', [], {'unique': 'True', 'max_length': '50'}), + 'user_id': ('django.db.models.fields.CharField', [], {'max_length': '1024'}) + } + } + + complete_apps = ['purple_robot_app'] \ No newline at end of file diff --git a/migrations/0039_auto__add_field_purplerobotdevice_last_reading_timestamp.py b/migrations/0039_auto__add_field_purplerobotdevice_last_reading_timestamp.py new file mode 100644 index 0000000..cc0d7e0 --- /dev/null +++ b/migrations/0039_auto__add_field_purplerobotdevice_last_reading_timestamp.py @@ -0,0 +1,138 @@ +# -*- coding: utf-8 -*- +from south.utils import datetime_utils as datetime +from south.db import db +from south.v2 import SchemaMigration +from django.db import models + + +class Migration(SchemaMigration): + + def forwards(self, orm): + # Adding field 'PurpleRobotDevice.last_reading_timestamp' + db.add_column(u'purple_robot_app_purplerobotdevice', 'last_reading_timestamp', + self.gf('django.db.models.fields.BigIntegerField')(default=0), + keep_default=False) + + + def backwards(self, orm): + # Deleting field 'PurpleRobotDevice.last_reading_timestamp' + db.delete_column(u'purple_robot_app_purplerobotdevice', 'last_reading_timestamp') + + + models = { + u'purple_robot_app.purplerobotalert': { + 'Meta': {'object_name': 'PurpleRobotAlert'}, + 'action_url': ('django.db.models.fields.URLField', [], {'max_length': '1024', 'null': 'True', 'blank': 'True'}), + 'dismissed': ('django.db.models.fields.DateTimeField', [], {'null': 'True', 'blank': 'True'}), + 'generated': ('django.db.models.fields.DateTimeField', [], {}), + u'id': ('django.db.models.fields.AutoField', [], {'primary_key': 'True'}), + 'manually_dismissed': ('django.db.models.fields.BooleanField', [], {'default': 'False'}), + 'message': ('django.db.models.fields.CharField', [], {'max_length': '2048'}), + 'probe': ('django.db.models.fields.CharField', [], {'max_length': '1024', 'null': 'True', 'blank': 'True'}), + 'severity': ('django.db.models.fields.IntegerField', [], {'default': '0'}), + 'tags': ('django.db.models.fields.CharField', [], {'max_length': '2048', 'null': 'True', 'blank': 'True'}), + 'user_id': ('django.db.models.fields.CharField', [], {'max_length': '1024', 'null': 'True', 'blank': 'True'}) + }, + u'purple_robot_app.purplerobotconfiguration': { + 'Meta': {'object_name': 'PurpleRobotConfiguration'}, + 'added': ('django.db.models.fields.DateTimeField', [], {}), + 'contents': ('django.db.models.fields.TextField', [], {'max_length': '1048576'}), + u'id': ('django.db.models.fields.AutoField', [], {'primary_key': 'True'}), + 'name': ('django.db.models.fields.CharField', [], {'max_length': '1024'}), + 'slug': ('django.db.models.fields.SlugField', [], {'unique': 'True', 'max_length': '1024'}) + }, + u'purple_robot_app.purplerobotdevice': { + 'Meta': {'object_name': 'PurpleRobotDevice'}, + 'config_last_fetched': ('django.db.models.fields.DateTimeField', [], {'null': 'True', 'blank': 'True'}), + 'config_last_user_agent': ('django.db.models.fields.CharField', [], {'max_length': '1024', 'null': 'True', 'blank': 'True'}), + 'configuration': ('django.db.models.fields.related.ForeignKey', [], {'blank': 'True', 'related_name': "'devices'", 'null': 'True', 'to': u"orm['purple_robot_app.PurpleRobotConfiguration']"}), + 'description': ('django.db.models.fields.TextField', [], {'max_length': '1048576', 'null': 'True', 'blank': 'True'}), + 'device_group': ('django.db.models.fields.related.ForeignKey', [], {'blank': 'True', 'related_name': "'devices'", 'null': 'True', 'to': u"orm['purple_robot_app.PurpleRobotDeviceGroup']"}), + 'device_id': ('django.db.models.fields.CharField', [], {'unique': 'True', 'max_length': '256', 'db_index': 'True'}), + 'first_reading_timestamp': ('django.db.models.fields.BigIntegerField', [], {'default': '0'}), + 'hash_key': ('django.db.models.fields.CharField', [], {'max_length': '128', 'null': 'True', 'blank': 'True'}), + u'id': ('django.db.models.fields.AutoField', [], {'primary_key': 'True'}), + 'last_reading_timestamp': ('django.db.models.fields.BigIntegerField', [], {'default': '0'}), + 'mute_alerts': ('django.db.models.fields.BooleanField', [], {'default': 'False'}), + 'name': ('django.db.models.fields.CharField', [], {'max_length': '1024'}), + 'performance_metadata': ('django.db.models.fields.TextField', [], {'default': "'{}'", 'max_length': '1048576'}), + 'test_device': ('django.db.models.fields.BooleanField', [], {'default': 'False'}) + }, + u'purple_robot_app.purplerobotdevicegroup': { + 'Meta': {'object_name': 'PurpleRobotDeviceGroup'}, + 'configuration': ('django.db.models.fields.related.ForeignKey', [], {'blank': 'True', 'related_name': "'groups'", 'null': 'True', 'to': u"orm['purple_robot_app.PurpleRobotConfiguration']"}), + 'description': ('django.db.models.fields.TextField', [], {'max_length': '1048576', 'null': 'True', 'blank': 'True'}), + 'group_id': ('django.db.models.fields.SlugField', [], {'unique': 'True', 'max_length': '256'}), + u'id': ('django.db.models.fields.AutoField', [], {'primary_key': 'True'}), + 'name': ('django.db.models.fields.CharField', [], {'max_length': '1024'}) + }, + u'purple_robot_app.purplerobotdevicenote': { + 'Meta': {'object_name': 'PurpleRobotDeviceNote'}, + 'added': ('django.db.models.fields.DateTimeField', [], {}), + 'device': ('django.db.models.fields.related.ForeignKey', [], {'related_name': "'notes'", 'to': u"orm['purple_robot_app.PurpleRobotDevice']"}), + u'id': ('django.db.models.fields.AutoField', [], {'primary_key': 'True'}), + 'note': ('django.db.models.fields.TextField', [], {'max_length': '1024'}) + }, + u'purple_robot_app.purplerobotevent': { + 'Meta': {'object_name': 'PurpleRobotEvent'}, + 'event': ('django.db.models.fields.CharField', [], {'max_length': '1024'}), + u'id': ('django.db.models.fields.AutoField', [], {'primary_key': 'True'}), + 'logged': ('django.db.models.fields.DateTimeField', [], {'db_index': 'True'}), + 'name': ('django.db.models.fields.CharField', [], {'db_index': 'True', 'max_length': '1024', 'null': 'True', 'blank': 'True'}), + 'payload': ('django.db.models.fields.TextField', [], {'max_length': '8388608', 'null': 'True', 'blank': 'True'}), + 'user_id': ('django.db.models.fields.CharField', [], {'max_length': '1024', 'db_index': 'True'}) + }, + u'purple_robot_app.purplerobotexportjob': { + 'Meta': {'object_name': 'PurpleRobotExportJob'}, + 'destination': ('django.db.models.fields.EmailField', [], {'max_length': '75', 'null': 'True', 'blank': 'True'}), + 'end_date': ('django.db.models.fields.DateField', [], {}), + 'export_file': ('django.db.models.fields.files.FileField', [], {'max_length': '100', 'blank': 'True'}), + u'id': ('django.db.models.fields.AutoField', [], {'primary_key': 'True'}), + 'probes': ('django.db.models.fields.TextField', [], {'max_length': '8196', 'null': 'True', 'blank': 'True'}), + 'start_date': ('django.db.models.fields.DateField', [], {}), + 'state': ('django.db.models.fields.CharField', [], {'default': "'pending'", 'max_length': '512'}), + 'users': ('django.db.models.fields.TextField', [], {'max_length': '8196', 'null': 'True', 'blank': 'True'}) + }, + u'purple_robot_app.purplerobotpayload': { + 'Meta': {'object_name': 'PurpleRobotPayload'}, + 'added': ('django.db.models.fields.DateTimeField', [], {'auto_now_add': 'True', 'blank': 'True'}), + 'errors': ('django.db.models.fields.TextField', [], {'max_length': '65536', 'null': 'True', 'blank': 'True'}), + u'id': ('django.db.models.fields.AutoField', [], {'primary_key': 'True'}), + 'payload': ('django.db.models.fields.TextField', [], {'max_length': '8388608'}), + 'process_tags': ('django.db.models.fields.CharField', [], {'db_index': 'True', 'max_length': '1024', 'null': 'True', 'blank': 'True'}), + 'user_id': ('django.db.models.fields.CharField', [], {'max_length': '1024', 'db_index': 'True'}) + }, + u'purple_robot_app.purplerobotreading': { + 'Meta': {'object_name': 'PurpleRobotReading', 'index_together': "[['probe', 'user_id'], ['logged', 'user_id'], ['probe', 'logged', 'user_id']]"}, + 'attachment': ('django.db.models.fields.files.FileField', [], {'max_length': '100', 'null': 'True', 'blank': 'True'}), + 'guid': ('django.db.models.fields.CharField', [], {'db_index': 'True', 'max_length': '1024', 'null': 'True', 'blank': 'True'}), + u'id': ('django.db.models.fields.AutoField', [], {'primary_key': 'True'}), + 'logged': ('django.db.models.fields.DateTimeField', [], {'db_index': 'True'}), + 'payload': ('django.db.models.fields.TextField', [], {'max_length': '8388608'}), + 'probe': ('django.db.models.fields.CharField', [], {'db_index': 'True', 'max_length': '1024', 'null': 'True', 'blank': 'True'}), + 'size': ('django.db.models.fields.IntegerField', [], {'default': '0', 'db_index': 'True'}), + 'user_id': ('django.db.models.fields.CharField', [], {'max_length': '1024', 'db_index': 'True'}) + }, + u'purple_robot_app.purplerobotreport': { + 'Meta': {'object_name': 'PurpleRobotReport'}, + 'generated': ('django.db.models.fields.DateTimeField', [], {}), + u'id': ('django.db.models.fields.AutoField', [], {'primary_key': 'True'}), + 'mime_type': ('django.db.models.fields.CharField', [], {'max_length': '1024'}), + 'probe': ('django.db.models.fields.CharField', [], {'max_length': '1024', 'null': 'True', 'blank': 'True'}), + 'report_file': ('django.db.models.fields.files.FileField', [], {'max_length': '100', 'blank': 'True'}), + 'user_id': ('django.db.models.fields.CharField', [], {'max_length': '1024'}) + }, + u'purple_robot_app.purplerobottest': { + 'Meta': {'object_name': 'PurpleRobotTest'}, + 'active': ('django.db.models.fields.BooleanField', [], {'default': 'False'}), + 'frequency': ('django.db.models.fields.FloatField', [], {'default': '1.0'}), + u'id': ('django.db.models.fields.AutoField', [], {'primary_key': 'True'}), + 'last_updated': ('django.db.models.fields.DateTimeField', [], {}), + 'probe': ('django.db.models.fields.CharField', [], {'max_length': '1024', 'null': 'True', 'blank': 'True'}), + 'report': ('django.db.models.fields.TextField', [], {'default': "'{}'"}), + 'slug': ('django.db.models.fields.SlugField', [], {'unique': 'True', 'max_length': '50'}), + 'user_id': ('django.db.models.fields.CharField', [], {'max_length': '1024'}) + } + } + + complete_apps = ['purple_robot_app'] \ No newline at end of file diff --git a/models.py b/models.py index ab9dd09..19aa66a 100644 --- a/models.py +++ b/models.py @@ -1,3 +1,6 @@ +# pylint: disable=line-too-long, no-member, old-style-class, no-init, unused-argument, too-many-lines, too-many-public-methods, too-few-public-methods + +import arrow import calendar import datetime import importlib @@ -9,7 +12,7 @@ import time import traceback -from django.core.cache import cache +from django.conf import settings from django.core.urlresolvers import reverse from django.db import models, connection from django.db.models import Sum @@ -19,9 +22,32 @@ from django.utils.safestring import SafeString from django.utils.text import slugify +from purple_robot_app.performance import fetch_performance_samples + +PROBE_USER_TIME_CACHE = {} + +PROBE_CACHE_MAP = { + 'edu.northwestern.cbits.purple_robot_manager.probes.builtin.RobotHealthProbe': [ + 'last_pending_count' + ], + 'edu.northwestern.cbits.purple_robot_manager.probes.builtin.HardwareInformationProbe': [ + 'last_model' + ], + 'edu.northwestern.cbits.purple_robot_manager.probes.builtin.SoftwareInformationProbe': [ + 'last_platform' + ], + 'edu.northwestern.cbits.purple_robot_manager.probes.builtin.BatteryProbe': [ + 'last_battery' + ], +} + +PAYLOAD_PERFORMANCE_CACHE = {} + + def my_slugify(str_obj): return slugify(str_obj.replace('.', ' ')).replace('-', '_') + class PurpleRobotConfiguration(models.Model): name = models.CharField(max_length=1024) slug = models.SlugField(max_length=1024, unique=True, db_index=True) @@ -30,24 +56,25 @@ class PurpleRobotConfiguration(models.Model): def __unicode__(self): return self.name - + def device_count(self): identifiers = set() - + for group in self.groups.all(): for device in group.devices.all(): identifiers.add(device.device_id) - + for device in self.devices.all(): identifiers.add(device.device_id) - + return len(identifiers) - + def format(self): if self.contents.lower().startswith('(begin'): return 'Scheme' - - return 'JSON'; + + return 'JSON' + class PurpleRobotDeviceGroup(models.Model): name = models.CharField(max_length=1024) @@ -58,6 +85,7 @@ class PurpleRobotDeviceGroup(models.Model): def __unicode__(self): return self.group_id + class PurpleRobotDevice(models.Model): name = models.CharField(max_length=1024) device_id = models.CharField(max_length=256, unique=True, db_index=True) @@ -67,37 +95,85 @@ class PurpleRobotDevice(models.Model): config_last_fetched = models.DateTimeField(null=True, blank=True) config_last_user_agent = models.CharField(max_length=1024, null=True, blank=True) hash_key = models.CharField(max_length=128, null=True, blank=True) - + + first_reading_timestamp = models.BigIntegerField(default=0) + last_reading_timestamp = models.BigIntegerField(default=0) + mute_alerts = models.BooleanField(default=False) - + test_device = models.BooleanField(default=False) + performance_metadata = models.TextField(max_length=1048576, default='{}') def __unicode__(self): return self.device_id - + def init_hash(self): - if self.hash_key == None or len(self.hash_key) < 32: - m = hashlib.md5() - m.update(self.device_id.encode('utf-8')) - - self.hash_key = m.hexdigest() + if self.hash_key is None or len(self.hash_key) < 32: + md5_hash = hashlib.md5() + md5_hash.update(self.device_id.encode('utf-8')) + + self.hash_key = md5_hash.hexdigest() self.save() - def user_hash(self, start=None, end=None): + def user_hash(self): self.init_hash() - + return self.hash_key - + + def earliest_reading_date(self): + self.init_hash() + + if self.first_reading_timestamp != 0: + if self.first_reading_timestamp > 0: + return datetime.datetime.utcfromtimestamp(self.first_reading_timestamp).replace(tzinfo=pytz.utc) + else: + return None + + first = PurpleRobotReading.objects.filter(user_id=self.hash_key).order_by('logged').first() + + if first is not None: + self.first_reading_timestamp = int(time.mktime(first.logged.timetuple())) + self.save() + + return first.logged + else: + self.first_reading_timestamp = -1 + self.save() + + return None + + def latest_reading_date(self): + self.init_hash() + + if self.last_reading_timestamp != 0: + if self.last_reading_timestamp > 0: + return datetime.datetime.utcfromtimestamp(self.last_reading_timestamp).replace(tzinfo=pytz.utc) + else: + return None + + first = PurpleRobotReading.objects.filter(user_id=self.hash_key).order_by('-logged').first() + + if first is not None: + self.last_reading_timestamp = int(time.mktime(first.logged.timetuple())) + self.save() + + return first.logged + else: + self.last_reading_timestamp = -1 + self.save() + + return None + def fetch_reading_count(self, probe): perf_data = json.loads(self.performance_metadata) - + if ('reading_counts' in perf_data) and (probe in perf_data['reading_counts']): return perf_data['reading_counts'][probe] - if ('reading_counts' in perf_data) == False: + if ('reading_counts' in perf_data) is False: perf_data['reading_counts'] = {} - - if (probe in perf_data['reading_counts']) == False: + + if (probe in perf_data['reading_counts']) is False: cursor = connection.cursor() cursor.execute("SELECT COUNT(logged) FROM \"purple_robot_app_purplerobotreading\" WHERE (\"purple_robot_app_purplerobotreading\".\"probe\" = '%s' AND \"purple_robot_app_purplerobotreading\".\"user_id\" = '%s' );" % (probe, self.hash_key)) row = cursor.fetchone() @@ -107,38 +183,54 @@ def fetch_reading_count(self, probe): self.save() return perf_data['reading_counts'][probe] - + def set_most_recent_reading(self, new_reading): + key = str(self.pk) + '-' + new_reading.probe + perf_data = json.loads(self.performance_metadata) - old_reading = None + old_reading_date = None updated = False - - if ('latest_readings' in perf_data) == False: + + if ('latest_readings' in perf_data) is False: perf_data['latest_readings'] = {} - elif (new_reading.probe in perf_data['latest_readings']): - old_reading = PurpleRobotReading.objects.filter(pk=perf_data['latest_readings'][new_reading.probe]).first() + elif new_reading.probe in perf_data['latest_readings']: + if (key in PROBE_USER_TIME_CACHE) is False: + old_reading = PurpleRobotReading.objects.filter(pk=perf_data['latest_readings'][new_reading.probe]).first() - if ('reading_counts' in perf_data) == False: + if old_reading is not None: + PROBE_USER_TIME_CACHE[key] = old_reading.logged + + if key in PROBE_USER_TIME_CACHE: + old_reading_date = PROBE_USER_TIME_CACHE[key] + + if ('reading_counts' in perf_data) is False: perf_data['reading_counts'] = {} - - if (new_reading.probe in perf_data['reading_counts']) == False: + + if (new_reading.probe in perf_data['reading_counts']) is False: perf_data['reading_counts'][new_reading.probe] = self.fetch_reading_count(new_reading.probe) - - perf_data['reading_counts'][new_reading.probe] = perf_data['reading_counts'][new_reading.probe] + 1 - if ('probes' in perf_data) == False: + perf_data['reading_counts'][new_reading.probe] += 1 + + if ('probes' in perf_data) is False: perf_data['probes'] = [] - - if (new_reading.probe in perf_data['probes']) == False: + + if (new_reading.probe in perf_data['probes']) is False: perf_data['probes'].append(new_reading.probe) updated = True - - if old_reading == None or new_reading.logged > old_reading.logged: + + if old_reading_date is None or new_reading.logged > old_reading_date: perf_data['latest_readings'][new_reading.probe] = new_reading.pk + PROBE_USER_TIME_CACHE[key] = new_reading.logged + updated = True - + if updated: + if new_reading.probe in PROBE_CACHE_MAP: + for key in PROBE_CACHE_MAP[new_reading.probe]: + if key in perf_data: + del perf_data[key] + self.performance_metadata = json.dumps(perf_data, indent=2) self.save() @@ -146,42 +238,37 @@ def set_most_recent_payload(self, new_payload): perf_data = json.loads(self.performance_metadata) old_payload = None - - if ('latest_payload' in perf_data) == False: - perf_data['latest_payload'] = -1 - else: + + if 'latest_payload' in perf_data: old_payload = PurpleRobotPayload.objects.filter(pk=perf_data['latest_payload']).first() - - if old_payload != None and new_payload.added > old_payload.added: + + if (old_payload is not None and new_payload.added > old_payload.added) or old_payload is None: perf_data['latest_payload'] = new_payload.pk - + self.performance_metadata = json.dumps(perf_data, indent=2) self.save() + self.set_performance_info('last_upload', new_payload.added.isoformat()) + def most_recent_payload(self): perf_data = json.loads(self.performance_metadata) - - if 'latest_payload' in perf_data: + + if 'latest_payload' in perf_data and perf_data['latest_payload'] != -1: return PurpleRobotPayload.objects.filter(pk=perf_data['latest_payload']).first() - else: - perf_data['latest_payload'] = -1 payload = PurpleRobotPayload.objects.filter(user_id=self.hash_key).order_by('-added').first() - - if payload != None: + + if payload is not None: perf_data['latest_payload'] = payload.pk - else: - perf_data['latest_payload'] = -1 - + self.performance_metadata = json.dumps(perf_data, indent=2) self.save() - + return payload - def most_recent_reading(self, probe_name): perf_data = json.loads(self.performance_metadata) - + if 'latest_readings' in perf_data: if probe_name in perf_data['latest_readings']: return PurpleRobotReading.objects.filter(pk=perf_data['latest_readings'][probe_name]).first() @@ -189,392 +276,526 @@ def most_recent_reading(self, probe_name): perf_data['latest_readings'] = {} reading = PurpleRobotReading.objects.filter(user_id=self.hash_key, probe=probe_name).order_by('-logged').first() - - if reading != None: - perf_data['latest_readings'][probe_name] = reading.pk + + if reading is not None: + self.set_most_recent_reading(reading) else: perf_data['latest_readings'][probe_name] = -1 - - self.performance_metadata = json.dumps(perf_data, indent=2) - self.save() - + self.performance_metadata = json.dumps(perf_data, indent=2) + self.save() + return reading + def earliest_reading(self, probe_name): + perf_data = json.loads(self.performance_metadata) + + if 'earliest_readings' in perf_data: + if probe_name in perf_data['earliest_readings']: + return PurpleRobotReading.objects.filter(pk=perf_data['earliest_readings'][probe_name]).first() + else: + perf_data['earliest_readings'] = {} + + reading = PurpleRobotReading.objects.filter(user_id=self.hash_key, probe=probe_name).order_by('logged').first() + + if reading is not None: + self.set_earliest_reading(reading) + self.set_latest_reading(reading) + + return reading + + def set_earliest_reading(self, new_reading): + perf_data = json.loads(self.performance_metadata) + + old_reading = None + updated = False + + if ('earliest_readings' in perf_data) is False: + perf_data['earliest_readings'] = {} + elif new_reading.probe in perf_data['earliest_readings']: + old_reading = PurpleRobotReading.objects.filter(pk=perf_data['earliest_readings'][new_reading.probe]).first() + + if old_reading is None or new_reading.logged < old_reading.logged: + perf_data['earliest_readings'][new_reading.probe] = new_reading.pk + updated = True + + if updated: + if new_reading.probe in PROBE_CACHE_MAP: + for key in PROBE_CACHE_MAP[new_reading.probe]: + if key in perf_data: + del perf_data[key] + + self.performance_metadata = json.dumps(perf_data, indent=2) + self.save() + +# Redundant? (set_most_recent_reading) +# def set_latest_reading(self, new_reading): +# perf_data = json.loads(self.performance_metadata) +# +# old_reading = None +# updated = False +# +# if ('latest_readings' in perf_data) is False: +# perf_data['latest_readings'] = {} +# elif (new_reading.probe in perf_data['latest_readings']): +# old_reading = PurpleRobotReading.objects.filter(pk=perf_data['latest_readings'][new_reading.probe]).first() +# +# if old_reading is None or new_reading.logged > old_reading.logged: +# perf_data['latest_readings'][new_reading.probe] = new_reading.pk +# updated = True +# +# if updated: +# if new_reading.probe in PROBE_CACHE_MAP: +# for key in PROBE_CACHE_MAP[new_reading.probe]: +# if key in perf_data: +# del perf_data[key] +# +# self.performance_metadata = json.dumps(perf_data, indent=2) +# self.save() + def clear_most_recent_reading(self, probe_name, new_pk=None): perf_data = json.loads(self.performance_metadata) - + if 'latest_readings' in perf_data: if probe_name in perf_data['latest_readings']: - if new_pk == None: + if new_pk is None: del perf_data['latest_readings'][probe_name] else: perf_data['latest_readings'][probe_name] = new_pk - + self.performance_metadata = json.dumps(perf_data, indent=2) self.save() - + def last_upload(self): self.init_hash() - + + last_upload = self.get_performance_info('last_upload') + + if last_upload is not None: + if last_upload == '': + return None + + last_upload = arrow.get(last_upload).datetime + + return last_upload + payload = self.most_recent_payload() - - if payload != None: - return payload.added - - return None - + + if payload is not None: + last_upload = payload.added + + self.set_performance_info('last_upload', last_upload.isoformat()) + else: + self.set_performance_info('last_upload', '') + + return last_upload + def last_upload_status(self): upload = self.last_upload() - + now = timezone.now() - + diff = now - upload - + if diff.days > 0: return "danger" elif diff.seconds > (8 * 60 * 60): return "warning" - + return "ok" def config_last_fetched_status(self): upload = self.config_last_fetched - + now = timezone.now() - + diff = now - upload - + if diff.days > 1: return "danger" elif diff.days > 0: return "warning" - + return "ok" def battery_history(self, start=None, end=None): self.init_hash() - + now = timezone.now() - - if start == None: + + if start is None: start = now - datetime.timedelta(days=1) - - if end == None: + + if end is None: end = now - + readings = [] - + for reading in PurpleRobotReading.objects.filter(user_id=self.hash_key, probe='edu.northwestern.cbits.purple_robot_manager.probes.builtin.BatteryProbe', logged__gte=start, logged__lte=end).order_by('logged'): data = json.loads(reading.payload) - - timestamp = calendar.timegm(reading.logged.timetuple()) - + + timestamp = calendar.timegm(reading.logged.timetuple()) + if len(readings) == 0 or readings[-1]['level'] != data['level'] or (timestamp - readings[-1]['timestamp']) > (30 * 60): - item = { 'level': data['level'], 'timestamp': timestamp } - + item = {'level': data['level'], 'timestamp': timestamp} + readings.append(item) - + return readings def last_battery(self): self.init_hash() - data = cache.get(self.hash_key + '__last_battery') - - if data != None: - return data['level'] - + battery = self.get_performance_info('last_battery') + + if battery is not None: + if battery == -1: + return None + + return battery + reading = self.most_recent_reading('edu.northwestern.cbits.purple_robot_manager.probes.builtin.BatteryProbe') - - if reading != None: + + if reading is not None: data = json.loads(reading.payload) - - cache.set(self.hash_key + '__last_battery', data, 15 * 60) - - return data['level'] - - return None + + battery = data['level'] + + self.set_performance_info('last_battery', battery) + else: + self.set_performance_info('last_battery', -1) + + return battery def last_battery_status(self): battery = self.last_battery() - + if battery <= 25: return "danger" elif battery <= 33: return "warning" - + return "ok" def projected_battery_lifetime(self): last_level = None last_timestamp = None - + deltas = [] drain_count = 0 - plug_count = 0 - count = 0 - + start = 0 - + for reading in PurpleRobotReading.objects.filter(user_id=self.hash_key, probe='edu.northwestern.cbits.purple_robot_manager.probes.builtin.BatteryProbe').order_by('-logged')[start:(start+250)]: - count += 1 data = json.loads(reading.payload) - - timestamp = calendar.timegm(reading.logged.timetuple()) - + + timestamp = calendar.timegm(reading.logged.timetuple()) + plugged = (data['plugged'] != 0) - - if plugged == False: + + if plugged is False: drain_count += 1 - if last_level == None or last_timestamp == None: + if last_level is None or last_timestamp is None: last_level = float(data['level']) last_timestamp = timestamp else: this_level = float(data['level']) this_timestamp = timestamp - + if this_level - last_level > 0 and (last_timestamp - this_timestamp) < (60 * 60) and this_level <= 98: delta = (float(last_timestamp - this_timestamp) / float(this_level - last_level)) - + deltas.append(delta) last_level = this_level last_timestamp = this_timestamp - else: - plug_count += 1 - + if drain_count > 100: break - + start += 250 - + if len(deltas) > 0: return numpy.mean(deltas) * 100 - - return -1 - - + return -1 def status(self): - statuses = [] - + if self.mute_alerts: + return 'ok' + severity = self.alert_severity() - + if severity > 1: return 'danger' if severity > 0: return 'warning' - + return 'ok' def pending_history(self, start=None, end=None): self.init_hash() - + now = timezone.now() - - if start == None: + + if start is None: start = now - datetime.timedelta(days=1) - - if end == None: + + if end is None: end = now - + readings = [] - + for reading in PurpleRobotReading.objects.filter(user_id=self.hash_key, probe='edu.northwestern.cbits.purple_robot_manager.probes.builtin.RobotHealthProbe', logged__gte=start, logged__lte=end).order_by('logged'): data = json.loads(reading.payload) - - timestamp = calendar.timegm(reading.logged.timetuple()) - + + timestamp = calendar.timegm(reading.logged.timetuple()) + if len(readings) == 0 or readings[-1]['count'] != data['PENDING_COUNT'] or (timestamp - readings[-1]['timestamp']) > (30 * 60): - item = { 'count': data['PENDING_COUNT'], 'timestamp': timestamp } - + item = {'count': data['PENDING_COUNT'], 'timestamp': timestamp} + readings.append(item) - + return readings + def get_performance_info(self, key): + perf_data = json.loads(self.performance_metadata) + + if key in perf_data: + return perf_data[key] + + return None + + def set_performance_info(self, key, value): + perf_data = json.loads(self.performance_metadata) + + perf_data[key] = value + + self.performance_metadata = json.dumps(perf_data, indent=2) + self.save() + def last_pending_count(self): + count = self.get_performance_info('last_pending_count') + + if count is not None: + return count + data = self.last_robot_health() - - if data != None: - return data['PENDING_COUNT'] - - return None + + if data is not None: + count = data['PENDING_COUNT'] + + self.set_performance_info('last_pending_count', count) + else: + self.set_performance_info('last_pending_count', 0) + + return count def triggers(self): data = self.last_robot_health() - - if data != None: + + if data is not None: return data['TRIGGERS'] - + return None def last_hardware_info(self): self.init_hash() - - data = cache.get(self.hash_key + '__last_hardware_info') - - if data != None: - return data - + + data = None + reading = self.most_recent_reading('edu.northwestern.cbits.purple_robot_manager.probes.builtin.HardwareInformationProbe') - - if reading != None: + + if reading is not None: data = json.loads(reading.payload) - - cache.set(self.hash_key + '__last_hardware_info', data) - + return data def last_model(self): + model = self.get_performance_info('last_model') + + if model is not None: + return model + data = self.last_hardware_info() - - if data != None: - return data['MODEL'] - - return None + + if data is not None: + model = data['MODEL'] + + self.set_performance_info('last_model', model) + else: + self.set_performance_info('last_model', 'Unknown') + + return model + + def last_manufacturer(self): + mfgr = self.get_performance_info('last_manufacturer') + + if mfgr is not None: + return mfgr + + data = self.last_hardware_info() + + if data is not None: + mfgr = data['MANUFACTURER'] + + self.set_performance_info('last_manufacturer', mfgr) + else: + self.set_performance_info('last_manufacturer', 'Unknown') + + return mfgr def last_robot_health(self): self.init_hash() - + reading = self.most_recent_reading('edu.northwestern.cbits.purple_robot_manager.probes.builtin.RobotHealthProbe') - + data = None - - if reading != None: + + if reading is not None: data = json.loads(reading.payload) - + return data - + def last_software_info(self): self.init_hash() - - data = cache.get(self.hash_key + '__last_software_info') - - if data != None: - return data - + reading = self.most_recent_reading('edu.northwestern.cbits.purple_robot_manager.probes.builtin.SoftwareInformationProbe') - - if reading != None: + + data = None + + if reading is not None: data = json.loads(reading.payload) - - cache.set(self.hash_key + '__last_software_info', data) - + return data def last_platform(self): + platform = self.get_performance_info('last_platform') + + if platform is not None: + return platform + data = self.last_software_info() - - if data != None: - return 'Android ' + data['RELEASE'] - - return None + + if data is not None: + platform = 'Android ' + data['RELEASE'] + self.set_performance_info('last_platform', platform) + else: + self.set_performance_info('last_platform', 'Unknown') + + return platform def last_location(self): self.init_hash() - location = cache.get(self.hash_key + '__last_robot_location') - - if location != None: - return location - reading = self.most_recent_reading('edu.northwestern.cbits.purple_robot_manager.probes.builtin.LocationProbe') - - if reading != None: + + if reading is not None: data = json.loads(reading.payload) - - location = { 'latitude': data['LATITUDE'], 'longitude': data['LONGITUDE'] } - - cache.set(self.hash_key + '__last_robot_location', location) - return location + location = {'latitude': data['LATITUDE'], 'longitude': data['LONGITUDE']} + + return location + + return None def installed_apps(self): self.init_hash() reading = self.most_recent_reading('edu.northwestern.cbits.purple_robot_manager.probes.builtin.SoftwareInformationProbe') - - if reading != None: + + if reading is not None: data = json.loads(reading.payload) - return sorted(data['INSTALLED_APPS'], key=lambda item: item['APP_NAME'].lower()) - + return [] - + def probes(self): perf_data = json.loads(self.performance_metadata) - if ('probes' in perf_data): + if 'probes' in perf_data: return perf_data['probes'] perf_data['probes'] = [] probe_readings = PurpleRobotReading.objects.values('probe').distinct() - + for probe_reading in probe_readings: item = self.most_recent_reading(probe_reading['probe']) - - if item != None: + + if item is not None: perf_data['probes'].append(item.probe) - + self.performance_metadata = json.dumps(perf_data, indent=2) self.save() - + return perf_data['probes'] - - + def last_readings(self, omit_readings=False): self.init_hash() readings = [] - + + start = timezone.now() - datetime.timedelta(days=1) + for probe_name in self.probes(): item = self.most_recent_reading(probe_name) - - if item != None: + + if item is not None: reading = {} - + reading['name'] = item.probe.split('.')[-1] reading['full_probe_name'] = item.probe reading['last_update'] = item.logged - - if omit_readings == False: -# cursor = connection.cursor() -# cursor.execute("SELECT COUNT(logged) FROM \"purple_robot_app_purplerobotreading\" WHERE (\"purple_robot_app_purplerobotreading\".\"probe\" = '%s' AND \"purple_robot_app_purplerobotreading\".\"user_id\" = '%s' );" % (item.probe, self.hash_key)) -# row = cursor.fetchone() - reading['num_readings'] = self.fetch_reading_count(probe_name) -# reading['num_readings'] = 0 -# reading['num_readings'] = PurpleRobotReading.objects.filter(user_id=self.hash_key, probe=item.probe).count() + + if omit_readings is False: + # cursor = connection.cursor() + # cursor.execute("SELECT COUNT(logged) FROM \"purple_robot_app_purplerobotreading\" WHERE (\"purple_robot_app_purplerobotreading\".\"probe\" = '%s' AND \"purple_robot_app_purplerobotreading\".\"user_id\" = '%s' );" % (item.probe, self.hash_key)) + # row = cursor.fetchone() + # reading['num_readings'] = self.fetch_reading_count(probe_name) + # reading['num_readings'] = 0 + # reading['num_readings'] = PurpleRobotReading.objects.filter(user_id=self.hash_key, probe=item.probe).count() + reading['status'] = 'TODO' reading['frequency'] = 'Unknown' - - for test in PurpleRobotTest.objects.filter(probe=item.probe, user_id=self.hash_key): - reading['frequency'] = test.average_frequency() - + reading['num_readings'] = 'None' + + samples = fetch_performance_samples(self.hash_key, item.probe, start=start) + + count = 0 + + for sample in samples: + count += sample['sample_count'] + + if count > 0: + reading['frequency'] = count / datetime.timedelta(days=1).total_seconds() + + reading['num_readings'] = count + + # for test in PurpleRobotTest.objects.filter(probe=item.probe, user_id=self.hash_key): + # reading['frequency'] = test.average_frequency() + readings.append(reading) - - readings = sorted(readings, key=lambda k: k['name']) - + + readings = sorted(readings, key=lambda k: k['name']) + return readings - + def total_readings_size(self): metadata = json.loads(self.performance_metadata) - + if 'total_readings_size' in metadata: return metadata['total_readings_size'] - + return -1 - def data_size_history(self, unit='day', count=None): now = timezone.localtime(timezone.now()) - + bin_size = None count = 0 - + if unit == 'day': bin_size = datetime.timedelta(days=1) end = datetime.datetime(now.year, now.month, now.day, 0, 0, 0, 0, now.tzinfo) @@ -583,110 +804,155 @@ def data_size_history(self, unit='day', count=None): elif unit == 'hour': bin_size = datetime.timedelta(seconds=(60 * 60)) end = datetime.datetime(now.year, now.month, now.day, now.hour, 0, 0, 0, now.tzinfo) - end = end + in_size + end = end + bin_size count = 48 elif unit == 'minute': bin_size = datetime.timedelta(seconds=60) end = datetime.datetime(now.year, now.month, now.day, now.hour, now.minute, 0, 0, now.tzinfo) end = end + datetime.timedelta(seconds=60) count = 240 - + size_history = [] - + while count > 0: start = end - bin_size - -# size = 0 -# -# readings = PurpleRobotReading.objects.filter(user_id=self.hash_key, logged__gte=start, logged__lt=end, size=0)[:250] -# -# while readings.count() > 0: -# for reading in readings: -# reading.size = len(reading.payload) -# reading.save() -# -# readings = PurpleRobotReading.objects.filter(user_id=self.hash_key, logged__gte=start, logged__lt=end, size=0)[:250] - + + # size = 0 + # + # readings = PurpleRobotReading.objects.filter(user_id=self.hash_key, logged__gte=start, logged__lt=end, size=0)[:250] + # + # while readings.count() > 0: + # for reading in readings: + # reading.size = len(reading.payload) + # reading.save() + # + # readings = PurpleRobotReading.objects.filter(user_id=self.hash_key, logged__gte=start, logged__lt=end, size=0)[:250] + size = PurpleRobotReading.objects.filter(user_id=self.hash_key, logged__gte=start, logged__lt=end).aggregate(Sum('size')) - + start_ts = time.mktime(start.timetuple()) end_ts = time.mktime(end.timetuple()) - + total_size = size['size__sum'] - - if total_size == None: + + if total_size is None: total_size = 0 - - history_point = { 'size': total_size, 'timestamp': ((start_ts + end_ts) / 2) } - + + history_point = {'size': total_size, 'timestamp': ((start_ts + end_ts) / 2)} + size_history.append(history_point) - + end = start count -= 1 - - + return reversed(size_history) def events(self, start=None, end=None): self.init_hash() - + now = timezone.now() - - if start == None: + + if start is None: start = now - datetime.timedelta(days=7) - - if end == None: + + if end is None: end = now - + return PurpleRobotEvent.objects.filter(user_id=self.hash_key, logged__gte=start, logged__lte=end).order_by('-logged') - + def alerts(self): self.init_hash() - + return PurpleRobotAlert.objects.filter(dismissed=None, user_id=self.hash_key) - + def alert_count(self): return self.alerts().count() - + def alert_severity(self): severity = 0 - + for alert in self.alerts(): if alert.severity > severity: severity = alert.severity - + return severity - + def visualization_for_probe(self, probe_name): formatter_name = my_slugify(probe_name).replace('edu_northwestern_cbits_purple_robot_manager_probes_', '') - + try: formatter = importlib.import_module('purple_robot_app.formatters.' + formatter_name) except ImportError: formatter = importlib.import_module('purple_robot_app.formatters.probe') - + readings = PurpleRobotReading.objects.filter(user_id=self.user_hash, probe=probe_name).order_by('-logged')[:500] - + return formatter.visualize(probe_name, readings) - + def sanity_messages(self): data = self.last_robot_health() - - if data == None: + + if data is None: return None - + messages = [] - + if 'CHECK_ERRORS' in data: for error in data['CHECK_ERRORS']: messages.append((error, "error",)) - + if 'CHECK_WARNINGS' in data: for warning in data['CHECK_WARNINGS']: messages.append((warning, "warning",)) - + return messages - + + def fetch_gap_status(self, probe_name, start=None, end=None): + if end is None: + end = timezone.now() + + if start is None: + start = end - datetime.timedelta(days=3) + + samples = fetch_performance_samples(self.hash_key, probe_name, start=start, end=end) + + status = {} + + status['num_samples'] = len(samples) + + if len(samples) > 0: + readings_start = arrow.get(samples[0]['sample_date']).datetime + readings_end = arrow.get(samples[-1]['sample_date']).datetime + + duration = float((readings_end - readings_start).total_seconds()) + + if duration > 0: + status['frequency'] = len(samples) / duration + else: + status['frequency'] = 0.0 + + gaps = [] + + largest_gap = 0.0 + + for i in range(0, len(samples) - 1): + one = samples[i] + two = samples[i + 1] + + gap = float((arrow.get(two['sample_date']).datetime - arrow.get(one['sample_date']).datetime).total_seconds()) + + gaps.append(gap) + + if gap > largest_gap: + largest_gap = gap + + # status['average_gap'] = numpy.mean(gaps) + status['largest_gap'] = largest_gap + # status['std_dev_gap'] = numpy.std(gaps) + + return status + + class PurpleRobotDeviceNote(models.Model): device = models.ForeignKey(PurpleRobotDevice, related_name='notes') note = models.TextField(max_length=1024) @@ -695,6 +961,7 @@ class PurpleRobotDeviceNote(models.Model): def __unicode__(self): return str(self.device) + ': ' + str(self.added) + class PurpleRobotPayload(models.Model): added = models.DateTimeField(auto_now_add=True) payload = models.TextField(max_length=8388608) @@ -702,17 +969,17 @@ class PurpleRobotPayload(models.Model): user_id = models.CharField(max_length=1024, db_index=True) errors = models.TextField(max_length=65536, null=True, blank=True) - + def ingest_readings(self): tags = self.process_tags - + tag = 'extracted_readings' - + items = json.loads(self.payload) - + device = PurpleRobotDevice.objects.filter(hash_key=self.user_id).first() - if device != None: + if device is not None: device.set_most_recent_payload(self) for item in items: @@ -721,68 +988,105 @@ def ingest_readings(self): reading.payload = json.dumps(item, indent=2) reading.logged = datetime.datetime.utcfromtimestamp(item['TIMESTAMP']).replace(tzinfo=pytz.utc) reading.guid = item['GUID'] + reading.size = len(reading.payload) + if reading.attachment is not None: + try: + reading.size += reading.attachment.size + except ValueError: + pass # No attachment... + reading.save() - - if device != None: + + if device is not None: device.set_most_recent_reading(reading) - except KeyError as e: - print('Missing Key: ' + json.dumps(item, indent=2)) - print(traceback.format_exc()) - + device.set_earliest_reading(reading) + # device.set_latest_reading(reading) + + if settings.PURPLE_ROBOT_DISABLE_DATA_CHECKS is False: + probe_name = my_slugify(item['PROBE']).replace('edu_northwestern_cbits_purple_robot_manager_probes_', '') + + found = False + probe = None + + if probe_name in PAYLOAD_PERFORMANCE_CACHE: + probe = PAYLOAD_PERFORMANCE_CACHE[probe_name] + else: + for app in settings.INSTALLED_APPS: + if found is False: + try: + probe = importlib.import_module(app + '.management.commands.analytics.' + probe_name) + found = True + except ImportError: + pass + + if found is False: + probe = importlib.import_module('purple_robot_app.management.commands.analytics.generic_probe') + + PAYLOAD_PERFORMANCE_CACHE[probe_name] = probe + probe.log_reading(reading) + + except KeyError: + print 'Missing Key: ' + json.dumps(item, indent=2) + print traceback.format_exc() + if tags is None or len(tags) == 0: tags = 'ingest_error' elif tags.find('ingest_error') == -1: tags += ' ingest_error' - + if tags is None or tags.find(tag) == -1: if tags is None or len(tags) == 0: tags = tag else: tags += ' ' + tag - + self.process_tags = tags - + self.save() - + class PurpleRobotEvent(models.Model): event = models.CharField(max_length=1024) name = models.CharField(max_length=1024, null=True, blank=True, db_index=True) - logged = models.DateTimeField() + logged = models.DateTimeField(db_index=True) user_id = models.CharField(max_length=1024, db_index=True) payload = models.TextField(max_length=(1024 * 1024 * 8), null=True, blank=True) - + def event_name(self): if self.name is None: return self.name - + if self.event == 'java_exception': payload = json.loads(self.payload) - + tokens = payload['content_object']['stacktrace'].split(':') - + self.name = tokens[0] self.save() - + return self.name - + def description(self): payload = json.loads(self.payload) - + if self.event == 'pr_script_log_message': return payload['message'] elif self.event == 'set_user_id': - return SafeString(payload['old_id'] + ' → ' + payload['new_id']) + if 'new_id' in payload: + return SafeString(payload['old_id'] + ' → ' + payload['new_id']) + else: + return SafeString(payload['old_id'] + ' → ?') elif self.event == 'java_exception': return payload['stacktrace'].split('\n')[0] elif self.event == 'broadcast_message': return payload['message'] - + return self.event + ' (' + str(self.pk) + ')' + class PurpleRobotReading(models.Model): probe = models.CharField(max_length=1024, null=True, blank=True, db_index=True) user_id = models.CharField(max_length=1024, db_index=True) @@ -790,9 +1094,9 @@ class PurpleRobotReading(models.Model): logged = models.DateTimeField(db_index=True) guid = models.CharField(max_length=1024, db_index=True, null=True, blank=True) size = models.IntegerField(default=0, db_index=True) - + attachment = models.FileField(upload_to='reading_attachments', null=True, blank=True) - + class Meta: index_together = [ ['probe', 'user_id'], @@ -802,33 +1106,35 @@ class Meta: def probe_name(self): return self.probe.replace('edu.northwestern.cbits.purple_robot_manager.probes.', '') - + def update_guid(self): - if self.guid != None: + if self.guid is not None: return - + reading_json = json.loads(self.payload) - + self.guid = reading_json['GUID'] self.save() - + def fetch_summary(self): probe_name = my_slugify(self.probe).replace('edu_northwestern_cbits_purple_robot_manager_probes_', '') - + try: formatter = importlib.import_module('purple_robot_app.formatters.' + probe_name) except ImportError: formatter = importlib.import_module('purple_robot_app.formatters.probe') - - return formatter.format(probe_name, self.payload) - + + return formatter.format_reading(probe_name, self.payload) + def payload_value(self): return json.loads(self.payload) + @receiver(pre_delete, sender=PurpleRobotReading) -def purplerobotreport_delete(sender, instance, **kwargs): +def purplerobotreading_delete(sender, instance, **kwargs): instance.attachment.delete(False) - + + EXPORT_JOB_STATE_CHOICES = ( ('pending', 'Pending'), ('processing', 'Processing'), @@ -836,6 +1142,7 @@ def purplerobotreport_delete(sender, instance, **kwargs): ('error', 'Error'), ) + class PurpleRobotExportJob(models.Model): probes = models.TextField(max_length=8196, null=True, blank=True) users = models.TextField(max_length=8196, null=True, blank=True) @@ -845,12 +1152,13 @@ class PurpleRobotExportJob(models.Model): export_file = models.FileField(blank=True, upload_to='export_files') destination = models.EmailField(null=True, blank=True) - + state = models.CharField(max_length=512, choices=EXPORT_JOB_STATE_CHOICES, default='pending') - + def export_file_url(self): return reverse('fetch_export_file', args=[str(self.pk)]) + @receiver(pre_delete, sender=PurpleRobotExportJob) def purplerobotexportjob_delete(sender, instance, **kwargs): instance.export_file.delete(False) @@ -866,10 +1174,12 @@ class PurpleRobotReport(models.Model): def __unicode__(self): return string.split(self.probe, '.')[-1] + @receiver(pre_delete, sender=PurpleRobotReport) def purplerobotreport_delete(sender, instance, **kwargs): instance.report_file.delete(False) + class PurpleRobotTest(models.Model): active = models.BooleanField(default=False) slug = models.SlugField(unique=True) @@ -877,118 +1187,112 @@ class PurpleRobotTest(models.Model): user_id = models.CharField(max_length=1024) frequency = models.FloatField(default=1.0) report = models.TextField(default='{}') - + last_updated = models.DateTimeField() - + def updated_ago(self): delta = timezone.now() - self.last_updated - + return delta.total_seconds() - + def update(self, days=1): report = json.loads(self.report) - + report_end = time.time() report_start = report_end - (days * 24 * 60 * 60) - + date_start = pytz.utc.localize(datetime.datetime.utcfromtimestamp(report_start)) date_end = pytz.utc.localize(datetime.datetime.utcfromtimestamp(report_end)) - + batteries = [] - + battery_probe = 'edu.northwestern.cbits.purple_robot_manager.probes.builtin.BatteryProbe' - - battery_readings = PurpleRobotReading.objects.filter(logged__gte=date_start, \ - logged__lte=date_end, \ - probe=battery_probe, \ + + battery_readings = PurpleRobotReading.objects.filter(logged__gte=date_start, + logged__lte=date_end, + probe=battery_probe, user_id=self.user_id).order_by('logged') - + for battery_reading in battery_readings: payload = json.loads(battery_reading.payload) - - reading = [ payload['TIMESTAMP'], payload['level'] ] - + reading = [payload['TIMESTAMP'], payload['level']] batteries.append(reading) - batteries = [b for b in batteries if (b[0] >= report_start and b[0] <= report_end)] + batteries = [b for b in batteries if b[0] >= report_start and b[0] <= report_end] batteries.append([report_start, 0]) batteries.append([report_end, 0]) batteries.sort(key=lambda reading: reading[0]) - + report['battery'] = batteries - + pending_files = [] - + health_probe = 'edu.northwestern.cbits.purple_robot_manager.probes.builtin.RobotHealthProbe' - - health_readings = PurpleRobotReading.objects.filter(logged__gte=date_start, \ - logged__lte=date_end, \ - probe=health_probe, \ + + health_readings = PurpleRobotReading.objects.filter(logged__gte=date_start, + logged__lte=date_end, + probe=health_probe, user_id=self.user_id).order_by('logged') - + for health_reading in health_readings: payload = json.loads(health_reading.payload) - - reading = [ payload['TIMESTAMP'], payload['PENDING_COUNT'], payload['ACTIVE_RUNTIME'] ] - + reading = [payload['TIMESTAMP'], payload['PENDING_COUNT'], payload['ACTIVE_RUNTIME']] pending_files.append(reading) - pending_files = [p for p in pending_files if (p[0] >= report_start and p[0] <= report_end)] + pending_files = [p for p in pending_files if p[0] >= report_start and p[0] <= report_end] pending_files.append([report_start, 0, 0]) pending_files.append([report_end, 0, 0]) pending_files.sort(key=lambda reading: reading[0]) - + report['pending_files'] = pending_files - - if ('target' in report) == False: + + if ('target' in report) is False: report['target'] = [] timestamps = [] - + target_readings = PurpleRobotReading.objects.filter(probe=self.probe, user_id=self.user_id, logged__gte=date_start).order_by('logged') - + total_readings = target_readings.count() start_index = 0 while start_index < total_readings: end_index = start_index + 100 - + for reading in target_readings[start_index:end_index]: payload = json.loads(reading.payload) - + if 'EVENT_TIMESTAMP' in payload: - for ts in payload['EVENT_TIMESTAMP']: - if ts > 1000000000000: - ts = ts / 1000 - - if ts >= report_start and ts <= report_end: - timestamps.append(ts) + for timestamp in payload['EVENT_TIMESTAMP']: + if timestamp > 1000000000000: + timestamp = timestamp / 1000 + + if timestamp >= report_start and timestamp <= report_end: + timestamps.append(timestamp) elif 'SENSOR_TIMESTAMP' in payload: - sensor_time = payload['TIMESTAMP'] - - for ts in payload['SENSOR_TIMESTAMP']: - if ts > 1000000000000: - ts = ts / 1000 - - if ts >= report_start and ts <= report_end: - timestamps.append(ts) + for timestamp in payload['SENSOR_TIMESTAMP']: + if timestamp > 1000000000000: + timestamp = timestamp / 1000 + + if timestamp >= report_start and timestamp <= report_end: + timestamps.append(timestamp) else: - ts = payload['TIMESTAMP'] - - if ts >= report_start and ts <= report_end: - timestamps.append(ts) - + timestamp = payload['TIMESTAMP'] + + if timestamp >= report_start and timestamp <= report_end: + timestamps.append(timestamp) + start_index = end_index - + timestamps.sort() start = report_start end = start + (60 * 15) - - counts = [ [start, 0] ] - + + counts = [[start, 0]] + count = 0 - + for timestamp in timestamps: if timestamp < report_start or timestamp > report_end: pass @@ -1000,165 +1304,164 @@ def update(self, days=1): start = end end = start + (60 * 15) count = 0 - + count += 1 counts.append([start, count]) counts.append([report_end, None]) report['target'] = counts - + self.report = json.dumps(report, indent=1) self.last_updated = timezone.now() self.save() - - + def average_frequency(self): report = json.loads(self.report) - + try: readings = report['target'] - + count = 0.0 - + for reading in readings: if reading[1] is not None: count += reading[1] - + first = float(readings[0][0]) last = float(readings[-1][0]) - + return float(count / (last - first)) except KeyError: return -1 - + def passes(self): return self.average_frequency() > self.frequency def max_gap_size(self): report = json.loads(self.report) - + if 'target' in report: readings = report['target'] - + timestamps = [] - + for reading in readings: timestamps.append(reading[0]) - + if len(timestamps) <= 1: return 0 - + timestamps.sort() - + max_gap = 0 - + for i in range(0, len(timestamps) - 1): one = timestamps[i] two = timestamps[i + 1] - + gap = two - one - + if gap > max_gap: max_gap = gap - + return max_gap - + return -1 - + def frequency_graph_json(self, indent=0): report = json.loads(self.report) - + output = [] - + if 'target' in report: timestamps = report['target'] - + for timestamp in timestamps: if len(timestamp) > 1: - output.append({ 'x': timestamp[0], 'y': timestamp[1] }) + output.append({'x': timestamp[0], 'y': timestamp[1]}) else: - output.append({ 'x': timestamp[0], 'y': None }) - + output.append({'x': timestamp[0], 'y': None}) + return json.dumps(output, indent=indent) - + return '[]' def battery_graph_json(self, indent=0): report = json.loads(self.report) - + now = time.time() start = now - (24 * 60 * 60) - - measurements = [ { 'x': start, 'y': None }] - + + measurements = [{'x': start, 'y': None}] + for record in report['battery']: timestamp = record[0] - + if timestamp < start: pass elif timestamp > now: pass else: - measurements.append({ 'x': timestamp, 'y': record[1] }) + measurements.append({'x': timestamp, 'y': record[1]}) - measurements.append({ 'x': now, 'y': None }) + measurements.append({'x': now, 'y': None}) return json.dumps(measurements, indent=indent) def pending_files_graph_json(self, indent=0): report = json.loads(self.report) - + now = time.time() start = now - (24 * 60 * 60) - - measurements = [ { 'x': start, 'y': None }] - + + measurements = [{'x': start, 'y': None}] + for record in report['pending_files']: timestamp = record[0] - + if timestamp < start: pass elif timestamp > now: pass else: - measurements.append({ 'x': timestamp, 'y': record[1] }) + measurements.append({'x': timestamp, 'y': record[1]}) - measurements.append({ 'x': now, 'y': None }) + measurements.append({'x': now, 'y': None}) return json.dumps(measurements, indent=indent) - + def last_recorded_sample(self): report = json.loads(self.report) - + if 'target' in report: readings = report['target'] - + timestamps = [] - + for reading in readings: timestamps.append(reading[0]) - + if len(timestamps) > 0: return datetime.datetime.fromtimestamp(timestamps[-1]) - + return None - + def probe_name(self): - return string.replace(self.probe, 'edu.northwestern.cbits.purple_robot_manager.probes.', '') - + return string.replace(self.probe, 'edu.northwestern.cbits.purple_robot_manager.probes.', '') + class PurpleRobotAlert(models.Model): severity = models.IntegerField(default=0) message = models.CharField(max_length=2048) tags = models.CharField(max_length=2048, null=True, blank=True) - + action_url = models.URLField(max_length=1024, null=True, blank=True) - + probe = models.CharField(max_length=1024, null=True, blank=True) user_id = models.CharField(max_length=1024, null=True, blank=True) generated = models.DateTimeField() dismissed = models.DateTimeField(null=True, blank=True) - + manually_dismissed = models.BooleanField(default=False) diff --git a/performance.py b/performance.py new file mode 100644 index 0000000..508c233 --- /dev/null +++ b/performance.py @@ -0,0 +1,95 @@ +# pylint: disable=line-too-long, bare-except + +import arrow +import datetime +# import msgpack +import os + +import cPickle as pickle + +from django.conf import settings +from django.utils import timezone + + +def append_performance_sample(user, item, detail_date=timezone.now(), value=''): + os.umask(000) + + today = datetime.date.today() + + folder = settings.MEDIA_ROOT + '/purple_robot_analytics/' + user + '/' + today.isoformat() + + if not os.path.exists(folder): + os.makedirs(folder) + + item_path = folder + '/' + item + '.pickle' + + content = {} + + if os.path.exists(item_path): + pickle_file = open(item_path, 'rb') + + try: + content = pickle.load(pickle_file) + except: + pass + + pickle_file.close() + + content[detail_date.isoformat()] = value + + pickle.dump(content, open(item_path, 'wb')) + + +def fetch_performance_samples(user, item, start=None, end=None): + if end is None: + end = timezone.now() + + if start is None: + today = datetime.date.today() + start = datetime.datetime(today.year, today.month, today.day, 0, 0, 0, 0, tzinfo=end.tzinfo) + + start_date = start.date() + end_date = end.date() + + samples = [] + + while start_date <= end_date: + item_path = settings.MEDIA_ROOT + '/purple_robot_analytics/' + user + '/' + start_date.isoformat() + '/' + item + '.pickle' + + if os.path.exists(item_path): + pickle_file = open(item_path, 'rb') + + content = pickle.load(pickle_file) + + for key, value in content.iteritems(): + sample_date = arrow.get(key).datetime + + if sample_date >= start and sample_date <= end: + value['sample_date'] = key + samples.append(value) + + pickle_file.close() + + start_date += datetime.timedelta(days=1) + + samples.sort(key=lambda sample: sample['sample_date']) + + return samples + + +def fetch_performance_users(): + items_path = settings.MEDIA_ROOT + '/purple_robot_analytics/' + + users = {} + + for item in os.listdir(items_path): + if item != 'system': + dates = os.listdir(items_path + '/' + item) + + last = sorted(dates, reverse=True)[0] + + toks = last.split('-') + + users[item] = datetime.date(int(toks[0]), int(toks[1]), int(toks[2])) + + return users diff --git a/requirements.txt b/requirements.txt index 69b9d32..92338da 100644 --- a/requirements.txt +++ b/requirements.txt @@ -1,11 +1,14 @@ +BeautifulSoup==3.2.1 Django==1.6.11 PyYAML==3.11 South==1.0 argparse==1.2.1 +arrow==0.7.0 astroid==1.3.6 datadiff==1.1.6 django-debug-toolbar==1.3.2 dodgy==0.1.7 +hockeyapp==0.4.0 logilab-common==0.63.2 mccabe==0.3 msgpack-python==0.4.6 @@ -26,7 +29,7 @@ pytz==2014.10 requests==2.6.0 requirements-detector==0.4 setoptconf==0.2.0 +sexpdata==0.0.3 six==1.9.0 sqlparse==0.1.15 wsgiref==0.1.2 -sexpdata diff --git a/static/bootstrap3/css/bootstrap-datepicker.css b/static/bootstrap3/css/bootstrap-datepicker.css new file mode 100755 index 0000000..5fc431d --- /dev/null +++ b/static/bootstrap3/css/bootstrap-datepicker.css @@ -0,0 +1,472 @@ +/*! + * Datepicker for Bootstrap v1.5.0-dev (https://github.com/eternicode/bootstrap-datepicker) + * + * Copyright 2012 Stefan Petre + * Improvements by Andrew Rowls + * Licensed under the Apache License v2.0 (http://www.apache.org/licenses/LICENSE-2.0) + */ +.datepicker { + padding: 4px; + -webkit-border-radius: 4px; + -moz-border-radius: 4px; + border-radius: 4px; + direction: ltr; +} +.datepicker-inline { + width: 220px; +} +.datepicker.datepicker-rtl { + direction: rtl; +} +.datepicker.datepicker-rtl table tr td span { + float: right; +} +.datepicker-dropdown { + top: 0; + left: 0; +} +.datepicker-dropdown:before { + content: ''; + display: inline-block; + border-left: 7px solid transparent; + border-right: 7px solid transparent; + border-bottom: 7px solid #999999; + border-top: 0; + border-bottom-color: rgba(0, 0, 0, 0.2); + position: absolute; +} +.datepicker-dropdown:after { + content: ''; + display: inline-block; + border-left: 6px solid transparent; + border-right: 6px solid transparent; + border-bottom: 6px solid #ffffff; + border-top: 0; + position: absolute; +} +.datepicker-dropdown.datepicker-orient-left:before { + left: 6px; +} +.datepicker-dropdown.datepicker-orient-left:after { + left: 7px; +} +.datepicker-dropdown.datepicker-orient-right:before { + right: 6px; +} +.datepicker-dropdown.datepicker-orient-right:after { + right: 7px; +} +.datepicker-dropdown.datepicker-orient-bottom:before { + top: -7px; +} +.datepicker-dropdown.datepicker-orient-bottom:after { + top: -6px; +} +.datepicker-dropdown.datepicker-orient-top:before { + bottom: -7px; + border-bottom: 0; + border-top: 7px solid #999999; +} +.datepicker-dropdown.datepicker-orient-top:after { + bottom: -6px; + border-bottom: 0; + border-top: 6px solid #ffffff; +} +.datepicker > div { + display: none; +} +.datepicker table { + margin: 0; + -webkit-touch-callout: none; + -webkit-user-select: none; + -khtml-user-select: none; + -moz-user-select: none; + -ms-user-select: none; + user-select: none; +} +.datepicker td, +.datepicker th { + text-align: center; + width: 20px; + height: 20px; + -webkit-border-radius: 4px; + -moz-border-radius: 4px; + border-radius: 4px; + border: none; +} +.table-striped .datepicker table tr td, +.table-striped .datepicker table tr th { + background-color: transparent; +} +.datepicker table tr td.day:hover, +.datepicker table tr td.day.focused { + background: #eeeeee; + cursor: pointer; +} +.datepicker table tr td.old, +.datepicker table tr td.new { + color: #999999; +} +.datepicker table tr td.disabled, +.datepicker table tr td.disabled:hover { + background: none; + color: #999999; + cursor: default; +} +.datepicker table tr td.highlighted { + background: #d9edf7; + border-radius: 0; +} +.datepicker table tr td.today, +.datepicker table tr td.today:hover, +.datepicker table tr td.today.disabled, +.datepicker table tr td.today.disabled:hover { + background-color: #fde19a; + background-image: -moz-linear-gradient(to bottom, #fdd49a, #fdf59a); + background-image: -ms-linear-gradient(to bottom, #fdd49a, #fdf59a); + background-image: -webkit-gradient(linear, 0 0, 0 100%, from(#fdd49a), to(#fdf59a)); + background-image: -webkit-linear-gradient(to bottom, #fdd49a, #fdf59a); + background-image: -o-linear-gradient(to bottom, #fdd49a, #fdf59a); + background-image: linear-gradient(to bottom, #fdd49a, #fdf59a); + background-repeat: repeat-x; + filter: progid:DXImageTransform.Microsoft.gradient(startColorstr='#fdd49a', endColorstr='#fdf59a', GradientType=0); + border-color: #fdf59a #fdf59a #fbed50; + border-color: rgba(0, 0, 0, 0.1) rgba(0, 0, 0, 0.1) rgba(0, 0, 0, 0.25); + filter: progid:DXImageTransform.Microsoft.gradient(enabled=false); + color: #000; +} +.datepicker table tr td.today:hover, +.datepicker table tr td.today:hover:hover, +.datepicker table tr td.today.disabled:hover, +.datepicker table tr td.today.disabled:hover:hover, +.datepicker table tr td.today:active, +.datepicker table tr td.today:hover:active, +.datepicker table tr td.today.disabled:active, +.datepicker table tr td.today.disabled:hover:active, +.datepicker table tr td.today.active, +.datepicker table tr td.today:hover.active, +.datepicker table tr td.today.disabled.active, +.datepicker table tr td.today.disabled:hover.active, +.datepicker table tr td.today.disabled, +.datepicker table tr td.today:hover.disabled, +.datepicker table tr td.today.disabled.disabled, +.datepicker table tr td.today.disabled:hover.disabled, +.datepicker table tr td.today[disabled], +.datepicker table tr td.today:hover[disabled], +.datepicker table tr td.today.disabled[disabled], +.datepicker table tr td.today.disabled:hover[disabled] { + background-color: #fdf59a; +} +.datepicker table tr td.today:active, +.datepicker table tr td.today:hover:active, +.datepicker table tr td.today.disabled:active, +.datepicker table tr td.today.disabled:hover:active, +.datepicker table tr td.today.active, +.datepicker table tr td.today:hover.active, +.datepicker table tr td.today.disabled.active, +.datepicker table tr td.today.disabled:hover.active { + background-color: #fbf069 \9; +} +.datepicker table tr td.today:hover:hover { + color: #000; +} +.datepicker table tr td.today.active:hover { + color: #fff; +} +.datepicker table tr td.range, +.datepicker table tr td.range:hover, +.datepicker table tr td.range.disabled, +.datepicker table tr td.range.disabled:hover { + background: #eeeeee; + -webkit-border-radius: 0; + -moz-border-radius: 0; + border-radius: 0; +} +.datepicker table tr td.range.today, +.datepicker table tr td.range.today:hover, +.datepicker table tr td.range.today.disabled, +.datepicker table tr td.range.today.disabled:hover { + background-color: #f3d17a; + background-image: -moz-linear-gradient(to bottom, #f3c17a, #f3e97a); + background-image: -ms-linear-gradient(to bottom, #f3c17a, #f3e97a); + background-image: -webkit-gradient(linear, 0 0, 0 100%, from(#f3c17a), to(#f3e97a)); + background-image: -webkit-linear-gradient(to bottom, #f3c17a, #f3e97a); + background-image: -o-linear-gradient(to bottom, #f3c17a, #f3e97a); + background-image: linear-gradient(to bottom, #f3c17a, #f3e97a); + background-repeat: repeat-x; + filter: progid:DXImageTransform.Microsoft.gradient(startColorstr='#f3c17a', endColorstr='#f3e97a', GradientType=0); + border-color: #f3e97a #f3e97a #edde34; + border-color: rgba(0, 0, 0, 0.1) rgba(0, 0, 0, 0.1) rgba(0, 0, 0, 0.25); + filter: progid:DXImageTransform.Microsoft.gradient(enabled=false); + -webkit-border-radius: 0; + -moz-border-radius: 0; + border-radius: 0; +} +.datepicker table tr td.range.today:hover, +.datepicker table tr td.range.today:hover:hover, +.datepicker table tr td.range.today.disabled:hover, +.datepicker table tr td.range.today.disabled:hover:hover, +.datepicker table tr td.range.today:active, +.datepicker table tr td.range.today:hover:active, +.datepicker table tr td.range.today.disabled:active, +.datepicker table tr td.range.today.disabled:hover:active, +.datepicker table tr td.range.today.active, +.datepicker table tr td.range.today:hover.active, +.datepicker table tr td.range.today.disabled.active, +.datepicker table tr td.range.today.disabled:hover.active, +.datepicker table tr td.range.today.disabled, +.datepicker table tr td.range.today:hover.disabled, +.datepicker table tr td.range.today.disabled.disabled, +.datepicker table tr td.range.today.disabled:hover.disabled, +.datepicker table tr td.range.today[disabled], +.datepicker table tr td.range.today:hover[disabled], +.datepicker table tr td.range.today.disabled[disabled], +.datepicker table tr td.range.today.disabled:hover[disabled] { + background-color: #f3e97a; +} +.datepicker table tr td.range.today:active, +.datepicker table tr td.range.today:hover:active, +.datepicker table tr td.range.today.disabled:active, +.datepicker table tr td.range.today.disabled:hover:active, +.datepicker table tr td.range.today.active, +.datepicker table tr td.range.today:hover.active, +.datepicker table tr td.range.today.disabled.active, +.datepicker table tr td.range.today.disabled:hover.active { + background-color: #efe24b \9; +} +.datepicker table tr td.selected, +.datepicker table tr td.selected:hover, +.datepicker table tr td.selected.disabled, +.datepicker table tr td.selected.disabled:hover { + background-color: #9e9e9e; + background-image: -moz-linear-gradient(to bottom, #b3b3b3, #808080); + background-image: -ms-linear-gradient(to bottom, #b3b3b3, #808080); + background-image: -webkit-gradient(linear, 0 0, 0 100%, from(#b3b3b3), to(#808080)); + background-image: -webkit-linear-gradient(to bottom, #b3b3b3, #808080); + background-image: -o-linear-gradient(to bottom, #b3b3b3, #808080); + background-image: linear-gradient(to bottom, #b3b3b3, #808080); + background-repeat: repeat-x; + filter: progid:DXImageTransform.Microsoft.gradient(startColorstr='#b3b3b3', endColorstr='#808080', GradientType=0); + border-color: #808080 #808080 #595959; + border-color: rgba(0, 0, 0, 0.1) rgba(0, 0, 0, 0.1) rgba(0, 0, 0, 0.25); + filter: progid:DXImageTransform.Microsoft.gradient(enabled=false); + color: #fff; + text-shadow: 0 -1px 0 rgba(0, 0, 0, 0.25); +} +.datepicker table tr td.selected:hover, +.datepicker table tr td.selected:hover:hover, +.datepicker table tr td.selected.disabled:hover, +.datepicker table tr td.selected.disabled:hover:hover, +.datepicker table tr td.selected:active, +.datepicker table tr td.selected:hover:active, +.datepicker table tr td.selected.disabled:active, +.datepicker table tr td.selected.disabled:hover:active, +.datepicker table tr td.selected.active, +.datepicker table tr td.selected:hover.active, +.datepicker table tr td.selected.disabled.active, +.datepicker table tr td.selected.disabled:hover.active, +.datepicker table tr td.selected.disabled, +.datepicker table tr td.selected:hover.disabled, +.datepicker table tr td.selected.disabled.disabled, +.datepicker table tr td.selected.disabled:hover.disabled, +.datepicker table tr td.selected[disabled], +.datepicker table tr td.selected:hover[disabled], +.datepicker table tr td.selected.disabled[disabled], +.datepicker table tr td.selected.disabled:hover[disabled] { + background-color: #808080; +} +.datepicker table tr td.selected:active, +.datepicker table tr td.selected:hover:active, +.datepicker table tr td.selected.disabled:active, +.datepicker table tr td.selected.disabled:hover:active, +.datepicker table tr td.selected.active, +.datepicker table tr td.selected:hover.active, +.datepicker table tr td.selected.disabled.active, +.datepicker table tr td.selected.disabled:hover.active { + background-color: #666666 \9; +} +.datepicker table tr td.active, +.datepicker table tr td.active:hover, +.datepicker table tr td.active.disabled, +.datepicker table tr td.active.disabled:hover { + background-color: #006dcc; + background-image: -moz-linear-gradient(to bottom, #0088cc, #0044cc); + background-image: -ms-linear-gradient(to bottom, #0088cc, #0044cc); + background-image: -webkit-gradient(linear, 0 0, 0 100%, from(#0088cc), to(#0044cc)); + background-image: -webkit-linear-gradient(to bottom, #0088cc, #0044cc); + background-image: -o-linear-gradient(to bottom, #0088cc, #0044cc); + background-image: linear-gradient(to bottom, #0088cc, #0044cc); + background-repeat: repeat-x; + filter: progid:DXImageTransform.Microsoft.gradient(startColorstr='#0088cc', endColorstr='#0044cc', GradientType=0); + border-color: #0044cc #0044cc #002a80; + border-color: rgba(0, 0, 0, 0.1) rgba(0, 0, 0, 0.1) rgba(0, 0, 0, 0.25); + filter: progid:DXImageTransform.Microsoft.gradient(enabled=false); + color: #fff; + text-shadow: 0 -1px 0 rgba(0, 0, 0, 0.25); +} +.datepicker table tr td.active:hover, +.datepicker table tr td.active:hover:hover, +.datepicker table tr td.active.disabled:hover, +.datepicker table tr td.active.disabled:hover:hover, +.datepicker table tr td.active:active, +.datepicker table tr td.active:hover:active, +.datepicker table tr td.active.disabled:active, +.datepicker table tr td.active.disabled:hover:active, +.datepicker table tr td.active.active, +.datepicker table tr td.active:hover.active, +.datepicker table tr td.active.disabled.active, +.datepicker table tr td.active.disabled:hover.active, +.datepicker table tr td.active.disabled, +.datepicker table tr td.active:hover.disabled, +.datepicker table tr td.active.disabled.disabled, +.datepicker table tr td.active.disabled:hover.disabled, +.datepicker table tr td.active[disabled], +.datepicker table tr td.active:hover[disabled], +.datepicker table tr td.active.disabled[disabled], +.datepicker table tr td.active.disabled:hover[disabled] { + background-color: #0044cc; +} +.datepicker table tr td.active:active, +.datepicker table tr td.active:hover:active, +.datepicker table tr td.active.disabled:active, +.datepicker table tr td.active.disabled:hover:active, +.datepicker table tr td.active.active, +.datepicker table tr td.active:hover.active, +.datepicker table tr td.active.disabled.active, +.datepicker table tr td.active.disabled:hover.active { + background-color: #003399 \9; +} +.datepicker table tr td span { + display: block; + width: 23%; + height: 54px; + line-height: 54px; + float: left; + margin: 1%; + cursor: pointer; + -webkit-border-radius: 4px; + -moz-border-radius: 4px; + border-radius: 4px; +} +.datepicker table tr td span:hover { + background: #eeeeee; +} +.datepicker table tr td span.disabled, +.datepicker table tr td span.disabled:hover { + background: none; + color: #999999; + cursor: default; +} +.datepicker table tr td span.active, +.datepicker table tr td span.active:hover, +.datepicker table tr td span.active.disabled, +.datepicker table tr td span.active.disabled:hover { + background-color: #006dcc; + background-image: -moz-linear-gradient(to bottom, #0088cc, #0044cc); + background-image: -ms-linear-gradient(to bottom, #0088cc, #0044cc); + background-image: -webkit-gradient(linear, 0 0, 0 100%, from(#0088cc), to(#0044cc)); + background-image: -webkit-linear-gradient(to bottom, #0088cc, #0044cc); + background-image: -o-linear-gradient(to bottom, #0088cc, #0044cc); + background-image: linear-gradient(to bottom, #0088cc, #0044cc); + background-repeat: repeat-x; + filter: progid:DXImageTransform.Microsoft.gradient(startColorstr='#0088cc', endColorstr='#0044cc', GradientType=0); + border-color: #0044cc #0044cc #002a80; + border-color: rgba(0, 0, 0, 0.1) rgba(0, 0, 0, 0.1) rgba(0, 0, 0, 0.25); + filter: progid:DXImageTransform.Microsoft.gradient(enabled=false); + color: #fff; + text-shadow: 0 -1px 0 rgba(0, 0, 0, 0.25); +} +.datepicker table tr td span.active:hover, +.datepicker table tr td span.active:hover:hover, +.datepicker table tr td span.active.disabled:hover, +.datepicker table tr td span.active.disabled:hover:hover, +.datepicker table tr td span.active:active, +.datepicker table tr td span.active:hover:active, +.datepicker table tr td span.active.disabled:active, +.datepicker table tr td span.active.disabled:hover:active, +.datepicker table tr td span.active.active, +.datepicker table tr td span.active:hover.active, +.datepicker table tr td span.active.disabled.active, +.datepicker table tr td span.active.disabled:hover.active, +.datepicker table tr td span.active.disabled, +.datepicker table tr td span.active:hover.disabled, +.datepicker table tr td span.active.disabled.disabled, +.datepicker table tr td span.active.disabled:hover.disabled, +.datepicker table tr td span.active[disabled], +.datepicker table tr td span.active:hover[disabled], +.datepicker table tr td span.active.disabled[disabled], +.datepicker table tr td span.active.disabled:hover[disabled] { + background-color: #0044cc; +} +.datepicker table tr td span.active:active, +.datepicker table tr td span.active:hover:active, +.datepicker table tr td span.active.disabled:active, +.datepicker table tr td span.active.disabled:hover:active, +.datepicker table tr td span.active.active, +.datepicker table tr td span.active:hover.active, +.datepicker table tr td span.active.disabled.active, +.datepicker table tr td span.active.disabled:hover.active { + background-color: #003399 \9; +} +.datepicker table tr td span.old, +.datepicker table tr td span.new { + color: #999999; +} +.datepicker .datepicker-switch { + width: 145px; +} +.datepicker .datepicker-switch, +.datepicker .prev, +.datepicker .next, +.datepicker tfoot tr th { + cursor: pointer; +} +.datepicker .datepicker-switch:hover, +.datepicker .prev:hover, +.datepicker .next:hover, +.datepicker tfoot tr th:hover { + background: #eeeeee; +} +.datepicker .cw { + font-size: 10px; + width: 12px; + padding: 0 2px 0 5px; + vertical-align: middle; +} +.input-append.date .add-on, +.input-prepend.date .add-on { + cursor: pointer; +} +.input-append.date .add-on i, +.input-prepend.date .add-on i { + margin-top: 3px; +} +.input-daterange input { + text-align: center; +} +.input-daterange input:first-child { + -webkit-border-radius: 3px 0 0 3px; + -moz-border-radius: 3px 0 0 3px; + border-radius: 3px 0 0 3px; +} +.input-daterange input:last-child { + -webkit-border-radius: 0 3px 3px 0; + -moz-border-radius: 0 3px 3px 0; + border-radius: 0 3px 3px 0; +} +.input-daterange .add-on { + display: inline-block; + width: auto; + min-width: 16px; + height: 18px; + padding: 4px 5px; + font-weight: normal; + line-height: 18px; + text-align: center; + text-shadow: 0 1px 0 #ffffff; + vertical-align: middle; + background-color: #eeeeee; + border: 1px solid #ccc; + margin-left: -5px; + margin-right: -5px; +} diff --git a/static/bootstrap3/css/bootstrap-datepicker.min.css b/static/bootstrap3/css/bootstrap-datepicker.min.css new file mode 100755 index 0000000..b342245 --- /dev/null +++ b/static/bootstrap3/css/bootstrap-datepicker.min.css @@ -0,0 +1,8 @@ +/*! + * Datepicker for Bootstrap v1.5.0-dev (https://github.com/eternicode/bootstrap-datepicker) + * + * Copyright 2012 Stefan Petre + * Improvements by Andrew Rowls + * Licensed under the Apache License v2.0 (http://www.apache.org/licenses/LICENSE-2.0) + */ +.datepicker{padding:4px;-webkit-border-radius:4px;-moz-border-radius:4px;border-radius:4px;direction:ltr}.datepicker-inline{width:220px}.datepicker.datepicker-rtl{direction:rtl}.datepicker.datepicker-rtl table tr td span{float:right}.datepicker-dropdown{top:0;left:0}.datepicker-dropdown:before{content:'';display:inline-block;border-left:7px solid transparent;border-right:7px solid transparent;border-bottom:7px solid #999;border-top:0;border-bottom-color:rgba(0,0,0,.2);position:absolute}.datepicker-dropdown:after{content:'';display:inline-block;border-left:6px solid transparent;border-right:6px solid transparent;border-bottom:6px solid #fff;border-top:0;position:absolute}.datepicker-dropdown.datepicker-orient-left:before{left:6px}.datepicker-dropdown.datepicker-orient-left:after{left:7px}.datepicker-dropdown.datepicker-orient-right:before{right:6px}.datepicker-dropdown.datepicker-orient-right:after{right:7px}.datepicker-dropdown.datepicker-orient-bottom:before{top:-7px}.datepicker-dropdown.datepicker-orient-bottom:after{top:-6px}.datepicker-dropdown.datepicker-orient-top:before{bottom:-7px;border-bottom:0;border-top:7px solid #999}.datepicker-dropdown.datepicker-orient-top:after{bottom:-6px;border-bottom:0;border-top:6px solid #fff}.datepicker>div{display:none}.datepicker table{margin:0;-webkit-touch-callout:none;-webkit-user-select:none;-khtml-user-select:none;-moz-user-select:none;-ms-user-select:none;user-select:none}.datepicker td,.datepicker th{text-align:center;width:20px;height:20px;-webkit-border-radius:4px;-moz-border-radius:4px;border-radius:4px;border:none}.table-striped .datepicker table tr td,.table-striped .datepicker table tr th{background-color:transparent}.datepicker table tr td.day:hover,.datepicker table tr td.day.focused{background:#eee;cursor:pointer}.datepicker table tr td.old,.datepicker table tr td.new{color:#999}.datepicker table tr td.disabled,.datepicker table tr td.disabled:hover{background:0 0;color:#999;cursor:default}.datepicker table tr td.highlighted{background:#d9edf7;border-radius:0}.datepicker table tr td.today,.datepicker table tr td.today:hover,.datepicker table tr td.today.disabled,.datepicker table tr td.today.disabled:hover{background-color:#fde19a;background-image:-moz-linear-gradient(to bottom,#fdd49a,#fdf59a);background-image:-ms-linear-gradient(to bottom,#fdd49a,#fdf59a);background-image:-webkit-gradient(linear,0 0,0 100%,from(#fdd49a),to(#fdf59a));background-image:-webkit-linear-gradient(to bottom,#fdd49a,#fdf59a);background-image:-o-linear-gradient(to bottom,#fdd49a,#fdf59a);background-image:linear-gradient(to bottom,#fdd49a,#fdf59a);background-repeat:repeat-x;filter:progid:DXImageTransform.Microsoft.gradient(startColorstr='#fdd49a', endColorstr='#fdf59a', GradientType=0);border-color:#fdf59a #fdf59a #fbed50;border-color:rgba(0,0,0,.1) rgba(0,0,0,.1) rgba(0,0,0,.25);filter:progid:DXImageTransform.Microsoft.gradient(enabled=false);color:#000}.datepicker table tr td.today:hover,.datepicker table tr td.today:hover:hover,.datepicker table tr td.today.disabled:hover,.datepicker table tr td.today.disabled:hover:hover,.datepicker table tr td.today:active,.datepicker table tr td.today:hover:active,.datepicker table tr td.today.disabled:active,.datepicker table tr td.today.disabled:hover:active,.datepicker table tr td.today.active,.datepicker table tr td.today:hover.active,.datepicker table tr td.today.disabled.active,.datepicker table tr td.today.disabled:hover.active,.datepicker table tr td.today.disabled,.datepicker table tr td.today:hover.disabled,.datepicker table tr td.today.disabled.disabled,.datepicker table tr td.today.disabled:hover.disabled,.datepicker table tr td.today[disabled],.datepicker table tr td.today:hover[disabled],.datepicker table tr td.today.disabled[disabled],.datepicker table tr td.today.disabled:hover[disabled]{background-color:#fdf59a}.datepicker table tr td.today:active,.datepicker table tr td.today:hover:active,.datepicker table tr td.today.disabled:active,.datepicker table tr td.today.disabled:hover:active,.datepicker table tr td.today.active,.datepicker table tr td.today:hover.active,.datepicker table tr td.today.disabled.active,.datepicker table tr td.today.disabled:hover.active{background-color:#fbf069 \9}.datepicker table tr td.today:hover:hover{color:#000}.datepicker table tr td.today.active:hover{color:#fff}.datepicker table tr td.range,.datepicker table tr td.range:hover,.datepicker table tr td.range.disabled,.datepicker table tr td.range.disabled:hover{background:#eee;-webkit-border-radius:0;-moz-border-radius:0;border-radius:0}.datepicker table tr td.range.today,.datepicker table tr td.range.today:hover,.datepicker table tr td.range.today.disabled,.datepicker table tr td.range.today.disabled:hover{background-color:#f3d17a;background-image:-moz-linear-gradient(to bottom,#f3c17a,#f3e97a);background-image:-ms-linear-gradient(to bottom,#f3c17a,#f3e97a);background-image:-webkit-gradient(linear,0 0,0 100%,from(#f3c17a),to(#f3e97a));background-image:-webkit-linear-gradient(to bottom,#f3c17a,#f3e97a);background-image:-o-linear-gradient(to bottom,#f3c17a,#f3e97a);background-image:linear-gradient(to bottom,#f3c17a,#f3e97a);background-repeat:repeat-x;filter:progid:DXImageTransform.Microsoft.gradient(startColorstr='#f3c17a', endColorstr='#f3e97a', GradientType=0);border-color:#f3e97a #f3e97a #edde34;border-color:rgba(0,0,0,.1) rgba(0,0,0,.1) rgba(0,0,0,.25);filter:progid:DXImageTransform.Microsoft.gradient(enabled=false);-webkit-border-radius:0;-moz-border-radius:0;border-radius:0}.datepicker table tr td.range.today:hover,.datepicker table tr td.range.today:hover:hover,.datepicker table tr td.range.today.disabled:hover,.datepicker table tr td.range.today.disabled:hover:hover,.datepicker table tr td.range.today:active,.datepicker table tr td.range.today:hover:active,.datepicker table tr td.range.today.disabled:active,.datepicker table tr td.range.today.disabled:hover:active,.datepicker table tr td.range.today.active,.datepicker table tr td.range.today:hover.active,.datepicker table tr td.range.today.disabled.active,.datepicker table tr td.range.today.disabled:hover.active,.datepicker table tr td.range.today.disabled,.datepicker table tr td.range.today:hover.disabled,.datepicker table tr td.range.today.disabled.disabled,.datepicker table tr td.range.today.disabled:hover.disabled,.datepicker table tr td.range.today[disabled],.datepicker table tr td.range.today:hover[disabled],.datepicker table tr td.range.today.disabled[disabled],.datepicker table tr td.range.today.disabled:hover[disabled]{background-color:#f3e97a}.datepicker table tr td.range.today:active,.datepicker table tr td.range.today:hover:active,.datepicker table tr td.range.today.disabled:active,.datepicker table tr td.range.today.disabled:hover:active,.datepicker table tr td.range.today.active,.datepicker table tr td.range.today:hover.active,.datepicker table tr td.range.today.disabled.active,.datepicker table tr td.range.today.disabled:hover.active{background-color:#efe24b \9}.datepicker table tr td.selected,.datepicker table tr td.selected:hover,.datepicker table tr td.selected.disabled,.datepicker table tr td.selected.disabled:hover{background-color:#9e9e9e;background-image:-moz-linear-gradient(to bottom,#b3b3b3,gray);background-image:-ms-linear-gradient(to bottom,#b3b3b3,gray);background-image:-webkit-gradient(linear,0 0,0 100%,from(#b3b3b3),to(gray));background-image:-webkit-linear-gradient(to bottom,#b3b3b3,gray);background-image:-o-linear-gradient(to bottom,#b3b3b3,gray);background-image:linear-gradient(to bottom,#b3b3b3,gray);background-repeat:repeat-x;filter:progid:DXImageTransform.Microsoft.gradient(startColorstr='#b3b3b3', endColorstr='#808080', GradientType=0);border-color:gray #808080 #595959;border-color:rgba(0,0,0,.1) rgba(0,0,0,.1) rgba(0,0,0,.25);filter:progid:DXImageTransform.Microsoft.gradient(enabled=false);color:#fff;text-shadow:0 -1px 0 rgba(0,0,0,.25)}.datepicker table tr td.selected:hover,.datepicker table tr td.selected:hover:hover,.datepicker table tr td.selected.disabled:hover,.datepicker table tr td.selected.disabled:hover:hover,.datepicker table tr td.selected:active,.datepicker table tr td.selected:hover:active,.datepicker table tr td.selected.disabled:active,.datepicker table tr td.selected.disabled:hover:active,.datepicker table tr td.selected.active,.datepicker table tr td.selected:hover.active,.datepicker table tr td.selected.disabled.active,.datepicker table tr td.selected.disabled:hover.active,.datepicker table tr td.selected.disabled,.datepicker table tr td.selected:hover.disabled,.datepicker table tr td.selected.disabled.disabled,.datepicker table tr td.selected.disabled:hover.disabled,.datepicker table tr td.selected[disabled],.datepicker table tr td.selected:hover[disabled],.datepicker table tr td.selected.disabled[disabled],.datepicker table tr td.selected.disabled:hover[disabled]{background-color:gray}.datepicker table tr td.selected:active,.datepicker table tr td.selected:hover:active,.datepicker table tr td.selected.disabled:active,.datepicker table tr td.selected.disabled:hover:active,.datepicker table tr td.selected.active,.datepicker table tr td.selected:hover.active,.datepicker table tr td.selected.disabled.active,.datepicker table tr td.selected.disabled:hover.active{background-color:#666 \9}.datepicker table tr td.active,.datepicker table tr td.active:hover,.datepicker table tr td.active.disabled,.datepicker table tr td.active.disabled:hover{background-color:#006dcc;background-image:-moz-linear-gradient(to bottom,#08c,#04c);background-image:-ms-linear-gradient(to bottom,#08c,#04c);background-image:-webkit-gradient(linear,0 0,0 100%,from(#08c),to(#04c));background-image:-webkit-linear-gradient(to bottom,#08c,#04c);background-image:-o-linear-gradient(to bottom,#08c,#04c);background-image:linear-gradient(to bottom,#08c,#04c);background-repeat:repeat-x;filter:progid:DXImageTransform.Microsoft.gradient(startColorstr='#0088cc', endColorstr='#0044cc', GradientType=0);border-color:#04c #04c #002a80;border-color:rgba(0,0,0,.1) rgba(0,0,0,.1) rgba(0,0,0,.25);filter:progid:DXImageTransform.Microsoft.gradient(enabled=false);color:#fff;text-shadow:0 -1px 0 rgba(0,0,0,.25)}.datepicker table tr td.active:hover,.datepicker table tr td.active:hover:hover,.datepicker table tr td.active.disabled:hover,.datepicker table tr td.active.disabled:hover:hover,.datepicker table tr td.active:active,.datepicker table tr td.active:hover:active,.datepicker table tr td.active.disabled:active,.datepicker table tr td.active.disabled:hover:active,.datepicker table tr td.active.active,.datepicker table tr td.active:hover.active,.datepicker table tr td.active.disabled.active,.datepicker table tr td.active.disabled:hover.active,.datepicker table tr td.active.disabled,.datepicker table tr td.active:hover.disabled,.datepicker table tr td.active.disabled.disabled,.datepicker table tr td.active.disabled:hover.disabled,.datepicker table tr td.active[disabled],.datepicker table tr td.active:hover[disabled],.datepicker table tr td.active.disabled[disabled],.datepicker table tr td.active.disabled:hover[disabled]{background-color:#04c}.datepicker table tr td.active:active,.datepicker table tr td.active:hover:active,.datepicker table tr td.active.disabled:active,.datepicker table tr td.active.disabled:hover:active,.datepicker table tr td.active.active,.datepicker table tr td.active:hover.active,.datepicker table tr td.active.disabled.active,.datepicker table tr td.active.disabled:hover.active{background-color:#039 \9}.datepicker table tr td span{display:block;width:23%;height:54px;line-height:54px;float:left;margin:1%;cursor:pointer;-webkit-border-radius:4px;-moz-border-radius:4px;border-radius:4px}.datepicker table tr td span:hover{background:#eee}.datepicker table tr td span.disabled,.datepicker table tr td span.disabled:hover{background:0 0;color:#999;cursor:default}.datepicker table tr td span.active,.datepicker table tr td span.active:hover,.datepicker table tr td span.active.disabled,.datepicker table tr td span.active.disabled:hover{background-color:#006dcc;background-image:-moz-linear-gradient(to bottom,#08c,#04c);background-image:-ms-linear-gradient(to bottom,#08c,#04c);background-image:-webkit-gradient(linear,0 0,0 100%,from(#08c),to(#04c));background-image:-webkit-linear-gradient(to bottom,#08c,#04c);background-image:-o-linear-gradient(to bottom,#08c,#04c);background-image:linear-gradient(to bottom,#08c,#04c);background-repeat:repeat-x;filter:progid:DXImageTransform.Microsoft.gradient(startColorstr='#0088cc', endColorstr='#0044cc', GradientType=0);border-color:#04c #04c #002a80;border-color:rgba(0,0,0,.1) rgba(0,0,0,.1) rgba(0,0,0,.25);filter:progid:DXImageTransform.Microsoft.gradient(enabled=false);color:#fff;text-shadow:0 -1px 0 rgba(0,0,0,.25)}.datepicker table tr td span.active:hover,.datepicker table tr td span.active:hover:hover,.datepicker table tr td span.active.disabled:hover,.datepicker table tr td span.active.disabled:hover:hover,.datepicker table tr td span.active:active,.datepicker table tr td span.active:hover:active,.datepicker table tr td span.active.disabled:active,.datepicker table tr td span.active.disabled:hover:active,.datepicker table tr td span.active.active,.datepicker table tr td span.active:hover.active,.datepicker table tr td span.active.disabled.active,.datepicker table tr td span.active.disabled:hover.active,.datepicker table tr td span.active.disabled,.datepicker table tr td span.active:hover.disabled,.datepicker table tr td span.active.disabled.disabled,.datepicker table tr td span.active.disabled:hover.disabled,.datepicker table tr td span.active[disabled],.datepicker table tr td span.active:hover[disabled],.datepicker table tr td span.active.disabled[disabled],.datepicker table tr td span.active.disabled:hover[disabled]{background-color:#04c}.datepicker table tr td span.active:active,.datepicker table tr td span.active:hover:active,.datepicker table tr td span.active.disabled:active,.datepicker table tr td span.active.disabled:hover:active,.datepicker table tr td span.active.active,.datepicker table tr td span.active:hover.active,.datepicker table tr td span.active.disabled.active,.datepicker table tr td span.active.disabled:hover.active{background-color:#039 \9}.datepicker table tr td span.old,.datepicker table tr td span.new{color:#999}.datepicker .datepicker-switch{width:145px}.datepicker .datepicker-switch,.datepicker .prev,.datepicker .next,.datepicker tfoot tr th{cursor:pointer}.datepicker .datepicker-switch:hover,.datepicker .prev:hover,.datepicker .next:hover,.datepicker tfoot tr th:hover{background:#eee}.datepicker .cw{font-size:10px;width:12px;padding:0 2px 0 5px;vertical-align:middle}.input-append.date .add-on,.input-prepend.date .add-on{cursor:pointer}.input-append.date .add-on i,.input-prepend.date .add-on i{margin-top:3px}.input-daterange input{text-align:center}.input-daterange input:first-child{-webkit-border-radius:3px 0 0 3px;-moz-border-radius:3px 0 0 3px;border-radius:3px 0 0 3px}.input-daterange input:last-child{-webkit-border-radius:0 3px 3px 0;-moz-border-radius:0 3px 3px 0;border-radius:0 3px 3px 0}.input-daterange .add-on{display:inline-block;width:auto;min-width:16px;height:18px;padding:4px 5px;font-weight:400;line-height:18px;text-align:center;text-shadow:0 1px 0 #fff;vertical-align:middle;background-color:#eee;border:1px solid #ccc;margin-left:-5px;margin-right:-5px} \ No newline at end of file diff --git a/static/bootstrap3/css/bootstrap-datepicker.standalone.css b/static/bootstrap3/css/bootstrap-datepicker.standalone.css new file mode 100755 index 0000000..fc642a1 --- /dev/null +++ b/static/bootstrap3/css/bootstrap-datepicker.standalone.css @@ -0,0 +1,505 @@ +/*! + * Datepicker for Bootstrap v1.5.0-dev (https://github.com/eternicode/bootstrap-datepicker) + * + * Copyright 2012 Stefan Petre + * Improvements by Andrew Rowls + * Licensed under the Apache License v2.0 (http://www.apache.org/licenses/LICENSE-2.0) + */ +.datepicker { + padding: 4px; + -webkit-border-radius: 4px; + -moz-border-radius: 4px; + border-radius: 4px; + direction: ltr; +} +.datepicker-inline { + width: 220px; +} +.datepicker.datepicker-rtl { + direction: rtl; +} +.datepicker.datepicker-rtl table tr td span { + float: right; +} +.datepicker-dropdown { + top: 0; + left: 0; +} +.datepicker-dropdown:before { + content: ''; + display: inline-block; + border-left: 7px solid transparent; + border-right: 7px solid transparent; + border-bottom: 7px solid #999999; + border-top: 0; + border-bottom-color: rgba(0, 0, 0, 0.2); + position: absolute; +} +.datepicker-dropdown:after { + content: ''; + display: inline-block; + border-left: 6px solid transparent; + border-right: 6px solid transparent; + border-bottom: 6px solid #ffffff; + border-top: 0; + position: absolute; +} +.datepicker-dropdown.datepicker-orient-left:before { + left: 6px; +} +.datepicker-dropdown.datepicker-orient-left:after { + left: 7px; +} +.datepicker-dropdown.datepicker-orient-right:before { + right: 6px; +} +.datepicker-dropdown.datepicker-orient-right:after { + right: 7px; +} +.datepicker-dropdown.datepicker-orient-bottom:before { + top: -7px; +} +.datepicker-dropdown.datepicker-orient-bottom:after { + top: -6px; +} +.datepicker-dropdown.datepicker-orient-top:before { + bottom: -7px; + border-bottom: 0; + border-top: 7px solid #999999; +} +.datepicker-dropdown.datepicker-orient-top:after { + bottom: -6px; + border-bottom: 0; + border-top: 6px solid #ffffff; +} +.datepicker > div { + display: none; +} +.datepicker table { + margin: 0; + -webkit-touch-callout: none; + -webkit-user-select: none; + -khtml-user-select: none; + -moz-user-select: none; + -ms-user-select: none; + user-select: none; +} +.datepicker td, +.datepicker th { + text-align: center; + width: 20px; + height: 20px; + -webkit-border-radius: 4px; + -moz-border-radius: 4px; + border-radius: 4px; + border: none; +} +.table-striped .datepicker table tr td, +.table-striped .datepicker table tr th { + background-color: transparent; +} +.datepicker table tr td.day:hover, +.datepicker table tr td.day.focused { + background: #eeeeee; + cursor: pointer; +} +.datepicker table tr td.old, +.datepicker table tr td.new { + color: #999999; +} +.datepicker table tr td.disabled, +.datepicker table tr td.disabled:hover { + background: none; + color: #999999; + cursor: default; +} +.datepicker table tr td.highlighted { + background: #d9edf7; + border-radius: 0; +} +.datepicker table tr td.today, +.datepicker table tr td.today:hover, +.datepicker table tr td.today.disabled, +.datepicker table tr td.today.disabled:hover { + background-color: #fde19a; + background-image: -moz-linear-gradient(to bottom, #fdd49a, #fdf59a); + background-image: -ms-linear-gradient(to bottom, #fdd49a, #fdf59a); + background-image: -webkit-gradient(linear, 0 0, 0 100%, from(#fdd49a), to(#fdf59a)); + background-image: -webkit-linear-gradient(to bottom, #fdd49a, #fdf59a); + background-image: -o-linear-gradient(to bottom, #fdd49a, #fdf59a); + background-image: linear-gradient(to bottom, #fdd49a, #fdf59a); + background-repeat: repeat-x; + filter: progid:DXImageTransform.Microsoft.gradient(startColorstr='#fdd49a', endColorstr='#fdf59a', GradientType=0); + border-color: #fdf59a #fdf59a #fbed50; + border-color: rgba(0, 0, 0, 0.1) rgba(0, 0, 0, 0.1) rgba(0, 0, 0, 0.25); + filter: progid:DXImageTransform.Microsoft.gradient(enabled=false); + color: #000; +} +.datepicker table tr td.today:hover, +.datepicker table tr td.today:hover:hover, +.datepicker table tr td.today.disabled:hover, +.datepicker table tr td.today.disabled:hover:hover, +.datepicker table tr td.today:active, +.datepicker table tr td.today:hover:active, +.datepicker table tr td.today.disabled:active, +.datepicker table tr td.today.disabled:hover:active, +.datepicker table tr td.today.active, +.datepicker table tr td.today:hover.active, +.datepicker table tr td.today.disabled.active, +.datepicker table tr td.today.disabled:hover.active, +.datepicker table tr td.today.disabled, +.datepicker table tr td.today:hover.disabled, +.datepicker table tr td.today.disabled.disabled, +.datepicker table tr td.today.disabled:hover.disabled, +.datepicker table tr td.today[disabled], +.datepicker table tr td.today:hover[disabled], +.datepicker table tr td.today.disabled[disabled], +.datepicker table tr td.today.disabled:hover[disabled] { + background-color: #fdf59a; +} +.datepicker table tr td.today:active, +.datepicker table tr td.today:hover:active, +.datepicker table tr td.today.disabled:active, +.datepicker table tr td.today.disabled:hover:active, +.datepicker table tr td.today.active, +.datepicker table tr td.today:hover.active, +.datepicker table tr td.today.disabled.active, +.datepicker table tr td.today.disabled:hover.active { + background-color: #fbf069 \9; +} +.datepicker table tr td.today:hover:hover { + color: #000; +} +.datepicker table tr td.today.active:hover { + color: #fff; +} +.datepicker table tr td.range, +.datepicker table tr td.range:hover, +.datepicker table tr td.range.disabled, +.datepicker table tr td.range.disabled:hover { + background: #eeeeee; + -webkit-border-radius: 0; + -moz-border-radius: 0; + border-radius: 0; +} +.datepicker table tr td.range.today, +.datepicker table tr td.range.today:hover, +.datepicker table tr td.range.today.disabled, +.datepicker table tr td.range.today.disabled:hover { + background-color: #f3d17a; + background-image: -moz-linear-gradient(to bottom, #f3c17a, #f3e97a); + background-image: -ms-linear-gradient(to bottom, #f3c17a, #f3e97a); + background-image: -webkit-gradient(linear, 0 0, 0 100%, from(#f3c17a), to(#f3e97a)); + background-image: -webkit-linear-gradient(to bottom, #f3c17a, #f3e97a); + background-image: -o-linear-gradient(to bottom, #f3c17a, #f3e97a); + background-image: linear-gradient(to bottom, #f3c17a, #f3e97a); + background-repeat: repeat-x; + filter: progid:DXImageTransform.Microsoft.gradient(startColorstr='#f3c17a', endColorstr='#f3e97a', GradientType=0); + border-color: #f3e97a #f3e97a #edde34; + border-color: rgba(0, 0, 0, 0.1) rgba(0, 0, 0, 0.1) rgba(0, 0, 0, 0.25); + filter: progid:DXImageTransform.Microsoft.gradient(enabled=false); + -webkit-border-radius: 0; + -moz-border-radius: 0; + border-radius: 0; +} +.datepicker table tr td.range.today:hover, +.datepicker table tr td.range.today:hover:hover, +.datepicker table tr td.range.today.disabled:hover, +.datepicker table tr td.range.today.disabled:hover:hover, +.datepicker table tr td.range.today:active, +.datepicker table tr td.range.today:hover:active, +.datepicker table tr td.range.today.disabled:active, +.datepicker table tr td.range.today.disabled:hover:active, +.datepicker table tr td.range.today.active, +.datepicker table tr td.range.today:hover.active, +.datepicker table tr td.range.today.disabled.active, +.datepicker table tr td.range.today.disabled:hover.active, +.datepicker table tr td.range.today.disabled, +.datepicker table tr td.range.today:hover.disabled, +.datepicker table tr td.range.today.disabled.disabled, +.datepicker table tr td.range.today.disabled:hover.disabled, +.datepicker table tr td.range.today[disabled], +.datepicker table tr td.range.today:hover[disabled], +.datepicker table tr td.range.today.disabled[disabled], +.datepicker table tr td.range.today.disabled:hover[disabled] { + background-color: #f3e97a; +} +.datepicker table tr td.range.today:active, +.datepicker table tr td.range.today:hover:active, +.datepicker table tr td.range.today.disabled:active, +.datepicker table tr td.range.today.disabled:hover:active, +.datepicker table tr td.range.today.active, +.datepicker table tr td.range.today:hover.active, +.datepicker table tr td.range.today.disabled.active, +.datepicker table tr td.range.today.disabled:hover.active { + background-color: #efe24b \9; +} +.datepicker table tr td.selected, +.datepicker table tr td.selected:hover, +.datepicker table tr td.selected.disabled, +.datepicker table tr td.selected.disabled:hover { + background-color: #9e9e9e; + background-image: -moz-linear-gradient(to bottom, #b3b3b3, #808080); + background-image: -ms-linear-gradient(to bottom, #b3b3b3, #808080); + background-image: -webkit-gradient(linear, 0 0, 0 100%, from(#b3b3b3), to(#808080)); + background-image: -webkit-linear-gradient(to bottom, #b3b3b3, #808080); + background-image: -o-linear-gradient(to bottom, #b3b3b3, #808080); + background-image: linear-gradient(to bottom, #b3b3b3, #808080); + background-repeat: repeat-x; + filter: progid:DXImageTransform.Microsoft.gradient(startColorstr='#b3b3b3', endColorstr='#808080', GradientType=0); + border-color: #808080 #808080 #595959; + border-color: rgba(0, 0, 0, 0.1) rgba(0, 0, 0, 0.1) rgba(0, 0, 0, 0.25); + filter: progid:DXImageTransform.Microsoft.gradient(enabled=false); + color: #fff; + text-shadow: 0 -1px 0 rgba(0, 0, 0, 0.25); +} +.datepicker table tr td.selected:hover, +.datepicker table tr td.selected:hover:hover, +.datepicker table tr td.selected.disabled:hover, +.datepicker table tr td.selected.disabled:hover:hover, +.datepicker table tr td.selected:active, +.datepicker table tr td.selected:hover:active, +.datepicker table tr td.selected.disabled:active, +.datepicker table tr td.selected.disabled:hover:active, +.datepicker table tr td.selected.active, +.datepicker table tr td.selected:hover.active, +.datepicker table tr td.selected.disabled.active, +.datepicker table tr td.selected.disabled:hover.active, +.datepicker table tr td.selected.disabled, +.datepicker table tr td.selected:hover.disabled, +.datepicker table tr td.selected.disabled.disabled, +.datepicker table tr td.selected.disabled:hover.disabled, +.datepicker table tr td.selected[disabled], +.datepicker table tr td.selected:hover[disabled], +.datepicker table tr td.selected.disabled[disabled], +.datepicker table tr td.selected.disabled:hover[disabled] { + background-color: #808080; +} +.datepicker table tr td.selected:active, +.datepicker table tr td.selected:hover:active, +.datepicker table tr td.selected.disabled:active, +.datepicker table tr td.selected.disabled:hover:active, +.datepicker table tr td.selected.active, +.datepicker table tr td.selected:hover.active, +.datepicker table tr td.selected.disabled.active, +.datepicker table tr td.selected.disabled:hover.active { + background-color: #666666 \9; +} +.datepicker table tr td.active, +.datepicker table tr td.active:hover, +.datepicker table tr td.active.disabled, +.datepicker table tr td.active.disabled:hover { + background-color: #006dcc; + background-image: -moz-linear-gradient(to bottom, #0088cc, #0044cc); + background-image: -ms-linear-gradient(to bottom, #0088cc, #0044cc); + background-image: -webkit-gradient(linear, 0 0, 0 100%, from(#0088cc), to(#0044cc)); + background-image: -webkit-linear-gradient(to bottom, #0088cc, #0044cc); + background-image: -o-linear-gradient(to bottom, #0088cc, #0044cc); + background-image: linear-gradient(to bottom, #0088cc, #0044cc); + background-repeat: repeat-x; + filter: progid:DXImageTransform.Microsoft.gradient(startColorstr='#0088cc', endColorstr='#0044cc', GradientType=0); + border-color: #0044cc #0044cc #002a80; + border-color: rgba(0, 0, 0, 0.1) rgba(0, 0, 0, 0.1) rgba(0, 0, 0, 0.25); + filter: progid:DXImageTransform.Microsoft.gradient(enabled=false); + color: #fff; + text-shadow: 0 -1px 0 rgba(0, 0, 0, 0.25); +} +.datepicker table tr td.active:hover, +.datepicker table tr td.active:hover:hover, +.datepicker table tr td.active.disabled:hover, +.datepicker table tr td.active.disabled:hover:hover, +.datepicker table tr td.active:active, +.datepicker table tr td.active:hover:active, +.datepicker table tr td.active.disabled:active, +.datepicker table tr td.active.disabled:hover:active, +.datepicker table tr td.active.active, +.datepicker table tr td.active:hover.active, +.datepicker table tr td.active.disabled.active, +.datepicker table tr td.active.disabled:hover.active, +.datepicker table tr td.active.disabled, +.datepicker table tr td.active:hover.disabled, +.datepicker table tr td.active.disabled.disabled, +.datepicker table tr td.active.disabled:hover.disabled, +.datepicker table tr td.active[disabled], +.datepicker table tr td.active:hover[disabled], +.datepicker table tr td.active.disabled[disabled], +.datepicker table tr td.active.disabled:hover[disabled] { + background-color: #0044cc; +} +.datepicker table tr td.active:active, +.datepicker table tr td.active:hover:active, +.datepicker table tr td.active.disabled:active, +.datepicker table tr td.active.disabled:hover:active, +.datepicker table tr td.active.active, +.datepicker table tr td.active:hover.active, +.datepicker table tr td.active.disabled.active, +.datepicker table tr td.active.disabled:hover.active { + background-color: #003399 \9; +} +.datepicker table tr td span { + display: block; + width: 23%; + height: 54px; + line-height: 54px; + float: left; + margin: 1%; + cursor: pointer; + -webkit-border-radius: 4px; + -moz-border-radius: 4px; + border-radius: 4px; +} +.datepicker table tr td span:hover { + background: #eeeeee; +} +.datepicker table tr td span.disabled, +.datepicker table tr td span.disabled:hover { + background: none; + color: #999999; + cursor: default; +} +.datepicker table tr td span.active, +.datepicker table tr td span.active:hover, +.datepicker table tr td span.active.disabled, +.datepicker table tr td span.active.disabled:hover { + background-color: #006dcc; + background-image: -moz-linear-gradient(to bottom, #0088cc, #0044cc); + background-image: -ms-linear-gradient(to bottom, #0088cc, #0044cc); + background-image: -webkit-gradient(linear, 0 0, 0 100%, from(#0088cc), to(#0044cc)); + background-image: -webkit-linear-gradient(to bottom, #0088cc, #0044cc); + background-image: -o-linear-gradient(to bottom, #0088cc, #0044cc); + background-image: linear-gradient(to bottom, #0088cc, #0044cc); + background-repeat: repeat-x; + filter: progid:DXImageTransform.Microsoft.gradient(startColorstr='#0088cc', endColorstr='#0044cc', GradientType=0); + border-color: #0044cc #0044cc #002a80; + border-color: rgba(0, 0, 0, 0.1) rgba(0, 0, 0, 0.1) rgba(0, 0, 0, 0.25); + filter: progid:DXImageTransform.Microsoft.gradient(enabled=false); + color: #fff; + text-shadow: 0 -1px 0 rgba(0, 0, 0, 0.25); +} +.datepicker table tr td span.active:hover, +.datepicker table tr td span.active:hover:hover, +.datepicker table tr td span.active.disabled:hover, +.datepicker table tr td span.active.disabled:hover:hover, +.datepicker table tr td span.active:active, +.datepicker table tr td span.active:hover:active, +.datepicker table tr td span.active.disabled:active, +.datepicker table tr td span.active.disabled:hover:active, +.datepicker table tr td span.active.active, +.datepicker table tr td span.active:hover.active, +.datepicker table tr td span.active.disabled.active, +.datepicker table tr td span.active.disabled:hover.active, +.datepicker table tr td span.active.disabled, +.datepicker table tr td span.active:hover.disabled, +.datepicker table tr td span.active.disabled.disabled, +.datepicker table tr td span.active.disabled:hover.disabled, +.datepicker table tr td span.active[disabled], +.datepicker table tr td span.active:hover[disabled], +.datepicker table tr td span.active.disabled[disabled], +.datepicker table tr td span.active.disabled:hover[disabled] { + background-color: #0044cc; +} +.datepicker table tr td span.active:active, +.datepicker table tr td span.active:hover:active, +.datepicker table tr td span.active.disabled:active, +.datepicker table tr td span.active.disabled:hover:active, +.datepicker table tr td span.active.active, +.datepicker table tr td span.active:hover.active, +.datepicker table tr td span.active.disabled.active, +.datepicker table tr td span.active.disabled:hover.active { + background-color: #003399 \9; +} +.datepicker table tr td span.old, +.datepicker table tr td span.new { + color: #999999; +} +.datepicker .datepicker-switch { + width: 145px; +} +.datepicker .datepicker-switch, +.datepicker .prev, +.datepicker .next, +.datepicker tfoot tr th { + cursor: pointer; +} +.datepicker .datepicker-switch:hover, +.datepicker .prev:hover, +.datepicker .next:hover, +.datepicker tfoot tr th:hover { + background: #eeeeee; +} +.datepicker .cw { + font-size: 10px; + width: 12px; + padding: 0 2px 0 5px; + vertical-align: middle; +} +.input-append.date .add-on, +.input-prepend.date .add-on { + cursor: pointer; +} +.input-append.date .add-on i, +.input-prepend.date .add-on i { + margin-top: 3px; +} +.input-daterange input { + text-align: center; +} +.input-daterange input:first-child { + -webkit-border-radius: 3px 0 0 3px; + -moz-border-radius: 3px 0 0 3px; + border-radius: 3px 0 0 3px; +} +.input-daterange input:last-child { + -webkit-border-radius: 0 3px 3px 0; + -moz-border-radius: 0 3px 3px 0; + border-radius: 0 3px 3px 0; +} +.input-daterange .add-on { + display: inline-block; + width: auto; + min-width: 16px; + height: 20px; + padding: 4px 5px; + font-weight: normal; + line-height: 20px; + text-align: center; + text-shadow: 0 1px 0 #ffffff; + vertical-align: middle; + background-color: #eeeeee; + border: 1px solid #ccc; + margin-left: -5px; + margin-right: -5px; +} +.datepicker.dropdown-menu { + position: absolute; + top: 100%; + left: 0; + z-index: 1000; + float: left; + display: none; + min-width: 160px; + list-style: none; + background-color: #ffffff; + border: 1px solid #ccc; + border: 1px solid rgba(0, 0, 0, 0.2); + -webkit-border-radius: 5px; + -moz-border-radius: 5px; + border-radius: 5px; + -webkit-box-shadow: 0 5px 10px rgba(0, 0, 0, 0.2); + -moz-box-shadow: 0 5px 10px rgba(0, 0, 0, 0.2); + box-shadow: 0 5px 10px rgba(0, 0, 0, 0.2); + -webkit-background-clip: padding-box; + -moz-background-clip: padding; + background-clip: padding-box; + *border-right-width: 2px; + *border-bottom-width: 2px; + color: #333333; + font-size: 13px; + line-height: 20px; +} +.datepicker.dropdown-menu th, +.datepicker.datepicker-inline th, +.datepicker.dropdown-menu td, +.datepicker.datepicker-inline td { + padding: 4px 5px; +} diff --git a/static/bootstrap3/css/bootstrap-datepicker.standalone.min.css b/static/bootstrap3/css/bootstrap-datepicker.standalone.min.css new file mode 100755 index 0000000..f5db74a --- /dev/null +++ b/static/bootstrap3/css/bootstrap-datepicker.standalone.min.css @@ -0,0 +1,8 @@ +/*! + * Datepicker for Bootstrap v1.5.0-dev (https://github.com/eternicode/bootstrap-datepicker) + * + * Copyright 2012 Stefan Petre + * Improvements by Andrew Rowls + * Licensed under the Apache License v2.0 (http://www.apache.org/licenses/LICENSE-2.0) + */ +.datepicker{padding:4px;-webkit-border-radius:4px;-moz-border-radius:4px;border-radius:4px;direction:ltr}.datepicker-inline{width:220px}.datepicker.datepicker-rtl{direction:rtl}.datepicker.datepicker-rtl table tr td span{float:right}.datepicker-dropdown{top:0;left:0}.datepicker-dropdown:before{content:'';display:inline-block;border-left:7px solid transparent;border-right:7px solid transparent;border-bottom:7px solid #999;border-top:0;border-bottom-color:rgba(0,0,0,.2);position:absolute}.datepicker-dropdown:after{content:'';display:inline-block;border-left:6px solid transparent;border-right:6px solid transparent;border-bottom:6px solid #fff;border-top:0;position:absolute}.datepicker-dropdown.datepicker-orient-left:before{left:6px}.datepicker-dropdown.datepicker-orient-left:after{left:7px}.datepicker-dropdown.datepicker-orient-right:before{right:6px}.datepicker-dropdown.datepicker-orient-right:after{right:7px}.datepicker-dropdown.datepicker-orient-bottom:before{top:-7px}.datepicker-dropdown.datepicker-orient-bottom:after{top:-6px}.datepicker-dropdown.datepicker-orient-top:before{bottom:-7px;border-bottom:0;border-top:7px solid #999}.datepicker-dropdown.datepicker-orient-top:after{bottom:-6px;border-bottom:0;border-top:6px solid #fff}.datepicker>div{display:none}.datepicker table{margin:0;-webkit-touch-callout:none;-webkit-user-select:none;-khtml-user-select:none;-moz-user-select:none;-ms-user-select:none;user-select:none}.datepicker td,.datepicker th{text-align:center;width:20px;height:20px;-webkit-border-radius:4px;-moz-border-radius:4px;border-radius:4px;border:none}.table-striped .datepicker table tr td,.table-striped .datepicker table tr th{background-color:transparent}.datepicker table tr td.day:hover,.datepicker table tr td.day.focused{background:#eee;cursor:pointer}.datepicker table tr td.old,.datepicker table tr td.new{color:#999}.datepicker table tr td.disabled,.datepicker table tr td.disabled:hover{background:0 0;color:#999;cursor:default}.datepicker table tr td.highlighted{background:#d9edf7;border-radius:0}.datepicker table tr td.today,.datepicker table tr td.today:hover,.datepicker table tr td.today.disabled,.datepicker table tr td.today.disabled:hover{background-color:#fde19a;background-image:-moz-linear-gradient(to bottom,#fdd49a,#fdf59a);background-image:-ms-linear-gradient(to bottom,#fdd49a,#fdf59a);background-image:-webkit-gradient(linear,0 0,0 100%,from(#fdd49a),to(#fdf59a));background-image:-webkit-linear-gradient(to bottom,#fdd49a,#fdf59a);background-image:-o-linear-gradient(to bottom,#fdd49a,#fdf59a);background-image:linear-gradient(to bottom,#fdd49a,#fdf59a);background-repeat:repeat-x;filter:progid:DXImageTransform.Microsoft.gradient(startColorstr='#fdd49a', endColorstr='#fdf59a', GradientType=0);border-color:#fdf59a #fdf59a #fbed50;border-color:rgba(0,0,0,.1) rgba(0,0,0,.1) rgba(0,0,0,.25);filter:progid:DXImageTransform.Microsoft.gradient(enabled=false);color:#000}.datepicker table tr td.today:hover,.datepicker table tr td.today:hover:hover,.datepicker table tr td.today.disabled:hover,.datepicker table tr td.today.disabled:hover:hover,.datepicker table tr td.today:active,.datepicker table tr td.today:hover:active,.datepicker table tr td.today.disabled:active,.datepicker table tr td.today.disabled:hover:active,.datepicker table tr td.today.active,.datepicker table tr td.today:hover.active,.datepicker table tr td.today.disabled.active,.datepicker table tr td.today.disabled:hover.active,.datepicker table tr td.today.disabled,.datepicker table tr td.today:hover.disabled,.datepicker table tr td.today.disabled.disabled,.datepicker table tr td.today.disabled:hover.disabled,.datepicker table tr td.today[disabled],.datepicker table tr td.today:hover[disabled],.datepicker table tr td.today.disabled[disabled],.datepicker table tr td.today.disabled:hover[disabled]{background-color:#fdf59a}.datepicker table tr td.today:active,.datepicker table tr td.today:hover:active,.datepicker table tr td.today.disabled:active,.datepicker table tr td.today.disabled:hover:active,.datepicker table tr td.today.active,.datepicker table tr td.today:hover.active,.datepicker table tr td.today.disabled.active,.datepicker table tr td.today.disabled:hover.active{background-color:#fbf069 \9}.datepicker table tr td.today:hover:hover{color:#000}.datepicker table tr td.today.active:hover{color:#fff}.datepicker table tr td.range,.datepicker table tr td.range:hover,.datepicker table tr td.range.disabled,.datepicker table tr td.range.disabled:hover{background:#eee;-webkit-border-radius:0;-moz-border-radius:0;border-radius:0}.datepicker table tr td.range.today,.datepicker table tr td.range.today:hover,.datepicker table tr td.range.today.disabled,.datepicker table tr td.range.today.disabled:hover{background-color:#f3d17a;background-image:-moz-linear-gradient(to bottom,#f3c17a,#f3e97a);background-image:-ms-linear-gradient(to bottom,#f3c17a,#f3e97a);background-image:-webkit-gradient(linear,0 0,0 100%,from(#f3c17a),to(#f3e97a));background-image:-webkit-linear-gradient(to bottom,#f3c17a,#f3e97a);background-image:-o-linear-gradient(to bottom,#f3c17a,#f3e97a);background-image:linear-gradient(to bottom,#f3c17a,#f3e97a);background-repeat:repeat-x;filter:progid:DXImageTransform.Microsoft.gradient(startColorstr='#f3c17a', endColorstr='#f3e97a', GradientType=0);border-color:#f3e97a #f3e97a #edde34;border-color:rgba(0,0,0,.1) rgba(0,0,0,.1) rgba(0,0,0,.25);filter:progid:DXImageTransform.Microsoft.gradient(enabled=false);-webkit-border-radius:0;-moz-border-radius:0;border-radius:0}.datepicker table tr td.range.today:hover,.datepicker table tr td.range.today:hover:hover,.datepicker table tr td.range.today.disabled:hover,.datepicker table tr td.range.today.disabled:hover:hover,.datepicker table tr td.range.today:active,.datepicker table tr td.range.today:hover:active,.datepicker table tr td.range.today.disabled:active,.datepicker table tr td.range.today.disabled:hover:active,.datepicker table tr td.range.today.active,.datepicker table tr td.range.today:hover.active,.datepicker table tr td.range.today.disabled.active,.datepicker table tr td.range.today.disabled:hover.active,.datepicker table tr td.range.today.disabled,.datepicker table tr td.range.today:hover.disabled,.datepicker table tr td.range.today.disabled.disabled,.datepicker table tr td.range.today.disabled:hover.disabled,.datepicker table tr td.range.today[disabled],.datepicker table tr td.range.today:hover[disabled],.datepicker table tr td.range.today.disabled[disabled],.datepicker table tr td.range.today.disabled:hover[disabled]{background-color:#f3e97a}.datepicker table tr td.range.today:active,.datepicker table tr td.range.today:hover:active,.datepicker table tr td.range.today.disabled:active,.datepicker table tr td.range.today.disabled:hover:active,.datepicker table tr td.range.today.active,.datepicker table tr td.range.today:hover.active,.datepicker table tr td.range.today.disabled.active,.datepicker table tr td.range.today.disabled:hover.active{background-color:#efe24b \9}.datepicker table tr td.selected,.datepicker table tr td.selected:hover,.datepicker table tr td.selected.disabled,.datepicker table tr td.selected.disabled:hover{background-color:#9e9e9e;background-image:-moz-linear-gradient(to bottom,#b3b3b3,gray);background-image:-ms-linear-gradient(to bottom,#b3b3b3,gray);background-image:-webkit-gradient(linear,0 0,0 100%,from(#b3b3b3),to(gray));background-image:-webkit-linear-gradient(to bottom,#b3b3b3,gray);background-image:-o-linear-gradient(to bottom,#b3b3b3,gray);background-image:linear-gradient(to bottom,#b3b3b3,gray);background-repeat:repeat-x;filter:progid:DXImageTransform.Microsoft.gradient(startColorstr='#b3b3b3', endColorstr='#808080', GradientType=0);border-color:gray #808080 #595959;border-color:rgba(0,0,0,.1) rgba(0,0,0,.1) rgba(0,0,0,.25);filter:progid:DXImageTransform.Microsoft.gradient(enabled=false);color:#fff;text-shadow:0 -1px 0 rgba(0,0,0,.25)}.datepicker table tr td.selected:hover,.datepicker table tr td.selected:hover:hover,.datepicker table tr td.selected.disabled:hover,.datepicker table tr td.selected.disabled:hover:hover,.datepicker table tr td.selected:active,.datepicker table tr td.selected:hover:active,.datepicker table tr td.selected.disabled:active,.datepicker table tr td.selected.disabled:hover:active,.datepicker table tr td.selected.active,.datepicker table tr td.selected:hover.active,.datepicker table tr td.selected.disabled.active,.datepicker table tr td.selected.disabled:hover.active,.datepicker table tr td.selected.disabled,.datepicker table tr td.selected:hover.disabled,.datepicker table tr td.selected.disabled.disabled,.datepicker table tr td.selected.disabled:hover.disabled,.datepicker table tr td.selected[disabled],.datepicker table tr td.selected:hover[disabled],.datepicker table tr td.selected.disabled[disabled],.datepicker table tr td.selected.disabled:hover[disabled]{background-color:gray}.datepicker table tr td.selected:active,.datepicker table tr td.selected:hover:active,.datepicker table tr td.selected.disabled:active,.datepicker table tr td.selected.disabled:hover:active,.datepicker table tr td.selected.active,.datepicker table tr td.selected:hover.active,.datepicker table tr td.selected.disabled.active,.datepicker table tr td.selected.disabled:hover.active{background-color:#666 \9}.datepicker table tr td.active,.datepicker table tr td.active:hover,.datepicker table tr td.active.disabled,.datepicker table tr td.active.disabled:hover{background-color:#006dcc;background-image:-moz-linear-gradient(to bottom,#08c,#04c);background-image:-ms-linear-gradient(to bottom,#08c,#04c);background-image:-webkit-gradient(linear,0 0,0 100%,from(#08c),to(#04c));background-image:-webkit-linear-gradient(to bottom,#08c,#04c);background-image:-o-linear-gradient(to bottom,#08c,#04c);background-image:linear-gradient(to bottom,#08c,#04c);background-repeat:repeat-x;filter:progid:DXImageTransform.Microsoft.gradient(startColorstr='#0088cc', endColorstr='#0044cc', GradientType=0);border-color:#04c #04c #002a80;border-color:rgba(0,0,0,.1) rgba(0,0,0,.1) rgba(0,0,0,.25);filter:progid:DXImageTransform.Microsoft.gradient(enabled=false);color:#fff;text-shadow:0 -1px 0 rgba(0,0,0,.25)}.datepicker table tr td.active:hover,.datepicker table tr td.active:hover:hover,.datepicker table tr td.active.disabled:hover,.datepicker table tr td.active.disabled:hover:hover,.datepicker table tr td.active:active,.datepicker table tr td.active:hover:active,.datepicker table tr td.active.disabled:active,.datepicker table tr td.active.disabled:hover:active,.datepicker table tr td.active.active,.datepicker table tr td.active:hover.active,.datepicker table tr td.active.disabled.active,.datepicker table tr td.active.disabled:hover.active,.datepicker table tr td.active.disabled,.datepicker table tr td.active:hover.disabled,.datepicker table tr td.active.disabled.disabled,.datepicker table tr td.active.disabled:hover.disabled,.datepicker table tr td.active[disabled],.datepicker table tr td.active:hover[disabled],.datepicker table tr td.active.disabled[disabled],.datepicker table tr td.active.disabled:hover[disabled]{background-color:#04c}.datepicker table tr td.active:active,.datepicker table tr td.active:hover:active,.datepicker table tr td.active.disabled:active,.datepicker table tr td.active.disabled:hover:active,.datepicker table tr td.active.active,.datepicker table tr td.active:hover.active,.datepicker table tr td.active.disabled.active,.datepicker table tr td.active.disabled:hover.active{background-color:#039 \9}.datepicker table tr td span{display:block;width:23%;height:54px;line-height:54px;float:left;margin:1%;cursor:pointer;-webkit-border-radius:4px;-moz-border-radius:4px;border-radius:4px}.datepicker table tr td span:hover{background:#eee}.datepicker table tr td span.disabled,.datepicker table tr td span.disabled:hover{background:0 0;color:#999;cursor:default}.datepicker table tr td span.active,.datepicker table tr td span.active:hover,.datepicker table tr td span.active.disabled,.datepicker table tr td span.active.disabled:hover{background-color:#006dcc;background-image:-moz-linear-gradient(to bottom,#08c,#04c);background-image:-ms-linear-gradient(to bottom,#08c,#04c);background-image:-webkit-gradient(linear,0 0,0 100%,from(#08c),to(#04c));background-image:-webkit-linear-gradient(to bottom,#08c,#04c);background-image:-o-linear-gradient(to bottom,#08c,#04c);background-image:linear-gradient(to bottom,#08c,#04c);background-repeat:repeat-x;filter:progid:DXImageTransform.Microsoft.gradient(startColorstr='#0088cc', endColorstr='#0044cc', GradientType=0);border-color:#04c #04c #002a80;border-color:rgba(0,0,0,.1) rgba(0,0,0,.1) rgba(0,0,0,.25);filter:progid:DXImageTransform.Microsoft.gradient(enabled=false);color:#fff;text-shadow:0 -1px 0 rgba(0,0,0,.25)}.datepicker table tr td span.active:hover,.datepicker table tr td span.active:hover:hover,.datepicker table tr td span.active.disabled:hover,.datepicker table tr td span.active.disabled:hover:hover,.datepicker table tr td span.active:active,.datepicker table tr td span.active:hover:active,.datepicker table tr td span.active.disabled:active,.datepicker table tr td span.active.disabled:hover:active,.datepicker table tr td span.active.active,.datepicker table tr td span.active:hover.active,.datepicker table tr td span.active.disabled.active,.datepicker table tr td span.active.disabled:hover.active,.datepicker table tr td span.active.disabled,.datepicker table tr td span.active:hover.disabled,.datepicker table tr td span.active.disabled.disabled,.datepicker table tr td span.active.disabled:hover.disabled,.datepicker table tr td span.active[disabled],.datepicker table tr td span.active:hover[disabled],.datepicker table tr td span.active.disabled[disabled],.datepicker table tr td span.active.disabled:hover[disabled]{background-color:#04c}.datepicker table tr td span.active:active,.datepicker table tr td span.active:hover:active,.datepicker table tr td span.active.disabled:active,.datepicker table tr td span.active.disabled:hover:active,.datepicker table tr td span.active.active,.datepicker table tr td span.active:hover.active,.datepicker table tr td span.active.disabled.active,.datepicker table tr td span.active.disabled:hover.active{background-color:#039 \9}.datepicker table tr td span.old,.datepicker table tr td span.new{color:#999}.datepicker .datepicker-switch{width:145px}.datepicker .datepicker-switch,.datepicker .prev,.datepicker .next,.datepicker tfoot tr th{cursor:pointer}.datepicker .datepicker-switch:hover,.datepicker .prev:hover,.datepicker .next:hover,.datepicker tfoot tr th:hover{background:#eee}.datepicker .cw{font-size:10px;width:12px;padding:0 2px 0 5px;vertical-align:middle}.input-append.date .add-on,.input-prepend.date .add-on{cursor:pointer}.input-append.date .add-on i,.input-prepend.date .add-on i{margin-top:3px}.input-daterange input{text-align:center}.input-daterange input:first-child{-webkit-border-radius:3px 0 0 3px;-moz-border-radius:3px 0 0 3px;border-radius:3px 0 0 3px}.input-daterange input:last-child{-webkit-border-radius:0 3px 3px 0;-moz-border-radius:0 3px 3px 0;border-radius:0 3px 3px 0}.input-daterange .add-on{display:inline-block;width:auto;min-width:16px;height:20px;padding:4px 5px;font-weight:400;line-height:20px;text-align:center;text-shadow:0 1px 0 #fff;vertical-align:middle;background-color:#eee;border:1px solid #ccc;margin-left:-5px;margin-right:-5px}.datepicker.dropdown-menu{position:absolute;top:100%;left:0;z-index:1000;float:left;display:none;min-width:160px;list-style:none;background-color:#fff;border:1px solid #ccc;border:1px solid rgba(0,0,0,.2);-webkit-border-radius:5px;-moz-border-radius:5px;border-radius:5px;-webkit-box-shadow:0 5px 10px rgba(0,0,0,.2);-moz-box-shadow:0 5px 10px rgba(0,0,0,.2);box-shadow:0 5px 10px rgba(0,0,0,.2);-webkit-background-clip:padding-box;-moz-background-clip:padding;background-clip:padding-box;*border-right-width:2px;*border-bottom-width:2px;color:#333;font-size:13px;line-height:20px}.datepicker.dropdown-menu th,.datepicker.datepicker-inline th,.datepicker.dropdown-menu td,.datepicker.datepicker-inline td{padding:4px 5px} \ No newline at end of file diff --git a/static/bootstrap3/css/bootstrap-datepicker3.css b/static/bootstrap3/css/bootstrap-datepicker3.css new file mode 100755 index 0000000..06837c8 --- /dev/null +++ b/static/bootstrap3/css/bootstrap-datepicker3.css @@ -0,0 +1,791 @@ +/*! + * Datepicker for Bootstrap v1.5.0-dev (https://github.com/eternicode/bootstrap-datepicker) + * + * Copyright 2012 Stefan Petre + * Improvements by Andrew Rowls + * Licensed under the Apache License v2.0 (http://www.apache.org/licenses/LICENSE-2.0) + */ +.datepicker { + border-radius: 4px; + direction: ltr; +} +.datepicker-inline { + width: 220px; +} +.datepicker.datepicker-rtl { + direction: rtl; +} +.datepicker.datepicker-rtl table tr td span { + float: right; +} +.datepicker-dropdown { + top: 0; + left: 0; + padding: 4px; +} +.datepicker-dropdown:before { + content: ''; + display: inline-block; + border-left: 7px solid transparent; + border-right: 7px solid transparent; + border-bottom: 7px solid rgba(0, 0, 0, 0.15); + border-top: 0; + border-bottom-color: rgba(0, 0, 0, 0.2); + position: absolute; +} +.datepicker-dropdown:after { + content: ''; + display: inline-block; + border-left: 6px solid transparent; + border-right: 6px solid transparent; + border-bottom: 6px solid #ffffff; + border-top: 0; + position: absolute; +} +.datepicker-dropdown.datepicker-orient-left:before { + left: 6px; +} +.datepicker-dropdown.datepicker-orient-left:after { + left: 7px; +} +.datepicker-dropdown.datepicker-orient-right:before { + right: 6px; +} +.datepicker-dropdown.datepicker-orient-right:after { + right: 7px; +} +.datepicker-dropdown.datepicker-orient-bottom:before { + top: -7px; +} +.datepicker-dropdown.datepicker-orient-bottom:after { + top: -6px; +} +.datepicker-dropdown.datepicker-orient-top:before { + bottom: -7px; + border-bottom: 0; + border-top: 7px solid rgba(0, 0, 0, 0.15); +} +.datepicker-dropdown.datepicker-orient-top:after { + bottom: -6px; + border-bottom: 0; + border-top: 6px solid #ffffff; +} +.datepicker > div { + display: none; +} +.datepicker table { + margin: 0; + -webkit-touch-callout: none; + -webkit-user-select: none; + -khtml-user-select: none; + -moz-user-select: none; + -ms-user-select: none; + user-select: none; +} +.datepicker table tr td, +.datepicker table tr th { + text-align: center; + width: 30px; + height: 30px; + border-radius: 4px; + border: none; +} +.table-striped .datepicker table tr td, +.table-striped .datepicker table tr th { + background-color: transparent; +} +.datepicker table tr td.old, +.datepicker table tr td.new { + color: #999999; +} +.datepicker table tr td.day:hover, +.datepicker table tr td.focused { + background: #eeeeee; + cursor: pointer; +} +.datepicker table tr td.disabled, +.datepicker table tr td.disabled:hover { + background: none; + color: #999999; + cursor: default; +} +.datepicker table tr td.highlighted { + color: #000000; + background-color: #d9edf7; + border-color: #85c5e5; + border-radius: 0; +} +.datepicker table tr td.highlighted:focus, +.datepicker table tr td.highlighted.focus { + color: #000000; + background-color: #afd9ee; + border-color: #298fc2; +} +.datepicker table tr td.highlighted:hover { + color: #000000; + background-color: #afd9ee; + border-color: #52addb; +} +.datepicker table tr td.highlighted:active, +.datepicker table tr td.highlighted.active, +.open > .dropdown-toggle.datepicker table tr td.highlighted { + color: #000000; + background-color: #afd9ee; + border-color: #52addb; +} +.datepicker table tr td.highlighted:active:hover, +.datepicker table tr td.highlighted.active:hover, +.open > .dropdown-toggle.datepicker table tr td.highlighted:hover, +.datepicker table tr td.highlighted:active:focus, +.datepicker table tr td.highlighted.active:focus, +.open > .dropdown-toggle.datepicker table tr td.highlighted:focus, +.datepicker table tr td.highlighted:active.focus, +.datepicker table tr td.highlighted.active.focus, +.open > .dropdown-toggle.datepicker table tr td.highlighted.focus { + color: #000000; + background-color: #91cbe8; + border-color: #298fc2; +} +.datepicker table tr td.highlighted:active, +.datepicker table tr td.highlighted.active, +.open > .dropdown-toggle.datepicker table tr td.highlighted { + background-image: none; +} +.datepicker table tr td.highlighted.disabled:hover, +.datepicker table tr td.highlighted[disabled]:hover, +fieldset[disabled] .datepicker table tr td.highlighted:hover, +.datepicker table tr td.highlighted.disabled:focus, +.datepicker table tr td.highlighted[disabled]:focus, +fieldset[disabled] .datepicker table tr td.highlighted:focus, +.datepicker table tr td.highlighted.disabled.focus, +.datepicker table tr td.highlighted[disabled].focus, +fieldset[disabled] .datepicker table tr td.highlighted.focus { + background-color: #d9edf7; + border-color: #85c5e5; +} +.datepicker table tr td.highlighted.focused { + background: #afd9ee; +} +.datepicker table tr td.highlighted.disabled, +.datepicker table tr td.highlighted.disabled:active { + background: #d9edf7; + color: #999999; +} +.datepicker table tr td.today { + color: #000000; + background-color: #ffdb99; + border-color: #ffb733; +} +.datepicker table tr td.today:focus, +.datepicker table tr td.today.focus { + color: #000000; + background-color: #ffc966; + border-color: #b37400; +} +.datepicker table tr td.today:hover { + color: #000000; + background-color: #ffc966; + border-color: #f59e00; +} +.datepicker table tr td.today:active, +.datepicker table tr td.today.active, +.open > .dropdown-toggle.datepicker table tr td.today { + color: #000000; + background-color: #ffc966; + border-color: #f59e00; +} +.datepicker table tr td.today:active:hover, +.datepicker table tr td.today.active:hover, +.open > .dropdown-toggle.datepicker table tr td.today:hover, +.datepicker table tr td.today:active:focus, +.datepicker table tr td.today.active:focus, +.open > .dropdown-toggle.datepicker table tr td.today:focus, +.datepicker table tr td.today:active.focus, +.datepicker table tr td.today.active.focus, +.open > .dropdown-toggle.datepicker table tr td.today.focus { + color: #000000; + background-color: #ffbc42; + border-color: #b37400; +} +.datepicker table tr td.today:active, +.datepicker table tr td.today.active, +.open > .dropdown-toggle.datepicker table tr td.today { + background-image: none; +} +.datepicker table tr td.today.disabled:hover, +.datepicker table tr td.today[disabled]:hover, +fieldset[disabled] .datepicker table tr td.today:hover, +.datepicker table tr td.today.disabled:focus, +.datepicker table tr td.today[disabled]:focus, +fieldset[disabled] .datepicker table tr td.today:focus, +.datepicker table tr td.today.disabled.focus, +.datepicker table tr td.today[disabled].focus, +fieldset[disabled] .datepicker table tr td.today.focus { + background-color: #ffdb99; + border-color: #ffb733; +} +.datepicker table tr td.today.focused { + background: #ffc966; +} +.datepicker table tr td.today.disabled, +.datepicker table tr td.today.disabled:active { + background: #ffdb99; + color: #999999; +} +.datepicker table tr td.range { + color: #000000; + background-color: #eeeeee; + border-color: #bbbbbb; + border-radius: 0; +} +.datepicker table tr td.range:focus, +.datepicker table tr td.range.focus { + color: #000000; + background-color: #d5d5d5; + border-color: #7c7c7c; +} +.datepicker table tr td.range:hover { + color: #000000; + background-color: #d5d5d5; + border-color: #9d9d9d; +} +.datepicker table tr td.range:active, +.datepicker table tr td.range.active, +.open > .dropdown-toggle.datepicker table tr td.range { + color: #000000; + background-color: #d5d5d5; + border-color: #9d9d9d; +} +.datepicker table tr td.range:active:hover, +.datepicker table tr td.range.active:hover, +.open > .dropdown-toggle.datepicker table tr td.range:hover, +.datepicker table tr td.range:active:focus, +.datepicker table tr td.range.active:focus, +.open > .dropdown-toggle.datepicker table tr td.range:focus, +.datepicker table tr td.range:active.focus, +.datepicker table tr td.range.active.focus, +.open > .dropdown-toggle.datepicker table tr td.range.focus { + color: #000000; + background-color: #c3c3c3; + border-color: #7c7c7c; +} +.datepicker table tr td.range:active, +.datepicker table tr td.range.active, +.open > .dropdown-toggle.datepicker table tr td.range { + background-image: none; +} +.datepicker table tr td.range.disabled:hover, +.datepicker table tr td.range[disabled]:hover, +fieldset[disabled] .datepicker table tr td.range:hover, +.datepicker table tr td.range.disabled:focus, +.datepicker table tr td.range[disabled]:focus, +fieldset[disabled] .datepicker table tr td.range:focus, +.datepicker table tr td.range.disabled.focus, +.datepicker table tr td.range[disabled].focus, +fieldset[disabled] .datepicker table tr td.range.focus { + background-color: #eeeeee; + border-color: #bbbbbb; +} +.datepicker table tr td.range.focused { + background: #d5d5d5; +} +.datepicker table tr td.range.disabled, +.datepicker table tr td.range.disabled:active { + background: #eeeeee; + color: #999999; +} +.datepicker table tr td.range.highlighted { + color: #000000; + background-color: #e4eef3; + border-color: #9dc1d3; +} +.datepicker table tr td.range.highlighted:focus, +.datepicker table tr td.range.highlighted.focus { + color: #000000; + background-color: #c1d7e3; + border-color: #4b88a6; +} +.datepicker table tr td.range.highlighted:hover { + color: #000000; + background-color: #c1d7e3; + border-color: #73a6c0; +} +.datepicker table tr td.range.highlighted:active, +.datepicker table tr td.range.highlighted.active, +.open > .dropdown-toggle.datepicker table tr td.range.highlighted { + color: #000000; + background-color: #c1d7e3; + border-color: #73a6c0; +} +.datepicker table tr td.range.highlighted:active:hover, +.datepicker table tr td.range.highlighted.active:hover, +.open > .dropdown-toggle.datepicker table tr td.range.highlighted:hover, +.datepicker table tr td.range.highlighted:active:focus, +.datepicker table tr td.range.highlighted.active:focus, +.open > .dropdown-toggle.datepicker table tr td.range.highlighted:focus, +.datepicker table tr td.range.highlighted:active.focus, +.datepicker table tr td.range.highlighted.active.focus, +.open > .dropdown-toggle.datepicker table tr td.range.highlighted.focus { + color: #000000; + background-color: #a8c8d8; + border-color: #4b88a6; +} +.datepicker table tr td.range.highlighted:active, +.datepicker table tr td.range.highlighted.active, +.open > .dropdown-toggle.datepicker table tr td.range.highlighted { + background-image: none; +} +.datepicker table tr td.range.highlighted.disabled:hover, +.datepicker table tr td.range.highlighted[disabled]:hover, +fieldset[disabled] .datepicker table tr td.range.highlighted:hover, +.datepicker table tr td.range.highlighted.disabled:focus, +.datepicker table tr td.range.highlighted[disabled]:focus, +fieldset[disabled] .datepicker table tr td.range.highlighted:focus, +.datepicker table tr td.range.highlighted.disabled.focus, +.datepicker table tr td.range.highlighted[disabled].focus, +fieldset[disabled] .datepicker table tr td.range.highlighted.focus { + background-color: #e4eef3; + border-color: #9dc1d3; +} +.datepicker table tr td.range.highlighted.focused { + background: #c1d7e3; +} +.datepicker table tr td.range.highlighted.disabled, +.datepicker table tr td.range.highlighted.disabled:active { + background: #e4eef3; + color: #999999; +} +.datepicker table tr td.range.today { + color: #000000; + background-color: #f7ca77; + border-color: #f1a417; +} +.datepicker table tr td.range.today:focus, +.datepicker table tr td.range.today.focus { + color: #000000; + background-color: #f4b747; + border-color: #815608; +} +.datepicker table tr td.range.today:hover { + color: #000000; + background-color: #f4b747; + border-color: #bf800c; +} +.datepicker table tr td.range.today:active, +.datepicker table tr td.range.today.active, +.open > .dropdown-toggle.datepicker table tr td.range.today { + color: #000000; + background-color: #f4b747; + border-color: #bf800c; +} +.datepicker table tr td.range.today:active:hover, +.datepicker table tr td.range.today.active:hover, +.open > .dropdown-toggle.datepicker table tr td.range.today:hover, +.datepicker table tr td.range.today:active:focus, +.datepicker table tr td.range.today.active:focus, +.open > .dropdown-toggle.datepicker table tr td.range.today:focus, +.datepicker table tr td.range.today:active.focus, +.datepicker table tr td.range.today.active.focus, +.open > .dropdown-toggle.datepicker table tr td.range.today.focus { + color: #000000; + background-color: #f2aa25; + border-color: #815608; +} +.datepicker table tr td.range.today:active, +.datepicker table tr td.range.today.active, +.open > .dropdown-toggle.datepicker table tr td.range.today { + background-image: none; +} +.datepicker table tr td.range.today.disabled:hover, +.datepicker table tr td.range.today[disabled]:hover, +fieldset[disabled] .datepicker table tr td.range.today:hover, +.datepicker table tr td.range.today.disabled:focus, +.datepicker table tr td.range.today[disabled]:focus, +fieldset[disabled] .datepicker table tr td.range.today:focus, +.datepicker table tr td.range.today.disabled.focus, +.datepicker table tr td.range.today[disabled].focus, +fieldset[disabled] .datepicker table tr td.range.today.focus { + background-color: #f7ca77; + border-color: #f1a417; +} +.datepicker table tr td.range.today.disabled, +.datepicker table tr td.range.today.disabled:active { + background: #f7ca77; + color: #999999; +} +.datepicker table tr td.selected, +.datepicker table tr td.selected.highlighted { + color: #ffffff; + background-color: #999999; + border-color: #555555; + text-shadow: 0 -1px 0 rgba(0, 0, 0, 0.25); +} +.datepicker table tr td.selected:focus, +.datepicker table tr td.selected.highlighted:focus, +.datepicker table tr td.selected.focus, +.datepicker table tr td.selected.highlighted.focus { + color: #ffffff; + background-color: #808080; + border-color: #161616; +} +.datepicker table tr td.selected:hover, +.datepicker table tr td.selected.highlighted:hover { + color: #ffffff; + background-color: #808080; + border-color: #373737; +} +.datepicker table tr td.selected:active, +.datepicker table tr td.selected.highlighted:active, +.datepicker table tr td.selected.active, +.datepicker table tr td.selected.highlighted.active, +.open > .dropdown-toggle.datepicker table tr td.selected, +.open > .dropdown-toggle.datepicker table tr td.selected.highlighted { + color: #ffffff; + background-color: #808080; + border-color: #373737; +} +.datepicker table tr td.selected:active:hover, +.datepicker table tr td.selected.highlighted:active:hover, +.datepicker table tr td.selected.active:hover, +.datepicker table tr td.selected.highlighted.active:hover, +.open > .dropdown-toggle.datepicker table tr td.selected:hover, +.open > .dropdown-toggle.datepicker table tr td.selected.highlighted:hover, +.datepicker table tr td.selected:active:focus, +.datepicker table tr td.selected.highlighted:active:focus, +.datepicker table tr td.selected.active:focus, +.datepicker table tr td.selected.highlighted.active:focus, +.open > .dropdown-toggle.datepicker table tr td.selected:focus, +.open > .dropdown-toggle.datepicker table tr td.selected.highlighted:focus, +.datepicker table tr td.selected:active.focus, +.datepicker table tr td.selected.highlighted:active.focus, +.datepicker table tr td.selected.active.focus, +.datepicker table tr td.selected.highlighted.active.focus, +.open > .dropdown-toggle.datepicker table tr td.selected.focus, +.open > .dropdown-toggle.datepicker table tr td.selected.highlighted.focus { + color: #ffffff; + background-color: #6e6e6e; + border-color: #161616; +} +.datepicker table tr td.selected:active, +.datepicker table tr td.selected.highlighted:active, +.datepicker table tr td.selected.active, +.datepicker table tr td.selected.highlighted.active, +.open > .dropdown-toggle.datepicker table tr td.selected, +.open > .dropdown-toggle.datepicker table tr td.selected.highlighted { + background-image: none; +} +.datepicker table tr td.selected.disabled:hover, +.datepicker table tr td.selected.highlighted.disabled:hover, +.datepicker table tr td.selected[disabled]:hover, +.datepicker table tr td.selected.highlighted[disabled]:hover, +fieldset[disabled] .datepicker table tr td.selected:hover, +fieldset[disabled] .datepicker table tr td.selected.highlighted:hover, +.datepicker table tr td.selected.disabled:focus, +.datepicker table tr td.selected.highlighted.disabled:focus, +.datepicker table tr td.selected[disabled]:focus, +.datepicker table tr td.selected.highlighted[disabled]:focus, +fieldset[disabled] .datepicker table tr td.selected:focus, +fieldset[disabled] .datepicker table tr td.selected.highlighted:focus, +.datepicker table tr td.selected.disabled.focus, +.datepicker table tr td.selected.highlighted.disabled.focus, +.datepicker table tr td.selected[disabled].focus, +.datepicker table tr td.selected.highlighted[disabled].focus, +fieldset[disabled] .datepicker table tr td.selected.focus, +fieldset[disabled] .datepicker table tr td.selected.highlighted.focus { + background-color: #999999; + border-color: #555555; +} +.datepicker table tr td.active, +.datepicker table tr td.active.highlighted { + color: #ffffff; + background-color: #428bca; + border-color: #357ebd; + text-shadow: 0 -1px 0 rgba(0, 0, 0, 0.25); +} +.datepicker table tr td.active:focus, +.datepicker table tr td.active.highlighted:focus, +.datepicker table tr td.active.focus, +.datepicker table tr td.active.highlighted.focus { + color: #ffffff; + background-color: #3071a9; + border-color: #193c5a; +} +.datepicker table tr td.active:hover, +.datepicker table tr td.active.highlighted:hover { + color: #ffffff; + background-color: #3071a9; + border-color: #285e8e; +} +.datepicker table tr td.active:active, +.datepicker table tr td.active.highlighted:active, +.datepicker table tr td.active.active, +.datepicker table tr td.active.highlighted.active, +.open > .dropdown-toggle.datepicker table tr td.active, +.open > .dropdown-toggle.datepicker table tr td.active.highlighted { + color: #ffffff; + background-color: #3071a9; + border-color: #285e8e; +} +.datepicker table tr td.active:active:hover, +.datepicker table tr td.active.highlighted:active:hover, +.datepicker table tr td.active.active:hover, +.datepicker table tr td.active.highlighted.active:hover, +.open > .dropdown-toggle.datepicker table tr td.active:hover, +.open > .dropdown-toggle.datepicker table tr td.active.highlighted:hover, +.datepicker table tr td.active:active:focus, +.datepicker table tr td.active.highlighted:active:focus, +.datepicker table tr td.active.active:focus, +.datepicker table tr td.active.highlighted.active:focus, +.open > .dropdown-toggle.datepicker table tr td.active:focus, +.open > .dropdown-toggle.datepicker table tr td.active.highlighted:focus, +.datepicker table tr td.active:active.focus, +.datepicker table tr td.active.highlighted:active.focus, +.datepicker table tr td.active.active.focus, +.datepicker table tr td.active.highlighted.active.focus, +.open > .dropdown-toggle.datepicker table tr td.active.focus, +.open > .dropdown-toggle.datepicker table tr td.active.highlighted.focus { + color: #ffffff; + background-color: #285e8e; + border-color: #193c5a; +} +.datepicker table tr td.active:active, +.datepicker table tr td.active.highlighted:active, +.datepicker table tr td.active.active, +.datepicker table tr td.active.highlighted.active, +.open > .dropdown-toggle.datepicker table tr td.active, +.open > .dropdown-toggle.datepicker table tr td.active.highlighted { + background-image: none; +} +.datepicker table tr td.active.disabled:hover, +.datepicker table tr td.active.highlighted.disabled:hover, +.datepicker table tr td.active[disabled]:hover, +.datepicker table tr td.active.highlighted[disabled]:hover, +fieldset[disabled] .datepicker table tr td.active:hover, +fieldset[disabled] .datepicker table tr td.active.highlighted:hover, +.datepicker table tr td.active.disabled:focus, +.datepicker table tr td.active.highlighted.disabled:focus, +.datepicker table tr td.active[disabled]:focus, +.datepicker table tr td.active.highlighted[disabled]:focus, +fieldset[disabled] .datepicker table tr td.active:focus, +fieldset[disabled] .datepicker table tr td.active.highlighted:focus, +.datepicker table tr td.active.disabled.focus, +.datepicker table tr td.active.highlighted.disabled.focus, +.datepicker table tr td.active[disabled].focus, +.datepicker table tr td.active.highlighted[disabled].focus, +fieldset[disabled] .datepicker table tr td.active.focus, +fieldset[disabled] .datepicker table tr td.active.highlighted.focus { + background-color: #428bca; + border-color: #357ebd; +} +.datepicker table tr td span { + display: block; + width: 23%; + height: 54px; + line-height: 54px; + float: left; + margin: 1%; + cursor: pointer; + border-radius: 4px; +} +.datepicker table tr td span:hover { + background: #eeeeee; +} +.datepicker table tr td span.disabled, +.datepicker table tr td span.disabled:hover { + background: none; + color: #999999; + cursor: default; +} +.datepicker table tr td span.active, +.datepicker table tr td span.active:hover, +.datepicker table tr td span.active.disabled, +.datepicker table tr td span.active.disabled:hover { + color: #ffffff; + background-color: #428bca; + border-color: #357ebd; + text-shadow: 0 -1px 0 rgba(0, 0, 0, 0.25); +} +.datepicker table tr td span.active:focus, +.datepicker table tr td span.active:hover:focus, +.datepicker table tr td span.active.disabled:focus, +.datepicker table tr td span.active.disabled:hover:focus, +.datepicker table tr td span.active.focus, +.datepicker table tr td span.active:hover.focus, +.datepicker table tr td span.active.disabled.focus, +.datepicker table tr td span.active.disabled:hover.focus { + color: #ffffff; + background-color: #3071a9; + border-color: #193c5a; +} +.datepicker table tr td span.active:hover, +.datepicker table tr td span.active:hover:hover, +.datepicker table tr td span.active.disabled:hover, +.datepicker table tr td span.active.disabled:hover:hover { + color: #ffffff; + background-color: #3071a9; + border-color: #285e8e; +} +.datepicker table tr td span.active:active, +.datepicker table tr td span.active:hover:active, +.datepicker table tr td span.active.disabled:active, +.datepicker table tr td span.active.disabled:hover:active, +.datepicker table tr td span.active.active, +.datepicker table tr td span.active:hover.active, +.datepicker table tr td span.active.disabled.active, +.datepicker table tr td span.active.disabled:hover.active, +.open > .dropdown-toggle.datepicker table tr td span.active, +.open > .dropdown-toggle.datepicker table tr td span.active:hover, +.open > .dropdown-toggle.datepicker table tr td span.active.disabled, +.open > .dropdown-toggle.datepicker table tr td span.active.disabled:hover { + color: #ffffff; + background-color: #3071a9; + border-color: #285e8e; +} +.datepicker table tr td span.active:active:hover, +.datepicker table tr td span.active:hover:active:hover, +.datepicker table tr td span.active.disabled:active:hover, +.datepicker table tr td span.active.disabled:hover:active:hover, +.datepicker table tr td span.active.active:hover, +.datepicker table tr td span.active:hover.active:hover, +.datepicker table tr td span.active.disabled.active:hover, +.datepicker table tr td span.active.disabled:hover.active:hover, +.open > .dropdown-toggle.datepicker table tr td span.active:hover, +.open > .dropdown-toggle.datepicker table tr td span.active:hover:hover, +.open > .dropdown-toggle.datepicker table tr td span.active.disabled:hover, +.open > .dropdown-toggle.datepicker table tr td span.active.disabled:hover:hover, +.datepicker table tr td span.active:active:focus, +.datepicker table tr td span.active:hover:active:focus, +.datepicker table tr td span.active.disabled:active:focus, +.datepicker table tr td span.active.disabled:hover:active:focus, +.datepicker table tr td span.active.active:focus, +.datepicker table tr td span.active:hover.active:focus, +.datepicker table tr td span.active.disabled.active:focus, +.datepicker table tr td span.active.disabled:hover.active:focus, +.open > .dropdown-toggle.datepicker table tr td span.active:focus, +.open > .dropdown-toggle.datepicker table tr td span.active:hover:focus, +.open > .dropdown-toggle.datepicker table tr td span.active.disabled:focus, +.open > .dropdown-toggle.datepicker table tr td span.active.disabled:hover:focus, +.datepicker table tr td span.active:active.focus, +.datepicker table tr td span.active:hover:active.focus, +.datepicker table tr td span.active.disabled:active.focus, +.datepicker table tr td span.active.disabled:hover:active.focus, +.datepicker table tr td span.active.active.focus, +.datepicker table tr td span.active:hover.active.focus, +.datepicker table tr td span.active.disabled.active.focus, +.datepicker table tr td span.active.disabled:hover.active.focus, +.open > .dropdown-toggle.datepicker table tr td span.active.focus, +.open > .dropdown-toggle.datepicker table tr td span.active:hover.focus, +.open > .dropdown-toggle.datepicker table tr td span.active.disabled.focus, +.open > .dropdown-toggle.datepicker table tr td span.active.disabled:hover.focus { + color: #ffffff; + background-color: #285e8e; + border-color: #193c5a; +} +.datepicker table tr td span.active:active, +.datepicker table tr td span.active:hover:active, +.datepicker table tr td span.active.disabled:active, +.datepicker table tr td span.active.disabled:hover:active, +.datepicker table tr td span.active.active, +.datepicker table tr td span.active:hover.active, +.datepicker table tr td span.active.disabled.active, +.datepicker table tr td span.active.disabled:hover.active, +.open > .dropdown-toggle.datepicker table tr td span.active, +.open > .dropdown-toggle.datepicker table tr td span.active:hover, +.open > .dropdown-toggle.datepicker table tr td span.active.disabled, +.open > .dropdown-toggle.datepicker table tr td span.active.disabled:hover { + background-image: none; +} +.datepicker table tr td span.active.disabled:hover, +.datepicker table tr td span.active:hover.disabled:hover, +.datepicker table tr td span.active.disabled.disabled:hover, +.datepicker table tr td span.active.disabled:hover.disabled:hover, +.datepicker table tr td span.active[disabled]:hover, +.datepicker table tr td span.active:hover[disabled]:hover, +.datepicker table tr td span.active.disabled[disabled]:hover, +.datepicker table tr td span.active.disabled:hover[disabled]:hover, +fieldset[disabled] .datepicker table tr td span.active:hover, +fieldset[disabled] .datepicker table tr td span.active:hover:hover, +fieldset[disabled] .datepicker table tr td span.active.disabled:hover, +fieldset[disabled] .datepicker table tr td span.active.disabled:hover:hover, +.datepicker table tr td span.active.disabled:focus, +.datepicker table tr td span.active:hover.disabled:focus, +.datepicker table tr td span.active.disabled.disabled:focus, +.datepicker table tr td span.active.disabled:hover.disabled:focus, +.datepicker table tr td span.active[disabled]:focus, +.datepicker table tr td span.active:hover[disabled]:focus, +.datepicker table tr td span.active.disabled[disabled]:focus, +.datepicker table tr td span.active.disabled:hover[disabled]:focus, +fieldset[disabled] .datepicker table tr td span.active:focus, +fieldset[disabled] .datepicker table tr td span.active:hover:focus, +fieldset[disabled] .datepicker table tr td span.active.disabled:focus, +fieldset[disabled] .datepicker table tr td span.active.disabled:hover:focus, +.datepicker table tr td span.active.disabled.focus, +.datepicker table tr td span.active:hover.disabled.focus, +.datepicker table tr td span.active.disabled.disabled.focus, +.datepicker table tr td span.active.disabled:hover.disabled.focus, +.datepicker table tr td span.active[disabled].focus, +.datepicker table tr td span.active:hover[disabled].focus, +.datepicker table tr td span.active.disabled[disabled].focus, +.datepicker table tr td span.active.disabled:hover[disabled].focus, +fieldset[disabled] .datepicker table tr td span.active.focus, +fieldset[disabled] .datepicker table tr td span.active:hover.focus, +fieldset[disabled] .datepicker table tr td span.active.disabled.focus, +fieldset[disabled] .datepicker table tr td span.active.disabled:hover.focus { + background-color: #428bca; + border-color: #357ebd; +} +.datepicker table tr td span.old, +.datepicker table tr td span.new { + color: #999999; +} +.datepicker .datepicker-switch { + width: 145px; +} +.datepicker .datepicker-switch, +.datepicker .prev, +.datepicker .next, +.datepicker tfoot tr th { + cursor: pointer; +} +.datepicker .datepicker-switch:hover, +.datepicker .prev:hover, +.datepicker .next:hover, +.datepicker tfoot tr th:hover { + background: #eeeeee; +} +.datepicker .cw { + font-size: 10px; + width: 12px; + padding: 0 2px 0 5px; + vertical-align: middle; +} +.input-group.date .input-group-addon { + cursor: pointer; +} +.input-daterange { + width: 100%; +} +.input-daterange input { + text-align: center; +} +.input-daterange input:first-child { + border-radius: 3px 0 0 3px; +} +.input-daterange input:last-child { + border-radius: 0 3px 3px 0; +} +.input-daterange .input-group-addon { + width: auto; + min-width: 16px; + padding: 4px 5px; + font-weight: normal; + line-height: 1.42857143; + text-align: center; + text-shadow: 0 1px 0 #fff; + vertical-align: middle; + background-color: #eeeeee; + border: solid #cccccc; + border-width: 1px 0; + margin-left: -5px; + margin-right: -5px; +} diff --git a/static/bootstrap3/css/bootstrap-datepicker3.min.css b/static/bootstrap3/css/bootstrap-datepicker3.min.css new file mode 100755 index 0000000..41cc778 --- /dev/null +++ b/static/bootstrap3/css/bootstrap-datepicker3.min.css @@ -0,0 +1,8 @@ +/*! + * Datepicker for Bootstrap v1.5.0-dev (https://github.com/eternicode/bootstrap-datepicker) + * + * Copyright 2012 Stefan Petre + * Improvements by Andrew Rowls + * Licensed under the Apache License v2.0 (http://www.apache.org/licenses/LICENSE-2.0) + */ +.datepicker{border-radius:4px;direction:ltr}.datepicker-inline{width:220px}.datepicker.datepicker-rtl{direction:rtl}.datepicker.datepicker-rtl table tr td span{float:right}.datepicker-dropdown{top:0;left:0;padding:4px}.datepicker-dropdown:before{content:'';display:inline-block;border-left:7px solid transparent;border-right:7px solid transparent;border-bottom:7px solid rgba(0,0,0,.15);border-top:0;border-bottom-color:rgba(0,0,0,.2);position:absolute}.datepicker-dropdown:after{content:'';display:inline-block;border-left:6px solid transparent;border-right:6px solid transparent;border-bottom:6px solid #fff;border-top:0;position:absolute}.datepicker-dropdown.datepicker-orient-left:before{left:6px}.datepicker-dropdown.datepicker-orient-left:after{left:7px}.datepicker-dropdown.datepicker-orient-right:before{right:6px}.datepicker-dropdown.datepicker-orient-right:after{right:7px}.datepicker-dropdown.datepicker-orient-bottom:before{top:-7px}.datepicker-dropdown.datepicker-orient-bottom:after{top:-6px}.datepicker-dropdown.datepicker-orient-top:before{bottom:-7px;border-bottom:0;border-top:7px solid rgba(0,0,0,.15)}.datepicker-dropdown.datepicker-orient-top:after{bottom:-6px;border-bottom:0;border-top:6px solid #fff}.datepicker>div{display:none}.datepicker table{margin:0;-webkit-touch-callout:none;-webkit-user-select:none;-khtml-user-select:none;-moz-user-select:none;-ms-user-select:none;user-select:none}.datepicker table tr td,.datepicker table tr th{text-align:center;width:30px;height:30px;border-radius:4px;border:none}.table-striped .datepicker table tr td,.table-striped .datepicker table tr th{background-color:transparent}.datepicker table tr td.old,.datepicker table tr td.new{color:#999}.datepicker table tr td.day:hover,.datepicker table tr td.focused{background:#eee;cursor:pointer}.datepicker table tr td.disabled,.datepicker table tr td.disabled:hover{background:0 0;color:#999;cursor:default}.datepicker table tr td.highlighted{color:#000;background-color:#d9edf7;border-color:#85c5e5;border-radius:0}.datepicker table tr td.highlighted:focus,.datepicker table tr td.highlighted.focus{color:#000;background-color:#afd9ee;border-color:#298fc2}.datepicker table tr td.highlighted:hover{color:#000;background-color:#afd9ee;border-color:#52addb}.datepicker table tr td.highlighted:active,.datepicker table tr td.highlighted.active,.open>.dropdown-toggle.datepicker table tr td.highlighted{color:#000;background-color:#afd9ee;border-color:#52addb}.datepicker table tr td.highlighted:active:hover,.datepicker table tr td.highlighted.active:hover,.open>.dropdown-toggle.datepicker table tr td.highlighted:hover,.datepicker table tr td.highlighted:active:focus,.datepicker table tr td.highlighted.active:focus,.open>.dropdown-toggle.datepicker table tr td.highlighted:focus,.datepicker table tr td.highlighted:active.focus,.datepicker table tr td.highlighted.active.focus,.open>.dropdown-toggle.datepicker table tr td.highlighted.focus{color:#000;background-color:#91cbe8;border-color:#298fc2}.datepicker table tr td.highlighted:active,.datepicker table tr td.highlighted.active,.open>.dropdown-toggle.datepicker table tr td.highlighted{background-image:none}.datepicker table tr td.highlighted.disabled:hover,.datepicker table tr td.highlighted[disabled]:hover,fieldset[disabled] .datepicker table tr td.highlighted:hover,.datepicker table tr td.highlighted.disabled:focus,.datepicker table tr td.highlighted[disabled]:focus,fieldset[disabled] .datepicker table tr td.highlighted:focus,.datepicker table tr td.highlighted.disabled.focus,.datepicker table tr td.highlighted[disabled].focus,fieldset[disabled] .datepicker table tr td.highlighted.focus{background-color:#d9edf7;border-color:#85c5e5}.datepicker table tr td.highlighted.focused{background:#afd9ee}.datepicker table tr td.highlighted.disabled,.datepicker table tr td.highlighted.disabled:active{background:#d9edf7;color:#999}.datepicker table tr td.today{color:#000;background-color:#ffdb99;border-color:#ffb733}.datepicker table tr td.today:focus,.datepicker table tr td.today.focus{color:#000;background-color:#ffc966;border-color:#b37400}.datepicker table tr td.today:hover{color:#000;background-color:#ffc966;border-color:#f59e00}.datepicker table tr td.today:active,.datepicker table tr td.today.active,.open>.dropdown-toggle.datepicker table tr td.today{color:#000;background-color:#ffc966;border-color:#f59e00}.datepicker table tr td.today:active:hover,.datepicker table tr td.today.active:hover,.open>.dropdown-toggle.datepicker table tr td.today:hover,.datepicker table tr td.today:active:focus,.datepicker table tr td.today.active:focus,.open>.dropdown-toggle.datepicker table tr td.today:focus,.datepicker table tr td.today:active.focus,.datepicker table tr td.today.active.focus,.open>.dropdown-toggle.datepicker table tr td.today.focus{color:#000;background-color:#ffbc42;border-color:#b37400}.datepicker table tr td.today:active,.datepicker table tr td.today.active,.open>.dropdown-toggle.datepicker table tr td.today{background-image:none}.datepicker table tr td.today.disabled:hover,.datepicker table tr td.today[disabled]:hover,fieldset[disabled] .datepicker table tr td.today:hover,.datepicker table tr td.today.disabled:focus,.datepicker table tr td.today[disabled]:focus,fieldset[disabled] .datepicker table tr td.today:focus,.datepicker table tr td.today.disabled.focus,.datepicker table tr td.today[disabled].focus,fieldset[disabled] .datepicker table tr td.today.focus{background-color:#ffdb99;border-color:#ffb733}.datepicker table tr td.today.focused{background:#ffc966}.datepicker table tr td.today.disabled,.datepicker table tr td.today.disabled:active{background:#ffdb99;color:#999}.datepicker table tr td.range{color:#000;background-color:#eee;border-color:#bbb;border-radius:0}.datepicker table tr td.range:focus,.datepicker table tr td.range.focus{color:#000;background-color:#d5d5d5;border-color:#7c7c7c}.datepicker table tr td.range:hover{color:#000;background-color:#d5d5d5;border-color:#9d9d9d}.datepicker table tr td.range:active,.datepicker table tr td.range.active,.open>.dropdown-toggle.datepicker table tr td.range{color:#000;background-color:#d5d5d5;border-color:#9d9d9d}.datepicker table tr td.range:active:hover,.datepicker table tr td.range.active:hover,.open>.dropdown-toggle.datepicker table tr td.range:hover,.datepicker table tr td.range:active:focus,.datepicker table tr td.range.active:focus,.open>.dropdown-toggle.datepicker table tr td.range:focus,.datepicker table tr td.range:active.focus,.datepicker table tr td.range.active.focus,.open>.dropdown-toggle.datepicker table tr td.range.focus{color:#000;background-color:#c3c3c3;border-color:#7c7c7c}.datepicker table tr td.range:active,.datepicker table tr td.range.active,.open>.dropdown-toggle.datepicker table tr td.range{background-image:none}.datepicker table tr td.range.disabled:hover,.datepicker table tr td.range[disabled]:hover,fieldset[disabled] .datepicker table tr td.range:hover,.datepicker table tr td.range.disabled:focus,.datepicker table tr td.range[disabled]:focus,fieldset[disabled] .datepicker table tr td.range:focus,.datepicker table tr td.range.disabled.focus,.datepicker table tr td.range[disabled].focus,fieldset[disabled] .datepicker table tr td.range.focus{background-color:#eee;border-color:#bbb}.datepicker table tr td.range.focused{background:#d5d5d5}.datepicker table tr td.range.disabled,.datepicker table tr td.range.disabled:active{background:#eee;color:#999}.datepicker table tr td.range.highlighted{color:#000;background-color:#e4eef3;border-color:#9dc1d3}.datepicker table tr td.range.highlighted:focus,.datepicker table tr td.range.highlighted.focus{color:#000;background-color:#c1d7e3;border-color:#4b88a6}.datepicker table tr td.range.highlighted:hover{color:#000;background-color:#c1d7e3;border-color:#73a6c0}.datepicker table tr td.range.highlighted:active,.datepicker table tr td.range.highlighted.active,.open>.dropdown-toggle.datepicker table tr td.range.highlighted{color:#000;background-color:#c1d7e3;border-color:#73a6c0}.datepicker table tr td.range.highlighted:active:hover,.datepicker table tr td.range.highlighted.active:hover,.open>.dropdown-toggle.datepicker table tr td.range.highlighted:hover,.datepicker table tr td.range.highlighted:active:focus,.datepicker table tr td.range.highlighted.active:focus,.open>.dropdown-toggle.datepicker table tr td.range.highlighted:focus,.datepicker table tr td.range.highlighted:active.focus,.datepicker table tr td.range.highlighted.active.focus,.open>.dropdown-toggle.datepicker table tr td.range.highlighted.focus{color:#000;background-color:#a8c8d8;border-color:#4b88a6}.datepicker table tr td.range.highlighted:active,.datepicker table tr td.range.highlighted.active,.open>.dropdown-toggle.datepicker table tr td.range.highlighted{background-image:none}.datepicker table tr td.range.highlighted.disabled:hover,.datepicker table tr td.range.highlighted[disabled]:hover,fieldset[disabled] .datepicker table tr td.range.highlighted:hover,.datepicker table tr td.range.highlighted.disabled:focus,.datepicker table tr td.range.highlighted[disabled]:focus,fieldset[disabled] .datepicker table tr td.range.highlighted:focus,.datepicker table tr td.range.highlighted.disabled.focus,.datepicker table tr td.range.highlighted[disabled].focus,fieldset[disabled] .datepicker table tr td.range.highlighted.focus{background-color:#e4eef3;border-color:#9dc1d3}.datepicker table tr td.range.highlighted.focused{background:#c1d7e3}.datepicker table tr td.range.highlighted.disabled,.datepicker table tr td.range.highlighted.disabled:active{background:#e4eef3;color:#999}.datepicker table tr td.range.today{color:#000;background-color:#f7ca77;border-color:#f1a417}.datepicker table tr td.range.today:focus,.datepicker table tr td.range.today.focus{color:#000;background-color:#f4b747;border-color:#815608}.datepicker table tr td.range.today:hover{color:#000;background-color:#f4b747;border-color:#bf800c}.datepicker table tr td.range.today:active,.datepicker table tr td.range.today.active,.open>.dropdown-toggle.datepicker table tr td.range.today{color:#000;background-color:#f4b747;border-color:#bf800c}.datepicker table tr td.range.today:active:hover,.datepicker table tr td.range.today.active:hover,.open>.dropdown-toggle.datepicker table tr td.range.today:hover,.datepicker table tr td.range.today:active:focus,.datepicker table tr td.range.today.active:focus,.open>.dropdown-toggle.datepicker table tr td.range.today:focus,.datepicker table tr td.range.today:active.focus,.datepicker table tr td.range.today.active.focus,.open>.dropdown-toggle.datepicker table tr td.range.today.focus{color:#000;background-color:#f2aa25;border-color:#815608}.datepicker table tr td.range.today:active,.datepicker table tr td.range.today.active,.open>.dropdown-toggle.datepicker table tr td.range.today{background-image:none}.datepicker table tr td.range.today.disabled:hover,.datepicker table tr td.range.today[disabled]:hover,fieldset[disabled] .datepicker table tr td.range.today:hover,.datepicker table tr td.range.today.disabled:focus,.datepicker table tr td.range.today[disabled]:focus,fieldset[disabled] .datepicker table tr td.range.today:focus,.datepicker table tr td.range.today.disabled.focus,.datepicker table tr td.range.today[disabled].focus,fieldset[disabled] .datepicker table tr td.range.today.focus{background-color:#f7ca77;border-color:#f1a417}.datepicker table tr td.range.today.disabled,.datepicker table tr td.range.today.disabled:active{background:#f7ca77;color:#999}.datepicker table tr td.selected,.datepicker table tr td.selected.highlighted{color:#fff;background-color:#999;border-color:#555;text-shadow:0 -1px 0 rgba(0,0,0,.25)}.datepicker table tr td.selected:focus,.datepicker table tr td.selected.highlighted:focus,.datepicker table tr td.selected.focus,.datepicker table tr td.selected.highlighted.focus{color:#fff;background-color:gray;border-color:#161616}.datepicker table tr td.selected:hover,.datepicker table tr td.selected.highlighted:hover{color:#fff;background-color:gray;border-color:#373737}.datepicker table tr td.selected:active,.datepicker table tr td.selected.highlighted:active,.datepicker table tr td.selected.active,.datepicker table tr td.selected.highlighted.active,.open>.dropdown-toggle.datepicker table tr td.selected,.open>.dropdown-toggle.datepicker table tr td.selected.highlighted{color:#fff;background-color:gray;border-color:#373737}.datepicker table tr td.selected:active:hover,.datepicker table tr td.selected.highlighted:active:hover,.datepicker table tr td.selected.active:hover,.datepicker table tr td.selected.highlighted.active:hover,.open>.dropdown-toggle.datepicker table tr td.selected:hover,.open>.dropdown-toggle.datepicker table tr td.selected.highlighted:hover,.datepicker table tr td.selected:active:focus,.datepicker table tr td.selected.highlighted:active:focus,.datepicker table tr td.selected.active:focus,.datepicker table tr td.selected.highlighted.active:focus,.open>.dropdown-toggle.datepicker table tr td.selected:focus,.open>.dropdown-toggle.datepicker table tr td.selected.highlighted:focus,.datepicker table tr td.selected:active.focus,.datepicker table tr td.selected.highlighted:active.focus,.datepicker table tr td.selected.active.focus,.datepicker table tr td.selected.highlighted.active.focus,.open>.dropdown-toggle.datepicker table tr td.selected.focus,.open>.dropdown-toggle.datepicker table tr td.selected.highlighted.focus{color:#fff;background-color:#6e6e6e;border-color:#161616}.datepicker table tr td.selected:active,.datepicker table tr td.selected.highlighted:active,.datepicker table tr td.selected.active,.datepicker table tr td.selected.highlighted.active,.open>.dropdown-toggle.datepicker table tr td.selected,.open>.dropdown-toggle.datepicker table tr td.selected.highlighted{background-image:none}.datepicker table tr td.selected.disabled:hover,.datepicker table tr td.selected.highlighted.disabled:hover,.datepicker table tr td.selected[disabled]:hover,.datepicker table tr td.selected.highlighted[disabled]:hover,fieldset[disabled] .datepicker table tr td.selected:hover,fieldset[disabled] .datepicker table tr td.selected.highlighted:hover,.datepicker table tr td.selected.disabled:focus,.datepicker table tr td.selected.highlighted.disabled:focus,.datepicker table tr td.selected[disabled]:focus,.datepicker table tr td.selected.highlighted[disabled]:focus,fieldset[disabled] .datepicker table tr td.selected:focus,fieldset[disabled] .datepicker table tr td.selected.highlighted:focus,.datepicker table tr td.selected.disabled.focus,.datepicker table tr td.selected.highlighted.disabled.focus,.datepicker table tr td.selected[disabled].focus,.datepicker table tr td.selected.highlighted[disabled].focus,fieldset[disabled] .datepicker table tr td.selected.focus,fieldset[disabled] .datepicker table tr td.selected.highlighted.focus{background-color:#999;border-color:#555}.datepicker table tr td.active,.datepicker table tr td.active.highlighted{color:#fff;background-color:#428bca;border-color:#357ebd;text-shadow:0 -1px 0 rgba(0,0,0,.25)}.datepicker table tr td.active:focus,.datepicker table tr td.active.highlighted:focus,.datepicker table tr td.active.focus,.datepicker table tr td.active.highlighted.focus{color:#fff;background-color:#3071a9;border-color:#193c5a}.datepicker table tr td.active:hover,.datepicker table tr td.active.highlighted:hover{color:#fff;background-color:#3071a9;border-color:#285e8e}.datepicker table tr td.active:active,.datepicker table tr td.active.highlighted:active,.datepicker table tr td.active.active,.datepicker table tr td.active.highlighted.active,.open>.dropdown-toggle.datepicker table tr td.active,.open>.dropdown-toggle.datepicker table tr td.active.highlighted{color:#fff;background-color:#3071a9;border-color:#285e8e}.datepicker table tr td.active:active:hover,.datepicker table tr td.active.highlighted:active:hover,.datepicker table tr td.active.active:hover,.datepicker table tr td.active.highlighted.active:hover,.open>.dropdown-toggle.datepicker table tr td.active:hover,.open>.dropdown-toggle.datepicker table tr td.active.highlighted:hover,.datepicker table tr td.active:active:focus,.datepicker table tr td.active.highlighted:active:focus,.datepicker table tr td.active.active:focus,.datepicker table tr td.active.highlighted.active:focus,.open>.dropdown-toggle.datepicker table tr td.active:focus,.open>.dropdown-toggle.datepicker table tr td.active.highlighted:focus,.datepicker table tr td.active:active.focus,.datepicker table tr td.active.highlighted:active.focus,.datepicker table tr td.active.active.focus,.datepicker table tr td.active.highlighted.active.focus,.open>.dropdown-toggle.datepicker table tr td.active.focus,.open>.dropdown-toggle.datepicker table tr td.active.highlighted.focus{color:#fff;background-color:#285e8e;border-color:#193c5a}.datepicker table tr td.active:active,.datepicker table tr td.active.highlighted:active,.datepicker table tr td.active.active,.datepicker table tr td.active.highlighted.active,.open>.dropdown-toggle.datepicker table tr td.active,.open>.dropdown-toggle.datepicker table tr td.active.highlighted{background-image:none}.datepicker table tr td.active.disabled:hover,.datepicker table tr td.active.highlighted.disabled:hover,.datepicker table tr td.active[disabled]:hover,.datepicker table tr td.active.highlighted[disabled]:hover,fieldset[disabled] .datepicker table tr td.active:hover,fieldset[disabled] .datepicker table tr td.active.highlighted:hover,.datepicker table tr td.active.disabled:focus,.datepicker table tr td.active.highlighted.disabled:focus,.datepicker table tr td.active[disabled]:focus,.datepicker table tr td.active.highlighted[disabled]:focus,fieldset[disabled] .datepicker table tr td.active:focus,fieldset[disabled] .datepicker table tr td.active.highlighted:focus,.datepicker table tr td.active.disabled.focus,.datepicker table tr td.active.highlighted.disabled.focus,.datepicker table tr td.active[disabled].focus,.datepicker table tr td.active.highlighted[disabled].focus,fieldset[disabled] .datepicker table tr td.active.focus,fieldset[disabled] .datepicker table tr td.active.highlighted.focus{background-color:#428bca;border-color:#357ebd}.datepicker table tr td span{display:block;width:23%;height:54px;line-height:54px;float:left;margin:1%;cursor:pointer;border-radius:4px}.datepicker table tr td span:hover{background:#eee}.datepicker table tr td span.disabled,.datepicker table tr td span.disabled:hover{background:0 0;color:#999;cursor:default}.datepicker table tr td span.active,.datepicker table tr td span.active:hover,.datepicker table tr td span.active.disabled,.datepicker table tr td span.active.disabled:hover{color:#fff;background-color:#428bca;border-color:#357ebd;text-shadow:0 -1px 0 rgba(0,0,0,.25)}.datepicker table tr td span.active:focus,.datepicker table tr td span.active:hover:focus,.datepicker table tr td span.active.disabled:focus,.datepicker table tr td span.active.disabled:hover:focus,.datepicker table tr td span.active.focus,.datepicker table tr td span.active:hover.focus,.datepicker table tr td span.active.disabled.focus,.datepicker table tr td span.active.disabled:hover.focus{color:#fff;background-color:#3071a9;border-color:#193c5a}.datepicker table tr td span.active:hover,.datepicker table tr td span.active:hover:hover,.datepicker table tr td span.active.disabled:hover,.datepicker table tr td span.active.disabled:hover:hover{color:#fff;background-color:#3071a9;border-color:#285e8e}.datepicker table tr td span.active:active,.datepicker table tr td span.active:hover:active,.datepicker table tr td span.active.disabled:active,.datepicker table tr td span.active.disabled:hover:active,.datepicker table tr td span.active.active,.datepicker table tr td span.active:hover.active,.datepicker table tr td span.active.disabled.active,.datepicker table tr td span.active.disabled:hover.active,.open>.dropdown-toggle.datepicker table tr td span.active,.open>.dropdown-toggle.datepicker table tr td span.active:hover,.open>.dropdown-toggle.datepicker table tr td span.active.disabled,.open>.dropdown-toggle.datepicker table tr td span.active.disabled:hover{color:#fff;background-color:#3071a9;border-color:#285e8e}.datepicker table tr td span.active:active:hover,.datepicker table tr td span.active:hover:active:hover,.datepicker table tr td span.active.disabled:active:hover,.datepicker table tr td span.active.disabled:hover:active:hover,.datepicker table tr td span.active.active:hover,.datepicker table tr td span.active:hover.active:hover,.datepicker table tr td span.active.disabled.active:hover,.datepicker table tr td span.active.disabled:hover.active:hover,.open>.dropdown-toggle.datepicker table tr td span.active:hover,.open>.dropdown-toggle.datepicker table tr td span.active:hover:hover,.open>.dropdown-toggle.datepicker table tr td span.active.disabled:hover,.open>.dropdown-toggle.datepicker table tr td span.active.disabled:hover:hover,.datepicker table tr td span.active:active:focus,.datepicker table tr td span.active:hover:active:focus,.datepicker table tr td span.active.disabled:active:focus,.datepicker table tr td span.active.disabled:hover:active:focus,.datepicker table tr td span.active.active:focus,.datepicker table tr td span.active:hover.active:focus,.datepicker table tr td span.active.disabled.active:focus,.datepicker table tr td span.active.disabled:hover.active:focus,.open>.dropdown-toggle.datepicker table tr td span.active:focus,.open>.dropdown-toggle.datepicker table tr td span.active:hover:focus,.open>.dropdown-toggle.datepicker table tr td span.active.disabled:focus,.open>.dropdown-toggle.datepicker table tr td span.active.disabled:hover:focus,.datepicker table tr td span.active:active.focus,.datepicker table tr td span.active:hover:active.focus,.datepicker table tr td span.active.disabled:active.focus,.datepicker table tr td span.active.disabled:hover:active.focus,.datepicker table tr td span.active.active.focus,.datepicker table tr td span.active:hover.active.focus,.datepicker table tr td span.active.disabled.active.focus,.datepicker table tr td span.active.disabled:hover.active.focus,.open>.dropdown-toggle.datepicker table tr td span.active.focus,.open>.dropdown-toggle.datepicker table tr td span.active:hover.focus,.open>.dropdown-toggle.datepicker table tr td span.active.disabled.focus,.open>.dropdown-toggle.datepicker table tr td span.active.disabled:hover.focus{color:#fff;background-color:#285e8e;border-color:#193c5a}.datepicker table tr td span.active:active,.datepicker table tr td span.active:hover:active,.datepicker table tr td span.active.disabled:active,.datepicker table tr td span.active.disabled:hover:active,.datepicker table tr td span.active.active,.datepicker table tr td span.active:hover.active,.datepicker table tr td span.active.disabled.active,.datepicker table tr td span.active.disabled:hover.active,.open>.dropdown-toggle.datepicker table tr td span.active,.open>.dropdown-toggle.datepicker table tr td span.active:hover,.open>.dropdown-toggle.datepicker table tr td span.active.disabled,.open>.dropdown-toggle.datepicker table tr td span.active.disabled:hover{background-image:none}.datepicker table tr td span.active.disabled:hover,.datepicker table tr td span.active:hover.disabled:hover,.datepicker table tr td span.active.disabled.disabled:hover,.datepicker table tr td span.active.disabled:hover.disabled:hover,.datepicker table tr td span.active[disabled]:hover,.datepicker table tr td span.active:hover[disabled]:hover,.datepicker table tr td span.active.disabled[disabled]:hover,.datepicker table tr td span.active.disabled:hover[disabled]:hover,fieldset[disabled] .datepicker table tr td span.active:hover,fieldset[disabled] .datepicker table tr td span.active:hover:hover,fieldset[disabled] .datepicker table tr td span.active.disabled:hover,fieldset[disabled] .datepicker table tr td span.active.disabled:hover:hover,.datepicker table tr td span.active.disabled:focus,.datepicker table tr td span.active:hover.disabled:focus,.datepicker table tr td span.active.disabled.disabled:focus,.datepicker table tr td span.active.disabled:hover.disabled:focus,.datepicker table tr td span.active[disabled]:focus,.datepicker table tr td span.active:hover[disabled]:focus,.datepicker table tr td span.active.disabled[disabled]:focus,.datepicker table tr td span.active.disabled:hover[disabled]:focus,fieldset[disabled] .datepicker table tr td span.active:focus,fieldset[disabled] .datepicker table tr td span.active:hover:focus,fieldset[disabled] .datepicker table tr td span.active.disabled:focus,fieldset[disabled] .datepicker table tr td span.active.disabled:hover:focus,.datepicker table tr td span.active.disabled.focus,.datepicker table tr td span.active:hover.disabled.focus,.datepicker table tr td span.active.disabled.disabled.focus,.datepicker table tr td span.active.disabled:hover.disabled.focus,.datepicker table tr td span.active[disabled].focus,.datepicker table tr td span.active:hover[disabled].focus,.datepicker table tr td span.active.disabled[disabled].focus,.datepicker table tr td span.active.disabled:hover[disabled].focus,fieldset[disabled] .datepicker table tr td span.active.focus,fieldset[disabled] .datepicker table tr td span.active:hover.focus,fieldset[disabled] .datepicker table tr td span.active.disabled.focus,fieldset[disabled] .datepicker table tr td span.active.disabled:hover.focus{background-color:#428bca;border-color:#357ebd}.datepicker table tr td span.old,.datepicker table tr td span.new{color:#999}.datepicker .datepicker-switch{width:145px}.datepicker .datepicker-switch,.datepicker .prev,.datepicker .next,.datepicker tfoot tr th{cursor:pointer}.datepicker .datepicker-switch:hover,.datepicker .prev:hover,.datepicker .next:hover,.datepicker tfoot tr th:hover{background:#eee}.datepicker .cw{font-size:10px;width:12px;padding:0 2px 0 5px;vertical-align:middle}.input-group.date .input-group-addon{cursor:pointer}.input-daterange{width:100%}.input-daterange input{text-align:center}.input-daterange input:first-child{border-radius:3px 0 0 3px}.input-daterange input:last-child{border-radius:0 3px 3px 0}.input-daterange .input-group-addon{width:auto;min-width:16px;padding:4px 5px;font-weight:400;line-height:1.42857143;text-align:center;text-shadow:0 1px 0 #fff;vertical-align:middle;background-color:#eee;border:solid #ccc;border-width:1px 0;margin-left:-5px;margin-right:-5px} \ No newline at end of file diff --git a/static/bootstrap3/css/bootstrap-datepicker3.standalone.css b/static/bootstrap3/css/bootstrap-datepicker3.standalone.css new file mode 100755 index 0000000..3d68a7f --- /dev/null +++ b/static/bootstrap3/css/bootstrap-datepicker3.standalone.css @@ -0,0 +1,822 @@ +/*! + * Datepicker for Bootstrap v1.5.0-dev (https://github.com/eternicode/bootstrap-datepicker) + * + * Copyright 2012 Stefan Petre + * Improvements by Andrew Rowls + * Licensed under the Apache License v2.0 (http://www.apache.org/licenses/LICENSE-2.0) + */ +.datepicker { + border-radius: 4px; + direction: ltr; +} +.datepicker-inline { + width: 220px; +} +.datepicker.datepicker-rtl { + direction: rtl; +} +.datepicker.datepicker-rtl table tr td span { + float: right; +} +.datepicker-dropdown { + top: 0; + left: 0; + padding: 4px; +} +.datepicker-dropdown:before { + content: ''; + display: inline-block; + border-left: 7px solid transparent; + border-right: 7px solid transparent; + border-bottom: 7px solid rgba(0, 0, 0, 0.15); + border-top: 0; + border-bottom-color: rgba(0, 0, 0, 0.2); + position: absolute; +} +.datepicker-dropdown:after { + content: ''; + display: inline-block; + border-left: 6px solid transparent; + border-right: 6px solid transparent; + border-bottom: 6px solid #ffffff; + border-top: 0; + position: absolute; +} +.datepicker-dropdown.datepicker-orient-left:before { + left: 6px; +} +.datepicker-dropdown.datepicker-orient-left:after { + left: 7px; +} +.datepicker-dropdown.datepicker-orient-right:before { + right: 6px; +} +.datepicker-dropdown.datepicker-orient-right:after { + right: 7px; +} +.datepicker-dropdown.datepicker-orient-bottom:before { + top: -7px; +} +.datepicker-dropdown.datepicker-orient-bottom:after { + top: -6px; +} +.datepicker-dropdown.datepicker-orient-top:before { + bottom: -7px; + border-bottom: 0; + border-top: 7px solid rgba(0, 0, 0, 0.15); +} +.datepicker-dropdown.datepicker-orient-top:after { + bottom: -6px; + border-bottom: 0; + border-top: 6px solid #ffffff; +} +.datepicker > div { + display: none; +} +.datepicker table { + margin: 0; + -webkit-touch-callout: none; + -webkit-user-select: none; + -khtml-user-select: none; + -moz-user-select: none; + -ms-user-select: none; + user-select: none; +} +.datepicker table tr td, +.datepicker table tr th { + text-align: center; + width: 30px; + height: 30px; + border-radius: 4px; + border: none; +} +.table-striped .datepicker table tr td, +.table-striped .datepicker table tr th { + background-color: transparent; +} +.datepicker table tr td.old, +.datepicker table tr td.new { + color: #999999; +} +.datepicker table tr td.day:hover, +.datepicker table tr td.focused { + background: #eeeeee; + cursor: pointer; +} +.datepicker table tr td.disabled, +.datepicker table tr td.disabled:hover { + background: none; + color: #999999; + cursor: default; +} +.datepicker table tr td.highlighted { + color: #000000; + background-color: #d9edf7; + border-color: #85c5e5; + border-radius: 0; +} +.datepicker table tr td.highlighted:focus, +.datepicker table tr td.highlighted.focus { + color: #000000; + background-color: #afd9ee; + border-color: #298fc2; +} +.datepicker table tr td.highlighted:hover { + color: #000000; + background-color: #afd9ee; + border-color: #52addb; +} +.datepicker table tr td.highlighted:active, +.datepicker table tr td.highlighted.active, +.open > .dropdown-toggle.datepicker table tr td.highlighted { + color: #000000; + background-color: #afd9ee; + border-color: #52addb; +} +.datepicker table tr td.highlighted:active:hover, +.datepicker table tr td.highlighted.active:hover, +.open > .dropdown-toggle.datepicker table tr td.highlighted:hover, +.datepicker table tr td.highlighted:active:focus, +.datepicker table tr td.highlighted.active:focus, +.open > .dropdown-toggle.datepicker table tr td.highlighted:focus, +.datepicker table tr td.highlighted:active.focus, +.datepicker table tr td.highlighted.active.focus, +.open > .dropdown-toggle.datepicker table tr td.highlighted.focus { + color: #000000; + background-color: #91cbe8; + border-color: #298fc2; +} +.datepicker table tr td.highlighted:active, +.datepicker table tr td.highlighted.active, +.open > .dropdown-toggle.datepicker table tr td.highlighted { + background-image: none; +} +.datepicker table tr td.highlighted.disabled:hover, +.datepicker table tr td.highlighted[disabled]:hover, +fieldset[disabled] .datepicker table tr td.highlighted:hover, +.datepicker table tr td.highlighted.disabled:focus, +.datepicker table tr td.highlighted[disabled]:focus, +fieldset[disabled] .datepicker table tr td.highlighted:focus, +.datepicker table tr td.highlighted.disabled.focus, +.datepicker table tr td.highlighted[disabled].focus, +fieldset[disabled] .datepicker table tr td.highlighted.focus { + background-color: #d9edf7; + border-color: #85c5e5; +} +.datepicker table tr td.highlighted.focused { + background: #afd9ee; +} +.datepicker table tr td.highlighted.disabled, +.datepicker table tr td.highlighted.disabled:active { + background: #d9edf7; + color: #999999; +} +.datepicker table tr td.today { + color: #000000; + background-color: #ffdb99; + border-color: #ffb733; +} +.datepicker table tr td.today:focus, +.datepicker table tr td.today.focus { + color: #000000; + background-color: #ffc966; + border-color: #b37400; +} +.datepicker table tr td.today:hover { + color: #000000; + background-color: #ffc966; + border-color: #f59e00; +} +.datepicker table tr td.today:active, +.datepicker table tr td.today.active, +.open > .dropdown-toggle.datepicker table tr td.today { + color: #000000; + background-color: #ffc966; + border-color: #f59e00; +} +.datepicker table tr td.today:active:hover, +.datepicker table tr td.today.active:hover, +.open > .dropdown-toggle.datepicker table tr td.today:hover, +.datepicker table tr td.today:active:focus, +.datepicker table tr td.today.active:focus, +.open > .dropdown-toggle.datepicker table tr td.today:focus, +.datepicker table tr td.today:active.focus, +.datepicker table tr td.today.active.focus, +.open > .dropdown-toggle.datepicker table tr td.today.focus { + color: #000000; + background-color: #ffbc42; + border-color: #b37400; +} +.datepicker table tr td.today:active, +.datepicker table tr td.today.active, +.open > .dropdown-toggle.datepicker table tr td.today { + background-image: none; +} +.datepicker table tr td.today.disabled:hover, +.datepicker table tr td.today[disabled]:hover, +fieldset[disabled] .datepicker table tr td.today:hover, +.datepicker table tr td.today.disabled:focus, +.datepicker table tr td.today[disabled]:focus, +fieldset[disabled] .datepicker table tr td.today:focus, +.datepicker table tr td.today.disabled.focus, +.datepicker table tr td.today[disabled].focus, +fieldset[disabled] .datepicker table tr td.today.focus { + background-color: #ffdb99; + border-color: #ffb733; +} +.datepicker table tr td.today.focused { + background: #ffc966; +} +.datepicker table tr td.today.disabled, +.datepicker table tr td.today.disabled:active { + background: #ffdb99; + color: #999999; +} +.datepicker table tr td.range { + color: #000000; + background-color: #eeeeee; + border-color: #bbbbbb; + border-radius: 0; +} +.datepicker table tr td.range:focus, +.datepicker table tr td.range.focus { + color: #000000; + background-color: #d5d5d5; + border-color: #7c7c7c; +} +.datepicker table tr td.range:hover { + color: #000000; + background-color: #d5d5d5; + border-color: #9d9d9d; +} +.datepicker table tr td.range:active, +.datepicker table tr td.range.active, +.open > .dropdown-toggle.datepicker table tr td.range { + color: #000000; + background-color: #d5d5d5; + border-color: #9d9d9d; +} +.datepicker table tr td.range:active:hover, +.datepicker table tr td.range.active:hover, +.open > .dropdown-toggle.datepicker table tr td.range:hover, +.datepicker table tr td.range:active:focus, +.datepicker table tr td.range.active:focus, +.open > .dropdown-toggle.datepicker table tr td.range:focus, +.datepicker table tr td.range:active.focus, +.datepicker table tr td.range.active.focus, +.open > .dropdown-toggle.datepicker table tr td.range.focus { + color: #000000; + background-color: #c3c3c3; + border-color: #7c7c7c; +} +.datepicker table tr td.range:active, +.datepicker table tr td.range.active, +.open > .dropdown-toggle.datepicker table tr td.range { + background-image: none; +} +.datepicker table tr td.range.disabled:hover, +.datepicker table tr td.range[disabled]:hover, +fieldset[disabled] .datepicker table tr td.range:hover, +.datepicker table tr td.range.disabled:focus, +.datepicker table tr td.range[disabled]:focus, +fieldset[disabled] .datepicker table tr td.range:focus, +.datepicker table tr td.range.disabled.focus, +.datepicker table tr td.range[disabled].focus, +fieldset[disabled] .datepicker table tr td.range.focus { + background-color: #eeeeee; + border-color: #bbbbbb; +} +.datepicker table tr td.range.focused { + background: #d5d5d5; +} +.datepicker table tr td.range.disabled, +.datepicker table tr td.range.disabled:active { + background: #eeeeee; + color: #999999; +} +.datepicker table tr td.range.highlighted { + color: #000000; + background-color: #e4eef3; + border-color: #9dc1d3; +} +.datepicker table tr td.range.highlighted:focus, +.datepicker table tr td.range.highlighted.focus { + color: #000000; + background-color: #c1d7e3; + border-color: #4b88a6; +} +.datepicker table tr td.range.highlighted:hover { + color: #000000; + background-color: #c1d7e3; + border-color: #73a6c0; +} +.datepicker table tr td.range.highlighted:active, +.datepicker table tr td.range.highlighted.active, +.open > .dropdown-toggle.datepicker table tr td.range.highlighted { + color: #000000; + background-color: #c1d7e3; + border-color: #73a6c0; +} +.datepicker table tr td.range.highlighted:active:hover, +.datepicker table tr td.range.highlighted.active:hover, +.open > .dropdown-toggle.datepicker table tr td.range.highlighted:hover, +.datepicker table tr td.range.highlighted:active:focus, +.datepicker table tr td.range.highlighted.active:focus, +.open > .dropdown-toggle.datepicker table tr td.range.highlighted:focus, +.datepicker table tr td.range.highlighted:active.focus, +.datepicker table tr td.range.highlighted.active.focus, +.open > .dropdown-toggle.datepicker table tr td.range.highlighted.focus { + color: #000000; + background-color: #a8c8d8; + border-color: #4b88a6; +} +.datepicker table tr td.range.highlighted:active, +.datepicker table tr td.range.highlighted.active, +.open > .dropdown-toggle.datepicker table tr td.range.highlighted { + background-image: none; +} +.datepicker table tr td.range.highlighted.disabled:hover, +.datepicker table tr td.range.highlighted[disabled]:hover, +fieldset[disabled] .datepicker table tr td.range.highlighted:hover, +.datepicker table tr td.range.highlighted.disabled:focus, +.datepicker table tr td.range.highlighted[disabled]:focus, +fieldset[disabled] .datepicker table tr td.range.highlighted:focus, +.datepicker table tr td.range.highlighted.disabled.focus, +.datepicker table tr td.range.highlighted[disabled].focus, +fieldset[disabled] .datepicker table tr td.range.highlighted.focus { + background-color: #e4eef3; + border-color: #9dc1d3; +} +.datepicker table tr td.range.highlighted.focused { + background: #c1d7e3; +} +.datepicker table tr td.range.highlighted.disabled, +.datepicker table tr td.range.highlighted.disabled:active { + background: #e4eef3; + color: #999999; +} +.datepicker table tr td.range.today { + color: #000000; + background-color: #f7ca77; + border-color: #f1a417; +} +.datepicker table tr td.range.today:focus, +.datepicker table tr td.range.today.focus { + color: #000000; + background-color: #f4b747; + border-color: #815608; +} +.datepicker table tr td.range.today:hover { + color: #000000; + background-color: #f4b747; + border-color: #bf800c; +} +.datepicker table tr td.range.today:active, +.datepicker table tr td.range.today.active, +.open > .dropdown-toggle.datepicker table tr td.range.today { + color: #000000; + background-color: #f4b747; + border-color: #bf800c; +} +.datepicker table tr td.range.today:active:hover, +.datepicker table tr td.range.today.active:hover, +.open > .dropdown-toggle.datepicker table tr td.range.today:hover, +.datepicker table tr td.range.today:active:focus, +.datepicker table tr td.range.today.active:focus, +.open > .dropdown-toggle.datepicker table tr td.range.today:focus, +.datepicker table tr td.range.today:active.focus, +.datepicker table tr td.range.today.active.focus, +.open > .dropdown-toggle.datepicker table tr td.range.today.focus { + color: #000000; + background-color: #f2aa25; + border-color: #815608; +} +.datepicker table tr td.range.today:active, +.datepicker table tr td.range.today.active, +.open > .dropdown-toggle.datepicker table tr td.range.today { + background-image: none; +} +.datepicker table tr td.range.today.disabled:hover, +.datepicker table tr td.range.today[disabled]:hover, +fieldset[disabled] .datepicker table tr td.range.today:hover, +.datepicker table tr td.range.today.disabled:focus, +.datepicker table tr td.range.today[disabled]:focus, +fieldset[disabled] .datepicker table tr td.range.today:focus, +.datepicker table tr td.range.today.disabled.focus, +.datepicker table tr td.range.today[disabled].focus, +fieldset[disabled] .datepicker table tr td.range.today.focus { + background-color: #f7ca77; + border-color: #f1a417; +} +.datepicker table tr td.range.today.disabled, +.datepicker table tr td.range.today.disabled:active { + background: #f7ca77; + color: #999999; +} +.datepicker table tr td.selected, +.datepicker table tr td.selected.highlighted { + color: #ffffff; + background-color: #999999; + border-color: #555555; + text-shadow: 0 -1px 0 rgba(0, 0, 0, 0.25); +} +.datepicker table tr td.selected:focus, +.datepicker table tr td.selected.highlighted:focus, +.datepicker table tr td.selected.focus, +.datepicker table tr td.selected.highlighted.focus { + color: #ffffff; + background-color: #808080; + border-color: #161616; +} +.datepicker table tr td.selected:hover, +.datepicker table tr td.selected.highlighted:hover { + color: #ffffff; + background-color: #808080; + border-color: #373737; +} +.datepicker table tr td.selected:active, +.datepicker table tr td.selected.highlighted:active, +.datepicker table tr td.selected.active, +.datepicker table tr td.selected.highlighted.active, +.open > .dropdown-toggle.datepicker table tr td.selected, +.open > .dropdown-toggle.datepicker table tr td.selected.highlighted { + color: #ffffff; + background-color: #808080; + border-color: #373737; +} +.datepicker table tr td.selected:active:hover, +.datepicker table tr td.selected.highlighted:active:hover, +.datepicker table tr td.selected.active:hover, +.datepicker table tr td.selected.highlighted.active:hover, +.open > .dropdown-toggle.datepicker table tr td.selected:hover, +.open > .dropdown-toggle.datepicker table tr td.selected.highlighted:hover, +.datepicker table tr td.selected:active:focus, +.datepicker table tr td.selected.highlighted:active:focus, +.datepicker table tr td.selected.active:focus, +.datepicker table tr td.selected.highlighted.active:focus, +.open > .dropdown-toggle.datepicker table tr td.selected:focus, +.open > .dropdown-toggle.datepicker table tr td.selected.highlighted:focus, +.datepicker table tr td.selected:active.focus, +.datepicker table tr td.selected.highlighted:active.focus, +.datepicker table tr td.selected.active.focus, +.datepicker table tr td.selected.highlighted.active.focus, +.open > .dropdown-toggle.datepicker table tr td.selected.focus, +.open > .dropdown-toggle.datepicker table tr td.selected.highlighted.focus { + color: #ffffff; + background-color: #6e6e6e; + border-color: #161616; +} +.datepicker table tr td.selected:active, +.datepicker table tr td.selected.highlighted:active, +.datepicker table tr td.selected.active, +.datepicker table tr td.selected.highlighted.active, +.open > .dropdown-toggle.datepicker table tr td.selected, +.open > .dropdown-toggle.datepicker table tr td.selected.highlighted { + background-image: none; +} +.datepicker table tr td.selected.disabled:hover, +.datepicker table tr td.selected.highlighted.disabled:hover, +.datepicker table tr td.selected[disabled]:hover, +.datepicker table tr td.selected.highlighted[disabled]:hover, +fieldset[disabled] .datepicker table tr td.selected:hover, +fieldset[disabled] .datepicker table tr td.selected.highlighted:hover, +.datepicker table tr td.selected.disabled:focus, +.datepicker table tr td.selected.highlighted.disabled:focus, +.datepicker table tr td.selected[disabled]:focus, +.datepicker table tr td.selected.highlighted[disabled]:focus, +fieldset[disabled] .datepicker table tr td.selected:focus, +fieldset[disabled] .datepicker table tr td.selected.highlighted:focus, +.datepicker table tr td.selected.disabled.focus, +.datepicker table tr td.selected.highlighted.disabled.focus, +.datepicker table tr td.selected[disabled].focus, +.datepicker table tr td.selected.highlighted[disabled].focus, +fieldset[disabled] .datepicker table tr td.selected.focus, +fieldset[disabled] .datepicker table tr td.selected.highlighted.focus { + background-color: #999999; + border-color: #555555; +} +.datepicker table tr td.active, +.datepicker table tr td.active.highlighted { + color: #ffffff; + background-color: #428bca; + border-color: #357ebd; + text-shadow: 0 -1px 0 rgba(0, 0, 0, 0.25); +} +.datepicker table tr td.active:focus, +.datepicker table tr td.active.highlighted:focus, +.datepicker table tr td.active.focus, +.datepicker table tr td.active.highlighted.focus { + color: #ffffff; + background-color: #3071a9; + border-color: #193c5a; +} +.datepicker table tr td.active:hover, +.datepicker table tr td.active.highlighted:hover { + color: #ffffff; + background-color: #3071a9; + border-color: #285e8e; +} +.datepicker table tr td.active:active, +.datepicker table tr td.active.highlighted:active, +.datepicker table tr td.active.active, +.datepicker table tr td.active.highlighted.active, +.open > .dropdown-toggle.datepicker table tr td.active, +.open > .dropdown-toggle.datepicker table tr td.active.highlighted { + color: #ffffff; + background-color: #3071a9; + border-color: #285e8e; +} +.datepicker table tr td.active:active:hover, +.datepicker table tr td.active.highlighted:active:hover, +.datepicker table tr td.active.active:hover, +.datepicker table tr td.active.highlighted.active:hover, +.open > .dropdown-toggle.datepicker table tr td.active:hover, +.open > .dropdown-toggle.datepicker table tr td.active.highlighted:hover, +.datepicker table tr td.active:active:focus, +.datepicker table tr td.active.highlighted:active:focus, +.datepicker table tr td.active.active:focus, +.datepicker table tr td.active.highlighted.active:focus, +.open > .dropdown-toggle.datepicker table tr td.active:focus, +.open > .dropdown-toggle.datepicker table tr td.active.highlighted:focus, +.datepicker table tr td.active:active.focus, +.datepicker table tr td.active.highlighted:active.focus, +.datepicker table tr td.active.active.focus, +.datepicker table tr td.active.highlighted.active.focus, +.open > .dropdown-toggle.datepicker table tr td.active.focus, +.open > .dropdown-toggle.datepicker table tr td.active.highlighted.focus { + color: #ffffff; + background-color: #285e8e; + border-color: #193c5a; +} +.datepicker table tr td.active:active, +.datepicker table tr td.active.highlighted:active, +.datepicker table tr td.active.active, +.datepicker table tr td.active.highlighted.active, +.open > .dropdown-toggle.datepicker table tr td.active, +.open > .dropdown-toggle.datepicker table tr td.active.highlighted { + background-image: none; +} +.datepicker table tr td.active.disabled:hover, +.datepicker table tr td.active.highlighted.disabled:hover, +.datepicker table tr td.active[disabled]:hover, +.datepicker table tr td.active.highlighted[disabled]:hover, +fieldset[disabled] .datepicker table tr td.active:hover, +fieldset[disabled] .datepicker table tr td.active.highlighted:hover, +.datepicker table tr td.active.disabled:focus, +.datepicker table tr td.active.highlighted.disabled:focus, +.datepicker table tr td.active[disabled]:focus, +.datepicker table tr td.active.highlighted[disabled]:focus, +fieldset[disabled] .datepicker table tr td.active:focus, +fieldset[disabled] .datepicker table tr td.active.highlighted:focus, +.datepicker table tr td.active.disabled.focus, +.datepicker table tr td.active.highlighted.disabled.focus, +.datepicker table tr td.active[disabled].focus, +.datepicker table tr td.active.highlighted[disabled].focus, +fieldset[disabled] .datepicker table tr td.active.focus, +fieldset[disabled] .datepicker table tr td.active.highlighted.focus { + background-color: #428bca; + border-color: #357ebd; +} +.datepicker table tr td span { + display: block; + width: 23%; + height: 54px; + line-height: 54px; + float: left; + margin: 1%; + cursor: pointer; + border-radius: 4px; +} +.datepicker table tr td span:hover { + background: #eeeeee; +} +.datepicker table tr td span.disabled, +.datepicker table tr td span.disabled:hover { + background: none; + color: #999999; + cursor: default; +} +.datepicker table tr td span.active, +.datepicker table tr td span.active:hover, +.datepicker table tr td span.active.disabled, +.datepicker table tr td span.active.disabled:hover { + color: #ffffff; + background-color: #428bca; + border-color: #357ebd; + text-shadow: 0 -1px 0 rgba(0, 0, 0, 0.25); +} +.datepicker table tr td span.active:focus, +.datepicker table tr td span.active:hover:focus, +.datepicker table tr td span.active.disabled:focus, +.datepicker table tr td span.active.disabled:hover:focus, +.datepicker table tr td span.active.focus, +.datepicker table tr td span.active:hover.focus, +.datepicker table tr td span.active.disabled.focus, +.datepicker table tr td span.active.disabled:hover.focus { + color: #ffffff; + background-color: #3071a9; + border-color: #193c5a; +} +.datepicker table tr td span.active:hover, +.datepicker table tr td span.active:hover:hover, +.datepicker table tr td span.active.disabled:hover, +.datepicker table tr td span.active.disabled:hover:hover { + color: #ffffff; + background-color: #3071a9; + border-color: #285e8e; +} +.datepicker table tr td span.active:active, +.datepicker table tr td span.active:hover:active, +.datepicker table tr td span.active.disabled:active, +.datepicker table tr td span.active.disabled:hover:active, +.datepicker table tr td span.active.active, +.datepicker table tr td span.active:hover.active, +.datepicker table tr td span.active.disabled.active, +.datepicker table tr td span.active.disabled:hover.active, +.open > .dropdown-toggle.datepicker table tr td span.active, +.open > .dropdown-toggle.datepicker table tr td span.active:hover, +.open > .dropdown-toggle.datepicker table tr td span.active.disabled, +.open > .dropdown-toggle.datepicker table tr td span.active.disabled:hover { + color: #ffffff; + background-color: #3071a9; + border-color: #285e8e; +} +.datepicker table tr td span.active:active:hover, +.datepicker table tr td span.active:hover:active:hover, +.datepicker table tr td span.active.disabled:active:hover, +.datepicker table tr td span.active.disabled:hover:active:hover, +.datepicker table tr td span.active.active:hover, +.datepicker table tr td span.active:hover.active:hover, +.datepicker table tr td span.active.disabled.active:hover, +.datepicker table tr td span.active.disabled:hover.active:hover, +.open > .dropdown-toggle.datepicker table tr td span.active:hover, +.open > .dropdown-toggle.datepicker table tr td span.active:hover:hover, +.open > .dropdown-toggle.datepicker table tr td span.active.disabled:hover, +.open > .dropdown-toggle.datepicker table tr td span.active.disabled:hover:hover, +.datepicker table tr td span.active:active:focus, +.datepicker table tr td span.active:hover:active:focus, +.datepicker table tr td span.active.disabled:active:focus, +.datepicker table tr td span.active.disabled:hover:active:focus, +.datepicker table tr td span.active.active:focus, +.datepicker table tr td span.active:hover.active:focus, +.datepicker table tr td span.active.disabled.active:focus, +.datepicker table tr td span.active.disabled:hover.active:focus, +.open > .dropdown-toggle.datepicker table tr td span.active:focus, +.open > .dropdown-toggle.datepicker table tr td span.active:hover:focus, +.open > .dropdown-toggle.datepicker table tr td span.active.disabled:focus, +.open > .dropdown-toggle.datepicker table tr td span.active.disabled:hover:focus, +.datepicker table tr td span.active:active.focus, +.datepicker table tr td span.active:hover:active.focus, +.datepicker table tr td span.active.disabled:active.focus, +.datepicker table tr td span.active.disabled:hover:active.focus, +.datepicker table tr td span.active.active.focus, +.datepicker table tr td span.active:hover.active.focus, +.datepicker table tr td span.active.disabled.active.focus, +.datepicker table tr td span.active.disabled:hover.active.focus, +.open > .dropdown-toggle.datepicker table tr td span.active.focus, +.open > .dropdown-toggle.datepicker table tr td span.active:hover.focus, +.open > .dropdown-toggle.datepicker table tr td span.active.disabled.focus, +.open > .dropdown-toggle.datepicker table tr td span.active.disabled:hover.focus { + color: #ffffff; + background-color: #285e8e; + border-color: #193c5a; +} +.datepicker table tr td span.active:active, +.datepicker table tr td span.active:hover:active, +.datepicker table tr td span.active.disabled:active, +.datepicker table tr td span.active.disabled:hover:active, +.datepicker table tr td span.active.active, +.datepicker table tr td span.active:hover.active, +.datepicker table tr td span.active.disabled.active, +.datepicker table tr td span.active.disabled:hover.active, +.open > .dropdown-toggle.datepicker table tr td span.active, +.open > .dropdown-toggle.datepicker table tr td span.active:hover, +.open > .dropdown-toggle.datepicker table tr td span.active.disabled, +.open > .dropdown-toggle.datepicker table tr td span.active.disabled:hover { + background-image: none; +} +.datepicker table tr td span.active.disabled:hover, +.datepicker table tr td span.active:hover.disabled:hover, +.datepicker table tr td span.active.disabled.disabled:hover, +.datepicker table tr td span.active.disabled:hover.disabled:hover, +.datepicker table tr td span.active[disabled]:hover, +.datepicker table tr td span.active:hover[disabled]:hover, +.datepicker table tr td span.active.disabled[disabled]:hover, +.datepicker table tr td span.active.disabled:hover[disabled]:hover, +fieldset[disabled] .datepicker table tr td span.active:hover, +fieldset[disabled] .datepicker table tr td span.active:hover:hover, +fieldset[disabled] .datepicker table tr td span.active.disabled:hover, +fieldset[disabled] .datepicker table tr td span.active.disabled:hover:hover, +.datepicker table tr td span.active.disabled:focus, +.datepicker table tr td span.active:hover.disabled:focus, +.datepicker table tr td span.active.disabled.disabled:focus, +.datepicker table tr td span.active.disabled:hover.disabled:focus, +.datepicker table tr td span.active[disabled]:focus, +.datepicker table tr td span.active:hover[disabled]:focus, +.datepicker table tr td span.active.disabled[disabled]:focus, +.datepicker table tr td span.active.disabled:hover[disabled]:focus, +fieldset[disabled] .datepicker table tr td span.active:focus, +fieldset[disabled] .datepicker table tr td span.active:hover:focus, +fieldset[disabled] .datepicker table tr td span.active.disabled:focus, +fieldset[disabled] .datepicker table tr td span.active.disabled:hover:focus, +.datepicker table tr td span.active.disabled.focus, +.datepicker table tr td span.active:hover.disabled.focus, +.datepicker table tr td span.active.disabled.disabled.focus, +.datepicker table tr td span.active.disabled:hover.disabled.focus, +.datepicker table tr td span.active[disabled].focus, +.datepicker table tr td span.active:hover[disabled].focus, +.datepicker table tr td span.active.disabled[disabled].focus, +.datepicker table tr td span.active.disabled:hover[disabled].focus, +fieldset[disabled] .datepicker table tr td span.active.focus, +fieldset[disabled] .datepicker table tr td span.active:hover.focus, +fieldset[disabled] .datepicker table tr td span.active.disabled.focus, +fieldset[disabled] .datepicker table tr td span.active.disabled:hover.focus { + background-color: #428bca; + border-color: #357ebd; +} +.datepicker table tr td span.old, +.datepicker table tr td span.new { + color: #999999; +} +.datepicker .datepicker-switch { + width: 145px; +} +.datepicker .datepicker-switch, +.datepicker .prev, +.datepicker .next, +.datepicker tfoot tr th { + cursor: pointer; +} +.datepicker .datepicker-switch:hover, +.datepicker .prev:hover, +.datepicker .next:hover, +.datepicker tfoot tr th:hover { + background: #eeeeee; +} +.datepicker .cw { + font-size: 10px; + width: 12px; + padding: 0 2px 0 5px; + vertical-align: middle; +} +.input-group.date .input-group-addon { + cursor: pointer; +} +.input-daterange { + width: 100%; +} +.input-daterange input { + text-align: center; +} +.input-daterange input:first-child { + border-radius: 3px 0 0 3px; +} +.input-daterange input:last-child { + border-radius: 0 3px 3px 0; +} +.input-daterange .input-group-addon { + width: auto; + min-width: 16px; + padding: 4px 5px; + font-weight: normal; + line-height: 1.42857143; + text-align: center; + text-shadow: 0 1px 0 #fff; + vertical-align: middle; + background-color: #eeeeee; + border: solid #cccccc; + border-width: 1px 0; + margin-left: -5px; + margin-right: -5px; +} +.datepicker.dropdown-menu { + position: absolute; + top: 100%; + left: 0; + z-index: 1000; + float: left; + display: none; + min-width: 160px; + list-style: none; + background-color: #ffffff; + border: 1px solid #ccc; + border: 1px solid rgba(0, 0, 0, 0.2); + border-radius: 5px; + -webkit-box-shadow: 0 5px 10px rgba(0, 0, 0, 0.2); + -moz-box-shadow: 0 5px 10px rgba(0, 0, 0, 0.2); + box-shadow: 0 5px 10px rgba(0, 0, 0, 0.2); + -webkit-background-clip: padding-box; + -moz-background-clip: padding; + background-clip: padding-box; + *border-right-width: 2px; + *border-bottom-width: 2px; + color: #333333; + font-size: 13px; + line-height: 1.42857143; +} +.datepicker.dropdown-menu th, +.datepicker.datepicker-inline th, +.datepicker.dropdown-menu td, +.datepicker.datepicker-inline td { + padding: 0px 5px; +} diff --git a/static/bootstrap3/css/bootstrap-datepicker3.standalone.min.css b/static/bootstrap3/css/bootstrap-datepicker3.standalone.min.css new file mode 100755 index 0000000..7d1a122 --- /dev/null +++ b/static/bootstrap3/css/bootstrap-datepicker3.standalone.min.css @@ -0,0 +1,8 @@ +/*! + * Datepicker for Bootstrap v1.5.0-dev (https://github.com/eternicode/bootstrap-datepicker) + * + * Copyright 2012 Stefan Petre + * Improvements by Andrew Rowls + * Licensed under the Apache License v2.0 (http://www.apache.org/licenses/LICENSE-2.0) + */ +.datepicker{border-radius:4px;direction:ltr}.datepicker-inline{width:220px}.datepicker.datepicker-rtl{direction:rtl}.datepicker.datepicker-rtl table tr td span{float:right}.datepicker-dropdown{top:0;left:0;padding:4px}.datepicker-dropdown:before{content:'';display:inline-block;border-left:7px solid transparent;border-right:7px solid transparent;border-bottom:7px solid rgba(0,0,0,.15);border-top:0;border-bottom-color:rgba(0,0,0,.2);position:absolute}.datepicker-dropdown:after{content:'';display:inline-block;border-left:6px solid transparent;border-right:6px solid transparent;border-bottom:6px solid #fff;border-top:0;position:absolute}.datepicker-dropdown.datepicker-orient-left:before{left:6px}.datepicker-dropdown.datepicker-orient-left:after{left:7px}.datepicker-dropdown.datepicker-orient-right:before{right:6px}.datepicker-dropdown.datepicker-orient-right:after{right:7px}.datepicker-dropdown.datepicker-orient-bottom:before{top:-7px}.datepicker-dropdown.datepicker-orient-bottom:after{top:-6px}.datepicker-dropdown.datepicker-orient-top:before{bottom:-7px;border-bottom:0;border-top:7px solid rgba(0,0,0,.15)}.datepicker-dropdown.datepicker-orient-top:after{bottom:-6px;border-bottom:0;border-top:6px solid #fff}.datepicker>div{display:none}.datepicker table{margin:0;-webkit-touch-callout:none;-webkit-user-select:none;-khtml-user-select:none;-moz-user-select:none;-ms-user-select:none;user-select:none}.datepicker table tr td,.datepicker table tr th{text-align:center;width:30px;height:30px;border-radius:4px;border:none}.table-striped .datepicker table tr td,.table-striped .datepicker table tr th{background-color:transparent}.datepicker table tr td.old,.datepicker table tr td.new{color:#999}.datepicker table tr td.day:hover,.datepicker table tr td.focused{background:#eee;cursor:pointer}.datepicker table tr td.disabled,.datepicker table tr td.disabled:hover{background:0 0;color:#999;cursor:default}.datepicker table tr td.highlighted{color:#000;background-color:#d9edf7;border-color:#85c5e5;border-radius:0}.datepicker table tr td.highlighted:focus,.datepicker table tr td.highlighted.focus{color:#000;background-color:#afd9ee;border-color:#298fc2}.datepicker table tr td.highlighted:hover{color:#000;background-color:#afd9ee;border-color:#52addb}.datepicker table tr td.highlighted:active,.datepicker table tr td.highlighted.active,.open>.dropdown-toggle.datepicker table tr td.highlighted{color:#000;background-color:#afd9ee;border-color:#52addb}.datepicker table tr td.highlighted:active:hover,.datepicker table tr td.highlighted.active:hover,.open>.dropdown-toggle.datepicker table tr td.highlighted:hover,.datepicker table tr td.highlighted:active:focus,.datepicker table tr td.highlighted.active:focus,.open>.dropdown-toggle.datepicker table tr td.highlighted:focus,.datepicker table tr td.highlighted:active.focus,.datepicker table tr td.highlighted.active.focus,.open>.dropdown-toggle.datepicker table tr td.highlighted.focus{color:#000;background-color:#91cbe8;border-color:#298fc2}.datepicker table tr td.highlighted:active,.datepicker table tr td.highlighted.active,.open>.dropdown-toggle.datepicker table tr td.highlighted{background-image:none}.datepicker table tr td.highlighted.disabled:hover,.datepicker table tr td.highlighted[disabled]:hover,fieldset[disabled] .datepicker table tr td.highlighted:hover,.datepicker table tr td.highlighted.disabled:focus,.datepicker table tr td.highlighted[disabled]:focus,fieldset[disabled] .datepicker table tr td.highlighted:focus,.datepicker table tr td.highlighted.disabled.focus,.datepicker table tr td.highlighted[disabled].focus,fieldset[disabled] .datepicker table tr td.highlighted.focus{background-color:#d9edf7;border-color:#85c5e5}.datepicker table tr td.highlighted.focused{background:#afd9ee}.datepicker table tr td.highlighted.disabled,.datepicker table tr td.highlighted.disabled:active{background:#d9edf7;color:#999}.datepicker table tr td.today{color:#000;background-color:#ffdb99;border-color:#ffb733}.datepicker table tr td.today:focus,.datepicker table tr td.today.focus{color:#000;background-color:#ffc966;border-color:#b37400}.datepicker table tr td.today:hover{color:#000;background-color:#ffc966;border-color:#f59e00}.datepicker table tr td.today:active,.datepicker table tr td.today.active,.open>.dropdown-toggle.datepicker table tr td.today{color:#000;background-color:#ffc966;border-color:#f59e00}.datepicker table tr td.today:active:hover,.datepicker table tr td.today.active:hover,.open>.dropdown-toggle.datepicker table tr td.today:hover,.datepicker table tr td.today:active:focus,.datepicker table tr td.today.active:focus,.open>.dropdown-toggle.datepicker table tr td.today:focus,.datepicker table tr td.today:active.focus,.datepicker table tr td.today.active.focus,.open>.dropdown-toggle.datepicker table tr td.today.focus{color:#000;background-color:#ffbc42;border-color:#b37400}.datepicker table tr td.today:active,.datepicker table tr td.today.active,.open>.dropdown-toggle.datepicker table tr td.today{background-image:none}.datepicker table tr td.today.disabled:hover,.datepicker table tr td.today[disabled]:hover,fieldset[disabled] .datepicker table tr td.today:hover,.datepicker table tr td.today.disabled:focus,.datepicker table tr td.today[disabled]:focus,fieldset[disabled] .datepicker table tr td.today:focus,.datepicker table tr td.today.disabled.focus,.datepicker table tr td.today[disabled].focus,fieldset[disabled] .datepicker table tr td.today.focus{background-color:#ffdb99;border-color:#ffb733}.datepicker table tr td.today.focused{background:#ffc966}.datepicker table tr td.today.disabled,.datepicker table tr td.today.disabled:active{background:#ffdb99;color:#999}.datepicker table tr td.range{color:#000;background-color:#eee;border-color:#bbb;border-radius:0}.datepicker table tr td.range:focus,.datepicker table tr td.range.focus{color:#000;background-color:#d5d5d5;border-color:#7c7c7c}.datepicker table tr td.range:hover{color:#000;background-color:#d5d5d5;border-color:#9d9d9d}.datepicker table tr td.range:active,.datepicker table tr td.range.active,.open>.dropdown-toggle.datepicker table tr td.range{color:#000;background-color:#d5d5d5;border-color:#9d9d9d}.datepicker table tr td.range:active:hover,.datepicker table tr td.range.active:hover,.open>.dropdown-toggle.datepicker table tr td.range:hover,.datepicker table tr td.range:active:focus,.datepicker table tr td.range.active:focus,.open>.dropdown-toggle.datepicker table tr td.range:focus,.datepicker table tr td.range:active.focus,.datepicker table tr td.range.active.focus,.open>.dropdown-toggle.datepicker table tr td.range.focus{color:#000;background-color:#c3c3c3;border-color:#7c7c7c}.datepicker table tr td.range:active,.datepicker table tr td.range.active,.open>.dropdown-toggle.datepicker table tr td.range{background-image:none}.datepicker table tr td.range.disabled:hover,.datepicker table tr td.range[disabled]:hover,fieldset[disabled] .datepicker table tr td.range:hover,.datepicker table tr td.range.disabled:focus,.datepicker table tr td.range[disabled]:focus,fieldset[disabled] .datepicker table tr td.range:focus,.datepicker table tr td.range.disabled.focus,.datepicker table tr td.range[disabled].focus,fieldset[disabled] .datepicker table tr td.range.focus{background-color:#eee;border-color:#bbb}.datepicker table tr td.range.focused{background:#d5d5d5}.datepicker table tr td.range.disabled,.datepicker table tr td.range.disabled:active{background:#eee;color:#999}.datepicker table tr td.range.highlighted{color:#000;background-color:#e4eef3;border-color:#9dc1d3}.datepicker table tr td.range.highlighted:focus,.datepicker table tr td.range.highlighted.focus{color:#000;background-color:#c1d7e3;border-color:#4b88a6}.datepicker table tr td.range.highlighted:hover{color:#000;background-color:#c1d7e3;border-color:#73a6c0}.datepicker table tr td.range.highlighted:active,.datepicker table tr td.range.highlighted.active,.open>.dropdown-toggle.datepicker table tr td.range.highlighted{color:#000;background-color:#c1d7e3;border-color:#73a6c0}.datepicker table tr td.range.highlighted:active:hover,.datepicker table tr td.range.highlighted.active:hover,.open>.dropdown-toggle.datepicker table tr td.range.highlighted:hover,.datepicker table tr td.range.highlighted:active:focus,.datepicker table tr td.range.highlighted.active:focus,.open>.dropdown-toggle.datepicker table tr td.range.highlighted:focus,.datepicker table tr td.range.highlighted:active.focus,.datepicker table tr td.range.highlighted.active.focus,.open>.dropdown-toggle.datepicker table tr td.range.highlighted.focus{color:#000;background-color:#a8c8d8;border-color:#4b88a6}.datepicker table tr td.range.highlighted:active,.datepicker table tr td.range.highlighted.active,.open>.dropdown-toggle.datepicker table tr td.range.highlighted{background-image:none}.datepicker table tr td.range.highlighted.disabled:hover,.datepicker table tr td.range.highlighted[disabled]:hover,fieldset[disabled] .datepicker table tr td.range.highlighted:hover,.datepicker table tr td.range.highlighted.disabled:focus,.datepicker table tr td.range.highlighted[disabled]:focus,fieldset[disabled] .datepicker table tr td.range.highlighted:focus,.datepicker table tr td.range.highlighted.disabled.focus,.datepicker table tr td.range.highlighted[disabled].focus,fieldset[disabled] .datepicker table tr td.range.highlighted.focus{background-color:#e4eef3;border-color:#9dc1d3}.datepicker table tr td.range.highlighted.focused{background:#c1d7e3}.datepicker table tr td.range.highlighted.disabled,.datepicker table tr td.range.highlighted.disabled:active{background:#e4eef3;color:#999}.datepicker table tr td.range.today{color:#000;background-color:#f7ca77;border-color:#f1a417}.datepicker table tr td.range.today:focus,.datepicker table tr td.range.today.focus{color:#000;background-color:#f4b747;border-color:#815608}.datepicker table tr td.range.today:hover{color:#000;background-color:#f4b747;border-color:#bf800c}.datepicker table tr td.range.today:active,.datepicker table tr td.range.today.active,.open>.dropdown-toggle.datepicker table tr td.range.today{color:#000;background-color:#f4b747;border-color:#bf800c}.datepicker table tr td.range.today:active:hover,.datepicker table tr td.range.today.active:hover,.open>.dropdown-toggle.datepicker table tr td.range.today:hover,.datepicker table tr td.range.today:active:focus,.datepicker table tr td.range.today.active:focus,.open>.dropdown-toggle.datepicker table tr td.range.today:focus,.datepicker table tr td.range.today:active.focus,.datepicker table tr td.range.today.active.focus,.open>.dropdown-toggle.datepicker table tr td.range.today.focus{color:#000;background-color:#f2aa25;border-color:#815608}.datepicker table tr td.range.today:active,.datepicker table tr td.range.today.active,.open>.dropdown-toggle.datepicker table tr td.range.today{background-image:none}.datepicker table tr td.range.today.disabled:hover,.datepicker table tr td.range.today[disabled]:hover,fieldset[disabled] .datepicker table tr td.range.today:hover,.datepicker table tr td.range.today.disabled:focus,.datepicker table tr td.range.today[disabled]:focus,fieldset[disabled] .datepicker table tr td.range.today:focus,.datepicker table tr td.range.today.disabled.focus,.datepicker table tr td.range.today[disabled].focus,fieldset[disabled] .datepicker table tr td.range.today.focus{background-color:#f7ca77;border-color:#f1a417}.datepicker table tr td.range.today.disabled,.datepicker table tr td.range.today.disabled:active{background:#f7ca77;color:#999}.datepicker table tr td.selected,.datepicker table tr td.selected.highlighted{color:#fff;background-color:#999;border-color:#555;text-shadow:0 -1px 0 rgba(0,0,0,.25)}.datepicker table tr td.selected:focus,.datepicker table tr td.selected.highlighted:focus,.datepicker table tr td.selected.focus,.datepicker table tr td.selected.highlighted.focus{color:#fff;background-color:gray;border-color:#161616}.datepicker table tr td.selected:hover,.datepicker table tr td.selected.highlighted:hover{color:#fff;background-color:gray;border-color:#373737}.datepicker table tr td.selected:active,.datepicker table tr td.selected.highlighted:active,.datepicker table tr td.selected.active,.datepicker table tr td.selected.highlighted.active,.open>.dropdown-toggle.datepicker table tr td.selected,.open>.dropdown-toggle.datepicker table tr td.selected.highlighted{color:#fff;background-color:gray;border-color:#373737}.datepicker table tr td.selected:active:hover,.datepicker table tr td.selected.highlighted:active:hover,.datepicker table tr td.selected.active:hover,.datepicker table tr td.selected.highlighted.active:hover,.open>.dropdown-toggle.datepicker table tr td.selected:hover,.open>.dropdown-toggle.datepicker table tr td.selected.highlighted:hover,.datepicker table tr td.selected:active:focus,.datepicker table tr td.selected.highlighted:active:focus,.datepicker table tr td.selected.active:focus,.datepicker table tr td.selected.highlighted.active:focus,.open>.dropdown-toggle.datepicker table tr td.selected:focus,.open>.dropdown-toggle.datepicker table tr td.selected.highlighted:focus,.datepicker table tr td.selected:active.focus,.datepicker table tr td.selected.highlighted:active.focus,.datepicker table tr td.selected.active.focus,.datepicker table tr td.selected.highlighted.active.focus,.open>.dropdown-toggle.datepicker table tr td.selected.focus,.open>.dropdown-toggle.datepicker table tr td.selected.highlighted.focus{color:#fff;background-color:#6e6e6e;border-color:#161616}.datepicker table tr td.selected:active,.datepicker table tr td.selected.highlighted:active,.datepicker table tr td.selected.active,.datepicker table tr td.selected.highlighted.active,.open>.dropdown-toggle.datepicker table tr td.selected,.open>.dropdown-toggle.datepicker table tr td.selected.highlighted{background-image:none}.datepicker table tr td.selected.disabled:hover,.datepicker table tr td.selected.highlighted.disabled:hover,.datepicker table tr td.selected[disabled]:hover,.datepicker table tr td.selected.highlighted[disabled]:hover,fieldset[disabled] .datepicker table tr td.selected:hover,fieldset[disabled] .datepicker table tr td.selected.highlighted:hover,.datepicker table tr td.selected.disabled:focus,.datepicker table tr td.selected.highlighted.disabled:focus,.datepicker table tr td.selected[disabled]:focus,.datepicker table tr td.selected.highlighted[disabled]:focus,fieldset[disabled] .datepicker table tr td.selected:focus,fieldset[disabled] .datepicker table tr td.selected.highlighted:focus,.datepicker table tr td.selected.disabled.focus,.datepicker table tr td.selected.highlighted.disabled.focus,.datepicker table tr td.selected[disabled].focus,.datepicker table tr td.selected.highlighted[disabled].focus,fieldset[disabled] .datepicker table tr td.selected.focus,fieldset[disabled] .datepicker table tr td.selected.highlighted.focus{background-color:#999;border-color:#555}.datepicker table tr td.active,.datepicker table tr td.active.highlighted{color:#fff;background-color:#428bca;border-color:#357ebd;text-shadow:0 -1px 0 rgba(0,0,0,.25)}.datepicker table tr td.active:focus,.datepicker table tr td.active.highlighted:focus,.datepicker table tr td.active.focus,.datepicker table tr td.active.highlighted.focus{color:#fff;background-color:#3071a9;border-color:#193c5a}.datepicker table tr td.active:hover,.datepicker table tr td.active.highlighted:hover{color:#fff;background-color:#3071a9;border-color:#285e8e}.datepicker table tr td.active:active,.datepicker table tr td.active.highlighted:active,.datepicker table tr td.active.active,.datepicker table tr td.active.highlighted.active,.open>.dropdown-toggle.datepicker table tr td.active,.open>.dropdown-toggle.datepicker table tr td.active.highlighted{color:#fff;background-color:#3071a9;border-color:#285e8e}.datepicker table tr td.active:active:hover,.datepicker table tr td.active.highlighted:active:hover,.datepicker table tr td.active.active:hover,.datepicker table tr td.active.highlighted.active:hover,.open>.dropdown-toggle.datepicker table tr td.active:hover,.open>.dropdown-toggle.datepicker table tr td.active.highlighted:hover,.datepicker table tr td.active:active:focus,.datepicker table tr td.active.highlighted:active:focus,.datepicker table tr td.active.active:focus,.datepicker table tr td.active.highlighted.active:focus,.open>.dropdown-toggle.datepicker table tr td.active:focus,.open>.dropdown-toggle.datepicker table tr td.active.highlighted:focus,.datepicker table tr td.active:active.focus,.datepicker table tr td.active.highlighted:active.focus,.datepicker table tr td.active.active.focus,.datepicker table tr td.active.highlighted.active.focus,.open>.dropdown-toggle.datepicker table tr td.active.focus,.open>.dropdown-toggle.datepicker table tr td.active.highlighted.focus{color:#fff;background-color:#285e8e;border-color:#193c5a}.datepicker table tr td.active:active,.datepicker table tr td.active.highlighted:active,.datepicker table tr td.active.active,.datepicker table tr td.active.highlighted.active,.open>.dropdown-toggle.datepicker table tr td.active,.open>.dropdown-toggle.datepicker table tr td.active.highlighted{background-image:none}.datepicker table tr td.active.disabled:hover,.datepicker table tr td.active.highlighted.disabled:hover,.datepicker table tr td.active[disabled]:hover,.datepicker table tr td.active.highlighted[disabled]:hover,fieldset[disabled] .datepicker table tr td.active:hover,fieldset[disabled] .datepicker table tr td.active.highlighted:hover,.datepicker table tr td.active.disabled:focus,.datepicker table tr td.active.highlighted.disabled:focus,.datepicker table tr td.active[disabled]:focus,.datepicker table tr td.active.highlighted[disabled]:focus,fieldset[disabled] .datepicker table tr td.active:focus,fieldset[disabled] .datepicker table tr td.active.highlighted:focus,.datepicker table tr td.active.disabled.focus,.datepicker table tr td.active.highlighted.disabled.focus,.datepicker table tr td.active[disabled].focus,.datepicker table tr td.active.highlighted[disabled].focus,fieldset[disabled] .datepicker table tr td.active.focus,fieldset[disabled] .datepicker table tr td.active.highlighted.focus{background-color:#428bca;border-color:#357ebd}.datepicker table tr td span{display:block;width:23%;height:54px;line-height:54px;float:left;margin:1%;cursor:pointer;border-radius:4px}.datepicker table tr td span:hover{background:#eee}.datepicker table tr td span.disabled,.datepicker table tr td span.disabled:hover{background:0 0;color:#999;cursor:default}.datepicker table tr td span.active,.datepicker table tr td span.active:hover,.datepicker table tr td span.active.disabled,.datepicker table tr td span.active.disabled:hover{color:#fff;background-color:#428bca;border-color:#357ebd;text-shadow:0 -1px 0 rgba(0,0,0,.25)}.datepicker table tr td span.active:focus,.datepicker table tr td span.active:hover:focus,.datepicker table tr td span.active.disabled:focus,.datepicker table tr td span.active.disabled:hover:focus,.datepicker table tr td span.active.focus,.datepicker table tr td span.active:hover.focus,.datepicker table tr td span.active.disabled.focus,.datepicker table tr td span.active.disabled:hover.focus{color:#fff;background-color:#3071a9;border-color:#193c5a}.datepicker table tr td span.active:hover,.datepicker table tr td span.active:hover:hover,.datepicker table tr td span.active.disabled:hover,.datepicker table tr td span.active.disabled:hover:hover{color:#fff;background-color:#3071a9;border-color:#285e8e}.datepicker table tr td span.active:active,.datepicker table tr td span.active:hover:active,.datepicker table tr td span.active.disabled:active,.datepicker table tr td span.active.disabled:hover:active,.datepicker table tr td span.active.active,.datepicker table tr td span.active:hover.active,.datepicker table tr td span.active.disabled.active,.datepicker table tr td span.active.disabled:hover.active,.open>.dropdown-toggle.datepicker table tr td span.active,.open>.dropdown-toggle.datepicker table tr td span.active:hover,.open>.dropdown-toggle.datepicker table tr td span.active.disabled,.open>.dropdown-toggle.datepicker table tr td span.active.disabled:hover{color:#fff;background-color:#3071a9;border-color:#285e8e}.datepicker table tr td span.active:active:hover,.datepicker table tr td span.active:hover:active:hover,.datepicker table tr td span.active.disabled:active:hover,.datepicker table tr td span.active.disabled:hover:active:hover,.datepicker table tr td span.active.active:hover,.datepicker table tr td span.active:hover.active:hover,.datepicker table tr td span.active.disabled.active:hover,.datepicker table tr td span.active.disabled:hover.active:hover,.open>.dropdown-toggle.datepicker table tr td span.active:hover,.open>.dropdown-toggle.datepicker table tr td span.active:hover:hover,.open>.dropdown-toggle.datepicker table tr td span.active.disabled:hover,.open>.dropdown-toggle.datepicker table tr td span.active.disabled:hover:hover,.datepicker table tr td span.active:active:focus,.datepicker table tr td span.active:hover:active:focus,.datepicker table tr td span.active.disabled:active:focus,.datepicker table tr td span.active.disabled:hover:active:focus,.datepicker table tr td span.active.active:focus,.datepicker table tr td span.active:hover.active:focus,.datepicker table tr td span.active.disabled.active:focus,.datepicker table tr td span.active.disabled:hover.active:focus,.open>.dropdown-toggle.datepicker table tr td span.active:focus,.open>.dropdown-toggle.datepicker table tr td span.active:hover:focus,.open>.dropdown-toggle.datepicker table tr td span.active.disabled:focus,.open>.dropdown-toggle.datepicker table tr td span.active.disabled:hover:focus,.datepicker table tr td span.active:active.focus,.datepicker table tr td span.active:hover:active.focus,.datepicker table tr td span.active.disabled:active.focus,.datepicker table tr td span.active.disabled:hover:active.focus,.datepicker table tr td span.active.active.focus,.datepicker table tr td span.active:hover.active.focus,.datepicker table tr td span.active.disabled.active.focus,.datepicker table tr td span.active.disabled:hover.active.focus,.open>.dropdown-toggle.datepicker table tr td span.active.focus,.open>.dropdown-toggle.datepicker table tr td span.active:hover.focus,.open>.dropdown-toggle.datepicker table tr td span.active.disabled.focus,.open>.dropdown-toggle.datepicker table tr td span.active.disabled:hover.focus{color:#fff;background-color:#285e8e;border-color:#193c5a}.datepicker table tr td span.active:active,.datepicker table tr td span.active:hover:active,.datepicker table tr td span.active.disabled:active,.datepicker table tr td span.active.disabled:hover:active,.datepicker table tr td span.active.active,.datepicker table tr td span.active:hover.active,.datepicker table tr td span.active.disabled.active,.datepicker table tr td span.active.disabled:hover.active,.open>.dropdown-toggle.datepicker table tr td span.active,.open>.dropdown-toggle.datepicker table tr td span.active:hover,.open>.dropdown-toggle.datepicker table tr td span.active.disabled,.open>.dropdown-toggle.datepicker table tr td span.active.disabled:hover{background-image:none}.datepicker table tr td span.active.disabled:hover,.datepicker table tr td span.active:hover.disabled:hover,.datepicker table tr td span.active.disabled.disabled:hover,.datepicker table tr td span.active.disabled:hover.disabled:hover,.datepicker table tr td span.active[disabled]:hover,.datepicker table tr td span.active:hover[disabled]:hover,.datepicker table tr td span.active.disabled[disabled]:hover,.datepicker table tr td span.active.disabled:hover[disabled]:hover,fieldset[disabled] .datepicker table tr td span.active:hover,fieldset[disabled] .datepicker table tr td span.active:hover:hover,fieldset[disabled] .datepicker table tr td span.active.disabled:hover,fieldset[disabled] .datepicker table tr td span.active.disabled:hover:hover,.datepicker table tr td span.active.disabled:focus,.datepicker table tr td span.active:hover.disabled:focus,.datepicker table tr td span.active.disabled.disabled:focus,.datepicker table tr td span.active.disabled:hover.disabled:focus,.datepicker table tr td span.active[disabled]:focus,.datepicker table tr td span.active:hover[disabled]:focus,.datepicker table tr td span.active.disabled[disabled]:focus,.datepicker table tr td span.active.disabled:hover[disabled]:focus,fieldset[disabled] .datepicker table tr td span.active:focus,fieldset[disabled] .datepicker table tr td span.active:hover:focus,fieldset[disabled] .datepicker table tr td span.active.disabled:focus,fieldset[disabled] .datepicker table tr td span.active.disabled:hover:focus,.datepicker table tr td span.active.disabled.focus,.datepicker table tr td span.active:hover.disabled.focus,.datepicker table tr td span.active.disabled.disabled.focus,.datepicker table tr td span.active.disabled:hover.disabled.focus,.datepicker table tr td span.active[disabled].focus,.datepicker table tr td span.active:hover[disabled].focus,.datepicker table tr td span.active.disabled[disabled].focus,.datepicker table tr td span.active.disabled:hover[disabled].focus,fieldset[disabled] .datepicker table tr td span.active.focus,fieldset[disabled] .datepicker table tr td span.active:hover.focus,fieldset[disabled] .datepicker table tr td span.active.disabled.focus,fieldset[disabled] .datepicker table tr td span.active.disabled:hover.focus{background-color:#428bca;border-color:#357ebd}.datepicker table tr td span.old,.datepicker table tr td span.new{color:#999}.datepicker .datepicker-switch{width:145px}.datepicker .datepicker-switch,.datepicker .prev,.datepicker .next,.datepicker tfoot tr th{cursor:pointer}.datepicker .datepicker-switch:hover,.datepicker .prev:hover,.datepicker .next:hover,.datepicker tfoot tr th:hover{background:#eee}.datepicker .cw{font-size:10px;width:12px;padding:0 2px 0 5px;vertical-align:middle}.input-group.date .input-group-addon{cursor:pointer}.input-daterange{width:100%}.input-daterange input{text-align:center}.input-daterange input:first-child{border-radius:3px 0 0 3px}.input-daterange input:last-child{border-radius:0 3px 3px 0}.input-daterange .input-group-addon{width:auto;min-width:16px;padding:4px 5px;font-weight:400;line-height:1.42857143;text-align:center;text-shadow:0 1px 0 #fff;vertical-align:middle;background-color:#eee;border:solid #ccc;border-width:1px 0;margin-left:-5px;margin-right:-5px}.datepicker.dropdown-menu{position:absolute;top:100%;left:0;z-index:1000;float:left;display:none;min-width:160px;list-style:none;background-color:#fff;border:1px solid #ccc;border:1px solid rgba(0,0,0,.2);border-radius:5px;-webkit-box-shadow:0 5px 10px rgba(0,0,0,.2);-moz-box-shadow:0 5px 10px rgba(0,0,0,.2);box-shadow:0 5px 10px rgba(0,0,0,.2);-webkit-background-clip:padding-box;-moz-background-clip:padding;background-clip:padding-box;*border-right-width:2px;*border-bottom-width:2px;color:#333;font-size:13px;line-height:1.42857143}.datepicker.dropdown-menu th,.datepicker.datepicker-inline th,.datepicker.dropdown-menu td,.datepicker.datepicker-inline td{padding:0 5px} \ No newline at end of file diff --git a/static/bootstrap3/css/bootstrap-datetimepicker-standalone.css b/static/bootstrap3/css/bootstrap-datetimepicker-standalone.css new file mode 100755 index 0000000..49e3ea6 --- /dev/null +++ b/static/bootstrap3/css/bootstrap-datetimepicker-standalone.css @@ -0,0 +1,103 @@ +/*! + * Datetimepicker for Bootstrap 3 + * version : 4.17.37 + * https://github.com/Eonasdan/bootstrap-datetimepicker/ + */ +@font-face { + font-family: 'Glyphicons Halflings'; + src: url('../fonts/glyphicons-halflings-regular.eot'); + src: url('../fonts/glyphicons-halflings-regular.eot?#iefix') format('embedded-opentype'), url('../fonts/glyphicons-halflings-regular.woff2') format('woff2'), url('../fonts/glyphicons-halflings-regular.woff') format('woff'), url('../fonts/glyphicons-halflings-regular.ttf') format('truetype'), url('../fonts/glyphicons-halflings-regular.svg#glyphicons_halflingsregular') format('svg'); +} + +.glyphicon { + position: relative; + top: 1px; + display: inline-block; + font-family: 'Glyphicons Halflings'; + font-style: normal; + font-weight: normal; + line-height: 1; + -webkit-font-smoothing: antialiased; + -moz-osx-font-smoothing: grayscale; +} + +.glyphicon-time:before { + content: "\e023"; +} + +.glyphicon-chevron-left:before { + content: "\e079"; +} + +.glyphicon-chevron-right:before { + content: "\e080"; +} + +.glyphicon-chevron-up:before { + content: "\e113"; +} + +.glyphicon-chevron-down:before { + content: "\e114"; +} + +.glyphicon-calendar:before { + content: "\e109"; +} + +.btn { + display: inline-block; + padding: 6px 12px; + margin-bottom: 0; + font-size: 14px; + font-weight: normal; + line-height: 1.42857143; + text-align: center; + white-space: nowrap; + vertical-align: middle; + -ms-touch-action: manipulation; + touch-action: manipulation; + cursor: pointer; + -webkit-user-select: none; + -moz-user-select: none; + -ms-user-select: none; + user-select: none; + background-image: none; + border: 1px solid transparent; + border-radius: 4px; +} + +.collapse { + display: none; +} + + .collapse.in { + display: block; + } + +.dropdown-menu { + position: absolute; + left: 0; + z-index: 1000; + display: none; + float: left; + min-width: 160px; + padding: 5px 0; + margin: 2px 0 0; + font-size: 14px; + text-align: left; + list-style: none; + background-color: #fff; + -webkit-background-clip: padding-box; + background-clip: padding-box; + border: 1px solid #ccc; + border: 1px solid rgba(0, 0, 0, .15); + border-radius: 4px; + -webkit-box-shadow: 0 6px 12px rgba(0, 0, 0, .175); + box-shadow: 0 6px 12px rgba(0, 0, 0, .175); +} + +.list-unstyled { + padding-left: 0; + list-style: none; +} \ No newline at end of file diff --git a/static/bootstrap3/css/bootstrap-datetimepicker.css b/static/bootstrap3/css/bootstrap-datetimepicker.css new file mode 100755 index 0000000..b8bf43b --- /dev/null +++ b/static/bootstrap3/css/bootstrap-datetimepicker.css @@ -0,0 +1,373 @@ +/*! + * Datetimepicker for Bootstrap 3 + * version : 4.17.37 + * https://github.com/Eonasdan/bootstrap-datetimepicker/ + */ +.bootstrap-datetimepicker-widget { + list-style: none; +} +.bootstrap-datetimepicker-widget.dropdown-menu { + margin: 2px 0; + padding: 4px; + width: 19em; +} +@media (min-width: 768px) { + .bootstrap-datetimepicker-widget.dropdown-menu.timepicker-sbs { + width: 38em; + } +} +@media (min-width: 992px) { + .bootstrap-datetimepicker-widget.dropdown-menu.timepicker-sbs { + width: 38em; + } +} +@media (min-width: 1200px) { + .bootstrap-datetimepicker-widget.dropdown-menu.timepicker-sbs { + width: 38em; + } +} +.bootstrap-datetimepicker-widget.dropdown-menu:before, +.bootstrap-datetimepicker-widget.dropdown-menu:after { + content: ''; + display: inline-block; + position: absolute; +} +.bootstrap-datetimepicker-widget.dropdown-menu.bottom:before { + border-left: 7px solid transparent; + border-right: 7px solid transparent; + border-bottom: 7px solid #cccccc; + border-bottom-color: rgba(0, 0, 0, 0.2); + top: -7px; + left: 7px; +} +.bootstrap-datetimepicker-widget.dropdown-menu.bottom:after { + border-left: 6px solid transparent; + border-right: 6px solid transparent; + border-bottom: 6px solid white; + top: -6px; + left: 8px; +} +.bootstrap-datetimepicker-widget.dropdown-menu.top:before { + border-left: 7px solid transparent; + border-right: 7px solid transparent; + border-top: 7px solid #cccccc; + border-top-color: rgba(0, 0, 0, 0.2); + bottom: -7px; + left: 6px; +} +.bootstrap-datetimepicker-widget.dropdown-menu.top:after { + border-left: 6px solid transparent; + border-right: 6px solid transparent; + border-top: 6px solid white; + bottom: -6px; + left: 7px; +} +.bootstrap-datetimepicker-widget.dropdown-menu.pull-right:before { + left: auto; + right: 6px; +} +.bootstrap-datetimepicker-widget.dropdown-menu.pull-right:after { + left: auto; + right: 7px; +} +.bootstrap-datetimepicker-widget .list-unstyled { + margin: 0; +} +.bootstrap-datetimepicker-widget a[data-action] { + padding: 6px 0; +} +.bootstrap-datetimepicker-widget a[data-action]:active { + box-shadow: none; +} +.bootstrap-datetimepicker-widget .timepicker-hour, +.bootstrap-datetimepicker-widget .timepicker-minute, +.bootstrap-datetimepicker-widget .timepicker-second { + width: 54px; + font-weight: bold; + font-size: 1.2em; + margin: 0; +} +.bootstrap-datetimepicker-widget button[data-action] { + padding: 6px; +} +.bootstrap-datetimepicker-widget .btn[data-action="incrementHours"]::after { + position: absolute; + width: 1px; + height: 1px; + margin: -1px; + padding: 0; + overflow: hidden; + clip: rect(0, 0, 0, 0); + border: 0; + content: "Increment Hours"; +} +.bootstrap-datetimepicker-widget .btn[data-action="incrementMinutes"]::after { + position: absolute; + width: 1px; + height: 1px; + margin: -1px; + padding: 0; + overflow: hidden; + clip: rect(0, 0, 0, 0); + border: 0; + content: "Increment Minutes"; +} +.bootstrap-datetimepicker-widget .btn[data-action="decrementHours"]::after { + position: absolute; + width: 1px; + height: 1px; + margin: -1px; + padding: 0; + overflow: hidden; + clip: rect(0, 0, 0, 0); + border: 0; + content: "Decrement Hours"; +} +.bootstrap-datetimepicker-widget .btn[data-action="decrementMinutes"]::after { + position: absolute; + width: 1px; + height: 1px; + margin: -1px; + padding: 0; + overflow: hidden; + clip: rect(0, 0, 0, 0); + border: 0; + content: "Decrement Minutes"; +} +.bootstrap-datetimepicker-widget .btn[data-action="showHours"]::after { + position: absolute; + width: 1px; + height: 1px; + margin: -1px; + padding: 0; + overflow: hidden; + clip: rect(0, 0, 0, 0); + border: 0; + content: "Show Hours"; +} +.bootstrap-datetimepicker-widget .btn[data-action="showMinutes"]::after { + position: absolute; + width: 1px; + height: 1px; + margin: -1px; + padding: 0; + overflow: hidden; + clip: rect(0, 0, 0, 0); + border: 0; + content: "Show Minutes"; +} +.bootstrap-datetimepicker-widget .btn[data-action="togglePeriod"]::after { + position: absolute; + width: 1px; + height: 1px; + margin: -1px; + padding: 0; + overflow: hidden; + clip: rect(0, 0, 0, 0); + border: 0; + content: "Toggle AM/PM"; +} +.bootstrap-datetimepicker-widget .btn[data-action="clear"]::after { + position: absolute; + width: 1px; + height: 1px; + margin: -1px; + padding: 0; + overflow: hidden; + clip: rect(0, 0, 0, 0); + border: 0; + content: "Clear the picker"; +} +.bootstrap-datetimepicker-widget .btn[data-action="today"]::after { + position: absolute; + width: 1px; + height: 1px; + margin: -1px; + padding: 0; + overflow: hidden; + clip: rect(0, 0, 0, 0); + border: 0; + content: "Set the date to today"; +} +.bootstrap-datetimepicker-widget .picker-switch { + text-align: center; +} +.bootstrap-datetimepicker-widget .picker-switch::after { + position: absolute; + width: 1px; + height: 1px; + margin: -1px; + padding: 0; + overflow: hidden; + clip: rect(0, 0, 0, 0); + border: 0; + content: "Toggle Date and Time Screens"; +} +.bootstrap-datetimepicker-widget .picker-switch td { + padding: 0; + margin: 0; + height: auto; + width: auto; + line-height: inherit; +} +.bootstrap-datetimepicker-widget .picker-switch td span { + line-height: 2.5; + height: 2.5em; + width: 100%; +} +.bootstrap-datetimepicker-widget table { + width: 100%; + margin: 0; +} +.bootstrap-datetimepicker-widget table td, +.bootstrap-datetimepicker-widget table th { + text-align: center; + border-radius: 4px; +} +.bootstrap-datetimepicker-widget table th { + height: 20px; + line-height: 20px; + width: 20px; +} +.bootstrap-datetimepicker-widget table th.picker-switch { + width: 145px; +} +.bootstrap-datetimepicker-widget table th.disabled, +.bootstrap-datetimepicker-widget table th.disabled:hover { + background: none; + color: #777777; + cursor: not-allowed; +} +.bootstrap-datetimepicker-widget table th.prev::after { + position: absolute; + width: 1px; + height: 1px; + margin: -1px; + padding: 0; + overflow: hidden; + clip: rect(0, 0, 0, 0); + border: 0; + content: "Previous Month"; +} +.bootstrap-datetimepicker-widget table th.next::after { + position: absolute; + width: 1px; + height: 1px; + margin: -1px; + padding: 0; + overflow: hidden; + clip: rect(0, 0, 0, 0); + border: 0; + content: "Next Month"; +} +.bootstrap-datetimepicker-widget table thead tr:first-child th { + cursor: pointer; +} +.bootstrap-datetimepicker-widget table thead tr:first-child th:hover { + background: #eeeeee; +} +.bootstrap-datetimepicker-widget table td { + height: 54px; + line-height: 54px; + width: 54px; +} +.bootstrap-datetimepicker-widget table td.cw { + font-size: .8em; + height: 20px; + line-height: 20px; + color: #777777; +} +.bootstrap-datetimepicker-widget table td.day { + height: 20px; + line-height: 20px; + width: 20px; +} +.bootstrap-datetimepicker-widget table td.day:hover, +.bootstrap-datetimepicker-widget table td.hour:hover, +.bootstrap-datetimepicker-widget table td.minute:hover, +.bootstrap-datetimepicker-widget table td.second:hover { + background: #eeeeee; + cursor: pointer; +} +.bootstrap-datetimepicker-widget table td.old, +.bootstrap-datetimepicker-widget table td.new { + color: #777777; +} +.bootstrap-datetimepicker-widget table td.today { + position: relative; +} +.bootstrap-datetimepicker-widget table td.today:before { + content: ''; + display: inline-block; + border: solid transparent; + border-width: 0 0 7px 7px; + border-bottom-color: #337ab7; + border-top-color: rgba(0, 0, 0, 0.2); + position: absolute; + bottom: 4px; + right: 4px; +} +.bootstrap-datetimepicker-widget table td.active, +.bootstrap-datetimepicker-widget table td.active:hover { + background-color: #337ab7; + color: #ffffff; + text-shadow: 0 -1px 0 rgba(0, 0, 0, 0.25); +} +.bootstrap-datetimepicker-widget table td.active.today:before { + border-bottom-color: #fff; +} +.bootstrap-datetimepicker-widget table td.disabled, +.bootstrap-datetimepicker-widget table td.disabled:hover { + background: none; + color: #777777; + cursor: not-allowed; +} +.bootstrap-datetimepicker-widget table td span { + display: inline-block; + width: 54px; + height: 54px; + line-height: 54px; + margin: 2px 1.5px; + cursor: pointer; + border-radius: 4px; +} +.bootstrap-datetimepicker-widget table td span:hover { + background: #eeeeee; +} +.bootstrap-datetimepicker-widget table td span.active { + background-color: #337ab7; + color: #ffffff; + text-shadow: 0 -1px 0 rgba(0, 0, 0, 0.25); +} +.bootstrap-datetimepicker-widget table td span.old { + color: #777777; +} +.bootstrap-datetimepicker-widget table td span.disabled, +.bootstrap-datetimepicker-widget table td span.disabled:hover { + background: none; + color: #777777; + cursor: not-allowed; +} +.bootstrap-datetimepicker-widget.usetwentyfour td.hour { + height: 27px; + line-height: 27px; +} +.bootstrap-datetimepicker-widget.wider { + width: 21em; +} +.bootstrap-datetimepicker-widget .datepicker-decades .decade { + line-height: 1.8em !important; +} +.input-group.date .input-group-addon { + cursor: pointer; +} +.sr-only { + position: absolute; + width: 1px; + height: 1px; + margin: -1px; + padding: 0; + overflow: hidden; + clip: rect(0, 0, 0, 0); + border: 0; +} diff --git a/static/bootstrap3/css/bootstrap-datetimepicker.min.css b/static/bootstrap3/css/bootstrap-datetimepicker.min.css new file mode 100755 index 0000000..63c2a3a --- /dev/null +++ b/static/bootstrap3/css/bootstrap-datetimepicker.min.css @@ -0,0 +1,5 @@ +/*! + * Datetimepicker for Bootstrap 3 + * version : 4.17.37 + * https://github.com/Eonasdan/bootstrap-datetimepicker/ + */.bootstrap-datetimepicker-widget{list-style:none}.bootstrap-datetimepicker-widget.dropdown-menu{margin:2px 0;padding:4px;width:19em}@media (min-width:768px){.bootstrap-datetimepicker-widget.dropdown-menu.timepicker-sbs{width:38em}}@media (min-width:992px){.bootstrap-datetimepicker-widget.dropdown-menu.timepicker-sbs{width:38em}}@media (min-width:1200px){.bootstrap-datetimepicker-widget.dropdown-menu.timepicker-sbs{width:38em}}.bootstrap-datetimepicker-widget.dropdown-menu:before,.bootstrap-datetimepicker-widget.dropdown-menu:after{content:'';display:inline-block;position:absolute}.bootstrap-datetimepicker-widget.dropdown-menu.bottom:before{border-left:7px solid transparent;border-right:7px solid transparent;border-bottom:7px solid #ccc;border-bottom-color:rgba(0,0,0,0.2);top:-7px;left:7px}.bootstrap-datetimepicker-widget.dropdown-menu.bottom:after{border-left:6px solid transparent;border-right:6px solid transparent;border-bottom:6px solid white;top:-6px;left:8px}.bootstrap-datetimepicker-widget.dropdown-menu.top:before{border-left:7px solid transparent;border-right:7px solid transparent;border-top:7px solid #ccc;border-top-color:rgba(0,0,0,0.2);bottom:-7px;left:6px}.bootstrap-datetimepicker-widget.dropdown-menu.top:after{border-left:6px solid transparent;border-right:6px solid transparent;border-top:6px solid white;bottom:-6px;left:7px}.bootstrap-datetimepicker-widget.dropdown-menu.pull-right:before{left:auto;right:6px}.bootstrap-datetimepicker-widget.dropdown-menu.pull-right:after{left:auto;right:7px}.bootstrap-datetimepicker-widget .list-unstyled{margin:0}.bootstrap-datetimepicker-widget a[data-action]{padding:6px 0}.bootstrap-datetimepicker-widget a[data-action]:active{box-shadow:none}.bootstrap-datetimepicker-widget .timepicker-hour,.bootstrap-datetimepicker-widget .timepicker-minute,.bootstrap-datetimepicker-widget .timepicker-second{width:54px;font-weight:bold;font-size:1.2em;margin:0}.bootstrap-datetimepicker-widget button[data-action]{padding:6px}.bootstrap-datetimepicker-widget .btn[data-action="incrementHours"]::after{position:absolute;width:1px;height:1px;margin:-1px;padding:0;overflow:hidden;clip:rect(0, 0, 0, 0);border:0;content:"Increment Hours"}.bootstrap-datetimepicker-widget .btn[data-action="incrementMinutes"]::after{position:absolute;width:1px;height:1px;margin:-1px;padding:0;overflow:hidden;clip:rect(0, 0, 0, 0);border:0;content:"Increment Minutes"}.bootstrap-datetimepicker-widget .btn[data-action="decrementHours"]::after{position:absolute;width:1px;height:1px;margin:-1px;padding:0;overflow:hidden;clip:rect(0, 0, 0, 0);border:0;content:"Decrement Hours"}.bootstrap-datetimepicker-widget .btn[data-action="decrementMinutes"]::after{position:absolute;width:1px;height:1px;margin:-1px;padding:0;overflow:hidden;clip:rect(0, 0, 0, 0);border:0;content:"Decrement Minutes"}.bootstrap-datetimepicker-widget .btn[data-action="showHours"]::after{position:absolute;width:1px;height:1px;margin:-1px;padding:0;overflow:hidden;clip:rect(0, 0, 0, 0);border:0;content:"Show Hours"}.bootstrap-datetimepicker-widget .btn[data-action="showMinutes"]::after{position:absolute;width:1px;height:1px;margin:-1px;padding:0;overflow:hidden;clip:rect(0, 0, 0, 0);border:0;content:"Show Minutes"}.bootstrap-datetimepicker-widget .btn[data-action="togglePeriod"]::after{position:absolute;width:1px;height:1px;margin:-1px;padding:0;overflow:hidden;clip:rect(0, 0, 0, 0);border:0;content:"Toggle AM/PM"}.bootstrap-datetimepicker-widget .btn[data-action="clear"]::after{position:absolute;width:1px;height:1px;margin:-1px;padding:0;overflow:hidden;clip:rect(0, 0, 0, 0);border:0;content:"Clear the picker"}.bootstrap-datetimepicker-widget .btn[data-action="today"]::after{position:absolute;width:1px;height:1px;margin:-1px;padding:0;overflow:hidden;clip:rect(0, 0, 0, 0);border:0;content:"Set the date to today"}.bootstrap-datetimepicker-widget .picker-switch{text-align:center}.bootstrap-datetimepicker-widget .picker-switch::after{position:absolute;width:1px;height:1px;margin:-1px;padding:0;overflow:hidden;clip:rect(0, 0, 0, 0);border:0;content:"Toggle Date and Time Screens"}.bootstrap-datetimepicker-widget .picker-switch td{padding:0;margin:0;height:auto;width:auto;line-height:inherit}.bootstrap-datetimepicker-widget .picker-switch td span{line-height:2.5;height:2.5em;width:100%}.bootstrap-datetimepicker-widget table{width:100%;margin:0}.bootstrap-datetimepicker-widget table td,.bootstrap-datetimepicker-widget table th{text-align:center;border-radius:4px}.bootstrap-datetimepicker-widget table th{height:20px;line-height:20px;width:20px}.bootstrap-datetimepicker-widget table th.picker-switch{width:145px}.bootstrap-datetimepicker-widget table th.disabled,.bootstrap-datetimepicker-widget table th.disabled:hover{background:none;color:#777;cursor:not-allowed}.bootstrap-datetimepicker-widget table th.prev::after{position:absolute;width:1px;height:1px;margin:-1px;padding:0;overflow:hidden;clip:rect(0, 0, 0, 0);border:0;content:"Previous Month"}.bootstrap-datetimepicker-widget table th.next::after{position:absolute;width:1px;height:1px;margin:-1px;padding:0;overflow:hidden;clip:rect(0, 0, 0, 0);border:0;content:"Next Month"}.bootstrap-datetimepicker-widget table thead tr:first-child th{cursor:pointer}.bootstrap-datetimepicker-widget table thead tr:first-child th:hover{background:#eee}.bootstrap-datetimepicker-widget table td{height:54px;line-height:54px;width:54px}.bootstrap-datetimepicker-widget table td.cw{font-size:.8em;height:20px;line-height:20px;color:#777}.bootstrap-datetimepicker-widget table td.day{height:20px;line-height:20px;width:20px}.bootstrap-datetimepicker-widget table td.day:hover,.bootstrap-datetimepicker-widget table td.hour:hover,.bootstrap-datetimepicker-widget table td.minute:hover,.bootstrap-datetimepicker-widget table td.second:hover{background:#eee;cursor:pointer}.bootstrap-datetimepicker-widget table td.old,.bootstrap-datetimepicker-widget table td.new{color:#777}.bootstrap-datetimepicker-widget table td.today{position:relative}.bootstrap-datetimepicker-widget table td.today:before{content:'';display:inline-block;border:solid transparent;border-width:0 0 7px 7px;border-bottom-color:#337ab7;border-top-color:rgba(0,0,0,0.2);position:absolute;bottom:4px;right:4px}.bootstrap-datetimepicker-widget table td.active,.bootstrap-datetimepicker-widget table td.active:hover{background-color:#337ab7;color:#fff;text-shadow:0 -1px 0 rgba(0,0,0,0.25)}.bootstrap-datetimepicker-widget table td.active.today:before{border-bottom-color:#fff}.bootstrap-datetimepicker-widget table td.disabled,.bootstrap-datetimepicker-widget table td.disabled:hover{background:none;color:#777;cursor:not-allowed}.bootstrap-datetimepicker-widget table td span{display:inline-block;width:54px;height:54px;line-height:54px;margin:2px 1.5px;cursor:pointer;border-radius:4px}.bootstrap-datetimepicker-widget table td span:hover{background:#eee}.bootstrap-datetimepicker-widget table td span.active{background-color:#337ab7;color:#fff;text-shadow:0 -1px 0 rgba(0,0,0,0.25)}.bootstrap-datetimepicker-widget table td span.old{color:#777}.bootstrap-datetimepicker-widget table td span.disabled,.bootstrap-datetimepicker-widget table td span.disabled:hover{background:none;color:#777;cursor:not-allowed}.bootstrap-datetimepicker-widget.usetwentyfour td.hour{height:27px;line-height:27px}.bootstrap-datetimepicker-widget.wider{width:21em}.bootstrap-datetimepicker-widget .datepicker-decades .decade{line-height:1.8em !important}.input-group.date .input-group-addon{cursor:pointer}.sr-only{position:absolute;width:1px;height:1px;margin:-1px;padding:0;overflow:hidden;clip:rect(0, 0, 0, 0);border:0} \ No newline at end of file diff --git a/static/bootstrap3/js/bootstrap-datepicker.js b/static/bootstrap3/js/bootstrap-datepicker.js new file mode 100755 index 0000000..80fafe4 --- /dev/null +++ b/static/bootstrap3/js/bootstrap-datepicker.js @@ -0,0 +1,1908 @@ +/*! + * Datepicker for Bootstrap v1.5.0-dev (https://github.com/eternicode/bootstrap-datepicker) + * + * Copyright 2012 Stefan Petre + * Improvements by Andrew Rowls + * Licensed under the Apache License v2.0 (http://www.apache.org/licenses/LICENSE-2.0) + */(function(factory){ + if (typeof define === "function" && define.amd) { + define(["jquery"], factory); + } else if (typeof exports === 'object') { + factory(require('jquery')); + } else { + factory(jQuery); + } +}(function($, undefined){ + + function UTCDate(){ + return new Date(Date.UTC.apply(Date, arguments)); + } + function UTCToday(){ + var today = new Date(); + return UTCDate(today.getFullYear(), today.getMonth(), today.getDate()); + } + function isUTCEquals(date1, date2) { + return ( + date1.getUTCFullYear() === date2.getUTCFullYear() && + date1.getUTCMonth() === date2.getUTCMonth() && + date1.getUTCDate() === date2.getUTCDate() + ); + } + function alias(method){ + return function(){ + return this[method].apply(this, arguments); + }; + } + function isValidDate(d) { + return d && !isNaN(d.getTime()); + } + + var DateArray = (function(){ + var extras = { + get: function(i){ + return this.slice(i)[0]; + }, + contains: function(d){ + // Array.indexOf is not cross-browser; + // $.inArray doesn't work with Dates + var val = d && d.valueOf(); + for (var i=0, l=this.length; i < l; i++) + if (this[i].valueOf() === val) + return i; + return -1; + }, + remove: function(i){ + this.splice(i,1); + }, + replace: function(new_array){ + if (!new_array) + return; + if (!$.isArray(new_array)) + new_array = [new_array]; + this.clear(); + this.push.apply(this, new_array); + }, + clear: function(){ + this.length = 0; + }, + copy: function(){ + var a = new DateArray(); + a.replace(this); + return a; + } + }; + + return function(){ + var a = []; + a.push.apply(a, arguments); + $.extend(a, extras); + return a; + }; + })(); + + + // Picker object + + var Datepicker = function(element, options){ + this._process_options(options); + + this.dates = new DateArray(); + this.viewDate = this.o.defaultViewDate; + this.focusDate = null; + + this.element = $(element); + this.isInline = false; + this.isInput = this.element.is('input'); + this.component = this.element.hasClass('date') ? this.element.find('.add-on, .input-group-addon, .btn') : false; + this.hasInput = this.component && this.element.find('input').length; + if (this.component && this.component.length === 0) + this.component = false; + + this.picker = $(DPGlobal.template); + this._buildEvents(); + this._attachEvents(); + + if (this.isInline){ + this.picker.addClass('datepicker-inline').appendTo(this.element); + } + else { + this.picker.addClass('datepicker-dropdown dropdown-menu'); + } + + if (this.o.rtl){ + this.picker.addClass('datepicker-rtl'); + } + + this.viewMode = this.o.startView; + + if (this.o.calendarWeeks) + this.picker.find('tfoot .today, tfoot .clear') + .attr('colspan', function(i, val){ + return parseInt(val) + 1; + }); + + this._allow_update = false; + + this.setStartDate(this._o.startDate); + this.setEndDate(this._o.endDate); + this.setDaysOfWeekDisabled(this.o.daysOfWeekDisabled); + this.setDaysOfWeekHighlighted(this.o.daysOfWeekHighlighted); + this.setDatesDisabled(this.o.datesDisabled); + + this.fillDow(); + this.fillMonths(); + + this._allow_update = true; + + this.update(); + this.showMode(); + + if (this.isInline){ + this.show(); + } + }; + + Datepicker.prototype = { + constructor: Datepicker, + + _process_options: function(opts){ + // Store raw options for reference + this._o = $.extend({}, this._o, opts); + // Processed options + var o = this.o = $.extend({}, this._o); + + // Check if "de-DE" style date is available, if not language should + // fallback to 2 letter code eg "de" + var lang = o.language; + if (!dates[lang]){ + lang = lang.split('-')[0]; + if (!dates[lang]) + lang = defaults.language; + } + o.language = lang; + + switch (o.startView){ + case 2: + case 'decade': + o.startView = 2; + break; + case 1: + case 'year': + o.startView = 1; + break; + default: + o.startView = 0; + } + + switch (o.minViewMode){ + case 1: + case 'months': + o.minViewMode = 1; + break; + case 2: + case 'years': + o.minViewMode = 2; + break; + default: + o.minViewMode = 0; + } + + switch (o.maxViewMode) { + case 0: + case 'days': + o.maxViewMode = 0; + break; + case 1: + case 'months': + o.maxViewMode = 1; + break; + default: + o.maxViewMode = 2; + } + + o.startView = Math.min(o.startView, o.maxViewMode); + o.startView = Math.max(o.startView, o.minViewMode); + + // true, false, or Number > 0 + if (o.multidate !== true){ + o.multidate = Number(o.multidate) || false; + if (o.multidate !== false) + o.multidate = Math.max(0, o.multidate); + } + o.multidateSeparator = String(o.multidateSeparator); + + o.weekStart %= 7; + o.weekEnd = ((o.weekStart + 6) % 7); + + var format = DPGlobal.parseFormat(o.format); + if (o.startDate !== -Infinity){ + if (!!o.startDate){ + if (o.startDate instanceof Date) + o.startDate = this._local_to_utc(this._zero_time(o.startDate)); + else + o.startDate = DPGlobal.parseDate(o.startDate, format, o.language); + } + else { + o.startDate = -Infinity; + } + } + if (o.endDate !== Infinity){ + if (!!o.endDate){ + if (o.endDate instanceof Date) + o.endDate = this._local_to_utc(this._zero_time(o.endDate)); + else + o.endDate = DPGlobal.parseDate(o.endDate, format, o.language); + } + else { + o.endDate = Infinity; + } + } + + o.daysOfWeekDisabled = o.daysOfWeekDisabled||[]; + if (!$.isArray(o.daysOfWeekDisabled)) + o.daysOfWeekDisabled = o.daysOfWeekDisabled.split(/[,\s]*/); + o.daysOfWeekDisabled = $.map(o.daysOfWeekDisabled, function(d){ + return parseInt(d, 10); + }); + + o.daysOfWeekHighlighted = o.daysOfWeekHighlighted||[]; + if (!$.isArray(o.daysOfWeekHighlighted)) + o.daysOfWeekHighlighted = o.daysOfWeekHighlighted.split(/[,\s]*/); + o.daysOfWeekHighlighted = $.map(o.daysOfWeekHighlighted, function(d){ + return parseInt(d, 10); + }); + + o.datesDisabled = o.datesDisabled||[]; + if (!$.isArray(o.datesDisabled)) { + var datesDisabled = []; + datesDisabled.push(DPGlobal.parseDate(o.datesDisabled, format, o.language)); + o.datesDisabled = datesDisabled; + } + o.datesDisabled = $.map(o.datesDisabled,function(d){ + return DPGlobal.parseDate(d, format, o.language); + }); + + var plc = String(o.orientation).toLowerCase().split(/\s+/g), + _plc = o.orientation.toLowerCase(); + plc = $.grep(plc, function(word){ + return /^auto|left|right|top|bottom$/.test(word); + }); + o.orientation = {x: 'auto', y: 'auto'}; + if (!_plc || _plc === 'auto') + ; // no action + else if (plc.length === 1){ + switch (plc[0]){ + case 'top': + case 'bottom': + o.orientation.y = plc[0]; + break; + case 'left': + case 'right': + o.orientation.x = plc[0]; + break; + } + } + else { + _plc = $.grep(plc, function(word){ + return /^left|right$/.test(word); + }); + o.orientation.x = _plc[0] || 'auto'; + + _plc = $.grep(plc, function(word){ + return /^top|bottom$/.test(word); + }); + o.orientation.y = _plc[0] || 'auto'; + } + if (o.defaultViewDate) { + var year = o.defaultViewDate.year || new Date().getFullYear(); + var month = o.defaultViewDate.month || 0; + var day = o.defaultViewDate.day || 1; + o.defaultViewDate = UTCDate(year, month, day); + } else { + o.defaultViewDate = UTCToday(); + } + o.showOnFocus = o.showOnFocus !== undefined ? o.showOnFocus : true; + o.zIndexOffset = o.zIndexOffset !== undefined ? o.zIndexOffset : 10; + }, + _events: [], + _secondaryEvents: [], + _applyEvents: function(evs){ + for (var i=0, el, ch, ev; i < evs.length; i++){ + el = evs[i][0]; + if (evs[i].length === 2){ + ch = undefined; + ev = evs[i][1]; + } + else if (evs[i].length === 3){ + ch = evs[i][1]; + ev = evs[i][2]; + } + el.on(ev, ch); + } + }, + _unapplyEvents: function(evs){ + for (var i=0, el, ev, ch; i < evs.length; i++){ + el = evs[i][0]; + if (evs[i].length === 2){ + ch = undefined; + ev = evs[i][1]; + } + else if (evs[i].length === 3){ + ch = evs[i][1]; + ev = evs[i][2]; + } + el.off(ev, ch); + } + }, + _buildEvents: function(){ + var events = { + keyup: $.proxy(function(e){ + if ($.inArray(e.keyCode, [27, 37, 39, 38, 40, 32, 13, 9]) === -1) + this.update(); + }, this), + keydown: $.proxy(this.keydown, this), + paste: $.proxy(this.paste, this) + }; + + if (this.o.showOnFocus === true) { + events.focus = $.proxy(this.show, this); + } + + if (this.isInput) { // single input + this._events = [ + [this.element, events] + ]; + } + else if (this.component && this.hasInput) { // component: input + button + this._events = [ + // For components that are not readonly, allow keyboard nav + [this.element.find('input'), events], + [this.component, { + click: $.proxy(this.show, this) + }] + ]; + } + else if (this.element.is('div')){ // inline datepicker + this.isInline = true; + } + else { + this._events = [ + [this.element, { + click: $.proxy(this.show, this) + }] + ]; + } + this._events.push( + // Component: listen for blur on element descendants + [this.element, '*', { + blur: $.proxy(function(e){ + this._focused_from = e.target; + }, this) + }], + // Input: listen for blur on element + [this.element, { + blur: $.proxy(function(e){ + this._focused_from = e.target; + }, this) + }] + ); + + if (this.o.immediateUpdates) { + // Trigger input updates immediately on changed year/month + this._events.push([this.element, { + 'changeYear changeMonth': $.proxy(function(e){ + this.update(e.date); + }, this) + }]); + } + + this._secondaryEvents = [ + [this.picker, { + click: $.proxy(this.click, this) + }], + [$(window), { + resize: $.proxy(this.place, this) + }], + [$(document), { + mousedown: $.proxy(function(e){ + // Clicked outside the datepicker, hide it + if (!( + this.element.is(e.target) || + this.element.find(e.target).length || + this.picker.is(e.target) || + this.picker.find(e.target).length || + this.picker.hasClass('datepicker-inline') + )){ + this.hide(); + } + }, this) + }] + ]; + }, + _attachEvents: function(){ + this._detachEvents(); + this._applyEvents(this._events); + }, + _detachEvents: function(){ + this._unapplyEvents(this._events); + }, + _attachSecondaryEvents: function(){ + this._detachSecondaryEvents(); + this._applyEvents(this._secondaryEvents); + }, + _detachSecondaryEvents: function(){ + this._unapplyEvents(this._secondaryEvents); + }, + _trigger: function(event, altdate){ + var date = altdate || this.dates.get(-1), + local_date = this._utc_to_local(date); + + this.element.trigger({ + type: event, + date: local_date, + dates: $.map(this.dates, this._utc_to_local), + format: $.proxy(function(ix, format){ + if (arguments.length === 0){ + ix = this.dates.length - 1; + format = this.o.format; + } + else if (typeof ix === 'string'){ + format = ix; + ix = this.dates.length - 1; + } + format = format || this.o.format; + var date = this.dates.get(ix); + return DPGlobal.formatDate(date, format, this.o.language); + }, this) + }); + }, + + show: function(){ + if (this.element.attr('readonly') && this.o.enableOnReadonly === false) + return; + if (!this.isInline) + this.picker.appendTo(this.o.container); + this.place(); + this.picker.show(); + this._attachSecondaryEvents(); + this._trigger('show'); + if ((window.navigator.msMaxTouchPoints || 'ontouchstart' in document) && this.o.disableTouchKeyboard) { + $(this.element).blur(); + } + return this; + }, + + hide: function(){ + if (this.isInline) + return this; + if (!this.picker.is(':visible')) + return this; + this.focusDate = null; + this.picker.hide().detach(); + this._detachSecondaryEvents(); + this.viewMode = this.o.startView; + this.showMode(); + + if ( + this.o.forceParse && + ( + this.isInput && this.element.val() || + this.hasInput && this.element.find('input').val() + ) + ) + this.setValue(); + this._trigger('hide'); + return this; + }, + + remove: function(){ + this.hide(); + this._detachEvents(); + this._detachSecondaryEvents(); + this.picker.remove(); + delete this.element.data().datepicker; + if (!this.isInput){ + delete this.element.data().date; + } + return this; + }, + + paste: function(evt){ + var dateString; + if (evt.originalEvent.clipboardData && evt.originalEvent.clipboardData.types + && $.inArray('text/plain', evt.originalEvent.clipboardData.types) !== -1) { + dateString = evt.originalEvent.clipboardData.getData('text/plain'); + } + else if (window.clipboardData) { + dateString = window.clipboardData.getData('Text'); + } + else { + return; + } + this.setDate(dateString); + this.update(); + evt.preventDefault(); + }, + + _utc_to_local: function(utc){ + return utc && new Date(utc.getTime() + (utc.getTimezoneOffset()*60000)); + }, + _local_to_utc: function(local){ + return local && new Date(local.getTime() - (local.getTimezoneOffset()*60000)); + }, + _zero_time: function(local){ + return local && new Date(local.getFullYear(), local.getMonth(), local.getDate()); + }, + _zero_utc_time: function(utc){ + return utc && new Date(Date.UTC(utc.getUTCFullYear(), utc.getUTCMonth(), utc.getUTCDate())); + }, + + getDates: function(){ + return $.map(this.dates, this._utc_to_local); + }, + + getUTCDates: function(){ + return $.map(this.dates, function(d){ + return new Date(d); + }); + }, + + getDate: function(){ + return this._utc_to_local(this.getUTCDate()); + }, + + getUTCDate: function(){ + var selected_date = this.dates.get(-1); + if (typeof selected_date !== 'undefined') { + return new Date(selected_date); + } else { + return null; + } + }, + + clearDates: function(){ + var element; + if (this.isInput) { + element = this.element; + } else if (this.component) { + element = this.element.find('input'); + } + + if (element) { + element.val(''); + } + + this.update(); + this._trigger('changeDate'); + + if (this.o.autoclose) { + this.hide(); + } + }, + setDates: function(){ + var args = $.isArray(arguments[0]) ? arguments[0] : arguments; + this.update.apply(this, args); + this._trigger('changeDate'); + this.setValue(); + return this; + }, + + setUTCDates: function(){ + var args = $.isArray(arguments[0]) ? arguments[0] : arguments; + this.update.apply(this, $.map(args, this._utc_to_local)); + this._trigger('changeDate'); + this.setValue(); + return this; + }, + + setDate: alias('setDates'), + setUTCDate: alias('setUTCDates'), + + setValue: function(){ + var formatted = this.getFormattedDate(); + if (!this.isInput){ + if (this.component){ + this.element.find('input').val(formatted); + } + } + else { + this.element.val(formatted); + } + return this; + }, + + getFormattedDate: function(format){ + if (format === undefined) + format = this.o.format; + + var lang = this.o.language; + return $.map(this.dates, function(d){ + return DPGlobal.formatDate(d, format, lang); + }).join(this.o.multidateSeparator); + }, + + setStartDate: function(startDate){ + this._process_options({startDate: startDate}); + this.update(); + this.updateNavArrows(); + return this; + }, + + setEndDate: function(endDate){ + this._process_options({endDate: endDate}); + this.update(); + this.updateNavArrows(); + return this; + }, + + setDaysOfWeekDisabled: function(daysOfWeekDisabled){ + this._process_options({daysOfWeekDisabled: daysOfWeekDisabled}); + this.update(); + this.updateNavArrows(); + return this; + }, + + setDaysOfWeekHighlighted: function(daysOfWeekHighlighted){ + this._process_options({daysOfWeekHighlighted: daysOfWeekHighlighted}); + this.update(); + return this; + }, + + setDatesDisabled: function(datesDisabled){ + this._process_options({datesDisabled: datesDisabled}); + this.update(); + this.updateNavArrows(); + }, + + place: function(){ + if (this.isInline) + return this; + var calendarWidth = this.picker.outerWidth(), + calendarHeight = this.picker.outerHeight(), + visualPadding = 10, + container = $(this.o.container), + windowWidth = container.width(), + scrollTop = container.scrollTop(), + appendOffset = container.offset(); + + var parentsZindex = []; + this.element.parents().each(function(){ + var itemZIndex = $(this).css('z-index'); + if (itemZIndex !== 'auto' && itemZIndex !== 0) parentsZindex.push(parseInt(itemZIndex)); + }); + var zIndex = Math.max.apply(Math, parentsZindex) + this.o.zIndexOffset; + var offset = this.component ? this.component.parent().offset() : this.element.offset(); + var height = this.component ? this.component.outerHeight(true) : this.element.outerHeight(false); + var width = this.component ? this.component.outerWidth(true) : this.element.outerWidth(false); + var left = offset.left - appendOffset.left, + top = offset.top - appendOffset.top; + + this.picker.removeClass( + 'datepicker-orient-top datepicker-orient-bottom '+ + 'datepicker-orient-right datepicker-orient-left' + ); + + if (this.o.orientation.x !== 'auto'){ + this.picker.addClass('datepicker-orient-' + this.o.orientation.x); + if (this.o.orientation.x === 'right') + left -= calendarWidth - width; + } + // auto x orientation is best-placement: if it crosses a window + // edge, fudge it sideways + else { + if (offset.left < 0) { + // component is outside the window on the left side. Move it into visible range + this.picker.addClass('datepicker-orient-left'); + left -= offset.left - visualPadding; + } else if (left + calendarWidth > windowWidth) { + // the calendar passes the widow right edge. Align it to component right side + this.picker.addClass('datepicker-orient-right'); + left = offset.left + width - calendarWidth; + } else { + // Default to left + this.picker.addClass('datepicker-orient-left'); + } + } + + // auto y orientation is best-situation: top or bottom, no fudging, + // decision based on which shows more of the calendar + var yorient = this.o.orientation.y, + top_overflow; + if (yorient === 'auto'){ + top_overflow = -scrollTop + top - calendarHeight; + yorient = top_overflow < 0 ? 'bottom' : 'top'; + } + + this.picker.addClass('datepicker-orient-' + yorient); + if (yorient === 'top') + top -= calendarHeight + parseInt(this.picker.css('padding-top')); + else + top += height; + + if (this.o.rtl) { + var right = windowWidth - (left + width); + this.picker.css({ + top: top, + right: right, + zIndex: zIndex + }); + } else { + this.picker.css({ + top: top, + left: left, + zIndex: zIndex + }); + } + return this; + }, + + _allow_update: true, + update: function(){ + if (!this._allow_update) + return this; + + var oldDates = this.dates.copy(), + dates = [], + fromArgs = false; + if (arguments.length){ + $.each(arguments, $.proxy(function(i, date){ + if (date instanceof Date) + date = this._local_to_utc(date); + dates.push(date); + }, this)); + fromArgs = true; + } + else { + dates = this.isInput + ? this.element.val() + : this.element.data('date') || this.element.find('input').val(); + if (dates && this.o.multidate) + dates = dates.split(this.o.multidateSeparator); + else + dates = [dates]; + delete this.element.data().date; + } + + dates = $.map(dates, $.proxy(function(date){ + return DPGlobal.parseDate(date, this.o.format, this.o.language); + }, this)); + dates = $.grep(dates, $.proxy(function(date){ + return ( + date < this.o.startDate || + date > this.o.endDate || + !date + ); + }, this), true); + this.dates.replace(dates); + + if (this.dates.length) + this.viewDate = new Date(this.dates.get(-1)); + else if (this.viewDate < this.o.startDate) + this.viewDate = new Date(this.o.startDate); + else if (this.viewDate > this.o.endDate) + this.viewDate = new Date(this.o.endDate); + else + this.viewDate = this.o.defaultViewDate; + + if (fromArgs){ + // setting date by clicking + this.setValue(); + } + else if (dates.length){ + // setting date by typing + if (String(oldDates) !== String(this.dates)) + this._trigger('changeDate'); + } + if (!this.dates.length && oldDates.length) + this._trigger('clearDate'); + + this.fill(); + this.element.change(); + return this; + }, + + fillDow: function(){ + var dowCnt = this.o.weekStart, + html = ''; + if (this.o.calendarWeeks){ + this.picker.find('.datepicker-days .datepicker-switch') + .attr('colspan', function(i, val){ + return parseInt(val) + 1; + }); + html += ' '; + } + while (dowCnt < this.o.weekStart + 7){ + html += ''+dates[this.o.language].daysMin[(dowCnt++)%7]+''; + } + html += ''; + this.picker.find('.datepicker-days thead').append(html); + }, + + fillMonths: function(){ + var html = '', + i = 0; + while (i < 12){ + html += ''+dates[this.o.language].monthsShort[i++]+''; + } + this.picker.find('.datepicker-months td').html(html); + }, + + setRange: function(range){ + if (!range || !range.length) + delete this.range; + else + this.range = $.map(range, function(d){ + return d.valueOf(); + }); + this.fill(); + }, + + getClassNames: function(date){ + var cls = [], + year = this.viewDate.getUTCFullYear(), + month = this.viewDate.getUTCMonth(), + today = new Date(); + if (date.getUTCFullYear() < year || (date.getUTCFullYear() === year && date.getUTCMonth() < month)){ + cls.push('old'); + } + else if (date.getUTCFullYear() > year || (date.getUTCFullYear() === year && date.getUTCMonth() > month)){ + cls.push('new'); + } + if (this.focusDate && date.valueOf() === this.focusDate.valueOf()) + cls.push('focused'); + // Compare internal UTC date with local today, not UTC today + if (this.o.todayHighlight && + date.getUTCFullYear() === today.getFullYear() && + date.getUTCMonth() === today.getMonth() && + date.getUTCDate() === today.getDate()){ + cls.push('today'); + } + if (this.dates.contains(date) !== -1) + cls.push('active'); + if (date.valueOf() < this.o.startDate || date.valueOf() > this.o.endDate || + $.inArray(date.getUTCDay(), this.o.daysOfWeekDisabled) !== -1){ + cls.push('disabled'); + } + if ($.inArray(date.getUTCDay(), this.o.daysOfWeekHighlighted) !== -1){ + cls.push('highlighted'); + } + if (this.o.datesDisabled.length > 0 && + $.grep(this.o.datesDisabled, function(d){ + return isUTCEquals(date, d); }).length > 0) { + cls.push('disabled', 'disabled-date'); + } + + if (this.range){ + if (date > this.range[0] && date < this.range[this.range.length-1]){ + cls.push('range'); + } + if ($.inArray(date.valueOf(), this.range) !== -1){ + cls.push('selected'); + } + if (date.valueOf() === this.range[0]){ + cls.push('range-start'); + } + if (date.valueOf() === this.range[this.range.length-1]){ + cls.push('range-end'); + } + } + return cls; + }, + + fill: function(){ + var d = new Date(this.viewDate), + year = d.getUTCFullYear(), + month = d.getUTCMonth(), + startYear = this.o.startDate !== -Infinity ? this.o.startDate.getUTCFullYear() : -Infinity, + startMonth = this.o.startDate !== -Infinity ? this.o.startDate.getUTCMonth() : -Infinity, + endYear = this.o.endDate !== Infinity ? this.o.endDate.getUTCFullYear() : Infinity, + endMonth = this.o.endDate !== Infinity ? this.o.endDate.getUTCMonth() : Infinity, + todaytxt = dates[this.o.language].today || dates['en'].today || '', + cleartxt = dates[this.o.language].clear || dates['en'].clear || '', + titleFormat = dates[this.o.language].titleFormat || dates['en'].titleFormat, + tooltip; + if (isNaN(year) || isNaN(month)) + return; + this.picker.find('.datepicker-days thead .datepicker-switch') + .text(DPGlobal.formatDate(new UTCDate(year, month), titleFormat, this.o.language)); + this.picker.find('tfoot .today') + .text(todaytxt) + .toggle(this.o.todayBtn !== false); + this.picker.find('tfoot .clear') + .text(cleartxt) + .toggle(this.o.clearBtn !== false); + this.picker.find('thead .datepicker-title') + .text(this.o.title) + .toggle(this.o.title !== ''); + this.updateNavArrows(); + this.fillMonths(); + var prevMonth = UTCDate(year, month-1, 28), + day = DPGlobal.getDaysInMonth(prevMonth.getUTCFullYear(), prevMonth.getUTCMonth()); + prevMonth.setUTCDate(day); + prevMonth.setUTCDate(day - (prevMonth.getUTCDay() - this.o.weekStart + 7)%7); + var nextMonth = new Date(prevMonth); + if (prevMonth.getUTCFullYear() < 100){ + nextMonth.setUTCFullYear(prevMonth.getUTCFullYear()); + } + nextMonth.setUTCDate(nextMonth.getUTCDate() + 42); + nextMonth = nextMonth.valueOf(); + var html = []; + var clsName; + while (prevMonth.valueOf() < nextMonth){ + if (prevMonth.getUTCDay() === this.o.weekStart){ + html.push(''); + if (this.o.calendarWeeks){ + // ISO 8601: First week contains first thursday. + // ISO also states week starts on Monday, but we can be more abstract here. + var + // Start of current week: based on weekstart/current date + ws = new Date(+prevMonth + (this.o.weekStart - prevMonth.getUTCDay() - 7) % 7 * 864e5), + // Thursday of this week + th = new Date(Number(ws) + (7 + 4 - ws.getUTCDay()) % 7 * 864e5), + // First Thursday of year, year from thursday + yth = new Date(Number(yth = UTCDate(th.getUTCFullYear(), 0, 1)) + (7 + 4 - yth.getUTCDay())%7*864e5), + // Calendar week: ms between thursdays, div ms per day, div 7 days + calWeek = (th - yth) / 864e5 / 7 + 1; + html.push(''+ calWeek +''); + + } + } + clsName = this.getClassNames(prevMonth); + clsName.push('day'); + + if (this.o.beforeShowDay !== $.noop){ + var before = this.o.beforeShowDay(this._utc_to_local(prevMonth)); + if (before === undefined) + before = {}; + else if (typeof(before) === 'boolean') + before = {enabled: before}; + else if (typeof(before) === 'string') + before = {classes: before}; + if (before.enabled === false) + clsName.push('disabled'); + if (before.classes) + clsName = clsName.concat(before.classes.split(/\s+/)); + if (before.tooltip) + tooltip = before.tooltip; + } + + clsName = $.unique(clsName); + html.push(''+prevMonth.getUTCDate() + ''); + tooltip = null; + if (prevMonth.getUTCDay() === this.o.weekEnd){ + html.push(''); + } + prevMonth.setUTCDate(prevMonth.getUTCDate()+1); + } + this.picker.find('.datepicker-days tbody').empty().append(html.join('')); + + var months = this.picker.find('.datepicker-months') + .find('.datepicker-switch') + .text(this.o.maxViewMode < 2 ? 'Months' : year) + .end() + .find('span').removeClass('active'); + + $.each(this.dates, function(i, d){ + if (d.getUTCFullYear() === year) + months.eq(d.getUTCMonth()).addClass('active'); + }); + + if (year < startYear || year > endYear){ + months.addClass('disabled'); + } + if (year === startYear){ + months.slice(0, startMonth).addClass('disabled'); + } + if (year === endYear){ + months.slice(endMonth+1).addClass('disabled'); + } + + if (this.o.beforeShowMonth !== $.noop){ + var that = this; + $.each(months, function(i, month){ + if (!$(month).hasClass('disabled')) { + var moDate = new Date(year, i, 1); + var before = that.o.beforeShowMonth(moDate); + if (before === false) + $(month).addClass('disabled'); + } + }); + } + + html = ''; + year = parseInt(year/10, 10) * 10; + var yearCont = this.picker.find('.datepicker-years') + .find('.datepicker-switch') + .text(year + '-' + (year + 9)) + .end() + .find('td'); + year -= 1; + var years = $.map(this.dates, function(d){ + return d.getUTCFullYear(); + }), + classes; + for (var i = -1; i < 11; i++){ + classes = ['year']; + tooltip = null; + + if (i === -1) + classes.push('old'); + else if (i === 10) + classes.push('new'); + if ($.inArray(year, years) !== -1) + classes.push('active'); + if (year < startYear || year > endYear) + classes.push('disabled'); + + if (this.o.beforeShowYear !== $.noop) { + var yrBefore = this.o.beforeShowYear(new Date(year, 0, 1)); + if (yrBefore === undefined) + yrBefore = {}; + else if (typeof(yrBefore) === 'boolean') + yrBefore = {enabled: yrBefore}; + else if (typeof(yrBefore) === 'string') + yrBefore = {classes: yrBefore}; + if (yrBefore.enabled === false) + classes.push('disabled'); + if (yrBefore.classes) + classes = classes.concat(yrBefore.classes.split(/\s+/)); + if (yrBefore.tooltip) + tooltip = yrBefore.tooltip; + } + + html += '' + year + ''; + year += 1; + } + yearCont.html(html); + }, + + updateNavArrows: function(){ + if (!this._allow_update) + return; + + var d = new Date(this.viewDate), + year = d.getUTCFullYear(), + month = d.getUTCMonth(); + switch (this.viewMode){ + case 0: + if (this.o.startDate !== -Infinity && year <= this.o.startDate.getUTCFullYear() && month <= this.o.startDate.getUTCMonth()){ + this.picker.find('.prev').css({visibility: 'hidden'}); + } + else { + this.picker.find('.prev').css({visibility: 'visible'}); + } + if (this.o.endDate !== Infinity && year >= this.o.endDate.getUTCFullYear() && month >= this.o.endDate.getUTCMonth()){ + this.picker.find('.next').css({visibility: 'hidden'}); + } + else { + this.picker.find('.next').css({visibility: 'visible'}); + } + break; + case 1: + case 2: + if (this.o.startDate !== -Infinity && year <= this.o.startDate.getUTCFullYear() || this.o.maxViewMode < 2){ + this.picker.find('.prev').css({visibility: 'hidden'}); + } + else { + this.picker.find('.prev').css({visibility: 'visible'}); + } + if (this.o.endDate !== Infinity && year >= this.o.endDate.getUTCFullYear() || this.o.maxViewMode < 2){ + this.picker.find('.next').css({visibility: 'hidden'}); + } + else { + this.picker.find('.next').css({visibility: 'visible'}); + } + break; + } + }, + + click: function(e){ + e.preventDefault(); + e.stopPropagation(); + var target = $(e.target).closest('span, td, th'), + year, month, day; + if (target.length === 1){ + switch (target[0].nodeName.toLowerCase()){ + case 'th': + switch (target[0].className){ + case 'datepicker-switch': + this.showMode(1); + break; + case 'prev': + case 'next': + var dir = DPGlobal.modes[this.viewMode].navStep * (target[0].className === 'prev' ? -1 : 1); + switch (this.viewMode){ + case 0: + this.viewDate = this.moveMonth(this.viewDate, dir); + this._trigger('changeMonth', this.viewDate); + break; + case 1: + case 2: + this.viewDate = this.moveYear(this.viewDate, dir); + if (this.viewMode === 1) + this._trigger('changeYear', this.viewDate); + break; + } + this.fill(); + break; + case 'today': + var date = new Date(); + date = UTCDate(date.getFullYear(), date.getMonth(), date.getDate(), 0, 0, 0); + + this.showMode(-2); + var which = this.o.todayBtn === 'linked' ? null : 'view'; + this._setDate(date, which); + break; + case 'clear': + this.clearDates(); + break; + } + break; + case 'span': + if (!target.hasClass('disabled')){ + this.viewDate.setUTCDate(1); + if (target.hasClass('month')){ + day = 1; + month = target.parent().find('span').index(target); + year = this.viewDate.getUTCFullYear(); + this.viewDate.setUTCMonth(month); + this._trigger('changeMonth', this.viewDate); + if (this.o.minViewMode === 1){ + this._setDate(UTCDate(year, month, day)); + this.showMode(); + } else { + this.showMode(-1); + } + } + else { + day = 1; + month = 0; + year = parseInt(target.text(), 10)||0; + this.viewDate.setUTCFullYear(year); + this._trigger('changeYear', this.viewDate); + if (this.o.minViewMode === 2){ + this._setDate(UTCDate(year, month, day)); + } + this.showMode(-1); + } + this.fill(); + } + break; + case 'td': + if (target.hasClass('day') && !target.hasClass('disabled')){ + day = parseInt(target.text(), 10)||1; + year = this.viewDate.getUTCFullYear(); + month = this.viewDate.getUTCMonth(); + if (target.hasClass('old')){ + if (month === 0){ + month = 11; + year -= 1; + } + else { + month -= 1; + } + } + else if (target.hasClass('new')){ + if (month === 11){ + month = 0; + year += 1; + } + else { + month += 1; + } + } + this._setDate(UTCDate(year, month, day)); + } + break; + } + } + if (this.picker.is(':visible') && this._focused_from){ + $(this._focused_from).focus(); + } + delete this._focused_from; + }, + + _toggle_multidate: function(date){ + var ix = this.dates.contains(date); + if (!date){ + this.dates.clear(); + } + + if (ix !== -1){ + if (this.o.multidate === true || this.o.multidate > 1 || this.o.toggleActive){ + this.dates.remove(ix); + } + } else if (this.o.multidate === false) { + this.dates.clear(); + this.dates.push(date); + } + else { + this.dates.push(date); + } + + if (typeof this.o.multidate === 'number') + while (this.dates.length > this.o.multidate) + this.dates.remove(0); + }, + + _setDate: function(date, which){ + if (!which || which === 'date') + this._toggle_multidate(date && new Date(date)); + if (!which || which === 'view') + this.viewDate = date && new Date(date); + + this.fill(); + this.setValue(); + if (!which || which !== 'view') { + this._trigger('changeDate'); + } + var element; + if (this.isInput){ + element = this.element; + } + else if (this.component){ + element = this.element.find('input'); + } + if (element){ + element.change(); + } + if (this.o.autoclose && (!which || which === 'date')){ + this.hide(); + } + }, + + moveMonth: function(date, dir){ + if (!isValidDate(date)) + return this.o.defaultViewDate; + if (!dir) + return date; + var new_date = new Date(date.valueOf()), + day = new_date.getUTCDate(), + month = new_date.getUTCMonth(), + mag = Math.abs(dir), + new_month, test; + dir = dir > 0 ? 1 : -1; + if (mag === 1){ + test = dir === -1 + // If going back one month, make sure month is not current month + // (eg, Mar 31 -> Feb 31 == Feb 28, not Mar 02) + ? function(){ + return new_date.getUTCMonth() === month; + } + // If going forward one month, make sure month is as expected + // (eg, Jan 31 -> Feb 31 == Feb 28, not Mar 02) + : function(){ + return new_date.getUTCMonth() !== new_month; + }; + new_month = month + dir; + new_date.setUTCMonth(new_month); + // Dec -> Jan (12) or Jan -> Dec (-1) -- limit expected date to 0-11 + if (new_month < 0 || new_month > 11) + new_month = (new_month + 12) % 12; + } + else { + // For magnitudes >1, move one month at a time... + for (var i=0; i < mag; i++) + // ...which might decrease the day (eg, Jan 31 to Feb 28, etc)... + new_date = this.moveMonth(new_date, dir); + // ...then reset the day, keeping it in the new month + new_month = new_date.getUTCMonth(); + new_date.setUTCDate(day); + test = function(){ + return new_month !== new_date.getUTCMonth(); + }; + } + // Common date-resetting loop -- if date is beyond end of month, make it + // end of month + while (test()){ + new_date.setUTCDate(--day); + new_date.setUTCMonth(new_month); + } + return new_date; + }, + + moveYear: function(date, dir){ + return this.moveMonth(date, dir*12); + }, + + dateWithinRange: function(date){ + return date >= this.o.startDate && date <= this.o.endDate; + }, + + keydown: function(e){ + if (!this.picker.is(':visible')){ + if (e.keyCode === 40 || e.keyCode === 27) { // allow down to re-show picker + this.show(); + e.stopPropagation(); + } + return; + } + var dateChanged = false, + dir, newDate, newViewDate, + focusDate = this.focusDate || this.viewDate; + switch (e.keyCode){ + case 27: // escape + if (this.focusDate){ + this.focusDate = null; + this.viewDate = this.dates.get(-1) || this.viewDate; + this.fill(); + } + else + this.hide(); + e.preventDefault(); + e.stopPropagation(); + break; + case 37: // left + case 39: // right + if (!this.o.keyboardNavigation) + break; + dir = e.keyCode === 37 ? -1 : 1; + if (e.ctrlKey){ + newDate = this.moveYear(this.dates.get(-1) || UTCToday(), dir); + newViewDate = this.moveYear(focusDate, dir); + this._trigger('changeYear', this.viewDate); + } + else if (e.shiftKey){ + newDate = this.moveMonth(this.dates.get(-1) || UTCToday(), dir); + newViewDate = this.moveMonth(focusDate, dir); + this._trigger('changeMonth', this.viewDate); + } + else { + newDate = new Date(this.dates.get(-1) || UTCToday()); + newDate.setUTCDate(newDate.getUTCDate() + dir); + newViewDate = new Date(focusDate); + newViewDate.setUTCDate(focusDate.getUTCDate() + dir); + } + if (this.dateWithinRange(newViewDate)){ + this.focusDate = this.viewDate = newViewDate; + this.setValue(); + this.fill(); + e.preventDefault(); + } + break; + case 38: // up + case 40: // down + if (!this.o.keyboardNavigation) + break; + dir = e.keyCode === 38 ? -1 : 1; + if (e.ctrlKey){ + newDate = this.moveYear(this.dates.get(-1) || UTCToday(), dir); + newViewDate = this.moveYear(focusDate, dir); + this._trigger('changeYear', this.viewDate); + } + else if (e.shiftKey){ + newDate = this.moveMonth(this.dates.get(-1) || UTCToday(), dir); + newViewDate = this.moveMonth(focusDate, dir); + this._trigger('changeMonth', this.viewDate); + } + else { + newDate = new Date(this.dates.get(-1) || UTCToday()); + newDate.setUTCDate(newDate.getUTCDate() + dir * 7); + newViewDate = new Date(focusDate); + newViewDate.setUTCDate(focusDate.getUTCDate() + dir * 7); + } + if (this.dateWithinRange(newViewDate)){ + this.focusDate = this.viewDate = newViewDate; + this.setValue(); + this.fill(); + e.preventDefault(); + } + break; + case 32: // spacebar + // Spacebar is used in manually typing dates in some formats. + // As such, its behavior should not be hijacked. + break; + case 13: // enter + if (!this.o.forceParse) { + break; + } + focusDate = this.focusDate || this.dates.get(-1) || this.viewDate; + if (this.o.keyboardNavigation) { + this._toggle_multidate(focusDate); + dateChanged = true; + } + this.focusDate = null; + this.viewDate = this.dates.get(-1) || this.viewDate; + this.setValue(); + this.fill(); + if (this.picker.is(':visible')){ + e.preventDefault(); + if (typeof e.stopPropagation === 'function') { + e.stopPropagation(); // All modern browsers, IE9+ + } else { + e.cancelBubble = true; // IE6,7,8 ignore "stopPropagation" + } + if (this.o.autoclose) + this.hide(); + } + break; + case 9: // tab + this.focusDate = null; + this.viewDate = this.dates.get(-1) || this.viewDate; + this.fill(); + this.hide(); + break; + } + if (dateChanged){ + if (this.dates.length) + this._trigger('changeDate'); + else + this._trigger('clearDate'); + var element; + if (this.isInput){ + element = this.element; + } + else if (this.component){ + element = this.element.find('input'); + } + if (element){ + element.change(); + } + } + }, + + showMode: function(dir){ + if (dir){ + this.viewMode = Math.max(this.o.minViewMode, Math.min(this.o.maxViewMode, this.viewMode + dir)); + } + this.picker + .children('div') + .hide() + .filter('.datepicker-' + DPGlobal.modes[this.viewMode].clsName) + .show(); + this.updateNavArrows(); + } + }; + + var DateRangePicker = function(element, options){ + this.element = $(element); + this.inputs = $.map(options.inputs, function(i){ + return i.jquery ? i[0] : i; + }); + delete options.inputs; + + datepickerPlugin.call($(this.inputs), options) + .on('changeDate', $.proxy(this.dateUpdated, this)); + + this.pickers = $.map(this.inputs, function(i){ + return $(i).data('datepicker'); + }); + this.updateDates(); + }; + DateRangePicker.prototype = { + updateDates: function(){ + this.dates = $.map(this.pickers, function(i){ + return i.getUTCDate(); + }); + this.updateRanges(); + }, + updateRanges: function(){ + var range = $.map(this.dates, function(d){ + return d.valueOf(); + }); + $.each(this.pickers, function(i, p){ + p.setRange(range); + }); + }, + dateUpdated: function(e){ + // `this.updating` is a workaround for preventing infinite recursion + // between `changeDate` triggering and `setUTCDate` calling. Until + // there is a better mechanism. + if (this.updating) + return; + this.updating = true; + + var dp = $(e.target).data('datepicker'); + + if (typeof(dp) === "undefined") { + return; + } + + var new_date = dp.getUTCDate(), + i = $.inArray(e.target, this.inputs), + j = i - 1, + k = i + 1, + l = this.inputs.length; + if (i === -1) + return; + + $.each(this.pickers, function(i, p){ + if (!p.getUTCDate()) + p.setUTCDate(new_date); + }); + + if (new_date < this.dates[j]){ + // Date being moved earlier/left + while (j >= 0 && new_date < this.dates[j]){ + this.pickers[j--].setUTCDate(new_date); + } + } + else if (new_date > this.dates[k]){ + // Date being moved later/right + while (k < l && new_date > this.dates[k]){ + this.pickers[k++].setUTCDate(new_date); + } + } + this.updateDates(); + + delete this.updating; + }, + remove: function(){ + $.map(this.pickers, function(p){ p.remove(); }); + delete this.element.data().datepicker; + } + }; + + function opts_from_el(el, prefix){ + // Derive options from element data-attrs + var data = $(el).data(), + out = {}, inkey, + replace = new RegExp('^' + prefix.toLowerCase() + '([A-Z])'); + prefix = new RegExp('^' + prefix.toLowerCase()); + function re_lower(_,a){ + return a.toLowerCase(); + } + for (var key in data) + if (prefix.test(key)){ + inkey = key.replace(replace, re_lower); + out[inkey] = data[key]; + } + return out; + } + + function opts_from_locale(lang){ + // Derive options from locale plugins + var out = {}; + // Check if "de-DE" style date is available, if not language should + // fallback to 2 letter code eg "de" + if (!dates[lang]){ + lang = lang.split('-')[0]; + if (!dates[lang]) + return; + } + var d = dates[lang]; + $.each(locale_opts, function(i,k){ + if (k in d) + out[k] = d[k]; + }); + return out; + } + + var old = $.fn.datepicker; + var datepickerPlugin = function(option){ + var args = Array.apply(null, arguments); + args.shift(); + var internal_return; + this.each(function(){ + var $this = $(this), + data = $this.data('datepicker'), + options = typeof option === 'object' && option; + if (!data){ + var elopts = opts_from_el(this, 'date'), + // Preliminary otions + xopts = $.extend({}, defaults, elopts, options), + locopts = opts_from_locale(xopts.language), + // Options priority: js args, data-attrs, locales, defaults + opts = $.extend({}, defaults, locopts, elopts, options); + if ($this.hasClass('input-daterange') || opts.inputs){ + var ropts = { + inputs: opts.inputs || $this.find('input').toArray() + }; + $this.data('datepicker', (data = new DateRangePicker(this, $.extend(opts, ropts)))); + } + else { + $this.data('datepicker', (data = new Datepicker(this, opts))); + } + } + if (typeof option === 'string' && typeof data[option] === 'function'){ + internal_return = data[option].apply(data, args); + if (internal_return !== undefined) + return false; + } + }); + if (internal_return !== undefined) + return internal_return; + else + return this; + }; + $.fn.datepicker = datepickerPlugin; + + var defaults = $.fn.datepicker.defaults = { + autoclose: false, + beforeShowDay: $.noop, + beforeShowMonth: $.noop, + beforeShowYear: $.noop, + calendarWeeks: false, + clearBtn: false, + toggleActive: false, + daysOfWeekDisabled: [], + daysOfWeekHighlighted: [], + datesDisabled: [], + endDate: Infinity, + forceParse: true, + format: 'mm/dd/yyyy', + keyboardNavigation: true, + language: 'en', + minViewMode: 0, + maxViewMode: 2, + multidate: false, + multidateSeparator: ',', + orientation: "auto", + rtl: false, + startDate: -Infinity, + startView: 0, + todayBtn: false, + todayHighlight: false, + weekStart: 0, + disableTouchKeyboard: false, + enableOnReadonly: true, + container: 'body', + immediateUpdates: false, + title: '' + }; + var locale_opts = $.fn.datepicker.locale_opts = [ + 'format', + 'rtl', + 'weekStart' + ]; + $.fn.datepicker.Constructor = Datepicker; + var dates = $.fn.datepicker.dates = { + en: { + days: ["Sunday", "Monday", "Tuesday", "Wednesday", "Thursday", "Friday", "Saturday"], + daysShort: ["Sun", "Mon", "Tue", "Wed", "Thu", "Fri", "Sat"], + daysMin: ["Su", "Mo", "Tu", "We", "Th", "Fr", "Sa"], + months: ["January", "February", "March", "April", "May", "June", "July", "August", "September", "October", "November", "December"], + monthsShort: ["Jan", "Feb", "Mar", "Apr", "May", "Jun", "Jul", "Aug", "Sep", "Oct", "Nov", "Dec"], + today: "Today", + clear: "Clear", + titleFormat: "MM yyyy" + } + }; + + var DPGlobal = { + modes: [ + { + clsName: 'days', + navFnc: 'Month', + navStep: 1 + }, + { + clsName: 'months', + navFnc: 'FullYear', + navStep: 1 + }, + { + clsName: 'years', + navFnc: 'FullYear', + navStep: 10 + }], + isLeapYear: function(year){ + return (((year % 4 === 0) && (year % 100 !== 0)) || (year % 400 === 0)); + }, + getDaysInMonth: function(year, month){ + return [31, (DPGlobal.isLeapYear(year) ? 29 : 28), 31, 30, 31, 30, 31, 31, 30, 31, 30, 31][month]; + }, + validParts: /dd?|DD?|mm?|MM?|yy(?:yy)?/g, + nonpunctuation: /[^ -\/:-@\[\u3400-\u9fff-`{-~\t\n\r]+/g, + parseFormat: function(format){ + // IE treats \0 as a string end in inputs (truncating the value), + // so it's a bad format delimiter, anyway + var separators = format.replace(this.validParts, '\0').split('\0'), + parts = format.match(this.validParts); + if (!separators || !separators.length || !parts || parts.length === 0){ + throw new Error("Invalid date format."); + } + return {separators: separators, parts: parts}; + }, + parseDate: function(date, format, language){ + if (!date) + return undefined; + if (date instanceof Date) + return date; + if (typeof format === 'string') + format = DPGlobal.parseFormat(format); + var part_re = /([\-+]\d+)([dmwy])/, + parts = date.match(/([\-+]\d+)([dmwy])/g), + part, dir, i; + if (/^[\-+]\d+[dmwy]([\s,]+[\-+]\d+[dmwy])*$/.test(date)){ + date = new Date(); + for (i=0; i < parts.length; i++){ + part = part_re.exec(parts[i]); + dir = parseInt(part[1]); + switch (part[2]){ + case 'd': + date.setUTCDate(date.getUTCDate() + dir); + break; + case 'm': + date = Datepicker.prototype.moveMonth.call(Datepicker.prototype, date, dir); + break; + case 'w': + date.setUTCDate(date.getUTCDate() + dir * 7); + break; + case 'y': + date = Datepicker.prototype.moveYear.call(Datepicker.prototype, date, dir); + break; + } + } + return UTCDate(date.getUTCFullYear(), date.getUTCMonth(), date.getUTCDate(), 0, 0, 0); + } + parts = date && date.match(this.nonpunctuation) || []; + date = new Date(); + var parsed = {}, + setters_order = ['yyyy', 'yy', 'M', 'MM', 'm', 'mm', 'd', 'dd'], + setters_map = { + yyyy: function(d,v){ + return d.setUTCFullYear(v); + }, + yy: function(d,v){ + return d.setUTCFullYear(2000+v); + }, + m: function(d,v){ + if (isNaN(d)) + return d; + v -= 1; + while (v < 0) v += 12; + v %= 12; + d.setUTCMonth(v); + while (d.getUTCMonth() !== v) + d.setUTCDate(d.getUTCDate()-1); + return d; + }, + d: function(d,v){ + return d.setUTCDate(v); + } + }, + val, filtered; + setters_map['M'] = setters_map['MM'] = setters_map['mm'] = setters_map['m']; + setters_map['dd'] = setters_map['d']; + date = UTCDate(date.getFullYear(), date.getMonth(), date.getDate(), 0, 0, 0); + var fparts = format.parts.slice(); + // Remove noop parts + if (parts.length !== fparts.length){ + fparts = $(fparts).filter(function(i,p){ + return $.inArray(p, setters_order) !== -1; + }).toArray(); + } + // Process remainder + function match_part(){ + var m = this.slice(0, parts[i].length), + p = parts[i].slice(0, m.length); + return m.toLowerCase() === p.toLowerCase(); + } + if (parts.length === fparts.length){ + var cnt; + for (i=0, cnt = fparts.length; i < cnt; i++){ + val = parseInt(parts[i], 10); + part = fparts[i]; + if (isNaN(val)){ + switch (part){ + case 'MM': + filtered = $(dates[language].months).filter(match_part); + val = $.inArray(filtered[0], dates[language].months) + 1; + break; + case 'M': + filtered = $(dates[language].monthsShort).filter(match_part); + val = $.inArray(filtered[0], dates[language].monthsShort) + 1; + break; + } + } + parsed[part] = val; + } + var _date, s; + for (i=0; i < setters_order.length; i++){ + s = setters_order[i]; + if (s in parsed && !isNaN(parsed[s])){ + _date = new Date(date); + setters_map[s](_date, parsed[s]); + if (!isNaN(_date)) + date = _date; + } + } + } + return date; + }, + formatDate: function(date, format, language){ + if (!date) + return ''; + if (typeof format === 'string') + format = DPGlobal.parseFormat(format); + var val = { + d: date.getUTCDate(), + D: dates[language].daysShort[date.getUTCDay()], + DD: dates[language].days[date.getUTCDay()], + m: date.getUTCMonth() + 1, + M: dates[language].monthsShort[date.getUTCMonth()], + MM: dates[language].months[date.getUTCMonth()], + yy: date.getUTCFullYear().toString().substring(2), + yyyy: date.getUTCFullYear() + }; + val.dd = (val.d < 10 ? '0' : '') + val.d; + val.mm = (val.m < 10 ? '0' : '') + val.m; + date = []; + var seps = $.extend([], format.separators); + for (var i=0, cnt = format.parts.length; i <= cnt; i++){ + if (seps.length) + date.push(seps.shift()); + date.push(val[format.parts[i]]); + } + return date.join(''); + }, + headTemplate: ''+ + ''+ + ''+ + ''+ + ''+ + '«'+ + ''+ + '»'+ + ''+ + '', + contTemplate: '', + footTemplate: ''+ + ''+ + ''+ + ''+ + ''+ + ''+ + ''+ + '' + }; + DPGlobal.template = '
'+ + '
'+ + ''+ + DPGlobal.headTemplate+ + ''+ + DPGlobal.footTemplate+ + '
'+ + '
'+ + '
'+ + ''+ + DPGlobal.headTemplate+ + DPGlobal.contTemplate+ + DPGlobal.footTemplate+ + '
'+ + '
'+ + '
'+ + ''+ + DPGlobal.headTemplate+ + DPGlobal.contTemplate+ + DPGlobal.footTemplate+ + '
'+ + '
'+ + '
'; + + $.fn.datepicker.DPGlobal = DPGlobal; + + + /* DATEPICKER NO CONFLICT + * =================== */ + + $.fn.datepicker.noConflict = function(){ + $.fn.datepicker = old; + return this; + }; + + /* DATEPICKER VERSION + * =================== */ + $.fn.datepicker.version = "1.4.1-dev"; + + /* DATEPICKER DATA-API + * ================== */ + + $(document).on( + 'focus.datepicker.data-api click.datepicker.data-api', + '[data-provide="datepicker"]', + function(e){ + var $this = $(this); + if ($this.data('datepicker')) + return; + e.preventDefault(); + // component click requires us to explicitly show it + datepickerPlugin.call($this, 'show'); + } + ); + $(function(){ + datepickerPlugin.call($('[data-provide="datepicker-inline"]')); + }); + +})); diff --git a/static/bootstrap3/js/bootstrap-datepicker.min.js b/static/bootstrap3/js/bootstrap-datepicker.min.js new file mode 100755 index 0000000..bb1b8fc --- /dev/null +++ b/static/bootstrap3/js/bootstrap-datepicker.min.js @@ -0,0 +1,8 @@ +/*! + * Datepicker for Bootstrap v1.5.0-dev (https://github.com/eternicode/bootstrap-datepicker) + * + * Copyright 2012 Stefan Petre + * Improvements by Andrew Rowls + * Licensed under the Apache License v2.0 (http://www.apache.org/licenses/LICENSE-2.0) + */ +!function(a){"function"==typeof define&&define.amd?define(["jquery"],a):a("object"==typeof exports?require("jquery"):jQuery)}(function(a,b){function c(){return new Date(Date.UTC.apply(Date,arguments))}function d(){var a=new Date;return c(a.getFullYear(),a.getMonth(),a.getDate())}function e(a,b){return a.getUTCFullYear()===b.getUTCFullYear()&&a.getUTCMonth()===b.getUTCMonth()&&a.getUTCDate()===b.getUTCDate()}function f(a){return function(){return this[a].apply(this,arguments)}}function g(a){return a&&!isNaN(a.getTime())}function h(b,c){function d(a,b){return b.toLowerCase()}var e,f=a(b).data(),g={},h=new RegExp("^"+c.toLowerCase()+"([A-Z])");c=new RegExp("^"+c.toLowerCase());for(var i in f)c.test(i)&&(e=i.replace(h,d),g[e]=f[i]);return g}function i(b){var c={};if(q[b]||(b=b.split("-")[0],q[b])){var d=q[b];return a.each(p,function(a,b){b in d&&(c[b]=d[b])}),c}}var j=function(){var b={get:function(a){return this.slice(a)[0]},contains:function(a){for(var b=a&&a.valueOf(),c=0,d=this.length;d>c;c++)if(this[c].valueOf()===b)return c;return-1},remove:function(a){this.splice(a,1)},replace:function(b){b&&(a.isArray(b)||(b=[b]),this.clear(),this.push.apply(this,b))},clear:function(){this.length=0},copy:function(){var a=new j;return a.replace(this),a}};return function(){var c=[];return c.push.apply(c,arguments),a.extend(c,b),c}}(),k=function(b,c){this._process_options(c),this.dates=new j,this.viewDate=this.o.defaultViewDate,this.focusDate=null,this.element=a(b),this.isInline=!1,this.isInput=this.element.is("input"),this.component=this.element.hasClass("date")?this.element.find(".add-on, .input-group-addon, .btn"):!1,this.hasInput=this.component&&this.element.find("input").length,this.component&&0===this.component.length&&(this.component=!1),this.picker=a(r.template),this._buildEvents(),this._attachEvents(),this.isInline?this.picker.addClass("datepicker-inline").appendTo(this.element):this.picker.addClass("datepicker-dropdown dropdown-menu"),this.o.rtl&&this.picker.addClass("datepicker-rtl"),this.viewMode=this.o.startView,this.o.calendarWeeks&&this.picker.find("tfoot .today, tfoot .clear").attr("colspan",function(a,b){return parseInt(b)+1}),this._allow_update=!1,this.setStartDate(this._o.startDate),this.setEndDate(this._o.endDate),this.setDaysOfWeekDisabled(this.o.daysOfWeekDisabled),this.setDaysOfWeekHighlighted(this.o.daysOfWeekHighlighted),this.setDatesDisabled(this.o.datesDisabled),this.fillDow(),this.fillMonths(),this._allow_update=!0,this.update(),this.showMode(),this.isInline&&this.show()};k.prototype={constructor:k,_process_options:function(e){this._o=a.extend({},this._o,e);var f=this.o=a.extend({},this._o),g=f.language;switch(q[g]||(g=g.split("-")[0],q[g]||(g=o.language)),f.language=g,f.startView){case 2:case"decade":f.startView=2;break;case 1:case"year":f.startView=1;break;default:f.startView=0}switch(f.minViewMode){case 1:case"months":f.minViewMode=1;break;case 2:case"years":f.minViewMode=2;break;default:f.minViewMode=0}switch(f.maxViewMode){case 0:case"days":f.maxViewMode=0;break;case 1:case"months":f.maxViewMode=1;break;default:f.maxViewMode=2}f.startView=Math.min(f.startView,f.maxViewMode),f.startView=Math.max(f.startView,f.minViewMode),f.multidate!==!0&&(f.multidate=Number(f.multidate)||!1,f.multidate!==!1&&(f.multidate=Math.max(0,f.multidate))),f.multidateSeparator=String(f.multidateSeparator),f.weekStart%=7,f.weekEnd=(f.weekStart+6)%7;var h=r.parseFormat(f.format);if(f.startDate!==-(1/0)&&(f.startDate?f.startDate instanceof Date?f.startDate=this._local_to_utc(this._zero_time(f.startDate)):f.startDate=r.parseDate(f.startDate,h,f.language):f.startDate=-(1/0)),f.endDate!==1/0&&(f.endDate?f.endDate instanceof Date?f.endDate=this._local_to_utc(this._zero_time(f.endDate)):f.endDate=r.parseDate(f.endDate,h,f.language):f.endDate=1/0),f.daysOfWeekDisabled=f.daysOfWeekDisabled||[],a.isArray(f.daysOfWeekDisabled)||(f.daysOfWeekDisabled=f.daysOfWeekDisabled.split(/[,\s]*/)),f.daysOfWeekDisabled=a.map(f.daysOfWeekDisabled,function(a){return parseInt(a,10)}),f.daysOfWeekHighlighted=f.daysOfWeekHighlighted||[],a.isArray(f.daysOfWeekHighlighted)||(f.daysOfWeekHighlighted=f.daysOfWeekHighlighted.split(/[,\s]*/)),f.daysOfWeekHighlighted=a.map(f.daysOfWeekHighlighted,function(a){return parseInt(a,10)}),f.datesDisabled=f.datesDisabled||[],!a.isArray(f.datesDisabled)){var i=[];i.push(r.parseDate(f.datesDisabled,h,f.language)),f.datesDisabled=i}f.datesDisabled=a.map(f.datesDisabled,function(a){return r.parseDate(a,h,f.language)});var j=String(f.orientation).toLowerCase().split(/\s+/g),k=f.orientation.toLowerCase();if(j=a.grep(j,function(a){return/^auto|left|right|top|bottom$/.test(a)}),f.orientation={x:"auto",y:"auto"},k&&"auto"!==k)if(1===j.length)switch(j[0]){case"top":case"bottom":f.orientation.y=j[0];break;case"left":case"right":f.orientation.x=j[0]}else k=a.grep(j,function(a){return/^left|right$/.test(a)}),f.orientation.x=k[0]||"auto",k=a.grep(j,function(a){return/^top|bottom$/.test(a)}),f.orientation.y=k[0]||"auto";else;if(f.defaultViewDate){var l=f.defaultViewDate.year||(new Date).getFullYear(),m=f.defaultViewDate.month||0,n=f.defaultViewDate.day||1;f.defaultViewDate=c(l,m,n)}else f.defaultViewDate=d();f.showOnFocus=f.showOnFocus!==b?f.showOnFocus:!0,f.zIndexOffset=f.zIndexOffset!==b?f.zIndexOffset:10},_events:[],_secondaryEvents:[],_applyEvents:function(a){for(var c,d,e,f=0;ff?(this.picker.addClass("datepicker-orient-right"),n=k.left+m-b):this.picker.addClass("datepicker-orient-left");var p,q=this.o.orientation.y;if("auto"===q&&(p=-g+o-c,q=0>p?"bottom":"top"),this.picker.addClass("datepicker-orient-"+q),"top"===q?o-=c+parseInt(this.picker.css("padding-top")):o+=l,this.o.rtl){var r=f-(n+m);this.picker.css({top:o,right:r,zIndex:j})}else this.picker.css({top:o,left:n,zIndex:j});return this},_allow_update:!0,update:function(){if(!this._allow_update)return this;var b=this.dates.copy(),c=[],d=!1;return arguments.length?(a.each(arguments,a.proxy(function(a,b){b instanceof Date&&(b=this._local_to_utc(b)),c.push(b)},this)),d=!0):(c=this.isInput?this.element.val():this.element.data("date")||this.element.find("input").val(),c=c&&this.o.multidate?c.split(this.o.multidateSeparator):[c],delete this.element.data().date),c=a.map(c,a.proxy(function(a){return r.parseDate(a,this.o.format,this.o.language)},this)),c=a.grep(c,a.proxy(function(a){return athis.o.endDate||!a},this),!0),this.dates.replace(c),this.dates.length?this.viewDate=new Date(this.dates.get(-1)):this.viewDatethis.o.endDate?this.viewDate=new Date(this.o.endDate):this.viewDate=this.o.defaultViewDate,d?this.setValue():c.length&&String(b)!==String(this.dates)&&this._trigger("changeDate"),!this.dates.length&&b.length&&this._trigger("clearDate"),this.fill(),this.element.change(),this},fillDow:function(){var a=this.o.weekStart,b="";for(this.o.calendarWeeks&&(this.picker.find(".datepicker-days .datepicker-switch").attr("colspan",function(a,b){return parseInt(b)+1}),b+=' ');a'+q[this.o.language].daysMin[a++%7]+"";b+="",this.picker.find(".datepicker-days thead").append(b)},fillMonths:function(){for(var a="",b=0;12>b;)a+=''+q[this.o.language].monthsShort[b++]+"";this.picker.find(".datepicker-months td").html(a)},setRange:function(b){b&&b.length?this.range=a.map(b,function(a){return a.valueOf()}):delete this.range,this.fill()},getClassNames:function(b){var c=[],d=this.viewDate.getUTCFullYear(),f=this.viewDate.getUTCMonth(),g=new Date;return b.getUTCFullYear()d||b.getUTCFullYear()===d&&b.getUTCMonth()>f)&&c.push("new"),this.focusDate&&b.valueOf()===this.focusDate.valueOf()&&c.push("focused"),this.o.todayHighlight&&b.getUTCFullYear()===g.getFullYear()&&b.getUTCMonth()===g.getMonth()&&b.getUTCDate()===g.getDate()&&c.push("today"),-1!==this.dates.contains(b)&&c.push("active"),(b.valueOf()this.o.endDate||-1!==a.inArray(b.getUTCDay(),this.o.daysOfWeekDisabled))&&c.push("disabled"),-1!==a.inArray(b.getUTCDay(),this.o.daysOfWeekHighlighted)&&c.push("highlighted"),this.o.datesDisabled.length>0&&a.grep(this.o.datesDisabled,function(a){return e(b,a)}).length>0&&c.push("disabled","disabled-date"),this.range&&(b>this.range[0]&&b"),this.o.calendarWeeks)){var v=new Date(+o+(this.o.weekStart-o.getUTCDay()-7)%7*864e5),w=new Date(Number(v)+(11-v.getUTCDay())%7*864e5),x=new Date(Number(x=c(w.getUTCFullYear(),0,1))+(11-x.getUTCDay())%7*864e5),y=(w-x)/864e5/7+1;u.push(''+y+"")}if(t=this.getClassNames(o),t.push("day"),this.o.beforeShowDay!==a.noop){var z=this.o.beforeShowDay(this._utc_to_local(o));z===b?z={}:"boolean"==typeof z?z={enabled:z}:"string"==typeof z&&(z={classes:z}),z.enabled===!1&&t.push("disabled"),z.classes&&(t=t.concat(z.classes.split(/\s+/))),z.tooltip&&(d=z.tooltip)}t=a.unique(t),u.push('"+o.getUTCDate()+""),d=null,o.getUTCDay()===this.o.weekEnd&&u.push(""),o.setUTCDate(o.getUTCDate()+1)}this.picker.find(".datepicker-days tbody").empty().append(u.join(""));var A=this.picker.find(".datepicker-months").find(".datepicker-switch").text(this.o.maxViewMode<2?"Months":f).end().find("span").removeClass("active");if(a.each(this.dates,function(a,b){b.getUTCFullYear()===f&&A.eq(b.getUTCMonth()).addClass("active")}),(h>f||f>j)&&A.addClass("disabled"),f===h&&A.slice(0,i).addClass("disabled"),f===j&&A.slice(k+1).addClass("disabled"),this.o.beforeShowMonth!==a.noop){var B=this;a.each(A,function(b,c){if(!a(c).hasClass("disabled")){var d=new Date(f,b,1),e=B.o.beforeShowMonth(d);e===!1&&a(c).addClass("disabled")}})}u="",f=10*parseInt(f/10,10);var C=this.picker.find(".datepicker-years").find(".datepicker-switch").text(f+"-"+(f+9)).end().find("td");f-=1;for(var D,E=a.map(this.dates,function(a){return a.getUTCFullYear()}),F=-1;11>F;F++){if(D=["year"],d=null,-1===F?D.push("old"):10===F&&D.push("new"),-1!==a.inArray(f,E)&&D.push("active"),(h>f||f>j)&&D.push("disabled"),this.o.beforeShowYear!==a.noop){var G=this.o.beforeShowYear(new Date(f,0,1));G===b?G={}:"boolean"==typeof G?G={enabled:G}:"string"==typeof G&&(G={classes:G}),G.enabled===!1&&D.push("disabled"),G.classes&&(D=D.concat(G.classes.split(/\s+/))),G.tooltip&&(d=G.tooltip)}u+='"+f+"",f+=1}C.html(u)}},updateNavArrows:function(){if(this._allow_update){var a=new Date(this.viewDate),b=a.getUTCFullYear(),c=a.getUTCMonth();switch(this.viewMode){case 0:this.o.startDate!==-(1/0)&&b<=this.o.startDate.getUTCFullYear()&&c<=this.o.startDate.getUTCMonth()?this.picker.find(".prev").css({visibility:"hidden"}):this.picker.find(".prev").css({visibility:"visible"}),this.o.endDate!==1/0&&b>=this.o.endDate.getUTCFullYear()&&c>=this.o.endDate.getUTCMonth()?this.picker.find(".next").css({visibility:"hidden"}):this.picker.find(".next").css({visibility:"visible"});break;case 1:case 2:this.o.startDate!==-(1/0)&&b<=this.o.startDate.getUTCFullYear()||this.o.maxViewMode<2?this.picker.find(".prev").css({visibility:"hidden"}):this.picker.find(".prev").css({visibility:"visible"}),this.o.endDate!==1/0&&b>=this.o.endDate.getUTCFullYear()||this.o.maxViewMode<2?this.picker.find(".next").css({visibility:"hidden"}):this.picker.find(".next").css({visibility:"visible"})}}},click:function(b){b.preventDefault(),b.stopPropagation();var d,e,f,g=a(b.target).closest("span, td, th");if(1===g.length)switch(g[0].nodeName.toLowerCase()){case"th":switch(g[0].className){case"datepicker-switch":this.showMode(1);break;case"prev":case"next":var h=r.modes[this.viewMode].navStep*("prev"===g[0].className?-1:1);switch(this.viewMode){case 0:this.viewDate=this.moveMonth(this.viewDate,h),this._trigger("changeMonth",this.viewDate);break;case 1:case 2:this.viewDate=this.moveYear(this.viewDate,h),1===this.viewMode&&this._trigger("changeYear",this.viewDate)}this.fill();break;case"today":var i=new Date;i=c(i.getFullYear(),i.getMonth(),i.getDate(),0,0,0),this.showMode(-2);var j="linked"===this.o.todayBtn?null:"view";this._setDate(i,j);break;case"clear":this.clearDates()}break;case"span":g.hasClass("disabled")||(this.viewDate.setUTCDate(1),g.hasClass("month")?(f=1,e=g.parent().find("span").index(g),d=this.viewDate.getUTCFullYear(),this.viewDate.setUTCMonth(e),this._trigger("changeMonth",this.viewDate),1===this.o.minViewMode?(this._setDate(c(d,e,f)),this.showMode()):this.showMode(-1)):(f=1,e=0,d=parseInt(g.text(),10)||0,this.viewDate.setUTCFullYear(d),this._trigger("changeYear",this.viewDate),2===this.o.minViewMode&&this._setDate(c(d,e,f)),this.showMode(-1)),this.fill());break;case"td":g.hasClass("day")&&!g.hasClass("disabled")&&(f=parseInt(g.text(),10)||1,d=this.viewDate.getUTCFullYear(),e=this.viewDate.getUTCMonth(),g.hasClass("old")?0===e?(e=11,d-=1):e-=1:g.hasClass("new")&&(11===e?(e=0,d+=1):e+=1),this._setDate(c(d,e,f)))}this.picker.is(":visible")&&this._focused_from&&a(this._focused_from).focus(),delete this._focused_from},_toggle_multidate:function(a){var b=this.dates.contains(a);if(a||this.dates.clear(),-1!==b?(this.o.multidate===!0||this.o.multidate>1||this.o.toggleActive)&&this.dates.remove(b):this.o.multidate===!1?(this.dates.clear(),this.dates.push(a)):this.dates.push(a),"number"==typeof this.o.multidate)for(;this.dates.length>this.o.multidate;)this.dates.remove(0)},_setDate:function(a,b){b&&"date"!==b||this._toggle_multidate(a&&new Date(a)),b&&"view"!==b||(this.viewDate=a&&new Date(a)),this.fill(),this.setValue(),b&&"view"===b||this._trigger("changeDate");var c;this.isInput?c=this.element:this.component&&(c=this.element.find("input")),c&&c.change(),!this.o.autoclose||b&&"date"!==b||this.hide()},moveMonth:function(a,b){if(!g(a))return this.o.defaultViewDate;if(!b)return a;var c,d,e=new Date(a.valueOf()),f=e.getUTCDate(),h=e.getUTCMonth(),i=Math.abs(b);if(b=b>0?1:-1,1===i)d=-1===b?function(){return e.getUTCMonth()===h}:function(){return e.getUTCMonth()!==c},c=h+b,e.setUTCMonth(c),(0>c||c>11)&&(c=(c+12)%12);else{for(var j=0;i>j;j++)e=this.moveMonth(e,b);c=e.getUTCMonth(),e.setUTCDate(f),d=function(){return c!==e.getUTCMonth()}}for(;d();)e.setUTCDate(--f),e.setUTCMonth(c);return e},moveYear:function(a,b){return this.moveMonth(a,12*b)},dateWithinRange:function(a){return a>=this.o.startDate&&a<=this.o.endDate},keydown:function(a){if(!this.picker.is(":visible"))return void((40===a.keyCode||27===a.keyCode)&&(this.show(),a.stopPropagation()));var b,c,e,f=!1,g=this.focusDate||this.viewDate;switch(a.keyCode){case 27:this.focusDate?(this.focusDate=null,this.viewDate=this.dates.get(-1)||this.viewDate,this.fill()):this.hide(),a.preventDefault(),a.stopPropagation();break;case 37:case 39:if(!this.o.keyboardNavigation)break;b=37===a.keyCode?-1:1,a.ctrlKey?(c=this.moveYear(this.dates.get(-1)||d(),b),e=this.moveYear(g,b),this._trigger("changeYear",this.viewDate)):a.shiftKey?(c=this.moveMonth(this.dates.get(-1)||d(),b),e=this.moveMonth(g,b),this._trigger("changeMonth",this.viewDate)):(c=new Date(this.dates.get(-1)||d()),c.setUTCDate(c.getUTCDate()+b),e=new Date(g),e.setUTCDate(g.getUTCDate()+b)),this.dateWithinRange(e)&&(this.focusDate=this.viewDate=e,this.setValue(),this.fill(),a.preventDefault());break;case 38:case 40:if(!this.o.keyboardNavigation)break;b=38===a.keyCode?-1:1,a.ctrlKey?(c=this.moveYear(this.dates.get(-1)||d(),b),e=this.moveYear(g,b),this._trigger("changeYear",this.viewDate)):a.shiftKey?(c=this.moveMonth(this.dates.get(-1)||d(),b),e=this.moveMonth(g,b),this._trigger("changeMonth",this.viewDate)):(c=new Date(this.dates.get(-1)||d()),c.setUTCDate(c.getUTCDate()+7*b),e=new Date(g),e.setUTCDate(g.getUTCDate()+7*b)),this.dateWithinRange(e)&&(this.focusDate=this.viewDate=e,this.setValue(),this.fill(),a.preventDefault());break;case 32:break;case 13:if(!this.o.forceParse)break;g=this.focusDate||this.dates.get(-1)||this.viewDate,this.o.keyboardNavigation&&(this._toggle_multidate(g),f=!0),this.focusDate=null,this.viewDate=this.dates.get(-1)||this.viewDate,this.setValue(),this.fill(),this.picker.is(":visible")&&(a.preventDefault(),"function"==typeof a.stopPropagation?a.stopPropagation():a.cancelBubble=!0,this.o.autoclose&&this.hide());break;case 9:this.focusDate=null,this.viewDate=this.dates.get(-1)||this.viewDate,this.fill(),this.hide()}if(f){this.dates.length?this._trigger("changeDate"):this._trigger("clearDate");var h;this.isInput?h=this.element:this.component&&(h=this.element.find("input")),h&&h.change()}},showMode:function(a){a&&(this.viewMode=Math.max(this.o.minViewMode,Math.min(this.o.maxViewMode,this.viewMode+a))),this.picker.children("div").hide().filter(".datepicker-"+r.modes[this.viewMode].clsName).show(),this.updateNavArrows()}};var l=function(b,c){this.element=a(b),this.inputs=a.map(c.inputs,function(a){return a.jquery?a[0]:a}),delete c.inputs,n.call(a(this.inputs),c).on("changeDate",a.proxy(this.dateUpdated,this)),this.pickers=a.map(this.inputs,function(b){return a(b).data("datepicker")}),this.updateDates()};l.prototype={updateDates:function(){this.dates=a.map(this.pickers,function(a){return a.getUTCDate()}),this.updateRanges()},updateRanges:function(){var b=a.map(this.dates,function(a){return a.valueOf()});a.each(this.pickers,function(a,c){c.setRange(b)})},dateUpdated:function(b){if(!this.updating){this.updating=!0;var c=a(b.target).data("datepicker");if("undefined"!=typeof c){var d=c.getUTCDate(),e=a.inArray(b.target,this.inputs),f=e-1,g=e+1,h=this.inputs.length;if(-1!==e){if(a.each(this.pickers,function(a,b){b.getUTCDate()||b.setUTCDate(d)}),d=0&&dthis.dates[g])for(;h>g&&d>this.dates[g];)this.pickers[g++].setUTCDate(d);this.updateDates(),delete this.updating}}}},remove:function(){a.map(this.pickers,function(a){a.remove()}),delete this.element.data().datepicker}};var m=a.fn.datepicker,n=function(c){var d=Array.apply(null,arguments);d.shift();var e;return this.each(function(){var f=a(this),g=f.data("datepicker"),j="object"==typeof c&&c;if(!g){var m=h(this,"date"),n=a.extend({},o,m,j),p=i(n.language),q=a.extend({},o,p,m,j);if(f.hasClass("input-daterange")||q.inputs){var r={inputs:q.inputs||f.find("input").toArray()};f.data("datepicker",g=new l(this,a.extend(q,r)))}else f.data("datepicker",g=new k(this,q))}return"string"==typeof c&&"function"==typeof g[c]&&(e=g[c].apply(g,d),e!==b)?!1:void 0}),e!==b?e:this};a.fn.datepicker=n;var o=a.fn.datepicker.defaults={autoclose:!1,beforeShowDay:a.noop,beforeShowMonth:a.noop,beforeShowYear:a.noop,calendarWeeks:!1,clearBtn:!1,toggleActive:!1,daysOfWeekDisabled:[],daysOfWeekHighlighted:[],datesDisabled:[],endDate:1/0,forceParse:!0,format:"mm/dd/yyyy",keyboardNavigation:!0,language:"en",minViewMode:0,maxViewMode:2,multidate:!1,multidateSeparator:",",orientation:"auto",rtl:!1,startDate:-(1/0),startView:0,todayBtn:!1,todayHighlight:!1,weekStart:0,disableTouchKeyboard:!1,enableOnReadonly:!0,container:"body",immediateUpdates:!1,title:""},p=a.fn.datepicker.locale_opts=["format","rtl","weekStart"];a.fn.datepicker.Constructor=k;var q=a.fn.datepicker.dates={en:{days:["Sunday","Monday","Tuesday","Wednesday","Thursday","Friday","Saturday"],daysShort:["Sun","Mon","Tue","Wed","Thu","Fri","Sat"],daysMin:["Su","Mo","Tu","We","Th","Fr","Sa"],months:["January","February","March","April","May","June","July","August","September","October","November","December"],monthsShort:["Jan","Feb","Mar","Apr","May","Jun","Jul","Aug","Sep","Oct","Nov","Dec"],today:"Today",clear:"Clear",titleFormat:"MM yyyy"}},r={modes:[{clsName:"days",navFnc:"Month",navStep:1},{clsName:"months",navFnc:"FullYear",navStep:1},{clsName:"years",navFnc:"FullYear",navStep:10}],isLeapYear:function(a){return a%4===0&&a%100!==0||a%400===0},getDaysInMonth:function(a,b){return[31,r.isLeapYear(a)?29:28,31,30,31,30,31,31,30,31,30,31][b]},validParts:/dd?|DD?|mm?|MM?|yy(?:yy)?/g,nonpunctuation:/[^ -\/:-@\[\u3400-\u9fff-`{-~\t\n\r]+/g,parseFormat:function(a){var b=a.replace(this.validParts,"\x00").split("\x00"),c=a.match(this.validParts);if(!b||!b.length||!c||0===c.length)throw new Error("Invalid date format.");return{separators:b,parts:c}},parseDate:function(d,e,f){function g(){var a=this.slice(0,m[j].length),b=m[j].slice(0,a.length);return a.toLowerCase()===b.toLowerCase()}if(!d)return b;if(d instanceof Date)return d;"string"==typeof e&&(e=r.parseFormat(e));var h,i,j,l=/([\-+]\d+)([dmwy])/,m=d.match(/([\-+]\d+)([dmwy])/g);if(/^[\-+]\d+[dmwy]([\s,]+[\-+]\d+[dmwy])*$/.test(d)){for(d=new Date,j=0;jb;)b+=12;for(b%=12,a.setUTCMonth(b);a.getUTCMonth()!==b;)a.setUTCDate(a.getUTCDate()-1);return a},d:function(a,b){return a.setUTCDate(b)}};t.M=t.MM=t.mm=t.m,t.dd=t.d,d=c(d.getFullYear(),d.getMonth(),d.getDate(),0,0,0);var u=e.parts.slice();if(m.length!==u.length&&(u=a(u).filter(function(b,c){return-1!==a.inArray(c,s)}).toArray()),m.length===u.length){var v;for(j=0,v=u.length;v>j;j++){if(n=parseInt(m[j],10),h=u[j],isNaN(n))switch(h){case"MM":o=a(q[f].months).filter(g),n=a.inArray(o[0],q[f].months)+1;break;case"M":o=a(q[f].monthsShort).filter(g),n=a.inArray(o[0],q[f].monthsShort)+1}p[h]=n}var w,x;for(j=0;j=g;g++)f.length&&b.push(f.shift()),b.push(e[c.parts[g]]);return b.join("")},headTemplate:'«»',contTemplate:'',footTemplate:''};r.template='
'+r.headTemplate+""+r.footTemplate+'
'+r.headTemplate+r.contTemplate+r.footTemplate+'
'+r.headTemplate+r.contTemplate+r.footTemplate+"
",a.fn.datepicker.DPGlobal=r,a.fn.datepicker.noConflict=function(){return a.fn.datepicker=m,this},a.fn.datepicker.version="1.4.1-dev",a(document).on("focus.datepicker.data-api click.datepicker.data-api",'[data-provide="datepicker"]',function(b){var c=a(this);c.data("datepicker")||(b.preventDefault(),n.call(c,"show"))}),a(function(){n.call(a('[data-provide="datepicker-inline"]'))})}); \ No newline at end of file diff --git a/static/bootstrap3/js/bootstrap-datetimepicker.min.js b/static/bootstrap3/js/bootstrap-datetimepicker.min.js new file mode 100755 index 0000000..db3d085 --- /dev/null +++ b/static/bootstrap3/js/bootstrap-datetimepicker.min.js @@ -0,0 +1,9 @@ +/*! version : 4.17.37 + ========================================================= + bootstrap-datetimejs + https://github.com/Eonasdan/bootstrap-datetimepicker + Copyright (c) 2015 Jonathan Peterson + ========================================================= + */ +!function(a){"use strict";if("function"==typeof define&&define.amd)define(["jquery","moment"],a);else if("object"==typeof exports)a(require("jquery"),require("moment"));else{if("undefined"==typeof jQuery)throw"bootstrap-datetimepicker requires jQuery to be loaded first";if("undefined"==typeof moment)throw"bootstrap-datetimepicker requires Moment.js to be loaded first";a(jQuery,moment)}}(function(a,b){"use strict";if(!b)throw new Error("bootstrap-datetimepicker requires Moment.js to be loaded first");var c=function(c,d){var e,f,g,h,i,j,k,l={},m=!0,n=!1,o=!1,p=0,q=[{clsName:"days",navFnc:"M",navStep:1},{clsName:"months",navFnc:"y",navStep:1},{clsName:"years",navFnc:"y",navStep:10},{clsName:"decades",navFnc:"y",navStep:100}],r=["days","months","years","decades"],s=["top","bottom","auto"],t=["left","right","auto"],u=["default","top","bottom"],v={up:38,38:"up",down:40,40:"down",left:37,37:"left",right:39,39:"right",tab:9,9:"tab",escape:27,27:"escape",enter:13,13:"enter",pageUp:33,33:"pageUp",pageDown:34,34:"pageDown",shift:16,16:"shift",control:17,17:"control",space:32,32:"space",t:84,84:"t","delete":46,46:"delete"},w={},x=function(a){var c,e,f,g,h,i=!1;return void 0!==b.tz&&void 0!==d.timeZone&&null!==d.timeZone&&""!==d.timeZone&&(i=!0),void 0===a||null===a?c=i?b().tz(d.timeZone).startOf("d"):b().startOf("d"):i?(e=b().tz(d.timeZone).utcOffset(),f=b(a,j,d.useStrict).utcOffset(),f!==e?(g=b().tz(d.timeZone).format("Z"),h=b(a,j,d.useStrict).format("YYYY-MM-DD[T]HH:mm:ss")+g,c=b(h,j,d.useStrict).tz(d.timeZone)):c=b(a,j,d.useStrict).tz(d.timeZone)):c=b(a,j,d.useStrict),c},y=function(a){if("string"!=typeof a||a.length>1)throw new TypeError("isEnabled expects a single character string parameter");switch(a){case"y":return-1!==i.indexOf("Y");case"M":return-1!==i.indexOf("M");case"d":return-1!==i.toLowerCase().indexOf("d");case"h":case"H":return-1!==i.toLowerCase().indexOf("h");case"m":return-1!==i.indexOf("m");case"s":return-1!==i.indexOf("s");default:return!1}},z=function(){return y("h")||y("m")||y("s")},A=function(){return y("y")||y("M")||y("d")},B=function(){var b=a("").append(a("").append(a("").addClass("prev").attr("data-action","previous").append(a("").addClass(d.icons.previous))).append(a("").addClass("picker-switch").attr("data-action","pickerSwitch").attr("colspan",d.calendarWeeks?"6":"5")).append(a("").addClass("next").attr("data-action","next").append(a("").addClass(d.icons.next)))),c=a("").append(a("").append(a("").attr("colspan",d.calendarWeeks?"8":"7")));return[a("
").addClass("datepicker-days").append(a("").addClass("table-condensed").append(b).append(a(""))),a("
").addClass("datepicker-months").append(a("
").addClass("table-condensed").append(b.clone()).append(c.clone())),a("
").addClass("datepicker-years").append(a("
").addClass("table-condensed").append(b.clone()).append(c.clone())),a("
").addClass("datepicker-decades").append(a("
").addClass("table-condensed").append(b.clone()).append(c.clone()))]},C=function(){var b=a(""),c=a(""),e=a("");return y("h")&&(b.append(a("
").append(a("").attr({href:"#",tabindex:"-1",title:d.tooltips.incrementHour}).addClass("btn").attr("data-action","incrementHours").append(a("").addClass(d.icons.up)))),c.append(a("").append(a("").addClass("timepicker-hour").attr({"data-time-component":"hours",title:d.tooltips.pickHour}).attr("data-action","showHours"))),e.append(a("").append(a("").attr({href:"#",tabindex:"-1",title:d.tooltips.decrementHour}).addClass("btn").attr("data-action","decrementHours").append(a("").addClass(d.icons.down))))),y("m")&&(y("h")&&(b.append(a("").addClass("separator")),c.append(a("").addClass("separator").html(":")),e.append(a("").addClass("separator"))),b.append(a("").append(a("").attr({href:"#",tabindex:"-1",title:d.tooltips.incrementMinute}).addClass("btn").attr("data-action","incrementMinutes").append(a("").addClass(d.icons.up)))),c.append(a("").append(a("").addClass("timepicker-minute").attr({"data-time-component":"minutes",title:d.tooltips.pickMinute}).attr("data-action","showMinutes"))),e.append(a("").append(a("").attr({href:"#",tabindex:"-1",title:d.tooltips.decrementMinute}).addClass("btn").attr("data-action","decrementMinutes").append(a("").addClass(d.icons.down))))),y("s")&&(y("m")&&(b.append(a("").addClass("separator")),c.append(a("").addClass("separator").html(":")),e.append(a("").addClass("separator"))),b.append(a("").append(a("").attr({href:"#",tabindex:"-1",title:d.tooltips.incrementSecond}).addClass("btn").attr("data-action","incrementSeconds").append(a("").addClass(d.icons.up)))),c.append(a("").append(a("").addClass("timepicker-second").attr({"data-time-component":"seconds",title:d.tooltips.pickSecond}).attr("data-action","showSeconds"))),e.append(a("").append(a("").attr({href:"#",tabindex:"-1",title:d.tooltips.decrementSecond}).addClass("btn").attr("data-action","decrementSeconds").append(a("").addClass(d.icons.down))))),h||(b.append(a("").addClass("separator")),c.append(a("").append(a("").addClass("separator"))),a("
").addClass("timepicker-picker").append(a("").addClass("table-condensed").append([b,c,e]))},D=function(){var b=a("
").addClass("timepicker-hours").append(a("
").addClass("table-condensed")),c=a("
").addClass("timepicker-minutes").append(a("
").addClass("table-condensed")),d=a("
").addClass("timepicker-seconds").append(a("
").addClass("table-condensed")),e=[C()];return y("h")&&e.push(b),y("m")&&e.push(c),y("s")&&e.push(d),e},E=function(){var b=[];return d.showTodayButton&&b.push(a(" {% for device in device_group_devices %} - + {% if device.mute_alerts %} + + {% else %} + + {% endif %}
").append(a("").attr({"data-action":"today",title:d.tooltips.today}).append(a("").addClass(d.icons.today)))),!d.sideBySide&&A()&&z()&&b.push(a("").append(a("").attr({"data-action":"togglePicker",title:d.tooltips.selectTime}).append(a("").addClass(d.icons.time)))),d.showClear&&b.push(a("").append(a("").attr({"data-action":"clear",title:d.tooltips.clear}).append(a("").addClass(d.icons.clear)))),d.showClose&&b.push(a("").append(a("").attr({"data-action":"close",title:d.tooltips.close}).append(a("").addClass(d.icons.close)))),a("").addClass("table-condensed").append(a("").append(a("").append(b)))},F=function(){var b=a("
").addClass("bootstrap-datetimepicker-widget dropdown-menu"),c=a("
").addClass("datepicker").append(B()),e=a("
").addClass("timepicker").append(D()),f=a("
    ").addClass("list-unstyled"),g=a("
  • ").addClass("picker-switch"+(d.collapse?" accordion-toggle":"")).append(E());return d.inline&&b.removeClass("dropdown-menu"),h&&b.addClass("usetwentyfour"),y("s")&&!h&&b.addClass("wider"),d.sideBySide&&A()&&z()?(b.addClass("timepicker-sbs"),"top"===d.toolbarPlacement&&b.append(g),b.append(a("
    ").addClass("row").append(c.addClass("col-md-6")).append(e.addClass("col-md-6"))),"bottom"===d.toolbarPlacement&&b.append(g),b):("top"===d.toolbarPlacement&&f.append(g),A()&&f.append(a("
  • ").addClass(d.collapse&&z()?"collapse in":"").append(c)),"default"===d.toolbarPlacement&&f.append(g),z()&&f.append(a("
  • ").addClass(d.collapse&&A()?"collapse":"").append(e)),"bottom"===d.toolbarPlacement&&f.append(g),b.append(f))},G=function(){var b,e={};return b=c.is("input")||d.inline?c.data():c.find("input").data(),b.dateOptions&&b.dateOptions instanceof Object&&(e=a.extend(!0,e,b.dateOptions)),a.each(d,function(a){var c="date"+a.charAt(0).toUpperCase()+a.slice(1);void 0!==b[c]&&(e[a]=b[c])}),e},H=function(){var b,e=(n||c).position(),f=(n||c).offset(),g=d.widgetPositioning.vertical,h=d.widgetPositioning.horizontal;if(d.widgetParent)b=d.widgetParent.append(o);else if(c.is("input"))b=c.after(o).parent();else{if(d.inline)return void(b=c.append(o));b=c,c.children().first().after(o)}if("auto"===g&&(g=f.top+1.5*o.height()>=a(window).height()+a(window).scrollTop()&&o.height()+c.outerHeight()a(window).width()?"right":"left"),"top"===g?o.addClass("top").removeClass("bottom"):o.addClass("bottom").removeClass("top"),"right"===h?o.addClass("pull-right"):o.removeClass("pull-right"),"relative"!==b.css("position")&&(b=b.parents().filter(function(){return"relative"===a(this).css("position")}).first()),0===b.length)throw new Error("datetimepicker component should be placed within a relative positioned container");o.css({top:"top"===g?"auto":e.top+c.outerHeight(),bottom:"top"===g?e.top+c.outerHeight():"auto",left:"left"===h?b===c?0:e.left:"auto",right:"left"===h?"auto":b.outerWidth()-c.outerWidth()-(b===c?0:e.left)})},I=function(a){"dp.change"===a.type&&(a.date&&a.date.isSame(a.oldDate)||!a.date&&!a.oldDate)||c.trigger(a)},J=function(a){"y"===a&&(a="YYYY"),I({type:"dp.update",change:a,viewDate:f.clone()})},K=function(a){o&&(a&&(k=Math.max(p,Math.min(3,k+a))),o.find(".datepicker > div").hide().filter(".datepicker-"+q[k].clsName).show())},L=function(){var b=a("
"),c=f.clone().startOf("w").startOf("d");for(d.calendarWeeks===!0&&b.append(a(""),d.calendarWeeks&&c.append('"),k.push(c)),g="",b.isBefore(f,"M")&&(g+=" old"),b.isAfter(f,"M")&&(g+=" new"),b.isSame(e,"d")&&!m&&(g+=" active"),Q(b,"d")||(g+=" disabled"),b.isSame(x(),"d")&&(g+=" today"),(0===b.day()||6===b.day())&&(g+=" weekend"),c.append('"),b.add(1,"d");i.find("tbody").empty().append(k),S(),T(),U()}},W=function(){var b=o.find(".timepicker-hours table"),c=f.clone().startOf("d"),d=[],e=a("");for(f.hour()>11&&!h&&c.hour(12);c.isSame(f,"d")&&(h||f.hour()<12&&c.hour()<12||f.hour()>11);)c.hour()%4===0&&(e=a(""),d.push(e)),e.append('"),c.add(1,"h");b.empty().append(d)},X=function(){for(var b=o.find(".timepicker-minutes table"),c=f.clone().startOf("h"),e=[],g=a(""),h=1===d.stepping?5:d.stepping;f.isSame(c,"h");)c.minute()%(4*h)===0&&(g=a(""),e.push(g)),g.append('"),c.add(h,"m");b.empty().append(e)},Y=function(){for(var b=o.find(".timepicker-seconds table"),c=f.clone().startOf("m"),d=[],e=a("");f.isSame(c,"m");)c.second()%20===0&&(e=a(""),d.push(e)),e.append('"),c.add(5,"s");b.empty().append(d)},Z=function(){var a,b,c=o.find(".timepicker span[data-time-component]");h||(a=o.find(".timepicker [data-action=togglePeriod]"),b=e.clone().add(e.hours()>=12?-12:12,"h"),a.text(e.format("A")),Q(b,"h")?a.removeClass("disabled"):a.addClass("disabled")),c.filter("[data-time-component=hours]").text(e.format(h?"HH":"hh")),c.filter("[data-time-component=minutes]").text(e.format("mm")),c.filter("[data-time-component=seconds]").text(e.format("ss")),W(),X(),Y()},$=function(){o&&(V(),Z())},_=function(a){var b=m?null:e;return a?(a=a.clone().locale(d.locale),1!==d.stepping&&a.minutes(Math.round(a.minutes()/d.stepping)*d.stepping%60).seconds(0),void(Q(a)?(e=a,f=e.clone(),g.val(e.format(i)),c.data("date",e.format(i)),m=!1,$(),I({type:"dp.change",date:e.clone(),oldDate:b})):(d.keepInvalid||g.val(m?"":e.format(i)),I({type:"dp.error",date:a})))):(m=!0,g.val(""),c.data("date",""),I({type:"dp.change",date:!1,oldDate:b}),void $())},aa=function(){var b=!1;return o?(o.find(".collapse").each(function(){var c=a(this).data("collapse");return c&&c.transitioning?(b=!0,!1):!0}),b?l:(n&&n.hasClass("btn")&&n.toggleClass("active"),o.hide(),a(window).off("resize",H),o.off("click","[data-action]"),o.off("mousedown",!1),o.remove(),o=!1,I({type:"dp.hide",date:e.clone()}),g.blur(),l)):l},ba=function(){_(null)},ca={next:function(){var a=q[k].navFnc;f.add(q[k].navStep,a),V(),J(a)},previous:function(){var a=q[k].navFnc;f.subtract(q[k].navStep,a),V(),J(a)},pickerSwitch:function(){K(1)},selectMonth:function(b){var c=a(b.target).closest("tbody").find("span").index(a(b.target));f.month(c),k===p?(_(e.clone().year(f.year()).month(f.month())),d.inline||aa()):(K(-1),V()),J("M")},selectYear:function(b){var c=parseInt(a(b.target).text(),10)||0;f.year(c),k===p?(_(e.clone().year(f.year())),d.inline||aa()):(K(-1),V()),J("YYYY")},selectDecade:function(b){var c=parseInt(a(b.target).data("selection"),10)||0;f.year(c),k===p?(_(e.clone().year(f.year())),d.inline||aa()):(K(-1),V()),J("YYYY")},selectDay:function(b){var c=f.clone();a(b.target).is(".old")&&c.subtract(1,"M"),a(b.target).is(".new")&&c.add(1,"M"),_(c.date(parseInt(a(b.target).text(),10))),z()||d.keepOpen||d.inline||aa()},incrementHours:function(){var a=e.clone().add(1,"h");Q(a,"h")&&_(a)},incrementMinutes:function(){var a=e.clone().add(d.stepping,"m");Q(a,"m")&&_(a)},incrementSeconds:function(){var a=e.clone().add(1,"s");Q(a,"s")&&_(a)},decrementHours:function(){var a=e.clone().subtract(1,"h");Q(a,"h")&&_(a)},decrementMinutes:function(){var a=e.clone().subtract(d.stepping,"m");Q(a,"m")&&_(a)},decrementSeconds:function(){var a=e.clone().subtract(1,"s");Q(a,"s")&&_(a)},togglePeriod:function(){_(e.clone().add(e.hours()>=12?-12:12,"h"))},togglePicker:function(b){var c,e=a(b.target),f=e.closest("ul"),g=f.find(".in"),h=f.find(".collapse:not(.in)");if(g&&g.length){if(c=g.data("collapse"),c&&c.transitioning)return;g.collapse?(g.collapse("hide"),h.collapse("show")):(g.removeClass("in"),h.addClass("in")),e.is("span")?e.toggleClass(d.icons.time+" "+d.icons.date):e.find("span").toggleClass(d.icons.time+" "+d.icons.date)}},showPicker:function(){o.find(".timepicker > div:not(.timepicker-picker)").hide(),o.find(".timepicker .timepicker-picker").show()},showHours:function(){o.find(".timepicker .timepicker-picker").hide(),o.find(".timepicker .timepicker-hours").show()},showMinutes:function(){o.find(".timepicker .timepicker-picker").hide(),o.find(".timepicker .timepicker-minutes").show()},showSeconds:function(){o.find(".timepicker .timepicker-picker").hide(),o.find(".timepicker .timepicker-seconds").show()},selectHour:function(b){var c=parseInt(a(b.target).text(),10);h||(e.hours()>=12?12!==c&&(c+=12):12===c&&(c=0)),_(e.clone().hours(c)),ca.showPicker.call(l)},selectMinute:function(b){_(e.clone().minutes(parseInt(a(b.target).text(),10))),ca.showPicker.call(l)},selectSecond:function(b){_(e.clone().seconds(parseInt(a(b.target).text(),10))),ca.showPicker.call(l)},clear:ba,today:function(){var a=x();Q(a,"d")&&_(a)},close:aa},da=function(b){return a(b.currentTarget).is(".disabled")?!1:(ca[a(b.currentTarget).data("action")].apply(l,arguments),!1)},ea=function(){var b,c={year:function(a){return a.month(0).date(1).hours(0).seconds(0).minutes(0)},month:function(a){return a.date(1).hours(0).seconds(0).minutes(0)},day:function(a){return a.hours(0).seconds(0).minutes(0)},hour:function(a){return a.seconds(0).minutes(0)},minute:function(a){return a.seconds(0)}};return g.prop("disabled")||!d.ignoreReadonly&&g.prop("readonly")||o?l:(void 0!==g.val()&&0!==g.val().trim().length?_(ga(g.val().trim())):d.useCurrent&&m&&(g.is("input")&&0===g.val().trim().length||d.inline)&&(b=x(),"string"==typeof d.useCurrent&&(b=c[d.useCurrent](b)),_(b)),o=F(),L(),R(),o.find(".timepicker-hours").hide(),o.find(".timepicker-minutes").hide(),o.find(".timepicker-seconds").hide(),$(),K(),a(window).on("resize",H),o.on("click","[data-action]",da),o.on("mousedown",!1),n&&n.hasClass("btn")&&n.toggleClass("active"),o.show(),H(),d.focusOnShow&&!g.is(":focus")&&g.focus(),I({type:"dp.show"}),l)},fa=function(){return o?aa():ea()},ga=function(a){return a=void 0===d.parseInputDate?b.isMoment(a)||a instanceof Date?b(a):x(a):d.parseInputDate(a),a.locale(d.locale),a},ha=function(a){var b,c,e,f,g=null,h=[],i={},j=a.which,k="p";w[j]=k;for(b in w)w.hasOwnProperty(b)&&w[b]===k&&(h.push(b),parseInt(b,10)!==j&&(i[b]=!0));for(b in d.keyBinds)if(d.keyBinds.hasOwnProperty(b)&&"function"==typeof d.keyBinds[b]&&(e=b.split(" "),e.length===h.length&&v[j]===e[e.length-1])){for(f=!0,c=e.length-2;c>=0;c--)if(!(v[e[c]]in i)){f=!1;break}if(f){g=d.keyBinds[b];break}}g&&(g.call(l,o),a.stopPropagation(),a.preventDefault())},ia=function(a){w[a.which]="r",a.stopPropagation(),a.preventDefault()},ja=function(b){var c=a(b.target).val().trim(),d=c?ga(c):null;return _(d),b.stopImmediatePropagation(),!1},ka=function(){g.on({change:ja,blur:d.debug?"":aa,keydown:ha,keyup:ia,focus:d.allowInputToggle?ea:""}),c.is("input")?g.on({focus:ea}):n&&(n.on("click",fa),n.on("mousedown",!1))},la=function(){g.off({change:ja,blur:blur,keydown:ha,keyup:ia,focus:d.allowInputToggle?aa:""}),c.is("input")?g.off({focus:ea}):n&&(n.off("click",fa),n.off("mousedown",!1))},ma=function(b){var c={};return a.each(b,function(){var a=ga(this);a.isValid()&&(c[a.format("YYYY-MM-DD")]=!0)}),Object.keys(c).length?c:!1},na=function(b){var c={};return a.each(b,function(){c[this]=!0}),Object.keys(c).length?c:!1},oa=function(){var a=d.format||"L LT";i=a.replace(/(\[[^\[]*\])|(\\)?(LTS|LT|LL?L?L?|l{1,4})/g,function(a){var b=e.localeData().longDateFormat(a)||a;return b.replace(/(\[[^\[]*\])|(\\)?(LTS|LT|LL?L?L?|l{1,4})/g,function(a){return e.localeData().longDateFormat(a)||a})}),j=d.extraFormats?d.extraFormats.slice():[],j.indexOf(a)<0&&j.indexOf(i)<0&&j.push(i),h=i.toLowerCase().indexOf("a")<1&&i.replace(/\[.*?\]/g,"").indexOf("h")<1,y("y")&&(p=2),y("M")&&(p=1),y("d")&&(p=0),k=Math.max(p,k),m||_(e)};if(l.destroy=function(){aa(),la(),c.removeData("DateTimePicker"),c.removeData("date")},l.toggle=fa,l.show=ea,l.hide=aa,l.disable=function(){return aa(),n&&n.hasClass("btn")&&n.addClass("disabled"),g.prop("disabled",!0),l},l.enable=function(){return n&&n.hasClass("btn")&&n.removeClass("disabled"),g.prop("disabled",!1),l},l.ignoreReadonly=function(a){if(0===arguments.length)return d.ignoreReadonly;if("boolean"!=typeof a)throw new TypeError("ignoreReadonly () expects a boolean parameter");return d.ignoreReadonly=a,l},l.options=function(b){if(0===arguments.length)return a.extend(!0,{},d);if(!(b instanceof Object))throw new TypeError("options() options parameter should be an object");return a.extend(!0,d,b),a.each(d,function(a,b){if(void 0===l[a])throw new TypeError("option "+a+" is not recognized!");l[a](b)}),l},l.date=function(a){if(0===arguments.length)return m?null:e.clone();if(!(null===a||"string"==typeof a||b.isMoment(a)||a instanceof Date))throw new TypeError("date() parameter must be one of [null, string, moment or Date]");return _(null===a?null:ga(a)),l},l.format=function(a){if(0===arguments.length)return d.format;if("string"!=typeof a&&("boolean"!=typeof a||a!==!1))throw new TypeError("format() expects a sting or boolean:false parameter "+a);return d.format=a,i&&oa(),l},l.timeZone=function(a){return 0===arguments.length?d.timeZone:(d.timeZone=a,l)},l.dayViewHeaderFormat=function(a){if(0===arguments.length)return d.dayViewHeaderFormat;if("string"!=typeof a)throw new TypeError("dayViewHeaderFormat() expects a string parameter");return d.dayViewHeaderFormat=a,l},l.extraFormats=function(a){if(0===arguments.length)return d.extraFormats;if(a!==!1&&!(a instanceof Array))throw new TypeError("extraFormats() expects an array or false parameter");return d.extraFormats=a,j&&oa(),l},l.disabledDates=function(b){if(0===arguments.length)return d.disabledDates?a.extend({},d.disabledDates):d.disabledDates;if(!b)return d.disabledDates=!1,$(),l;if(!(b instanceof Array))throw new TypeError("disabledDates() expects an array parameter");return d.disabledDates=ma(b),d.enabledDates=!1,$(),l},l.enabledDates=function(b){if(0===arguments.length)return d.enabledDates?a.extend({},d.enabledDates):d.enabledDates;if(!b)return d.enabledDates=!1,$(),l;if(!(b instanceof Array))throw new TypeError("enabledDates() expects an array parameter");return d.enabledDates=ma(b),d.disabledDates=!1,$(),l},l.daysOfWeekDisabled=function(a){if(0===arguments.length)return d.daysOfWeekDisabled.splice(0);if("boolean"==typeof a&&!a)return d.daysOfWeekDisabled=!1,$(),l;if(!(a instanceof Array))throw new TypeError("daysOfWeekDisabled() expects an array parameter");if(d.daysOfWeekDisabled=a.reduce(function(a,b){return b=parseInt(b,10),b>6||0>b||isNaN(b)?a:(-1===a.indexOf(b)&&a.push(b),a)},[]).sort(),d.useCurrent&&!d.keepInvalid){for(var b=0;!Q(e,"d");){if(e.add(1,"d"),7===b)throw"Tried 7 times to find a valid date";b++}_(e)}return $(),l},l.maxDate=function(a){if(0===arguments.length)return d.maxDate?d.maxDate.clone():d.maxDate;if("boolean"==typeof a&&a===!1)return d.maxDate=!1,$(),l;"string"==typeof a&&("now"===a||"moment"===a)&&(a=x());var b=ga(a);if(!b.isValid())throw new TypeError("maxDate() Could not parse date parameter: "+a);if(d.minDate&&b.isBefore(d.minDate))throw new TypeError("maxDate() date parameter is before options.minDate: "+b.format(i));return d.maxDate=b,d.useCurrent&&!d.keepInvalid&&e.isAfter(a)&&_(d.maxDate),f.isAfter(b)&&(f=b.clone().subtract(d.stepping,"m")),$(),l},l.minDate=function(a){if(0===arguments.length)return d.minDate?d.minDate.clone():d.minDate;if("boolean"==typeof a&&a===!1)return d.minDate=!1,$(),l;"string"==typeof a&&("now"===a||"moment"===a)&&(a=x());var b=ga(a);if(!b.isValid())throw new TypeError("minDate() Could not parse date parameter: "+a);if(d.maxDate&&b.isAfter(d.maxDate))throw new TypeError("minDate() date parameter is after options.maxDate: "+b.format(i));return d.minDate=b,d.useCurrent&&!d.keepInvalid&&e.isBefore(a)&&_(d.minDate),f.isBefore(b)&&(f=b.clone().add(d.stepping,"m")),$(),l},l.defaultDate=function(a){if(0===arguments.length)return d.defaultDate?d.defaultDate.clone():d.defaultDate;if(!a)return d.defaultDate=!1,l;"string"==typeof a&&("now"===a||"moment"===a)&&(a=x());var b=ga(a);if(!b.isValid())throw new TypeError("defaultDate() Could not parse date parameter: "+a);if(!Q(b))throw new TypeError("defaultDate() date passed is invalid according to component setup validations");return d.defaultDate=b,(d.defaultDate&&d.inline||""===g.val().trim())&&_(d.defaultDate),l},l.locale=function(a){if(0===arguments.length)return d.locale;if(!b.localeData(a))throw new TypeError("locale() locale "+a+" is not loaded from moment locales!");return d.locale=a,e.locale(d.locale),f.locale(d.locale),i&&oa(),o&&(aa(),ea()),l},l.stepping=function(a){return 0===arguments.length?d.stepping:(a=parseInt(a,10),(isNaN(a)||1>a)&&(a=1),d.stepping=a,l)},l.useCurrent=function(a){var b=["year","month","day","hour","minute"];if(0===arguments.length)return d.useCurrent;if("boolean"!=typeof a&&"string"!=typeof a)throw new TypeError("useCurrent() expects a boolean or string parameter");if("string"==typeof a&&-1===b.indexOf(a.toLowerCase()))throw new TypeError("useCurrent() expects a string parameter of "+b.join(", "));return d.useCurrent=a,l},l.collapse=function(a){if(0===arguments.length)return d.collapse;if("boolean"!=typeof a)throw new TypeError("collapse() expects a boolean parameter");return d.collapse===a?l:(d.collapse=a,o&&(aa(),ea()),l)},l.icons=function(b){if(0===arguments.length)return a.extend({},d.icons);if(!(b instanceof Object))throw new TypeError("icons() expects parameter to be an Object");return a.extend(d.icons,b),o&&(aa(),ea()),l},l.tooltips=function(b){if(0===arguments.length)return a.extend({},d.tooltips);if(!(b instanceof Object))throw new TypeError("tooltips() expects parameter to be an Object");return a.extend(d.tooltips,b),o&&(aa(),ea()),l},l.useStrict=function(a){if(0===arguments.length)return d.useStrict;if("boolean"!=typeof a)throw new TypeError("useStrict() expects a boolean parameter");return d.useStrict=a,l},l.sideBySide=function(a){if(0===arguments.length)return d.sideBySide;if("boolean"!=typeof a)throw new TypeError("sideBySide() expects a boolean parameter");return d.sideBySide=a,o&&(aa(),ea()),l},l.viewMode=function(a){if(0===arguments.length)return d.viewMode;if("string"!=typeof a)throw new TypeError("viewMode() expects a string parameter");if(-1===r.indexOf(a))throw new TypeError("viewMode() parameter must be one of ("+r.join(", ")+") value");return d.viewMode=a,k=Math.max(r.indexOf(a),p),K(),l},l.toolbarPlacement=function(a){if(0===arguments.length)return d.toolbarPlacement;if("string"!=typeof a)throw new TypeError("toolbarPlacement() expects a string parameter");if(-1===u.indexOf(a))throw new TypeError("toolbarPlacement() parameter must be one of ("+u.join(", ")+") value");return d.toolbarPlacement=a,o&&(aa(),ea()),l},l.widgetPositioning=function(b){if(0===arguments.length)return a.extend({},d.widgetPositioning);if("[object Object]"!=={}.toString.call(b))throw new TypeError("widgetPositioning() expects an object variable");if(b.horizontal){if("string"!=typeof b.horizontal)throw new TypeError("widgetPositioning() horizontal variable must be a string");if(b.horizontal=b.horizontal.toLowerCase(),-1===t.indexOf(b.horizontal))throw new TypeError("widgetPositioning() expects horizontal parameter to be one of ("+t.join(", ")+")");d.widgetPositioning.horizontal=b.horizontal}if(b.vertical){if("string"!=typeof b.vertical)throw new TypeError("widgetPositioning() vertical variable must be a string");if(b.vertical=b.vertical.toLowerCase(),-1===s.indexOf(b.vertical))throw new TypeError("widgetPositioning() expects vertical parameter to be one of ("+s.join(", ")+")");d.widgetPositioning.vertical=b.vertical}return $(),l},l.calendarWeeks=function(a){if(0===arguments.length)return d.calendarWeeks;if("boolean"!=typeof a)throw new TypeError("calendarWeeks() expects parameter to be a boolean value");return d.calendarWeeks=a,$(),l},l.showTodayButton=function(a){if(0===arguments.length)return d.showTodayButton;if("boolean"!=typeof a)throw new TypeError("showTodayButton() expects a boolean parameter");return d.showTodayButton=a,o&&(aa(),ea()),l},l.showClear=function(a){if(0===arguments.length)return d.showClear;if("boolean"!=typeof a)throw new TypeError("showClear() expects a boolean parameter");return d.showClear=a,o&&(aa(),ea()),l},l.widgetParent=function(b){if(0===arguments.length)return d.widgetParent;if("string"==typeof b&&(b=a(b)),null!==b&&"string"!=typeof b&&!(b instanceof a))throw new TypeError("widgetParent() expects a string or a jQuery object parameter");return d.widgetParent=b,o&&(aa(),ea()),l},l.keepOpen=function(a){if(0===arguments.length)return d.keepOpen;if("boolean"!=typeof a)throw new TypeError("keepOpen() expects a boolean parameter");return d.keepOpen=a,l},l.focusOnShow=function(a){if(0===arguments.length)return d.focusOnShow;if("boolean"!=typeof a)throw new TypeError("focusOnShow() expects a boolean parameter");return d.focusOnShow=a,l},l.inline=function(a){if(0===arguments.length)return d.inline;if("boolean"!=typeof a)throw new TypeError("inline() expects a boolean parameter");return d.inline=a,l},l.clear=function(){return ba(),l},l.keyBinds=function(a){return d.keyBinds=a,l},l.getMoment=function(a){return x(a)},l.debug=function(a){if("boolean"!=typeof a)throw new TypeError("debug() expects a boolean parameter");return d.debug=a,l},l.allowInputToggle=function(a){if(0===arguments.length)return d.allowInputToggle;if("boolean"!=typeof a)throw new TypeError("allowInputToggle() expects a boolean parameter");return d.allowInputToggle=a,l},l.showClose=function(a){if(0===arguments.length)return d.showClose;if("boolean"!=typeof a)throw new TypeError("showClose() expects a boolean parameter");return d.showClose=a,l},l.keepInvalid=function(a){if(0===arguments.length)return d.keepInvalid;if("boolean"!=typeof a)throw new TypeError("keepInvalid() expects a boolean parameter");return d.keepInvalid=a,l},l.datepickerInput=function(a){if(0===arguments.length)return d.datepickerInput;if("string"!=typeof a)throw new TypeError("datepickerInput() expects a string parameter");return d.datepickerInput=a,l},l.parseInputDate=function(a){if(0===arguments.length)return d.parseInputDate; +if("function"!=typeof a)throw new TypeError("parseInputDate() sholud be as function");return d.parseInputDate=a,l},l.disabledTimeIntervals=function(b){if(0===arguments.length)return d.disabledTimeIntervals?a.extend({},d.disabledTimeIntervals):d.disabledTimeIntervals;if(!b)return d.disabledTimeIntervals=!1,$(),l;if(!(b instanceof Array))throw new TypeError("disabledTimeIntervals() expects an array parameter");return d.disabledTimeIntervals=b,$(),l},l.disabledHours=function(b){if(0===arguments.length)return d.disabledHours?a.extend({},d.disabledHours):d.disabledHours;if(!b)return d.disabledHours=!1,$(),l;if(!(b instanceof Array))throw new TypeError("disabledHours() expects an array parameter");if(d.disabledHours=na(b),d.enabledHours=!1,d.useCurrent&&!d.keepInvalid){for(var c=0;!Q(e,"h");){if(e.add(1,"h"),24===c)throw"Tried 24 times to find a valid date";c++}_(e)}return $(),l},l.enabledHours=function(b){if(0===arguments.length)return d.enabledHours?a.extend({},d.enabledHours):d.enabledHours;if(!b)return d.enabledHours=!1,$(),l;if(!(b instanceof Array))throw new TypeError("enabledHours() expects an array parameter");if(d.enabledHours=na(b),d.disabledHours=!1,d.useCurrent&&!d.keepInvalid){for(var c=0;!Q(e,"h");){if(e.add(1,"h"),24===c)throw"Tried 24 times to find a valid date";c++}_(e)}return $(),l},l.viewDate=function(a){if(0===arguments.length)return f.clone();if(!a)return f=e.clone(),l;if(!("string"==typeof a||b.isMoment(a)||a instanceof Date))throw new TypeError("viewDate() parameter must be one of [string, moment or Date]");return f=ga(a),J(),l},c.is("input"))g=c;else if(g=c.find(d.datepickerInput),0===g.size())g=c.find("input");else if(!g.is("input"))throw new Error('CSS class "'+d.datepickerInput+'" cannot be applied to non input element');if(c.hasClass("input-group")&&(n=0===c.find(".datepickerbutton").size()?c.find(".input-group-addon"):c.find(".datepickerbutton")),!d.inline&&!g.is("input"))throw new Error("Could not initialize DateTimePicker without an input element");return e=x(),f=e.clone(),a.extend(!0,d,G()),l.options(d),oa(),ka(),g.prop("disabled")&&l.disable(),g.is("input")&&0!==g.val().trim().length?_(ga(g.val().trim())):d.defaultDate&&void 0===g.attr("placeholder")&&_(d.defaultDate),d.inline&&ea(),l};a.fn.datetimepicker=function(b){return this.each(function(){var d=a(this);d.data("DateTimePicker")||(b=a.extend(!0,{},a.fn.datetimepicker.defaults,b),d.data("DateTimePicker",c(d,b)))})},a.fn.datetimepicker.defaults={timeZone:"Etc/UTC",format:!1,dayViewHeaderFormat:"MMMM YYYY",extraFormats:!1,stepping:1,minDate:!1,maxDate:!1,useCurrent:!0,collapse:!0,locale:b.locale(),defaultDate:!1,disabledDates:!1,enabledDates:!1,icons:{time:"glyphicon glyphicon-time",date:"glyphicon glyphicon-calendar",up:"glyphicon glyphicon-chevron-up",down:"glyphicon glyphicon-chevron-down",previous:"glyphicon glyphicon-chevron-left",next:"glyphicon glyphicon-chevron-right",today:"glyphicon glyphicon-screenshot",clear:"glyphicon glyphicon-trash",close:"glyphicon glyphicon-remove"},tooltips:{today:"Go to today",clear:"Clear selection",close:"Close the picker",selectMonth:"Select Month",prevMonth:"Previous Month",nextMonth:"Next Month",selectYear:"Select Year",prevYear:"Previous Year",nextYear:"Next Year",selectDecade:"Select Decade",prevDecade:"Previous Decade",nextDecade:"Next Decade",prevCentury:"Previous Century",nextCentury:"Next Century",pickHour:"Pick Hour",incrementHour:"Increment Hour",decrementHour:"Decrement Hour",pickMinute:"Pick Minute",incrementMinute:"Increment Minute",decrementMinute:"Decrement Minute",pickSecond:"Pick Second",incrementSecond:"Increment Second",decrementSecond:"Decrement Second",togglePeriod:"Toggle Period",selectTime:"Select Time"},useStrict:!1,sideBySide:!1,daysOfWeekDisabled:!1,calendarWeeks:!1,viewMode:"days",toolbarPlacement:"default",showTodayButton:!1,showClear:!1,showClose:!1,widgetPositioning:{horizontal:"auto",vertical:"auto"},widgetParent:null,ignoreReadonly:!1,keepOpen:!1,focusOnShow:!0,inline:!1,keepInvalid:!1,datepickerInput:".datepickerinput",keyBinds:{up:function(a){if(a){var b=this.date()||this.getMoment();a.find(".datepicker").is(":visible")?this.date(b.clone().subtract(7,"d")):this.date(b.clone().add(this.stepping(),"m"))}},down:function(a){if(!a)return void this.show();var b=this.date()||this.getMoment();a.find(".datepicker").is(":visible")?this.date(b.clone().add(7,"d")):this.date(b.clone().subtract(this.stepping(),"m"))},"control up":function(a){if(a){var b=this.date()||this.getMoment();a.find(".datepicker").is(":visible")?this.date(b.clone().subtract(1,"y")):this.date(b.clone().add(1,"h"))}},"control down":function(a){if(a){var b=this.date()||this.getMoment();a.find(".datepicker").is(":visible")?this.date(b.clone().add(1,"y")):this.date(b.clone().subtract(1,"h"))}},left:function(a){if(a){var b=this.date()||this.getMoment();a.find(".datepicker").is(":visible")&&this.date(b.clone().subtract(1,"d"))}},right:function(a){if(a){var b=this.date()||this.getMoment();a.find(".datepicker").is(":visible")&&this.date(b.clone().add(1,"d"))}},pageUp:function(a){if(a){var b=this.date()||this.getMoment();a.find(".datepicker").is(":visible")&&this.date(b.clone().subtract(1,"M"))}},pageDown:function(a){if(a){var b=this.date()||this.getMoment();a.find(".datepicker").is(":visible")&&this.date(b.clone().add(1,"M"))}},enter:function(){this.hide()},escape:function(){this.hide()},"control space":function(a){a.find(".timepicker").is(":visible")&&a.find('.btn[data-action="togglePeriod"]').click()},t:function(){this.date(this.getMoment())},"delete":function(){this.clear()}},debug:!1,allowInputToggle:!1,disabledTimeIntervals:!1,disabledHours:!1,enabledHours:!1,viewDate:!1}}); \ No newline at end of file diff --git a/static/bootstrap3/locales/bootstrap-datepicker.ar.min.js b/static/bootstrap3/locales/bootstrap-datepicker.ar.min.js new file mode 100755 index 0000000..ece41af --- /dev/null +++ b/static/bootstrap3/locales/bootstrap-datepicker.ar.min.js @@ -0,0 +1 @@ +!function(a){a.fn.datepicker.dates.ar={days:["الأحد","الاثنين","الثلاثاء","الأربعاء","الخميس","الجمعة","السبت","الأحد"],daysShort:["أحد","اثنين","ثلاثاء","أربعاء","خميس","جمعة","سبت","أحد"],daysMin:["ح","ن","ث","ع","خ","ج","س","ح"],months:["يناير","فبراير","مارس","أبريل","مايو","يونيو","يوليو","أغسطس","سبتمبر","أكتوبر","نوفمبر","ديسمبر"],monthsShort:["يناير","فبراير","مارس","أبريل","مايو","يونيو","يوليو","أغسطس","سبتمبر","أكتوبر","نوفمبر","ديسمبر"],today:"هذا اليوم",rtl:!0}}(jQuery); \ No newline at end of file diff --git a/static/bootstrap3/locales/bootstrap-datepicker.az.min.js b/static/bootstrap3/locales/bootstrap-datepicker.az.min.js new file mode 100755 index 0000000..56bedf8 --- /dev/null +++ b/static/bootstrap3/locales/bootstrap-datepicker.az.min.js @@ -0,0 +1 @@ +!function(a){a.fn.datepicker.dates.az={days:["Bazar","Bazar ertəsi","Çərşənbə axşamı","Çərşənbə","Cümə axşamı","Cümə","Şənbə"],daysShort:["B.","B.e","Ç.a","Ç.","C.a","C.","Ş."],daysMin:["B.","B.e","Ç.a","Ç.","C.a","C.","Ş."],months:["Yanvar","Fevral","Mart","Aprel","May","İyun","İyul","Avqust","Sentyabr","Oktyabr","Noyabr","Dekabr"],monthsShort:["Yan","Fev","Mar","Apr","May","İyun","İyul","Avq","Sen","Okt","Noy","Dek"],today:"Bu gün",weekStart:1}}(jQuery); \ No newline at end of file diff --git a/static/bootstrap3/locales/bootstrap-datepicker.bg.min.js b/static/bootstrap3/locales/bootstrap-datepicker.bg.min.js new file mode 100755 index 0000000..28e8b22 --- /dev/null +++ b/static/bootstrap3/locales/bootstrap-datepicker.bg.min.js @@ -0,0 +1 @@ +!function(a){a.fn.datepicker.dates.bg={days:["Неделя","Понеделник","Вторник","Сряда","Четвъртък","Петък","Събота"],daysShort:["Нед","Пон","Вто","Сря","Чет","Пет","Съб"],daysMin:["Н","П","В","С","Ч","П","С"],months:["Януари","Февруари","Март","Април","Май","Юни","Юли","Август","Септември","Октомври","Ноември","Декември"],monthsShort:["Ян","Фев","Мар","Апр","Май","Юни","Юли","Авг","Сеп","Окт","Ное","Дек"],today:"днес"}}(jQuery); \ No newline at end of file diff --git a/static/bootstrap3/locales/bootstrap-datepicker.bs.min.js b/static/bootstrap3/locales/bootstrap-datepicker.bs.min.js new file mode 100755 index 0000000..cfb06fd --- /dev/null +++ b/static/bootstrap3/locales/bootstrap-datepicker.bs.min.js @@ -0,0 +1 @@ +!function(a){a.fn.datepicker.dates.bs={days:["Nedjelja","Ponedjeljak","Utorak","Srijeda","Četvrtak","Petak","Subota"],daysShort:["Ned","Pon","Uto","Sri","Čet","Pet","Sub"],daysMin:["N","Po","U","Sr","Č","Pe","Su"],months:["Januar","Februar","Mart","April","Maj","Juni","Juli","August","Septembar","Oktobar","Novembar","Decembar"],monthsShort:["Jan","Feb","Mar","Apr","Maj","Jun","Jul","Aug","Sep","Okt","Nov","Dec"],today:"Danas",weekStart:1,format:"dd.mm.yyyy"}}(jQuery); \ No newline at end of file diff --git a/static/bootstrap3/locales/bootstrap-datepicker.ca.min.js b/static/bootstrap3/locales/bootstrap-datepicker.ca.min.js new file mode 100755 index 0000000..c555561 --- /dev/null +++ b/static/bootstrap3/locales/bootstrap-datepicker.ca.min.js @@ -0,0 +1 @@ +!function(a){a.fn.datepicker.dates.ca={days:["Diumenge","Dilluns","Dimarts","Dimecres","Dijous","Divendres","Dissabte"],daysShort:["Diu","Dil","Dmt","Dmc","Dij","Div","Dis"],daysMin:["dg","dl","dt","dc","dj","dv","ds"],months:["Gener","Febrer","Març","Abril","Maig","Juny","Juliol","Agost","Setembre","Octubre","Novembre","Desembre"],monthsShort:["Gen","Feb","Mar","Abr","Mai","Jun","Jul","Ago","Set","Oct","Nov","Des"],today:"Avui",clear:"Esborrar",weekStart:1,format:"dd/mm/yyyy"}}(jQuery); \ No newline at end of file diff --git a/static/bootstrap3/locales/bootstrap-datepicker.cs.min.js b/static/bootstrap3/locales/bootstrap-datepicker.cs.min.js new file mode 100755 index 0000000..8b711e1 --- /dev/null +++ b/static/bootstrap3/locales/bootstrap-datepicker.cs.min.js @@ -0,0 +1 @@ +!function(a){a.fn.datepicker.dates.cs={days:["Neděle","Pondělí","Úterý","Středa","Čtvrtek","Pátek","Sobota"],daysShort:["Ned","Pon","Úte","Stř","Čtv","Pát","Sob"],daysMin:["Ne","Po","Út","St","Čt","Pá","So"],months:["Leden","Únor","Březen","Duben","Květen","Červen","Červenec","Srpen","Září","Říjen","Listopad","Prosinec"],monthsShort:["Led","Úno","Bře","Dub","Kvě","Čer","Čnc","Srp","Zář","Říj","Lis","Pro"],today:"Dnes",clear:"Vymazat",weekStart:1,format:"dd.m.yyyy"}}(jQuery); \ No newline at end of file diff --git a/static/bootstrap3/locales/bootstrap-datepicker.cy.min.js b/static/bootstrap3/locales/bootstrap-datepicker.cy.min.js new file mode 100755 index 0000000..f85ea03 --- /dev/null +++ b/static/bootstrap3/locales/bootstrap-datepicker.cy.min.js @@ -0,0 +1 @@ +!function(a){a.fn.datepicker.dates.cy={days:["Sul","Llun","Mawrth","Mercher","Iau","Gwener","Sadwrn"],daysShort:["Sul","Llu","Maw","Mer","Iau","Gwe","Sad"],daysMin:["Su","Ll","Ma","Me","Ia","Gwe","Sa"],months:["Ionawr","Chewfror","Mawrth","Ebrill","Mai","Mehefin","Gorfennaf","Awst","Medi","Hydref","Tachwedd","Rhagfyr"],monthsShort:["Ion","Chw","Maw","Ebr","Mai","Meh","Gor","Aws","Med","Hyd","Tach","Rha"],today:"Heddiw"}}(jQuery); \ No newline at end of file diff --git a/static/bootstrap3/locales/bootstrap-datepicker.da.min.js b/static/bootstrap3/locales/bootstrap-datepicker.da.min.js new file mode 100755 index 0000000..53935bc --- /dev/null +++ b/static/bootstrap3/locales/bootstrap-datepicker.da.min.js @@ -0,0 +1 @@ +!function(a){a.fn.datepicker.dates.da={days:["søndag","mandag","tirsdag","onsdag","torsdag","fredag","lørdag"],daysShort:["søn","man","tir","ons","tor","fre","lør"],daysMin:["sø","ma","ti","on","to","fr","lø"],months:["januar","februar","marts","april","maj","juni","juli","august","september","oktober","november","december"],monthsShort:["jan","feb","mar","apr","maj","jun","jul","aug","sep","okt","nov","dec"],today:"I Dag",clear:"Nulstil"}}(jQuery); \ No newline at end of file diff --git a/static/bootstrap3/locales/bootstrap-datepicker.de.min.js b/static/bootstrap3/locales/bootstrap-datepicker.de.min.js new file mode 100755 index 0000000..c63bcf0 --- /dev/null +++ b/static/bootstrap3/locales/bootstrap-datepicker.de.min.js @@ -0,0 +1 @@ +!function(a){a.fn.datepicker.dates.de={days:["Sonntag","Montag","Dienstag","Mittwoch","Donnerstag","Freitag","Samstag"],daysShort:["Son","Mon","Die","Mit","Don","Fre","Sam"],daysMin:["So","Mo","Di","Mi","Do","Fr","Sa"],months:["Januar","Februar","März","April","Mai","Juni","Juli","August","September","Oktober","November","Dezember"],monthsShort:["Jan","Feb","Mär","Apr","Mai","Jun","Jul","Aug","Sep","Okt","Nov","Dez"],today:"Heute",clear:"Löschen",weekStart:1,format:"dd.mm.yyyy"}}(jQuery); \ No newline at end of file diff --git a/static/bootstrap3/locales/bootstrap-datepicker.el.min.js b/static/bootstrap3/locales/bootstrap-datepicker.el.min.js new file mode 100755 index 0000000..046e9eb --- /dev/null +++ b/static/bootstrap3/locales/bootstrap-datepicker.el.min.js @@ -0,0 +1 @@ +!function(a){a.fn.datepicker.dates.el={days:["Κυριακή","Δευτέρα","Τρίτη","Τετάρτη","Πέμπτη","Παρασκευή","Σάββατο"],daysShort:["Κυρ","Δευ","Τρι","Τετ","Πεμ","Παρ","Σαβ"],daysMin:["Κυ","Δε","Τρ","Τε","Πε","Πα","Σα"],months:["Ιανουάριος","Φεβρουάριος","Μάρτιος","Απρίλιος","Μάιος","Ιούνιος","Ιούλιος","Αύγουστος","Σεπτέμβριος","Οκτώβριος","Νοέμβριος","Δεκέμβριος"],monthsShort:["Ιαν","Φεβ","Μαρ","Απρ","Μάι","Ιουν","Ιουλ","Αυγ","Σεπ","Οκτ","Νοε","Δεκ"],today:"Σήμερα",clear:"Καθαρισμός",weekStart:1,format:"d/m/yyyy"}}(jQuery); \ No newline at end of file diff --git a/static/bootstrap3/locales/bootstrap-datepicker.en-GB.min.js b/static/bootstrap3/locales/bootstrap-datepicker.en-GB.min.js new file mode 100755 index 0000000..bc45e5a --- /dev/null +++ b/static/bootstrap3/locales/bootstrap-datepicker.en-GB.min.js @@ -0,0 +1 @@ +!function(a){a.fn.datepicker.dates["en-GB"]={days:["Sunday","Monday","Tuesday","Wednesday","Thursday","Friday","Saturday"],daysShort:["Sun","Mon","Tue","Wed","Thu","Fri","Sat"],daysMin:["Su","Mo","Tu","We","Th","Fr","Sa"],months:["January","February","March","April","May","June","July","August","September","October","November","December"],monthsShort:["Jan","Feb","Mar","Apr","May","Jun","Jul","Aug","Sep","Oct","Nov","Dec"],today:"Today",clear:"Clear",weekStart:1,format:"dd/mm/yyyy"}}(jQuery); \ No newline at end of file diff --git a/static/bootstrap3/locales/bootstrap-datepicker.eo.min.js b/static/bootstrap3/locales/bootstrap-datepicker.eo.min.js new file mode 100755 index 0000000..736db02 --- /dev/null +++ b/static/bootstrap3/locales/bootstrap-datepicker.eo.min.js @@ -0,0 +1 @@ +!function(a){a.fn.datepicker.dates.eo={days:["dimanĉo","lundo","mardo","merkredo","ĵaŭdo","vendredo","sabato"],daysShort:["dim.","lun.","mar.","mer.","ĵaŭ.","ven.","sam."],daysMin:["d","l","ma","me","ĵ","v","s"],months:["januaro","februaro","marto","aprilo","majo","junio","julio","aŭgusto","septembro","oktobro","novembro","decembro"],monthsShort:["jan.","feb.","mar.","apr.","majo","jun.","jul.","aŭg.","sep.","okt.","nov.","dec."],today:"Hodiaŭ",clear:"Nuligi",weekStart:1,format:"yyyy-mm-dd"}}(jQuery); \ No newline at end of file diff --git a/static/bootstrap3/locales/bootstrap-datepicker.es.min.js b/static/bootstrap3/locales/bootstrap-datepicker.es.min.js new file mode 100755 index 0000000..018ac48 --- /dev/null +++ b/static/bootstrap3/locales/bootstrap-datepicker.es.min.js @@ -0,0 +1 @@ +!function(a){a.fn.datepicker.dates.es={days:["Domingo","Lunes","Martes","Miércoles","Jueves","Viernes","Sábado"],daysShort:["Dom","Lun","Mar","Mié","Jue","Vie","Sáb"],daysMin:["Do","Lu","Ma","Mi","Ju","Vi","Sa"],months:["Enero","Febrero","Marzo","Abril","Mayo","Junio","Julio","Agosto","Septiembre","Octubre","Noviembre","Diciembre"],monthsShort:["Ene","Feb","Mar","Abr","May","Jun","Jul","Ago","Sep","Oct","Nov","Dic"],today:"Hoy",clear:"Borrar",weekStart:1,format:"dd/mm/yyyy"}}(jQuery); \ No newline at end of file diff --git a/static/bootstrap3/locales/bootstrap-datepicker.et.min.js b/static/bootstrap3/locales/bootstrap-datepicker.et.min.js new file mode 100755 index 0000000..34cd9c6 --- /dev/null +++ b/static/bootstrap3/locales/bootstrap-datepicker.et.min.js @@ -0,0 +1 @@ +!function(a){a.fn.datepicker.dates.et={days:["Pühapäev","Esmaspäev","Teisipäev","Kolmapäev","Neljapäev","Reede","Laupäev"],daysShort:["Pühap","Esmasp","Teisip","Kolmap","Neljap","Reede","Laup"],daysMin:["P","E","T","K","N","R","L"],months:["Jaanuar","Veebruar","Märts","Aprill","Mai","Juuni","Juuli","August","September","Oktoober","November","Detsember"],monthsShort:["Jaan","Veebr","Märts","Apr","Mai","Juuni","Juuli","Aug","Sept","Okt","Nov","Dets"],today:"Täna",clear:"Tühjenda",weekStart:1,format:"dd.mm.yyyy"}}(jQuery); \ No newline at end of file diff --git a/static/bootstrap3/locales/bootstrap-datepicker.eu.min.js b/static/bootstrap3/locales/bootstrap-datepicker.eu.min.js new file mode 100755 index 0000000..af27854 --- /dev/null +++ b/static/bootstrap3/locales/bootstrap-datepicker.eu.min.js @@ -0,0 +1 @@ +!function(a){a.fn.datepicker.dates.eu={days:["Igandea","Astelehena","Asteartea","Asteazkena","Osteguna","Ostirala","Larunbata"],daysShort:["Ig","Al","Ar","Az","Og","Ol","Lr"],daysMin:["Ig","Al","Ar","Az","Og","Ol","Lr"],months:["Urtarrila","Otsaila","Martxoa","Apirila","Maiatza","Ekaina","Uztaila","Abuztua","Iraila","Urria","Azaroa","Abendua"],monthsShort:["Urt","Ots","Mar","Api","Mai","Eka","Uzt","Abu","Ira","Urr","Aza","Abe"],today:"Gaur"}}(jQuery); \ No newline at end of file diff --git a/static/bootstrap3/locales/bootstrap-datepicker.fa.min.js b/static/bootstrap3/locales/bootstrap-datepicker.fa.min.js new file mode 100755 index 0000000..8575237 --- /dev/null +++ b/static/bootstrap3/locales/bootstrap-datepicker.fa.min.js @@ -0,0 +1 @@ +!function(a){a.fn.datepicker.dates.fa={days:["یک‌شنبه","دوشنبه","سه‌شنبه","چهارشنبه","پنج‌شنبه","جمعه","شنبه","یک‌شنبه"],daysShort:["یک","دو","سه","چهار","پنج","جمعه","شنبه","یک"],daysMin:["ی","د","س","چ","پ","ج","ش","ی"],months:["ژانویه","فوریه","مارس","آوریل","مه","ژوئن","ژوئیه","اوت","سپتامبر","اکتبر","نوامبر","دسامبر"],monthsShort:["ژان","فور","مار","آور","مه","ژون","ژوی","اوت","سپت","اکت","نوا","دسا"],today:"امروز",clear:"پاک کن",weekStart:1,format:"yyyy/mm/dd"}}(jQuery); \ No newline at end of file diff --git a/static/bootstrap3/locales/bootstrap-datepicker.fi.min.js b/static/bootstrap3/locales/bootstrap-datepicker.fi.min.js new file mode 100755 index 0000000..239dfb7 --- /dev/null +++ b/static/bootstrap3/locales/bootstrap-datepicker.fi.min.js @@ -0,0 +1 @@ +!function(a){a.fn.datepicker.dates.fi={days:["sunnuntai","maanantai","tiistai","keskiviikko","torstai","perjantai","lauantai"],daysShort:["sun","maa","tii","kes","tor","per","lau"],daysMin:["su","ma","ti","ke","to","pe","la"],months:["tammikuu","helmikuu","maaliskuu","huhtikuu","toukokuu","kesäkuu","heinäkuu","elokuu","syyskuu","lokakuu","marraskuu","joulukuu"],monthsShort:["tam","hel","maa","huh","tou","kes","hei","elo","syy","lok","mar","jou"],today:"tänään",clear:"Tyhjennä",weekStart:1,format:"d.m.yyyy"}}(jQuery); \ No newline at end of file diff --git a/static/bootstrap3/locales/bootstrap-datepicker.fo.min.js b/static/bootstrap3/locales/bootstrap-datepicker.fo.min.js new file mode 100755 index 0000000..fa24e3a --- /dev/null +++ b/static/bootstrap3/locales/bootstrap-datepicker.fo.min.js @@ -0,0 +1 @@ +!function(a){a.fn.datepicker.dates.fo={days:["Sunnudagur","Mánadagur","Týsdagur","Mikudagur","Hósdagur","Fríggjadagur","Leygardagur"],daysShort:["Sun","Mán","Týs","Mik","Hós","Frí","Ley"],daysMin:["Su","Má","Tý","Mi","Hó","Fr","Le"],months:["Januar","Februar","Marts","Apríl","Mei","Juni","Juli","August","Septembur","Oktobur","Novembur","Desembur"],monthsShort:["Jan","Feb","Mar","Apr","Mei","Jun","Jul","Aug","Sep","Okt","Nov","Des"],today:"Í Dag",clear:"Reinsa"}}(jQuery); \ No newline at end of file diff --git a/static/bootstrap3/locales/bootstrap-datepicker.fr-CH.min.js b/static/bootstrap3/locales/bootstrap-datepicker.fr-CH.min.js new file mode 100755 index 0000000..56e821c --- /dev/null +++ b/static/bootstrap3/locales/bootstrap-datepicker.fr-CH.min.js @@ -0,0 +1 @@ +!function(a){a.fn.datepicker.dates.fr={days:["Dimanche","Lundi","Mardi","Mercredi","Jeudi","Vendredi","Samedi"],daysShort:["Dim","Lun","Mar","Mer","Jeu","Ven","Sam"],daysMin:["D","L","Ma","Me","J","V","S"],months:["Janvier","Février","Mars","Avril","Mai","Juin","Juillet","Août","Septembre","Octobre","Novembre","Décembre"],monthsShort:["Jan","Fév","Mar","Avr","Mai","Jui","Jul","Aou","Sep","Oct","Nov","Déc"],today:"Aujourd'hui",clear:"Effacer",weekStart:1,format:"dd.mm.yyyy"}}(jQuery); \ No newline at end of file diff --git a/static/bootstrap3/locales/bootstrap-datepicker.fr.min.js b/static/bootstrap3/locales/bootstrap-datepicker.fr.min.js new file mode 100755 index 0000000..9edaf62 --- /dev/null +++ b/static/bootstrap3/locales/bootstrap-datepicker.fr.min.js @@ -0,0 +1 @@ +!function(a){a.fn.datepicker.dates.fr={days:["dimanche","lundi","mardi","mercredi","jeudi","vendredi","samedi"],daysShort:["dim.","lun.","mar.","mer.","jeu.","ven.","sam."],daysMin:["d","l","ma","me","j","v","s"],months:["janvier","février","mars","avril","mai","juin","juillet","août","septembre","octobre","novembre","décembre"],monthsShort:["janv.","févr.","mars","avril","mai","juin","juil.","août","sept.","oct.","nov.","déc."],today:"Aujourd'hui",clear:"Effacer",weekStart:1,format:"dd/mm/yyyy"}}(jQuery); \ No newline at end of file diff --git a/static/bootstrap3/locales/bootstrap-datepicker.gl.min.js b/static/bootstrap3/locales/bootstrap-datepicker.gl.min.js new file mode 100755 index 0000000..3d92606 --- /dev/null +++ b/static/bootstrap3/locales/bootstrap-datepicker.gl.min.js @@ -0,0 +1 @@ +!function(a){a.fn.datepicker.dates.gl={days:["Domingo","Luns","Martes","Mércores","Xoves","Venres","Sábado"],daysShort:["Dom","Lun","Mar","Mér","Xov","Ven","Sáb"],daysMin:["Do","Lu","Ma","Me","Xo","Ve","Sa"],months:["Xaneiro","Febreiro","Marzo","Abril","Maio","Xuño","Xullo","Agosto","Setembro","Outubro","Novembro","Decembro"],monthsShort:["Xan","Feb","Mar","Abr","Mai","Xun","Xul","Ago","Sep","Out","Nov","Dec"],today:"Hoxe",clear:"Limpar",weekStart:1,format:"dd/mm/yyyy"}}(jQuery); \ No newline at end of file diff --git a/static/bootstrap3/locales/bootstrap-datepicker.he.min.js b/static/bootstrap3/locales/bootstrap-datepicker.he.min.js new file mode 100755 index 0000000..191cb45 --- /dev/null +++ b/static/bootstrap3/locales/bootstrap-datepicker.he.min.js @@ -0,0 +1 @@ +!function(a){a.fn.datepicker.dates.he={days:["ראשון","שני","שלישי","רביעי","חמישי","שישי","שבת","ראשון"],daysShort:["א","ב","ג","ד","ה","ו","ש","א"],daysMin:["א","ב","ג","ד","ה","ו","ש","א"],months:["ינואר","פברואר","מרץ","אפריל","מאי","יוני","יולי","אוגוסט","ספטמבר","אוקטובר","נובמבר","דצמבר"],monthsShort:["ינו","פבר","מרץ","אפר","מאי","יונ","יול","אוג","ספט","אוק","נוב","דצמ"],today:"היום",rtl:!0}}(jQuery); \ No newline at end of file diff --git a/static/bootstrap3/locales/bootstrap-datepicker.hr.min.js b/static/bootstrap3/locales/bootstrap-datepicker.hr.min.js new file mode 100755 index 0000000..8b34bce --- /dev/null +++ b/static/bootstrap3/locales/bootstrap-datepicker.hr.min.js @@ -0,0 +1 @@ +!function(a){a.fn.datepicker.dates.hr={days:["Nedjelja","Ponedjeljak","Utorak","Srijeda","Četvrtak","Petak","Subota"],daysShort:["Ned","Pon","Uto","Sri","Čet","Pet","Sub"],daysMin:["Ne","Po","Ut","Sr","Če","Pe","Su"],months:["Siječanj","Veljača","Ožujak","Travanj","Svibanj","Lipanj","Srpanj","Kolovoz","Rujan","Listopad","Studeni","Prosinac"],monthsShort:["Sij","Velj","Ožu","Tra","Svi","Lip","Srp","Kol","Ruj","Lis","Stu","Pro"],today:"Danas"}}(jQuery); \ No newline at end of file diff --git a/static/bootstrap3/locales/bootstrap-datepicker.hu.min.js b/static/bootstrap3/locales/bootstrap-datepicker.hu.min.js new file mode 100755 index 0000000..f9decf9 --- /dev/null +++ b/static/bootstrap3/locales/bootstrap-datepicker.hu.min.js @@ -0,0 +1 @@ +!function(a){a.fn.datepicker.dates.hu={days:["vasárnap","hétfő","kedd","szerda","csütörtök","péntek","szombat"],daysShort:["vas","hét","ked","sze","csü","pén","szo"],daysMin:["V","H","K","Sze","Cs","P","Szo"],months:["január","február","március","április","május","június","július","augusztus","szeptember","október","november","december"],monthsShort:["jan","feb","már","ápr","máj","jún","júl","aug","sze","okt","nov","dec"],today:"ma",weekStart:1,clear:"töröl",titleFormat:"yyyy. MM",format:"yyyy.mm.dd"}}(jQuery); \ No newline at end of file diff --git a/static/bootstrap3/locales/bootstrap-datepicker.hy.min.js b/static/bootstrap3/locales/bootstrap-datepicker.hy.min.js new file mode 100755 index 0000000..9fd894a --- /dev/null +++ b/static/bootstrap3/locales/bootstrap-datepicker.hy.min.js @@ -0,0 +1 @@ +!function(a){a.fn.datepicker.dates.hy={days:["Կիրակի","Երկուշաբթի","Երեքշաբթի","Չորեքշաբթի","Հինգշաբթի","Ուրբաթ","Շաբաթ"],daysShort:["Կիր","Երկ","Երք","Չոր","Հնգ","Ուր","Շաբ"],daysMin:["Կի","Եկ","Եք","Չո","Հի","Ու","Շա"],months:["Հունվար","Փետրվար","Մարտ","Ապրիլ","Մայիս","Հունիս","Հուլիս","Օգոստոս","Սեպտեմբեր","Հոկտեմբեր","Նոյեմբեր","Դեկտեմբեր"],monthsShort:["Հնվ","Փետ","Մար","Ապր","Մայ","Հուն","Հուլ","Օգս","Սեպ","Հոկ","Նոյ","Դեկ"],today:"Այսօր",clear:"Ջնջել",format:"dd.mm.yyyy",weekStart:1}}(jQuery); \ No newline at end of file diff --git a/static/bootstrap3/locales/bootstrap-datepicker.id.min.js b/static/bootstrap3/locales/bootstrap-datepicker.id.min.js new file mode 100755 index 0000000..7c3220a --- /dev/null +++ b/static/bootstrap3/locales/bootstrap-datepicker.id.min.js @@ -0,0 +1 @@ +!function(a){a.fn.datepicker.dates.id={days:["Minggu","Senin","Selasa","Rabu","Kamis","Jumat","Sabtu"],daysShort:["Mgu","Sen","Sel","Rab","Kam","Jum","Sab"],daysMin:["Mg","Sn","Sl","Ra","Ka","Ju","Sa"],months:["Januari","Februari","Maret","April","Mei","Juni","Juli","Agustus","September","Oktober","November","Desember"],monthsShort:["Jan","Feb","Mar","Apr","Mei","Jun","Jul","Ags","Sep","Okt","Nov","Des"],today:"Hari Ini",clear:"Kosongkan"}}(jQuery); \ No newline at end of file diff --git a/static/bootstrap3/locales/bootstrap-datepicker.is.min.js b/static/bootstrap3/locales/bootstrap-datepicker.is.min.js new file mode 100755 index 0000000..f49bd18 --- /dev/null +++ b/static/bootstrap3/locales/bootstrap-datepicker.is.min.js @@ -0,0 +1 @@ +!function(a){a.fn.datepicker.dates.is={days:["Sunnudagur","Mánudagur","Þriðjudagur","Miðvikudagur","Fimmtudagur","Föstudagur","Laugardagur"],daysShort:["Sun","Mán","Þri","Mið","Fim","Fös","Lau"],daysMin:["Su","Má","Þr","Mi","Fi","Fö","La"],months:["Janúar","Febrúar","Mars","Apríl","Maí","Júní","Júlí","Ágúst","September","Október","Nóvember","Desember"],monthsShort:["Jan","Feb","Mar","Apr","Maí","Jún","Júl","Ágú","Sep","Okt","Nóv","Des"],today:"Í Dag"}}(jQuery); \ No newline at end of file diff --git a/static/bootstrap3/locales/bootstrap-datepicker.it-CH.min.js b/static/bootstrap3/locales/bootstrap-datepicker.it-CH.min.js new file mode 100755 index 0000000..7e1adbb --- /dev/null +++ b/static/bootstrap3/locales/bootstrap-datepicker.it-CH.min.js @@ -0,0 +1 @@ +!function(a){a.fn.datepicker.dates.it={days:["Domenica","Lunedì","Martedì","Mercoledì","Giovedì","Venerdì","Sabato"],daysShort:["Dom","Lun","Mar","Mer","Gio","Ven","Sab"],daysMin:["Do","Lu","Ma","Me","Gi","Ve","Sa"],months:["Gennaio","Febbraio","Marzo","Aprile","Maggio","Giugno","Luglio","Agosto","Settembre","Ottobre","Novembre","Dicembre"],monthsShort:["Gen","Feb","Mar","Apr","Mag","Giu","Lug","Ago","Set","Ott","Nov","Dic"],today:"Oggi",clear:"Cancella",weekStart:1,format:"dd.mm.yyyy"}}(jQuery); \ No newline at end of file diff --git a/static/bootstrap3/locales/bootstrap-datepicker.it.min.js b/static/bootstrap3/locales/bootstrap-datepicker.it.min.js new file mode 100755 index 0000000..f95ee7e --- /dev/null +++ b/static/bootstrap3/locales/bootstrap-datepicker.it.min.js @@ -0,0 +1 @@ +!function(a){a.fn.datepicker.dates.it={days:["Domenica","Lunedì","Martedì","Mercoledì","Giovedì","Venerdì","Sabato"],daysShort:["Dom","Lun","Mar","Mer","Gio","Ven","Sab"],daysMin:["Do","Lu","Ma","Me","Gi","Ve","Sa"],months:["Gennaio","Febbraio","Marzo","Aprile","Maggio","Giugno","Luglio","Agosto","Settembre","Ottobre","Novembre","Dicembre"],monthsShort:["Gen","Feb","Mar","Apr","Mag","Giu","Lug","Ago","Set","Ott","Nov","Dic"],today:"Oggi",clear:"Cancella",weekStart:1,format:"dd/mm/yyyy"}}(jQuery); \ No newline at end of file diff --git a/static/bootstrap3/locales/bootstrap-datepicker.ja.min.js b/static/bootstrap3/locales/bootstrap-datepicker.ja.min.js new file mode 100755 index 0000000..e321f04 --- /dev/null +++ b/static/bootstrap3/locales/bootstrap-datepicker.ja.min.js @@ -0,0 +1 @@ +!function(a){a.fn.datepicker.dates.ja={days:["日曜","月曜","火曜","水曜","木曜","金曜","土曜"],daysShort:["日","月","火","水","木","金","土"],daysMin:["日","月","火","水","木","金","土"],months:["1月","2月","3月","4月","5月","6月","7月","8月","9月","10月","11月","12月"],monthsShort:["1月","2月","3月","4月","5月","6月","7月","8月","9月","10月","11月","12月"],today:"今日",format:"yyyy/mm/dd",titleFormat:"yyyy年mm月",clear:"クリア"}}(jQuery); \ No newline at end of file diff --git a/static/bootstrap3/locales/bootstrap-datepicker.ka.min.js b/static/bootstrap3/locales/bootstrap-datepicker.ka.min.js new file mode 100755 index 0000000..db247aa --- /dev/null +++ b/static/bootstrap3/locales/bootstrap-datepicker.ka.min.js @@ -0,0 +1 @@ +!function(a){a.fn.datepicker.dates.ka={days:["კვირა","ორშაბათი","სამშაბათი","ოთხშაბათი","ხუთშაბათი","პარასკევი","შაბათი"],daysShort:["კვი","ორშ","სამ","ოთხ","ხუთ","პარ","შაბ"],daysMin:["კვ","ორ","სა","ოთ","ხუ","პა","შა"],months:["იანვარი","თებერვალი","მარტი","აპრილი","მაისი","ივნისი","ივლისი","აგვისტო","სექტემბერი","ოქტომები","ნოემბერი","დეკემბერი"],monthsShort:["იან","თებ","მარ","აპრ","მაი","ივნ","ივლ","აგვ","სექ","ოქტ","ნოე","დეკ"],today:"დღეს",clear:"გასუფთავება",weekStart:1,format:"dd.mm.yyyy"}}(jQuery); \ No newline at end of file diff --git a/static/bootstrap3/locales/bootstrap-datepicker.kh.min.js b/static/bootstrap3/locales/bootstrap-datepicker.kh.min.js new file mode 100755 index 0000000..cc41e1a --- /dev/null +++ b/static/bootstrap3/locales/bootstrap-datepicker.kh.min.js @@ -0,0 +1 @@ +!function(a){a.fn.datepicker.dates.kh={days:["អាទិត្យ","ចន្ទ","អង្គារ","ពុធ","ព្រហស្បតិ៍","សុក្រ","សៅរ៍","អាទិត្យ"],daysShort:["អា.ទិ","ចន្ទ","អង្គារ","ពុធ","ព្រ.ហ","សុក្រ","សៅរ៍","អា.ទិ"],daysMin:["អា.ទិ","ចន្ទ","អង្គារ","ពុធ","ព្រ.ហ","សុក្រ","សៅរ៍","អា.ទិ"],months:["មករា","កុម្ភះ","មិនា","មេសា","ឧសភា","មិថុនា","កក្កដា","សីហា","កញ្ញា","តុលា","វិច្ឆិកា","ធ្នូ"],monthsShort:["មករា","កុម្ភះ","មិនា","មេសា","ឧសភា","មិថុនា","កក្កដា","សីហា","កញ្ញា","តុលា","វិច្ឆិកា","ធ្នូ"],today:"ថ្ងៃនេះ",clear:"សំអាត"}}(jQuery); \ No newline at end of file diff --git a/static/bootstrap3/locales/bootstrap-datepicker.kk.min.js b/static/bootstrap3/locales/bootstrap-datepicker.kk.min.js new file mode 100755 index 0000000..0b1c123 --- /dev/null +++ b/static/bootstrap3/locales/bootstrap-datepicker.kk.min.js @@ -0,0 +1 @@ +!function(a){a.fn.datepicker.dates.kk={days:["Жексенбі","Дүйсенбі","Сейсенбі","Сәрсенбі","Бейсенбі","Жұма","Сенбі"],daysShort:["Жек","Дүй","Сей","Сәр","Бей","Жұм","Сен"],daysMin:["Жк","Дс","Сс","Ср","Бс","Жм","Сн"],months:["Қаңтар","Ақпан","Наурыз","Сәуір","Мамыр","Маусым","Шілде","Тамыз","Қыркүйек","Қазан","Қараша","Желтоқсан"],monthsShort:["Қаң","Ақп","Нау","Сәу","Мамыр","Мау","Шлд","Тмз","Қыр","Қзн","Қар","Жел"],today:"Бүгін",weekStart:1}}(jQuery); \ No newline at end of file diff --git a/static/bootstrap3/locales/bootstrap-datepicker.ko.min.js b/static/bootstrap3/locales/bootstrap-datepicker.ko.min.js new file mode 100755 index 0000000..af739fa --- /dev/null +++ b/static/bootstrap3/locales/bootstrap-datepicker.ko.min.js @@ -0,0 +1 @@ +!function(a){a.fn.datepicker.dates.ko={days:["일요일","월요일","화요일","수요일","목요일","금요일","토요일"],daysShort:["일","월","화","수","목","금","토"],daysMin:["일","월","화","수","목","금","토"],months:["1월","2월","3월","4월","5월","6월","7월","8월","9월","10월","11월","12월"],monthsShort:["1월","2월","3월","4월","5월","6월","7월","8월","9월","10월","11월","12월"],today:"오늘",clear:"투명",format:"YYYY-MM-DD",titleFormat:"yyyy년mm월",weekStart:1}}(jQuery); \ No newline at end of file diff --git a/static/bootstrap3/locales/bootstrap-datepicker.kr.min.js b/static/bootstrap3/locales/bootstrap-datepicker.kr.min.js new file mode 100755 index 0000000..fbc3ddf --- /dev/null +++ b/static/bootstrap3/locales/bootstrap-datepicker.kr.min.js @@ -0,0 +1 @@ +!function(a){a.fn.datepicker.dates.kr={days:["일요일","월요일","화요일","수요일","목요일","금요일","토요일"],daysShort:["일","월","화","수","목","금","토"],daysMin:["일","월","화","수","목","금","토"],months:["1월","2월","3월","4월","5월","6월","7월","8월","9월","10월","11월","12월"],monthsShort:["1월","2월","3월","4월","5월","6월","7월","8월","9월","10월","11월","12월"]}}(jQuery); \ No newline at end of file diff --git a/static/bootstrap3/locales/bootstrap-datepicker.lt.min.js b/static/bootstrap3/locales/bootstrap-datepicker.lt.min.js new file mode 100755 index 0000000..b4c6216 --- /dev/null +++ b/static/bootstrap3/locales/bootstrap-datepicker.lt.min.js @@ -0,0 +1 @@ +!function(a){a.fn.datepicker.dates.lt={days:["Sekmadienis","Pirmadienis","Antradienis","Trečiadienis","Ketvirtadienis","Penktadienis","Šeštadienis"],daysShort:["S","Pr","A","T","K","Pn","Š"],daysMin:["Sk","Pr","An","Tr","Ke","Pn","Št"],months:["Sausis","Vasaris","Kovas","Balandis","Gegužė","Birželis","Liepa","Rugpjūtis","Rugsėjis","Spalis","Lapkritis","Gruodis"],monthsShort:["Sau","Vas","Kov","Bal","Geg","Bir","Lie","Rugp","Rugs","Spa","Lap","Gru"],today:"Šiandien",weekStart:1}}(jQuery); \ No newline at end of file diff --git a/static/bootstrap3/locales/bootstrap-datepicker.lv.min.js b/static/bootstrap3/locales/bootstrap-datepicker.lv.min.js new file mode 100755 index 0000000..5383880 --- /dev/null +++ b/static/bootstrap3/locales/bootstrap-datepicker.lv.min.js @@ -0,0 +1 @@ +!function(a){a.fn.datepicker.dates.lv={days:["Svētdiena","Pirmdiena","Otrdiena","Trešdiena","Ceturtdiena","Piektdiena","Sestdiena"],daysShort:["Sv","P","O","T","C","Pk","S"],daysMin:["Sv","Pr","Ot","Tr","Ce","Pk","Se"],months:["Janvāris","Februāris","Marts","Aprīlis","Maijs","Jūnijs","Jūlijs","Augusts","Septembris","Oktobris","Novembris","Decembris"],monthsShort:["Jan","Feb","Mar","Apr","Mai","Jūn","Jūl","Aug","Sep","Okt","Nov","Dec"],today:"Šodien",weekStart:1}}(jQuery); \ No newline at end of file diff --git a/static/bootstrap3/locales/bootstrap-datepicker.me.min.js b/static/bootstrap3/locales/bootstrap-datepicker.me.min.js new file mode 100755 index 0000000..c65a891 --- /dev/null +++ b/static/bootstrap3/locales/bootstrap-datepicker.me.min.js @@ -0,0 +1 @@ +!function(a){a.fn.datepicker.dates.me={days:["Nedjelja","Ponedjeljak","Utorak","Srijeda","Četvrtak","Petak","Subota"],daysShort:["Ned","Pon","Uto","Sri","Čet","Pet","Sub"],daysMin:["Ne","Po","Ut","Sr","Če","Pe","Su"],months:["Januar","Februar","Mart","April","Maj","Jun","Jul","Avgust","Septembar","Oktobar","Novembar","Decembar"],monthsShort:["Jan","Feb","Mar","Apr","Maj","Jun","Jul","Avg","Sep","Okt","Nov","Dec"],today:"Danas",weekStart:1,clear:"Izbriši",format:"dd.mm.yyyy"}}(jQuery); \ No newline at end of file diff --git a/static/bootstrap3/locales/bootstrap-datepicker.mk.min.js b/static/bootstrap3/locales/bootstrap-datepicker.mk.min.js new file mode 100755 index 0000000..46423f7 --- /dev/null +++ b/static/bootstrap3/locales/bootstrap-datepicker.mk.min.js @@ -0,0 +1 @@ +!function(a){a.fn.datepicker.dates.mk={days:["Недела","Понеделник","Вторник","Среда","Четврток","Петок","Сабота"],daysShort:["Нед","Пон","Вто","Сре","Чет","Пет","Саб"],daysMin:["Не","По","Вт","Ср","Че","Пе","Са"],months:["Јануари","Февруари","Март","Април","Мај","Јуни","Јули","Август","Септември","Октомври","Ноември","Декември"],monthsShort:["Јан","Фев","Мар","Апр","Мај","Јун","Јул","Авг","Сеп","Окт","Ное","Дек"],today:"Денес",format:"dd.mm.yyyy"}}(jQuery); \ No newline at end of file diff --git a/static/bootstrap3/locales/bootstrap-datepicker.mn.min.js b/static/bootstrap3/locales/bootstrap-datepicker.mn.min.js new file mode 100755 index 0000000..6ebaec9 --- /dev/null +++ b/static/bootstrap3/locales/bootstrap-datepicker.mn.min.js @@ -0,0 +1 @@ +!function(a){a.fn.datepicker.dates.mn={days:["Ням","Даваа","Мягмар","Лхагва","Пүрэв","Баасан","Бямба"],daysShort:["Ням","Дав","Мяг","Лха","Пүр","Баа","Бям"],daysMin:["Ня","Да","Мя","Лх","Пү","Ба","Бя"],months:["Хулгана","Үхэр","Бар","Туулай","Луу","Могой","Морь","Хонь","Бич","Тахиа","Нохой","Гахай"],monthsShort:["Хул","Үхэ","Бар","Туу","Луу","Мог","Мор","Хон","Бич","Тах","Нох","Гах"],today:"Өнөөдөр",clear:"Тодорхой",format:"yyyy.mm.dd",weekStart:1}}(jQuery); \ No newline at end of file diff --git a/static/bootstrap3/locales/bootstrap-datepicker.ms.min.js b/static/bootstrap3/locales/bootstrap-datepicker.ms.min.js new file mode 100755 index 0000000..ff44c91 --- /dev/null +++ b/static/bootstrap3/locales/bootstrap-datepicker.ms.min.js @@ -0,0 +1 @@ +!function(a){a.fn.datepicker.dates.ms={days:["Ahad","Isnin","Selasa","Rabu","Khamis","Jumaat","Sabtu"],daysShort:["Aha","Isn","Sel","Rab","Kha","Jum","Sab"],daysMin:["Ah","Is","Se","Ra","Kh","Ju","Sa"],months:["Januari","Februari","Mac","April","Mei","Jun","Julai","Ogos","September","Oktober","November","Disember"],monthsShort:["Jan","Feb","Mar","Apr","Mei","Jun","Jul","Ogo","Sep","Okt","Nov","Dis"],today:"Hari Ini"}}(jQuery); \ No newline at end of file diff --git a/static/bootstrap3/locales/bootstrap-datepicker.nb.min.js b/static/bootstrap3/locales/bootstrap-datepicker.nb.min.js new file mode 100755 index 0000000..273624a --- /dev/null +++ b/static/bootstrap3/locales/bootstrap-datepicker.nb.min.js @@ -0,0 +1 @@ +!function(a){a.fn.datepicker.dates.nb={days:["Søndag","Mandag","Tirsdag","Onsdag","Torsdag","Fredag","Lørdag"],daysShort:["Søn","Man","Tir","Ons","Tor","Fre","Lør"],daysMin:["Sø","Ma","Ti","On","To","Fr","Lø"],months:["Januar","Februar","Mars","April","Mai","Juni","Juli","August","September","Oktober","November","Desember"],monthsShort:["Jan","Feb","Mar","Apr","Mai","Jun","Jul","Aug","Sep","Okt","Nov","Des"],today:"I Dag"}}(jQuery); \ No newline at end of file diff --git a/static/bootstrap3/locales/bootstrap-datepicker.nl-BE.min.js b/static/bootstrap3/locales/bootstrap-datepicker.nl-BE.min.js new file mode 100755 index 0000000..5f38710 --- /dev/null +++ b/static/bootstrap3/locales/bootstrap-datepicker.nl-BE.min.js @@ -0,0 +1 @@ +!function(a){a.fn.datepicker.dates["nl-BE"]={days:["zondag","maandag","dinsdag","woensdag","donderdag","vrijdag","zaterdag"],daysShort:["zo","ma","di","wo","do","vr","za"],daysMin:["zo","ma","di","wo","do","vr","za"],months:["januari","februari","maart","april","mei","juni","juli","augustus","september","oktober","november","december"],monthsShort:["jan","feb","mrt","apr","mei","jun","jul","aug","sep","okt","nov","dec"],today:"Vandaag",clear:"Leegmaken",weekStart:1,format:"dd/mm/yyyy"}}(jQuery); \ No newline at end of file diff --git a/static/bootstrap3/locales/bootstrap-datepicker.nl.min.js b/static/bootstrap3/locales/bootstrap-datepicker.nl.min.js new file mode 100755 index 0000000..7931afe --- /dev/null +++ b/static/bootstrap3/locales/bootstrap-datepicker.nl.min.js @@ -0,0 +1 @@ +!function(a){a.fn.datepicker.dates.nl={days:["zondag","maandag","dinsdag","woensdag","donderdag","vrijdag","zaterdag"],daysShort:["zo","ma","di","wo","do","vr","za"],daysMin:["zo","ma","di","wo","do","vr","za"],months:["januari","februari","maart","april","mei","juni","juli","augustus","september","oktober","november","december"],monthsShort:["jan","feb","mrt","apr","mei","jun","jul","aug","sep","okt","nov","dec"],today:"Vandaag",clear:"Wissen",weekStart:1,format:"dd-mm-yyyy"}}(jQuery); \ No newline at end of file diff --git a/static/bootstrap3/locales/bootstrap-datepicker.no.min.js b/static/bootstrap3/locales/bootstrap-datepicker.no.min.js new file mode 100755 index 0000000..a606e7e --- /dev/null +++ b/static/bootstrap3/locales/bootstrap-datepicker.no.min.js @@ -0,0 +1 @@ +!function(a){a.fn.datepicker.dates.no={days:["Søndag","Mandag","Tirsdag","Onsdag","Torsdag","Fredag","Lørdag"],daysShort:["Søn","Man","Tir","Ons","Tor","Fre","Lør"],daysMin:["Sø","Ma","Ti","On","To","Fr","Lø"],months:["Januar","Februar","Mars","April","Mai","Juni","Juli","August","September","Oktober","November","Desember"],monthsShort:["Jan","Feb","Mar","Apr","Mai","Jun","Jul","Aug","Sep","Okt","Nov","Des"],today:"I dag",clear:"Nullstill",weekStart:1,format:"dd.mm.yyyy"}}(jQuery); \ No newline at end of file diff --git a/static/bootstrap3/locales/bootstrap-datepicker.pl.min.js b/static/bootstrap3/locales/bootstrap-datepicker.pl.min.js new file mode 100755 index 0000000..7cea53a --- /dev/null +++ b/static/bootstrap3/locales/bootstrap-datepicker.pl.min.js @@ -0,0 +1 @@ +!function(a){a.fn.datepicker.dates.pl={days:["niedziela","poniedziałek","wtorek","środa","czwartek","piątek","sobota"],daysShort:["niedz.","pon.","wt.","śr.","czw.","piąt.","sob."],daysMin:["ndz.","pn.","wt.","śr.","czw.","pt.","sob."],months:["styczeń","luty","marzec","kwiecień","maj","czerwiec","lipiec","sierpień","wrzesień","październik","listopad","grudzień"],monthsShort:["sty.","lut.","mar.","kwi.","maj","cze.","lip.","sie.","wrz.","paź.","lis.","gru."],today:"dzisiaj",weekStart:1,clear:"wyczyść",format:"dd.mm.yyyy"}}(jQuery); \ No newline at end of file diff --git a/static/bootstrap3/locales/bootstrap-datepicker.pt-BR.min.js b/static/bootstrap3/locales/bootstrap-datepicker.pt-BR.min.js new file mode 100755 index 0000000..9fe60ce --- /dev/null +++ b/static/bootstrap3/locales/bootstrap-datepicker.pt-BR.min.js @@ -0,0 +1 @@ +!function(a){a.fn.datepicker.dates["pt-BR"]={days:["Domingo","Segunda","Terça","Quarta","Quinta","Sexta","Sábado"],daysShort:["Dom","Seg","Ter","Qua","Qui","Sex","Sáb"],daysMin:["Do","Se","Te","Qu","Qu","Se","Sa"],months:["Janeiro","Fevereiro","Março","Abril","Maio","Junho","Julho","Agosto","Setembro","Outubro","Novembro","Dezembro"],monthsShort:["Jan","Fev","Mar","Abr","Mai","Jun","Jul","Ago","Set","Out","Nov","Dez"],today:"Hoje",clear:"Limpar",format:"dd/mm/yyyy"}}(jQuery); \ No newline at end of file diff --git a/static/bootstrap3/locales/bootstrap-datepicker.pt.min.js b/static/bootstrap3/locales/bootstrap-datepicker.pt.min.js new file mode 100755 index 0000000..27af6b1 --- /dev/null +++ b/static/bootstrap3/locales/bootstrap-datepicker.pt.min.js @@ -0,0 +1 @@ +!function(a){a.fn.datepicker.dates.pt={days:["Domingo","Segunda","Terça","Quarta","Quinta","Sexta","Sábado"],daysShort:["Dom","Seg","Ter","Qua","Qui","Sex","Sáb"],daysMin:["Do","Se","Te","Qu","Qu","Se","Sa"],months:["Janeiro","Fevereiro","Março","Abril","Maio","Junho","Julho","Agosto","Setembro","Outubro","Novembro","Dezembro"],monthsShort:["Jan","Fev","Mar","Abr","Mai","Jun","Jul","Ago","Set","Out","Nov","Dez"],today:"Hoje",clear:"Limpar",format:"dd/mm/yyyy"}}(jQuery); \ No newline at end of file diff --git a/static/bootstrap3/locales/bootstrap-datepicker.ro.min.js b/static/bootstrap3/locales/bootstrap-datepicker.ro.min.js new file mode 100755 index 0000000..731b21b --- /dev/null +++ b/static/bootstrap3/locales/bootstrap-datepicker.ro.min.js @@ -0,0 +1 @@ +!function(a){a.fn.datepicker.dates.ro={days:["Duminică","Luni","Marţi","Miercuri","Joi","Vineri","Sâmbătă"],daysShort:["Dum","Lun","Mar","Mie","Joi","Vin","Sâm"],daysMin:["Du","Lu","Ma","Mi","Jo","Vi","Sâ"],months:["Ianuarie","Februarie","Martie","Aprilie","Mai","Iunie","Iulie","August","Septembrie","Octombrie","Noiembrie","Decembrie"],monthsShort:["Ian","Feb","Mar","Apr","Mai","Iun","Iul","Aug","Sep","Oct","Nov","Dec"],today:"Astăzi",clear:"Șterge",weekStart:1}}(jQuery); \ No newline at end of file diff --git a/static/bootstrap3/locales/bootstrap-datepicker.rs-latin.min.js b/static/bootstrap3/locales/bootstrap-datepicker.rs-latin.min.js new file mode 100755 index 0000000..b0285f4 --- /dev/null +++ b/static/bootstrap3/locales/bootstrap-datepicker.rs-latin.min.js @@ -0,0 +1 @@ +!function(a){a.fn.datepicker.dates["rs-latin"]={days:["Nedelja","Ponedeljak","Utorak","Sreda","Četvrtak","Petak","Subota"],daysShort:["Ned","Pon","Uto","Sre","Čet","Pet","Sub"],daysMin:["N","Po","U","Sr","Č","Pe","Su"],months:["Januar","Februar","Mart","April","Maj","Jun","Jul","Avgust","Septembar","Oktobar","Novembar","Decembar"],monthsShort:["Jan","Feb","Mar","Apr","Maj","Jun","Jul","Avg","Sep","Okt","Nov","Dec"],today:"Danas",weekStart:1,format:"dd.mm.yyyy"}}(jQuery); \ No newline at end of file diff --git a/static/bootstrap3/locales/bootstrap-datepicker.rs.min.js b/static/bootstrap3/locales/bootstrap-datepicker.rs.min.js new file mode 100755 index 0000000..050f1ca --- /dev/null +++ b/static/bootstrap3/locales/bootstrap-datepicker.rs.min.js @@ -0,0 +1 @@ +!function(a){a.fn.datepicker.dates.rs={days:["Недеља","Понедељак","Уторак","Среда","Четвртак","Петак","Субота"],daysShort:["Нед","Пон","Уто","Сре","Чет","Пет","Суб"],daysMin:["Н","По","У","Ср","Ч","Пе","Су"],months:["Јануар","Фебруар","Март","Април","Мај","Јун","Јул","Август","Септембар","Октобар","Новембар","Децембар"],monthsShort:["Јан","Феб","Мар","Апр","Мај","Јун","Јул","Авг","Сеп","Окт","Нов","Дец"],today:"Данас",weekStart:1,format:"dd.mm.yyyy"}}(jQuery); \ No newline at end of file diff --git a/static/bootstrap3/locales/bootstrap-datepicker.ru.min.js b/static/bootstrap3/locales/bootstrap-datepicker.ru.min.js new file mode 100755 index 0000000..1bcbcb7 --- /dev/null +++ b/static/bootstrap3/locales/bootstrap-datepicker.ru.min.js @@ -0,0 +1 @@ +!function(a){a.fn.datepicker.dates.ru={days:["Воскресенье","Понедельник","Вторник","Среда","Четверг","Пятница","Суббота"],daysShort:["Вск","Пнд","Втр","Срд","Чтв","Птн","Суб"],daysMin:["Вс","Пн","Вт","Ср","Чт","Пт","Сб"],months:["Январь","Февраль","Март","Апрель","Май","Июнь","Июль","Август","Сентябрь","Октябрь","Ноябрь","Декабрь"],monthsShort:["Янв","Фев","Мар","Апр","Май","Июн","Июл","Авг","Сен","Окт","Ноя","Дек"],today:"Сегодня",clear:"Очистить",format:"dd.mm.yyyy",weekStart:1}}(jQuery); \ No newline at end of file diff --git a/static/bootstrap3/locales/bootstrap-datepicker.sk.min.js b/static/bootstrap3/locales/bootstrap-datepicker.sk.min.js new file mode 100755 index 0000000..7c91c6e --- /dev/null +++ b/static/bootstrap3/locales/bootstrap-datepicker.sk.min.js @@ -0,0 +1 @@ +!function(a){a.fn.datepicker.dates.sk={days:["Nedeľa","Pondelok","Utorok","Streda","Štvrtok","Piatok","Sobota"],daysShort:["Ned","Pon","Uto","Str","Štv","Pia","Sob"],daysMin:["Ne","Po","Ut","St","Št","Pia","So"],months:["Január","Február","Marec","Apríl","Máj","Jún","Júl","August","September","Október","November","December"],monthsShort:["Jan","Feb","Mar","Apr","Máj","Jún","Júl","Aug","Sep","Okt","Nov","Dec"],today:"Dnes",format:"dd.mm.yyyy"}}(jQuery); \ No newline at end of file diff --git a/static/bootstrap3/locales/bootstrap-datepicker.sl.min.js b/static/bootstrap3/locales/bootstrap-datepicker.sl.min.js new file mode 100755 index 0000000..d54d20b --- /dev/null +++ b/static/bootstrap3/locales/bootstrap-datepicker.sl.min.js @@ -0,0 +1 @@ +!function(a){a.fn.datepicker.dates.sl={days:["Nedelja","Ponedeljek","Torek","Sreda","Četrtek","Petek","Sobota"],daysShort:["Ned","Pon","Tor","Sre","Čet","Pet","Sob"],daysMin:["Ne","Po","To","Sr","Če","Pe","So"],months:["Januar","Februar","Marec","April","Maj","Junij","Julij","Avgust","September","Oktober","November","December"],monthsShort:["Jan","Feb","Mar","Apr","Maj","Jun","Jul","Avg","Sep","Okt","Nov","Dec"],today:"Danes"}}(jQuery); \ No newline at end of file diff --git a/static/bootstrap3/locales/bootstrap-datepicker.sq.min.js b/static/bootstrap3/locales/bootstrap-datepicker.sq.min.js new file mode 100755 index 0000000..40f3e1f --- /dev/null +++ b/static/bootstrap3/locales/bootstrap-datepicker.sq.min.js @@ -0,0 +1 @@ +!function(a){a.fn.datepicker.dates.sq={days:["E Diel","E Hënë","E Martē","E Mërkurë","E Enjte","E Premte","E Shtunë"],daysShort:["Die","Hën","Mar","Mër","Enj","Pre","Shtu"],daysMin:["Di","Hë","Ma","Më","En","Pr","Sht"],months:["Janar","Shkurt","Mars","Prill","Maj","Qershor","Korrik","Gusht","Shtator","Tetor","Nëntor","Dhjetor"],monthsShort:["Jan","Shk","Mar","Pri","Maj","Qer","Korr","Gu","Sht","Tet","Nën","Dhjet"],today:"Sot"}}(jQuery); \ No newline at end of file diff --git a/static/bootstrap3/locales/bootstrap-datepicker.sr-latin.min.js b/static/bootstrap3/locales/bootstrap-datepicker.sr-latin.min.js new file mode 100755 index 0000000..c6b7001 --- /dev/null +++ b/static/bootstrap3/locales/bootstrap-datepicker.sr-latin.min.js @@ -0,0 +1 @@ +!function(a){a.fn.datepicker.dates["sr-latin"]={days:["Nedelja","Ponedeljak","Utorak","Sreda","Četvrtak","Petak","Subota"],daysShort:["Ned","Pon","Uto","Sre","Čet","Pet","Sub"],daysMin:["N","Po","U","Sr","Č","Pe","Su"],months:["Januar","Februar","Mart","April","Maj","Jun","Jul","Avgust","Septembar","Oktobar","Novembar","Decembar"],monthsShort:["Jan","Feb","Mar","Apr","Maj","Jun","Jul","Avg","Sep","Okt","Nov","Dec"],today:"Danas",weekStart:1,format:"dd.mm.yyyy"}}(jQuery); \ No newline at end of file diff --git a/static/bootstrap3/locales/bootstrap-datepicker.sr.min.js b/static/bootstrap3/locales/bootstrap-datepicker.sr.min.js new file mode 100755 index 0000000..4e46dbf --- /dev/null +++ b/static/bootstrap3/locales/bootstrap-datepicker.sr.min.js @@ -0,0 +1 @@ +!function(a){a.fn.datepicker.dates.sr={days:["Недеља","Понедељак","Уторак","Среда","Четвртак","Петак","Субота"],daysShort:["Нед","Пон","Уто","Сре","Чет","Пет","Суб"],daysMin:["Н","По","У","Ср","Ч","Пе","Су"],months:["Јануар","Фебруар","Март","Април","Мај","Јун","Јул","Август","Септембар","Октобар","Новембар","Децембар"],monthsShort:["Јан","Феб","Мар","Апр","Мај","Јун","Јул","Авг","Сеп","Окт","Нов","Дец"],today:"Данас",weekStart:1,format:"dd.mm.yyyy"}}(jQuery); \ No newline at end of file diff --git a/static/bootstrap3/locales/bootstrap-datepicker.sv.min.js b/static/bootstrap3/locales/bootstrap-datepicker.sv.min.js new file mode 100755 index 0000000..f088f82 --- /dev/null +++ b/static/bootstrap3/locales/bootstrap-datepicker.sv.min.js @@ -0,0 +1 @@ +!function(a){a.fn.datepicker.dates.sv={days:["Söndag","Måndag","Tisdag","Onsdag","Torsdag","Fredag","Lördag"],daysShort:["Sön","Mån","Tis","Ons","Tor","Fre","Lör"],daysMin:["Sö","Må","Ti","On","To","Fr","Lö"],months:["Januari","Februari","Mars","April","Maj","Juni","Juli","Augusti","September","Oktober","November","December"],monthsShort:["Jan","Feb","Mar","Apr","Maj","Jun","Jul","Aug","Sep","Okt","Nov","Dec"],today:"Idag",format:"yyyy-mm-dd",weekStart:1,clear:"Rensa"}}(jQuery); \ No newline at end of file diff --git a/static/bootstrap3/locales/bootstrap-datepicker.sw.min.js b/static/bootstrap3/locales/bootstrap-datepicker.sw.min.js new file mode 100755 index 0000000..454d305 --- /dev/null +++ b/static/bootstrap3/locales/bootstrap-datepicker.sw.min.js @@ -0,0 +1 @@ +!function(a){a.fn.datepicker.dates.sw={days:["Jumapili","Jumatatu","Jumanne","Jumatano","Alhamisi","Ijumaa","Jumamosi"],daysShort:["J2","J3","J4","J5","Alh","Ij","J1"],daysMin:["2","3","4","5","A","I","1"],months:["Januari","Februari","Machi","Aprili","Mei","Juni","Julai","Agosti","Septemba","Oktoba","Novemba","Desemba"],monthsShort:["Jan","Feb","Mac","Apr","Mei","Jun","Jul","Ago","Sep","Okt","Nov","Des"],today:"Leo"}}(jQuery); \ No newline at end of file diff --git a/static/bootstrap3/locales/bootstrap-datepicker.th.min.js b/static/bootstrap3/locales/bootstrap-datepicker.th.min.js new file mode 100755 index 0000000..1e398ba --- /dev/null +++ b/static/bootstrap3/locales/bootstrap-datepicker.th.min.js @@ -0,0 +1 @@ +!function(a){a.fn.datepicker.dates.th={days:["อาทิตย์","จันทร์","อังคาร","พุธ","พฤหัส","ศุกร์","เสาร์","อาทิตย์"],daysShort:["อา","จ","อ","พ","พฤ","ศ","ส","อา"],daysMin:["อา","จ","อ","พ","พฤ","ศ","ส","อา"],months:["มกราคม","กุมภาพันธ์","มีนาคม","เมษายน","พฤษภาคม","มิถุนายน","กรกฎาคม","สิงหาคม","กันยายน","ตุลาคม","พฤศจิกายน","ธันวาคม"],monthsShort:["ม.ค.","ก.พ.","มี.ค.","เม.ย.","พ.ค.","มิ.ย.","ก.ค.","ส.ค.","ก.ย.","ต.ค.","พ.ย.","ธ.ค."],today:"วันนี้"}}(jQuery); \ No newline at end of file diff --git a/static/bootstrap3/locales/bootstrap-datepicker.tr.min.js b/static/bootstrap3/locales/bootstrap-datepicker.tr.min.js new file mode 100755 index 0000000..7889b11 --- /dev/null +++ b/static/bootstrap3/locales/bootstrap-datepicker.tr.min.js @@ -0,0 +1 @@ +!function(a){a.fn.datepicker.dates.tr={days:["Pazar","Pazartesi","Salı","Çarşamba","Perşembe","Cuma","Cumartesi"],daysShort:["Pz","Pzt","Sal","Çrş","Prş","Cu","Cts"],daysMin:["Pz","Pzt","Sa","Çr","Pr","Cu","Ct"],months:["Ocak","Şubat","Mart","Nisan","Mayıs","Haziran","Temmuz","Ağustos","Eylül","Ekim","Kasım","Aralık"],monthsShort:["Oca","Şub","Mar","Nis","May","Haz","Tem","Ağu","Eyl","Eki","Kas","Ara"],today:"Bugün",clear:"Temizle",weekStart:1,format:"dd.mm.yyyy"}}(jQuery); \ No newline at end of file diff --git a/static/bootstrap3/locales/bootstrap-datepicker.uk.min.js b/static/bootstrap3/locales/bootstrap-datepicker.uk.min.js new file mode 100755 index 0000000..41b02e6 --- /dev/null +++ b/static/bootstrap3/locales/bootstrap-datepicker.uk.min.js @@ -0,0 +1 @@ +!function(a){a.fn.datepicker.dates.uk={days:["Неділя","Понеділок","Вівторок","Середа","Четвер","П'ятниця","Субота"],daysShort:["Нед","Пнд","Втр","Срд","Чтв","Птн","Суб"],daysMin:["Нд","Пн","Вт","Ср","Чт","Пт","Сб"],months:["Cічень","Лютий","Березень","Квітень","Травень","Червень","Липень","Серпень","Вересень","Жовтень","Листопад","Грудень"],monthsShort:["Січ","Лют","Бер","Кві","Тра","Чер","Лип","Сер","Вер","Жов","Лис","Гру"],today:"Сьогодні",clear:"Очистити",format:"dd.mm.yyyy",weekStart:1}}(jQuery); \ No newline at end of file diff --git a/static/bootstrap3/locales/bootstrap-datepicker.vi.min.js b/static/bootstrap3/locales/bootstrap-datepicker.vi.min.js new file mode 100755 index 0000000..3311d23 --- /dev/null +++ b/static/bootstrap3/locales/bootstrap-datepicker.vi.min.js @@ -0,0 +1 @@ +!function(a){a.fn.datepicker.dates.vi={days:["Chủ nhật","Thứ hai","Thứ ba","Thứ tư","Thứ năm","Thứ sáu","Thứ bảy"],daysShort:["CN","Thứ 2","Thứ 3","Thứ 4","Thứ 5","Thứ 6","Thứ 7"],daysMin:["CN","T2","T3","T4","T5","T6","T7"],months:["Tháng 1","Tháng 2","Tháng 3","Tháng 4","Tháng 5","Tháng 6","Tháng 7","Tháng 8","Tháng 9","Tháng 10","Tháng 11","Tháng 12"],monthsShort:["Th1","Th2","Th3","Th4","Th5","Th6","Th7","Th8","Th9","Th10","Th11","Th12"],today:"Hôm nay",clear:"Xóa",format:"dd/mm/yyyy"}}(jQuery); \ No newline at end of file diff --git a/static/bootstrap3/locales/bootstrap-datepicker.zh-CN.min.js b/static/bootstrap3/locales/bootstrap-datepicker.zh-CN.min.js new file mode 100755 index 0000000..1279176 --- /dev/null +++ b/static/bootstrap3/locales/bootstrap-datepicker.zh-CN.min.js @@ -0,0 +1 @@ +!function(a){a.fn.datepicker.dates["zh-CN"]={days:["星期日","星期一","星期二","星期三","星期四","星期五","星期六"],daysShort:["周日","周一","周二","周三","周四","周五","周六"],daysMin:["日","一","二","三","四","五","六"],months:["一月","二月","三月","四月","五月","六月","七月","八月","九月","十月","十一月","十二月"],monthsShort:["1月","2月","3月","4月","5月","6月","7月","8月","9月","10月","11月","12月"],today:"今日",clear:"清除",format:"yyyy年mm月dd日",titleFormat:"yyyy年mm月",weekStart:1}}(jQuery); \ No newline at end of file diff --git a/static/bootstrap3/locales/bootstrap-datepicker.zh-TW.min.js b/static/bootstrap3/locales/bootstrap-datepicker.zh-TW.min.js new file mode 100755 index 0000000..e309c1d --- /dev/null +++ b/static/bootstrap3/locales/bootstrap-datepicker.zh-TW.min.js @@ -0,0 +1 @@ +!function(a){a.fn.datepicker.dates["zh-TW"]={days:["星期日","星期一","星期二","星期三","星期四","星期五","星期六"],daysShort:["週日","週一","週二","週三","週四","週五","週六"],daysMin:["日","一","二","三","四","五","六"],months:["一月","二月","三月","四月","五月","六月","七月","八月","九月","十月","十一月","十二月"],monthsShort:["1月","2月","3月","4月","5月","6月","7月","8月","9月","10月","11月","12月"],today:"今天",format:"yyyy年mm月dd日",weekStart:1,clear:"清除"}}(jQuery); \ No newline at end of file diff --git a/static/vendor/d3.min.js b/static/vendor/d3.min.js index 9a9af75..8fb0b32 100755 --- a/static/vendor/d3.min.js +++ b/static/vendor/d3.min.js @@ -1,2 +1,5 @@ -(function(){function e(a){var b=-1,c=a.length,d=[];while(++b=0?a.substring(b):(b=a.length,""),d=[];while(b>0)d.push(a.substring(b-=3,b+3));return d.reverse().join(",")+c}function w(a,b){return{scale:Math.pow(10,(8-b)*3),symbol:a}}function B(a){return function(b){return b<=0?0:b>=1?1:a(b)}}function C(a){return function(b){return 1-a(1-b)}}function D(a){return function(b){return.5*(b<.5?a(2*b):2-a(2-2*b))}}function E(a){return a}function F(a){return function(b){return Math.pow(b,a)}}function G(a){return 1-Math.cos(a*Math.PI/2)}function H(a){return Math.pow(2,10*(a-1))}function I(a){return 1-Math.sqrt(1-a*a)}function J(a,b){var c;return arguments.length<2&&(b=.45),arguments.length<1?(a=1,c=b/4):c=b/(2*Math.PI)*Math.asin(1/a),function(d){return 1+a*Math.pow(2,10*-d)*Math.sin((d-c)*2*Math.PI/b)}}function K(a){return a||(a=1.70158),function(b){return b*b*((a+1)*b-a)}}function L(a){return a<1/2.75?7.5625*a*a:a<2/2.75?7.5625*(a-=1.5/2.75)*a+.75:a<2.5/2.75?7.5625*(a-=2.25/2.75)*a+.9375:7.5625*(a-=2.625/2.75)*a+.984375}function M(){d3.event.stopPropagation(),d3.event.preventDefault()}function O(a){return a=="transform"?d3.interpolateTransform:d3.interpolate}function P(a,b){return b=b-(a=+a)?1/(b-a):0,function(c){return(c-a)*b}}function Q(a,b){return b=b-(a=+a)?1/(b-a):0,function(c){return Math.max(0,Math.min(1,(c-a)*b))}}function R(a,b,c){return new S(a,b,c)}function S(a,b,c){this.r=a,this.g=b,this.b=c}function T(a){return a<16?"0"+Math.max(0,a).toString(16):Math.min(255,a).toString(16)}function U(a,b,c){var d=0,e=0,f=0,g,h,i;g=/([a-z]+)\((.*)\)/i.exec(a);if(g){h=g[2].split(",");switch(g[1]){case"hsl":return c(parseFloat(h[0]),parseFloat(h[1])/100,parseFloat(h[2])/100);case"rgb":return b(W(h[0]),W(h[1]),W(h[2]))}}return(i=X[a])?b(i.r,i.g,i.b):(a!=null&&a.charAt(0)==="#"&&(a.length===4?(d=a.charAt(1),d+=d,e=a.charAt(2),e+=e,f=a.charAt(3),f+=f):a.length===7&&(d=a.substring(1,3),e=a.substring(3,5),f=a.substring(5,7)),d=parseInt(d,16),e=parseInt(e,16),f=parseInt(f,16)),b(d,e,f))}function V(a,b,c){var d=Math.min(a/=255,b/=255,c/=255),e=Math.max(a,b,c),f=e-d,g,h,i=(e+d)/2;return f?(h=i<.5?f/(e+d):f/(2-e-d),a==e?g=(b-c)/f+(b360?a-=360:a<0&&(a+=360),a<60?d+(e-d)*a/60:a<180?e:a<240?d+(e-d)*(240-a)/60:d}function g(a){return Math.round(f(a)*255)}var d,e;return a%=360,a<0&&(a+=360),b=b<0?0:b>1?1:b,c=c<0?0:c>1?1:c,e=c<=.5?c*(1+b):c+b-c*b,d=2*c-e,R(g(a+120),g(a),g(a-120))}function ba(a){return h(a,bd),a}function be(a){return function(){return bb(a,this)}}function bf(a){return function(){return bc(a,this)}}function bh(a,b){function f(){if(b=this.classList)return b.add(a);var b=this.className,d=b.baseVal!=null,e=d?b.baseVal:b;c.lastIndex=0,c.test(e)||(e=m(e+" "+a),d?b.baseVal=e:this.className=e)}function g(){if(b=this.classList)return b.remove(a);var b=this.className,d=b.baseVal!=null,e=d?b.baseVal:b;e=m(e.replace(c," ")),d?b.baseVal=e:this.className=e}function h(){(b.apply(this,arguments)?f:g).call(this)}var c=new RegExp("(^|\\s+)"+d3.requote(a)+"(\\s+|$)","g");if(arguments.length<2){var d=this.node();if(e=d.classList)return e.contains(a);var e=d.className;return c.lastIndex=0,c.test(e.baseVal!=null?e.baseVal:e)}return this.each(typeof b=="function"?h:b?f:g)}function bi(a){return{__data__:a}}function bj(a){return arguments.length||(a=d3.ascending),function(b,c){return a(b&&b.__data__,c&&c.__data__)}}function bl(a){return h(a,bm),a}function bn(a,b,c){h(a,br);var d={},e=d3.dispatch("start","end"),f=bu;return a.id=b,a.time=c,a.tween=function(b,c){return arguments.length<2?d[b]:(c==null?delete d[b]:d[b]=c,a)},a.ease=function(b){return arguments.length?(f=typeof b=="function"?b:d3.ease.apply(d3,arguments),a):f},a.each=function(b,c){return arguments.length<2?bv.call(a,b):(e.on(b,c),a)},d3.timer(function(g){return a.each(function(h,i,j){function p(a){if(o.active>b)return r();o.active=b;for(var f in d)(f=d[f].call(l,h,i))&&k.push(f);return e.start.call(l,h,i),q(a)||d3.timer(q,0,c),1}function q(a){if(o.active!==b)return r();var c=(a-m)/n,d=f(c),g=k.length;while(g>0)k[--g].call(l,d);if(c>=1)return r(),bt=b,e.end.call(l,h,i),bt=0,1}function r(){return--o.count||delete l.__transition__,1}var k=[],l=this,m=a[j][i].delay,n=a[j][i].duration,o=l.__transition__||(l.__transition__={active:0,count:0});++o.count,m<=g?p(g):d3.timer(p,m,c)}),1},0,c),a}function bp(a,b,c){return c!=""&&bo}function bq(a,b){function d(a,d,e){var f=b.call(this,a,d);return f==null?e!=""&&bo:e!=f&&c(e,f)}function e(a,d,e){return e!=b&&c(e,b)}var c=O(a);return typeof b=="function"?d:b==null?bp:(b+="",e)}function bv(a){for(var b=0,c=this.length;b=c.delay&&(c.flush=c.callback(a)),c=c.next;var d=bA()-b;d>24?(isFinite(d)&&(clearTimeout(by),by=setTimeout(bz,d)),bx=0):(bx=1,bB(bz))}function bA(){var a=null,b=bw,c=Infinity;while(b)b.flush?b=a?a.next=b.next:bw=b.next:(c=Math.min(c,b.then+b.delay),b=(a=b).next);return c}function bC(a){var b=[a.a,a.b],c=[a.c,a.d],d=bE(b),e=bD(b,c),f=bE(bF(c,b,-e));this.translate=[a.e,a.f],this.rotate=Math.atan2(a.b,a.a)*bH,this.scale=[d,f||0],this.skew=f?e/f*bH:0}function bD(a,b){return a[0]*b[0]+a[1]*b[1]}function bE(a){var b=Math.sqrt(bD(a,a));return a[0]/=b,a[1]/=b,b}function bF(a,b,c){return a[0]+=c*b[0],a[1]+=c*b[1],a}function bI(){}function bJ(a){var b=a[0],c=a[a.length-1];return b0;j--)e.push(c(f)*j)}else{for(;fi;g--);e=e.slice(f,g)}return e},d.tickFormat=function(a,e){arguments.length<2&&(e=bV);if(arguments.length<1)return e;var f=a/d.ticks().length,g=b===bX?(h=-1e-15,Math.floor):(h=1e-15,Math.ceil),h;return function(a){return a/c(g(b(a)+h))1){h=b[1],f=a[i],i++,d+="C"+(e[0]+g[0])+","+(e[1]+g[1])+","+(f[0]-h[0])+","+(f[1]-h[1])+","+f[0]+","+f[1];for(var j=2;j9&&(f=c*3/Math.sqrt(f),g[h]=f*d,g[h+1]=f*e));h=-1;while(++h<=i)f=(a[Math.min(i,h+1)][0]-a[Math.max(0,h-1)][0])/(6*(1+g[h]*g[h])),b.push([f||0,g[h]*f||0]);return b}function cK(a){return a.length<3?cq(a):a[0]+cw(a,cJ(a))}function cL(a){var b,c=-1,d=a.length,e,f;while(++c1){var d=bJ(a.domain()),e,f=-1,g=b.length,h=(b[1]-b[0])/++c,i,j;while(++f0;)(j=+b[f]-i*h)>=d[0]&&e.push(j);for(--f,i=0;++ib?1:a>=b?0:NaN},d3.descending=function(a,b){return ba?1:b>=a?0:NaN},d3.mean=function(a,b){var c=a.length,d,e=0,f=-1,g=0;if(arguments.length===1)while(++f1&&(a=a.map(b)),a=a.filter(j),a.length?d3.quantile(a.sort(d3.ascending),.5):undefined},d3.min=function(a,b){var c=-1,d=a.length,e,f;if(arguments.length===1){while(++cf&&(e=f)}else{while(++cf&&(e=f)}return e},d3.max=function(a,b){var c=-1,d=a.length,e,f;if(arguments.length===1){while(++ce&&(e=f)}else{while(++ce&&(e=f)}return e},d3.extent=function(a,b){var c=-1,d=a.length,e,f,g;if(arguments.length===1){while(++cf&&(e=f),gf&&(e=f),g1);return a+b*c*Math.sqrt(-2*Math.log(e)/e)}}},d3.sum=function(a,b){var c=0,d=a.length,e,f=-1;if(arguments.length===1)while(++f>1;a[e]>1;b0&&(e=f);return e},d3.last=function(a,b){var c=0,d=a.length,e=a[0],f;arguments.length===1&&(b=d3.ascending);while(++c=b.length)return e?e.call(a,c):d?c.sort(d):c;var h=-1,i=c.length,j=b[g++],k,l,m={};while(++h=b.length)return a;var e=[],f=c[d++],h;for(h in a)e.push({key:h,values:g(a[h],d)});return f&&e.sort(function(a,b){return f(a.key,b.key)}),e}var a={},b=[],c=[],d,e;return a.map=function(a){return f(a,0)},a.entries=function(a){return g(f(a,0),0)},a.key=function(c){return b.push(c),a},a.sortKeys=function(d){return c[b.length-1]=d,a},a.sortValues=function(b){return d=b,a},a.rollup=function(b){return e=b,a},a},d3.keys=function(a){var b=[];for(var c in a)b.push(c);return b},d3.values=function(a){var b=[];for(var c in a)b.push(a[c]);return b},d3.entries=function(a){var b=[];for(var c in a)b.push({key:c,value:a[c]});return b},d3.permute=function(a,b){var c=[],d=-1,e=b.length;while(++db)d.push(f);else while((f=a+c*++e)0&&(d=a.substring(c+1),a=a.substring(0,c)),this[a].on(d,b)},d3.format=function(a){var b=q.exec(a),c=b[1]||" ",d=b[3]||"",e=b[5],f=+b[6],g=b[7],h=b[8],i=b[9],j=1,k="",l=!1;h&&(h=+h.substring(1)),e&&(c="0",g&&(f-=Math.floor((f-1)/4)));switch(i){case"n":g=!0,i="g";break;case"%":j=100,k="%",i="f";break;case"p":j=100,k="%",i="r";break;case"d":l=!0,h=0;break;case"s":j=-1,i="r"}return i=="r"&&!h&&(i="g"),i=r[i]||t,function(a){if(l&&a%1)return"";var b=a<0&&(a=-a)?"−":d;if(j<0){var m=d3.formatPrefix(a,h);a*=m.scale,k=m.symbol}else a*=j;a=i(a,h);if(e){var n=a.length+b.length;n=^]))?([+\- ])?(#)?(0)?([0-9]+)?(,)?(\.[0-9]+)?([a-zA-Z%])?/,r={g:function(a,b){return a.toPrecision(b)},e:function(a,b){return a.toExponential(b)},f:function(a,b){return a.toFixed(b)},r:function(a,b){return d3.round(a,b=s(a,b)).toFixed(Math.max(0,Math.min(20,b)))}},v=["y","z","a","f","p","n","μ","m","","k","M","G","T","P","E","Z","Y"].map(w);d3.formatPrefix=function(a,b){var c=0;return a&&(a<0&&(a*=-1),b&&(a=d3.round(a,s(a,b))),c=1+Math.floor(1e-12+Math.log(a)/Math.LN10),c=Math.max(-24,Math.min(24,Math.floor((c<=0?c+1:c-1)/3)*3))),v[8+c/3]};var x=F(2),y=F(3),z={linear:function(){return E},poly:F,quad:function(){return x},cubic:function(){return y},sin:function(){return G},exp:function(){return H},circle:function(){return I},elastic:J,back:K,bounce:function(){return L}},A={"in":function(a){return a},out:C,"in-out":D,"out-in":function(a){return D(C(a))}};d3.ease=function(a){var b=a.indexOf("-"),c=b>=0?a.substring(0,b):a,d=b>=0?a.substring(b+1):"in";return B(A[d](z[c].apply(null,Array.prototype.slice.call(arguments,1))))},d3.event=null,d3.interpolate=function(a,b){var c=d3.interpolators.length,d;while(--c>=0&&!(d=d3.interpolators[c](a,b)));return d},d3.interpolateNumber=function(a,b){return b-=a,function(c){return a+b*c}},d3.interpolateRound=function(a,b){return b-=a,function(c){return Math.round(a+b*c)}},d3.interpolateString=function(a,b){var c,d,e,f=0,g=0,h=[],i=[],j,k;N.lastIndex=0;for(d=0;c=N.exec(b);++d)c.index&&h.push(b.substring(f,g=c.index)),i.push({i:h.length,x:c[0]}),h.push(null),f=N.lastIndex;f1){while(++e0&&(a=a.substring(0,e)),arguments.length<2?(e=this.node()[d])&&e._:this.each(function(e,f){function h(a){var c=d3.event;d3.event=a;try{b.call(g,g.__data__,f)}finally{d3.event=c}}var g=this;g[d]&&g.removeEventListener(a,g[d],c),b&&g.addEventListener(a,g[d]=h,c),h._=b})},bd.each=function(a){for(var b=-1,c=this.length;++b=cg?e?"M0,"+f+"A"+f+","+f+" 0 1,1 0,"+ -f+"A"+f+","+f+" 0 1,1 0,"+f+"M0,"+e+"A"+e+","+e+" 0 1,0 0,"+ -e+"A"+e+","+e+" 0 1,0 0,"+e+"Z":"M0,"+f+"A"+f+","+f+" 0 1,1 0,"+ -f+"A"+f+","+f+" 0 1,1 0,"+f+"Z":e?"M"+f*k+","+f*l+"A"+f+","+f+" 0 "+j+",1 "+f*m+","+f*n+"L"+e*m+","+e*n+"A"+e+","+e+" 0 "+j+",0 "+e*k+","+e*l+"Z":"M"+f*k+","+f*l+"A"+f+","+f+" 0 "+j+",1 "+f*m+","+f*n+"L0,0"+"Z"}var a=ch,b=ci,c=cj,d=ck;return e.innerRadius=function(b){return arguments.length?(a=d3.functor(b),e):a},e.outerRadius=function(a){return arguments.length?(b=d3.functor(a),e):b},e.startAngle=function(a){return arguments.length?(c=d3.functor(a),e):c},e.endAngle=function(a){return arguments.length?(d=d3.functor(a),e):d},e.centroid=function(){var e=(a.apply(this,arguments)+b.apply(this,arguments))/2,f=(c.apply(this,arguments)+d.apply(this,arguments))/2+cf;return[Math.cos(f)*e,Math.sin(f)*e]},e};var cf=-Math.PI/2,cg=2*Math.PI-1e-6;d3.svg.line=function(){return cl(Object)};var cp={linear:cq,"step-before":cr,"step-after":cs,basis:cy,"basis-open":cz,"basis-closed":cA,bundle:cB,cardinal:cv,"cardinal-open":ct,"cardinal-closed":cu,monotone:cK},cD=[0,2/3,1/3,0],cE=[0,1/3,2/3,0],cF=[0,1/6,2/3,1/6];d3.svg.line.radial=function(){var a=cl(cL);return a.radius=a.x,delete a.x,a.angle=a.y,delete a.y,a},cr.reverse=cs,cs.reverse=cr,d3.svg.area=function(){return cM(Object)},d3.svg.area.radial=function(){var a=cM(cL);return a.radius=a.x,delete a.x,a.innerRadius=a.x0,delete a.x0,a.outerRadius=a.x1,delete a.x1,a.angle=a.y,delete a.y,a.startAngle=a.y0,delete a.y0,a.endAngle=a.y1,delete a.y1,a},d3.svg.chord=function(){function f(c,d){var e=g(this,a,c,d),f=g(this,b,c,d);return"M"+e.p0+i(e.r,e.p1)+(h(e,f)?j(e.r,e.p1,e.r,e.p0):j(e.r,e.p1,f.r,f.p0)+i(f.r,f.p1)+j(f.r,f.p1,e.r,e.p0))+"Z"}function g(a,b,f,g){var h=b.call(a,f,g),i=c.call(a,h,g),j=d.call(a,h,g)+cf,k=e.call(a,h,g)+cf;return{r:i,a0:j,a1:k,p0:[i*Math.cos(j),i*Math.sin(j)],p1:[i*Math.cos(k),i*Math.sin(k)]}}function h(a,b){return a.a0==b.a0&&a.a1==b.a1}function i(a,b){return"A"+a+","+a+" 0 0,1 "+b}function j(a,b,c,d){return"Q 0,0 "+d}var a=cP,b=cQ,c=cR,d=cj,e=ck;return f.radius=function(a){return arguments.length?(c=d3.functor(a),f):c},f.source=function(b){return arguments.length?(a=d3.functor(b),f):a},f.target=function(a){return arguments.length?(b=d3.functor(a),f):b},f.startAngle=function(a){return arguments.length?(d=d3.functor(a),f):d},f.endAngle=function(a){return arguments.length?(e=d3.functor(a),f):e},f},d3.svg.diagonal=function(){function d(d,e){var f=a.call(this,d,e),g=b.call(this,d,e),h=(f.y+g.y)/2,i=[f,{x:f.x,y:h},{x:g.x,y:h},g];return i=i.map(c),"M"+i[0]+"C"+i[1]+" "+i[2]+" "+i[3]}var a=cP,b=cQ,c=cU;return d.source=function(b){return arguments.length?(a=d3.functor(b),d):a},d.target=function(a){return arguments.length?(b=d3.functor(a),d):b},d.projection=function(a){return arguments.length?(c=a,d):c},d},d3.svg.diagonal.radial=function(){var a=d3.svg.diagonal(),b=cU,c=a.projection;return a.projection=function(a){return arguments.length?c(cV(b=a)):b},a},d3.svg.mouse=function(a){return cX(a,d3.event)};var cW=/WebKit/.test(navigator.userAgent)?-1:0;d3.svg.touches=function(a,b){return arguments.length<2&&(b=d3.event.touches),b?d(b).map(function(b){var c=cX(a,b);return c.identifier=b.identifier,c}):[]},d3.svg.symbol=function(){function c(c,d){return(c$[a.call(this,c,d)]||c$.circle)(b.call(this,c,d))}var a=cZ,b=cY;return c.type=function(b){return arguments.length?(a=d3.functor(b),c):a},c.size=function(a){return arguments.length?(b=d3.functor(a),c):b},c};var c$={circle:function(a){var b=Math.sqrt(a/Math.PI);return"M0,"+b+"A"+b+","+b+" 0 1,1 0,"+ -b+"A"+b+","+b+" 0 1,1 0,"+b+"Z"},cross:function(a){var b=Math.sqrt(a/5)/2;return"M"+ -3*b+","+ -b+"H"+ -b+"V"+ -3*b+"H"+b+"V"+ -b+"H"+3*b+"V"+b+"H"+b+"V"+3*b+"H"+ -b+"V"+b+"H"+ -3*b+"Z"},diamond:function(a){var b=Math.sqrt(a/(2*da)),c=b*da;return"M0,"+ -b+"L"+c+",0"+" 0,"+b+" "+ -c+",0"+"Z"},square:function(a){var b=Math.sqrt(a)/2;return"M"+ -b+","+ -b+"L"+b+","+ -b+" "+b+","+b+" "+ -b+","+b+"Z"},"triangle-down":function(a){var b=Math.sqrt(a/c_),c=b*c_/2;return"M0,"+c+"L"+b+","+ -c+" "+ -b+","+ -c+"Z"},"triangle-up":function(a){var b=Math.sqrt(a/c_),c=b*c_/2;return"M0,"+ -c+"L"+b+","+c+" "+ -b+","+c+"Z"}};d3.svg.symbolTypes=d3.keys(c$);var c_=Math.sqrt(3),da=Math.tan(30*Math.PI/180);d3.svg.axis=function(){function j(j){j.each(function(k,l,m){var n=d3.select(this),o=j.delay?function(a){var b=bt;try{return bt=j.id,a.transition().delay(j[m][l].delay).duration(j[m][l].duration).ease(j.ease())}finally{bt=b}}:Object,p=a.ticks.apply(a,g),q=h==null?a.tickFormat.apply(a,g):h,r=dd(a,p,i),s=n.selectAll(".minor").data(r,String),t=s.enter().insert("svg:line","g").attr("class","tick minor").style("opacity",1e-6),u=o(s.exit()).style("opacity",1e-6).remove(),v=o(s).style("opacity",1),w=n.selectAll("g").data(p,String),x=w.enter().insert("svg:g","path").style("opacity",1e-6),y=o(w.exit()).style("opacity",1e-6).remove(),z=o(w).style("opacity",1),A,B=bJ(a.range()),C=n.selectAll(".domain").data([0]),D=C.enter().append("svg:path").attr("class","domain"),E=o(C),F=this.__chart__||a;this.__chart__=a.copy(),x.append("svg:line").attr("class","tick"),x.append("svg:text"),z.select("text").text(q);switch(b){case"bottom":A=db,v.attr("x2",0).attr("y2",d),z.select("line").attr("x2",0).attr("y2",c),z.select("text").attr("x",0).attr("y",Math.max(c,0)+f).attr("dy",".71em").attr("text-anchor","middle"),E.attr("d","M"+B[0]+","+e+"V0H"+B[1]+"V"+e);break;case"top":A=db,v.attr("x2",0).attr("y2",-d),z.select("line").attr("x2",0).attr("y2",-c),z.select("text").attr("x",0).attr("y",-(Math.max(c,0)+f)).attr("dy","0em").attr("text-anchor","middle"),E.attr("d","M"+B[0]+","+ -e+"V0H"+B[1]+"V"+ -e);break;case"left":A=dc,v.attr("x2",-d).attr("y2",0),z.select("line").attr("x2",-c).attr("y2",0),z.select("text").attr("x",-(Math.max(c,0)+f)).attr("y",0).attr("dy",".32em").attr("text-anchor","end"),E.attr("d","M"+ -e+","+B[0]+"H0V"+B[1]+"H"+ -e);break;case"right":A=dc,v.attr("x2",d).attr("y2",0),z.select("line").attr("x2",c).attr("y2",0),z.select("text").attr("x",Math.max(c,0)+f).attr("y",0).attr("dy",".32em").attr("text-anchor","start"),E.attr("d","M"+e+","+B[0]+"H0V"+B[1]+"H"+e)}x.call(A,F),z.call(A,a),y.call(A,a),t.call(A,F),v.call(A,a),u.call(A,a)})}var a=d3.scale.linear(),b="bottom",c=6,d=6,e=6,f=3,g=[10],h,i=0;return j.scale=function(b){return arguments.length?(a=b,j):a},j.orient=function(a){return arguments.length?(b=a,j):b},j.ticks=function(){return arguments.length?(g=arguments,j):g},j.tickFormat=function(a){return arguments.length?(h=a,j):h},j.tickSize=function(a,b,f){if(!arguments.length)return c;var g=arguments.length-1;return c=+a,d=g>1?+b:c,e=g>0?+arguments[g]:c,j},j.tickPadding=function(a){return arguments.length?(f=+a,j):f},j.tickSubdivide=function(a){return arguments.length?(i=+a,j):i},j},d3.svg.brush=function(){function e(a){var g=b&&c?["n","e","s","w","nw","ne","se","sw"]:b?["e","w"]:c?["n","s"]:[];a.each(function(){var a=d3.select(this).on("mousedown.brush",f),h=a.selectAll(".background").data([,]),i=a.selectAll(".extent").data([,]),j=a.selectAll(".resize").data(g,String),k;h.enter().append("svg:rect").attr("class","background").style("visibility","hidden").style("pointer-events","all").style("cursor","crosshair"),i.enter().append("svg:rect").attr("class","extent").style("cursor","move"),j.enter().append("svg:rect").attr("class",function(a){return"resize "+a}).attr("width",6).attr("height",6).style("visibility","hidden").style("pointer-events",e.empty()?"none":"all").style("cursor",function(a){return dw[a]}),j.exit().remove(),b&&(k=bJ(b.range()),h.attr("x",k[0]).attr("width",k[1]-k[0]),dp(a,d)),c&&(k=bJ(c.range()),h.attr("y",k[0]).attr("height",k[1]-k[0]),dq(a,d))})}function f(){var a=d3.select(d3.event.target);de=e,dg=this,dj=d,dn=d3.svg.mouse(dg),(dk=a.classed("extent"))?(dn[0]=d[0][0]-dn[0],dn[1]=d[0][1]-dn[1]):a.classed("resize")?(dl=d3.event.target.__data__,dn[0]=d[+/w$/.test(dl)][0],dn[1]=d[+/^n/.test(dl)][1]):d3.event.altKey&&(dm=dn.slice()),dh=!/^(n|s)$/.test(dl)&&b,di=!/^(e|w)$/.test(dl)&&c,df=g(this,arguments),df("brushstart"),dt(),M()}function g(b,c){return function(d){var f=d3.event;try{d3.event={type:d,target:e},a[d].apply(b,c)}finally{d3.event=f}}}var a=d3.dispatch("brushstart","brush","brushend"),b,c,d=[[0,0],[0,0]];return e.x=function(a){return arguments.length?(b=a,e):b},e.y=function(a){return arguments.length?(c=a,e):c},e.extent=function(a){var f,g,h,i,j;return arguments.length?(b&&(f=a[0],g=a[1],c&&(f=f[0],g=g[0]),f=b(f),g=b(g),gn?-1:n>t?1:n>=t?0:0/0}function r(n){return null===n?0/0:+n}function u(n){return!isNaN(n)}function i(n){return{left:function(t,e,r,u){for(arguments.length<3&&(r=0),arguments.length<4&&(u=t.length);u>r;){var i=r+u>>>1;n(t[i],e)<0?r=i+1:u=i}return r},right:function(t,e,r,u){for(arguments.length<3&&(r=0),arguments.length<4&&(u=t.length);u>r;){var i=r+u>>>1;n(t[i],e)>0?u=i:r=i+1}return r}}}function o(n){return n.length}function a(n){for(var t=1;n*t%1;)t*=10;return t}function l(n,t){for(var e in t)Object.defineProperty(n.prototype,e,{value:t[e],enumerable:!1})}function c(){this._=Object.create(null)}function s(n){return(n+="")===xa||n[0]===ba?ba+n:n}function f(n){return(n+="")[0]===ba?n.slice(1):n}function h(n){return s(n)in this._}function g(n){return(n=s(n))in this._&&delete this._[n]}function p(){var n=[];for(var t in this._)n.push(f(t));return n}function v(){var n=0;for(var t in this._)++n;return n}function d(){for(var n in this._)return!1;return!0}function m(){this._=Object.create(null)}function y(n){return n}function M(n,t,e){return function(){var r=e.apply(t,arguments);return r===t?n:r}}function x(n,t){if(t in n)return t;t=t.charAt(0).toUpperCase()+t.slice(1);for(var e=0,r=_a.length;r>e;++e){var u=_a[e]+t;if(u in n)return u}}function b(){}function _(){}function w(n){function t(){for(var t,r=e,u=-1,i=r.length;++ue;e++)for(var u,i=n[e],o=0,a=i.length;a>o;o++)(u=i[o])&&t(u,o,e);return n}function Z(n){return Sa(n,za),n}function V(n){var t,e;return function(r,u,i){var o,a=n[i].update,l=a.length;for(i!=e&&(e=i,t=0),u>=t&&(t=u+1);!(o=a[t])&&++t0&&(n=n.slice(0,a));var c=La.get(n);return c&&(n=c,l=B),a?t?u:r:t?b:i}function $(n,t){return function(e){var r=aa.event;aa.event=e,t[0]=this.__data__;try{n.apply(this,t)}finally{aa.event=r}}}function B(n,t){var e=$(n,t);return function(n){var t=this,r=n.relatedTarget;r&&(r===t||8&r.compareDocumentPosition(t))||e.call(t,n)}}function W(e){var r=".dragsuppress-"+ ++Ta,u="click"+r,i=aa.select(t(e)).on("touchmove"+r,S).on("dragstart"+r,S).on("selectstart"+r,S);if(null==qa&&(qa="onselectstart"in e?!1:x(e.style,"userSelect")),qa){var o=n(e).style,a=o[qa];o[qa]="none"}return function(n){if(i.on(r,null),qa&&(o[qa]=a),n){var t=function(){i.on(u,null)};i.on(u,function(){S(),t()},!0),setTimeout(t,0)}}}function J(n,e){e.changedTouches&&(e=e.changedTouches[0]);var r=n.ownerSVGElement||n;if(r.createSVGPoint){var u=r.createSVGPoint();if(0>Ra){var i=t(n);if(i.scrollX||i.scrollY){r=aa.select("body").append("svg").style({position:"absolute",top:0,left:0,margin:0,padding:0,border:"none"},"important");var o=r[0][0].getScreenCTM();Ra=!(o.f||o.e),r.remove()}}return Ra?(u.x=e.pageX,u.y=e.pageY):(u.x=e.clientX,u.y=e.clientY),u=u.matrixTransform(n.getScreenCTM().inverse()),[u.x,u.y]}var a=n.getBoundingClientRect();return[e.clientX-a.left-n.clientLeft,e.clientY-a.top-n.clientTop]}function G(){return aa.event.changedTouches[0].identifier}function K(n){return n>0?1:0>n?-1:0}function Q(n,t,e){return(t[0]-n[0])*(e[1]-n[1])-(t[1]-n[1])*(e[0]-n[0])}function nt(n){return n>1?0:-1>n?ja:Math.acos(n)}function tt(n){return n>1?Ha:-1>n?-Ha:Math.asin(n)}function et(n){return((n=Math.exp(n))-1/n)/2}function rt(n){return((n=Math.exp(n))+1/n)/2}function ut(n){return((n=Math.exp(2*n))-1)/(n+1)}function it(n){return(n=Math.sin(n/2))*n}function ot(){}function at(n,t,e){return this instanceof at?(this.h=+n,this.s=+t,void(this.l=+e)):arguments.length<2?n instanceof at?new at(n.h,n.s,n.l):bt(""+n,_t,at):new at(n,t,e)}function lt(n,t,e){function r(n){return n>360?n-=360:0>n&&(n+=360),60>n?i+(o-i)*n/60:180>n?o:240>n?i+(o-i)*(240-n)/60:i}function u(n){return Math.round(255*r(n))}var i,o;return n=isNaN(n)?0:(n%=360)<0?n+360:n,t=isNaN(t)?0:0>t?0:t>1?1:t,e=0>e?0:e>1?1:e,o=.5>=e?e*(1+t):e+t-e*t,i=2*e-o,new mt(u(n+120),u(n),u(n-120))}function ct(n,t,e){return this instanceof ct?(this.h=+n,this.c=+t,void(this.l=+e)):arguments.length<2?n instanceof ct?new ct(n.h,n.c,n.l):n instanceof ft?gt(n.l,n.a,n.b):gt((n=wt((n=aa.rgb(n)).r,n.g,n.b)).l,n.a,n.b):new ct(n,t,e)}function st(n,t,e){return isNaN(n)&&(n=0),isNaN(t)&&(t=0),new ft(e,Math.cos(n*=Oa)*t,Math.sin(n)*t)}function ft(n,t,e){return this instanceof ft?(this.l=+n,this.a=+t,void(this.b=+e)):arguments.length<2?n instanceof ft?new ft(n.l,n.a,n.b):n instanceof ct?st(n.h,n.c,n.l):wt((n=mt(n)).r,n.g,n.b):new ft(n,t,e)}function ht(n,t,e){var r=(n+16)/116,u=r+t/500,i=r-e/200;return u=pt(u)*Ka,r=pt(r)*Qa,i=pt(i)*nl,new mt(dt(3.2404542*u-1.5371385*r-.4985314*i),dt(-.969266*u+1.8760108*r+.041556*i),dt(.0556434*u-.2040259*r+1.0572252*i))}function gt(n,t,e){return n>0?new ct(Math.atan2(e,t)*Ia,Math.sqrt(t*t+e*e),n):new ct(0/0,0/0,n)}function pt(n){return n>.206893034?n*n*n:(n-4/29)/7.787037}function vt(n){return n>.008856?Math.pow(n,1/3):7.787037*n+4/29}function dt(n){return Math.round(255*(.00304>=n?12.92*n:1.055*Math.pow(n,1/2.4)-.055))}function mt(n,t,e){return this instanceof mt?(this.r=~~n,this.g=~~t,void(this.b=~~e)):arguments.length<2?n instanceof mt?new mt(n.r,n.g,n.b):bt(""+n,mt,lt):new mt(n,t,e)}function yt(n){return new mt(n>>16,n>>8&255,255&n)}function Mt(n){return yt(n)+""}function xt(n){return 16>n?"0"+Math.max(0,n).toString(16):Math.min(255,n).toString(16)}function bt(n,t,e){var r,u,i,o=0,a=0,l=0;if(r=/([a-z]+)\((.*)\)/.exec(n=n.toLowerCase()))switch(u=r[2].split(","),r[1]){case"hsl":return e(parseFloat(u[0]),parseFloat(u[1])/100,parseFloat(u[2])/100);case"rgb":return t(kt(u[0]),kt(u[1]),kt(u[2]))}return(i=rl.get(n))?t(i.r,i.g,i.b):(null==n||"#"!==n.charAt(0)||isNaN(i=parseInt(n.slice(1),16))||(4===n.length?(o=(3840&i)>>4,o=o>>4|o,a=240&i,a=a>>4|a,l=15&i,l=l<<4|l):7===n.length&&(o=(16711680&i)>>16,a=(65280&i)>>8,l=255&i)),t(o,a,l))}function _t(n,t,e){var r,u,i=Math.min(n/=255,t/=255,e/=255),o=Math.max(n,t,e),a=o-i,l=(o+i)/2;return a?(u=.5>l?a/(o+i):a/(2-o-i),r=n==o?(t-e)/a+(e>t?6:0):t==o?(e-n)/a+2:(n-t)/a+4,r*=60):(r=0/0,u=l>0&&1>l?0:r),new at(r,u,l)}function wt(n,t,e){n=St(n),t=St(t),e=St(e);var r=vt((.4124564*n+.3575761*t+.1804375*e)/Ka),u=vt((.2126729*n+.7151522*t+.072175*e)/Qa),i=vt((.0193339*n+.119192*t+.9503041*e)/nl);return ft(116*u-16,500*(r-u),200*(u-i))}function St(n){return(n/=255)<=.04045?n/12.92:Math.pow((n+.055)/1.055,2.4)}function kt(n){var t=parseFloat(n);return"%"===n.charAt(n.length-1)?Math.round(2.55*t):t}function Et(n){return"function"==typeof n?n:function(){return n}}function At(n){return function(t,e,r){return 2===arguments.length&&"function"==typeof e&&(r=e,e=null),Nt(t,e,n,r)}}function Nt(n,t,e,r){function u(){var n,t=l.status;if(!t&&zt(l)||t>=200&&300>t||304===t){try{n=e.call(i,l)}catch(r){return void o.error.call(i,r)}o.load.call(i,n)}else o.error.call(i,l)}var i={},o=aa.dispatch("beforesend","progress","load","error"),a={},l=new XMLHttpRequest,c=null;return!this.XDomainRequest||"withCredentials"in l||!/^(http(s)?:)?\/\//.test(n)||(l=new XDomainRequest),"onload"in l?l.onload=l.onerror=u:l.onreadystatechange=function(){l.readyState>3&&u()},l.onprogress=function(n){var t=aa.event;aa.event=n;try{o.progress.call(i,l)}finally{aa.event=t}},i.header=function(n,t){return n=(n+"").toLowerCase(),arguments.length<2?a[n]:(null==t?delete a[n]:a[n]=t+"",i)},i.mimeType=function(n){return arguments.length?(t=null==n?null:n+"",i):t},i.responseType=function(n){return arguments.length?(c=n,i):c},i.response=function(n){return e=n,i},["get","post"].forEach(function(n){i[n]=function(){return i.send.apply(i,[n].concat(ca(arguments)))}}),i.send=function(e,r,u){if(2===arguments.length&&"function"==typeof r&&(u=r,r=null),l.open(e,n,!0),null==t||"accept"in a||(a.accept=t+",*/*"),l.setRequestHeader)for(var s in a)l.setRequestHeader(s,a[s]);return null!=t&&l.overrideMimeType&&l.overrideMimeType(t),null!=c&&(l.responseType=c),null!=u&&i.on("error",u).on("load",function(n){u(null,n)}),o.beforesend.call(i,l),l.send(null==r?null:r),i},i.abort=function(){return l.abort(),i},aa.rebind(i,o,"on"),null==r?i:i.get(Ct(r))}function Ct(n){return 1===n.length?function(t,e){n(null==t?e:null)}:n}function zt(n){var t=n.responseType;return t&&"text"!==t?n.response:n.responseText}function Lt(n,t,e){var r=arguments.length;2>r&&(t=0),3>r&&(e=Date.now());var u=e+t,i={c:n,t:u,n:null};return il?il.n=i:ul=i,il=i,ol||(al=clearTimeout(al),ol=1,ll(qt)),i}function qt(){var n=Tt(),t=Rt()-n;t>24?(isFinite(t)&&(clearTimeout(al),al=setTimeout(qt,t)),ol=0):(ol=1,ll(qt))}function Tt(){for(var n=Date.now(),t=ul;t;)n>=t.t&&t.c(n-t.t)&&(t.c=null),t=t.n;return n}function Rt(){for(var n,t=ul,e=1/0;t;)t.c?(t.t8?function(n){return n/e}:function(n){return n*e},symbol:n}}function jt(n){var t=n.decimal,e=n.thousands,r=n.grouping,u=n.currency,i=r&&e?function(n,t){for(var u=n.length,i=[],o=0,a=r[0],l=0;u>0&&a>0&&(l+a+1>t&&(a=Math.max(1,t-l)),i.push(n.substring(u-=a,u+a)),!((l+=a+1)>t));)a=r[o=(o+1)%r.length];return i.reverse().join(e)}:y;return function(n){var e=sl.exec(n),r=e[1]||" ",o=e[2]||">",a=e[3]||"-",l=e[4]||"",c=e[5],s=+e[6],f=e[7],h=e[8],g=e[9],p=1,v="",d="",m=!1,y=!0;switch(h&&(h=+h.substring(1)),(c||"0"===r&&"="===o)&&(c=r="0",o="="),g){case"n":f=!0,g="g";break;case"%":p=100,d="%",g="f";break;case"p":p=100,d="%",g="r";break;case"b":case"o":case"x":case"X":"#"===l&&(v="0"+g.toLowerCase());case"c":y=!1;case"d":m=!0,h=0;break;case"s":p=-1,g="r"}"$"===l&&(v=u[0],d=u[1]),"r"!=g||h||(g="g"),null!=h&&("g"==g?h=Math.max(1,Math.min(21,h)):("e"==g||"f"==g)&&(h=Math.max(0,Math.min(20,h)))),g=fl.get(g)||Ut;var M=c&&f;return function(n){var e=d;if(m&&n%1)return"";var u=0>n||0===n&&0>1/n?(n=-n,"-"):"-"===a?"":a;if(0>p){var l=aa.formatPrefix(n,h);n=l.scale(n),e=l.symbol+d}else n*=p;n=g(n,h);var x,b,_=n.lastIndexOf(".");if(0>_){var w=y?n.lastIndexOf("e"):-1;0>w?(x=n,b=""):(x=n.substring(0,w),b=n.substring(w))}else x=n.substring(0,_),b=t+n.substring(_+1);!c&&f&&(x=i(x,1/0));var S=v.length+x.length+b.length+(M?0:u.length),k=s>S?new Array(S=s-S+1).join(r):"";return M&&(x=i(k+x,k.length?s-b.length:1/0)),u+=v,n=x+b,("<"===o?u+n+k:">"===o?k+u+n:"^"===o?k.substring(0,S>>=1)+u+n+k.substring(S):u+(M?n:k+n))+e}}}function Ut(n){return n+""}function Ft(){this._=new Date(arguments.length>1?Date.UTC.apply(this,arguments):arguments[0])}function Ht(n,t,e){function r(t){var e=n(t),r=i(e,1);return r-t>t-e?e:r}function u(e){return t(e=n(new gl(e-1)),1),e}function i(n,e){return t(n=new gl(+n),e),n}function o(n,r,i){var o=u(n),a=[];if(i>1)for(;r>o;)e(o)%i||a.push(new Date(+o)),t(o,1);else for(;r>o;)a.push(new Date(+o)),t(o,1);return a}function a(n,t,e){try{gl=Ft;var r=new Ft;return r._=n,o(r,t,e)}finally{gl=Date}}n.floor=n,n.round=r,n.ceil=u,n.offset=i,n.range=o;var l=n.utc=Ot(n);return l.floor=l,l.round=Ot(r),l.ceil=Ot(u),l.offset=Ot(i),l.range=a,n}function Ot(n){return function(t,e){try{gl=Ft;var r=new Ft;return r._=t,n(r,e)._}finally{gl=Date}}}function It(n){function t(n){function t(t){for(var e,u,i,o=[],a=-1,l=0;++aa;){if(r>=c)return-1;if(u=t.charCodeAt(a++),37===u){if(o=t.charAt(a++),i=C[o in vl?t.charAt(a++):o],!i||(r=i(n,e,r))<0)return-1}else if(u!=e.charCodeAt(r++))return-1}return r}function r(n,t,e){_.lastIndex=0;var r=_.exec(t.slice(e));return r?(n.w=w.get(r[0].toLowerCase()),e+r[0].length):-1}function u(n,t,e){x.lastIndex=0;var r=x.exec(t.slice(e));return r?(n.w=b.get(r[0].toLowerCase()),e+r[0].length):-1}function i(n,t,e){E.lastIndex=0;var r=E.exec(t.slice(e));return r?(n.m=A.get(r[0].toLowerCase()),e+r[0].length):-1}function o(n,t,e){S.lastIndex=0;var r=S.exec(t.slice(e));return r?(n.m=k.get(r[0].toLowerCase()),e+r[0].length):-1}function a(n,t,r){return e(n,N.c.toString(),t,r)}function l(n,t,r){return e(n,N.x.toString(),t,r)}function c(n,t,r){return e(n,N.X.toString(),t,r)}function s(n,t,e){var r=M.get(t.slice(e,e+=2).toLowerCase());return null==r?-1:(n.p=r,e)}var f=n.dateTime,h=n.date,g=n.time,p=n.periods,v=n.days,d=n.shortDays,m=n.months,y=n.shortMonths;t.utc=function(n){function e(n){try{gl=Ft;var t=new gl;return t._=n,r(t)}finally{gl=Date}}var r=t(n);return e.parse=function(n){try{gl=Ft;var t=r.parse(n);return t&&t._}finally{gl=Date}},e.toString=r.toString,e},t.multi=t.utc.multi=le;var M=aa.map(),x=Zt(v),b=Vt(v),_=Zt(d),w=Vt(d),S=Zt(m),k=Vt(m),E=Zt(y),A=Vt(y);p.forEach(function(n,t){M.set(n.toLowerCase(),t)});var N={a:function(n){return d[n.getDay()]},A:function(n){return v[n.getDay()]},b:function(n){return y[n.getMonth()]},B:function(n){return m[n.getMonth()]},c:t(f),d:function(n,t){return Yt(n.getDate(),t,2)},e:function(n,t){return Yt(n.getDate(),t,2)},H:function(n,t){return Yt(n.getHours(),t,2)},I:function(n,t){return Yt(n.getHours()%12||12,t,2)},j:function(n,t){return Yt(1+hl.dayOfYear(n),t,3)},L:function(n,t){return Yt(n.getMilliseconds(),t,3)},m:function(n,t){return Yt(n.getMonth()+1,t,2)},M:function(n,t){return Yt(n.getMinutes(),t,2)},p:function(n){return p[+(n.getHours()>=12)]},S:function(n,t){return Yt(n.getSeconds(),t,2)},U:function(n,t){return Yt(hl.sundayOfYear(n),t,2)},w:function(n){return n.getDay()},W:function(n,t){return Yt(hl.mondayOfYear(n),t,2)},x:t(h),X:t(g),y:function(n,t){return Yt(n.getFullYear()%100,t,2)},Y:function(n,t){return Yt(n.getFullYear()%1e4,t,4)},Z:oe,"%":function(){return"%"}},C={a:r,A:u,b:i,B:o,c:a,d:ne,e:ne,H:ee,I:ee,j:te,L:ie,m:Qt,M:re,p:s,S:ue,U:$t,w:Xt,W:Bt,x:l,X:c,y:Jt,Y:Wt,Z:Gt,"%":ae};return t}function Yt(n,t,e){var r=0>n?"-":"",u=(r?-n:n)+"",i=u.length;return r+(e>i?new Array(e-i+1).join(t)+u:u)}function Zt(n){return new RegExp("^(?:"+n.map(aa.requote).join("|")+")","i")}function Vt(n){for(var t=new c,e=-1,r=n.length;++e68?1900:2e3)}function Qt(n,t,e){dl.lastIndex=0;var r=dl.exec(t.slice(e,e+2));return r?(n.m=r[0]-1,e+r[0].length):-1}function ne(n,t,e){dl.lastIndex=0;var r=dl.exec(t.slice(e,e+2));return r?(n.d=+r[0],e+r[0].length):-1}function te(n,t,e){dl.lastIndex=0;var r=dl.exec(t.slice(e,e+3));return r?(n.j=+r[0],e+r[0].length):-1}function ee(n,t,e){dl.lastIndex=0;var r=dl.exec(t.slice(e,e+2));return r?(n.H=+r[0],e+r[0].length):-1}function re(n,t,e){dl.lastIndex=0;var r=dl.exec(t.slice(e,e+2));return r?(n.M=+r[0],e+r[0].length):-1}function ue(n,t,e){dl.lastIndex=0;var r=dl.exec(t.slice(e,e+2));return r?(n.S=+r[0],e+r[0].length):-1}function ie(n,t,e){dl.lastIndex=0;var r=dl.exec(t.slice(e,e+3));return r?(n.L=+r[0],e+r[0].length):-1}function oe(n){var t=n.getTimezoneOffset(),e=t>0?"-":"+",r=Ma(t)/60|0,u=Ma(t)%60;return e+Yt(r,"0",2)+Yt(u,"0",2)}function ae(n,t,e){ml.lastIndex=0;var r=ml.exec(t.slice(e,e+1));return r?e+r[0].length:-1}function le(n){for(var t=n.length,e=-1;++e=0?1:-1,a=o*e,l=Math.cos(t),c=Math.sin(t),s=i*c,f=u*l+s*Math.cos(a),h=s*o*Math.sin(a);wl.add(Math.atan2(h,f)),r=n,u=l,i=c}var t,e,r,u,i;Sl.point=function(o,a){Sl.point=n,r=(t=o)*Oa,u=Math.cos(a=(e=a)*Oa/2+ja/4),i=Math.sin(a)},Sl.lineEnd=function(){n(t,e)}}function ve(n){var t=n[0],e=n[1],r=Math.cos(e);return[r*Math.cos(t),r*Math.sin(t),Math.sin(e)]}function de(n,t){return n[0]*t[0]+n[1]*t[1]+n[2]*t[2]}function me(n,t){return[n[1]*t[2]-n[2]*t[1],n[2]*t[0]-n[0]*t[2],n[0]*t[1]-n[1]*t[0]]}function ye(n,t){n[0]+=t[0],n[1]+=t[1],n[2]+=t[2]}function Me(n,t){return[n[0]*t,n[1]*t,n[2]*t]}function xe(n){var t=Math.sqrt(n[0]*n[0]+n[1]*n[1]+n[2]*n[2]);n[0]/=t,n[1]/=t,n[2]/=t}function be(n){return[Math.atan2(n[1],n[0]),tt(n[2])]}function _e(n,t){return Ma(n[0]-t[0])a;++a)u.point((e=n[a])[0],e[1]);return void u.lineEnd()}var l=new qe(e,n,null,!0),c=new qe(e,null,l,!1);l.o=c,i.push(l),o.push(c),l=new qe(r,n,null,!1),c=new qe(r,null,l,!0),l.o=c,i.push(l),o.push(c)}}),o.sort(t),Le(i),Le(o),i.length){for(var a=0,l=e,c=o.length;c>a;++a)o[a].e=l=!l;for(var s,f,h=i[0];;){for(var g=h,p=!0;g.v;)if((g=g.n)===h)return;s=g.z,u.lineStart();do{if(g.v=g.o.v=!0,g.e){if(p)for(var a=0,c=s.length;c>a;++a)u.point((f=s[a])[0],f[1]);else r(g.x,g.n.x,1,u);g=g.n}else{if(p){s=g.p.z;for(var a=s.length-1;a>=0;--a)u.point((f=s[a])[0],f[1])}else r(g.x,g.p.x,-1,u);g=g.p}g=g.o,s=g.z,p=!p}while(!g.v);u.lineEnd()}}}function Le(n){if(t=n.length){for(var t,e,r=0,u=n[0];++r0){for(b||(i.polygonStart(),b=!0),i.lineStart();++o1&&2&t&&e.push(e.pop().concat(e.shift())),g.push(e.filter(Re))}var g,p,v,d=t(i),m=u.invert(r[0],r[1]),y={point:o,lineStart:l,lineEnd:c,polygonStart:function(){y.point=s,y.lineStart=f,y.lineEnd=h,g=[],p=[]},polygonEnd:function(){y.point=o,y.lineStart=l,y.lineEnd=c,g=aa.merge(g);var n=He(m,p);g.length?(b||(i.polygonStart(),b=!0),ze(g,Pe,n,e,i)):n&&(b||(i.polygonStart(),b=!0),i.lineStart(),e(null,null,1,i),i.lineEnd()),b&&(i.polygonEnd(),b=!1),g=p=null},sphere:function(){i.polygonStart(),i.lineStart(),e(null,null,1,i),i.lineEnd(),i.polygonEnd()}},M=De(),x=t(M),b=!1;return y}}function Re(n){return n.length>1}function De(){var n,t=[];return{lineStart:function(){t.push(n=[])},point:function(t,e){n.push([t,e])},lineEnd:b,buffer:function(){var e=t;return t=[],n=null,e},rejoin:function(){t.length>1&&t.push(t.pop().concat(t.shift()))}}}function Pe(n,t){return((n=n.x)[0]<0?n[1]-Ha-Da:Ha-n[1])-((t=t.x)[0]<0?t[1]-Ha-Da:Ha-t[1])}function je(n){var t,e=0/0,r=0/0,u=0/0;return{lineStart:function(){n.lineStart(),t=1},point:function(i,o){var a=i>0?ja:-ja,l=Ma(i-e);Ma(l-ja)0?Ha:-Ha),n.point(u,r),n.lineEnd(),n.lineStart(),n.point(a,r),n.point(i,r),t=0):u!==a&&l>=ja&&(Ma(e-u)Da?Math.atan((Math.sin(t)*(i=Math.cos(r))*Math.sin(e)-Math.sin(r)*(u=Math.cos(t))*Math.sin(n))/(u*i*o)):(t+r)/2}function Fe(n,t,e,r){var u;if(null==n)u=e*Ha,r.point(-ja,u),r.point(0,u),r.point(ja,u),r.point(ja,0),r.point(ja,-u),r.point(0,-u),r.point(-ja,-u),r.point(-ja,0),r.point(-ja,u);else if(Ma(n[0]-t[0])>Da){var i=n[0]a;++a){var c=t[a],s=c.length;if(s)for(var f=c[0],h=f[0],g=f[1]/2+ja/4,p=Math.sin(g),v=Math.cos(g),d=1;;){d===s&&(d=0),n=c[d];var m=n[0],y=n[1]/2+ja/4,M=Math.sin(y),x=Math.cos(y),b=m-h,_=b>=0?1:-1,w=_*b,S=w>ja,k=p*M;if(wl.add(Math.atan2(k*_*Math.sin(w),v*x+k*Math.cos(w))),i+=S?b+_*Ua:b,S^h>=e^m>=e){var E=me(ve(f),ve(n));xe(E);var A=me(u,E);xe(A);var N=(S^b>=0?-1:1)*tt(A[2]);(r>N||r===N&&(E[0]||E[1]))&&(o+=S^b>=0?1:-1)}if(!d++)break;h=m,p=M,v=x,f=n}}return(-Da>i||Da>i&&0>wl)^1&o}function Oe(n){function t(n,t){return Math.cos(n)*Math.cos(t)>i}function e(n){var e,i,l,c,s;return{lineStart:function(){c=l=!1,s=1},point:function(f,h){var g,p=[f,h],v=t(f,h),d=o?v?0:u(f,h):v?u(f+(0>f?ja:-ja),h):0;if(!e&&(c=l=v)&&n.lineStart(),v!==l&&(g=r(e,p),(_e(e,g)||_e(p,g))&&(p[0]+=Da,p[1]+=Da,v=t(p[0],p[1]))),v!==l)s=0,v?(n.lineStart(),g=r(p,e),n.point(g[0],g[1])):(g=r(e,p),n.point(g[0],g[1]),n.lineEnd()),e=g;else if(a&&e&&o^v){var m;d&i||!(m=r(p,e,!0))||(s=0,o?(n.lineStart(),n.point(m[0][0],m[0][1]),n.point(m[1][0],m[1][1]),n.lineEnd()):(n.point(m[1][0],m[1][1]),n.lineEnd(),n.lineStart(),n.point(m[0][0],m[0][1])))}!v||e&&_e(e,p)||n.point(p[0],p[1]),e=p,l=v,i=d},lineEnd:function(){l&&n.lineEnd(),e=null},clean:function(){return s|(c&&l)<<1}}}function r(n,t,e){var r=ve(n),u=ve(t),o=[1,0,0],a=me(r,u),l=de(a,a),c=a[0],s=l-c*c;if(!s)return!e&&n;var f=i*l/s,h=-i*c/s,g=me(o,a),p=Me(o,f),v=Me(a,h);ye(p,v);var d=g,m=de(p,d),y=de(d,d),M=m*m-y*(de(p,p)-1);if(!(0>M)){var x=Math.sqrt(M),b=Me(d,(-m-x)/y);if(ye(b,p),b=be(b),!e)return b;var _,w=n[0],S=t[0],k=n[1],E=t[1];w>S&&(_=w,w=S,S=_);var A=S-w,N=Ma(A-ja)A;if(!N&&k>E&&(_=k,k=E,E=_),C?N?k+E>0^b[1]<(Ma(b[0]-w)ja^(w<=b[0]&&b[0]<=S)){var z=Me(d,(-m+x)/y);return ye(z,p),[b,be(z)]}}}function u(t,e){var r=o?n:ja-n,u=0;return-r>t?u|=1:t>r&&(u|=2),-r>e?u|=4:e>r&&(u|=8),u}var i=Math.cos(n),o=i>0,a=Ma(i)>Da,l=pr(n,6*Oa);return Te(t,e,l,o?[0,-n]:[-ja,n-ja])}function Ie(n,t,e,r){return function(u){var i,o=u.a,a=u.b,l=o.x,c=o.y,s=a.x,f=a.y,h=0,g=1,p=s-l,v=f-c;if(i=n-l,p||!(i>0)){if(i/=p,0>p){if(h>i)return;g>i&&(g=i)}else if(p>0){if(i>g)return;i>h&&(h=i)}if(i=e-l,p||!(0>i)){if(i/=p,0>p){if(i>g)return;i>h&&(h=i)}else if(p>0){if(h>i)return;g>i&&(g=i)}if(i=t-c,v||!(i>0)){if(i/=v,0>v){if(h>i)return;g>i&&(g=i)}else if(v>0){if(i>g)return;i>h&&(h=i)}if(i=r-c,v||!(0>i)){if(i/=v,0>v){if(i>g)return;i>h&&(h=i)}else if(v>0){if(h>i)return;g>i&&(g=i)}return h>0&&(u.a={x:l+h*p,y:c+h*v}),1>g&&(u.b={x:l+g*p,y:c+g*v}),u}}}}}}function Ye(n,t,e,r){function u(r,u){return Ma(r[0]-n)0?0:3:Ma(r[0]-e)0?2:1:Ma(r[1]-t)0?1:0:u>0?3:2}function i(n,t){return o(n.x,t.x)}function o(n,t){var e=u(n,1),r=u(t,1);return e!==r?e-r:0===e?t[1]-n[1]:1===e?n[0]-t[0]:2===e?n[1]-t[1]:t[0]-n[0]}return function(a){function l(n){for(var t=0,e=d.length,r=n[1],u=0;e>u;++u)for(var i,o=1,a=d[u],l=a.length,c=a[0];l>o;++o)i=a[o],c[1]<=r?i[1]>r&&Q(c,i,n)>0&&++t:i[1]<=r&&Q(c,i,n)<0&&--t,c=i;return 0!==t}function c(i,a,l,c){var s=0,f=0;if(null==i||(s=u(i,l))!==(f=u(a,l))||o(i,a)<0^l>0){do c.point(0===s||3===s?n:e,s>1?r:t);while((s=(s+l+4)%4)!==f)}else c.point(a[0],a[1])}function s(u,i){return u>=n&&e>=u&&i>=t&&r>=i}function f(n,t){s(n,t)&&a.point(n,t)}function h(){C.point=p,d&&d.push(m=[]),S=!0,w=!1,b=_=0/0}function g(){v&&(p(y,M),x&&w&&A.rejoin(),v.push(A.buffer())),C.point=f,w&&a.lineEnd()}function p(n,t){n=Math.max(-Ul,Math.min(Ul,n)),t=Math.max(-Ul,Math.min(Ul,t));var e=s(n,t);if(d&&m.push([n,t]),S)y=n,M=t,x=e,S=!1,e&&(a.lineStart(),a.point(n,t));else if(e&&w)a.point(n,t);else{var r={a:{x:b,y:_},b:{x:n,y:t}};N(r)?(w||(a.lineStart(),a.point(r.a.x,r.a.y)),a.point(r.b.x,r.b.y),e||a.lineEnd(),k=!1):e&&(a.lineStart(),a.point(n,t),k=!1)}b=n,_=t,w=e}var v,d,m,y,M,x,b,_,w,S,k,E=a,A=De(),N=Ie(n,t,e,r),C={point:f,lineStart:h,lineEnd:g,polygonStart:function(){a=A,v=[],d=[],k=!0},polygonEnd:function(){a=E,v=aa.merge(v);var t=l([n,r]),e=k&&t,u=v.length;(e||u)&&(a.polygonStart(),e&&(a.lineStart(),c(null,null,1,a),a.lineEnd()),u&&ze(v,i,t,c,a),a.polygonEnd()),v=d=m=null}};return C}}function Ze(n){var t=0,e=ja/3,r=or(n),u=r(t,e);return u.parallels=function(n){return arguments.length?r(t=n[0]*ja/180,e=n[1]*ja/180):[t/ja*180,e/ja*180]},u}function Ve(n,t){function e(n,t){var e=Math.sqrt(i-2*u*Math.sin(t))/u;return[e*Math.sin(n*=u),o-e*Math.cos(n)]}var r=Math.sin(n),u=(r+Math.sin(t))/2,i=1+r*(2*u-r),o=Math.sqrt(i)/u;return e.invert=function(n,t){var e=o-t;return[Math.atan2(n,e)/u,tt((i-(n*n+e*e)*u*u)/(2*u))]},e}function Xe(){function n(n,t){Hl+=u*n-r*t,r=n,u=t}var t,e,r,u;Vl.point=function(i,o){Vl.point=n,t=r=i,e=u=o},Vl.lineEnd=function(){n(t,e)}}function $e(n,t){Ol>n&&(Ol=n),n>Yl&&(Yl=n),Il>t&&(Il=t),t>Zl&&(Zl=t)}function Be(){function n(n,t){o.push("M",n,",",t,i)}function t(n,t){o.push("M",n,",",t),a.point=e}function e(n,t){o.push("L",n,",",t)}function r(){a.point=n}function u(){o.push("Z")}var i=We(4.5),o=[],a={point:n,lineStart:function(){a.point=t},lineEnd:r,polygonStart:function(){a.lineEnd=u},polygonEnd:function(){a.lineEnd=r,a.point=n},pointRadius:function(n){return i=We(n),a},result:function(){if(o.length){var n=o.join("");return o=[],n}}};return a}function We(n){return"m0,"+n+"a"+n+","+n+" 0 1,1 0,"+-2*n+"a"+n+","+n+" 0 1,1 0,"+2*n+"z"}function Je(n,t){Al+=n,Nl+=t,++Cl}function Ge(){function n(n,r){var u=n-t,i=r-e,o=Math.sqrt(u*u+i*i);zl+=o*(t+n)/2,Ll+=o*(e+r)/2,ql+=o,Je(t=n,e=r)}var t,e;$l.point=function(r,u){$l.point=n,Je(t=r,e=u)}}function Ke(){$l.point=Je}function Qe(){function n(n,t){var e=n-r,i=t-u,o=Math.sqrt(e*e+i*i);zl+=o*(r+n)/2,Ll+=o*(u+t)/2,ql+=o,o=u*n-r*t,Tl+=o*(r+n),Rl+=o*(u+t),Dl+=3*o,Je(r=n,u=t)}var t,e,r,u;$l.point=function(i,o){$l.point=n,Je(t=r=i,e=u=o)},$l.lineEnd=function(){n(t,e)}}function nr(n){function t(t,e){n.moveTo(t+o,e),n.arc(t,e,o,0,Ua)}function e(t,e){n.moveTo(t,e),a.point=r}function r(t,e){n.lineTo(t,e)}function u(){a.point=t}function i(){n.closePath()}var o=4.5,a={point:t,lineStart:function(){a.point=e},lineEnd:u,polygonStart:function(){a.lineEnd=i},polygonEnd:function(){a.lineEnd=u,a.point=t},pointRadius:function(n){return o=n,a},result:b};return a}function tr(n){function t(n){return(a?r:e)(n)}function e(t){return ur(t,function(e,r){e=n(e,r),t.point(e[0],e[1])})}function r(t){function e(e,r){e=n(e,r),t.point(e[0],e[1])}function r(){M=0/0,S.point=i,t.lineStart()}function i(e,r){var i=ve([e,r]),o=n(e,r);u(M,x,y,b,_,w,M=o[0],x=o[1],y=e,b=i[0],_=i[1],w=i[2],a,t),t.point(M,x)}function o(){S.point=e,t.lineEnd()}function l(){r(),S.point=c,S.lineEnd=s}function c(n,t){i(f=n,h=t),g=M,p=x,v=b,d=_,m=w,S.point=i +}function s(){u(M,x,y,b,_,w,g,p,f,v,d,m,a,t),S.lineEnd=o,o()}var f,h,g,p,v,d,m,y,M,x,b,_,w,S={point:e,lineStart:r,lineEnd:o,polygonStart:function(){t.polygonStart(),S.lineStart=l},polygonEnd:function(){t.polygonEnd(),S.lineStart=r}};return S}function u(t,e,r,a,l,c,s,f,h,g,p,v,d,m){var y=s-t,M=f-e,x=y*y+M*M;if(x>4*i&&d--){var b=a+g,_=l+p,w=c+v,S=Math.sqrt(b*b+_*_+w*w),k=Math.asin(w/=S),E=Ma(Ma(w)-1)i||Ma((y*z+M*L)/x-.5)>.3||o>a*g+l*p+c*v)&&(u(t,e,r,a,l,c,N,C,E,b/=S,_/=S,w,d,m),m.point(N,C),u(N,C,E,b,_,w,s,f,h,g,p,v,d,m))}}var i=.5,o=Math.cos(30*Oa),a=16;return t.precision=function(n){return arguments.length?(a=(i=n*n)>0&&16,t):Math.sqrt(i)},t}function er(n){var t=tr(function(t,e){return n([t*Ia,e*Ia])});return function(n){return ar(t(n))}}function rr(n){this.stream=n}function ur(n,t){return{point:t,sphere:function(){n.sphere()},lineStart:function(){n.lineStart()},lineEnd:function(){n.lineEnd()},polygonStart:function(){n.polygonStart()},polygonEnd:function(){n.polygonEnd()}}}function ir(n){return or(function(){return n})()}function or(n){function t(n){return n=a(n[0]*Oa,n[1]*Oa),[n[0]*h+l,c-n[1]*h]}function e(n){return n=a.invert((n[0]-l)/h,(c-n[1])/h),n&&[n[0]*Ia,n[1]*Ia]}function r(){a=Ne(o=sr(m,M,x),i);var n=i(v,d);return l=g-n[0]*h,c=p+n[1]*h,u()}function u(){return s&&(s.valid=!1,s=null),t}var i,o,a,l,c,s,f=tr(function(n,t){return n=i(n,t),[n[0]*h+l,c-n[1]*h]}),h=150,g=480,p=250,v=0,d=0,m=0,M=0,x=0,b=jl,_=y,w=null,S=null;return t.stream=function(n){return s&&(s.valid=!1),s=ar(b(o,f(_(n)))),s.valid=!0,s},t.clipAngle=function(n){return arguments.length?(b=null==n?(w=n,jl):Oe((w=+n)*Oa),u()):w},t.clipExtent=function(n){return arguments.length?(S=n,_=n?Ye(n[0][0],n[0][1],n[1][0],n[1][1]):y,u()):S},t.scale=function(n){return arguments.length?(h=+n,r()):h},t.translate=function(n){return arguments.length?(g=+n[0],p=+n[1],r()):[g,p]},t.center=function(n){return arguments.length?(v=n[0]%360*Oa,d=n[1]%360*Oa,r()):[v*Ia,d*Ia]},t.rotate=function(n){return arguments.length?(m=n[0]%360*Oa,M=n[1]%360*Oa,x=n.length>2?n[2]%360*Oa:0,r()):[m*Ia,M*Ia,x*Ia]},aa.rebind(t,f,"precision"),function(){return i=n.apply(this,arguments),t.invert=i.invert&&e,r()}}function ar(n){return ur(n,function(t,e){n.point(t*Oa,e*Oa)})}function lr(n,t){return[n,t]}function cr(n,t){return[n>ja?n-Ua:-ja>n?n+Ua:n,t]}function sr(n,t,e){return n?t||e?Ne(hr(n),gr(t,e)):hr(n):t||e?gr(t,e):cr}function fr(n){return function(t,e){return t+=n,[t>ja?t-Ua:-ja>t?t+Ua:t,e]}}function hr(n){var t=fr(n);return t.invert=fr(-n),t}function gr(n,t){function e(n,t){var e=Math.cos(t),a=Math.cos(n)*e,l=Math.sin(n)*e,c=Math.sin(t),s=c*r+a*u;return[Math.atan2(l*i-s*o,a*r-c*u),tt(s*i+l*o)]}var r=Math.cos(n),u=Math.sin(n),i=Math.cos(t),o=Math.sin(t);return e.invert=function(n,t){var e=Math.cos(t),a=Math.cos(n)*e,l=Math.sin(n)*e,c=Math.sin(t),s=c*i-l*o;return[Math.atan2(l*i+c*o,a*r+s*u),tt(s*r-a*u)]},e}function pr(n,t){var e=Math.cos(n),r=Math.sin(n);return function(u,i,o,a){var l=o*t;null!=u?(u=vr(e,u),i=vr(e,i),(o>0?i>u:u>i)&&(u+=o*Ua)):(u=n+o*Ua,i=n-.5*l);for(var c,s=u;o>0?s>i:i>s;s-=l)a.point((c=be([e,-r*Math.cos(s),-r*Math.sin(s)]))[0],c[1])}}function vr(n,t){var e=ve(t);e[0]-=n,xe(e);var r=nt(-e[1]);return((-e[2]<0?-r:r)+2*Math.PI-Da)%(2*Math.PI)}function dr(n,t,e){var r=aa.range(n,t-Da,e).concat(t);return function(n){return r.map(function(t){return[n,t]})}}function mr(n,t,e){var r=aa.range(n,t-Da,e).concat(t);return function(n){return r.map(function(t){return[t,n]})}}function yr(n){return n.source}function Mr(n){return n.target}function xr(n,t,e,r){var u=Math.cos(t),i=Math.sin(t),o=Math.cos(r),a=Math.sin(r),l=u*Math.cos(n),c=u*Math.sin(n),s=o*Math.cos(e),f=o*Math.sin(e),h=2*Math.asin(Math.sqrt(it(r-t)+u*o*it(e-n))),g=1/Math.sin(h),p=h?function(n){var t=Math.sin(n*=h)*g,e=Math.sin(h-n)*g,r=e*l+t*s,u=e*c+t*f,o=e*i+t*a;return[Math.atan2(u,r)*Ia,Math.atan2(o,Math.sqrt(r*r+u*u))*Ia]}:function(){return[n*Ia,t*Ia]};return p.distance=h,p}function br(){function n(n,u){var i=Math.sin(u*=Oa),o=Math.cos(u),a=Ma((n*=Oa)-t),l=Math.cos(a);Bl+=Math.atan2(Math.sqrt((a=o*Math.sin(a))*a+(a=r*i-e*o*l)*a),e*i+r*o*l),t=n,e=i,r=o}var t,e,r;Wl.point=function(u,i){t=u*Oa,e=Math.sin(i*=Oa),r=Math.cos(i),Wl.point=n},Wl.lineEnd=function(){Wl.point=Wl.lineEnd=b}}function _r(n,t){function e(t,e){var r=Math.cos(t),u=Math.cos(e),i=n(r*u);return[i*u*Math.sin(t),i*Math.sin(e)]}return e.invert=function(n,e){var r=Math.sqrt(n*n+e*e),u=t(r),i=Math.sin(u),o=Math.cos(u);return[Math.atan2(n*i,r*o),Math.asin(r&&e*i/r)]},e}function wr(n,t){function e(n,t){o>0?-Ha+Da>t&&(t=-Ha+Da):t>Ha-Da&&(t=Ha-Da);var e=o/Math.pow(u(t),i);return[e*Math.sin(i*n),o-e*Math.cos(i*n)]}var r=Math.cos(n),u=function(n){return Math.tan(ja/4+n/2)},i=n===t?Math.sin(n):Math.log(r/Math.cos(t))/Math.log(u(t)/u(n)),o=r*Math.pow(u(n),i)/i;return i?(e.invert=function(n,t){var e=o-t,r=K(i)*Math.sqrt(n*n+e*e);return[Math.atan2(n,e)/i,2*Math.atan(Math.pow(o/r,1/i))-Ha]},e):kr}function Sr(n,t){function e(n,t){var e=i-t;return[e*Math.sin(u*n),i-e*Math.cos(u*n)]}var r=Math.cos(n),u=n===t?Math.sin(n):(r-Math.cos(t))/(t-n),i=r/u+n;return Ma(u)u;u++){for(;r>1&&Q(n[e[r-2]],n[e[r-1]],n[u])<=0;)--r;e[r++]=u}return e.slice(0,r)}function Lr(n,t){return n[0]-t[0]||n[1]-t[1]}function qr(n,t,e){return(e[0]-t[0])*(n[1]-t[1])<(e[1]-t[1])*(n[0]-t[0])}function Tr(n,t,e,r){var u=n[0],i=e[0],o=t[0]-u,a=r[0]-i,l=n[1],c=e[1],s=t[1]-l,f=r[1]-c,h=(a*(l-c)-f*(u-i))/(f*o-a*s);return[u+h*o,l+h*s]}function Rr(n){var t=n[0],e=n[n.length-1];return!(t[0]-e[0]||t[1]-e[1])}function Dr(){eu(this),this.edge=this.site=this.circle=null}function Pr(n){var t=ac.pop()||new Dr;return t.site=n,t}function jr(n){$r(n),uc.remove(n),ac.push(n),eu(n)}function Ur(n){var t=n.circle,e=t.x,r=t.cy,u={x:e,y:r},i=n.P,o=n.N,a=[n];jr(n);for(var l=i;l.circle&&Ma(e-l.circle.x)s;++s)c=a[s],l=a[s-1],Qr(c.edge,l.site,c.site,u);l=a[0],c=a[f-1],c.edge=Gr(l.site,c.site,null,u),Xr(l),Xr(c)}function Fr(n){for(var t,e,r,u,i=n.x,o=n.y,a=uc._;a;)if(r=Hr(a,o)-i,r>Da)a=a.L;else{if(u=i-Or(a,o),!(u>Da)){r>-Da?(t=a.P,e=a):u>-Da?(t=a,e=a.N):t=e=a;break}if(!a.R){t=a;break}a=a.R}var l=Pr(n);if(uc.insert(t,l),t||e){if(t===e)return $r(t),e=Pr(t.site),uc.insert(l,e),l.edge=e.edge=Gr(t.site,l.site),Xr(t),void Xr(e);if(!e)return void(l.edge=Gr(t.site,l.site));$r(t),$r(e);var c=t.site,s=c.x,f=c.y,h=n.x-s,g=n.y-f,p=e.site,v=p.x-s,d=p.y-f,m=2*(h*d-g*v),y=h*h+g*g,M=v*v+d*d,x={x:(d*y-g*M)/m+s,y:(h*M-v*y)/m+f};Qr(e.edge,c,p,x),l.edge=Gr(c,n,null,x),e.edge=Gr(n,p,null,x),Xr(t),Xr(e)}}function Hr(n,t){var e=n.site,r=e.x,u=e.y,i=u-t;if(!i)return r;var o=n.P;if(!o)return-1/0;e=o.site;var a=e.x,l=e.y,c=l-t;if(!c)return a;var s=a-r,f=1/i-1/c,h=s/c;return f?(-h+Math.sqrt(h*h-2*f*(s*s/(-2*c)-l+c/2+u-i/2)))/f+r:(r+a)/2}function Or(n,t){var e=n.N;if(e)return Hr(e,t);var r=n.site;return r.y===t?r.x:1/0}function Ir(n){this.site=n,this.edges=[]}function Yr(n){for(var t,e,r,u,i,o,a,l,c,s,f=n[0][0],h=n[1][0],g=n[0][1],p=n[1][1],v=rc,d=v.length;d--;)if(i=v[d],i&&i.prepare())for(a=i.edges,l=a.length,o=0;l>o;)s=a[o].end(),r=s.x,u=s.y,c=a[++o%l].start(),t=c.x,e=c.y,(Ma(r-t)>Da||Ma(u-e)>Da)&&(a.splice(o,0,new nu(Kr(i.site,s,Ma(r-f)Da?{x:f,y:Ma(t-f)Da?{x:Ma(e-p)Da?{x:h,y:Ma(t-h)Da?{x:Ma(e-g)=-Pa)){var g=l*l+c*c,p=s*s+f*f,v=(f*g-c*p)/h,d=(l*p-s*g)/h,f=d+a,m=lc.pop()||new Vr;m.arc=n,m.site=u,m.x=v+o,m.y=f+Math.sqrt(v*v+d*d),m.cy=f,n.circle=m;for(var y=null,M=oc._;M;)if(m.yd||d>=a)return;if(h>p){if(i){if(i.y>=c)return}else i={x:d,y:l};e={x:d,y:c}}else{if(i){if(i.yr||r>1)if(h>p){if(i){if(i.y>=c)return}else i={x:(l-u)/r,y:l};e={x:(c-u)/r,y:c}}else{if(i){if(i.yg){if(i){if(i.x>=a)return}else i={x:o,y:r*o+u};e={x:a,y:r*a+u}}else{if(i){if(i.xi||f>o||r>h||u>g)){if(p=n.point){var p,v=t-n.x,d=e-n.y,m=v*v+d*d;if(l>m){var y=Math.sqrt(l=m);r=t-y,u=e-y,i=t+y,o=e+y,a=p}}for(var M=n.nodes,x=.5*(s+h),b=.5*(f+g),_=t>=x,w=e>=b,S=w<<1|_,k=S+4;k>S;++S)if(n=M[3&S])switch(3&S){case 0:c(n,s,f,x,b);break;case 1:c(n,x,f,h,b);break;case 2:c(n,s,b,x,g);break;case 3:c(n,x,b,h,g)}}}(n,r,u,i,o),a}function pu(n,t){n=aa.rgb(n),t=aa.rgb(t);var e=n.r,r=n.g,u=n.b,i=t.r-e,o=t.g-r,a=t.b-u;return function(n){return"#"+xt(Math.round(e+i*n))+xt(Math.round(r+o*n))+xt(Math.round(u+a*n))}}function vu(n,t){var e,r={},u={};for(e in n)e in t?r[e]=yu(n[e],t[e]):u[e]=n[e];for(e in t)e in n||(u[e]=t[e]);return function(n){for(e in r)u[e]=r[e](n);return u}}function du(n,t){return n=+n,t=+t,function(e){return n*(1-e)+t*e}}function mu(n,t){var e,r,u,i=sc.lastIndex=fc.lastIndex=0,o=-1,a=[],l=[];for(n+="",t+="";(e=sc.exec(n))&&(r=fc.exec(t));)(u=r.index)>i&&(u=t.slice(i,u),a[o]?a[o]+=u:a[++o]=u),(e=e[0])===(r=r[0])?a[o]?a[o]+=r:a[++o]=r:(a[++o]=null,l.push({i:o,x:du(e,r)})),i=fc.lastIndex;return ir;++r)a[(e=l[r]).i]=e.x(n);return a.join("")})}function yu(n,t){for(var e,r=aa.interpolators.length;--r>=0&&!(e=aa.interpolators[r](n,t)););return e}function Mu(n,t){var e,r=[],u=[],i=n.length,o=t.length,a=Math.min(n.length,t.length);for(e=0;a>e;++e)r.push(yu(n[e],t[e]));for(;i>e;++e)u[e]=n[e];for(;o>e;++e)u[e]=t[e];return function(n){for(e=0;a>e;++e)u[e]=r[e](n);return u}}function xu(n){return function(t){return 0>=t?0:t>=1?1:n(t)}}function bu(n){return function(t){return 1-n(1-t)}}function _u(n){return function(t){return.5*(.5>t?n(2*t):2-n(2-2*t))}}function wu(n){return n*n}function Su(n){return n*n*n}function ku(n){if(0>=n)return 0;if(n>=1)return 1;var t=n*n,e=t*n;return 4*(.5>n?e:3*(n-t)+e-.75)}function Eu(n){return function(t){return Math.pow(t,n)}}function Au(n){return 1-Math.cos(n*Ha)}function Nu(n){return Math.pow(2,10*(n-1))}function Cu(n){return 1-Math.sqrt(1-n*n)}function zu(n,t){var e;return arguments.length<2&&(t=.45),arguments.length?e=t/Ua*Math.asin(1/n):(n=1,e=t/4),function(r){return 1+n*Math.pow(2,-10*r)*Math.sin((r-e)*Ua/t)}}function Lu(n){return n||(n=1.70158),function(t){return t*t*((n+1)*t-n)}}function qu(n){return 1/2.75>n?7.5625*n*n:2/2.75>n?7.5625*(n-=1.5/2.75)*n+.75:2.5/2.75>n?7.5625*(n-=2.25/2.75)*n+.9375:7.5625*(n-=2.625/2.75)*n+.984375}function Tu(n,t){n=aa.hcl(n),t=aa.hcl(t);var e=n.h,r=n.c,u=n.l,i=t.h-e,o=t.c-r,a=t.l-u;return isNaN(o)&&(o=0,r=isNaN(r)?t.c:r),isNaN(i)?(i=0,e=isNaN(e)?t.h:e):i>180?i-=360:-180>i&&(i+=360),function(n){return st(e+i*n,r+o*n,u+a*n)+""}}function Ru(n,t){n=aa.hsl(n),t=aa.hsl(t);var e=n.h,r=n.s,u=n.l,i=t.h-e,o=t.s-r,a=t.l-u;return isNaN(o)&&(o=0,r=isNaN(r)?t.s:r),isNaN(i)?(i=0,e=isNaN(e)?t.h:e):i>180?i-=360:-180>i&&(i+=360),function(n){return lt(e+i*n,r+o*n,u+a*n)+""}}function Du(n,t){n=aa.lab(n),t=aa.lab(t);var e=n.l,r=n.a,u=n.b,i=t.l-e,o=t.a-r,a=t.b-u;return function(n){return ht(e+i*n,r+o*n,u+a*n)+""}}function Pu(n,t){return t-=n,function(e){return Math.round(n+t*e)}}function ju(n){var t=[n.a,n.b],e=[n.c,n.d],r=Fu(t),u=Uu(t,e),i=Fu(Hu(e,t,-u))||0;t[0]*e[1]180?t+=360:t-n>180&&(n+=360),r.push({i:e.push(Ou(e)+"rotate(",null,")")-2,x:du(n,t)})):t&&e.push(Ou(e)+"rotate("+t+")")}function Zu(n,t,e,r){n!==t?r.push({i:e.push(Ou(e)+"skewX(",null,")")-2,x:du(n,t)}):t&&e.push(Ou(e)+"skewX("+t+")")}function Vu(n,t,e,r){if(n[0]!==t[0]||n[1]!==t[1]){var u=e.push(Ou(e)+"scale(",null,",",null,")");r.push({i:u-4,x:du(n[0],t[0])},{i:u-2,x:du(n[1],t[1])})}else(1!==t[0]||1!==t[1])&&e.push(Ou(e)+"scale("+t+")")}function Xu(n,t){var e=[],r=[];return n=aa.transform(n),t=aa.transform(t),Iu(n.translate,t.translate,e,r),Yu(n.rotate,t.rotate,e,r),Zu(n.skew,t.skew,e,r),Vu(n.scale,t.scale,e,r),n=t=null,function(n){for(var t,u=-1,i=r.length;++u=0;)e.push(u[r])}function ii(n,t){for(var e=[n],r=[];null!=(n=e.pop());)if(r.push(n),(i=n.children)&&(u=i.length))for(var u,i,o=-1;++oe;++e)(t=n[e][1])>u&&(r=e,u=t);return r}function di(n){return n.reduce(mi,0)}function mi(n,t){return n+t[1]}function yi(n,t){return Mi(n,Math.ceil(Math.log(t.length)/Math.LN2+1))}function Mi(n,t){for(var e=-1,r=+n[0],u=(n[1]-r)/t,i=[];++e<=t;)i[e]=u*e+r;return i}function xi(n){return[aa.min(n),aa.max(n)]}function bi(n,t){return n.value-t.value}function _i(n,t){var e=n._pack_next;n._pack_next=t,t._pack_prev=n,t._pack_next=e,e._pack_prev=t}function wi(n,t){n._pack_next=t,t._pack_prev=n}function Si(n,t){var e=t.x-n.x,r=t.y-n.y,u=n.r+t.r;return.999*u*u>e*e+r*r}function ki(n){function t(n){s=Math.min(n.x-n.r,s),f=Math.max(n.x+n.r,f),h=Math.min(n.y-n.r,h),g=Math.max(n.y+n.r,g)}if((e=n.children)&&(c=e.length)){var e,r,u,i,o,a,l,c,s=1/0,f=-1/0,h=1/0,g=-1/0;if(e.forEach(Ei),r=e[0],r.x=-r.r,r.y=0,t(r),c>1&&(u=e[1],u.x=u.r,u.y=0,t(u),c>2))for(i=e[2],Ci(r,u,i),t(i),_i(r,i),r._pack_prev=i,_i(i,u),u=r._pack_next,o=3;c>o;o++){Ci(r,u,i=e[o]);var p=0,v=1,d=1;for(a=u._pack_next;a!==u;a=a._pack_next,v++)if(Si(a,i)){p=1;break}if(1==p)for(l=r._pack_prev;l!==a._pack_prev&&!Si(l,i);l=l._pack_prev,d++);p?(d>v||v==d&&u.ro;o++)i=e[o],i.x-=m,i.y-=y,M=Math.max(M,i.r+Math.sqrt(i.x*i.x+i.y*i.y));n.r=M,e.forEach(Ai)}}function Ei(n){n._pack_next=n._pack_prev=n}function Ai(n){delete n._pack_next,delete n._pack_prev}function Ni(n,t,e,r){var u=n.children;if(n.x=t+=r*n.x,n.y=e+=r*n.y,n.r*=r,u)for(var i=-1,o=u.length;++i=0;)t=u[i],t.z+=e,t.m+=e,e+=t.s+(r+=t.c)}function Di(n,t,e){return n.a.parent===t.parent?n.a:e}function Pi(n){return 1+aa.max(n,function(n){return n.y})}function ji(n){return n.reduce(function(n,t){return n+t.x},0)/n.length}function Ui(n){var t=n.children;return t&&t.length?Ui(t[0]):n}function Fi(n){var t,e=n.children;return e&&(t=e.length)?Fi(e[t-1]):n}function Hi(n){return{x:n.x,y:n.y,dx:n.dx,dy:n.dy}}function Oi(n,t){var e=n.x+t[3],r=n.y+t[0],u=n.dx-t[1]-t[3],i=n.dy-t[0]-t[2];return 0>u&&(e+=u/2,u=0),0>i&&(r+=i/2,i=0),{x:e,y:r,dx:u,dy:i}}function Ii(n){var t=n[0],e=n[n.length-1];return e>t?[t,e]:[e,t]}function Yi(n){return n.rangeExtent?n.rangeExtent():Ii(n.range())}function Zi(n,t,e,r){var u=e(n[0],n[1]),i=r(t[0],t[1]);return function(n){return i(u(n))}}function Vi(n,t){var e,r=0,u=n.length-1,i=n[r],o=n[u];return i>o&&(e=r,r=u,u=e,e=i,i=o,o=e),n[r]=t.floor(i),n[u]=t.ceil(o),n}function Xi(n){return n?{floor:function(t){return Math.floor(t/n)*n},ceil:function(t){return Math.ceil(t/n)*n}}:_c}function $i(n,t,e,r){var u=[],i=[],o=0,a=Math.min(n.length,t.length)-1;for(n[a]2?$i:Zi,l=r?Bu:$u;return o=u(n,t,l,e),a=u(t,n,l,yu),i}function i(n){return o(n)}var o,a;return i.invert=function(n){return a(n)},i.domain=function(t){return arguments.length?(n=t.map(Number),u()):n},i.range=function(n){return arguments.length?(t=n,u()):t},i.rangeRound=function(n){return i.range(n).interpolate(Pu)},i.clamp=function(n){return arguments.length?(r=n,u()):r},i.interpolate=function(n){return arguments.length?(e=n,u()):e},i.ticks=function(t){return Ki(n,t)},i.tickFormat=function(t,e){return Qi(n,t,e)},i.nice=function(t){return Ji(n,t),u()},i.copy=function(){return Bi(n,t,e,r)},u()}function Wi(n,t){return aa.rebind(n,t,"range","rangeRound","interpolate","clamp")}function Ji(n,t){return Vi(n,Xi(Gi(n,t)[2]))}function Gi(n,t){null==t&&(t=10);var e=Ii(n),r=e[1]-e[0],u=Math.pow(10,Math.floor(Math.log(r/t)/Math.LN10)),i=t/r*u;return.15>=i?u*=10:.35>=i?u*=5:.75>=i&&(u*=2),e[0]=Math.ceil(e[0]/u)*u,e[1]=Math.floor(e[1]/u)*u+.5*u,e[2]=u,e}function Ki(n,t){return aa.range.apply(aa,Gi(n,t))}function Qi(n,t,e){var r=Gi(n,t);if(e){var u=sl.exec(e);if(u.shift(),"s"===u[8]){var i=aa.formatPrefix(Math.max(Ma(r[0]),Ma(r[1])));return u[7]||(u[7]="."+no(i.scale(r[2]))),u[8]="f",e=aa.format(u.join("")),function(n){return e(i.scale(n))+i.symbol}}u[7]||(u[7]="."+to(u[8],r)),e=u.join("")}else e=",."+no(r[2])+"f";return aa.format(e)}function no(n){return-Math.floor(Math.log(n)/Math.LN10+.01)}function to(n,t){var e=no(t[2]);return n in wc?Math.abs(e-no(Math.max(Ma(t[0]),Ma(t[1]))))+ +("e"!==n):e-2*("%"===n)}function eo(n,t,e,r){function u(n){return(e?Math.log(0>n?0:n):-Math.log(n>0?0:-n))/Math.log(t)}function i(n){return e?Math.pow(t,n):-Math.pow(t,-n)}function o(t){return n(u(t))}return o.invert=function(t){return i(n.invert(t))},o.domain=function(t){return arguments.length?(e=t[0]>=0,n.domain((r=t.map(Number)).map(u)),o):r},o.base=function(e){return arguments.length?(t=+e,n.domain(r.map(u)),o):t},o.nice=function(){var t=Vi(r.map(u),e?Math:kc);return n.domain(t),r=t.map(i),o},o.ticks=function(){var n=Ii(r),o=[],a=n[0],l=n[1],c=Math.floor(u(a)),s=Math.ceil(u(l)),f=t%1?2:t;if(isFinite(s-c)){if(e){for(;s>c;c++)for(var h=1;f>h;h++)o.push(i(c)*h);o.push(i(c))}else for(o.push(i(c));c++0;h--)o.push(i(c)*h);for(c=0;o[c]l;s--);o=o.slice(c,s)}return o},o.tickFormat=function(n,t){if(!arguments.length)return Sc;arguments.length<2?t=Sc:"function"!=typeof t&&(t=aa.format(t));var r,a=Math.max(.1,n/o.ticks().length),l=e?(r=1e-12,Math.ceil):(r=-1e-12,Math.floor);return function(n){return n/i(l(u(n)+r))<=a?t(n):""}},o.copy=function(){return eo(n.copy(),t,e,r)},Wi(o,n)}function ro(n,t,e){function r(t){return n(u(t))}var u=uo(t),i=uo(1/t);return r.invert=function(t){return i(n.invert(t))},r.domain=function(t){return arguments.length?(n.domain((e=t.map(Number)).map(u)),r):e},r.ticks=function(n){return Ki(e,n)},r.tickFormat=function(n,t){return Qi(e,n,t)},r.nice=function(n){return r.domain(Ji(e,n))},r.exponent=function(o){return arguments.length?(u=uo(t=o),i=uo(1/t),n.domain(e.map(u)),r):t},r.copy=function(){return ro(n.copy(),t,e)},Wi(r,n)}function uo(n){return function(t){return 0>t?-Math.pow(-t,n):Math.pow(t,n)}}function io(n,t){function e(e){return i[((u.get(e)||("range"===t.t?u.set(e,n.push(e)):0/0))-1)%i.length]}function r(t,e){return aa.range(n.length).map(function(n){return t+e*n})}var u,i,o;return e.domain=function(r){if(!arguments.length)return n;n=[],u=new c;for(var i,o=-1,a=r.length;++oe?[0/0,0/0]:[e>0?a[e-1]:n[0],et?0/0:t/i+n,[t,t+1/i]},r.copy=function(){return ao(n,t,e)},u()}function lo(n,t){function e(e){return e>=e?t[aa.bisect(n,e)]:void 0}return e.domain=function(t){return arguments.length?(n=t,e):n},e.range=function(n){return arguments.length?(t=n,e):t},e.invertExtent=function(e){return e=t.indexOf(e),[n[e-1],n[e]]},e.copy=function(){return lo(n,t)},e}function co(n){function t(n){return+n}return t.invert=t,t.domain=t.range=function(e){return arguments.length?(n=e.map(t),t):n},t.ticks=function(t){return Ki(n,t)},t.tickFormat=function(t,e){return Qi(n,t,e)},t.copy=function(){return co(n)},t}function so(){return 0}function fo(n){return n.innerRadius}function ho(n){return n.outerRadius}function go(n){return n.startAngle}function po(n){return n.endAngle}function vo(n){return n&&n.padAngle}function mo(n,t,e,r){return(n-e)*t-(t-r)*n>0?0:1}function yo(n,t,e,r,u){var i=n[0]-t[0],o=n[1]-t[1],a=(u?r:-r)/Math.sqrt(i*i+o*o),l=a*o,c=-a*i,s=n[0]+l,f=n[1]+c,h=t[0]+l,g=t[1]+c,p=(s+h)/2,v=(f+g)/2,d=h-s,m=g-f,y=d*d+m*m,M=e-r,x=s*g-h*f,b=(0>m?-1:1)*Math.sqrt(Math.max(0,M*M*y-x*x)),_=(x*m-d*b)/y,w=(-x*d-m*b)/y,S=(x*m+d*b)/y,k=(-x*d+m*b)/y,E=_-p,A=w-v,N=S-p,C=k-v;return E*E+A*A>N*N+C*C&&(_=S,w=k),[[_-l,w-c],[_*e/M,w*e/M]]}function Mo(n){function t(t){function o(){c.push("M",i(n(s),a))}for(var l,c=[],s=[],f=-1,h=t.length,g=Et(e),p=Et(r);++f1?n.join("L"):n+"Z"}function bo(n){return n.join("L")+"Z"}function _o(n){for(var t=0,e=n.length,r=n[0],u=[r[0],",",r[1]];++t1&&u.push("H",r[0]),u.join("")}function wo(n){for(var t=0,e=n.length,r=n[0],u=[r[0],",",r[1]];++t1){a=t[1],i=n[l],l++,r+="C"+(u[0]+o[0])+","+(u[1]+o[1])+","+(i[0]-a[0])+","+(i[1]-a[1])+","+i[0]+","+i[1];for(var c=2;c9&&(u=3*t/Math.sqrt(u),o[a]=u*e,o[a+1]=u*r));for(a=-1;++a<=l;)u=(n[Math.min(l,a+1)][0]-n[Math.max(0,a-1)][0])/(6*(1+o[a]*o[a])),i.push([u||0,o[a]*u||0]);return i}function Fo(n){return n.length<3?xo(n):n[0]+No(n,Uo(n))}function Ho(n){for(var t,e,r,u=-1,i=n.length;++u=t?o(n-t):void(s.c=o)}function o(e){var u=p.active,i=p[u];i&&(i.timer.c=null,i.timer.t=0/0,--p.count,delete p[u],i.event&&i.event.interrupt.call(n,n.__data__,i.index));for(var o in p)if(r>+o){var c=p[o];c.timer.c=null,c.timer.t=0/0,--p.count,delete p[o]}p.active=r,v.event&&v.event.start.call(n,n.__data__,t),g=[],v.tween.forEach(function(e,r){(r=r.call(n,n.__data__,t))&&g.push(r)}),h=v.ease,f=v.duration,s.c=a,Lt(function(){return s.c&&a(e||1)&&(s.c=null,s.t=0/0),1},0,l)}function a(u){for(var i=u/f,o=h(i),a=g.length;a>0;)g[--a].call(n,o);return i>=1?(v.event&&v.event.end.call(n,n.__data__,t),--p.count?delete p[r]:delete n[e],1):void 0}var l,s,f,h,g,p=n[e]||(n[e]={active:0,count:0}),v=p[r];v||(l=u.time,s=Lt(i,0,l),v=p[r]={tween:new c,time:l,timer:s,delay:u.delay,duration:u.duration,ease:u.ease,index:t},u=null,++p.count)}function na(n,t,e){n.attr("transform",function(n){var r=t(n);return"translate("+(isFinite(r)?r:e(n))+",0)"})}function ta(n,t,e){n.attr("transform",function(n){var r=t(n);return"translate(0,"+(isFinite(r)?r:e(n))+")"})}function ea(n){return n.toISOString()}function ra(n,t,e){function r(t){return n(t)}function u(n,e){var r=n[1]-n[0],u=r/e,i=aa.bisect(Jc,u);return i==Jc.length?[t.year,Gi(n.map(function(n){return n/31536e6}),e)[2]]:i?t[u/Jc[i-1]1?{floor:function(t){for(;e(t=n.floor(t));)t=ua(t-1);return t},ceil:function(t){for(;e(t=n.ceil(t));)t=ua(+t+1);return t}}:n))},r.ticks=function(n,t){var e=Ii(r.domain()),i=null==n?u(e,10):"number"==typeof n?u(e,n):!n.range&&[{range:n},t];return i&&(n=i[0],t=i[1]),n.range(e[0],ua(+e[1]+1),1>t?1:t)},r.tickFormat=function(){return e},r.copy=function(){return ra(n.copy(),t,e)},Wi(r,n)}function ua(n){return new Date(n)}function ia(n){return JSON.parse(n.responseText)}function oa(n){var t=sa.createRange();return t.selectNode(sa.body),t.createContextualFragment(n.responseText)}var aa={version:"3.5.8"},la=[].slice,ca=function(n){return la.call(n)},sa=this.document;if(sa)try{ca(sa.documentElement.childNodes)[0].nodeType}catch(fa){ca=function(n){for(var t=n.length,e=new Array(t);t--;)e[t]=n[t];return e}}if(Date.now||(Date.now=function(){return+new Date}),sa)try{sa.createElement("DIV").style.setProperty("opacity",0,"")}catch(ha){var ga=this.Element.prototype,pa=ga.setAttribute,va=ga.setAttributeNS,da=this.CSSStyleDeclaration.prototype,ma=da.setProperty;ga.setAttribute=function(n,t){pa.call(this,n,t+"")},ga.setAttributeNS=function(n,t,e){va.call(this,n,t,e+"")},da.setProperty=function(n,t,e){ma.call(this,n,t+"",e)}}aa.ascending=e,aa.descending=function(n,t){return n>t?-1:t>n?1:t>=n?0:0/0},aa.min=function(n,t){var e,r,u=-1,i=n.length;if(1===arguments.length){for(;++u=r){e=r;break}for(;++ur&&(e=r)}else{for(;++u=r){e=r;break}for(;++ur&&(e=r)}return e},aa.max=function(n,t){var e,r,u=-1,i=n.length;if(1===arguments.length){for(;++u=r){e=r;break}for(;++ue&&(e=r)}else{for(;++u=r){e=r;break}for(;++ue&&(e=r)}return e},aa.extent=function(n,t){var e,r,u,i=-1,o=n.length;if(1===arguments.length){for(;++i=r){e=u=r;break}for(;++ir&&(e=r),r>u&&(u=r))}else{for(;++i=r){e=u=r;break}for(;++ir&&(e=r),r>u&&(u=r))}return[e,u]},aa.sum=function(n,t){var e,r=0,i=n.length,o=-1;if(1===arguments.length)for(;++o1?l/(s-1):void 0},aa.deviation=function(){var n=aa.variance.apply(this,arguments);return n?Math.sqrt(n):n};var ya=i(e);aa.bisectLeft=ya.left,aa.bisect=aa.bisectRight=ya.right,aa.bisector=function(n){return i(1===n.length?function(t,r){return e(n(t),r)}:n)},aa.shuffle=function(n,t,e){(i=arguments.length)<3&&(e=n.length,2>i&&(t=0));for(var r,u,i=e-t;i;)u=Math.random()*i--|0,r=n[i+t],n[i+t]=n[u+t],n[u+t]=r;return n},aa.permute=function(n,t){for(var e=t.length,r=new Array(e);e--;)r[e]=n[t[e]];return r},aa.pairs=function(n){for(var t,e=0,r=n.length-1,u=n[0],i=new Array(0>r?0:r);r>e;)i[e]=[t=u,u=n[++e]];return i},aa.zip=function(){if(!(r=arguments.length))return[];for(var n=-1,t=aa.min(arguments,o),e=new Array(t);++n=0;)for(r=n[u],t=r.length;--t>=0;)e[--o]=r[t];return e};var Ma=Math.abs;aa.range=function(n,t,e){if(arguments.length<3&&(e=1,arguments.length<2&&(t=n,n=0)),(t-n)/e===1/0)throw new Error("infinite range");var r,u=[],i=a(Ma(e)),o=-1;if(n*=i,t*=i,e*=i,0>e)for(;(r=n+e*++o)>t;)u.push(r/i);else for(;(r=n+e*++o)=i.length)return r?r.call(u,o):e?o.sort(e):o;for(var l,s,f,h,g=-1,p=o.length,v=i[a++],d=new c;++g=i.length)return n;var r=[],u=o[e++];return n.forEach(function(n,u){r.push({key:n,values:t(u,e)})}),u?r.sort(function(n,t){return u(n.key,t.key)}):r}var e,r,u={},i=[],o=[];return u.map=function(t,e){return n(e,t,0)},u.entries=function(e){return t(n(aa.map,e,0),0)},u.key=function(n){return i.push(n),u},u.sortKeys=function(n){return o[i.length-1]=n,u},u.sortValues=function(n){return e=n,u},u.rollup=function(n){return r=n,u},u},aa.set=function(n){var t=new m;if(n)for(var e=0,r=n.length;r>e;++e)t.add(n[e]);return t},l(m,{has:h,add:function(n){return this._[s(n+="")]=!0,n},remove:g,values:p,size:v,empty:d,forEach:function(n){for(var t in this._)n.call(this,f(t))}}),aa.behavior={},aa.rebind=function(n,t){for(var e,r=1,u=arguments.length;++r=0&&(r=n.slice(e+1),n=n.slice(0,e)),n)return arguments.length<2?this[n].on(r):this[n].on(r,t);if(2===arguments.length){if(null==t)for(n in this)this.hasOwnProperty(n)&&this[n].on(r,null);return this}},aa.event=null,aa.requote=function(n){return n.replace(wa,"\\$&")};var wa=/[\\\^\$\*\+\?\|\[\]\(\)\.\{\}]/g,Sa={}.__proto__?function(n,t){n.__proto__=t}:function(n,t){for(var e in t)n[e]=t[e]},ka=function(n,t){return t.querySelector(n)},Ea=function(n,t){return t.querySelectorAll(n)},Aa=function(n,t){var e=n.matches||n[x(n,"matchesSelector")];return(Aa=function(n,t){return e.call(n,t)})(n,t)};"function"==typeof Sizzle&&(ka=function(n,t){return Sizzle(n,t)[0]||null},Ea=Sizzle,Aa=Sizzle.matchesSelector),aa.selection=function(){return aa.select(sa.documentElement)};var Na=aa.selection.prototype=[];Na.select=function(n){var t,e,r,u,i=[];n=N(n);for(var o=-1,a=this.length;++o=0&&"xmlns"!==(e=n.slice(0,t))&&(n=n.slice(t+1)),Ca.hasOwnProperty(e)?{space:Ca[e],local:n}:n}},Na.attr=function(n,t){if(arguments.length<2){if("string"==typeof n){var e=this.node();return n=aa.ns.qualify(n),n.local?e.getAttributeNS(n.space,n.local):e.getAttribute(n)}for(t in n)this.each(z(t,n[t]));return this}return this.each(z(n,t))},Na.classed=function(n,t){if(arguments.length<2){if("string"==typeof n){var e=this.node(),r=(n=T(n)).length,u=-1;if(t=e.classList){for(;++uu){if("string"!=typeof n){2>u&&(e="");for(r in n)this.each(P(r,n[r],e));return this}if(2>u){var i=this.node();return t(i).getComputedStyle(i,null).getPropertyValue(n)}r=""}return this.each(P(n,e,r))},Na.property=function(n,t){if(arguments.length<2){if("string"==typeof n)return this.node()[n];for(t in n)this.each(j(t,n[t]));return this}return this.each(j(n,t))},Na.text=function(n){return arguments.length?this.each("function"==typeof n?function(){var t=n.apply(this,arguments);this.textContent=null==t?"":t}:null==n?function(){this.textContent=""}:function(){this.textContent=n}):this.node().textContent},Na.html=function(n){return arguments.length?this.each("function"==typeof n?function(){var t=n.apply(this,arguments);this.innerHTML=null==t?"":t}:null==n?function(){this.innerHTML=""}:function(){this.innerHTML=n}):this.node().innerHTML},Na.append=function(n){return n=U(n),this.select(function(){return this.appendChild(n.apply(this,arguments))})},Na.insert=function(n,t){return n=U(n),t=N(t),this.select(function(){return this.insertBefore(n.apply(this,arguments),t.apply(this,arguments)||null)})},Na.remove=function(){return this.each(F)},Na.data=function(n,t){function e(n,e){var r,u,i,o=n.length,f=e.length,h=Math.min(o,f),g=new Array(f),p=new Array(f),v=new Array(o);if(t){var d,m=new c,y=new Array(o);for(r=-1;++rr;++r)p[r]=H(e[r]);for(;o>r;++r)v[r]=n[r]}p.update=g,p.parentNode=g.parentNode=v.parentNode=n.parentNode,a.push(p),l.push(g),s.push(v)}var r,u,i=-1,o=this.length;if(!arguments.length){for(n=new Array(o=(r=this[0]).length);++ii;i++){u.push(t=[]),t.parentNode=(e=this[i]).parentNode;for(var a=0,l=e.length;l>a;a++)(r=e[a])&&n.call(r,r.__data__,a,i)&&t.push(r)}return A(u)},Na.order=function(){for(var n=-1,t=this.length;++n=0;)(e=r[u])&&(i&&i!==e.nextSibling&&i.parentNode.insertBefore(e,i),i=e);return this},Na.sort=function(n){n=I.apply(this,arguments);for(var t=-1,e=this.length;++tn;n++)for(var e=this[n],r=0,u=e.length;u>r;r++){var i=e[r];if(i)return i}return null},Na.size=function(){var n=0;return Y(this,function(){++n}),n};var za=[];aa.selection.enter=Z,aa.selection.enter.prototype=za,za.append=Na.append,za.empty=Na.empty,za.node=Na.node,za.call=Na.call,za.size=Na.size,za.select=function(n){for(var t,e,r,u,i,o=[],a=-1,l=this.length;++ar){if("string"!=typeof n){2>r&&(t=!1);for(e in n)this.each(X(e,n[e],t));return this}if(2>r)return(r=this.node()["__on"+n])&&r._;e=!1}return this.each(X(n,t,e))};var La=aa.map({mouseenter:"mouseover",mouseleave:"mouseout"});sa&&La.forEach(function(n){"on"+n in sa&&La.remove(n)});var qa,Ta=0;aa.mouse=function(n){return J(n,k())};var Ra=this.navigator&&/WebKit/.test(this.navigator.userAgent)?-1:0;aa.touch=function(n,t,e){if(arguments.length<3&&(e=t,t=k().changedTouches),t)for(var r,u=0,i=t.length;i>u;++u)if((r=t[u]).identifier===e)return J(n,r)},aa.behavior.drag=function(){function n(){this.on("mousedown.drag",i).on("touchstart.drag",o)}function e(n,t,e,i,o){return function(){function a(){var n,e,r=t(h,v);r&&(n=r[0]-M[0],e=r[1]-M[1],p|=n|e,M=r,g({type:"drag",x:r[0]+c[0],y:r[1]+c[1],dx:n,dy:e}))}function l(){t(h,v)&&(m.on(i+d,null).on(o+d,null),y(p&&aa.event.target===f),g({type:"dragend"}))}var c,s=this,f=aa.event.target,h=s.parentNode,g=r.of(s,arguments),p=0,v=n(),d=".drag"+(null==v?"":"-"+v),m=aa.select(e(f)).on(i+d,a).on(o+d,l),y=W(f),M=t(h,v);u?(c=u.apply(s,arguments),c=[c.x-M[0],c.y-M[1]]):c=[0,0],g({type:"dragstart"})}}var r=E(n,"drag","dragstart","dragend"),u=null,i=e(b,aa.mouse,t,"mousemove","mouseup"),o=e(G,aa.touch,y,"touchmove","touchend");return n.origin=function(t){return arguments.length?(u=t,n):u},aa.rebind(n,r,"on")},aa.touches=function(n,t){return arguments.length<2&&(t=k().touches),t?ca(t).map(function(t){var e=J(n,t);return e.identifier=t.identifier,e}):[]};var Da=1e-6,Pa=Da*Da,ja=Math.PI,Ua=2*ja,Fa=Ua-Da,Ha=ja/2,Oa=ja/180,Ia=180/ja,Ya=Math.SQRT2,Za=2,Va=4;aa.interpolateZoom=function(n,t){var e,r,u=n[0],i=n[1],o=n[2],a=t[0],l=t[1],c=t[2],s=a-u,f=l-i,h=s*s+f*f;if(Pa>h)r=Math.log(c/o)/Ya,e=function(n){return[u+n*s,i+n*f,o*Math.exp(Ya*n*r)]};else{var g=Math.sqrt(h),p=(c*c-o*o+Va*h)/(2*o*Za*g),v=(c*c-o*o-Va*h)/(2*c*Za*g),d=Math.log(Math.sqrt(p*p+1)-p),m=Math.log(Math.sqrt(v*v+1)-v);r=(m-d)/Ya,e=function(n){var t=n*r,e=rt(d),a=o/(Za*g)*(e*ut(Ya*t+d)-et(d));return[u+a*s,i+a*f,o*e/rt(Ya*t+d)]}}return e.duration=1e3*r,e},aa.behavior.zoom=function(){function n(n){n.on(L,f).on($a+".zoom",g).on("dblclick.zoom",p).on(R,h)}function e(n){return[(n[0]-k.x)/k.k,(n[1]-k.y)/k.k]}function r(n){return[n[0]*k.k+k.x,n[1]*k.k+k.y]}function u(n){k.k=Math.max(N[0],Math.min(N[1],n))}function i(n,t){t=r(t),k.x+=n[0]-t[0],k.y+=n[1]-t[1]}function o(t,e,r,o){t.__chart__={x:k.x,y:k.y,k:k.k},u(Math.pow(2,o)),i(d=e,r),t=aa.select(t),C>0&&(t=t.transition().duration(C)),t.call(n.event)}function a(){b&&b.domain(x.range().map(function(n){return(n-k.x)/k.k}).map(x.invert)),w&&w.domain(_.range().map(function(n){return(n-k.y)/k.k}).map(_.invert))}function l(n){z++||n({type:"zoomstart"})}function c(n){a(),n({type:"zoom",scale:k.k,translate:[k.x,k.y]})}function s(n){--z||(n({type:"zoomend"}),d=null)}function f(){function n(){f=1,i(aa.mouse(u),g),c(a)}function r(){h.on(q,null).on(T,null),p(f&&aa.event.target===o),s(a)}var u=this,o=aa.event.target,a=D.of(u,arguments),f=0,h=aa.select(t(u)).on(q,n).on(T,r),g=e(aa.mouse(u)),p=W(u);Hc.call(u),l(a)}function h(){function n(){var n=aa.touches(p);return g=k.k,n.forEach(function(n){n.identifier in d&&(d[n.identifier]=e(n))}),n}function t(){var t=aa.event.target;aa.select(t).on(x,r).on(b,a),_.push(t);for(var e=aa.event.changedTouches,u=0,i=e.length;i>u;++u)d[e[u].identifier]=null;var l=n(),c=Date.now();if(1===l.length){if(500>c-M){var s=l[0];o(p,s,d[s.identifier],Math.floor(Math.log(k.k)/Math.LN2)+1),S()}M=c}else if(l.length>1){var s=l[0],f=l[1],h=s[0]-f[0],g=s[1]-f[1];m=h*h+g*g}}function r(){var n,t,e,r,o=aa.touches(p);Hc.call(p);for(var a=0,l=o.length;l>a;++a,r=null)if(e=o[a],r=d[e.identifier]){if(t)break;n=e,t=r}if(r){var s=(s=e[0]-n[0])*s+(s=e[1]-n[1])*s,f=m&&Math.sqrt(s/m);n=[(n[0]+e[0])/2,(n[1]+e[1])/2],t=[(t[0]+r[0])/2,(t[1]+r[1])/2],u(f*g)}M=null,i(n,t),c(v)}function a(){if(aa.event.touches.length){for(var t=aa.event.changedTouches,e=0,r=t.length;r>e;++e)delete d[t[e].identifier];for(var u in d)return void n()}aa.selectAll(_).on(y,null),w.on(L,f).on(R,h),E(),s(v)}var g,p=this,v=D.of(p,arguments),d={},m=0,y=".zoom-"+aa.event.changedTouches[0].identifier,x="touchmove"+y,b="touchend"+y,_=[],w=aa.select(p),E=W(p);t(),l(v),w.on(L,null).on(R,t)}function g(){var n=D.of(this,arguments);y?clearTimeout(y):(Hc.call(this),v=e(d=m||aa.mouse(this)),l(n)),y=setTimeout(function(){y=null,s(n)},50),S(),u(Math.pow(2,.002*Xa())*k.k),i(d,v),c(n)}function p(){var n=aa.mouse(this),t=Math.log(k.k)/Math.LN2;o(this,n,e(n),aa.event.shiftKey?Math.ceil(t)-1:Math.floor(t)+1)}var v,d,m,y,M,x,b,_,w,k={x:0,y:0,k:1},A=[960,500],N=Ba,C=250,z=0,L="mousedown.zoom",q="mousemove.zoom",T="mouseup.zoom",R="touchstart.zoom",D=E(n,"zoomstart","zoom","zoomend");return $a||($a="onwheel"in sa?(Xa=function(){return-aa.event.deltaY*(aa.event.deltaMode?120:1)},"wheel"):"onmousewheel"in sa?(Xa=function(){return aa.event.wheelDelta},"mousewheel"):(Xa=function(){return-aa.event.detail},"MozMousePixelScroll")),n.event=function(n){n.each(function(){var n=D.of(this,arguments),t=k;Uc?aa.select(this).transition().each("start.zoom",function(){k=this.__chart__||{x:0,y:0,k:1},l(n)}).tween("zoom:zoom",function(){var e=A[0],r=A[1],u=d?d[0]:e/2,i=d?d[1]:r/2,o=aa.interpolateZoom([(u-k.x)/k.k,(i-k.y)/k.k,e/k.k],[(u-t.x)/t.k,(i-t.y)/t.k,e/t.k]);return function(t){var r=o(t),a=e/r[2];this.__chart__=k={x:u-r[0]*a,y:i-r[1]*a,k:a},c(n)}}).each("interrupt.zoom",function(){s(n)}).each("end.zoom",function(){s(n)}):(this.__chart__=k,l(n),c(n),s(n))})},n.translate=function(t){return arguments.length?(k={x:+t[0],y:+t[1],k:k.k},a(),n):[k.x,k.y]},n.scale=function(t){return arguments.length?(k={x:k.x,y:k.y,k:null},u(+t),a(),n):k.k},n.scaleExtent=function(t){return arguments.length?(N=null==t?Ba:[+t[0],+t[1]],n):N},n.center=function(t){return arguments.length?(m=t&&[+t[0],+t[1]],n):m},n.size=function(t){return arguments.length?(A=t&&[+t[0],+t[1]],n):A},n.duration=function(t){return arguments.length?(C=+t,n):C},n.x=function(t){return arguments.length?(b=t,x=t.copy(),k={x:0,y:0,k:1},n):b},n.y=function(t){return arguments.length?(w=t,_=t.copy(),k={x:0,y:0,k:1},n):w},aa.rebind(n,D,"on")};var Xa,$a,Ba=[0,1/0];aa.color=ot,ot.prototype.toString=function(){return this.rgb()+""},aa.hsl=at;var Wa=at.prototype=new ot;Wa.brighter=function(n){return n=Math.pow(.7,arguments.length?n:1),new at(this.h,this.s,this.l/n)},Wa.darker=function(n){return n=Math.pow(.7,arguments.length?n:1),new at(this.h,this.s,n*this.l)},Wa.rgb=function(){return lt(this.h,this.s,this.l)},aa.hcl=ct;var Ja=ct.prototype=new ot;Ja.brighter=function(n){return new ct(this.h,this.c,Math.min(100,this.l+Ga*(arguments.length?n:1)))},Ja.darker=function(n){return new ct(this.h,this.c,Math.max(0,this.l-Ga*(arguments.length?n:1)))},Ja.rgb=function(){return st(this.h,this.c,this.l).rgb()},aa.lab=ft;var Ga=18,Ka=.95047,Qa=1,nl=1.08883,tl=ft.prototype=new ot;tl.brighter=function(n){return new ft(Math.min(100,this.l+Ga*(arguments.length?n:1)),this.a,this.b)},tl.darker=function(n){return new ft(Math.max(0,this.l-Ga*(arguments.length?n:1)),this.a,this.b)},tl.rgb=function(){return ht(this.l,this.a,this.b)},aa.rgb=mt;var el=mt.prototype=new ot;el.brighter=function(n){n=Math.pow(.7,arguments.length?n:1);var t=this.r,e=this.g,r=this.b,u=30;return t||e||r?(t&&u>t&&(t=u),e&&u>e&&(e=u),r&&u>r&&(r=u),new mt(Math.min(255,t/n),Math.min(255,e/n),Math.min(255,r/n))):new mt(u,u,u)},el.darker=function(n){return n=Math.pow(.7,arguments.length?n:1),new mt(n*this.r,n*this.g,n*this.b)},el.hsl=function(){return _t(this.r,this.g,this.b)},el.toString=function(){return"#"+xt(this.r)+xt(this.g)+xt(this.b)};var rl=aa.map({aliceblue:15792383,antiquewhite:16444375,aqua:65535,aquamarine:8388564,azure:15794175,beige:16119260,bisque:16770244,black:0,blanchedalmond:16772045,blue:255,blueviolet:9055202,brown:10824234,burlywood:14596231,cadetblue:6266528,chartreuse:8388352,chocolate:13789470,coral:16744272,cornflowerblue:6591981,cornsilk:16775388,crimson:14423100,cyan:65535,darkblue:139,darkcyan:35723,darkgoldenrod:12092939,darkgray:11119017,darkgreen:25600,darkgrey:11119017,darkkhaki:12433259,darkmagenta:9109643,darkolivegreen:5597999,darkorange:16747520,darkorchid:10040012,darkred:9109504,darksalmon:15308410,darkseagreen:9419919,darkslateblue:4734347,darkslategray:3100495,darkslategrey:3100495,darkturquoise:52945,darkviolet:9699539,deeppink:16716947,deepskyblue:49151,dimgray:6908265,dimgrey:6908265,dodgerblue:2003199,firebrick:11674146,floralwhite:16775920,forestgreen:2263842,fuchsia:16711935,gainsboro:14474460,ghostwhite:16316671,gold:16766720,goldenrod:14329120,gray:8421504,green:32768,greenyellow:11403055,grey:8421504,honeydew:15794160,hotpink:16738740,indianred:13458524,indigo:4915330,ivory:16777200,khaki:15787660,lavender:15132410,lavenderblush:16773365,lawngreen:8190976,lemonchiffon:16775885,lightblue:11393254,lightcoral:15761536,lightcyan:14745599,lightgoldenrodyellow:16448210,lightgray:13882323,lightgreen:9498256,lightgrey:13882323,lightpink:16758465,lightsalmon:16752762,lightseagreen:2142890,lightskyblue:8900346,lightslategray:7833753,lightslategrey:7833753,lightsteelblue:11584734,lightyellow:16777184,lime:65280,limegreen:3329330,linen:16445670,magenta:16711935,maroon:8388608,mediumaquamarine:6737322,mediumblue:205,mediumorchid:12211667,mediumpurple:9662683,mediumseagreen:3978097,mediumslateblue:8087790,mediumspringgreen:64154,mediumturquoise:4772300,mediumvioletred:13047173,midnightblue:1644912,mintcream:16121850,mistyrose:16770273,moccasin:16770229,navajowhite:16768685,navy:128,oldlace:16643558,olive:8421376,olivedrab:7048739,orange:16753920,orangered:16729344,orchid:14315734,palegoldenrod:15657130,palegreen:10025880,paleturquoise:11529966,palevioletred:14381203,papayawhip:16773077,peachpuff:16767673,peru:13468991,pink:16761035,plum:14524637,powderblue:11591910,purple:8388736,rebeccapurple:6697881,red:16711680,rosybrown:12357519,royalblue:4286945,saddlebrown:9127187,salmon:16416882,sandybrown:16032864,seagreen:3050327,seashell:16774638,sienna:10506797,silver:12632256,skyblue:8900331,slateblue:6970061,slategray:7372944,slategrey:7372944,snow:16775930,springgreen:65407,steelblue:4620980,tan:13808780,teal:32896,thistle:14204888,tomato:16737095,turquoise:4251856,violet:15631086,wheat:16113331,white:16777215,whitesmoke:16119285,yellow:16776960,yellowgreen:10145074});rl.forEach(function(n,t){rl.set(n,yt(t))}),aa.functor=Et,aa.xhr=At(y),aa.dsv=function(n,t){function e(n,e,i){arguments.length<3&&(i=e,e=null);var o=Nt(n,t,null==e?r:u(e),i);return o.row=function(n){return arguments.length?o.response(null==(e=n)?r:u(n)):e},o}function r(n){return e.parse(n.responseText)}function u(n){return function(t){return e.parse(t.responseText,n)}}function i(t){return t.map(o).join(n)}function o(n){return a.test(n)?'"'+n.replace(/\"/g,'""')+'"':n}var a=new RegExp('["'+n+"\n]"),l=n.charCodeAt(0);return e.parse=function(n,t){var r;return e.parseRows(n,function(n,e){if(r)return r(n,e-1);var u=new Function("d","return {"+n.map(function(n,t){return JSON.stringify(n)+": d["+t+"]"}).join(",")+"}");r=t?function(n,e){return t(u(n),e)}:u})},e.parseRows=function(n,t){function e(){if(s>=c)return o;if(u)return u=!1,i;var t=s;if(34===n.charCodeAt(t)){for(var e=t;e++s;){var r=n.charCodeAt(s++),a=1;if(10===r)u=!0;else if(13===r)u=!0,10===n.charCodeAt(s)&&(++s,++a);else if(r!==l)continue;return n.slice(t,s-a)}return n.slice(t)}for(var r,u,i={},o={},a=[],c=n.length,s=0,f=0;(r=e())!==o;){for(var h=[];r!==i&&r!==o;)h.push(r),r=e();t&&null==(h=t(h,f++))||a.push(h)}return a},e.format=function(t){if(Array.isArray(t[0]))return e.formatRows(t);var r=new m,u=[];return t.forEach(function(n){for(var t in n)r.has(t)||u.push(r.add(t))}),[u.map(o).join(n)].concat(t.map(function(t){return u.map(function(n){return o(t[n])}).join(n)})).join("\n")},e.formatRows=function(n){return n.map(i).join("\n")},e},aa.csv=aa.dsv(",","text/csv"),aa.tsv=aa.dsv(" ","text/tab-separated-values");var ul,il,ol,al,ll=this[x(this,"requestAnimationFrame")]||function(n){setTimeout(n,17)};aa.timer=function(){Lt.apply(this,arguments)},aa.timer.flush=function(){Tt(),Rt()},aa.round=function(n,t){return t?Math.round(n*(t=Math.pow(10,t)))/t:Math.round(n)};var cl=["y","z","a","f","p","n","\xb5","m","","k","M","G","T","P","E","Z","Y"].map(Pt);aa.formatPrefix=function(n,t){var e=0;return(n=+n)&&(0>n&&(n*=-1),t&&(n=aa.round(n,Dt(n,t))),e=1+Math.floor(1e-12+Math.log(n)/Math.LN10),e=Math.max(-24,Math.min(24,3*Math.floor((e-1)/3)))),cl[8+e/3]};var sl=/(?:([^{])?([<>=^]))?([+\- ])?([$#])?(0)?(\d+)?(,)?(\.-?\d+)?([a-z%])?/i,fl=aa.map({b:function(n){return n.toString(2)},c:function(n){return String.fromCharCode(n)},o:function(n){return n.toString(8)},x:function(n){return n.toString(16)},X:function(n){return n.toString(16).toUpperCase()},g:function(n,t){return n.toPrecision(t)},e:function(n,t){return n.toExponential(t)},f:function(n,t){return n.toFixed(t)},r:function(n,t){return(n=aa.round(n,Dt(n,t))).toFixed(Math.max(0,Math.min(20,Dt(n*(1+1e-15),t))))}}),hl=aa.time={},gl=Date;Ft.prototype={getDate:function(){return this._.getUTCDate()},getDay:function(){return this._.getUTCDay()},getFullYear:function(){return this._.getUTCFullYear()},getHours:function(){return this._.getUTCHours()},getMilliseconds:function(){return this._.getUTCMilliseconds()},getMinutes:function(){return this._.getUTCMinutes()},getMonth:function(){return this._.getUTCMonth()},getSeconds:function(){return this._.getUTCSeconds()},getTime:function(){return this._.getTime()},getTimezoneOffset:function(){return 0},valueOf:function(){return this._.valueOf()},setDate:function(){pl.setUTCDate.apply(this._,arguments)},setDay:function(){pl.setUTCDay.apply(this._,arguments)},setFullYear:function(){pl.setUTCFullYear.apply(this._,arguments)},setHours:function(){pl.setUTCHours.apply(this._,arguments)},setMilliseconds:function(){pl.setUTCMilliseconds.apply(this._,arguments)},setMinutes:function(){pl.setUTCMinutes.apply(this._,arguments)},setMonth:function(){pl.setUTCMonth.apply(this._,arguments)},setSeconds:function(){pl.setUTCSeconds.apply(this._,arguments)},setTime:function(){pl.setTime.apply(this._,arguments)}};var pl=Date.prototype;hl.year=Ht(function(n){return n=hl.day(n),n.setMonth(0,1),n},function(n,t){n.setFullYear(n.getFullYear()+t)},function(n){return n.getFullYear()}),hl.years=hl.year.range,hl.years.utc=hl.year.utc.range,hl.day=Ht(function(n){var t=new gl(2e3,0);return t.setFullYear(n.getFullYear(),n.getMonth(),n.getDate()),t},function(n,t){n.setDate(n.getDate()+t)},function(n){return n.getDate()-1}),hl.days=hl.day.range,hl.days.utc=hl.day.utc.range,hl.dayOfYear=function(n){var t=hl.year(n);return Math.floor((n-t-6e4*(n.getTimezoneOffset()-t.getTimezoneOffset()))/864e5)},["sunday","monday","tuesday","wednesday","thursday","friday","saturday"].forEach(function(n,t){t=7-t;var e=hl[n]=Ht(function(n){return(n=hl.day(n)).setDate(n.getDate()-(n.getDay()+t)%7),n},function(n,t){n.setDate(n.getDate()+7*Math.floor(t))},function(n){var e=hl.year(n).getDay();return Math.floor((hl.dayOfYear(n)+(e+t)%7)/7)-(e!==t)});hl[n+"s"]=e.range,hl[n+"s"].utc=e.utc.range,hl[n+"OfYear"]=function(n){var e=hl.year(n).getDay();return Math.floor((hl.dayOfYear(n)+(e+t)%7)/7)}}),hl.week=hl.sunday,hl.weeks=hl.sunday.range,hl.weeks.utc=hl.sunday.utc.range,hl.weekOfYear=hl.sundayOfYear;var vl={"-":"",_:" ",0:"0"},dl=/^\s*\d+/,ml=/^%/;aa.locale=function(n){return{numberFormat:jt(n),timeFormat:It(n)}};var yl=aa.locale({decimal:".",thousands:",",grouping:[3],currency:["$",""],dateTime:"%a %b %e %X %Y",date:"%m/%d/%Y",time:"%H:%M:%S",periods:["AM","PM"],days:["Sunday","Monday","Tuesday","Wednesday","Thursday","Friday","Saturday"],shortDays:["Sun","Mon","Tue","Wed","Thu","Fri","Sat"],months:["January","February","March","April","May","June","July","August","September","October","November","December"],shortMonths:["Jan","Feb","Mar","Apr","May","Jun","Jul","Aug","Sep","Oct","Nov","Dec"]}); +aa.format=yl.numberFormat,aa.geo={},ce.prototype={s:0,t:0,add:function(n){se(n,this.t,Ml),se(Ml.s,this.s,this),this.s?this.t+=Ml.t:this.s=Ml.t},reset:function(){this.s=this.t=0},valueOf:function(){return this.s}};var Ml=new ce;aa.geo.stream=function(n,t){n&&xl.hasOwnProperty(n.type)?xl[n.type](n,t):fe(n,t)};var xl={Feature:function(n,t){fe(n.geometry,t)},FeatureCollection:function(n,t){for(var e=n.features,r=-1,u=e.length;++rn?4*ja+n:n,Sl.lineStart=Sl.lineEnd=Sl.point=b}};aa.geo.bounds=function(){function n(n,t){M.push(x=[s=n,h=n]),f>t&&(f=t),t>g&&(g=t)}function t(t,e){var r=ve([t*Oa,e*Oa]);if(m){var u=me(m,r),i=[u[1],-u[0],0],o=me(i,u);xe(o),o=be(o);var l=t-p,c=l>0?1:-1,v=o[0]*Ia*c,d=Ma(l)>180;if(d^(v>c*p&&c*t>v)){var y=o[1]*Ia;y>g&&(g=y)}else if(v=(v+360)%360-180,d^(v>c*p&&c*t>v)){var y=-o[1]*Ia;f>y&&(f=y)}else f>e&&(f=e),e>g&&(g=e);d?p>t?a(s,t)>a(s,h)&&(h=t):a(t,h)>a(s,h)&&(s=t):h>=s?(s>t&&(s=t),t>h&&(h=t)):t>p?a(s,t)>a(s,h)&&(h=t):a(t,h)>a(s,h)&&(s=t)}else n(t,e);m=r,p=t}function e(){b.point=t}function r(){x[0]=s,x[1]=h,b.point=n,m=null}function u(n,e){if(m){var r=n-p;y+=Ma(r)>180?r+(r>0?360:-360):r}else v=n,d=e;Sl.point(n,e),t(n,e)}function i(){Sl.lineStart()}function o(){u(v,d),Sl.lineEnd(),Ma(y)>Da&&(s=-(h=180)),x[0]=s,x[1]=h,m=null}function a(n,t){return(t-=n)<0?t+360:t}function l(n,t){return n[0]-t[0]}function c(n,t){return t[0]<=t[1]?t[0]<=n&&n<=t[1]:nwl?(s=-(h=180),f=-(g=90)):y>Da?g=90:-Da>y&&(f=-90),x[0]=s,x[1]=h}};return function(n){g=h=-(s=f=1/0),M=[],aa.geo.stream(n,b);var t=M.length;if(t){M.sort(l);for(var e,r=1,u=M[0],i=[u];t>r;++r)e=M[r],c(e[0],u)||c(e[1],u)?(a(u[0],e[1])>a(u[0],u[1])&&(u[1]=e[1]),a(e[0],u[1])>a(u[0],u[1])&&(u[0]=e[0])):i.push(u=e);for(var o,e,p=-1/0,t=i.length-1,r=0,u=i[t];t>=r;u=e,++r)e=i[r],(o=a(u[1],e[0]))>p&&(p=o,s=e[0],h=u[1])}return M=x=null,1/0===s||1/0===f?[[0/0,0/0],[0/0,0/0]]:[[s,f],[h,g]]}}(),aa.geo.centroid=function(n){kl=El=Al=Nl=Cl=zl=Ll=ql=Tl=Rl=Dl=0,aa.geo.stream(n,Pl);var t=Tl,e=Rl,r=Dl,u=t*t+e*e+r*r;return Pa>u&&(t=zl,e=Ll,r=ql,Da>El&&(t=Al,e=Nl,r=Cl),u=t*t+e*e+r*r,Pa>u)?[0/0,0/0]:[Math.atan2(e,t)*Ia,tt(r/Math.sqrt(u))*Ia]};var kl,El,Al,Nl,Cl,zl,Ll,ql,Tl,Rl,Dl,Pl={sphere:b,point:we,lineStart:ke,lineEnd:Ee,polygonStart:function(){Pl.lineStart=Ae},polygonEnd:function(){Pl.lineStart=ke}},jl=Te(Ce,je,Fe,[-ja,-ja/2]),Ul=1e9;aa.geo.clipExtent=function(){var n,t,e,r,u,i,o={stream:function(n){return u&&(u.valid=!1),u=i(n),u.valid=!0,u},extent:function(a){return arguments.length?(i=Ye(n=+a[0][0],t=+a[0][1],e=+a[1][0],r=+a[1][1]),u&&(u.valid=!1,u=null),o):[[n,t],[e,r]]}};return o.extent([[0,0],[960,500]])},(aa.geo.conicEqualArea=function(){return Ze(Ve)}).raw=Ve,aa.geo.albers=function(){return aa.geo.conicEqualArea().rotate([96,0]).center([-.6,38.7]).parallels([29.5,45.5]).scale(1070)},aa.geo.albersUsa=function(){function n(n){var i=n[0],o=n[1];return t=null,e(i,o),t||(r(i,o),t)||u(i,o),t}var t,e,r,u,i=aa.geo.albers(),o=aa.geo.conicEqualArea().rotate([154,0]).center([-2,58.5]).parallels([55,65]),a=aa.geo.conicEqualArea().rotate([157,0]).center([-3,19.9]).parallels([8,18]),l={point:function(n,e){t=[n,e]}};return n.invert=function(n){var t=i.scale(),e=i.translate(),r=(n[0]-e[0])/t,u=(n[1]-e[1])/t;return(u>=.12&&.234>u&&r>=-.425&&-.214>r?o:u>=.166&&.234>u&&r>=-.214&&-.115>r?a:i).invert(n)},n.stream=function(n){var t=i.stream(n),e=o.stream(n),r=a.stream(n);return{point:function(n,u){t.point(n,u),e.point(n,u),r.point(n,u)},sphere:function(){t.sphere(),e.sphere(),r.sphere()},lineStart:function(){t.lineStart(),e.lineStart(),r.lineStart()},lineEnd:function(){t.lineEnd(),e.lineEnd(),r.lineEnd()},polygonStart:function(){t.polygonStart(),e.polygonStart(),r.polygonStart()},polygonEnd:function(){t.polygonEnd(),e.polygonEnd(),r.polygonEnd()}}},n.precision=function(t){return arguments.length?(i.precision(t),o.precision(t),a.precision(t),n):i.precision()},n.scale=function(t){return arguments.length?(i.scale(t),o.scale(.35*t),a.scale(t),n.translate(i.translate())):i.scale()},n.translate=function(t){if(!arguments.length)return i.translate();var c=i.scale(),s=+t[0],f=+t[1];return e=i.translate(t).clipExtent([[s-.455*c,f-.238*c],[s+.455*c,f+.238*c]]).stream(l).point,r=o.translate([s-.307*c,f+.201*c]).clipExtent([[s-.425*c+Da,f+.12*c+Da],[s-.214*c-Da,f+.234*c-Da]]).stream(l).point,u=a.translate([s-.205*c,f+.212*c]).clipExtent([[s-.214*c+Da,f+.166*c+Da],[s-.115*c-Da,f+.234*c-Da]]).stream(l).point,n},n.scale(1070)};var Fl,Hl,Ol,Il,Yl,Zl,Vl={point:b,lineStart:b,lineEnd:b,polygonStart:function(){Hl=0,Vl.lineStart=Xe},polygonEnd:function(){Vl.lineStart=Vl.lineEnd=Vl.point=b,Fl+=Ma(Hl/2)}},Xl={point:$e,lineStart:b,lineEnd:b,polygonStart:b,polygonEnd:b},$l={point:Je,lineStart:Ge,lineEnd:Ke,polygonStart:function(){$l.lineStart=Qe},polygonEnd:function(){$l.point=Je,$l.lineStart=Ge,$l.lineEnd=Ke}};aa.geo.path=function(){function n(n){return n&&("function"==typeof a&&i.pointRadius(+a.apply(this,arguments)),o&&o.valid||(o=u(i)),aa.geo.stream(n,o)),i.result()}function t(){return o=null,n}var e,r,u,i,o,a=4.5;return n.area=function(n){return Fl=0,aa.geo.stream(n,u(Vl)),Fl},n.centroid=function(n){return Al=Nl=Cl=zl=Ll=ql=Tl=Rl=Dl=0,aa.geo.stream(n,u($l)),Dl?[Tl/Dl,Rl/Dl]:ql?[zl/ql,Ll/ql]:Cl?[Al/Cl,Nl/Cl]:[0/0,0/0]},n.bounds=function(n){return Yl=Zl=-(Ol=Il=1/0),aa.geo.stream(n,u(Xl)),[[Ol,Il],[Yl,Zl]]},n.projection=function(n){return arguments.length?(u=(e=n)?n.stream||er(n):y,t()):e},n.context=function(n){return arguments.length?(i=null==(r=n)?new Be:new nr(n),"function"!=typeof a&&i.pointRadius(a),t()):r},n.pointRadius=function(t){return arguments.length?(a="function"==typeof t?t:(i.pointRadius(+t),+t),n):a},n.projection(aa.geo.albersUsa()).context(null)},aa.geo.transform=function(n){return{stream:function(t){var e=new rr(t);for(var r in n)e[r]=n[r];return e}}},rr.prototype={point:function(n,t){this.stream.point(n,t)},sphere:function(){this.stream.sphere()},lineStart:function(){this.stream.lineStart()},lineEnd:function(){this.stream.lineEnd()},polygonStart:function(){this.stream.polygonStart()},polygonEnd:function(){this.stream.polygonEnd()}},aa.geo.projection=ir,aa.geo.projectionMutator=or,(aa.geo.equirectangular=function(){return ir(lr)}).raw=lr.invert=lr,aa.geo.rotation=function(n){function t(t){return t=n(t[0]*Oa,t[1]*Oa),t[0]*=Ia,t[1]*=Ia,t}return n=sr(n[0]%360*Oa,n[1]*Oa,n.length>2?n[2]*Oa:0),t.invert=function(t){return t=n.invert(t[0]*Oa,t[1]*Oa),t[0]*=Ia,t[1]*=Ia,t},t},cr.invert=lr,aa.geo.circle=function(){function n(){var n="function"==typeof r?r.apply(this,arguments):r,t=sr(-n[0]*Oa,-n[1]*Oa,0).invert,u=[];return e(null,null,1,{point:function(n,e){u.push(n=t(n,e)),n[0]*=Ia,n[1]*=Ia}}),{type:"Polygon",coordinates:[u]}}var t,e,r=[0,0],u=6;return n.origin=function(t){return arguments.length?(r=t,n):r},n.angle=function(r){return arguments.length?(e=pr((t=+r)*Oa,u*Oa),n):t},n.precision=function(r){return arguments.length?(e=pr(t*Oa,(u=+r)*Oa),n):u},n.angle(90)},aa.geo.distance=function(n,t){var e,r=(t[0]-n[0])*Oa,u=n[1]*Oa,i=t[1]*Oa,o=Math.sin(r),a=Math.cos(r),l=Math.sin(u),c=Math.cos(u),s=Math.sin(i),f=Math.cos(i);return Math.atan2(Math.sqrt((e=f*o)*e+(e=c*s-l*f*a)*e),l*s+c*f*a)},aa.geo.graticule=function(){function n(){return{type:"MultiLineString",coordinates:t()}}function t(){return aa.range(Math.ceil(i/d)*d,u,d).map(h).concat(aa.range(Math.ceil(c/m)*m,l,m).map(g)).concat(aa.range(Math.ceil(r/p)*p,e,p).filter(function(n){return Ma(n%d)>Da}).map(s)).concat(aa.range(Math.ceil(a/v)*v,o,v).filter(function(n){return Ma(n%m)>Da}).map(f))}var e,r,u,i,o,a,l,c,s,f,h,g,p=10,v=p,d=90,m=360,y=2.5;return n.lines=function(){return t().map(function(n){return{type:"LineString",coordinates:n}})},n.outline=function(){return{type:"Polygon",coordinates:[h(i).concat(g(l).slice(1),h(u).reverse().slice(1),g(c).reverse().slice(1))]}},n.extent=function(t){return arguments.length?n.majorExtent(t).minorExtent(t):n.minorExtent()},n.majorExtent=function(t){return arguments.length?(i=+t[0][0],u=+t[1][0],c=+t[0][1],l=+t[1][1],i>u&&(t=i,i=u,u=t),c>l&&(t=c,c=l,l=t),n.precision(y)):[[i,c],[u,l]]},n.minorExtent=function(t){return arguments.length?(r=+t[0][0],e=+t[1][0],a=+t[0][1],o=+t[1][1],r>e&&(t=r,r=e,e=t),a>o&&(t=a,a=o,o=t),n.precision(y)):[[r,a],[e,o]]},n.step=function(t){return arguments.length?n.majorStep(t).minorStep(t):n.minorStep()},n.majorStep=function(t){return arguments.length?(d=+t[0],m=+t[1],n):[d,m]},n.minorStep=function(t){return arguments.length?(p=+t[0],v=+t[1],n):[p,v]},n.precision=function(t){return arguments.length?(y=+t,s=dr(a,o,90),f=mr(r,e,y),h=dr(c,l,90),g=mr(i,u,y),n):y},n.majorExtent([[-180,-90+Da],[180,90-Da]]).minorExtent([[-180,-80-Da],[180,80+Da]])},aa.geo.greatArc=function(){function n(){return{type:"LineString",coordinates:[t||r.apply(this,arguments),e||u.apply(this,arguments)]}}var t,e,r=yr,u=Mr;return n.distance=function(){return aa.geo.distance(t||r.apply(this,arguments),e||u.apply(this,arguments))},n.source=function(e){return arguments.length?(r=e,t="function"==typeof e?null:e,n):r},n.target=function(t){return arguments.length?(u=t,e="function"==typeof t?null:t,n):u},n.precision=function(){return arguments.length?n:0},n},aa.geo.interpolate=function(n,t){return xr(n[0]*Oa,n[1]*Oa,t[0]*Oa,t[1]*Oa)},aa.geo.length=function(n){return Bl=0,aa.geo.stream(n,Wl),Bl};var Bl,Wl={sphere:b,point:b,lineStart:br,lineEnd:b,polygonStart:b,polygonEnd:b},Jl=_r(function(n){return Math.sqrt(2/(1+n))},function(n){return 2*Math.asin(n/2)});(aa.geo.azimuthalEqualArea=function(){return ir(Jl)}).raw=Jl;var Gl=_r(function(n){var t=Math.acos(n);return t&&t/Math.sin(t)},y);(aa.geo.azimuthalEquidistant=function(){return ir(Gl)}).raw=Gl,(aa.geo.conicConformal=function(){return Ze(wr)}).raw=wr,(aa.geo.conicEquidistant=function(){return Ze(Sr)}).raw=Sr;var Kl=_r(function(n){return 1/n},Math.atan);(aa.geo.gnomonic=function(){return ir(Kl)}).raw=Kl,kr.invert=function(n,t){return[n,2*Math.atan(Math.exp(t))-Ha]},(aa.geo.mercator=function(){return Er(kr)}).raw=kr;var Ql=_r(function(){return 1},Math.asin);(aa.geo.orthographic=function(){return ir(Ql)}).raw=Ql;var nc=_r(function(n){return 1/(1+n)},function(n){return 2*Math.atan(n)});(aa.geo.stereographic=function(){return ir(nc)}).raw=nc,Ar.invert=function(n,t){return[-t,2*Math.atan(Math.exp(n))-Ha]},(aa.geo.transverseMercator=function(){var n=Er(Ar),t=n.center,e=n.rotate;return n.center=function(n){return n?t([-n[1],n[0]]):(n=t(),[n[1],-n[0]])},n.rotate=function(n){return n?e([n[0],n[1],n.length>2?n[2]+90:90]):(n=e(),[n[0],n[1],n[2]-90])},e([0,0,90])}).raw=Ar,aa.geom={},aa.geom.hull=function(n){function t(n){if(n.length<3)return[];var t,u=Et(e),i=Et(r),o=n.length,a=[],l=[];for(t=0;o>t;t++)a.push([+u.call(this,n[t],t),+i.call(this,n[t],t),t]);for(a.sort(Lr),t=0;o>t;t++)l.push([a[t][0],-a[t][1]]);var c=zr(a),s=zr(l),f=s[0]===c[0],h=s[s.length-1]===c[c.length-1],g=[];for(t=c.length-1;t>=0;--t)g.push(n[a[c[t]][2]]);for(t=+f;t=r&&c.x<=i&&c.y>=u&&c.y<=o?[[r,o],[i,o],[i,u],[r,u]]:[];s.point=n[a]}),t}function e(n){return n.map(function(n,t){return{x:Math.round(i(n,t)/Da)*Da,y:Math.round(o(n,t)/Da)*Da,i:t}})}var r=Nr,u=Cr,i=r,o=u,a=cc;return n?t(n):(t.links=function(n){return ou(e(n)).edges.filter(function(n){return n.l&&n.r}).map(function(t){return{source:n[t.l.i],target:n[t.r.i]}})},t.triangles=function(n){var t=[];return ou(e(n)).cells.forEach(function(e,r){for(var u,i,o=e.site,a=e.edges.sort(Zr),l=-1,c=a.length,s=a[c-1].edge,f=s.l===o?s.r:s.l;++l=c,h=r>=s,g=h<<1|f;n.leaf=!1,n=n.nodes[g]||(n.nodes[g]=fu()),f?u=c:a=c,h?o=s:l=s,i(n,t,e,r,u,o,a,l)}var s,f,h,g,p,v,d,m,y,M=Et(a),x=Et(l);if(null!=t)v=t,d=e,m=r,y=u;else if(m=y=-(v=d=1/0),f=[],h=[],p=n.length,o)for(g=0;p>g;++g)s=n[g],s.xm&&(m=s.x),s.y>y&&(y=s.y),f.push(s.x),h.push(s.y);else for(g=0;p>g;++g){var b=+M(s=n[g],g),_=+x(s,g);v>b&&(v=b),d>_&&(d=_),b>m&&(m=b),_>y&&(y=_),f.push(b),h.push(_)}var w=m-v,S=y-d;w>S?y=d+w:m=v+S;var k=fu();if(k.add=function(n){i(k,n,+M(n,++g),+x(n,g),v,d,m,y)},k.visit=function(n){hu(n,k,v,d,m,y)},k.find=function(n){return gu(k,n[0],n[1],v,d,m,y)},g=-1,null==t){for(;++g=0?n.slice(0,t):n,r=t>=0?n.slice(t+1):"in";return e=gc.get(e)||hc,r=pc.get(r)||y,xu(r(e.apply(null,la.call(arguments,1))))},aa.interpolateHcl=Tu,aa.interpolateHsl=Ru,aa.interpolateLab=Du,aa.interpolateRound=Pu,aa.transform=function(n){var t=sa.createElementNS(aa.ns.prefix.svg,"g");return(aa.transform=function(n){if(null!=n){t.setAttribute("transform",n);var e=t.transform.baseVal.consolidate()}return new ju(e?e.matrix:vc)})(n)},ju.prototype.toString=function(){return"translate("+this.translate+")rotate("+this.rotate+")skewX("+this.skew+")scale("+this.scale+")"};var vc={a:1,b:0,c:0,d:1,e:0,f:0};aa.interpolateTransform=Xu,aa.layout={},aa.layout.bundle=function(){return function(n){for(var t=[],e=-1,r=n.length;++ea*a/m){if(v>l){var c=t.charge/l;n.px-=i*c,n.py-=o*c}return!0}if(t.point&&l&&v>l){var c=t.pointCharge/l;n.px-=i*c,n.py-=o*c}}return!t.charge}}function t(n){n.px=aa.event.x,n.py=aa.event.y,l.resume()}var e,r,u,i,o,a,l={},c=aa.dispatch("start","tick","end"),s=[1,1],f=.9,h=dc,g=mc,p=-30,v=yc,d=.1,m=.64,M=[],x=[];return l.tick=function(){if((u*=.99)<.005)return e=null,c.end({type:"end",alpha:u=0}),!0;var t,r,l,h,g,v,m,y,b,_=M.length,w=x.length;for(r=0;w>r;++r)l=x[r],h=l.source,g=l.target,y=g.x-h.x,b=g.y-h.y,(v=y*y+b*b)&&(v=u*o[r]*((v=Math.sqrt(v))-i[r])/v,y*=v,b*=v,g.x-=y*(m=h.weight+g.weight?h.weight/(h.weight+g.weight):.5),g.y-=b*m,h.x+=y*(m=1-m),h.y+=b*m);if((m=u*d)&&(y=s[0]/2,b=s[1]/2,r=-1,m))for(;++r<_;)l=M[r],l.x+=(y-l.x)*m,l.y+=(b-l.y)*m;if(p)for(ei(t=aa.geom.quadtree(M),u,a),r=-1;++r<_;)(l=M[r]).fixed||t.visit(n(l));for(r=-1;++r<_;)l=M[r],l.fixed?(l.x=l.px,l.y=l.py):(l.x-=(l.px-(l.px=l.x))*f,l.y-=(l.py-(l.py=l.y))*f);c.tick({type:"tick",alpha:u})},l.nodes=function(n){return arguments.length?(M=n,l):M},l.links=function(n){return arguments.length?(x=n,l):x},l.size=function(n){return arguments.length?(s=n,l):s},l.linkDistance=function(n){return arguments.length?(h="function"==typeof n?n:+n,l):h},l.distance=l.linkDistance,l.linkStrength=function(n){return arguments.length?(g="function"==typeof n?n:+n,l):g},l.friction=function(n){return arguments.length?(f=+n,l):f},l.charge=function(n){return arguments.length?(p="function"==typeof n?n:+n,l):p},l.chargeDistance=function(n){return arguments.length?(v=n*n,l):Math.sqrt(v)},l.gravity=function(n){return arguments.length?(d=+n,l):d},l.theta=function(n){return arguments.length?(m=n*n,l):Math.sqrt(m)},l.alpha=function(n){return arguments.length?(n=+n,u?n>0?u=n:(e.c=null,e.t=0/0,e=null,c.start({type:"end",alpha:u=0})):n>0&&(c.start({type:"start",alpha:u=n}),e=Lt(l.tick)),l):u},l.start=function(){function n(n,r){if(!e){for(e=new Array(u),l=0;u>l;++l)e[l]=[];for(l=0;c>l;++l){var i=x[l];e[i.source.index].push(i.target),e[i.target.index].push(i.source)}}for(var o,a=e[t],l=-1,s=a.length;++lt;++t)(r=M[t]).index=t,r.weight=0;for(t=0;c>t;++t)r=x[t],"number"==typeof r.source&&(r.source=M[r.source]),"number"==typeof r.target&&(r.target=M[r.target]),++r.source.weight,++r.target.weight;for(t=0;u>t;++t)r=M[t],isNaN(r.x)&&(r.x=n("x",f)),isNaN(r.y)&&(r.y=n("y",v)),isNaN(r.px)&&(r.px=r.x),isNaN(r.py)&&(r.py=r.y);if(i=[],"function"==typeof h)for(t=0;c>t;++t)i[t]=+h.call(this,x[t],t);else for(t=0;c>t;++t)i[t]=h;if(o=[],"function"==typeof g)for(t=0;c>t;++t)o[t]=+g.call(this,x[t],t);else for(t=0;c>t;++t)o[t]=g;if(a=[],"function"==typeof p)for(t=0;u>t;++t)a[t]=+p.call(this,M[t],t);else for(t=0;u>t;++t)a[t]=p;return l.resume()},l.resume=function(){return l.alpha(.1)},l.stop=function(){return l.alpha(0)},l.drag=function(){return r||(r=aa.behavior.drag().origin(y).on("dragstart.force",Ku).on("drag.force",t).on("dragend.force",Qu)),arguments.length?void this.on("mouseover.force",ni).on("mouseout.force",ti).call(r):r},aa.rebind(l,c,"on")};var dc=20,mc=1,yc=1/0;aa.layout.hierarchy=function(){function n(u){var i,o=[u],a=[];for(u.depth=0;null!=(i=o.pop());)if(a.push(i),(c=e.call(n,i,i.depth))&&(l=c.length)){for(var l,c,s;--l>=0;)o.push(s=c[l]),s.parent=i,s.depth=i.depth+1;r&&(i.value=0),i.children=c}else r&&(i.value=+r.call(n,i,i.depth)||0),delete i.children;return ii(u,function(n){var e,u;t&&(e=n.children)&&e.sort(t),r&&(u=n.parent)&&(u.value+=n.value)}),a}var t=li,e=oi,r=ai;return n.sort=function(e){return arguments.length?(t=e,n):t},n.children=function(t){return arguments.length?(e=t,n):e},n.value=function(t){return arguments.length?(r=t,n):r},n.revalue=function(t){return r&&(ui(t,function(n){n.children&&(n.value=0)}),ii(t,function(t){var e;t.children||(t.value=+r.call(n,t,t.depth)||0),(e=t.parent)&&(e.value+=t.value)})),t},n},aa.layout.partition=function(){function n(t,e,r,u){var i=t.children;if(t.x=e,t.y=t.depth*u,t.dx=r,t.dy=u,i&&(o=i.length)){var o,a,l,c=-1;for(r=t.value?r/t.value:0;++cf?-1:1),p=aa.sum(c),v=p?(f-l*g)/p:0,d=aa.range(l),m=[];return null!=e&&d.sort(e===Mc?function(n,t){return c[t]-c[n]}:function(n,t){return e(o[n],o[t])}),d.forEach(function(n){m[n]={data:o[n],value:a=c[n],startAngle:s,endAngle:s+=a*v+g,padAngle:h}}),m}var t=Number,e=Mc,r=0,u=Ua,i=0;return n.value=function(e){return arguments.length?(t=e,n):t},n.sort=function(t){return arguments.length?(e=t,n):e},n.startAngle=function(t){return arguments.length?(r=t,n):r},n.endAngle=function(t){return arguments.length?(u=t,n):u},n.padAngle=function(t){return arguments.length?(i=t,n):i},n};var Mc={};aa.layout.stack=function(){function n(a,l){if(!(h=a.length))return a;var c=a.map(function(e,r){return t.call(n,e,r)}),s=c.map(function(t){return t.map(function(t,e){return[i.call(n,t,e),o.call(n,t,e)]})}),f=e.call(n,s,l);c=aa.permute(c,f),s=aa.permute(s,f);var h,g,p,v,d=r.call(n,s,l),m=c[0].length;for(p=0;m>p;++p)for(u.call(n,c[0][p],v=d[p],s[0][p][1]),g=1;h>g;++g)u.call(n,c[g][p],v+=s[g-1][p][1],s[g][p][1]);return a}var t=y,e=gi,r=pi,u=hi,i=si,o=fi;return n.values=function(e){return arguments.length?(t=e,n):t},n.order=function(t){return arguments.length?(e="function"==typeof t?t:xc.get(t)||gi,n):e},n.offset=function(t){return arguments.length?(r="function"==typeof t?t:bc.get(t)||pi,n):r},n.x=function(t){return arguments.length?(i=t,n):i},n.y=function(t){return arguments.length?(o=t,n):o},n.out=function(t){return arguments.length?(u=t,n):u},n};var xc=aa.map({"inside-out":function(n){var t,e,r=n.length,u=n.map(vi),i=n.map(di),o=aa.range(r).sort(function(n,t){return u[n]-u[t]}),a=0,l=0,c=[],s=[];for(t=0;r>t;++t)e=o[t],l>a?(a+=i[e],c.push(e)):(l+=i[e],s.push(e));return s.reverse().concat(c)},reverse:function(n){return aa.range(n.length).reverse()},"default":gi}),bc=aa.map({silhouette:function(n){var t,e,r,u=n.length,i=n[0].length,o=[],a=0,l=[];for(e=0;i>e;++e){for(t=0,r=0;u>t;t++)r+=n[t][e][1];r>a&&(a=r),o.push(r)}for(e=0;i>e;++e)l[e]=(a-o[e])/2;return l},wiggle:function(n){var t,e,r,u,i,o,a,l,c,s=n.length,f=n[0],h=f.length,g=[];for(g[0]=l=c=0,e=1;h>e;++e){for(t=0,u=0;s>t;++t)u+=n[t][e][1];for(t=0,i=0,a=f[e][0]-f[e-1][0];s>t;++t){for(r=0,o=(n[t][e][1]-n[t][e-1][1])/(2*a);t>r;++r)o+=(n[r][e][1]-n[r][e-1][1])/a;i+=o*n[t][e][1]}g[e]=l-=u?i/u*a:0,c>l&&(c=l)}for(e=0;h>e;++e)g[e]-=c;return g},expand:function(n){var t,e,r,u=n.length,i=n[0].length,o=1/u,a=[];for(e=0;i>e;++e){for(t=0,r=0;u>t;t++)r+=n[t][e][1];if(r)for(t=0;u>t;t++)n[t][e][1]/=r;else for(t=0;u>t;t++)n[t][e][1]=o}for(e=0;i>e;++e)a[e]=0;return a},zero:pi});aa.layout.histogram=function(){function n(n,i){for(var o,a,l=[],c=n.map(e,this),s=r.call(this,c,i),f=u.call(this,s,c,i),i=-1,h=c.length,g=f.length-1,p=t?1:1/h;++i0)for(i=-1;++i=s[0]&&a<=s[1]&&(o=l[aa.bisect(f,a,1,g)-1],o.y+=p,o.push(n[i]));return l}var t=!0,e=Number,r=xi,u=yi;return n.value=function(t){return arguments.length?(e=t,n):e},n.range=function(t){return arguments.length?(r=Et(t),n):r},n.bins=function(t){return arguments.length?(u="number"==typeof t?function(n){return Mi(n,t)}:Et(t),n):u},n.frequency=function(e){return arguments.length?(t=!!e,n):t},n},aa.layout.pack=function(){function n(n,i){var o=e.call(this,n,i),a=o[0],l=u[0],c=u[1],s=null==t?Math.sqrt:"function"==typeof t?t:function(){return t};if(a.x=a.y=0,ii(a,function(n){n.r=+s(n.value)}),ii(a,ki),r){var f=r*(t?1:Math.max(2*a.r/l,2*a.r/c))/2;ii(a,function(n){n.r+=f}),ii(a,ki),ii(a,function(n){n.r-=f})}return Ni(a,l/2,c/2,t?1:1/Math.max(2*a.r/l,2*a.r/c)),o}var t,e=aa.layout.hierarchy().sort(bi),r=0,u=[1,1];return n.size=function(t){return arguments.length?(u=t,n):u},n.radius=function(e){return arguments.length?(t=null==e||"function"==typeof e?e:+e,n):t},n.padding=function(t){return arguments.length?(r=+t,n):r},ri(n,e)},aa.layout.tree=function(){function n(n,u){var s=o.call(this,n,u),f=s[0],h=t(f);if(ii(h,e),h.parent.m=-h.z,ui(h,r),c)ui(f,i);else{var g=f,p=f,v=f;ui(f,function(n){n.xp.x&&(p=n),n.depth>v.depth&&(v=n)});var d=a(g,p)/2-g.x,m=l[0]/(p.x+a(p,g)/2+d),y=l[1]/(v.depth||1);ui(f,function(n){n.x=(n.x+d)*m,n.y=n.depth*y})}return s}function t(n){for(var t,e={A:null,children:[n]},r=[e];null!=(t=r.pop());)for(var u,i=t.children,o=0,a=i.length;a>o;++o)r.push((i[o]=u={_:i[o],parent:t,children:(u=i[o].children)&&u.slice()||[],A:null,a:null,z:0,m:0,c:0,s:0,t:null,i:o}).a=u);return e.children[0]}function e(n){var t=n.children,e=n.parent.children,r=n.i?e[n.i-1]:null;if(t.length){Ri(n);var i=(t[0].z+t[t.length-1].z)/2;r?(n.z=r.z+a(n._,r._),n.m=n.z-i):n.z=i}else r&&(n.z=r.z+a(n._,r._));n.parent.A=u(n,r,n.parent.A||e[0])}function r(n){n._.x=n.z+n.parent.m,n.m+=n.parent.m}function u(n,t,e){if(t){for(var r,u=n,i=n,o=t,l=u.parent.children[0],c=u.m,s=i.m,f=o.m,h=l.m;o=qi(o),u=Li(u),o&&u;)l=Li(l),i=qi(i),i.a=n,r=o.z+f-u.z-c+a(o._,u._),r>0&&(Ti(Di(o,n,e),n,r),c+=r,s+=r),f+=o.m,c+=u.m,h+=l.m,s+=i.m;o&&!qi(i)&&(i.t=o,i.m+=f-s),u&&!Li(l)&&(l.t=u,l.m+=c-h,e=n)}return e}function i(n){n.x*=l[0],n.y=n.depth*l[1]}var o=aa.layout.hierarchy().sort(null).value(null),a=zi,l=[1,1],c=null;return n.separation=function(t){return arguments.length?(a=t,n):a},n.size=function(t){return arguments.length?(c=null==(l=t)?i:null,n):c?null:l},n.nodeSize=function(t){return arguments.length?(c=null==(l=t)?null:i,n):c?l:null},ri(n,o)},aa.layout.cluster=function(){function n(n,i){var o,a=t.call(this,n,i),l=a[0],c=0;ii(l,function(n){var t=n.children;t&&t.length?(n.x=ji(t),n.y=Pi(t)):(n.x=o?c+=e(n,o):0,n.y=0,o=n)});var s=Ui(l),f=Fi(l),h=s.x-e(s,f)/2,g=f.x+e(f,s)/2;return ii(l,u?function(n){n.x=(n.x-l.x)*r[0],n.y=(l.y-n.y)*r[1]}:function(n){n.x=(n.x-h)/(g-h)*r[0],n.y=(1-(l.y?n.y/l.y:1))*r[1]}),a}var t=aa.layout.hierarchy().sort(null).value(null),e=zi,r=[1,1],u=!1;return n.separation=function(t){return arguments.length?(e=t,n):e},n.size=function(t){return arguments.length?(u=null==(r=t),n):u?null:r},n.nodeSize=function(t){return arguments.length?(u=null!=(r=t),n):u?r:null},ri(n,t)},aa.layout.treemap=function(){function n(n,t){for(var e,r,u=-1,i=n.length;++ut?0:t),e.area=isNaN(r)||0>=r?0:r}function t(e){var i=e.children;if(i&&i.length){var o,a,l,c=f(e),s=[],h=i.slice(),p=1/0,v="slice"===g?c.dx:"dice"===g?c.dy:"slice-dice"===g?1&e.depth?c.dy:c.dx:Math.min(c.dx,c.dy);for(n(h,c.dx*c.dy/e.value),s.area=0;(l=h.length)>0;)s.push(o=h[l-1]),s.area+=o.area,"squarify"!==g||(a=r(s,v))<=p?(h.pop(),p=a):(s.area-=s.pop().area,u(s,v,c,!1),v=Math.min(c.dx,c.dy),s.length=s.area=0,p=1/0);s.length&&(u(s,v,c,!0),s.length=s.area=0),i.forEach(t)}}function e(t){var r=t.children;if(r&&r.length){var i,o=f(t),a=r.slice(),l=[];for(n(a,o.dx*o.dy/t.value),l.area=0;i=a.pop();)l.push(i),l.area+=i.area,null!=i.z&&(u(l,i.z?o.dx:o.dy,o,!a.length),l.length=l.area=0);r.forEach(e)}}function r(n,t){for(var e,r=n.area,u=0,i=1/0,o=-1,a=n.length;++oe&&(i=e),e>u&&(u=e));return r*=r,t*=t,r?Math.max(t*u*p/r,r/(t*i*p)):1/0}function u(n,t,e,r){var u,i=-1,o=n.length,a=e.x,c=e.y,s=t?l(n.area/t):0;if(t==e.dx){for((r||s>e.dy)&&(s=e.dy);++ie.dx)&&(s=e.dx);++ie&&(t=1),1>e&&(n=0),function(){var e,r,u;do e=2*Math.random()-1,r=2*Math.random()-1,u=e*e+r*r;while(!u||u>1);return n+t*e*Math.sqrt(-2*Math.log(u)/u)}},logNormal:function(){var n=aa.random.normal.apply(aa,arguments);return function(){return Math.exp(n())}},bates:function(n){var t=aa.random.irwinHall(n);return function(){return t()/n}},irwinHall:function(n){return function(){for(var t=0,e=0;n>e;e++)t+=Math.random();return t}}},aa.scale={};var _c={floor:y,ceil:y};aa.scale.linear=function(){return Bi([0,1],[0,1],yu,!1)};var wc={s:1,g:1,p:1,r:1,e:1};aa.scale.log=function(){return eo(aa.scale.linear().domain([0,1]),10,!0,[1,10])};var Sc=aa.format(".0e"),kc={floor:function(n){return-Math.ceil(-n)},ceil:function(n){return-Math.floor(-n)}};aa.scale.pow=function(){return ro(aa.scale.linear(),1,[0,1])},aa.scale.sqrt=function(){return aa.scale.pow().exponent(.5)},aa.scale.ordinal=function(){return io([],{t:"range",a:[[]]})},aa.scale.category10=function(){return aa.scale.ordinal().range(Ec)},aa.scale.category20=function(){return aa.scale.ordinal().range(Ac)},aa.scale.category20b=function(){return aa.scale.ordinal().range(Nc)},aa.scale.category20c=function(){return aa.scale.ordinal().range(Cc)};var Ec=[2062260,16744206,2924588,14034728,9725885,9197131,14907330,8355711,12369186,1556175].map(Mt),Ac=[2062260,11454440,16744206,16759672,2924588,10018698,14034728,16750742,9725885,12955861,9197131,12885140,14907330,16234194,8355711,13092807,12369186,14408589,1556175,10410725].map(Mt),Nc=[3750777,5395619,7040719,10264286,6519097,9216594,11915115,13556636,9202993,12426809,15186514,15190932,8666169,11356490,14049643,15177372,8077683,10834324,13528509,14589654].map(Mt),Cc=[3244733,7057110,10406625,13032431,15095053,16616764,16625259,16634018,3253076,7652470,10607003,13101504,7695281,10394312,12369372,14342891,6513507,9868950,12434877,14277081].map(Mt);aa.scale.quantile=function(){return oo([],[])},aa.scale.quantize=function(){return ao(0,1,[0,1])},aa.scale.threshold=function(){return lo([.5],[0,1])},aa.scale.identity=function(){return co([0,1])},aa.svg={},aa.svg.arc=function(){function n(){var n=Math.max(0,+e.apply(this,arguments)),c=Math.max(0,+r.apply(this,arguments)),s=o.apply(this,arguments)-Ha,f=a.apply(this,arguments)-Ha,h=Math.abs(f-s),g=s>f?0:1;if(n>c&&(p=c,c=n,n=p),h>=Fa)return t(c,g)+(n?t(n,1-g):"")+"Z";var p,v,d,m,y,M,x,b,_,w,S,k,E=0,A=0,N=[];if((m=(+l.apply(this,arguments)||0)/2)&&(d=i===zc?Math.sqrt(n*n+c*c):+i.apply(this,arguments),g||(A*=-1),c&&(A=tt(d/c*Math.sin(m))),n&&(E=tt(d/n*Math.sin(m)))),c){y=c*Math.cos(s+A),M=c*Math.sin(s+A),x=c*Math.cos(f-A),b=c*Math.sin(f-A);var C=Math.abs(f-s-2*A)<=ja?0:1;if(A&&mo(y,M,x,b)===g^C){var z=(s+f)/2;y=c*Math.cos(z),M=c*Math.sin(z),x=b=null}}else y=M=0;if(n){_=n*Math.cos(f-E),w=n*Math.sin(f-E),S=n*Math.cos(s+E),k=n*Math.sin(s+E);var L=Math.abs(s-f+2*E)<=ja?0:1;if(E&&mo(_,w,S,k)===1-g^L){var q=(s+f)/2;_=n*Math.cos(q),w=n*Math.sin(q),S=k=null}}else _=w=0;if(h>Da&&(p=Math.min(Math.abs(c-n)/2,+u.apply(this,arguments)))>.001){v=c>n^g?0:1;var T=p,R=p;if(ja>h){var D=null==S?[_,w]:null==x?[y,M]:Tr([y,M],[S,k],[x,b],[_,w]),P=y-D[0],j=M-D[1],U=x-D[0],F=b-D[1],H=1/Math.sin(Math.acos((P*U+j*F)/(Math.sqrt(P*P+j*j)*Math.sqrt(U*U+F*F)))/2),O=Math.sqrt(D[0]*D[0]+D[1]*D[1]);R=Math.min(p,(n-O)/(H-1)),T=Math.min(p,(c-O)/(H+1))}if(null!=x){var I=yo(null==S?[_,w]:[S,k],[y,M],c,T,g),Y=yo([x,b],[_,w],c,T,g);p===T?N.push("M",I[0],"A",T,",",T," 0 0,",v," ",I[1],"A",c,",",c," 0 ",1-g^mo(I[1][0],I[1][1],Y[1][0],Y[1][1]),",",g," ",Y[1],"A",T,",",T," 0 0,",v," ",Y[0]):N.push("M",I[0],"A",T,",",T," 0 1,",v," ",Y[0])}else N.push("M",y,",",M);if(null!=S){var Z=yo([y,M],[S,k],n,-R,g),V=yo([_,w],null==x?[y,M]:[x,b],n,-R,g);p===R?N.push("L",V[0],"A",R,",",R," 0 0,",v," ",V[1],"A",n,",",n," 0 ",g^mo(V[1][0],V[1][1],Z[1][0],Z[1][1]),",",1-g," ",Z[1],"A",R,",",R," 0 0,",v," ",Z[0]):N.push("L",V[0],"A",R,",",R," 0 0,",v," ",Z[0])}else N.push("L",_,",",w)}else N.push("M",y,",",M),null!=x&&N.push("A",c,",",c," 0 ",C,",",g," ",x,",",b),N.push("L",_,",",w),null!=S&&N.push("A",n,",",n," 0 ",L,",",1-g," ",S,",",k);return N.push("Z"),N.join("")}function t(n,t){return"M0,"+n+"A"+n+","+n+" 0 1,"+t+" 0,"+-n+"A"+n+","+n+" 0 1,"+t+" 0,"+n}var e=fo,r=ho,u=so,i=zc,o=go,a=po,l=vo;return n.innerRadius=function(t){return arguments.length?(e=Et(t),n):e},n.outerRadius=function(t){return arguments.length?(r=Et(t),n):r},n.cornerRadius=function(t){return arguments.length?(u=Et(t),n):u},n.padRadius=function(t){return arguments.length?(i=t==zc?zc:Et(t),n):i},n.startAngle=function(t){return arguments.length?(o=Et(t),n):o},n.endAngle=function(t){return arguments.length?(a=Et(t),n):a},n.padAngle=function(t){return arguments.length?(l=Et(t),n):l},n.centroid=function(){var n=(+e.apply(this,arguments)+ +r.apply(this,arguments))/2,t=(+o.apply(this,arguments)+ +a.apply(this,arguments))/2-Ha;return[Math.cos(t)*n,Math.sin(t)*n]},n};var zc="auto";aa.svg.line=function(){return Mo(y)};var Lc=aa.map({linear:xo,"linear-closed":bo,step:_o,"step-before":wo,"step-after":So,basis:zo,"basis-open":Lo,"basis-closed":qo,bundle:To,cardinal:Ao,"cardinal-open":ko,"cardinal-closed":Eo,monotone:Fo});Lc.forEach(function(n,t){t.key=n,t.closed=/-closed$/.test(n)});var qc=[0,2/3,1/3,0],Tc=[0,1/3,2/3,0],Rc=[0,1/6,2/3,1/6];aa.svg.line.radial=function(){var n=Mo(Ho);return n.radius=n.x,delete n.x,n.angle=n.y,delete n.y,n},wo.reverse=So,So.reverse=wo,aa.svg.area=function(){return Oo(y)},aa.svg.area.radial=function(){var n=Oo(Ho);return n.radius=n.x,delete n.x,n.innerRadius=n.x0,delete n.x0,n.outerRadius=n.x1,delete n.x1,n.angle=n.y,delete n.y,n.startAngle=n.y0,delete n.y0,n.endAngle=n.y1,delete n.y1,n},aa.svg.chord=function(){function n(n,a){var l=t(this,i,n,a),c=t(this,o,n,a);return"M"+l.p0+r(l.r,l.p1,l.a1-l.a0)+(e(l,c)?u(l.r,l.p1,l.r,l.p0):u(l.r,l.p1,c.r,c.p0)+r(c.r,c.p1,c.a1-c.a0)+u(c.r,c.p1,l.r,l.p0))+"Z"}function t(n,t,e,r){var u=t.call(n,e,r),i=a.call(n,u,r),o=l.call(n,u,r)-Ha,s=c.call(n,u,r)-Ha;return{r:i,a0:o,a1:s,p0:[i*Math.cos(o),i*Math.sin(o)],p1:[i*Math.cos(s),i*Math.sin(s)]}}function e(n,t){return n.a0==t.a0&&n.a1==t.a1}function r(n,t,e){return"A"+n+","+n+" 0 "+ +(e>ja)+",1 "+t}function u(n,t,e,r){return"Q 0,0 "+r}var i=yr,o=Mr,a=Io,l=go,c=po;return n.radius=function(t){return arguments.length?(a=Et(t),n):a},n.source=function(t){return arguments.length?(i=Et(t),n):i},n.target=function(t){return arguments.length?(o=Et(t),n):o},n.startAngle=function(t){return arguments.length?(l=Et(t),n):l},n.endAngle=function(t){return arguments.length?(c=Et(t),n):c},n},aa.svg.diagonal=function(){function n(n,u){var i=t.call(this,n,u),o=e.call(this,n,u),a=(i.y+o.y)/2,l=[i,{x:i.x,y:a},{x:o.x,y:a},o];return l=l.map(r),"M"+l[0]+"C"+l[1]+" "+l[2]+" "+l[3]}var t=yr,e=Mr,r=Yo;return n.source=function(e){return arguments.length?(t=Et(e),n):t},n.target=function(t){return arguments.length?(e=Et(t),n):e},n.projection=function(t){return arguments.length?(r=t,n):r},n},aa.svg.diagonal.radial=function(){var n=aa.svg.diagonal(),t=Yo,e=n.projection;return n.projection=function(n){return arguments.length?e(Zo(t=n)):t},n},aa.svg.symbol=function(){function n(n,r){return(Dc.get(t.call(this,n,r))||$o)(e.call(this,n,r))}var t=Xo,e=Vo;return n.type=function(e){return arguments.length?(t=Et(e),n):t},n.size=function(t){return arguments.length?(e=Et(t),n):e},n};var Dc=aa.map({circle:$o,cross:function(n){var t=Math.sqrt(n/5)/2;return"M"+-3*t+","+-t+"H"+-t+"V"+-3*t+"H"+t+"V"+-t+"H"+3*t+"V"+t+"H"+t+"V"+3*t+"H"+-t+"V"+t+"H"+-3*t+"Z"},diamond:function(n){var t=Math.sqrt(n/(2*jc)),e=t*jc;return"M0,"+-t+"L"+e+",0 0,"+t+" "+-e+",0Z"},square:function(n){var t=Math.sqrt(n)/2;return"M"+-t+","+-t+"L"+t+","+-t+" "+t+","+t+" "+-t+","+t+"Z"},"triangle-down":function(n){var t=Math.sqrt(n/Pc),e=t*Pc/2;return"M0,"+e+"L"+t+","+-e+" "+-t+","+-e+"Z"},"triangle-up":function(n){var t=Math.sqrt(n/Pc),e=t*Pc/2;return"M0,"+-e+"L"+t+","+e+" "+-t+","+e+"Z"}});aa.svg.symbolTypes=Dc.keys();var Pc=Math.sqrt(3),jc=Math.tan(30*Oa);Na.transition=function(n){for(var t,e,r=Uc||++Ic,u=Ko(n),i=[],o=Fc||{time:Date.now(),ease:ku,delay:0,duration:250},a=-1,l=this.length;++ai;i++){u.push(t=[]);for(var e=this[i],a=0,l=e.length;l>a;a++)(r=e[a])&&n.call(r,r.__data__,a,i)&&t.push(r)}return Wo(u,this.namespace,this.id)},Oc.tween=function(n,t){var e=this.id,r=this.namespace;return arguments.length<2?this.node()[r][e].tween.get(n):Y(this,null==t?function(t){t[r][e].tween.remove(n)}:function(u){u[r][e].tween.set(n,t)})},Oc.attr=function(n,t){function e(){this.removeAttribute(a)}function r(){this.removeAttributeNS(a.space,a.local)}function u(n){return null==n?e:(n+="",function(){var t,e=this.getAttribute(a);return e!==n&&(t=o(e,n),function(n){this.setAttribute(a,t(n))})})}function i(n){return null==n?r:(n+="",function(){var t,e=this.getAttributeNS(a.space,a.local);return e!==n&&(t=o(e,n),function(n){this.setAttributeNS(a.space,a.local,t(n))})})}if(arguments.length<2){for(t in n)this.attr(t,n[t]);return this}var o="transform"==n?Xu:yu,a=aa.ns.qualify(n);return Jo(this,"attr."+n,t,a.local?i:u)},Oc.attrTween=function(n,t){function e(n,e){var r=t.call(this,n,e,this.getAttribute(u));return r&&function(n){this.setAttribute(u,r(n))}}function r(n,e){var r=t.call(this,n,e,this.getAttributeNS(u.space,u.local));return r&&function(n){this.setAttributeNS(u.space,u.local,r(n))}}var u=aa.ns.qualify(n);return this.tween("attr."+n,u.local?r:e)},Oc.style=function(n,e,r){function u(){this.style.removeProperty(n)}function i(e){return null==e?u:(e+="",function(){var u,i=t(this).getComputedStyle(this,null).getPropertyValue(n);return i!==e&&(u=yu(i,e),function(t){this.style.setProperty(n,u(t),r)})})}var o=arguments.length;if(3>o){if("string"!=typeof n){2>o&&(e="");for(r in n)this.style(r,n[r],e);return this}r=""}return Jo(this,"style."+n,e,i)},Oc.styleTween=function(n,e,r){function u(u,i){var o=e.call(this,u,i,t(this).getComputedStyle(this,null).getPropertyValue(n));return o&&function(t){this.style.setProperty(n,o(t),r)}}return arguments.length<3&&(r=""),this.tween("style."+n,u)},Oc.text=function(n){return Jo(this,"text",n,Go)},Oc.remove=function(){var n=this.namespace;return this.each("end.transition",function(){var t;this[n].count<2&&(t=this.parentNode)&&t.removeChild(this)})},Oc.ease=function(n){var t=this.id,e=this.namespace;return arguments.length<1?this.node()[e][t].ease:("function"!=typeof n&&(n=aa.ease.apply(aa,arguments)),Y(this,function(r){r[e][t].ease=n}))},Oc.delay=function(n){var t=this.id,e=this.namespace;return arguments.length<1?this.node()[e][t].delay:Y(this,"function"==typeof n?function(r,u,i){r[e][t].delay=+n.call(r,r.__data__,u,i)}:(n=+n,function(r){r[e][t].delay=n}))},Oc.duration=function(n){var t=this.id,e=this.namespace;return arguments.length<1?this.node()[e][t].duration:Y(this,"function"==typeof n?function(r,u,i){r[e][t].duration=Math.max(1,n.call(r,r.__data__,u,i))}:(n=Math.max(1,n),function(r){r[e][t].duration=n}))},Oc.each=function(n,t){var e=this.id,r=this.namespace;if(arguments.length<2){var u=Fc,i=Uc;try{Uc=e,Y(this,function(t,u,i){Fc=t[r][e],n.call(t,t.__data__,u,i)})}finally{Fc=u,Uc=i}}else Y(this,function(u){var i=u[r][e];(i.event||(i.event=aa.dispatch("start","end","interrupt"))).on(n,t)});return this},Oc.transition=function(){for(var n,t,e,r,u=this.id,i=++Ic,o=this.namespace,a=[],l=0,c=this.length;c>l;l++){a.push(n=[]);for(var t=this[l],s=0,f=t.length;f>s;s++)(e=t[s])&&(r=e[o][u],Qo(e,s,o,i,{time:r.time,ease:r.ease,delay:r.delay+r.duration,duration:r.duration})),n.push(e)}return Wo(a,o,i)},aa.svg.axis=function(){function n(n){n.each(function(){var n,c=aa.select(this),s=this.__chart__||e,f=this.__chart__=e.copy(),h=null==l?f.ticks?f.ticks.apply(f,a):f.domain():l,g=null==t?f.tickFormat?f.tickFormat.apply(f,a):y:t,p=c.selectAll(".tick").data(h,f),v=p.enter().insert("g",".domain").attr("class","tick").style("opacity",Da),d=aa.transition(p.exit()).style("opacity",Da).remove(),m=aa.transition(p.order()).style("opacity",1),M=Math.max(u,0)+o,x=Yi(f),b=c.selectAll(".domain").data([0]),_=(b.enter().append("path").attr("class","domain"),aa.transition(b));v.append("line"),v.append("text");var w,S,k,E,A=v.select("line"),N=m.select("line"),C=p.select("text").text(g),z=v.select("text"),L=m.select("text"),q="top"===r||"left"===r?-1:1;if("bottom"===r||"top"===r?(n=na,w="x",k="y",S="x2",E="y2",C.attr("dy",0>q?"0em":".71em").style("text-anchor","middle"),_.attr("d","M"+x[0]+","+q*i+"V0H"+x[1]+"V"+q*i)):(n=ta,w="y",k="x",S="y2",E="x2",C.attr("dy",".32em").style("text-anchor",0>q?"end":"start"),_.attr("d","M"+q*i+","+x[0]+"H0V"+x[1]+"H"+q*i)),A.attr(E,q*u),z.attr(k,q*M),N.attr(S,0).attr(E,q*u),L.attr(w,0).attr(k,q*M),f.rangeBand){var T=f,R=T.rangeBand()/2;s=f=function(n){return T(n)+R}}else s.rangeBand?s=f:d.call(n,f,s);v.call(n,s,f),m.call(n,f,f)})}var t,e=aa.scale.linear(),r=Yc,u=6,i=6,o=3,a=[10],l=null;return n.scale=function(t){return arguments.length?(e=t,n):e},n.orient=function(t){return arguments.length?(r=t in Zc?t+"":Yc,n):r},n.ticks=function(){return arguments.length?(a=ca(arguments),n):a},n.tickValues=function(t){return arguments.length?(l=t,n):l},n.tickFormat=function(e){return arguments.length?(t=e,n):t},n.tickSize=function(t){var e=arguments.length;return e?(u=+t,i=+arguments[e-1],n):u},n.innerTickSize=function(t){return arguments.length?(u=+t,n):u},n.outerTickSize=function(t){return arguments.length?(i=+t,n):i},n.tickPadding=function(t){return arguments.length?(o=+t,n):o},n.tickSubdivide=function(){return arguments.length&&n},n};var Yc="bottom",Zc={top:1,right:1,bottom:1,left:1};aa.svg.brush=function(){function n(t){t.each(function(){var t=aa.select(this).style("pointer-events","all").style("-webkit-tap-highlight-color","rgba(0,0,0,0)").on("mousedown.brush",i).on("touchstart.brush",i),o=t.selectAll(".background").data([0]);o.enter().append("rect").attr("class","background").style("visibility","hidden").style("cursor","crosshair"),t.selectAll(".extent").data([0]).enter().append("rect").attr("class","extent").style("cursor","move");var a=t.selectAll(".resize").data(v,y);a.exit().remove(),a.enter().append("g").attr("class",function(n){return"resize "+n}).style("cursor",function(n){return Vc[n]}).append("rect").attr("x",function(n){return/[ew]$/.test(n)?-3:null}).attr("y",function(n){return/^[ns]/.test(n)?-3:null}).attr("width",6).attr("height",6).style("visibility","hidden"),a.style("display",n.empty()?"none":null);var l,f=aa.transition(t),h=aa.transition(o);c&&(l=Yi(c),h.attr("x",l[0]).attr("width",l[1]-l[0]),r(f)),s&&(l=Yi(s),h.attr("y",l[0]).attr("height",l[1]-l[0]),u(f)),e(f)})}function e(n){n.selectAll(".resize").attr("transform",function(n){return"translate("+f[+/e$/.test(n)]+","+h[+/^s/.test(n)]+")"})}function r(n){n.select(".extent").attr("x",f[0]),n.selectAll(".extent,.n>rect,.s>rect").attr("width",f[1]-f[0])}function u(n){n.select(".extent").attr("y",h[0]),n.selectAll(".extent,.e>rect,.w>rect").attr("height",h[1]-h[0])}function i(){function i(){32==aa.event.keyCode&&(C||(M=null,L[0]-=f[1],L[1]-=h[1],C=2),S())}function v(){32==aa.event.keyCode&&2==C&&(L[0]+=f[1],L[1]+=h[1],C=0,S())}function d(){var n=aa.mouse(b),t=!1;x&&(n[0]+=x[0],n[1]+=x[1]),C||(aa.event.altKey?(M||(M=[(f[0]+f[1])/2,(h[0]+h[1])/2]),L[0]=f[+(n[0]s?(u=r,r=s):u=s),v[0]!=r||v[1]!=u?(e?a=null:o=null,v[0]=r,v[1]=u,!0):void 0}function y(){d(),k.style("pointer-events","all").selectAll(".resize").style("display",n.empty()?"none":null),aa.select("body").style("cursor",null),q.on("mousemove.brush",null).on("mouseup.brush",null).on("touchmove.brush",null).on("touchend.brush",null).on("keydown.brush",null).on("keyup.brush",null),z(),w({type:"brushend"})}var M,x,b=this,_=aa.select(aa.event.target),w=l.of(b,arguments),k=aa.select(b),E=_.datum(),A=!/^(n|s)$/.test(E)&&c,N=!/^(e|w)$/.test(E)&&s,C=_.classed("extent"),z=W(b),L=aa.mouse(b),q=aa.select(t(b)).on("keydown.brush",i).on("keyup.brush",v);if(aa.event.changedTouches?q.on("touchmove.brush",d).on("touchend.brush",y):q.on("mousemove.brush",d).on("mouseup.brush",y),k.interrupt().selectAll("*").interrupt(),C)L[0]=f[0]-L[0],L[1]=h[0]-L[1];else if(E){var T=+/w$/.test(E),R=+/^n/.test(E);x=[f[1-T]-L[0],h[1-R]-L[1]],L[0]=f[T],L[1]=h[R]}else aa.event.altKey&&(M=L.slice());k.style("pointer-events","none").selectAll(".resize").style("display",null),aa.select("body").style("cursor",_.style("cursor")),w({type:"brushstart"}),d()}var o,a,l=E(n,"brushstart","brush","brushend"),c=null,s=null,f=[0,0],h=[0,0],g=!0,p=!0,v=Xc[0];return n.event=function(n){n.each(function(){var n=l.of(this,arguments),t={x:f,y:h,i:o,j:a},e=this.__chart__||t;this.__chart__=t,Uc?aa.select(this).transition().each("start.brush",function(){o=e.i,a=e.j,f=e.x,h=e.y,n({type:"brushstart"})}).tween("brush:brush",function(){var e=Mu(f,t.x),r=Mu(h,t.y);return o=a=null,function(u){f=t.x=e(u),h=t.y=r(u),n({type:"brush",mode:"resize"})}}).each("end.brush",function(){o=t.i,a=t.j,n({type:"brush",mode:"resize"}),n({type:"brushend"})}):(n({type:"brushstart"}),n({type:"brush",mode:"resize"}),n({type:"brushend"}))})},n.x=function(t){return arguments.length?(c=t,v=Xc[!c<<1|!s],n):c},n.y=function(t){return arguments.length?(s=t,v=Xc[!c<<1|!s],n):s},n.clamp=function(t){return arguments.length?(c&&s?(g=!!t[0],p=!!t[1]):c?g=!!t:s&&(p=!!t),n):c&&s?[g,p]:c?g:s?p:null},n.extent=function(t){var e,r,u,i,l;return arguments.length?(c&&(e=t[0],r=t[1],s&&(e=e[0],r=r[0]),o=[e,r],c.invert&&(e=c(e),r=c(r)),e>r&&(l=e,e=r,r=l),(e!=f[0]||r!=f[1])&&(f=[e,r])),s&&(u=t[0],i=t[1],c&&(u=u[1],i=i[1]),a=[u,i],s.invert&&(u=s(u),i=s(i)),u>i&&(l=u,u=i,i=l),(u!=h[0]||i!=h[1])&&(h=[u,i])),n):(c&&(o?(e=o[0],r=o[1]):(e=f[0],r=f[1],c.invert&&(e=c.invert(e),r=c.invert(r)),e>r&&(l=e,e=r,r=l))),s&&(a?(u=a[0],i=a[1]):(u=h[0],i=h[1],s.invert&&(u=s.invert(u),i=s.invert(i)),u>i&&(l=u,u=i,i=l))),c&&s?[[e,u],[r,i]]:c?[e,r]:s&&[u,i])},n.clear=function(){return n.empty()||(f=[0,0],h=[0,0],o=a=null),n},n.empty=function(){return!!c&&f[0]==f[1]||!!s&&h[0]==h[1]},aa.rebind(n,l,"on")};var Vc={n:"ns-resize",e:"ew-resize",s:"ns-resize",w:"ew-resize",nw:"nwse-resize",ne:"nesw-resize",se:"nwse-resize",sw:"nesw-resize"},Xc=[["n","e","s","w","nw","ne","se","sw"],["e","w"],["n","s"],[]],$c=hl.format=yl.timeFormat,Bc=$c.utc,Wc=Bc("%Y-%m-%dT%H:%M:%S.%LZ");$c.iso=Date.prototype.toISOString&&+new Date("2000-01-01T00:00:00.000Z")?ea:Wc,ea.parse=function(n){var t=new Date(n);return isNaN(t)?null:t},ea.toString=Wc.toString,hl.second=Ht(function(n){return new gl(1e3*Math.floor(n/1e3))},function(n,t){n.setTime(n.getTime()+1e3*Math.floor(t))},function(n){return n.getSeconds()}),hl.seconds=hl.second.range,hl.seconds.utc=hl.second.utc.range,hl.minute=Ht(function(n){return new gl(6e4*Math.floor(n/6e4))},function(n,t){n.setTime(n.getTime()+6e4*Math.floor(t))},function(n){return n.getMinutes()}),hl.minutes=hl.minute.range,hl.minutes.utc=hl.minute.utc.range,hl.hour=Ht(function(n){var t=n.getTimezoneOffset()/60;return new gl(36e5*(Math.floor(n/36e5-t)+t))},function(n,t){n.setTime(n.getTime()+36e5*Math.floor(t))},function(n){return n.getHours()}),hl.hours=hl.hour.range,hl.hours.utc=hl.hour.utc.range,hl.month=Ht(function(n){return n=hl.day(n),n.setDate(1),n},function(n,t){n.setMonth(n.getMonth()+t)},function(n){return n.getMonth()}),hl.months=hl.month.range,hl.months.utc=hl.month.utc.range;var Jc=[1e3,5e3,15e3,3e4,6e4,3e5,9e5,18e5,36e5,108e5,216e5,432e5,864e5,1728e5,6048e5,2592e6,7776e6,31536e6],Gc=[[hl.second,1],[hl.second,5],[hl.second,15],[hl.second,30],[hl.minute,1],[hl.minute,5],[hl.minute,15],[hl.minute,30],[hl.hour,1],[hl.hour,3],[hl.hour,6],[hl.hour,12],[hl.day,1],[hl.day,2],[hl.week,1],[hl.month,1],[hl.month,3],[hl.year,1]],Kc=$c.multi([[".%L",function(n){return n.getMilliseconds()}],[":%S",function(n){return n.getSeconds()}],["%I:%M",function(n){return n.getMinutes()}],["%I %p",function(n){return n.getHours()}],["%a %d",function(n){return n.getDay()&&1!=n.getDate()}],["%b %d",function(n){return 1!=n.getDate()}],["%B",function(n){return n.getMonth()}],["%Y",Ce]]),Qc={range:function(n,t,e){return aa.range(Math.ceil(n/e)*e,+t,e).map(ua)},floor:y,ceil:y};Gc.year=hl.year,hl.scale=function(){return ra(aa.scale.linear(),Gc,Kc)};var ns=Gc.map(function(n){return[n[0].utc,n[1]]}),ts=Bc.multi([[".%L",function(n){return n.getUTCMilliseconds()}],[":%S",function(n){return n.getUTCSeconds()}],["%I:%M",function(n){return n.getUTCMinutes()}],["%I %p",function(n){return n.getUTCHours()}],["%a %d",function(n){return n.getUTCDay()&&1!=n.getUTCDate()}],["%b %d",function(n){return 1!=n.getUTCDate()}],["%B",function(n){return n.getUTCMonth()}],["%Y",Ce]]);ns.year=hl.year.utc,hl.scale.utc=function(){return ra(aa.scale.linear(),ns,ts)},aa.text=At(function(n){return n.responseText}),aa.json=function(n,t){return Nt(n,"application/json",ia,t)},aa.html=function(n,t){return Nt(n,"text/html",oa,t)},aa.xml=At(function(n){return n.responseXML}),"function"==typeof define&&define.amd?define(this.d3=aa):"object"==typeof module&&module.exports?module.exports=aa:this.d3=aa}(); \ No newline at end of file diff --git a/static/vendor/moment-timezone-with-data-2010-2020.js b/static/vendor/moment-timezone-with-data-2010-2020.js new file mode 100644 index 0000000..9302392 --- /dev/null +++ b/static/vendor/moment-timezone-with-data-2010-2020.js @@ -0,0 +1,1016 @@ +//! moment-timezone.js +//! version : 0.4.1 +//! author : Tim Wood +//! license : MIT +//! github.com/moment/moment-timezone + +(function (root, factory) { + "use strict"; + + /*global define*/ + if (typeof define === 'function' && define.amd) { + define(['moment'], factory); // AMD + } else if (typeof exports === 'object') { + module.exports = factory(require('moment')); // Node + } else { + factory(root.moment); // Browser + } +}(this, function (moment) { + "use strict"; + + // Do not load moment-timezone a second time. + if (moment.tz !== undefined) { + logError('Moment Timezone ' + moment.tz.version + ' was already loaded ' + (moment.tz.dataVersion ? 'with data from ' : 'without any data') + moment.tz.dataVersion); + return moment; + } + + var VERSION = "0.4.1", + zones = {}, + links = {}, + names = {}, + + momentVersion = moment.version.split('.'), + major = +momentVersion[0], + minor = +momentVersion[1]; + + // Moment.js version check + if (major < 2 || (major === 2 && minor < 6)) { + logError('Moment Timezone requires Moment.js >= 2.6.0. You are using Moment.js ' + moment.version + '. See momentjs.com'); + } + + /************************************ + Unpacking + ************************************/ + + function charCodeToInt(charCode) { + if (charCode > 96) { + return charCode - 87; + } else if (charCode > 64) { + return charCode - 29; + } + return charCode - 48; + } + + function unpackBase60(string) { + var i = 0, + parts = string.split('.'), + whole = parts[0], + fractional = parts[1] || '', + multiplier = 1, + num, + out = 0, + sign = 1; + + // handle negative numbers + if (string.charCodeAt(0) === 45) { + i = 1; + sign = -1; + } + + // handle digits before the decimal + for (i; i < whole.length; i++) { + num = charCodeToInt(whole.charCodeAt(i)); + out = 60 * out + num; + } + + // handle digits after the decimal + for (i = 0; i < fractional.length; i++) { + multiplier = multiplier / 60; + num = charCodeToInt(fractional.charCodeAt(i)); + out += num * multiplier; + } + + return out * sign; + } + + function arrayToInt (array) { + for (var i = 0; i < array.length; i++) { + array[i] = unpackBase60(array[i]); + } + } + + function intToUntil (array, length) { + for (var i = 0; i < length; i++) { + array[i] = Math.round((array[i - 1] || 0) + (array[i] * 60000)); // minutes to milliseconds + } + + array[length - 1] = Infinity; + } + + function mapIndices (source, indices) { + var out = [], i; + + for (i = 0; i < indices.length; i++) { + out[i] = source[indices[i]]; + } + + return out; + } + + function unpack (string) { + var data = string.split('|'), + offsets = data[2].split(' '), + indices = data[3].split(''), + untils = data[4].split(' '); + + arrayToInt(offsets); + arrayToInt(indices); + arrayToInt(untils); + + intToUntil(untils, indices.length); + + return { + name : data[0], + abbrs : mapIndices(data[1].split(' '), indices), + offsets : mapIndices(offsets, indices), + untils : untils + }; + } + + /************************************ + Zone object + ************************************/ + + function Zone (packedString) { + if (packedString) { + this._set(unpack(packedString)); + } + } + + Zone.prototype = { + _set : function (unpacked) { + this.name = unpacked.name; + this.abbrs = unpacked.abbrs; + this.untils = unpacked.untils; + this.offsets = unpacked.offsets; + }, + + _index : function (timestamp) { + var target = +timestamp, + untils = this.untils, + i; + + for (i = 0; i < untils.length; i++) { + if (target < untils[i]) { + return i; + } + } + }, + + parse : function (timestamp) { + var target = +timestamp, + offsets = this.offsets, + untils = this.untils, + max = untils.length - 1, + offset, offsetNext, offsetPrev, i; + + for (i = 0; i < max; i++) { + offset = offsets[i]; + offsetNext = offsets[i + 1]; + offsetPrev = offsets[i ? i - 1 : i]; + + if (offset < offsetNext && tz.moveAmbiguousForward) { + offset = offsetNext; + } else if (offset > offsetPrev && tz.moveInvalidForward) { + offset = offsetPrev; + } + + if (target < untils[i] - (offset * 60000)) { + return offsets[i]; + } + } + + return offsets[max]; + }, + + abbr : function (mom) { + return this.abbrs[this._index(mom)]; + }, + + offset : function (mom) { + return this.offsets[this._index(mom)]; + } + }; + + /************************************ + Global Methods + ************************************/ + + function normalizeName (name) { + return (name || '').toLowerCase().replace(/\//g, '_'); + } + + function addZone (packed) { + var i, name, normalized; + + if (typeof packed === "string") { + packed = [packed]; + } + + for (i = 0; i < packed.length; i++) { + name = packed[i].split('|')[0]; + normalized = normalizeName(name); + zones[normalized] = packed[i]; + names[normalized] = name; + } + } + + function getZone (name, caller) { + name = normalizeName(name); + + var zone = zones[name]; + var link; + + if (zone instanceof Zone) { + return zone; + } + + if (typeof zone === 'string') { + zone = new Zone(zone); + zones[name] = zone; + return zone; + } + + // Pass getZone to prevent recursion more than 1 level deep + if (links[name] && caller !== getZone && (link = getZone(links[name], getZone))) { + zone = zones[name] = new Zone(); + zone._set(link); + zone.name = names[name]; + return zone; + } + + return null; + } + + function getNames () { + var i, out = []; + + for (i in names) { + if (names.hasOwnProperty(i) && (zones[i] || zones[links[i]]) && names[i]) { + out.push(names[i]); + } + } + + return out.sort(); + } + + function addLink (aliases) { + var i, alias, normal0, normal1; + + if (typeof aliases === "string") { + aliases = [aliases]; + } + + for (i = 0; i < aliases.length; i++) { + alias = aliases[i].split('|'); + + normal0 = normalizeName(alias[0]); + normal1 = normalizeName(alias[1]); + + links[normal0] = normal1; + names[normal0] = alias[0]; + + links[normal1] = normal0; + names[normal1] = alias[1]; + } + } + + function loadData (data) { + addZone(data.zones); + addLink(data.links); + tz.dataVersion = data.version; + } + + function zoneExists (name) { + if (!zoneExists.didShowError) { + zoneExists.didShowError = true; + logError("moment.tz.zoneExists('" + name + "') has been deprecated in favor of !moment.tz.zone('" + name + "')"); + } + return !!getZone(name); + } + + function needsOffset (m) { + return !!(m._a && (m._tzm === undefined)); + } + + function logError (message) { + if (typeof console !== 'undefined' && typeof console.error === 'function') { + console.error(message); + } + } + + /************************************ + moment.tz namespace + ************************************/ + + function tz (input) { + var args = Array.prototype.slice.call(arguments, 0, -1), + name = arguments[arguments.length - 1], + zone = getZone(name), + out = moment.utc.apply(null, args); + + if (zone && !moment.isMoment(input) && needsOffset(out)) { + out.add(zone.parse(out), 'minutes'); + } + + out.tz(name); + + return out; + } + + tz.version = VERSION; + tz.dataVersion = ''; + tz._zones = zones; + tz._links = links; + tz._names = names; + tz.add = addZone; + tz.link = addLink; + tz.load = loadData; + tz.zone = getZone; + tz.zoneExists = zoneExists; // deprecated in 0.1.0 + tz.names = getNames; + tz.Zone = Zone; + tz.unpack = unpack; + tz.unpackBase60 = unpackBase60; + tz.needsOffset = needsOffset; + tz.moveInvalidForward = true; + tz.moveAmbiguousForward = false; + + /************************************ + Interface with Moment.js + ************************************/ + + var fn = moment.fn; + + moment.tz = tz; + + moment.defaultZone = null; + + moment.updateOffset = function (mom, keepTime) { + var zone = moment.defaultZone, + offset; + + if (mom._z === undefined) { + if (zone && needsOffset(mom) && !mom._isUTC) { + mom._d = moment.utc(mom._a)._d; + mom.utc().add(zone.parse(mom), 'minutes'); + } + mom._z = zone; + } + if (mom._z) { + offset = mom._z.offset(mom); + if (Math.abs(offset) < 16) { + offset = offset / 60; + } + if (mom.utcOffset !== undefined) { + mom.utcOffset(-offset, keepTime); + } else { + mom.zone(offset, keepTime); + } + } + }; + + fn.tz = function (name) { + if (name) { + this._z = getZone(name); + if (this._z) { + moment.updateOffset(this); + } else { + logError("Moment Timezone has no data for " + name + ". See http://momentjs.com/timezone/docs/#/data-loading/."); + } + return this; + } + if (this._z) { return this._z.name; } + }; + + function abbrWrap (old) { + return function () { + if (this._z) { return this._z.abbr(this); } + return old.call(this); + }; + } + + function resetZoneWrap (old) { + return function () { + this._z = null; + return old.apply(this, arguments); + }; + } + + fn.zoneName = abbrWrap(fn.zoneName); + fn.zoneAbbr = abbrWrap(fn.zoneAbbr); + fn.utc = resetZoneWrap(fn.utc); + + moment.tz.setDefault = function(name) { + if (major < 2 || (major === 2 && minor < 9)) { + logError('Moment Timezone setDefault() requires Moment.js >= 2.9.0. You are using Moment.js ' + moment.version + '.'); + } + moment.defaultZone = name ? getZone(name) : null; + return moment; + }; + + // Cloning a moment should include the _z property. + var momentProperties = moment.momentProperties; + if (Object.prototype.toString.call(momentProperties) === '[object Array]') { + // moment 2.8.1+ + momentProperties.push('_z'); + momentProperties.push('_a'); + } else if (momentProperties) { + // moment 2.7.0 + momentProperties._z = null; + } + + loadData({ + "version": "2015g", + "zones": [ + "Africa/Abidjan|GMT|0|0|", + "Africa/Addis_Ababa|EAT|-30|0|", + "Africa/Algiers|CET|-10|0|", + "Africa/Bangui|WAT|-10|0|", + "Africa/Blantyre|CAT|-20|0|", + "Africa/Cairo|EET EEST|-20 -30|010101010|1Cby0 Fb0 c10 8n0 8Nd0 gL0 e10 mn0", + "Africa/Casablanca|WET WEST|0 -10|01010101010101010101010101010101010101010|1Cco0 Db0 1zd0 Lz0 1Nf0 wM0 co0 go0 1o00 s00 dA0 vc0 11A0 A00 e00 y00 11A0 uM0 e00 Dc0 11A0 s00 e00 IM0 WM0 mo0 gM0 LA0 WM0 jA0 e00 Rc0 11A0 e00 e00 U00 11A0 8o0 e00 11A0", + "Africa/Ceuta|CET CEST|-10 -20|01010101010101010101010|1BWp0 1qM0 WM0 1qM0 WM0 1qM0 11A0 1o00 11A0 1o00 11A0 1o00 11A0 1qM0 WM0 1qM0 WM0 1qM0 11A0 1o00 11A0 1o00", + "Africa/Johannesburg|SAST|-20|0|", + "Africa/Tripoli|EET CET CEST|-20 -10 -20|0120|1IlA0 TA0 1o00", + "Africa/Windhoek|WAST WAT|-20 -10|01010101010101010101010|1C1c0 11B0 1nX0 11B0 1nX0 11B0 1qL0 WN0 1qL0 11B0 1nX0 11B0 1nX0 11B0 1nX0 11B0 1nX0 11B0 1qL0 WN0 1qL0 11B0", + "America/Adak|HST HDT|a0 90|01010101010101010101010|1BR00 1zb0 Op0 1zb0 Op0 1zb0 Op0 1zb0 Op0 1zb0 Op0 1zb0 Rd0 1zb0 Op0 1zb0 Op0 1zb0 Op0 1zb0 Op0 1zb0", + "America/Anchorage|AKST AKDT|90 80|01010101010101010101010|1BQX0 1zb0 Op0 1zb0 Op0 1zb0 Op0 1zb0 Op0 1zb0 Op0 1zb0 Rd0 1zb0 Op0 1zb0 Op0 1zb0 Op0 1zb0 Op0 1zb0", + "America/Anguilla|AST|40|0|", + "America/Araguaina|BRT BRST|30 20|010|1IdD0 Lz0", + "America/Argentina/Buenos_Aires|ART|30|0|", + "America/Asuncion|PYST PYT|30 40|01010101010101010101010|1C430 1a10 1fz0 1a10 1fz0 1cN0 17b0 1ip0 17b0 1ip0 17b0 1ip0 19X0 1fB0 19X0 1fB0 19X0 1ip0 17b0 1ip0 17b0 1ip0", + "America/Atikokan|EST|50|0|", + "America/Bahia|BRT BRST|30 20|010|1FJf0 Rb0", + "America/Bahia_Banderas|MST CDT CST|70 50 60|01212121212121212121212|1C1l0 1nW0 11B0 1nX0 11B0 1nX0 14p0 1lb0 14p0 1lb0 14p0 1lb0 14p0 1nX0 11B0 1nX0 11B0 1nX0 14p0 1lb0 14p0 1lb0", + "America/Belem|BRT|30|0|", + "America/Belize|CST|60|0|", + "America/Boa_Vista|AMT|40|0|", + "America/Bogota|COT|50|0|", + "America/Boise|MST MDT|70 60|01010101010101010101010|1BQV0 1zb0 Op0 1zb0 Op0 1zb0 Op0 1zb0 Op0 1zb0 Op0 1zb0 Rd0 1zb0 Op0 1zb0 Op0 1zb0 Op0 1zb0 Op0 1zb0", + "America/Campo_Grande|AMST AMT|30 40|01010101010101010101010|1BIr0 1zd0 On0 1zd0 Rb0 1zd0 Lz0 1C10 Lz0 1C10 On0 1zd0 On0 1zd0 On0 1zd0 On0 1C10 Lz0 1C10 Lz0 1C10", + "America/Cancun|CST CDT EST|60 50 50|010101010102|1C1k0 1nX0 11B0 1nX0 11B0 1nX0 14p0 1lb0 14p0 1lb0 Dd0", + "America/Caracas|VET|4u|0|", + "America/Cayenne|GFT|30|0|", + "America/Cayman|EST EDT|50 40|01010101010|1Qtj0 1zb0 Op0 1zb0 Op0 1zb0 Op0 1zb0 Op0 1zb0", + "America/Chicago|CST CDT|60 50|01010101010101010101010|1BQU0 1zb0 Op0 1zb0 Op0 1zb0 Op0 1zb0 Op0 1zb0 Op0 1zb0 Rd0 1zb0 Op0 1zb0 Op0 1zb0 Op0 1zb0 Op0 1zb0", + "America/Chihuahua|MST MDT|70 60|01010101010101010101010|1C1l0 1nX0 11B0 1nX0 11B0 1nX0 14p0 1lb0 14p0 1lb0 14p0 1lb0 14p0 1nX0 11B0 1nX0 11B0 1nX0 14p0 1lb0 14p0 1lb0", + "America/Creston|MST|70|0|", + "America/Dawson|PST PDT|80 70|01010101010101010101010|1BQW0 1zb0 Op0 1zb0 Op0 1zb0 Op0 1zb0 Op0 1zb0 Op0 1zb0 Rd0 1zb0 Op0 1zb0 Op0 1zb0 Op0 1zb0 Op0 1zb0", + "America/Detroit|EST EDT|50 40|01010101010101010101010|1BQT0 1zb0 Op0 1zb0 Op0 1zb0 Op0 1zb0 Op0 1zb0 Op0 1zb0 Rd0 1zb0 Op0 1zb0 Op0 1zb0 Op0 1zb0 Op0 1zb0", + "America/Eirunepe|AMT ACT|40 50|01|1KLE0", + "America/Fort_Nelson|PST PDT MST|80 70 70|010101010102|1BQW0 1zb0 Op0 1zb0 Op0 1zb0 Op0 1zb0 Op0 1zb0 Op0", + "America/Glace_Bay|AST ADT|40 30|01010101010101010101010|1BQS0 1zb0 Op0 1zb0 Op0 1zb0 Op0 1zb0 Op0 1zb0 Op0 1zb0 Rd0 1zb0 Op0 1zb0 Op0 1zb0 Op0 1zb0 Op0 1zb0", + "America/Godthab|WGT WGST|30 20|01010101010101010101010|1BWp0 1qM0 WM0 1qM0 WM0 1qM0 11A0 1o00 11A0 1o00 11A0 1o00 11A0 1qM0 WM0 1qM0 WM0 1qM0 11A0 1o00 11A0 1o00", + "America/Goose_Bay|AST ADT|40 30|01010101010101010101010|1BQQ1 1zb0 Op0 1zcX Op0 1zb0 Op0 1zb0 Op0 1zb0 Op0 1zb0 Rd0 1zb0 Op0 1zb0 Op0 1zb0 Op0 1zb0 Op0 1zb0", + "America/Grand_Turk|EST EDT AST|50 40 40|0101010101012|1BQT0 1zb0 Op0 1zb0 Op0 1zb0 Op0 1zb0 Op0 1zb0 Op0 1zb0", + "America/Guayaquil|ECT|50|0|", + "America/Guyana|GYT|40|0|", + "America/Havana|CST CDT|50 40|01010101010101010101010|1BQR0 1wo0 U00 1zc0 U00 1qM0 Oo0 1zc0 Oo0 1zc0 Oo0 1zc0 Rc0 1zc0 Oo0 1zc0 Oo0 1zc0 Oo0 1zc0 Oo0 1zc0", + "America/La_Paz|BOT|40|0|", + "America/Lima|PET|50|0|", + "America/Merida|CST CDT|60 50|01010101010101010101010|1C1k0 1nX0 11B0 1nX0 11B0 1nX0 14p0 1lb0 14p0 1lb0 14p0 1lb0 14p0 1nX0 11B0 1nX0 11B0 1nX0 14p0 1lb0 14p0 1lb0", + "America/Metlakatla|PST|80|0|", + "America/Miquelon|PMST PMDT|30 20|01010101010101010101010|1BQR0 1zb0 Op0 1zb0 Op0 1zb0 Op0 1zb0 Op0 1zb0 Op0 1zb0 Rd0 1zb0 Op0 1zb0 Op0 1zb0 Op0 1zb0 Op0 1zb0", + "America/Montevideo|UYST UYT|20 30|010101010101|1BQQ0 1ld0 14n0 1ld0 14n0 1o10 11z0 1o10 11z0 1o10 11z0", + "America/Noronha|FNT|20|0|", + "America/North_Dakota/Beulah|MST MDT CST CDT|70 60 60 50|01232323232323232323232|1BQV0 1zb0 Oo0 1zb0 Op0 1zb0 Op0 1zb0 Op0 1zb0 Op0 1zb0 Rd0 1zb0 Op0 1zb0 Op0 1zb0 Op0 1zb0 Op0 1zb0", + "America/Paramaribo|SRT|30|0|", + "America/Port-au-Prince|EST EDT|50 40|0101010101010101010|1GI70 1zb0 Op0 1zb0 Op0 1zb0 Op0 1zb0 Rd0 1zb0 Op0 1zb0 Op0 1zb0 Op0 1zb0 Op0 1zb0", + "America/Santa_Isabel|PST PDT|80 70|01010101010101010101010|1C1m0 1nX0 11B0 1nX0 11B0 1nX0 14p0 1lb0 14p0 1lb0 14p0 1lb0 14p0 1nX0 11B0 1nX0 11B0 1nX0 14p0 1lb0 14p0 1lb0", + "America/Santiago|CLST CLT CLT|30 40 30|010101010102|1C1f0 1fB0 1nX0 G10 1EL0 Op0 1zb0 Rd0 1wn0 Rd0 1wn0", + "America/Sao_Paulo|BRST BRT|20 30|01010101010101010101010|1BIq0 1zd0 On0 1zd0 Rb0 1zd0 Lz0 1C10 Lz0 1C10 On0 1zd0 On0 1zd0 On0 1zd0 On0 1C10 Lz0 1C10 Lz0 1C10", + "America/Scoresbysund|EGT EGST|10 0|01010101010101010101010|1BWp0 1qM0 WM0 1qM0 WM0 1qM0 11A0 1o00 11A0 1o00 11A0 1o00 11A0 1qM0 WM0 1qM0 WM0 1qM0 11A0 1o00 11A0 1o00", + "America/St_Johns|NST NDT|3u 2u|01010101010101010101010|1BQPv 1zb0 Op0 1zcX Op0 1zb0 Op0 1zb0 Op0 1zb0 Op0 1zb0 Rd0 1zb0 Op0 1zb0 Op0 1zb0 Op0 1zb0 Op0 1zb0", + "Antarctica/Casey|CAST AWST|-b0 -80|0101|1BN30 40P0 KL0", + "Antarctica/Davis|DAVT DAVT|-50 -70|0101|1BPw0 3Wn0 KN0", + "Antarctica/DumontDUrville|DDUT|-a0|0|", + "Antarctica/Macquarie|AEDT MIST|-b0 -b0|01|1C140", + "Antarctica/Mawson|MAWT|-50|0|", + "Antarctica/McMurdo|NZDT NZST|-d0 -c0|01010101010101010101010|1C120 1a00 1fA0 1a00 1fA0 1cM0 1fA0 1a00 1fA0 1a00 1fA0 1a00 1fA0 1a00 1fA0 1a00 1fA0 1cM0 1fA0 1a00 1fA0 1a00", + "Antarctica/Rothera|ROTT|30|0|", + "Antarctica/Syowa|SYOT|-30|0|", + "Antarctica/Troll|UTC CEST|0 -20|01010101010101010101010|1BWp0 1qM0 WM0 1qM0 WM0 1qM0 11A0 1o00 11A0 1o00 11A0 1o00 11A0 1qM0 WM0 1qM0 WM0 1qM0 11A0 1o00 11A0 1o00", + "Antarctica/Vostok|VOST|-60|0|", + "Asia/Aden|AST|-30|0|", + "Asia/Almaty|ALMT|-60|0|", + "Asia/Amman|EET EEST|-20 -30|010101010101010101010|1BVy0 1qM0 11A0 1o00 11A0 4bX0 Dd0 1qM0 WM0 1qM0 11A0 1o00 11A0 1o00 11A0 1o00 11A0 1o00 11A0 1qM0", + "Asia/Anadyr|ANAT ANAST ANAT|-c0 -c0 -b0|0120|1BWe0 1qN0 WM0", + "Asia/Aqtau|AQTT|-50|0|", + "Asia/Ashgabat|TMT|-50|0|", + "Asia/Baku|AZT AZST|-40 -50|01010101010101010101010|1BWo0 1qM0 WM0 1qM0 WM0 1qM0 11A0 1o00 11A0 1o00 11A0 1o00 11A0 1qM0 WM0 1qM0 WM0 1qM0 11A0 1o00 11A0 1o00", + "Asia/Bangkok|ICT|-70|0|", + "Asia/Beirut|EET EEST|-20 -30|01010101010101010101010|1BWm0 1qL0 WN0 1qL0 WN0 1qL0 11B0 1nX0 11B0 1nX0 11B0 1nX0 11B0 1qL0 WN0 1qL0 WN0 1qL0 11B0 1nX0 11B0 1nX0", + "Asia/Bishkek|KGT|-60|0|", + "Asia/Brunei|BNT|-80|0|", + "Asia/Calcutta|IST|-5u|0|", + "Asia/Chita|YAKT YAKST YAKT IRKT|-90 -a0 -a0 -80|01023|1BWh0 1qM0 WM0 8Hz0", + "Asia/Choibalsan|CHOT CHOST|-80 -90|0101010101010|1O8G0 1cJ0 1cP0 1cJ0 1cP0 1fx0 1cP0 1cJ0 1cP0 1cJ0 1cP0 1cJ0", + "Asia/Chongqing|CST|-80|0|", + "Asia/Dacca|BDT|-60|0|", + "Asia/Damascus|EET EEST|-20 -30|01010101010101010101010|1C0m0 1nX0 11B0 1nX0 11B0 1nX0 11B0 1nX0 11B0 1qL0 WN0 1qL0 WN0 1qL0 11B0 1nX0 11B0 1nX0 11B0 1nX0 11B0 1qL0", + "Asia/Dili|TLT|-90|0|", + "Asia/Dubai|GST|-40|0|", + "Asia/Dushanbe|TJT|-50|0|", + "Asia/Gaza|EET EEST|-20 -30|01010101010101010101010|1BVW1 SKX 1xd1 MKX 1AN0 1a00 1fA0 1cL0 1cN0 1nX0 1210 1nz0 1210 1nz0 14N0 1nz0 1210 1nz0 1210 1nz0 1210 1nz0", + "Asia/Hebron|EET EEST|-20 -30|0101010101010101010101010|1BVy0 Tb0 1xd1 MKX bB0 cn0 1cN0 1a00 1fA0 1cL0 1cN0 1nX0 1210 1nz0 1210 1nz0 14N0 1nz0 1210 1nz0 1210 1nz0 1210 1nz0", + "Asia/Hong_Kong|HKT|-80|0|", + "Asia/Hovd|HOVT HOVST|-70 -80|0101010101010|1O8H0 1cJ0 1cP0 1cJ0 1cP0 1fx0 1cP0 1cJ0 1cP0 1cJ0 1cP0 1cJ0", + "Asia/Irkutsk|IRKT IRKST IRKT|-80 -90 -90|01020|1BWi0 1qM0 WM0 8Hz0", + "Asia/Istanbul|EET EEST|-20 -30|01010101010101010101010|1BWp0 1qM0 Xc0 1qo0 WM0 1qM0 11A0 1o00 1200 1nA0 11A0 1tA0 U00 1qM0 WM0 1qM0 WM0 1qM0 11A0 1o00 11A0 1o00", + "Asia/Jakarta|WIB|-70|0|", + "Asia/Jayapura|WIT|-90|0|", + "Asia/Jerusalem|IST IDT|-20 -30|01010101010101010101010|1BVA0 17X0 1kp0 1dz0 1c10 1aL0 1eN0 1oL0 10N0 1oL0 10N0 1oL0 10N0 1rz0 W10 1rz0 W10 1rz0 10N0 1oL0 10N0 1oL0", + "Asia/Kabul|AFT|-4u|0|", + "Asia/Kamchatka|PETT PETST PETT|-c0 -c0 -b0|0120|1BWe0 1qN0 WM0", + "Asia/Karachi|PKT|-50|0|", + "Asia/Kashgar|XJT|-60|0|", + "Asia/Kathmandu|NPT|-5J|0|", + "Asia/Khandyga|VLAT VLAST VLAT YAKT YAKT|-a0 -b0 -b0 -a0 -90|010234|1BWg0 1qM0 WM0 17V0 7zD0", + "Asia/Krasnoyarsk|KRAT KRAST KRAT|-70 -80 -80|01020|1BWj0 1qM0 WM0 8Hz0", + "Asia/Kuala_Lumpur|MYT|-80|0|", + "Asia/Magadan|MAGT MAGST MAGT MAGT|-b0 -c0 -c0 -a0|01023|1BWf0 1qM0 WM0 8Hz0", + "Asia/Makassar|WITA|-80|0|", + "Asia/Manila|PHT|-80|0|", + "Asia/Nicosia|EET EEST|-20 -30|01010101010101010101010|1BWp0 1qM0 WM0 1qM0 WM0 1qM0 11A0 1o00 11A0 1o00 11A0 1o00 11A0 1qM0 WM0 1qM0 WM0 1qM0 11A0 1o00 11A0 1o00", + "Asia/Novokuznetsk|KRAT NOVST NOVT NOVT|-70 -70 -60 -70|01230|1BWj0 1qN0 WM0 8Hz0", + "Asia/Novosibirsk|NOVT NOVST NOVT|-60 -70 -70|01020|1BWk0 1qM0 WM0 8Hz0", + "Asia/Omsk|OMST OMSST OMST|-60 -70 -70|01020|1BWk0 1qM0 WM0 8Hz0", + "Asia/Oral|ORAT|-50|0|", + "Asia/Pyongyang|KST KST|-90 -8u|01|1P4D0", + "Asia/Qyzylorda|QYZT|-60|0|", + "Asia/Rangoon|MMT|-6u|0|", + "Asia/Sakhalin|SAKT SAKST SAKT|-a0 -b0 -b0|01020|1BWg0 1qM0 WM0 8Hz0", + "Asia/Samarkand|UZT|-50|0|", + "Asia/Seoul|KST|-90|0|", + "Asia/Singapore|SGT|-80|0|", + "Asia/Srednekolymsk|MAGT MAGST MAGT SRET|-b0 -c0 -c0 -b0|01023|1BWf0 1qM0 WM0 8Hz0", + "Asia/Tbilisi|GET|-40|0|", + "Asia/Tehran|IRST IRDT|-3u -4u|01010101010101010101010|1BTUu 1dz0 1cp0 1dz0 1cp0 1dz0 1cN0 1dz0 1cp0 1dz0 1cp0 1dz0 1cp0 1dz0 1cN0 1dz0 1cp0 1dz0 1cp0 1dz0 1cp0 1dz0", + "Asia/Thimbu|BTT|-60|0|", + "Asia/Tokyo|JST|-90|0|", + "Asia/Ulaanbaatar|ULAT ULAST|-80 -90|0101010101010|1O8G0 1cJ0 1cP0 1cJ0 1cP0 1fx0 1cP0 1cJ0 1cP0 1cJ0 1cP0 1cJ0", + "Asia/Ust-Nera|MAGT MAGST MAGT VLAT VLAT|-b0 -c0 -c0 -b0 -a0|010234|1BWf0 1qM0 WM0 17V0 7zD0", + "Asia/Vladivostok|VLAT VLAST VLAT|-a0 -b0 -b0|01020|1BWg0 1qM0 WM0 8Hz0", + "Asia/Yakutsk|YAKT YAKST YAKT|-90 -a0 -a0|01020|1BWh0 1qM0 WM0 8Hz0", + "Asia/Yekaterinburg|YEKT YEKST YEKT|-50 -60 -60|01020|1BWl0 1qM0 WM0 8Hz0", + "Asia/Yerevan|AMT AMST|-40 -50|01010|1BWm0 1qM0 WM0 1qM0", + "Atlantic/Azores|AZOT AZOST|10 0|01010101010101010101010|1BWp0 1qM0 WM0 1qM0 WM0 1qM0 11A0 1o00 11A0 1o00 11A0 1o00 11A0 1qM0 WM0 1qM0 WM0 1qM0 11A0 1o00 11A0 1o00", + "Atlantic/Canary|WET WEST|0 -10|01010101010101010101010|1BWp0 1qM0 WM0 1qM0 WM0 1qM0 11A0 1o00 11A0 1o00 11A0 1o00 11A0 1qM0 WM0 1qM0 WM0 1qM0 11A0 1o00 11A0 1o00", + "Atlantic/Cape_Verde|CVT|10|0|", + "Atlantic/South_Georgia|GST|20|0|", + "Atlantic/Stanley|FKST FKT|30 40|010|1C6R0 U10", + "Australia/ACT|AEDT AEST|-b0 -a0|01010101010101010101010|1C140 1cM0 1cM0 1cM0 1cM0 1fA0 1cM0 1cM0 1cM0 1cM0 1cM0 1cM0 1cM0 1cM0 1cM0 1cM0 1cM0 1fA0 1cM0 1cM0 1cM0 1cM0", + "Australia/Adelaide|ACDT ACST|-au -9u|01010101010101010101010|1C14u 1cM0 1cM0 1cM0 1cM0 1fA0 1cM0 1cM0 1cM0 1cM0 1cM0 1cM0 1cM0 1cM0 1cM0 1cM0 1cM0 1fA0 1cM0 1cM0 1cM0 1cM0", + "Australia/Brisbane|AEST|-a0|0|", + "Australia/Darwin|ACST|-9u|0|", + "Australia/Eucla|ACWST|-8J|0|", + "Australia/LHI|LHDT LHST|-b0 -au|01010101010101010101010|1C130 1cMu 1cLu 1cMu 1cLu 1fAu 1cLu 1cMu 1cLu 1cMu 1cLu 1cMu 1cLu 1cMu 1cLu 1cMu 1cLu 1fAu 1cLu 1cMu 1cLu 1cMu", + "Australia/Perth|AWST|-80|0|", + "Chile/EasterIsland|EASST EAST EAST|50 60 50|010101010102|1C1f0 1fB0 1nX0 G10 1EL0 Op0 1zb0 Rd0 1wn0 Rd0 1wn0", + "Eire|GMT IST|0 -10|01010101010101010101010|1BWp0 1qM0 WM0 1qM0 WM0 1qM0 11A0 1o00 11A0 1o00 11A0 1o00 11A0 1qM0 WM0 1qM0 WM0 1qM0 11A0 1o00 11A0 1o00", + "Etc/GMT+1|GMT+1|10|0|", + "Etc/GMT+10|GMT+10|a0|0|", + "Etc/GMT+11|GMT+11|b0|0|", + "Etc/GMT+12|GMT+12|c0|0|", + "Etc/GMT+2|GMT+2|20|0|", + "Etc/GMT+3|GMT+3|30|0|", + "Etc/GMT+4|GMT+4|40|0|", + "Etc/GMT+5|GMT+5|50|0|", + "Etc/GMT+6|GMT+6|60|0|", + "Etc/GMT+7|GMT+7|70|0|", + "Etc/GMT+8|GMT+8|80|0|", + "Etc/GMT+9|GMT+9|90|0|", + "Etc/GMT-1|GMT-1|-10|0|", + "Etc/GMT-10|GMT-10|-a0|0|", + "Etc/GMT-11|GMT-11|-b0|0|", + "Etc/GMT-12|GMT-12|-c0|0|", + "Etc/GMT-13|GMT-13|-d0|0|", + "Etc/GMT-14|GMT-14|-e0|0|", + "Etc/GMT-2|GMT-2|-20|0|", + "Etc/GMT-3|GMT-3|-30|0|", + "Etc/GMT-4|GMT-4|-40|0|", + "Etc/GMT-5|GMT-5|-50|0|", + "Etc/GMT-6|GMT-6|-60|0|", + "Etc/GMT-7|GMT-7|-70|0|", + "Etc/GMT-8|GMT-8|-80|0|", + "Etc/GMT-9|GMT-9|-90|0|", + "Etc/UCT|UCT|0|0|", + "Etc/UTC|UTC|0|0|", + "Europe/Belfast|GMT BST|0 -10|01010101010101010101010|1BWp0 1qM0 WM0 1qM0 WM0 1qM0 11A0 1o00 11A0 1o00 11A0 1o00 11A0 1qM0 WM0 1qM0 WM0 1qM0 11A0 1o00 11A0 1o00", + "Europe/Chisinau|EET EEST|-20 -30|01010101010101010101010|1BWo0 1qM0 WM0 1qM0 WM0 1qM0 11A0 1o00 11A0 1o00 11A0 1o00 11A0 1qM0 WM0 1qM0 WM0 1qM0 11A0 1o00 11A0 1o00", + "Europe/Kaliningrad|EET EEST FET|-20 -30 -30|01020|1BWo0 1qM0 WM0 8Hz0", + "Europe/Minsk|EET EEST FET MSK|-20 -30 -30 -30|01023|1BWo0 1qM0 WM0 8Hy0", + "Europe/Moscow|MSK MSD MSK|-30 -40 -40|01020|1BWn0 1qM0 WM0 8Hz0", + "Europe/Samara|SAMT SAMST SAMT|-40 -40 -30|0120|1BWm0 1qN0 WM0", + "Europe/Simferopol|EET EEST MSK MSK|-20 -30 -40 -30|01010101023|1BWp0 1qM0 WM0 1qM0 WM0 1qM0 11A0 1o00 11z0 1nW0", + "HST|HST|a0|0|", + "Indian/Chagos|IOT|-60|0|", + "Indian/Christmas|CXT|-70|0|", + "Indian/Cocos|CCT|-6u|0|", + "Indian/Kerguelen|TFT|-50|0|", + "Indian/Mahe|SCT|-40|0|", + "Indian/Maldives|MVT|-50|0|", + "Indian/Mauritius|MUT|-40|0|", + "Indian/Reunion|RET|-40|0|", + "Kwajalein|MHT|-c0|0|", + "MET|MET MEST|-10 -20|01010101010101010101010|1BWp0 1qM0 WM0 1qM0 WM0 1qM0 11A0 1o00 11A0 1o00 11A0 1o00 11A0 1qM0 WM0 1qM0 WM0 1qM0 11A0 1o00 11A0 1o00", + "NZ-CHAT|CHADT CHAST|-dJ -cJ|01010101010101010101010|1C120 1a00 1fA0 1a00 1fA0 1cM0 1fA0 1a00 1fA0 1a00 1fA0 1a00 1fA0 1a00 1fA0 1a00 1fA0 1cM0 1fA0 1a00 1fA0 1a00", + "Pacific/Apia|SST SDT WSDT WSST|b0 a0 -e0 -d0|01012323232323232323232|1Dbn0 1ff0 1a00 CI0 AQ0 1cM0 1fA0 1a00 1fA0 1a00 1fA0 1a00 1fA0 1a00 1fA0 1a00 1fA0 1cM0 1fA0 1a00 1fA0 1a00", + "Pacific/Bougainville|PGT BST|-a0 -b0|01|1NwE0", + "Pacific/Chuuk|CHUT|-a0|0|", + "Pacific/Efate|VUT|-b0|0|", + "Pacific/Enderbury|PHOT|-d0|0|", + "Pacific/Fakaofo|TKT TKT|b0 -d0|01|1Gfn0", + "Pacific/Fiji|FJST FJT|-d0 -c0|01010101010101010101010|1BWe0 1o00 Rc0 1wo0 Ao0 1Nc0 Ao0 1Q00 xz0 1SN0 uM0 1SM0 uM0 1VA0 s00 1VA0 uM0 1SM0 uM0 1SM0 uM0 1SM0", + "Pacific/Funafuti|TVT|-c0|0|", + "Pacific/Galapagos|GALT|60|0|", + "Pacific/Gambier|GAMT|90|0|", + "Pacific/Guadalcanal|SBT|-b0|0|", + "Pacific/Guam|ChST|-a0|0|", + "Pacific/Kiritimati|LINT|-e0|0|", + "Pacific/Kosrae|KOST|-b0|0|", + "Pacific/Marquesas|MART|9u|0|", + "Pacific/Midway|SST|b0|0|", + "Pacific/Nauru|NRT|-c0|0|", + "Pacific/Niue|NUT|b0|0|", + "Pacific/Norfolk|NFT NFT|-bu -b0|01|1PoCu", + "Pacific/Noumea|NCT|-b0|0|", + "Pacific/Palau|PWT|-90|0|", + "Pacific/Pohnpei|PONT|-b0|0|", + "Pacific/Port_Moresby|PGT|-a0|0|", + "Pacific/Rarotonga|CKT|a0|0|", + "Pacific/Tahiti|TAHT|a0|0|", + "Pacific/Tarawa|GILT|-c0|0|", + "Pacific/Tongatapu|TOT|-d0|0|", + "Pacific/Wake|WAKT|-c0|0|", + "Pacific/Wallis|WFT|-c0|0|" + ], + "links": [ + "Africa/Abidjan|Africa/Accra", + "Africa/Abidjan|Africa/Bamako", + "Africa/Abidjan|Africa/Banjul", + "Africa/Abidjan|Africa/Bissau", + "Africa/Abidjan|Africa/Conakry", + "Africa/Abidjan|Africa/Dakar", + "Africa/Abidjan|Africa/Freetown", + "Africa/Abidjan|Africa/Lome", + "Africa/Abidjan|Africa/Monrovia", + "Africa/Abidjan|Africa/Nouakchott", + "Africa/Abidjan|Africa/Ouagadougou", + "Africa/Abidjan|Africa/Sao_Tome", + "Africa/Abidjan|Africa/Timbuktu", + "Africa/Abidjan|America/Danmarkshavn", + "Africa/Abidjan|Atlantic/Reykjavik", + "Africa/Abidjan|Atlantic/St_Helena", + "Africa/Abidjan|Etc/GMT", + "Africa/Abidjan|Etc/GMT+0", + "Africa/Abidjan|Etc/GMT-0", + "Africa/Abidjan|Etc/GMT0", + "Africa/Abidjan|Etc/Greenwich", + "Africa/Abidjan|GMT", + "Africa/Abidjan|GMT+0", + "Africa/Abidjan|GMT-0", + "Africa/Abidjan|GMT0", + "Africa/Abidjan|Greenwich", + "Africa/Abidjan|Iceland", + "Africa/Addis_Ababa|Africa/Asmara", + "Africa/Addis_Ababa|Africa/Asmera", + "Africa/Addis_Ababa|Africa/Dar_es_Salaam", + "Africa/Addis_Ababa|Africa/Djibouti", + "Africa/Addis_Ababa|Africa/Juba", + "Africa/Addis_Ababa|Africa/Kampala", + "Africa/Addis_Ababa|Africa/Khartoum", + "Africa/Addis_Ababa|Africa/Mogadishu", + "Africa/Addis_Ababa|Africa/Nairobi", + "Africa/Addis_Ababa|Indian/Antananarivo", + "Africa/Addis_Ababa|Indian/Comoro", + "Africa/Addis_Ababa|Indian/Mayotte", + "Africa/Algiers|Africa/Tunis", + "Africa/Bangui|Africa/Brazzaville", + "Africa/Bangui|Africa/Douala", + "Africa/Bangui|Africa/Kinshasa", + "Africa/Bangui|Africa/Lagos", + "Africa/Bangui|Africa/Libreville", + "Africa/Bangui|Africa/Luanda", + "Africa/Bangui|Africa/Malabo", + "Africa/Bangui|Africa/Ndjamena", + "Africa/Bangui|Africa/Niamey", + "Africa/Bangui|Africa/Porto-Novo", + "Africa/Blantyre|Africa/Bujumbura", + "Africa/Blantyre|Africa/Gaborone", + "Africa/Blantyre|Africa/Harare", + "Africa/Blantyre|Africa/Kigali", + "Africa/Blantyre|Africa/Lubumbashi", + "Africa/Blantyre|Africa/Lusaka", + "Africa/Blantyre|Africa/Maputo", + "Africa/Cairo|Egypt", + "Africa/Casablanca|Africa/El_Aaiun", + "Africa/Ceuta|Arctic/Longyearbyen", + "Africa/Ceuta|Atlantic/Jan_Mayen", + "Africa/Ceuta|CET", + "Africa/Ceuta|Europe/Amsterdam", + "Africa/Ceuta|Europe/Andorra", + "Africa/Ceuta|Europe/Belgrade", + "Africa/Ceuta|Europe/Berlin", + "Africa/Ceuta|Europe/Bratislava", + "Africa/Ceuta|Europe/Brussels", + "Africa/Ceuta|Europe/Budapest", + "Africa/Ceuta|Europe/Busingen", + "Africa/Ceuta|Europe/Copenhagen", + "Africa/Ceuta|Europe/Gibraltar", + "Africa/Ceuta|Europe/Ljubljana", + "Africa/Ceuta|Europe/Luxembourg", + "Africa/Ceuta|Europe/Madrid", + "Africa/Ceuta|Europe/Malta", + "Africa/Ceuta|Europe/Monaco", + "Africa/Ceuta|Europe/Oslo", + "Africa/Ceuta|Europe/Paris", + "Africa/Ceuta|Europe/Podgorica", + "Africa/Ceuta|Europe/Prague", + "Africa/Ceuta|Europe/Rome", + "Africa/Ceuta|Europe/San_Marino", + "Africa/Ceuta|Europe/Sarajevo", + "Africa/Ceuta|Europe/Skopje", + "Africa/Ceuta|Europe/Stockholm", + "Africa/Ceuta|Europe/Tirane", + "Africa/Ceuta|Europe/Vaduz", + "Africa/Ceuta|Europe/Vatican", + "Africa/Ceuta|Europe/Vienna", + "Africa/Ceuta|Europe/Warsaw", + "Africa/Ceuta|Europe/Zagreb", + "Africa/Ceuta|Europe/Zurich", + "Africa/Ceuta|Poland", + "Africa/Johannesburg|Africa/Maseru", + "Africa/Johannesburg|Africa/Mbabane", + "Africa/Tripoli|Libya", + "America/Adak|America/Atka", + "America/Adak|US/Aleutian", + "America/Anchorage|America/Juneau", + "America/Anchorage|America/Nome", + "America/Anchorage|America/Sitka", + "America/Anchorage|America/Yakutat", + "America/Anchorage|US/Alaska", + "America/Anguilla|America/Antigua", + "America/Anguilla|America/Aruba", + "America/Anguilla|America/Barbados", + "America/Anguilla|America/Blanc-Sablon", + "America/Anguilla|America/Curacao", + "America/Anguilla|America/Dominica", + "America/Anguilla|America/Grenada", + "America/Anguilla|America/Guadeloupe", + "America/Anguilla|America/Kralendijk", + "America/Anguilla|America/Lower_Princes", + "America/Anguilla|America/Marigot", + "America/Anguilla|America/Martinique", + "America/Anguilla|America/Montserrat", + "America/Anguilla|America/Port_of_Spain", + "America/Anguilla|America/Puerto_Rico", + "America/Anguilla|America/Santo_Domingo", + "America/Anguilla|America/St_Barthelemy", + "America/Anguilla|America/St_Kitts", + "America/Anguilla|America/St_Lucia", + "America/Anguilla|America/St_Thomas", + "America/Anguilla|America/St_Vincent", + "America/Anguilla|America/Tortola", + "America/Anguilla|America/Virgin", + "America/Argentina/Buenos_Aires|America/Argentina/Catamarca", + "America/Argentina/Buenos_Aires|America/Argentina/ComodRivadavia", + "America/Argentina/Buenos_Aires|America/Argentina/Cordoba", + "America/Argentina/Buenos_Aires|America/Argentina/Jujuy", + "America/Argentina/Buenos_Aires|America/Argentina/La_Rioja", + "America/Argentina/Buenos_Aires|America/Argentina/Mendoza", + "America/Argentina/Buenos_Aires|America/Argentina/Rio_Gallegos", + "America/Argentina/Buenos_Aires|America/Argentina/Salta", + "America/Argentina/Buenos_Aires|America/Argentina/San_Juan", + "America/Argentina/Buenos_Aires|America/Argentina/San_Luis", + "America/Argentina/Buenos_Aires|America/Argentina/Tucuman", + "America/Argentina/Buenos_Aires|America/Argentina/Ushuaia", + "America/Argentina/Buenos_Aires|America/Buenos_Aires", + "America/Argentina/Buenos_Aires|America/Catamarca", + "America/Argentina/Buenos_Aires|America/Cordoba", + "America/Argentina/Buenos_Aires|America/Jujuy", + "America/Argentina/Buenos_Aires|America/Mendoza", + "America/Argentina/Buenos_Aires|America/Rosario", + "America/Atikokan|America/Coral_Harbour", + "America/Atikokan|America/Jamaica", + "America/Atikokan|America/Panama", + "America/Atikokan|EST", + "America/Atikokan|Jamaica", + "America/Belem|America/Fortaleza", + "America/Belem|America/Maceio", + "America/Belem|America/Recife", + "America/Belem|America/Santarem", + "America/Belize|America/Costa_Rica", + "America/Belize|America/El_Salvador", + "America/Belize|America/Guatemala", + "America/Belize|America/Managua", + "America/Belize|America/Regina", + "America/Belize|America/Swift_Current", + "America/Belize|America/Tegucigalpa", + "America/Belize|Canada/East-Saskatchewan", + "America/Belize|Canada/Saskatchewan", + "America/Boa_Vista|America/Manaus", + "America/Boa_Vista|America/Porto_Velho", + "America/Boa_Vista|Brazil/West", + "America/Boise|America/Cambridge_Bay", + "America/Boise|America/Denver", + "America/Boise|America/Edmonton", + "America/Boise|America/Inuvik", + "America/Boise|America/Ojinaga", + "America/Boise|America/Shiprock", + "America/Boise|America/Yellowknife", + "America/Boise|Canada/Mountain", + "America/Boise|MST7MDT", + "America/Boise|Navajo", + "America/Boise|US/Mountain", + "America/Campo_Grande|America/Cuiaba", + "America/Chicago|America/Indiana/Knox", + "America/Chicago|America/Indiana/Tell_City", + "America/Chicago|America/Knox_IN", + "America/Chicago|America/Matamoros", + "America/Chicago|America/Menominee", + "America/Chicago|America/North_Dakota/Center", + "America/Chicago|America/North_Dakota/New_Salem", + "America/Chicago|America/Rainy_River", + "America/Chicago|America/Rankin_Inlet", + "America/Chicago|America/Resolute", + "America/Chicago|America/Winnipeg", + "America/Chicago|CST6CDT", + "America/Chicago|Canada/Central", + "America/Chicago|US/Central", + "America/Chicago|US/Indiana-Starke", + "America/Chihuahua|America/Mazatlan", + "America/Chihuahua|Mexico/BajaSur", + "America/Creston|America/Dawson_Creek", + "America/Creston|America/Hermosillo", + "America/Creston|America/Phoenix", + "America/Creston|MST", + "America/Creston|US/Arizona", + "America/Dawson|America/Ensenada", + "America/Dawson|America/Los_Angeles", + "America/Dawson|America/Tijuana", + "America/Dawson|America/Vancouver", + "America/Dawson|America/Whitehorse", + "America/Dawson|Canada/Pacific", + "America/Dawson|Canada/Yukon", + "America/Dawson|Mexico/BajaNorte", + "America/Dawson|PST8PDT", + "America/Dawson|US/Pacific", + "America/Dawson|US/Pacific-New", + "America/Detroit|America/Fort_Wayne", + "America/Detroit|America/Indiana/Indianapolis", + "America/Detroit|America/Indiana/Marengo", + "America/Detroit|America/Indiana/Petersburg", + "America/Detroit|America/Indiana/Vevay", + "America/Detroit|America/Indiana/Vincennes", + "America/Detroit|America/Indiana/Winamac", + "America/Detroit|America/Indianapolis", + "America/Detroit|America/Iqaluit", + "America/Detroit|America/Kentucky/Louisville", + "America/Detroit|America/Kentucky/Monticello", + "America/Detroit|America/Louisville", + "America/Detroit|America/Montreal", + "America/Detroit|America/Nassau", + "America/Detroit|America/New_York", + "America/Detroit|America/Nipigon", + "America/Detroit|America/Pangnirtung", + "America/Detroit|America/Thunder_Bay", + "America/Detroit|America/Toronto", + "America/Detroit|Canada/Eastern", + "America/Detroit|EST5EDT", + "America/Detroit|US/East-Indiana", + "America/Detroit|US/Eastern", + "America/Detroit|US/Michigan", + "America/Eirunepe|America/Porto_Acre", + "America/Eirunepe|America/Rio_Branco", + "America/Eirunepe|Brazil/Acre", + "America/Glace_Bay|America/Halifax", + "America/Glace_Bay|America/Moncton", + "America/Glace_Bay|America/Thule", + "America/Glace_Bay|Atlantic/Bermuda", + "America/Glace_Bay|Canada/Atlantic", + "America/Havana|Cuba", + "America/Merida|America/Mexico_City", + "America/Merida|America/Monterrey", + "America/Merida|Mexico/General", + "America/Metlakatla|Pacific/Pitcairn", + "America/Noronha|Brazil/DeNoronha", + "America/Santiago|Antarctica/Palmer", + "America/Santiago|Chile/Continental", + "America/Sao_Paulo|Brazil/East", + "America/St_Johns|Canada/Newfoundland", + "Antarctica/McMurdo|Antarctica/South_Pole", + "Antarctica/McMurdo|NZ", + "Antarctica/McMurdo|Pacific/Auckland", + "Asia/Aden|Asia/Baghdad", + "Asia/Aden|Asia/Bahrain", + "Asia/Aden|Asia/Kuwait", + "Asia/Aden|Asia/Qatar", + "Asia/Aden|Asia/Riyadh", + "Asia/Aqtau|Asia/Aqtobe", + "Asia/Ashgabat|Asia/Ashkhabad", + "Asia/Bangkok|Asia/Ho_Chi_Minh", + "Asia/Bangkok|Asia/Phnom_Penh", + "Asia/Bangkok|Asia/Saigon", + "Asia/Bangkok|Asia/Vientiane", + "Asia/Calcutta|Asia/Colombo", + "Asia/Calcutta|Asia/Kolkata", + "Asia/Chongqing|Asia/Chungking", + "Asia/Chongqing|Asia/Harbin", + "Asia/Chongqing|Asia/Macao", + "Asia/Chongqing|Asia/Macau", + "Asia/Chongqing|Asia/Shanghai", + "Asia/Chongqing|Asia/Taipei", + "Asia/Chongqing|PRC", + "Asia/Chongqing|ROC", + "Asia/Dacca|Asia/Dhaka", + "Asia/Dubai|Asia/Muscat", + "Asia/Hong_Kong|Hongkong", + "Asia/Istanbul|Europe/Istanbul", + "Asia/Istanbul|Turkey", + "Asia/Jakarta|Asia/Pontianak", + "Asia/Jerusalem|Asia/Tel_Aviv", + "Asia/Jerusalem|Israel", + "Asia/Kashgar|Asia/Urumqi", + "Asia/Kathmandu|Asia/Katmandu", + "Asia/Kuala_Lumpur|Asia/Kuching", + "Asia/Makassar|Asia/Ujung_Pandang", + "Asia/Nicosia|EET", + "Asia/Nicosia|Europe/Athens", + "Asia/Nicosia|Europe/Bucharest", + "Asia/Nicosia|Europe/Helsinki", + "Asia/Nicosia|Europe/Kiev", + "Asia/Nicosia|Europe/Mariehamn", + "Asia/Nicosia|Europe/Nicosia", + "Asia/Nicosia|Europe/Riga", + "Asia/Nicosia|Europe/Sofia", + "Asia/Nicosia|Europe/Tallinn", + "Asia/Nicosia|Europe/Uzhgorod", + "Asia/Nicosia|Europe/Vilnius", + "Asia/Nicosia|Europe/Zaporozhye", + "Asia/Samarkand|Asia/Tashkent", + "Asia/Seoul|ROK", + "Asia/Singapore|Singapore", + "Asia/Tehran|Iran", + "Asia/Thimbu|Asia/Thimphu", + "Asia/Tokyo|Japan", + "Asia/Ulaanbaatar|Asia/Ulan_Bator", + "Atlantic/Canary|Atlantic/Faeroe", + "Atlantic/Canary|Atlantic/Faroe", + "Atlantic/Canary|Atlantic/Madeira", + "Atlantic/Canary|Europe/Lisbon", + "Atlantic/Canary|Portugal", + "Atlantic/Canary|WET", + "Australia/ACT|Australia/Canberra", + "Australia/ACT|Australia/Currie", + "Australia/ACT|Australia/Hobart", + "Australia/ACT|Australia/Melbourne", + "Australia/ACT|Australia/NSW", + "Australia/ACT|Australia/Sydney", + "Australia/ACT|Australia/Tasmania", + "Australia/ACT|Australia/Victoria", + "Australia/Adelaide|Australia/Broken_Hill", + "Australia/Adelaide|Australia/South", + "Australia/Adelaide|Australia/Yancowinna", + "Australia/Brisbane|Australia/Lindeman", + "Australia/Brisbane|Australia/Queensland", + "Australia/Darwin|Australia/North", + "Australia/LHI|Australia/Lord_Howe", + "Australia/Perth|Australia/West", + "Chile/EasterIsland|Pacific/Easter", + "Eire|Europe/Dublin", + "Etc/UCT|UCT", + "Etc/UTC|Etc/Universal", + "Etc/UTC|Etc/Zulu", + "Etc/UTC|UTC", + "Etc/UTC|Universal", + "Etc/UTC|Zulu", + "Europe/Belfast|Europe/Guernsey", + "Europe/Belfast|Europe/Isle_of_Man", + "Europe/Belfast|Europe/Jersey", + "Europe/Belfast|Europe/London", + "Europe/Belfast|GB", + "Europe/Belfast|GB-Eire", + "Europe/Chisinau|Europe/Tiraspol", + "Europe/Moscow|Europe/Volgograd", + "Europe/Moscow|W-SU", + "HST|Pacific/Honolulu", + "HST|Pacific/Johnston", + "HST|US/Hawaii", + "Kwajalein|Pacific/Kwajalein", + "Kwajalein|Pacific/Majuro", + "NZ-CHAT|Pacific/Chatham", + "Pacific/Chuuk|Pacific/Truk", + "Pacific/Chuuk|Pacific/Yap", + "Pacific/Guam|Pacific/Saipan", + "Pacific/Midway|Pacific/Pago_Pago", + "Pacific/Midway|Pacific/Samoa", + "Pacific/Midway|US/Samoa", + "Pacific/Pohnpei|Pacific/Ponape" + ] + }); + + + return moment; +})); diff --git a/templates/purple_robot_base.html b/templates/purple_robot_base.html index 9b5984a..535a66f 100644 --- a/templates/purple_robot_base.html +++ b/templates/purple_robot_base.html @@ -1,4 +1,5 @@ {% load staticfiles %} +{% load purple_robot %} @@ -27,7 +28,9 @@ - + + + + + - + - + + @@ -55,30 +61,28 @@ white-space:pre; max-width:none; } + + tr.muted { + background-color: #EEEEEE; + } + + tr.muted .text-danger { + color: #515151; + } + - +
{% block 'content' %} {% endblock %} diff --git a/templates/purple_robot_device.html b/templates/purple_robot_device.html index f8149af..1fd9ef2 100644 --- a/templates/purple_robot_device.html +++ b/templates/purple_robot_device.html @@ -4,9 +4,11 @@ {% block 'title' %}{{ device.name }}{% endblock %} {% block 'content' %} {% pr_device_custom_console %} -
-

{{ device.name }}

-
+ {% if pr_show_device_id_header %} +
+

{{ device.name }}

+
+ {% endif %}
{% with sanity_messages=device.sanity_messages %} {% if sanity_messages == None %} @@ -266,86 +268,88 @@

Details

{% pr_data_size device.total_readings_size %}
-

Add New Note

+ {% if pr_show_notes %} +

Add New Note

-
- - - - +
+ + + + - + -

Previous Notes

- {% for note in device.notes.all %} -
- {{ note.added }}
-
{{ note.note|linebreaks }}
-
- {% empty %} - No notes for this device… - {% endfor %} +

Previous Notes

+ {% for note in device.notes.all %} +
+ {{ note.added }}
+
{{ note.note|linebreaks }}
+
+ {% empty %} + No notes for this device… + {% endfor %} + {% endif %} -

Basic Settings

- [ Common settings ]
+

Installed Apps

@@ -354,8 +358,6 @@

Installed Apps

{% endfor %}
- - {% endblock %} {% block 'page_script' %} $(document).ready(function() diff --git a/templates/purple_robot_device_probe.html b/templates/purple_robot_device_probe.html index b1db364..3dcb338 100644 --- a/templates/purple_robot_device_probe.html +++ b/templates/purple_robot_device_probe.html @@ -3,7 +3,7 @@ {% load static %} {% block 'title' %}{{ device.name }}{% endblock %} {% block 'content' %} -
+

{{ device.name }}: {{ short_name }}

diff --git a/templates/purple_robot_home.html b/templates/purple_robot_home.html index da26253..c276d89 100644 --- a/templates/purple_robot_home.html +++ b/templates/purple_robot_home.html @@ -28,6 +28,11 @@

{{ group.name }}

{% if unattached_devices.count > 0 %}

Unaffiliated Devices

{% pr_group_table '' %} +
+ + Add New Group + +
{% endif %}
").addClass("cw").text("#"));c.isBefore(f.clone().endOf("w"));)b.append(a("").addClass("dow").text(c.format("dd"))),c.add(1,"d");o.find(".datepicker-days thead").append(b)},M=function(a){return d.disabledDates[a.format("YYYY-MM-DD")]===!0},N=function(a){return d.enabledDates[a.format("YYYY-MM-DD")]===!0},O=function(a){return d.disabledHours[a.format("H")]===!0},P=function(a){return d.enabledHours[a.format("H")]===!0},Q=function(b,c){if(!b.isValid())return!1;if(d.disabledDates&&"d"===c&&M(b))return!1;if(d.enabledDates&&"d"===c&&!N(b))return!1;if(d.minDate&&b.isBefore(d.minDate,c))return!1;if(d.maxDate&&b.isAfter(d.maxDate,c))return!1;if(d.daysOfWeekDisabled&&"d"===c&&-1!==d.daysOfWeekDisabled.indexOf(b.day()))return!1;if(d.disabledHours&&("h"===c||"m"===c||"s"===c)&&O(b))return!1;if(d.enabledHours&&("h"===c||"m"===c||"s"===c)&&!P(b))return!1;if(d.disabledTimeIntervals&&("h"===c||"m"===c||"s"===c)){var e=!1;if(a.each(d.disabledTimeIntervals,function(){return b.isBetween(this[0],this[1])?(e=!0,!1):void 0}),e)return!1}return!0},R=function(){for(var b=[],c=f.clone().startOf("y").startOf("d");c.isSame(f,"y");)b.push(a("").attr("data-action","selectMonth").addClass("month").text(c.format("MMM"))),c.add(1,"M");o.find(".datepicker-months td").empty().append(b)},S=function(){var b=o.find(".datepicker-months"),c=b.find("th"),g=b.find("tbody").find("span");c.eq(0).find("span").attr("title",d.tooltips.prevYear),c.eq(1).attr("title",d.tooltips.selectYear),c.eq(2).find("span").attr("title",d.tooltips.nextYear),b.find(".disabled").removeClass("disabled"),Q(f.clone().subtract(1,"y"),"y")||c.eq(0).addClass("disabled"),c.eq(1).text(f.year()),Q(f.clone().add(1,"y"),"y")||c.eq(2).addClass("disabled"),g.removeClass("active"),e.isSame(f,"y")&&!m&&g.eq(e.month()).addClass("active"),g.each(function(b){Q(f.clone().month(b),"M")||a(this).addClass("disabled")})},T=function(){var a=o.find(".datepicker-years"),b=a.find("th"),c=f.clone().subtract(5,"y"),g=f.clone().add(6,"y"),h="";for(b.eq(0).find("span").attr("title",d.tooltips.prevDecade),b.eq(1).attr("title",d.tooltips.selectDecade),b.eq(2).find("span").attr("title",d.tooltips.nextDecade),a.find(".disabled").removeClass("disabled"),d.minDate&&d.minDate.isAfter(c,"y")&&b.eq(0).addClass("disabled"),b.eq(1).text(c.year()+"-"+g.year()),d.maxDate&&d.maxDate.isBefore(g,"y")&&b.eq(2).addClass("disabled");!c.isAfter(g,"y");)h+=''+c.year()+"",c.add(1,"y");a.find("td").html(h)},U=function(){var a=o.find(".datepicker-decades"),c=a.find("th"),g=b({y:f.year()-f.year()%100-1}),h=g.clone().add(100,"y"),i=g.clone(),j="";for(c.eq(0).find("span").attr("title",d.tooltips.prevCentury),c.eq(2).find("span").attr("title",d.tooltips.nextCentury),a.find(".disabled").removeClass("disabled"),(g.isSame(b({y:1900}))||d.minDate&&d.minDate.isAfter(g,"y"))&&c.eq(0).addClass("disabled"),c.eq(1).text(g.year()+"-"+h.year()),(g.isSame(b({y:2e3}))||d.maxDate&&d.maxDate.isBefore(h,"y"))&&c.eq(2).addClass("disabled");!g.isAfter(h,"y");)j+=''+(g.year()+1)+" - "+(g.year()+12)+"",g.add(12,"y");j+="",a.find("td").html(j),c.eq(1).text(i.year()+1+"-"+g.year())},V=function(){var b,c,g,h,i=o.find(".datepicker-days"),j=i.find("th"),k=[];if(A()){for(j.eq(0).find("span").attr("title",d.tooltips.prevMonth),j.eq(1).attr("title",d.tooltips.selectMonth),j.eq(2).find("span").attr("title",d.tooltips.nextMonth),i.find(".disabled").removeClass("disabled"),j.eq(1).text(f.format(d.dayViewHeaderFormat)),Q(f.clone().subtract(1,"M"),"M")||j.eq(0).addClass("disabled"),Q(f.clone().add(1,"M"),"M")||j.eq(2).addClass("disabled"),b=f.clone().startOf("M").startOf("w").startOf("d"),h=0;42>h;h++)0===b.weekday()&&(c=a("
'+b.week()+"'+b.date()+"
'+c.format(h?"HH":"hh")+"
'+c.format("mm")+"
'+c.format("ss")+"
+ + + + + + + + + + {% for device in group.devices.all %} + + + + + + + {% endfor %} + +
Device IDSoftwareLast Config FetchLast Reading
{{ device.device_id }}{{ device.config_last_user_agent }}{{ device.config_last_fetched }}{{ device.latest_reading_date }}
+ {% endfor %} + +

Unaffiliated ({{ unaffiliated.count }} Devices)

+ + + + + + + + + + + + {% for device in unaffiliated %} + + + + + + + {% endfor %} + +
Device IDSoftwareLast Config FetchLast Reading
{{ device.device_id }}{{ device.config_last_user_agent }}{{ device.config_last_fetched }}{{ device.latest_reading_date }}
+ +

Phantoms ({{ phantoms|length }} Devices)

+ + + + + + + + + + {% for item in phantoms.iteritems %} + + + + + {% endfor %} + +
Device HashLast Activity
{{ item.0 }}{{ item.1 }}
+ + + + + +{% endblock %} + +{% block 'page_script' %} + $(document).ready(function() { + var timeUnit = { + 'name': '4 hour', + 'seconds': 3600 * 4, + 'formatter': function(d) + { + var date = new Date(d); + + var minutes = "" + date.getMinutes(); + + if (minutes.length == 1) + minutes = "0" + minutes + + var am = "am"; + + var hours = date.getHours(); + + if (hours < 12) + { + if (hours == 0) + hours = 12; + } + else + { + if (hours > 12) + hours -= 12; + + am = "pm"; + } + + return "" + hours + ":" + minutes + " " + am; + } + }; + + var startDate = moment(); + + startDate.set('hour', 0); + startDate.set('minute', 0); + startDate.set('second', 0); + startDate.set('millisecond', 0); + + var minSeries = [{ "y": 0, "x": startDate.unix() }, { "y": 0, "x": moment().unix() }]; + + var uploadSeries = []; + + {% for item in payload_uploads %} + uploadSeries.push({ "y": {{ item.count }}, "x": moment("{{ item.date }}").unix() }); + {% endfor %} + + var serverLoadSeries = []; + + {% for item in server_performance %} + serverLoadSeries.push({ "y": {{ item.load_five }}, "x": moment("{{ item.sample_date }}").unix() }); + {% endfor %} + + var readingIngestSeries = []; + + {% for item in ingest_performance %} + readingIngestSeries.push({ "y": ({{ item.num_extracted }} / ({{ item.extraction_time }} + {{ item.query_time }})), "x": moment("{{ item.sample_date }}").unix() }); + {% endfor %} + + var readingMirrorSeries = []; + var readingMirrorLocalDb = []; + var readingMirrorLocalApp = []; + var readingMirrorRemoteDb = []; + var readingMirrorTimes = []; + + readingMirrorTimes.push(minSeries[0]); + readingMirrorLocalDb.push(minSeries[0]); + readingMirrorLocalApp.push(minSeries[0]); + readingMirrorRemoteDb.push(minSeries[0]); + + {% for item in mirror_performance %} + readingMirrorSeries.push({ "y": ({{ item.num_mirrored }} / ({{ item.extraction_time }} + {{ item.query_time }})), "x": moment("{{ item.sample_date }}").unix() }); + + {% if item.local_db != None %} + {% if item.local_app != None %} + {% if item.remote_db != None %} + if (readingMirrorTimes.length == 1) + { + readingMirrorRemoteDb.push({ "y": null, "x": (moment("{{ item.sample_date }}").unix() - 1) }); + readingMirrorLocalDb.push({ "y": null, "x": (moment("{{ item.sample_date }}").unix() - 1) }); + readingMirrorLocalApp.push({ "y": null, "x": (moment("{{ item.sample_date }}").unix() - 1) }); + readingMirrorTimes.push({ "y": null, "x": (moment("{{ item.sample_date }}").unix() - 1) }); + } + + readingMirrorRemoteDb.push({ "y": {{ item.remote_db }}, "x": moment("{{ item.sample_date }}").unix() }); + readingMirrorLocalDb.push({ "y": {{ item.local_db }}, "x": moment("{{ item.sample_date }}").unix() }); + readingMirrorLocalApp.push({ "y": {{ item.local_app }}, "x": moment("{{ item.sample_date }}").unix() }); + readingMirrorTimes.push({ "y": null, "x": moment("{{ item.sample_date }}").unix() }); + {% endif %} + {% endif %} + {% endif %} + {% endfor %} + + readingMirrorTimes.push(minSeries[1]); + readingMirrorLocalDb.push(minSeries[1]); + readingMirrorLocalApp.push(minSeries[1]); + readingMirrorRemoteDb.push(minSeries[1]); + +// readingMirrorLocalDb[readingMirrorLocalDb.length - 1]["y"] = readingMirrorLocalDb[readingMirrorLocalDb.length - 2]["y"]; +// readingMirrorLocalApp[readingMirrorLocalApp.length - 1]["y"] = readingMirrorLocalApp[readingMirrorLocalApp.length - 2]["y"]; +// readingMirrorRemoteDb[readingMirrorRemoteDb.length - 1]["y"] = readingMirrorRemoteDb[readingMirrorRemoteDb.length - 2]["y"]; + + var pendingIngestSeries = []; + + {% for item in pending_ingest %} + pendingIngestSeries.push({ "y": {{ item.count }}, "x": moment("{{ item.sample_date }}").unix() }); + {% endfor %} + + var pendingMirrorSeries = []; + + {% for item in pending_mirror %} + pendingMirrorSeries.push({ "y": {{ item.count }}, "x": moment("{{ item.sample_date }}").unix() }); + {% endfor %} + + var graph = new Rickshaw.Graph( { + element: document.getElementById("ingest_readings_chart"), + height: 180, + width: $("div#ingest_readings_chart").width(), + renderer: 'line', + interpolation: 'linear', + series: [ + { + color: "#1B5E20", + data: readingIngestSeries, + name: 'Payload Ingest Rate' + }, + { + color: "#ffffff", + data: minSeries, + name: "" + } + ], + padding: { top: 0.25, bottom: 0.25 } + }); + + var x_axis = new Rickshaw.Graph.Axis.Time( { graph: graph, timeUnit: timeUnit, timeFixture: new Rickshaw.Fixtures.Time.Local() } ); + + var hoverDetail = new Rickshaw.Graph.HoverDetail( { + graph: graph, + xFormatter: function(x) + { + return moment(x * 1000).format('h:mm a'); + }, + yFormatter: function(y) { return y.toFixed(2) + " payloads per second" } + }); + + graph.render(); + + graph = new Rickshaw.Graph( { + element: document.getElementById("mirror_readings_chart"), + height: 180, + width: $("div#mirror_readings_chart").width(), + renderer: 'line', + interpolation: 'linear', + series: [ + { + color: "#1B5E20", + data: readingMirrorSeries, + name: 'Payload Mirror Rate' + }, + { + color: "#ffffff", + data: minSeries, + name: "" + } + ], + padding: { top: 0.25, bottom: 0.25 } + }); + + x_axis = new Rickshaw.Graph.Axis.Time( { graph: graph, timeUnit: timeUnit } ); + + hoverDetail = new Rickshaw.Graph.HoverDetail( { + graph: graph, + xFormatter: function(x) + { + return moment(x * 1000).tz("{{ timezone }}").format('h:mm a'); + }, + yFormatter: function(y) { return y.toFixed(2) + " payloads per second" } + }); + + graph.render(); + + + graph = new Rickshaw.Graph( { + element: document.getElementById("ingest_pending_chart"), + height: 180, + width: $("div#ingest_pending_chart").width(), + renderer: 'line', + interpolation: 'linear', + series: [ + { + color: "#E65100", + data: pendingIngestSeries, + name: 'Pending Ingest Payloads' + }, + { + color: "#ffffff", + data: minSeries, + name: "" + } + ], + padding: { top: 0.25, bottom: 0.25 } + }); + + x_axis = new Rickshaw.Graph.Axis.Time( { graph: graph, timeUnit: timeUnit } ); + + hoverDetail = new Rickshaw.Graph.HoverDetail( { + graph: graph, + xFormatter: function(x) + { + return moment(x * 1000).tz("{{ timezone }}").format('h:mm a'); + }, + yFormatter: function(y) { return y + " payloads" } + }); + + graph.render(); + + graph = new Rickshaw.Graph( { + element: document.getElementById("mirror_pending_chart"), + height: 180, + width: $("div#mirror_pending_chart").width(), + renderer: 'line', + interpolation: 'linear', + series: [ + { + color: "#E65100", + data: pendingMirrorSeries, + name: 'Pending Mirror Payloads' + }, + { + color: "#ffffff", + data: minSeries, + name: "" + } + ], + padding: { top: 0.25, bottom: 0.25 } + }); + + x_axis = new Rickshaw.Graph.Axis.Time( { graph: graph, timeUnit: timeUnit } ); + + hoverDetail = new Rickshaw.Graph.HoverDetail( { + graph: graph, + xFormatter: function(x) + { + return moment(x * 1000).tz("{{ timezone }}").format('h:mm a'); + }, + yFormatter: function(y) { return y + " payloads" } + }); + + graph.render(); + + graph = new Rickshaw.Graph( { + element: document.getElementById("server_load_chart"), + height: 180, + width: $("div#server_load_chart").width(), + renderer: 'line', + interpolation: 'linear', + series: [ + { + color: "#E65100", + data: serverLoadSeries, + name: 'Load Average' + }, + { + color: "#ffffff", + data: minSeries, + name: "" + } + ], + padding: { top: 0.25, bottom: 0.25 } + }); + + x_axis = new Rickshaw.Graph.Axis.Time( { graph: graph, timeUnit: timeUnit } ); + + hoverDetail = new Rickshaw.Graph.HoverDetail( { + graph: graph, + xFormatter: function(x) + { + return moment(x * 1000).tz("{{ timezone }}").format('h:mm a'); + }, + yFormatter: function(y) { return "" + y.toFixed(2) } + }); + + graph.render(); + + graph = new Rickshaw.Graph( { + element: document.getElementById("payload_uploads_chart"), + height: 180, + width: $("div#server_load_chart").width(), + renderer: 'line', + interpolation: 'linear', + series: [ + { + color: "#E65100", + data: uploadSeries, + name: 'Payloads Added' + }, + { + color: "#ffffff", + data: minSeries, + name: "" + } + ], + padding: { top: 0.25, bottom: 0.25 } + }); + + x_axis = new Rickshaw.Graph.Axis.Time( { graph: graph, timeUnit: timeUnit } ); + + hoverDetail = new Rickshaw.Graph.HoverDetail( { + graph: graph, + xFormatter: function(x) + { + return moment(x * 1000).tz("{{ timezone }}").format('h:mm a'); + }, + yFormatter: function(y) { return "" + y } + }); + + graph.render(); + + graph = new Rickshaw.Graph( { + element: document.getElementById("mirror_performance_chart"), + height: 180, + width: $("div#mirror_performance_chart").width(), +// renderer: 'line', + interpolation: 'linear', + series: [ + { + color: "#ffffff", + data: readingMirrorTimes, + name: "" + }, + { + color: "#616161", + data: readingMirrorLocalApp, + name: 'Local App Server Time' + }, + { + color: "#5D4037", + data: readingMirrorLocalDb, + name: 'Local Database Time' + }, + { + color: "#1976D2", + data: readingMirrorRemoteDb, + name: 'Remote Database Time' + } + ], + padding: { top: 0.25, bottom: 0.25 } + }); + + x_axis = new Rickshaw.Graph.Axis.Time( { graph: graph, timeUnit: timeUnit } ); + + hoverDetail = new Rickshaw.Graph.HoverDetail( { + graph: graph, + xFormatter: function(x) + { + return moment(x * 1000).tz("{{ timezone }}").format('h:mm a'); + }, + yFormatter: function(y) { if (y == null) return null; return y.toFixed(2) + " seconds" } + }); + + graph.render(); + + {% with pending_mirror_ages|last as last_mirror_age %} + var mirrorAgeSeries = []; + + mirrorAgeSeries.push({ "x": 0, "y": {{ last_mirror_age.less_hour_count }} }); + mirrorAgeSeries.push({ "x": 1, "y": {{ last_mirror_age.hour_count }} }); + mirrorAgeSeries.push({ "x": 2, "y": {{ last_mirror_age.quarter_day_count }} }); + mirrorAgeSeries.push({ "x": 3, "y": {{ last_mirror_age.half_day_count }} }); + mirrorAgeSeries.push({ "x": 4, "y": {{ last_mirror_age.day_count }} }); + mirrorAgeSeries.push({ "x": 5, "y": {{ last_mirror_age.week_count }} }); + + graph = new Rickshaw.Graph( { + element: document.getElementById("mirror_age_chart"), + height: 180, + width: $("div#mirror_age_chart").width(), + renderer: 'bar', + series: [{ + color: "#9E9E9E", + data: mirrorAgeSeries, + name: "Pending Mirror Ages" + }], + padding: { top: 0.25, bottom: 0.25 } + }); + + var hoverDetail = new Rickshaw.Graph.HoverDetail( { + graph: graph, + xFormatter: function(x) + { + if (x == 0) + return "Less than 1 hour"; + else if (x == 1) + return "1 to 6 hours"; + else if (x == 2) + return "6 to 12 hours"; + else if (x == 3) + return "12 to 24 hours"; + else if (x == 4) + return "1 to 7 days"; + else if (x == 5) + return "More than 7 days"; + + return "--" + x + "--"; + }, + yFormatter: function(y) { return y + " payloads" } + }); + + graph.render(); + {% endwith %} + + {% with pending_ingest_ages|last as last_ingest_age %} + var ingestAgeSeries = []; + + ingestAgeSeries.push({ "x": 0, "y": {{ last_ingest_age.less_hour_count }} }); + ingestAgeSeries.push({ "x": 1, "y": {{ last_ingest_age.hour_count }} }); + ingestAgeSeries.push({ "x": 2, "y": {{ last_ingest_age.quarter_day_count }} }); + ingestAgeSeries.push({ "x": 3, "y": {{ last_ingest_age.half_day_count }} }); + ingestAgeSeries.push({ "x": 4, "y": {{ last_ingest_age.day_count }} }); + ingestAgeSeries.push({ "x": 5, "y": {{ last_ingest_age.week_count }} }); + + graph = new Rickshaw.Graph( { + element: document.getElementById("ingest_age_chart"), + height: 180, + width: $("div#ingest_age_chart").width(), + renderer: 'bar', + series: [{ + color: "#9E9E9E", + data: ingestAgeSeries, + name: "Pending Ingest Ages" + }], + padding: { top: 0.25, bottom: 0.25 } + }); + + var hoverDetail = new Rickshaw.Graph.HoverDetail( { + graph: graph, + xFormatter: function(x) + { + if (x == 0) + return "Less than 1 hour"; + else if (x == 1) + return "1 to 6 hours"; + else if (x == 2) + return "6 to 12 hours"; + else if (x == 3) + return "12 to 24 hours"; + else if (x == 4) + return "1 to 7 days"; + else if (x == 5) + return "More than 7 days"; + + return "--" + x + "--"; + }, + yFormatter: function(y) { return y + " payloads" } + }); + + graph.render(); + {% endwith %} + + }); +{% endblock %} diff --git a/templates/tag_pr_device_custom_navbar_default.html b/templates/tag_pr_device_custom_navbar_default.html new file mode 100644 index 0000000..ad40612 --- /dev/null +++ b/templates/tag_pr_device_custom_navbar_default.html @@ -0,0 +1,29 @@ + + diff --git a/templates/tag_pr_group_table.html b/templates/tag_pr_group_table.html index a9c2c9e..8251a66 100644 --- a/templates/tag_pr_group_table.html +++ b/templates/tag_pr_group_table.html @@ -56,7 +56,11 @@
{{ device.name }} {% if device.mute_alerts %} diff --git a/templates/tag_pr_human_duration.html b/templates/tag_pr_human_duration.html new file mode 100644 index 0000000..70b22c3 --- /dev/null +++ b/templates/tag_pr_human_duration.html @@ -0,0 +1 @@ +{{ human_duration }} \ No newline at end of file diff --git a/templatetags/purple_robot.py b/templatetags/purple_robot.py index d6b0b01..3b0c9a8 100644 --- a/templatetags/purple_robot.py +++ b/templatetags/purple_robot.py @@ -1,3 +1,4 @@ +import arrow import datetime import pytz @@ -12,10 +13,12 @@ register = template.Library() + @register.tag(name="pr_device_custom_sidebar") def pr_device_custom_sidebar(parser, token): return CustomSidebarNode() + class CustomSidebarNode(template.Node): def __init__(self): pass @@ -25,14 +28,33 @@ def render(self, context): return settings.PURPLE_ROBOT_CUSTOM_SIDEBAR(context) except AttributeError: pass - + return render_to_string('tag_pr_device_custom_sidebar_unknown.html') +@register.tag(name="pr_device_custom_navbar") +def pr_device_custom_navbar(parser, token): + return CustomNavbarNode() + + +class CustomNavbarNode(template.Node): + def __init__(self): + pass + + def render(self, context): + try: + return settings.PURPLE_ROBOT_CUSTOM_NAVBAR(context) + except AttributeError: + pass + + return render_to_string('tag_pr_device_custom_navbar_default.html', context) + + @register.tag(name="pr_home_custom_console") def tag_pr_home_custom_console(parser, token): return HomeCustomConsoleNode() + class HomeCustomConsoleNode(template.Node): def __init__(self): pass @@ -42,13 +64,15 @@ def render(self, context): return settings.PURPLE_ROBOT_HOME_CONSOLE(context) except AttributeError: pass - + return render_to_string('tag_pr_home_custom_console_unknown.html') + @register.tag(name="pr_device_custom_console") def tag_pr_device_custom_console(parser, token): return DeviceCustomConsoleNode() + class DeviceCustomConsoleNode(template.Node): def __init__(self): pass @@ -58,9 +82,10 @@ def render(self, context): return settings.PURPLE_ROBOT_DEVICE_CONSOLE(context) except AttributeError: pass - + return render_to_string('tag_pr_device_custom_console_unknown.html') + @register.tag(name="pr_group_table") def tag_pr_group_table(parser, token): try: @@ -70,64 +95,67 @@ def tag_pr_group_table(parser, token): return GroupTableNode(group_id) + class GroupTableNode(template.Node): def __init__(self, group_id): self.group_id = template.Variable(group_id) def render(self, context): group_id = self.group_id.resolve(context) - + context['device_group'] = PurpleRobotDeviceGroup.objects.filter(group_id=group_id).first() - + if context['device_group'] != None: context['device_group_devices'] = list(context['device_group'].devices.all().order_by('name', 'device_id')) else: context['device_group_devices'] = list(PurpleRobotDevice.objects.filter(device_group=None).order_by('name', 'device_id')) - + return render_to_string('tag_pr_group_table.html', context) + @register.tag(name="pr_timestamp_ago") def tag_pr_timestamp_ago(parser, token): try: tag_name, timestamp = token.split_contents() except ValueError: raise template.TemplateSyntaxError("%r tag requires a single argument" % token.contents.split()[0]) - + return TimestampAgoNode(timestamp) + class TimestampAgoNode(template.Node): def __init__(self, timestamp): self.timestamp = template.Variable(timestamp) def render(self, context): timestamp = self.timestamp.resolve(context) - + date_obj = datetime.datetime.fromtimestamp(int(timestamp) / 1000, pytz.utc) - - if date_obj == None: + + if date_obj is None: return 'None' - + now = timezone.now() - - diff = now - date_obj - + + diff = arrow.get(now.isoformat()).datetime - arrow.get(date_obj.isoformat()).datetime + ago_str = 'Unknown' - + if diff.days > 0: ago_str = str(diff.days) + 'd' else: minutes = diff.seconds / 60 - + if minutes >= 60: ago_str = str(minutes / 60) + 'h' else: ago_str = str(minutes) + 'm' - + context['ago'] = ago_str context['date'] = date_obj - + return render_to_string('tag_pr_date_ago.html', context) - + @register.tag(name="pr_date_ago") def tag_pr_date_ago(parser, token): @@ -138,37 +166,73 @@ def tag_pr_date_ago(parser, token): return DateAgoNode(date_obj) + class DateAgoNode(template.Node): def __init__(self, date_obj): self.date_obj = template.Variable(date_obj) def render(self, context): date_obj = self.date_obj.resolve(context) - - if date_obj == None: + + if date_obj is None: return 'None' - + now = timezone.now() - - diff = now - date_obj - + + diff = arrow.get(now.isoformat()).datetime - arrow.get(date_obj.isoformat()).datetime + ago_str = 'Unknown' - + if diff.days > 0: ago_str = str(diff.days) + 'd' else: minutes = diff.seconds / 60 - + if minutes >= 60: ago_str = str(minutes / 60) + 'h' else: ago_str = str(minutes) + 'm' - + context['ago'] = ago_str context['date'] = date_obj - + return render_to_string('tag_pr_date_ago.html', context) - + + +@register.tag(name="pr_human_duration") +def tag_pr_human_duration(parser, token): + try: + tag_name, seconds_obj = token.split_contents() + except ValueError: + raise template.TemplateSyntaxError("%r tag requires a single argument" % token.contents.split()[0]) + + return HumanDurationNode(seconds_obj) + + +class HumanDurationNode(template.Node): + def __init__(self, seconds_obj): + self.seconds_obj = template.Variable(seconds_obj) + + def render(self, context): + seconds_obj = self.seconds_obj.resolve(context) + + if seconds_obj is None: + return '' + + ago_str = str(seconds_obj) + 's' + + if seconds_obj > (24.0 * 60 * 60): + ago_str = "{0:.2f}".format(seconds_obj / (24.0 * 60 * 60)) + 'd' + elif seconds_obj > (60.0 * 60): + ago_str = "{0:.2f}".format(seconds_obj / (60.0 * 60)) + 'h' + elif seconds_obj > 60.0: + ago_str = "{0:.2f}".format(seconds_obj / 60.0) + 'm' + + context['human_duration'] = ago_str + context['seconds'] = seconds_obj + + return render_to_string('tag_pr_human_duration.html', context) + @register.tag(name="pr_frequency") def tag_pr_frequency(parser, token): @@ -179,49 +243,51 @@ def tag_pr_frequency(parser, token): return FrequencyNode(frequency) + class FrequencyNode(template.Node): def __init__(self, frequency): self.frequency = template.Variable(frequency) def render(self, context): frequency = self.frequency.resolve(context) - - if frequency == None: + + if frequency is None: return 'None' elif frequency == 'Unknown': return frequency value = "{:10.3f}".format(frequency) + " Hz" + + if frequency < 1.0: + value = "{:10.3f}".format(frequency * 1000) + " mHz" + tooltip = "{:10.3f}".format(frequency) + " samples per second" - + if frequency < 1.0: frequency *= 60 - + if frequency > 1.0: tooltip = "{:10.3f}".format(frequency) + " samples per minute" else: frequency *= 60 - + if frequency > 1.0: tooltip = "{:10.3f}".format(frequency) + " samples per hour" else: frequency *= 24 - + if frequency > 1.0: tooltip = "{:10.3f}".format(frequency) + " samples per day" else: frequency *= 7 - + tooltip = "{:10.3f}".format(frequency) + " samples per week" context['value'] = value context['tooltip'] = tooltip - + return render_to_string('tag_pr_frequency.html', context) -# -# return render_to_string('tag_pr_date_ago.html', context) - - + @register.tag(name="pr_device_alerts") def tag_pr_device_alerts(parser, token): @@ -232,46 +298,52 @@ def tag_pr_device_alerts(parser, token): return DeviceAlertsNode(user_id) + class DeviceAlertsNode(template.Node): def __init__(self, user_id): self.user_id = template.Variable(user_id) def render(self, context): user_id = self.user_id.resolve(context) - + + device = PurpleRobotDevice.objects.filter(hash_key=user_id).first() + + if device is not None and device.mute_alerts: + context['class'] = '' + context['value'] = 0 + context['tooltip'] = 'No alerts.' + + return render_to_string('tag_pr_device_alerts.html', context) + alerts = PurpleRobotAlert.objects.filter(user_id=user_id, dismissed=None).order_by('-severity') - + tooltip = 'No alerts.' - + severity = 0 - + if alerts.count() > 0: tooltip = '' - + for alert in alerts: if len(tooltip) > 0: tooltip += '\n' - + tooltip += alert.message - + if alert.severity > severity: severity = alert.severity - + if severity > 1: context['class'] = 'text-danger' - elif severity > 1: + elif severity > 0: context['class'] = 'text-warning' else: context['class'] = '' context['value'] = alerts.count() context['tooltip'] = tooltip - + return render_to_string('tag_pr_device_alerts.html', context) -# -# return render_to_string('tag_pr_date_ago.html', context) - - @register.tag(name="pr_data_size") @@ -283,21 +355,22 @@ def tag_pr_data_size(parser, token): return DataSizeNode(data_size) + class DataSizeNode(template.Node): def __init__(self, data_size): self.data_size = template.Variable(data_size) def render(self, context): size = self.data_size.resolve(context) - - if size == None: + + if size is None: size = 0 - + data_size = float(size) - + if data_size < 0: return 'Unknown / None' - + if data_size > (1024 * 1024 * 1024): return "{:10.1f}".format(data_size / (1024 * 1024 * 1024)) + " GB" elif data_size > (1024 * 1024): @@ -307,31 +380,34 @@ def render(self, context): return (str(data_size) + " B") + @register.filter(name='to_percent') def to_percent(value): return (value * 100) + @register.tag(name="pr_total_data_size") def tag_pr_total_database_size(parser, token): return TotalDataSizeNode() + class TotalDataSizeNode(template.Node): def render(self, context): data_size = 0 - + for device in PurpleRobotDevice.objects.all(): size = device.total_readings_size() - - if size == None: + + if size is None: size = 0 - + data_size += size - + data_size *= 2 - + if data_size <= 0: return 'Unknown / None' - + if data_size > (1024 * 1024 * 1024): return "{:10.1f}".format(data_size / (1024 * 1024 * 1024)) + " GB" elif data_size > (1024 * 1024): diff --git a/tests.py b/tests.py index 7ce503c..0cfce84 100644 --- a/tests.py +++ b/tests.py @@ -1,3 +1,5 @@ +# pylint: disable=unused-import + from django.test import TestCase # Create your tests here. diff --git a/urls.py b/urls.py index fdf31cf..1e05877 100644 --- a/urls.py +++ b/urls.py @@ -1,32 +1,36 @@ +# pylint: disable=line-too-long, invalid-name + from django.conf.urls import patterns, url -from views import ingest_payload_print, log_event, pr_home, pr_by_probe, \ - pr_by_user, test_report, test_details_json, tests_by_user, \ - create_export_job, fetch_export_file, tests_all, ingest_payload, config, \ - pr_device, pr_add_group, pr_add_device, pr_configurations, pr_configuration, \ - pr_device_probe, pr_add_note, pr_remove_device, pr_move_device +from purple_robot_app.views import ingest_payload_print, log_event, pr_home, pr_by_probe, \ + pr_by_user, test_report, test_details_json, tests_by_user, \ + create_export_job, fetch_export_file, tests_all, ingest_payload, pr_config, \ + pr_device, pr_add_group, pr_add_device, pr_configurations, pr_configuration, \ + pr_device_probe, pr_add_note, pr_remove_device, pr_move_device, pr_status, \ + pr_users urlpatterns = patterns('', - url(r'^config$', config, name='pr_config'), - url(r'^configurations$', pr_configurations, name='pr_configurations'), - url(r'^configuration/(?P.+)$', pr_configuration, name='pr_configuration'), - url(r'^print$', ingest_payload_print, name='ingest_payload_print'), - url(r'^log$', log_event, name='log_event'), - url(r'^home$', pr_home, name='pr_home'), - url(r'^add_group$', pr_add_group, name='pr_add_group'), - url(r'^move_device$', pr_move_device, name='pr_move_device'), - url(r'^group/(?P.+)/remove_device/(?P.+)$', pr_remove_device, name='pr_remove_device'), - url(r'^group/(?P.+)/add_device$', pr_add_device, name='pr_add_device'), - url(r'^device/(?P.+)/(?P.+)$', pr_device_probe, name='pr_device_probe'), - url(r'^device/(?P.+)$', pr_device, name='pr_device'), - url(r'^probes$', pr_by_probe, name='pr_by_probe'), - url(r'^user$', pr_by_user, name='pr_by_user'), - url(r'^test/(?P.+)$', test_report, name='test_report'), - url(r'^tests/(?P.+)/detail.json$', test_details_json, name='test_details_json'), - url(r'^tests/(?P.+)$', tests_by_user, name='tests_by_user'), - url(r'^export$', create_export_job, name='create_export_job'), - url(r'^export_files/(?P.+)$', fetch_export_file, name='fetch_export_file'), - url(r'^tests/$', tests_all, name='tests_all'), - url(r'^add_note.json$', pr_add_note, name='pr_add_note'), - url(r'^$', ingest_payload, name='ingest_payload'), -) + url(r'^config$', pr_config, name='pr_config'), + url(r'^configurations$', pr_configurations, name='pr_configurations'), + url(r'^configuration/(?P.+)$', pr_configuration, name='pr_configuration'), + url(r'^print$', ingest_payload_print, name='ingest_payload_print'), + url(r'^log$', log_event, name='log_event'), + url(r'^home$', pr_home, name='pr_home'), + url(r'^add_group$', pr_add_group, name='pr_add_group'), + url(r'^move_device$', pr_move_device, name='pr_move_device'), + url(r'^group/(?P.+)/remove_device/(?P.+)$', pr_remove_device, name='pr_remove_device'), + url(r'^group/(?P.+)/add_device$', pr_add_device, name='pr_add_device'), + url(r'^device/(?P.+)/(?P.+)$', pr_device_probe, name='pr_device_probe'), + url(r'^device/(?P.+)$', pr_device, name='pr_device'), + url(r'^probes$', pr_by_probe, name='pr_by_probe'), + url(r'^user$', pr_by_user, name='pr_by_user'), + url(r'^status$', pr_status, name='pr_status'), + url(r'^test/(?P.+)$', test_report, name='test_report'), + url(r'^tests/(?P.+)/detail.json$', test_details_json, name='test_details_json'), + url(r'^tests/(?P.+)$', tests_by_user, name='tests_by_user'), + url(r'^export$', create_export_job, name='create_export_job'), + url(r'^export_files/(?P.+)$', fetch_export_file, name='fetch_export_file'), + url(r'^tests/$', tests_all, name='tests_all'), + url(r'^add_note.json$', pr_add_note, name='pr_add_note'), + url(r'^users$', pr_users, name='pr_users'), + url(r'^$', ingest_payload, name='ingest_payload'), ) diff --git a/views.py b/views.py index 0cf921e..a4b9e1d 100644 --- a/views.py +++ b/views.py @@ -1,7 +1,12 @@ +# pylint: disable=line-too-long, no-member, unused-argument, bare-except + +import arrow import datetime import hashlib import json +import numpy import pytz +import sys from django.conf import settings from django.contrib.admin.views.decorators import staff_member_required @@ -14,99 +19,111 @@ from django.views.decorators.cache import never_cache from django.views.decorators.csrf import csrf_exempt -from forms import ExportJobForm -from models import PurpleRobotPayload, PurpleRobotTest, PurpleRobotEvent, \ - PurpleRobotReport, PurpleRobotExportJob, PurpleRobotReading, \ - PurpleRobotConfiguration, PurpleRobotDevice, PurpleRobotDeviceGroup, \ - PurpleRobotDeviceNote +from purple_robot_app.forms import ExportJobForm +from purple_robot_app.models import PurpleRobotPayload, PurpleRobotTest, PurpleRobotEvent, \ + PurpleRobotReport, PurpleRobotExportJob, PurpleRobotReading, \ + PurpleRobotConfiguration, PurpleRobotDevice, PurpleRobotDeviceGroup, \ + PurpleRobotDeviceNote + +from purple_robot_app.performance import fetch_performance_samples, fetch_performance_users + @never_cache -def config(request): +def pr_config(request): config = None - + try: device_id = request.GET['user_id'] - + device = PurpleRobotDevice.objects.get(device_id=device_id) - - if device.configuration != None: + + if device.configuration is not None: config = device.configuration - elif device.device_group.configuration != None: + elif device.device_group.configuration is not None: config = device.device_group.configuration - + device.config_last_fetched = datetime.datetime.now() - + try: device.config_last_user_agent = request.META['HTTP_USER_AGENT'] except KeyError: device.config_last_user_agent = 'Unknown' - + device.save() except: pass - - if config == None: + + if config is None: config = get_object_or_404(PurpleRobotConfiguration, slug='default') - + content_type = 'application/json' - + if config.contents.strip().lower().startswith('(begin'): content_type = 'text/x-scheme' - + return HttpResponse(config.contents, content_type=content_type) + @csrf_exempt @never_cache def ingest_payload(request): result = {} result['Status'] = 'error' result['Payload'] = "{}" - + if request.method == 'POST': try: json_str = request.POST['json'] - + json_obj = json.loads(json_str) - + payload_str = json_obj['Payload'] - - m = hashlib.md5() - m.update((json_obj['UserHash'] + json_obj['Operation'] + json_obj['Payload']).encode('utf-8')) - - checksum_str = m.hexdigest() - + + md5_hash = hashlib.md5() + md5_hash.update((json_obj['UserHash'] + json_obj['Operation'] + json_obj['Payload']).encode('utf-8')) + + checksum_str = md5_hash.hexdigest() + result = {} result['Status'] = 'error' result['Payload'] = "{}" - + if checksum_str == json_obj['Checksum']: result['Status'] = 'success' - - payload_json = json.loads(payload_str) - - payload = PurpleRobotPayload(payload=json.dumps(payload_json, indent=2, ensure_ascii=False), user_id=json_obj['UserHash']) - payload.save() - - m = hashlib.md5() - m.update(result['Status'] + result['Payload']) - - result['Checksum'] = m.hexdigest() - - if 'media_url' in payload_str: - payload.ingest_readings() - for k, v in request.FILES.iteritems(): - reading = PurpleRobotReading.objects.filter(guid=k).first() - - if reading != None: - reading.attachment.save(v.name, v) + + try: + payload_json = json.loads(payload_str) + + payload = PurpleRobotPayload(payload=json.dumps(payload_json, indent=2, ensure_ascii=False), user_id=json_obj['UserHash']) + payload.save() + + md5_hash = hashlib.md5() + md5_hash.update(result['Status'] + result['Payload']) + + result['Checksum'] = md5_hash.hexdigest() + + if 'media_url' in payload_str: + payload.ingest_readings() + for key, value in request.FILES.iteritems(): + reading = PurpleRobotReading.objects.filter(guid=key).first() + + if reading is not None: + reading.attachment.save(value.name, value) + + reading.size = len(reading.payload) + reading.size += reading.attachment.size + + reading.save() + except ValueError: + result['Status'] = 'error' + result['Error'] = 'Unable to parse payload.' else: result['Error'] = 'Source checksum ' + json_obj['Checksum'] + ' doesn\'t match destination checksum ' + checksum_str + '.' - except Exception, e: - result['Error'] = str(e) + except UnreadablePostError, error: + result['Error'] = str(error) else: result['Error'] = 'GET requests not supported.' - - + return HttpResponse(json.dumps(result), content_type='application/json') @@ -116,44 +133,42 @@ def ingest_payload_print(request): result = {} result['Status'] = 'error' result['Payload'] = "{}" - + try: json_str = request.POST['json'] - + json_obj = json.loads(json_str) - - # payload_str = json_obj['Payload'] - - m = hashlib.md5() - m.update((json_obj['UserHash'] + json_obj['Operation'] + json_obj['Payload']).encode('utf-8')) - - checksum_str = m.hexdigest() - + + md5_hash = hashlib.md5() + md5_hash.update((json_obj['UserHash'] + json_obj['Operation'] + json_obj['Payload']).encode('utf-8')) + + checksum_str = md5_hash.hexdigest() + result = {} result['Status'] = 'error' result['Payload'] = "{}" - + if checksum_str == json_obj['Checksum']: result['Status'] = 'success' - - # payload_json = json.loads(payload_str) - # print('PAYLOAD: ' + json.dumps(payload_json, indent=2, ensure_ascii=False)) - - m = hashlib.md5() - m.update(result['Status'] + result['Payload']) - - result['Checksum'] = m.hexdigest() + + md5_hash = hashlib.md5() + md5_hash.update(result['Status'] + result['Payload']) + + result['Checksum'] = md5_hash.hexdigest() else: result['Error'] = 'Source checksum ' + json_obj['Checksum'] + ' doesn\'t match destination checksum ' + checksum_str + '.' - except Exception, e: - result['Error'] = str(e) - + except: + error = sys.exc_info()[0] + result['Error'] = str(error) + return HttpResponse(json.dumps(result), content_type='application/json') + @csrf_exempt @never_cache def log_event(request): try: + here_tz = pytz.timezone(settings.TIME_ZONE) payload = json.loads(request.POST['json']) tz = pytz.timezone(settings.TIME_ZONE) @@ -165,96 +180,128 @@ def log_event(request): payload['user_id'] = '-' except KeyError: payload['user_id'] = '-' - - event = PurpleRobotEvent(payload=json.dumps(payload, indent=2)) - event.logged = logged - event.event = payload['event_type'] - event.user_id = payload['user_id'] - + except KeyError: + payload['user_id'] = '-' + + event = PurpleRobotEvent(payload=json.dumps(payload, indent=2)) + event.logged = logged + event.event = payload['event_type'] + event.user_id = payload['user_id'] + + try: event.save() - - return HttpResponse(json.dumps({ 'result': 'success' }), content_type='application/json') + except pytz.AmbiguousTimeError: + pass + + return HttpResponse(json.dumps({'result': 'success'}), content_type='application/json') except UnreadablePostError: - return HttpResponse(json.dumps({ 'result': 'error', 'message': 'Unreadable POST request' }), content_type='application/json') + return HttpResponse(json.dumps({'result': 'error', 'message': 'Unreadable POST request'}), content_type='application/json') + + return HttpResponse(json.dumps({'result': 'error', 'message': 'Unknown error'}), content_type='application/json') - return HttpResponse(json.dumps({ 'result': 'error', 'message': 'Unknown error' }), content_type='application/json') @staff_member_required @never_cache def test_report(request, slug): - c = RequestContext(request) - - c['test'] = get_object_or_404(PurpleRobotTest, slug=slug) - - return render_to_response('purple_robot_test.html', c) + context = RequestContext(request) + + context['test'] = get_object_or_404(PurpleRobotTest, slug=slug) + + return render_to_response('purple_robot_test.html', context) + @staff_member_required @never_cache def tests_by_user(request, user_id): - c = RequestContext(request) - - c['tests'] = PurpleRobotTest.objects.filter(user_id=user_id) - c['user_id'] = user_id - - c['success'] = True - - for test in c['tests']: + context = RequestContext(request) + + context['tests'] = PurpleRobotTest.objects.filter(user_id=user_id) + context['user_id'] = user_id + + context['success'] = True + + for test in context['tests']: if test.active and test.passes() == False: - c['success'] = False - - return render_to_response('purple_robot_tests.html', c) + context['success'] = False + + return render_to_response('purple_robot_tests.html', context) + @staff_member_required @never_cache def tests_all(request): - c = RequestContext(request) - - c['tests'] = PurpleRobotTest.objects.order_by('-last_updated') - c['user_id'] = 'All' - - c['success'] = True - - for test in c['tests']: + context = RequestContext(request) + + context['tests'] = PurpleRobotTest.objects.order_by('-last_updated') + context['user_id'] = 'All' + + context['success'] = True + + for test in context['tests']: if test.active and test.passes() == False: - c['success'] = False - - return render_to_response('purple_robot_tests.html', c) + context['success'] = False + + return render_to_response('purple_robot_tests.html', context) + @staff_member_required @never_cache def pr_home(request): - c = RequestContext(request) - - c['device_groups'] = PurpleRobotDeviceGroup.objects.all() - c['unattached_devices'] = PurpleRobotDevice.objects.filter(device_group=None) + context = RequestContext(request) + + context['device_groups'] = PurpleRobotDeviceGroup.objects.all() + context['unattached_devices'] = PurpleRobotDevice.objects.filter(device_group=None) + + return render_to_response('purple_robot_home.html', context) - return render_to_response('purple_robot_home.html', c) @staff_member_required @never_cache def pr_device(request, device_id): - c = RequestContext(request) - - c['device'] = PurpleRobotDevice.objects.get(device_id=device_id) + context = RequestContext(request) + + context['device'] = PurpleRobotDevice.objects.get(device_id=device_id) + + context.update(csrf(request)) - c.update(csrf(request)) + try: + context['pr_show_device_id_header'] = settings.PURPLE_ROBOT_SHOW_DEVICE_ID_HEADER + except KeyError: + context['pr_show_device_id_header'] = True + + try: + context['pr_show_notes'] = settings.PURPLE_ROBOT_SHOW_NOTES + except KeyError: + context['pr_show_notes'] = True + + return render_to_response('purple_robot_device.html', context) - return render_to_response('purple_robot_device.html', c) @staff_member_required @never_cache def pr_device_probe(request, device_id, probe_name): - c = RequestContext(request) - - c['device'] = PurpleRobotDevice.objects.get(device_id=device_id) - c['probe_name'] = probe_name - c['short_name'] = probe_name.split('.')[-1] - c['last_reading'] = PurpleRobotReading.objects.filter(user_id=c['device'].user_hash, probe=probe_name).order_by('-logged').first() - c['test'] = PurpleRobotTest.objects.filter(user_id=c['device'].user_hash, probe=probe_name).first() - c['last_readings'] = PurpleRobotReading.objects.filter(user_id=c['device'].user_hash, probe=probe_name).order_by('-logged')[:500] - c['visualization'] = c['device'].visualization_for_probe(probe_name) + context = RequestContext(request) + + context['device'] = PurpleRobotDevice.objects.get(device_id=device_id) + context['probe_name'] = probe_name + context['short_name'] = probe_name.split('.')[-1] + context['last_reading'] = PurpleRobotReading.objects.filter(user_id=context['device'].user_hash, probe=probe_name).order_by('-logged').first() + context['test'] = PurpleRobotTest.objects.filter(user_id=context['device'].user_hash, probe=probe_name).first() + context['last_readings'] = PurpleRobotReading.objects.filter(user_id=context['device'].user_hash, probe=probe_name).order_by('-logged')[:500] + context['visualization'] = context['device'].visualization_for_probe(probe_name) + + try: + context['pr_show_device_id_header'] = settings.PURPLE_ROBOT_SHOW_DEVICE_ID_HEADER + except KeyError: + context['pr_show_device_id_header'] = True + + try: + context['pr_show_notes'] = settings.PURPLE_ROBOT_SHOW_NOTES + except KeyError: + context['pr_show_notes'] = True + + return render_to_response('purple_robot_device_probe.html', context) - return render_to_response('purple_robot_device_probe.html', c) @staff_member_required @never_cache @@ -266,270 +313,290 @@ def pr_by_probe(request): @never_cache def pr_by_user(request): users = {} - + hashes = PurpleRobotReport.objects.order_by().values('user_id').distinct() - + for user_hash in hashes: user_id = user_hash['user_id'] - + user_dict = {} - + for report in PurpleRobotReport.objects.filter(user_id=user_id).order_by('-generated'): key = str(report) + '-' + report.mime_type - - if (key in user_dict) == False: + + if (key in user_dict) is False: user_dict[key] = report - + users[user_id] = user_dict - c = RequestContext(request) - c['users'] = users + context = RequestContext(request) + context['users'] = users - return render_to_response('purple_robot_user.html', c) + return render_to_response('purple_robot_user.html', context) @staff_member_required @never_cache def test_details_json(request, slug): results = [] - + test = PurpleRobotTest.objects.get(slug=slug) - - cpu_frequency = { 'color': 'rgba(0,0,128,0.25)', 'data': [], 'name': 'CPU Timestamps'} - sensor_frequency = { 'color': 'rgba(128,0,0,0.75)', 'data': [], 'name': 'Sensor Timestamps'} - + + cpu_frequency = {'color': 'rgba(0,0,128,0.25)', 'data': [], 'name': 'CPU Timestamps'} + sensor_frequency = {'color': 'rgba(128,0,0,0.75)', 'data': [], 'name': 'Sensor Timestamps'} + if request.method == 'GET': timestamp = float(request.GET['timestamp']) - + sample_date = datetime.datetime.utcfromtimestamp(timestamp) - + delta = datetime.timedelta(seconds=450) - + start = sample_date - delta end = sample_date + delta - + readings = PurpleRobotReading.objects.filter(probe=test.probe, user_id=test.user_id, logged__gte=start, logged__lte=end).order_by('logged') - + sensor_stamps = [] cpu_stamps = [] - + for reading in readings: payload = json.loads(reading.payload) - + if 'SENSOR_TIMESTAMP' in payload: - for ts in payload['SENSOR_TIMESTAMP']: + for timestamp in payload['SENSOR_TIMESTAMP']: if payload['PROBE'] == 'edu.northwestern.cbits.purple_robot_manager.probes.devices.PebbleProbe': - sensor_stamps.append(float(ts) / 1000) + sensor_stamps.append(float(timestamp) / 1000) else: - sensor_stamps.append(float(ts) / 1000000000) + sensor_stamps.append(float(timestamp) / 1000000000) if 'EVENT_TIMESTAMP' in payload: - for ts in payload['EVENT_TIMESTAMP']: - cpu_stamps.append(ts) + for timestamp in payload['EVENT_TIMESTAMP']: + cpu_stamps.append(timestamp) else: cpu_stamps.append(payload['TIMESTAMP']) - + if len(cpu_stamps) > 0: cpu_stamps.sort() - + start_cpu = cpu_stamps[0] - + index = start_cpu count = 0 cpu_data = [] - - for ts in cpu_stamps: - if ts < index + 1: + + for timestamp in cpu_stamps: + if timestamp < index + 1: count += 1 else: - while ts > index + 1: - cpu_data.append({ 'x': index, 'y': count }) + while timestamp > index + 1: + cpu_data.append({'x': index, 'y': count}) index += 1 count = 0 - + cpu_frequency['data'] = cpu_data - + if len(sensor_stamps) > 0: sensor_stamps.sort() start_sensor = sensor_stamps[0] - + new_sensor_stamps = [] - - for ts in sensor_stamps: - new_sensor_stamps.append(ts - start_sensor + start_cpu) - + + for timestamp in sensor_stamps: + new_sensor_stamps.append(timestamp - start_sensor + start_cpu) + sensor_stamps = new_sensor_stamps index = start_cpu count = 0 sensor_data = [] - - for ts in sensor_stamps: - if ts < index + 1: + + for timestamp in sensor_stamps: + if timestamp < index + 1: count += 1 else: - while ts > index + 1: - sensor_data.append({ 'x': index, 'y': count }) + while timestamp > index + 1: + sensor_data.append({'x': index, 'y': count}) index += 1 count = 0 sensor_frequency['data'] = sensor_data - + results.append(sensor_frequency) results.append(cpu_frequency) return HttpResponse(json.dumps(results), content_type='application/json') - + + @staff_member_required @never_cache def fetch_export_file(request, job_pk): job = PurpleRobotExportJob.objects.get(pk=int(job_pk)) - + return redirect(job.export_file.url) + @staff_member_required @never_cache def create_export_job(request): - c = RequestContext(request) + context = RequestContext(request) + + context['form'] = ExportJobForm() - c['form'] = ExportJobForm() - if request.method == 'POST': form = ExportJobForm(request.POST) - + if form.is_valid(): - job = PurpleRobotExportJob(destination=form.cleaned_data.get('destination'), \ - start_date=form.cleaned_data.get('start_date'), \ + job = PurpleRobotExportJob(destination=form.cleaned_data.get('destination'), + start_date=form.cleaned_data.get('start_date'), end_date=form.cleaned_data.get('end_date')) - + probes = '' - + for probe in form.cleaned_data.get('probes'): if len(probes) > 0: probes += '\n' - + probes += probe - + job.probes = probes hashes = '' - + for user_hash in form.cleaned_data.get('hashes'): if len(user_hash) > 0: hashes += '\n' - + hashes += user_hash - + job.users = hashes - + job.save() - - c['message'] = 'Export job queued successfully.' + + context['message'] = 'Export job queued successfully.' else: - c['form'] = form + context['form'] = form + + return render_to_response('purple_robot_export.html', context) + - return render_to_response('purple_robot_export.html', c) - @staff_member_required @never_cache def pr_add_group(request): if request.method == 'POST': group_id = request.POST['group_id'] group_name = request.POST['group_name'] - - if group_id == None or len(group_id.strip()) == 0 or group_name == None or len(group_id.strip()) == 0: - request.session['pr_messages'] = [ 'Please provide a non-empty group name and identifier.' ] + + if group_id is None or len(group_id.strip()) == 0 or group_name is None or len(group_id.strip()) == 0: + request.session['pr_messages'] = ['Please provide a non-empty group name and identifier.'] elif PurpleRobotDeviceGroup.objects.filter(group_id=group_id).count() == 0: group = PurpleRobotDeviceGroup(group_id=group_id, name=group_name) - + group.save() else: - request.session['pr_messages'] = [ 'Unable to create group. A group already exists with identifier "' + group_id + '".' ] - + request.session['pr_messages'] = ['Unable to create group. A group already exists with identifier "' + group_id + '".'] + return redirect(reverse('pr_home')) + @staff_member_required @never_cache def pr_add_device(request, group_id): group = PurpleRobotDeviceGroup.objects.filter(group_id=group_id).first() - if group != None: + if group is not None: if request.method == 'POST': device_id = request.POST['device_id'] device_name = request.POST['device_name'] - if device_id == None or len(device_id.strip()) == 0 or device_name == None or len(device_name.strip()) == 0: - request.session['pr_messages'] = [ 'Please provide a non-empty device name and identifier.' ] + if device_id is None or len(device_id.strip()) == 0 or device_name is None or len(device_name.strip()) == 0: + request.session['pr_messages'] = ['Please provide a non-empty device name and identifier.'] elif PurpleRobotDevice.objects.filter(device_id=device_id).count() == 0: device = PurpleRobotDevice(device_id=device_id, name=device_name, device_group=group) + perf_data = json.loads(device.performance_metadata) + + perf_data['latest_readings'] = {} + perf_data['latest_readings']['edu.northwestern.cbits.purple_robot_manager.probes.builtin.RobotHealthProbe'] = -1 + perf_data['latest_readings']['edu.northwestern.cbits.purple_robot_manager.probes.builtin.BatteryProbe'] = -1 + perf_data['latest_readings']['edu.northwestern.cbits.purple_robot_manager.probes.builtin.SoftwareInformationProbe'] = -1 + perf_data['latest_readings']['edu.northwestern.cbits.purple_robot_manager.probes.builtin.HardwareInformationProbe'] = -1 + + device.performance_metadata = json.dumps(perf_data, indent=2) + device.save() else: - request.session['pr_messages'] = [ 'Unable to create device. A device already exists with identifier "' + device_id + '".' ] + request.session['pr_messages'] = ['Unable to create device. A device already exists with identifier "' + device_id + '".'] else: - request.session['pr_messages'] = [ 'Unable to locate group with identifier "' + group_id + '" and create new device.' ] - - return redirect(reverse('pr_home')) + request.session['pr_messages'] = ['Unable to locate group with identifier "' + group_id + '" and create new device.'] + + response = redirect(reverse('pr_home')) + + return response + @staff_member_required @never_cache def pr_remove_device(request, group_id, device_id): group = PurpleRobotDeviceGroup.objects.filter(pk=int(group_id)).first() - + device = group.devices.filter(pk=int(device_id)).first() - - if device != None: + + if device is not None: group.devices.remove(device) group.save() - + return redirect(reverse('pr_home')) + @staff_member_required @never_cache def pr_configurations(request): - c = RequestContext(request) - c.update(csrf(request)) - - c['configurations'] = PurpleRobotConfiguration.objects.all() - - return render_to_response('purple_robot_configurations.html', c) + context = RequestContext(request) + context.update(csrf(request)) + + context['configurations'] = PurpleRobotConfiguration.objects.all() + + return render_to_response('purple_robot_configurations.html', context) @staff_member_required @never_cache def pr_configuration(request, config_id): - c = RequestContext(request) - c.update(csrf(request)) + context = RequestContext(request) + context.update(csrf(request)) + + context['config'] = get_object_or_404(PurpleRobotConfiguration, slug=config_id) + + return render_to_response('purple_robot_configuration.html', context) - c['config'] = get_object_or_404(PurpleRobotConfiguration, slug=config_id) - return render_to_response('purple_robot_configuration.html', c) - @staff_member_required @never_cache def pr_move_device(request): group = PurpleRobotDeviceGroup.objects.filter(pk=int(request.POST['field_new_group'])).first() - + device = PurpleRobotDevice.objects.filter(pk=int(request.POST['field_device_id'])).first() - - if device != None: + + if device is not None: device.device_group = group device.save() - + return redirect(reverse('pr_home')) + @staff_member_required @never_cache def pr_add_note(request): - response = { 'result': 'error', 'message': 'No note provided or other error. See server logs.' } - + response = {'result': 'error', 'message': 'No note provided or other error. See server logs.'} + if request.method == 'POST': device_id = request.POST['device_id'] note_contents = request.POST['note_contents'] - + device = PurpleRobotDevice.objects.filter(device_id=device_id).first() - - if device != None: + + if device is not None: PurpleRobotDeviceNote(device=device, added=timezone.now(), note=note_contents).save() response['message'] = 'Note added. Reloading page...' response['result'] = 'success' @@ -537,4 +604,127 @@ def pr_add_note(request): response['message'] = 'Note not added. Device does not exist.' return HttpResponse(json.dumps(response, indent=2), content_type='application/json') - + + +@never_cache +def pr_status(request): + context = RequestContext(request) + context.update(csrf(request)) + + context['server_performance'] = fetch_performance_samples('system', 'server_performance') + + context['ingest_performance'] = fetch_performance_samples('system', 'reading_ingestion_performance') + context['mirror_performance'] = fetch_performance_samples('system', 'reading_mirror_performance') + context['pending_ingest'] = fetch_performance_samples('system', 'pending_ingest_payloads') + context['pending_mirror'] = fetch_performance_samples('system', 'pending_mirror_payloads') + + context['skipped_ingest'] = fetch_performance_samples('system', 'skipped_ingest_payloads') + context['skipped_mirror'] = fetch_performance_samples('system', 'skipped_mirror_payloads') + context['uploads_today'] = fetch_performance_samples('system', 'uploads_today') + context['uploads_hour'] = fetch_performance_samples('system', 'uploads_hour') + + context['pending_mirror_ages'] = fetch_performance_samples('system', 'pending_mirror_ages') + context['pending_ingest_ages'] = fetch_performance_samples('system', 'pending_ingest_ages') + + context['payload_uploads'] = fetch_performance_samples('system', 'payload_uploads')[-1]['counts'] + + context['timezone'] = settings.TIME_ZONE + + here_tz = pytz.timezone(settings.TIME_ZONE) + now = arrow.get(timezone.now().astimezone(here_tz)) + + start_today = now.replace(hour=0, minute=0, second=0, microsecond=0).datetime + start_hour = now.datetime - datetime.timedelta(hours=1) + + week_ago = timezone.now() - datetime.timedelta(days=7) + + upload_seconds = (arrow.get(context['uploads_today'][-1]['sample_date']).datetime.astimezone(here_tz) - start_today).total_seconds() + + context['now'] = now + context['start_today'] = start_today + context['start_hour'] = start_hour + + context['upload_count'] = '-' + context['upload_rate'] = '-' + + if len(context['uploads_today']) > 0: + context['upload_count'] = context['uploads_today'][-1]['count'] + context['upload_rate'] = context['uploads_today'][-1]['count'] / upload_seconds + context['upload_seconds'] = upload_seconds + context['uploads_today'] = arrow.get(context['uploads_today'][-1]['sample_date']).datetime.astimezone(here_tz) # c['uploads_today'][-1]['sample_date'] + + upload_seconds = (arrow.get(context['uploads_hour'][-1]['sample_date']).datetime.astimezone(here_tz) - start_hour).total_seconds() + + context['upload_hour_count'] = '-' + context['upload_hour_rate'] = '-' + + if len(context['uploads_hour']) > 0: + context['upload_hour_count'] = context['uploads_hour'][-1]['count'] + context['upload_hour_rate'] = context['uploads_hour'][-1]['count'] / upload_seconds + context['upload_hour_seconds'] = upload_seconds + context['uploads_hour'] = arrow.get(context['uploads_hour'][-1]['sample_date']).datetime.astimezone(here_tz) # c['uploads_hour'][-1]['sample_date'] + + active_count = 0 + inactive_count = 0 + + for device in PurpleRobotDevice.objects.all(): + last_reading = device.most_recent_reading('edu.northwestern.cbits.purple_robot_manager.probes.builtin.RobotHealthProbe') + + if last_reading is not None and last_reading.logged > week_ago: + active_count += 1 + else: + inactive_count += 1 + + context['active_devices'] = active_count + context['inactive_devices'] = inactive_count + + day_items = [] + hour_items = [] + + for item in context['ingest_performance']: + if arrow.get(item['sample_date']).datetime >= start_today: + day_items.append(item['num_extracted'] / (item['extraction_time'] + item['query_time'])) + + if arrow.get(item['sample_date']).datetime >= start_hour: + hour_items.append(item['num_extracted'] / (item['extraction_time'] + item['query_time'])) + + context['ingest_average_day'] = numpy.mean(day_items) + context['ingest_average_hour'] = numpy.mean(hour_items) + + day_items = [] + hour_items = [] + + for item in context['mirror_performance']: + if arrow.get(item['sample_date']).datetime >= start_today: + day_items.append(item['num_mirrored'] / (item['extraction_time'] + item['query_time'])) + + if arrow.get(item['sample_date']).datetime >= start_hour: + hour_items.append(item['num_mirrored'] / (item['extraction_time'] + item['query_time'])) + + if len(day_items) > 0: + context['mirror_average_day'] = numpy.mean(day_items) + + if len(hour_items) > 0: + context['mirror_average_hour'] = numpy.mean(hour_items) + + return render_to_response('purple_robot_status.html', context) + + +@staff_member_required +@never_cache +def pr_users(request): + context = RequestContext(request) + context.update(csrf(request)) + + context['groups'] = PurpleRobotDeviceGroup.objects.all().order_by('group_id') + context['unaffiliated'] = PurpleRobotDevice.objects.filter(device_group=None).order_by('device_id') + + phantoms = fetch_performance_users() + + for device in PurpleRobotDevice.objects.all(): + if device.hash_key is not None and device.hash_key in phantoms: + del phantoms[device.hash_key] + + context['phantoms'] = phantoms + + return render_to_response('purple_robot_users.html', context)