]> www.vanbest.org Git - motioneye-debian/commitdiff
config ui is now linked with the config files
authorCalin Crisan <ccrisan@gmail.com>
Sat, 28 Sep 2013 11:33:21 +0000 (14:33 +0300)
committerCalin Crisan <ccrisan@gmail.com>
Sat, 28 Sep 2013 11:33:21 +0000 (14:33 +0300)
doc/todo.txt
motioneye.py
src/config.py
src/handlers.py
static/js/base-site.js
static/js/ui.js
templates/base-site.html

index 4121850a2e1f8c0c8daa665ddb9f722ba129aa63..9fd65dc2fbaeb3a892f2360056953d211f7d843d 100644 (file)
@@ -1,5 +1,6 @@
+-> add complete js validation
 -> group @config rules to top
--> notification: multiple email addresses and phone numbers
+-> notification: multiple email addresses
 -> what do we do with working schedule
 -> browser compatibility test
 -> hint text next to section titles
index eeec7a910edef2a06ec17dcfcc656ed8f88b85a0..f3414f23cdc0d02675af3c3c382303e57dfe940c 100644 (file)
@@ -46,7 +46,7 @@ if __name__ == '__main__':
     
 #     import config
 #     main_config = config.get_main()
-#     #config.add_camera('v4l2:///dev/video0')
+#     config.add_camera('v4l2:///dev/video0')
 #     #data = config.get_camera(1)
 #     #data['@enabled'] = True
 #     #config.set_camera(1, data)
index 61f945c7c83a32e2024bd5adf1594dd9db105dc9..9d67eab76d184a3c9a500ba78ab2a290cf4883bd 100644 (file)
@@ -294,238 +294,6 @@ def rem_camera(camera_id):
         raise
 
 
-def camera_ui_to_dict(camera_id, ui):
-    video_device = ui.get('device', '')
-    if video_device.count('://'):
-        video_device = video_device.split('://')[-1]
-
-    data = {
-        # device
-        '@name': ui.get('name', ''),
-        '@enabled': ui.get('enabled', False),
-        'videodevice': video_device,
-        'lightswitch': int(ui.get('light_switch_detect', False) * 5),
-        'auto_brightness': ui.get('auto_brightness', False),
-        'brightness': int(int(ui.get('brightness', 0)) * 2.55),
-        'contrast': int(int(ui.get('contrast', 0)) * 2.55),
-        'saturation': int(int(ui.get('saturation', 0)) * 2.55),
-        'hue': int(int(ui.get('hue', 0))),
-        'width': int(ui.get('resolution', '352x288').split('x')[0]),
-        'height': int(ui.get('resolution', '352x288').split('x')[1]),
-        'framerate': int(ui.get('framerate', 1)),
-        
-        # file storage
-        '@storage_device': ui.get('storage_device', 'local-disk'),
-        '@network_server': ui.get('network_server', ''),
-        '@network_share_name': ui.get('network_share_name', ''),
-        '@network_username': ui.get('network_username', ''),
-        '@network_password': ui.get('network_password', ''),
-        'target_dir': ui.get('root_directory', '/'),
-        
-        # text overlay
-        'text_left': '',
-        'text_right': '',
-        
-        # streaming
-        'webcam_localhost': not ui.get('video_streaming', True),
-        'webcam_port': int(ui.get('streaming_port', 8080)),
-        'webcam_maxrate': int(ui.get('streaming_framerate', 1)),
-        'webcam_quality': max(1, int(ui.get('streaming_quality', 50))),
-        
-        # still images
-        'output_normal': False,
-        'output_all': False,
-        'output_motion': False,
-        'snapshot_interval': 0,
-        'jpeg_filename': '',
-        'snapshot_filename': '',
-        # TODO preserve images
-        
-        # movies
-        'ffmpeg_variable_bitrate': 0,
-        'ffmpeg_video_codec': 'mpeg4',
-        'ffmpeg_cap_new': True,
-        'movie_filename': '',
-        # TODO preserve movies
-    
-        # motion detection
-        'text_changes': ui.get('show_frame_changes', False),
-        'locate': ui.get('show_frame_changes', False),
-        'threshold': ui.get('frame_change_threshold', 1500),
-        'noise_tune': ui.get('auto_noise_detect', True),
-        'noise_level': max(1, int(int(ui.get('noise_level', 8)) * 2.55)),
-        'gap': int(ui.get('gap', 60)),
-        'pre_capture': int(ui.get('pre_capture', 0)),
-        'post_capture': int(ui.get('post_capture', 0)),
-        
-        # TODO notifications
-    }
-    
-    if ui.get('text_overlay', False):
-        left_text = ui.get('left_text', 'camera-name')
-        if left_text == 'camera-name':
-            data['text_left'] = ui.get('name')
-            
-        elif left_text == 'timestamp':
-            data['text_left'] = '%Y-%m-%d\n%T'
-            
-        else:
-            data['text_left'] = ui.get('custom_left_text', '')
-        
-        right_text = ui.get('right_text', 'timestamp')
-        if right_text == 'camera-name':
-            data['text_right'] = ui.get('name')
-            
-        elif right_text == 'timestamp':
-            data['text_right'] = '%Y-%m-%d\n%T'
-            
-        else:
-            data['text_right'] = ui.get('custom_right_text', '')
-
-    if ui.get('still_images', False):
-        capture_mode = ui.get('capture_mode', 'motion-triggered')
-        if capture_mode == 'motion-triggered':
-            data['output_normal'] = True
-            data['jpeg_filename'] = ui.get('image_file_name', '%Y-%m-%d-%H-%M-%S-%q')  
-            
-        elif capture_mode == 'interval-snapshots':
-            data['snapshot_interval'] = int(ui.get('snapshot_interval'), 300)
-            data['snapshot_filename'] = ui.get('image_file_name', '%Y-%m-%d-%H-%M-%S-%q')
-            
-        elif capture_mode == 'all-frames':
-            data['output_all'] = True
-            data['jpeg_filename'] = ui.get('image_file_name', '%Y-%m-%d-%H-%M-%S')
-            
-        data['quality'] = max(1, int(ui.get('image_quality', 75)))
-        
-    if ui.get('motion_movies', False):
-        data['ffmpeg_variable_bitrate'] = 2 + int((100 - int(ui.get('movie_quality', 50))) * 0.29)
-        data['movie_filename'] = ui.get('movie_file_name', '%Y-%m-%d-%H-%M-%S-%q')
-
-    return data
-    
-
-def camera_dict_to_ui(camera_id, data):
-    # set the default options if not present
-    _set_default_motion_camera(data)
-    
-    ui = {
-        # device
-        'name': data['@name'],
-        'enabled': data['@enabled'],
-        'device': 'v4l2://' + data['videodevice'],
-        'light_switch_detect': data['lightswitch'] > 0,
-        'auto_brightness': data['auto_brightness'],
-        'brightness': int(int(data['brightness']) / 2.55),
-        'contrast': int(int(data['contrast']) / 2.55),
-        'saturation': int(int(data['saturation']) / 2.55),
-        'hue': int(int(data['hue'])),
-        'resolution': str(data['width']) + 'x' + str(data['height']),
-        'framerate': int(data['framerate']),
-        
-        # file storage
-        'storage_device': data['@storage_device'],
-        'network_server': data['@network_server'],
-        'network_share_name': data['@network_share_name'],
-        'network_username': data['@network_username'],
-        'network_password': data['@network_password'],
-        'root_directory': data['target_dir'],
-        
-        # text overlay
-        'text_overlay': False,
-        'left_text': 'camera-name',
-        'right_text': 'timestamp',
-        
-        # streaming
-        'vudeo_streaming': not data['webcam_localhost'],
-        'streaming_port': int(data['webcam_port']),
-        'streaming_framerate': int(data['webcam_maxrate']),
-        'streaming_quality': int(data['webcam_quality']),
-        
-        # still images
-        'still_images': False,
-        'capture_mode': 'motion-triggered',
-        'image_file_name': '%Y-%m-%d-%H-%M-%S',
-        'image_quality': 75,
-        # TODO preserve images
-        
-        # motion movies
-        'motion_movies': False,
-        'movie_quality': 50,
-        'movie_file_name': '%Y-%m-%d-%H-%M-%S-%q',
-        # TODO preserve movies
-        
-        # motion detection
-        'show_frame_changes': data.get('text_changes') or data.get('locate'),
-        'frame_change_threshold': data['threshold'],
-        'auto_noise_detect': data['noise_tune'],
-        'noise_level': int(int(data['noise_level']) / 2.55),
-        'gap': int(data['gap']),
-        'pre_capture': int(data['pre_capture']),
-        'post_capture': int(data['post_capture']),
-        
-        # TODO notifications
-    }
-    
-    text_left = data['text_left']
-    text_right = data['text_right'] 
-    if text_left or text_right:
-        ui['text_overlay'] = True
-        
-        if text_left == data['@name']:
-            ui['left_text'] = 'camera-name'
-            
-        elif text_left == '%Y-%m-%d\n%T':
-            ui['left_text'] = 'timestamp'
-            
-        else:
-            ui['left_text'] = 'custom-text'
-            ui['custom_left_text'] = text_left
-
-        if text_right == data['@name']:
-            ui['right_text'] = 'camera-name'
-            
-        elif text_right == '%Y-%m-%d\n%T':
-            ui['right_text'] = 'timestamp'
-            
-        else:
-            ui['right_text'] = 'custom-text'
-            ui['custom_right_text'] = text_right
-
-    output_all = data.get('output_all')
-    output_normal = data.get('output_normal')
-    jpeg_filename = data.get('jpeg_filename')
-    snapshot_interval = data.get('snapshot_interval')
-    snapshot_filename = data.get('snapshot_filename')
-    
-    if (((output_all or output_normal) and jpeg_filename) or
-        (snapshot_interval and snapshot_filename)):
-        
-        ui['still_images'] = True
-        
-        if output_all:
-            ui['capture_mode'] = 'all-frames'
-            ui['image_file_name'] = jpeg_filename
-            
-        elif data.get('snapshot_interval'):
-            ui['capture-mode'] = 'interval-snapshots'
-            ui['image_file_name'] = snapshot_filename
-            
-        elif data.get('output_normal'):
-            ui['capture-mode'] = 'motion-triggered'
-            ui['image_file_name'] = jpeg_filename  
-            
-        ui['image_quality'] = ui.get('quality', 75)
-    
-    movie_filename = data.get('movie_filename')
-    if movie_filename:
-        ui['motion_movies'] = True
-        ui['movie_quality'] = int((max(2, data['ffmpeg_variable_bitrate']) - 2) / 0.29)
-        ui['movie_file_name'] = movie_filename
-    
-    return data
-    
-
 def _value_to_python(value):
     value_lower = value.lower()
     if value_lower == 'off':
@@ -693,6 +461,7 @@ def _set_default_motion(data):
 def _set_default_motion_camera(data):
     data.setdefault('@name', '')
     data.setdefault('@enabled', False)
+    data.setdefault('@proto', 'v4l2')
     data.setdefault('videodevice', '')
     data.setdefault('lightswitch', 0)
     data.setdefault('auto_brightness', False)
@@ -715,6 +484,7 @@ def _set_default_motion_camera(data):
     data.setdefault('webcam_port', 8080)
     data.setdefault('webcam_maxrate', 1)
     data.setdefault('webcam_quality', 50)
+    data.setdefault('webcam_motion', False)
     
     data.setdefault('text_left', '')
     data.setdefault('text_right', '')
@@ -735,6 +505,14 @@ def _set_default_motion_camera(data):
     data.setdefault('snapshot_interval', 0)
     data.setdefault('snapshot_filename', '')
     data.setdefault('quality', 75)
+    data.setdefault('@preserve_images', 0)
     
     data.setdefault('movie_filename', '')
     data.setdefault('ffmpeg_variable_bitrate', 14)
+    data.setdefault('@preserve_movies', 0)
+    
+    data.setdefault('@motion_notifications', False)
+    data.setdefault('@motion_notifications_emails', '')
+    
+    data.setdefault('@working_schedule', '')
+
index 655f9586082b8e5aa6422e8a77beefa4d4115f79..b5097294de583f9ac8fd8068f4c05eb74285995c 100644 (file)
@@ -63,11 +63,13 @@ class ConfigHandler(BaseHandler):
             if camera_id not in camera_ids:
                 raise HTTPError(404, 'no such camera')
             
-            self.finish_json(config.get_camera(camera_id))
+            ui_config = self._camera_dict_to_ui(config.get_camera(camera_id))
+            self.finish_json(ui_config)
             
         else:
             logging.debug('getting main config')
             
+            # TODO _main_dict_to_ui
             self.finish_json(config.get_main())
     
     def set_config(self, camera_id):
@@ -85,7 +87,8 @@ class ConfigHandler(BaseHandler):
             camera_ids = config.get_camera_ids()
             if camera_id not in camera_ids:
                 raise HTTPError(404, 'no such camera')
-            
+
+            data = self._camera_ui_to_dict(data)    
             config.set_camera(camera_id, data)
 
         else:
@@ -99,6 +102,7 @@ class ConfigHandler(BaseHandler):
                 
                 raise
             
+            # TODO _main_ui_to_dict
             config.set_main(data)
     
     def list_cameras(self):
@@ -127,6 +131,281 @@ class ConfigHandler(BaseHandler):
         
         config.rem_camera(camera_id)
 
+    def _camera_ui_to_dict(self, ui):
+        video_device = ui.get('device', '')
+        if video_device.count('://'):
+            video_device = video_device.split('://')[-1]
+            
+        if not ui.get('resolution'): # avoid errors for empty resolution setting
+            ui['resolution'] = '352x288'
+    
+        data = {
+            # device
+            '@name': ui.get('name', ''),
+            '@enabled': ui.get('enabled', False),
+            'videodevice': video_device,
+            'lightswitch': int(ui.get('light_switch_detect', False) * 5),
+            'auto_brightness': ui.get('auto_brightness', False),
+            'brightness': int(int(ui.get('brightness', 0)) * 2.55),
+            'contrast': int(int(ui.get('contrast', 0)) * 2.55),
+            'saturation': int(int(ui.get('saturation', 0)) * 2.55),
+            'hue': int(int(ui.get('hue', 0))),
+            'width': int(ui['resolution'].split('x')[0]),
+            'height': int(ui['resolution'].split('x')[1]),
+            'framerate': int(ui.get('framerate', 1)),
+            
+            # file storage
+            '@storage_device': ui.get('storage_device', 'local-disk'),
+            '@network_server': ui.get('network_server', ''),
+            '@network_share_name': ui.get('network_share_name', ''),
+            '@network_username': ui.get('network_username', ''),
+            '@network_password': ui.get('network_password', ''),
+            'target_dir': ui.get('root_directory', '/'),
+            
+            # text overlay
+            'text_left': '',
+            'text_right': '',
+            
+            # streaming
+            'webcam_localhost': not ui.get('video_streaming', True),
+            'webcam_port': int(ui.get('streaming_port', 8080)),
+            'webcam_maxrate': int(ui.get('streaming_framerate', 1)),
+            'webcam_quality': max(1, int(ui.get('streaming_quality', 50))),
+            'webcam_motion': ui.get('streaming_motion', False),
+            
+            # still images
+            'output_normal': False,
+            'output_all': False,
+            'output_motion': False,
+            'snapshot_interval': 0,
+            'jpeg_filename': '',
+            'snapshot_filename': '',
+            '@preserve_images': int(ui.get('preserve_images', 0)),
+            
+            # movies
+            'ffmpeg_variable_bitrate': 0,
+            'ffmpeg_video_codec': 'mpeg4',
+            'ffmpeg_cap_new': True,
+            'movie_filename': '',
+            '@preserve_movies': int(ui.get('preserve_movies', 0)),
+        
+            # motion detection
+            'text_changes': ui.get('show_frame_changes', False),
+            'locate': ui.get('show_frame_changes', False),
+            'threshold': ui.get('frame_change_threshold', 1500),
+            'noise_tune': ui.get('auto_noise_detect', True),
+            'noise_level': max(1, int(int(ui.get('noise_level', 8)) * 2.55)),
+            'gap': int(ui.get('gap', 60)),
+            'pre_capture': int(ui.get('pre_capture', 0)),
+            'post_capture': int(ui.get('post_capture', 0)),
+            
+            # motion notifications
+            '@motion_notifications': ui.get('motion_notifications', False),
+            '@motion_notifications_emails': ui.get('motion_notifications_emails', ''),
+            
+            # working schedule
+            '@working_schedule': ''
+        }
+        
+        if ui.get('text_overlay', False):
+            left_text = ui.get('left_text', 'camera-name')
+            if left_text == 'camera-name':
+                data['text_left'] = ui.get('name')
+                
+            elif left_text == 'timestamp':
+                data['text_left'] = '%Y-%m-%d\n%T'
+                
+            else:
+                data['text_left'] = ui.get('custom_left_text', '')
+            
+            right_text = ui.get('right_text', 'timestamp')
+            if right_text == 'camera-name':
+                data['text_right'] = ui.get('name')
+                
+            elif right_text == 'timestamp':
+                data['text_right'] = '%Y-%m-%d\n%T'
+                
+            else:
+                data['text_right'] = ui.get('custom_right_text', '')
+    
+        if ui.get('still_images', False):
+            capture_mode = ui.get('capture_mode', 'motion-triggered')
+            if capture_mode == 'motion-triggered':
+                data['output_normal'] = True
+                data['jpeg_filename'] = ui.get('image_file_name', '%Y-%m-%d-%H-%M-%S-%q')  
+                
+            elif capture_mode == 'interval-snapshots':
+                data['snapshot_interval'] = int(ui.get('snapshot_interval'), 300)
+                data['snapshot_filename'] = ui.get('image_file_name', '%Y-%m-%d-%H-%M-%S-%q')
+                
+            elif capture_mode == 'all-frames':
+                data['output_all'] = True
+                data['jpeg_filename'] = ui.get('image_file_name', '%Y-%m-%d-%H-%M-%S')
+                
+            data['quality'] = max(1, int(ui.get('image_quality', 75)))
+            
+        if ui.get('motion_movies', False):
+            data['ffmpeg_variable_bitrate'] = 2 + int((100 - int(ui.get('movie_quality', 50))) * 0.29)
+            data['movie_filename'] = ui.get('movie_file_name', '%Y-%m-%d-%H-%M-%S-%q')
+            
+        if ui.get('working_schedule', False):
+            data['@working_schedule'] = (
+                    ui.get('monday_from', '') + '-' + ui.get('monday_to') + '|' +
+                    ui.get('tuesday_from', '') + '-' + ui.get('tuesday_to') + '|' +
+                    ui.get('wednesday_from', '') + '-' + ui.get('wednesday_to') + '|' +
+                    ui.get('thursday_from', '') + '-' + ui.get('thursday_to') + '|' +
+                    ui.get('friday_from', '') + '-' + ui.get('friday_to') + '|' +
+                    ui.get('saturday_from', '') + '-' + ui.get('saturday_to') + '|' +
+                    ui.get('sunday_from', '') + '-' + ui.get('sunday_to'))
+    
+        return data
+        
+    def _camera_dict_to_ui(self, data):
+        ui = {
+            # device
+            'name': data['@name'],
+            'enabled': data['@enabled'],
+            'device': data['@proto'] + '://' + data['videodevice'],
+            'light_switch_detect': data['lightswitch'] > 0,
+            'auto_brightness': data['auto_brightness'],
+            'brightness': int(int(data['brightness']) / 2.55),
+            'contrast': int(int(data['contrast']) / 2.55),
+            'saturation': int(int(data['saturation']) / 2.55),
+            'hue': int(int(data['hue'])),
+            'resolution': str(data['width']) + 'x' + str(data['height']),
+            'framerate': int(data['framerate']),
+            
+            # file storage
+            'storage_device': data['@storage_device'],
+            'network_server': data['@network_server'],
+            'network_share_name': data['@network_share_name'],
+            'network_username': data['@network_username'],
+            'network_password': data['@network_password'],
+            'root_directory': data['target_dir'],
+            
+            # text overlay
+            'text_overlay': False,
+            'left_text': 'camera-name',
+            'right_text': 'timestamp',
+            'custom_left_text': '',
+            'custom_right_text': '',
+            
+            # streaming
+            'vudeo_streaming': not data['webcam_localhost'],
+            'streaming_port': int(data['webcam_port']),
+            'streaming_framerate': int(data['webcam_maxrate']),
+            'streaming_quality': int(data['webcam_quality']),
+            'streaming_motion': int(data['webcam_motion']),
+            
+            # still images
+            'still_images': False,
+            'capture_mode': 'motion-triggered',
+            'image_file_name': '%Y-%m-%d-%H-%M-%S',
+            'image_quality': 75,
+            'snapshot_interval': 0,
+            'preserve_images': data['@preserve_images'],
+            
+            # motion movies
+            'motion_movies': False,
+            'movie_quality': 50,
+            'movie_file_name': '%Y-%m-%d-%H-%M-%S-%q',
+            'preserve_movies': data['@preserve_movies'],
+            
+            # motion detection
+            'show_frame_changes': data.get('text_changes') or data.get('locate'),
+            'frame_change_threshold': data['threshold'],
+            'auto_noise_detect': data['noise_tune'],
+            'noise_level': int(int(data['noise_level']) / 2.55),
+            'gap': int(data['gap']),
+            'pre_capture': int(data['pre_capture']),
+            'post_capture': int(data['post_capture']),
+            
+            # motion notifications
+            'motion_notifications': data['@motion_notifications'],
+            'motion_notifications_emails': data['@motion_notifications_emails'],
+            
+            # working schedule
+            'working_schedule': False,
+            'monday_from': '09:00', 'monday_to': '17:00',
+            'tuesday_from': '09:00', 'tuesday_to': '17:00',
+            'wednesday_from': '09:00', 'wednesday_to': '17:00',
+            'thursday_from': '09:00', 'thursday_to': '17:00',
+            'friday_from': '09:00', 'friday_to': '17:00',
+            'saturday_from': '09:00', 'saturday_to': '17:00',
+            'sunday_from': '09:00', 'sunday_to': '17:00'
+        }
+        
+        text_left = data['text_left']
+        text_right = data['text_right'] 
+        if text_left or text_right:
+            ui['text_overlay'] = True
+            
+            if text_left == data['@name']:
+                ui['left_text'] = 'camera-name'
+                
+            elif text_left == '%Y-%m-%d\n%T':
+                ui['left_text'] = 'timestamp'
+                
+            else:
+                ui['left_text'] = 'custom-text'
+                ui['custom_left_text'] = text_left
+    
+            if text_right == data['@name']:
+                ui['right_text'] = 'camera-name'
+                
+            elif text_right == '%Y-%m-%d\n%T':
+                ui['right_text'] = 'timestamp'
+                
+            else:
+                ui['right_text'] = 'custom-text'
+                ui['custom_right_text'] = text_right
+    
+        output_all = data.get('output_all')
+        output_normal = data.get('output_normal')
+        jpeg_filename = data.get('jpeg_filename')
+        snapshot_interval = data.get('snapshot_interval')
+        snapshot_filename = data.get('snapshot_filename')
+        
+        if (((output_all or output_normal) and jpeg_filename) or
+            (snapshot_interval and snapshot_filename)):
+            
+            ui['still_images'] = True
+            
+            if output_all:
+                ui['capture_mode'] = 'all-frames'
+                ui['image_file_name'] = jpeg_filename
+                
+            elif data.get('snapshot_interval'):
+                ui['capture-mode'] = 'interval-snapshots'
+                ui['image_file_name'] = snapshot_filename
+                ui['snapshot_interval'] = snapshot_interval
+                
+            elif data.get('output_normal'):
+                ui['capture-mode'] = 'motion-triggered'
+                ui['image_file_name'] = jpeg_filename  
+                
+            ui['image_quality'] = ui.get('quality', 75)
+        
+        movie_filename = data.get('movie_filename')
+        if movie_filename:
+            ui['motion_movies'] = True
+            ui['movie_quality'] = int((max(2, data['ffmpeg_variable_bitrate']) - 2) / 0.29)
+            ui['movie_file_name'] = movie_filename
+            
+        working_schedule = data.get('@working_schedule')
+        if working_schedule:
+            days = working_schedule.split('|')
+            ui['monday_from'], ui['monday_to'] = days[0].split('-')
+            ui['tuesday_from'], ui['tuesday_to'] = days[1].split('-')
+            ui['wednesday_from'], ui['wednesday_to'] = days[2].split('-')
+            ui['thursday_from'], ui['thursday_to'] = days[3].split('-')
+            ui['friday_from'], ui['friday_to'] = days[4].split('-')
+            ui['saturday_from'], ui['saturday_to'] = days[5].split('-')
+            ui['sunday_from'], ui['sunday_to'] = days[6].split('-')
+            ui['working_schedule'] = True
+        
+        return ui
+        
 
 class SnapshotHandler(BaseHandler):
     def get(self, camera_id, op, filename=None):
index 69be880d4fb954f2d1a452814af94cbf94fb5318..58e55d06892bb3314295e9dc6eb206c578c7ceda 100644 (file)
@@ -5,15 +5,18 @@ var noPushLock = 0;
 
     /* Ajax */
 
-function ajax(method, url, data, success) {
+function ajax(method, url, data, callback) {
     var options = {
         type: method,
         url: url,
         data: data,
         cache: false,
-        success: success,
+        success: callback,
         failure: function (request, options, error) {
             alert('Request failed with code: ' + request.status);
+            if (callback) {
+                callback();
+            }
         }
     };
     
@@ -29,10 +32,12 @@ function ajax(method, url, data, success) {
     /* UI */
 
 function initUI() {
+    /* checkboxes */
     $('input[type=checkbox].styled').each(function () {
         makeCheckBox($(this));
     });
 
+    /* sliders */
     makeSlider($('#brightnessSlider'), 0, 100, 0, null, 5, 0, '%');
     makeSlider($('#contrastSlider'), 0, 100, 0, null, 5, 0, '%');
     makeSlider($('#saturationSlider'), 0, 100, 0, null, 5, 0, '%');
@@ -61,11 +66,14 @@ function initUI() {
     makeSlider($('#frameChangeThresholdSlider'), 0, 10000, 0, null, 3, 0, 'px');
     makeSlider($('#noiseLevelSlider'), 0, 100, 0, null, 5, 0, '%');
     
-    makeNumberValidator($('#snapshotIntervalEntry'), 1, 86400, false, false);
-    makeNumberValidator($('#gapEntry'), 1, 86400, false, false);
-    makeNumberValidator($('#preCaptureEntry'), 0, 100, false, false);
-    makeNumberValidator($('#postCaptureEntry'), 0, 100, false, false);
+    /* number validators */
+    makeNumberValidator($('#streamingPortEntry'), 1024, 65535, false, false, true);
+    makeNumberValidator($('#snapshotIntervalEntry'), 1, 86400, false, false, true);
+    makeNumberValidator($('#gapEntry'), 1, 86400, false, false, true);
+    makeNumberValidator($('#preCaptureEntry'), 0, 100, false, false, true);
+    makeNumberValidator($('#postCaptureEntry'), 0, 100, false, false, true);
     
+    /* time validators */
     makeTimeValidator($('#mondayFrom'));
     makeTimeValidator($('#mondayTo'));
     makeTimeValidator($('#tuesdayFrom'));
@@ -81,6 +89,7 @@ function initUI() {
     makeTimeValidator($('#sundayFrom'));
     makeTimeValidator($('#sundayTo'));
     
+    /* ui elements that enable/disable other ui elements */
     $('#motionEyeSwitch').change(updateConfigUI);
     $('#showAdvancedSwitch').change(updateConfigUI);
     $('#storageDeviceSelect').change(updateConfigUI);
@@ -97,8 +106,18 @@ function initUI() {
     $('#motionNotificationsSwitch').change(updateConfigUI);
     $('#workingScheduleSwitch').change(updateConfigUI);
     
-    $('#videoDeviceSwitch').change(fetchCameraConfig);
+    /* fetch & push handlers */
+    $('#videoDeviceSelect').change(fetchCameraConfig);
     $('input.general').change(pushMainConfig);
+    $('input.device, select.device, ' +
+      'input.storage, select.storage, ' +
+      'input.text-overlay, select.text-overlay, ' + 
+      'input.streaming, select.streaming, ' +
+      'input.still-images, select.still-images, ' +
+      'input.motion-movies, select.motion-movies, ' +
+      'input.motion-detection, select.motion-detection, ' +
+      'input.notifications, select.notifications, ' +
+      'input.working-schedule, select.working-schedule').change(pushCameraConfig);
 }
 
 function updateConfigUI() {
@@ -203,6 +222,23 @@ function updateConfigUI() {
         }
     });
     
+    /* re-validate all the input validators */
+    $('div.settings').find('input.number-validator, input.time-validator').each(function () {
+        this.validate();
+    });
+    
+    /* update all checkboxes and sliders */
+    $('div.settings').find('input[type=checkbox], input.range').each(function () {
+        this.update();
+    });
+    
+    /* select the first option for the selects with no current selection */
+    $('div.settings').find('select').each(function () {
+        if (this.selectedIndex === -1) {
+            this.selectedIndex = 0;
+        }
+    });
+    
     noPushLock--;
 }
 
@@ -221,22 +257,98 @@ function dict2MainUi(dict) {
     noPushLock++;
     
     $('#motionEyeSwitch')[0].checked = dict['@enabled'];
-    $('#motionEyeSwitch').change();
-    
     $('#showAdvancedSwitch')[0].checked = dict['@show_advanced'];
-    $('#showAdvancedSwitch').change();
-    
     $('#adminUsernameEntry').val(dict['@admin_username']);
     $('#adminPasswordEntry').val(dict['@admin_password']);
     $('#normalUsernameEntry').val(dict['@normal_username']);
     $('#normalPasswordEntry').val(dict['@normal_password']);
     
+    updateConfigUI();
+    
     noPushLock--;
 }
 
 function cameraUi2Dict() {
     return {
+        /* video device */
+        'enabled': $('#videoDeviceSwitch')[0].checked,
+        'name': $('#deviceNameEntry').val(),
+        'device': $('#deviceEntry').val(),
+        'light_switch_detect': $('#lightSwitchDetectSwitch')[0].checked,
+        'auto_brightness': $('#autoBrightnessSwitch')[0].checked,
+        'brightness': $('#brightnessSlider').val(),
+        'contrast': $('#contrastSlider').val(),
+        'saturation': $('#saturationSlider').val(),
+        'hue': $('#hueSlider').val(),
+        'resolution': $('#resolutionSelect').val(),
+        'rotation': $('#rotationSelect').val(),
+        'framerate': $('#framerateSlider').val(),
+        
+        /* file storage */
+        'storage_device': $('#storageDeviceSelect').val(),
+        'network_server': $('#networkServerEntry').val(),
+        'network_share_name': $('#networkShareNameEntry').val(),
+        'network_username': $('#networkUsernameEntry').val(),
+        'network_password': $('#networkPasswordEntry').val(),
+        'root_directory': $('#rootDirectoryEntry').val(),
+        
+        /* text overlay */
+        'text_overlay': $('#textOverlaySwitch')[0].checked,
+        'left_text': $('#leftTextSelect').val(),
+        'custom_left_text': $('#leftTextEntry').val(),
+        'right_text': $('#rightTextSelect').val(),
+        'custom_right_text': $('#rightTextEntry').val(),
+        
+        /* video streaming */
+        'video_streaming': $('#videoStreamingSwitch')[0].checked,
+        'streaming_port': $('#streamingPortEntry').val(),
+        'streaming_framerate': $('#streamingFramerateSlider').val(),
+        'streaming_quality': $('#streamingQualitySlider').val(),
+        'streaming_motion': $('#streamingMotion')[0].checked,
+        
+        /* still images */
+        'still_images': $('#stillImagesSwitch')[0].checked,
+        'image_file_name': $('#imageFileNameEntry').val(),
+        'image_quality': $('#imageQualitySlider').val(),
+        'capture_mode': $('#captureModeSelect').val(),
+        'snapshot_interval': $('#snapshotIntervalEntry').val(),
+        'preserve_images': $('#preserveImagesSelect').val(),
+        
+        /* motion movies */
+        'motion_movies': $('#motionMoviesSwitch')[0].checked,
+        'movie_file_name': $('#movieFileNameEntry').val(),
+        'movie_quality': $('#movieQualitySlider').val(),
+        'preserve_movies': $('#preserveMoviesSelect').val(),
+        
+        /* motion detection */
+        'show_frame_changes': $('#showFrameChangesSwitch')[0].checked,
+        'frame_change_threshold': $('#frameChangeThresholdSlider').val(),
+        'auto_noise_detect': $('#autoNoiseDetectSwitch')[0].checked,
+        'noise_level': $('#noiseLevelSlider').val(),
+        'gap': $('#gapEntry').val(),
+        'pre_capture': $('#preCaptureEntry').val(),
+        'post_capture': $('#postCaptureEntry').val(),
         
+        /* motion notifications */
+        'motion_notifications': $('#motionNotificationsSwitch')[0].checked,
+        'motion_notifications_emails': $('#emailAddressesEntry').val(),
+        
+        /* working schedule */
+        'working_schedule': $('#workingScheduleSwitch')[0].checked,
+        'monday_from': $('#mondayFrom').val(),
+        'monday_to':$('#mondayTo').val(),
+        'tuesday_from': $('#tuesdayFrom').val(),
+        'tuesday_to': $('#tuesdayTo').val(),
+        'wednesday_from': $('#wednesdayFrom').val(),
+        'wednesday_to': $('#wednesdayTo').val(),
+        'thursday_from': $('#thursdayFrom').val(),
+        'thursday_to': $('#thursdayTo').val(),
+        'friday_from':$('#fridayFrom').val(),
+        'friday_to': $('#fridayTo').val(),
+        'saturday_from':$('#saturdayFrom').val(),
+        'saturday_to': $('#saturdayTo').val(),
+        'sunday_from': $('#sundayFrom').val(),
+        'sunday_to': $('#sundayTo').val(),
     };
 }
 
@@ -244,83 +356,86 @@ function dict2CameraUi(dict) {
     noPushLock++;
     
     /* video device */
-    $('#videoDeviceSwitch');
-    $('#deviceNameEntry');
-    $('#lightSwitchDetectSwitch');
-    $('#autoBrightnessSwitch');
-    $('#brightnessSlider');
-    $('#constrastSlider');
-    $('#saturationSlider');
-    $('#hueSlider');
-    $('#resolutionSelect');
-    $('#rotationSelect');
-    $('#framerateSlider');
+    $('#videoDeviceSwitch')[0].checked = dict['enabled'];
+    $('#deviceNameEntry').val(dict['name']);
+    $('#deviceEntry').val(dict['device']);
+    $('#lightSwitchDetectSwitch')[0].checked = dict['light_switch_detect'];
+    $('#autoBrightnessSwitch')[0].checked = dict['auto_brightness'];
+    $('#brightnessSlider').val(dict['brightness']);
+    $('#contrastSlider').val(dict['contrast']);
+    $('#saturationSlider').val(dict['saturation']);
+    $('#hueSlider').val(dict['hue']);
+    $('#resolutionSelect').val(dict['resolution']);
+    $('#rotationSelect').val(dict['rotation']);
+    $('#framerateSlider').val(dict['framerate']);
     
     /* file storage */
-    $('#storageDeviceSelect');
-    $('#networkServerEntry');
-    $('#networkShareNameEntry');
-    $('#networkUsernameEntry');
-    $('#networkPasswordEntry');
-    $('#rootDirectoryEntry');
+    $('#storageDeviceSelect').val(dict['storage_device']);
+    $('#networkServerEntry').val(dict['network_server']);
+    $('#networkShareNameEntry').val(dict['network_share_name']);
+    $('#networkUsernameEntry').val(dict['network_username']);
+    $('#networkPasswordEntry').val(dict['network_password']);
+    $('#rootDirectoryEntry').val(dict['root_directory']);
     
     /* text overlay */
-    $('#textOverlaySwitch');
-    $('#leftTextSelect');
-    $('#leftTextEntry');
-    $('#rightTextSelect');
-    $('#rightTextEntry');
+    $('#textOverlaySwitch')[0].checked = dict['text_overlay'];
+    $('#leftTextSelect').val(dict['left_text']);
+    $('#leftTextEntry').val(dict['custom_left_text']);
+    $('#rightTextSelect').val(dict['right_text']);
+    $('#rightTextEntry').val(dict['custom_right_text']);
     
     /* video streaming */
-    $('#videoStreamingSwitch');
-    $('#streamingFramerateSlider');
-    $('#streamingQualitySlider');
-    $('#motionOptimizationSwitch');
+    $('#videoStreamingSwitch')[0].checked = dict['video_streaming'];
+    $('#streamingPortEntry').val(dict['streaming_port']);
+    $('#streamingFramerateSlider').val(dict['streaming_framerate']);
+    $('#streamingQualitySlider').val(dict['streaming_quality']);
+    $('#streamingMotion')[0].checked = dict['streaming_motion'];
     
     /* still images */
-    $('#stillImagesSwitch');
-    $('#imageFileNameEntry');
-    $('#imageQualitySlider');
-    $('#captureModeSelect');
-    $('#snapshotIntervalEntry');
-    $('#preserveImagesSelect');
+    $('#stillImagesSwitch')[0].checked = dict['still_images'];
+    $('#imageFileNameEntry').val(dict['image_file_name']);
+    $('#imageQualitySlider').val(dict['image_quality']);
+    $('#captureModeSelect').val(dict['capture_mode']);
+    $('#snapshotIntervalEntry').val(dict['snapshot_interval']);
+    $('#preserveImagesSelect').val(dict['preserve_images']);
     
     /* motion movies */
-    $('#motionMoviesSwitch');
-    $('#movieFileNameEntry');
-    $('#movieQualitySlider');
-    $('#preserveMoviesSelect');
+    $('#motionMoviesSwitch')[0].checked = dict['motion_movies'];
+    $('#movieFileNameEntry').val(dict['movie_file_name']);
+    $('#movieQualitySlider').val(dict['movie_quality']);
+    $('#preserveMoviesSelect').val(dict['preserve_movies']);
     
     /* motion detection */
-    $('#showFrameChangesSwitch');
-    $('#frameChangeThresholdSlider');
-    $('#autoNoiseDetectSwitch');
-    $('#noiseLevelSlider');
-    $('#gapEntry');
-    $('#preCaptureEntry');
-    $('#postCaptureEntry');
+    $('#showFrameChangesSwitch')[0].checked = dict['show_frame_changes'];
+    $('#frameChangeThresholdSlider').val(dict['frame_change_threshold']);
+    $('#autoNoiseDetectSwitch')[0].checked = dict['auto_noise_detect'];
+    $('#noiseLevelSlider').val(dict['noise_level']);
+    $('#gapEntry').val(dict['gap']);
+    $('#preCaptureEntry').val(dict['pre_capture']);
+    $('#postCaptureEntry').val(dict['post_capture']);
     
     /* motion notifications */
-    $('#motionNotificationsSwitch');
-    $('#emailAddressEntry');
-    $('#phoneNumberEntry');
+    $('#motionNotificationsSwitch')[0].checked = dict['motion_notifications'];
+    $('#emailAddressesEntry').val(dict['motion_notifications_emails']);
     
     /* working schedule */
-    $('#workingScheduleSwitch');
-    $('#mondayFrom');
-    $('#mondayTo');
-    $('#tuesdayFrom');
-    $('#tuesdayTo');
-    $('#wednesdayFrom');
-    $('#wednesdayTo');
-    $('#thursdayFrom');
-    $('#thursdayTo');
-    $('#fridayFrom');
-    $('#fridayTo');
-    $('#saturdayFrom');
-    $('#saturdayTo');
-    $('#sundayFrom');
-    $('#sundayTo');
+    $('#workingScheduleSwitch')[0].checked = dict['working_schedule'];
+    $('#mondayFrom').val(dict['monday_from']);
+    $('#mondayTo').val(dict['monday_to']);
+    $('#tuesdayFrom').val(dict['tuesday_from']);
+    $('#tuesdayTo').val(dict['tuesday_to']);
+    $('#wednesdayFrom').val(dict['wednesday_from']);
+    $('#wednesdayTo').val(dict['wednesday_to']);
+    $('#thursdayFrom').val(dict['thursday_from']);
+    $('#thursdayTo').val(dict['thursday_to']);
+    $('#fridayFrom').val(dict['friday_from']);
+    $('#fridayTo').val(dict['friday_to']);
+    $('#saturdayFrom').val(dict['saturday_from']);
+    $('#saturdayTo').val(dict['saturday_to']);
+    $('#sundayFrom').val(dict['sunday_from']);
+    $('#sundayTo').val(dict['sunday_to']);
+    
+    updateConfigUI();
     
     noPushLock--;
 }
@@ -365,10 +480,27 @@ function pushMainConfig() {
         return;
     }
     
+    noPushLock++;
+    
     var mainConfig = mainUi2Dict();
     
     ajax('POST', '/config/main/set/', mainConfig, function () {
-        
+        noPushLock--;
+    });
+}
+
+function pushCameraConfig() {
+    if (noPushLock) {
+        return;
+    }
+    
+    noPushLock++;
+    
+    var cameraConfig = cameraUi2Dict();
+    var cameraId = $('#videoDeviceSelect').val();
+    
+    ajax('POST', '/config/' + cameraId + '/set/', cameraConfig, function () {
+        noPushLock--;
     });
 }
 
index 8fbb1a3099e6c033058b228b96748dbdbd93bf60..f9b87475a96faf44c0e18e776b311fe41a042413 100644 (file)
@@ -23,15 +23,17 @@ function makeCheckBox($input) {
     /* add the element */
     $input.after(mainDiv);
     
-    /* add event handers */
-    $input.change(function () {
-        if (this.checked) {
+    function update() {
+        if ($input[0].checked) {
             setOn();
         }
         else {
             setOff();
         }
-    }).change();
+    }
+    
+    /* add event handers */
+    $input.change(update).change();
     
     mainDiv.click(function () {
         $input[0].checked = !$input[0].checked;
@@ -51,6 +53,8 @@ function makeCheckBox($input) {
         }
     });
     
+    $input[0].update = update;
+    
     return mainDiv;
 }
 
@@ -134,6 +138,8 @@ function makeSlider($input, minVal, maxVal, snapMode, ticks, ticksNumber, decima
 
         $('body').unbind('mousemove', bodyMouseMove);
         $('body').unbind('mouseup', bodyMouseUp);
+        
+        $input.change();
     }
     
     bar.mousedown(function (e) {
@@ -212,6 +218,8 @@ function makeSlider($input, minVal, maxVal, snapMode, ticks, ticksNumber, decima
         }
     });
     
+    $input[0].update = input2slider;
+    
     return slider;
 }
 
@@ -258,7 +266,7 @@ function makeNumberValidator($input, minVal, maxVal, floating, sign, required) {
         msg = 'enter a positive';
     }
     else {
-        msg = 'enter a'
+        msg = 'enter a';
     }
     if (floating) {
         msg += ' number';
@@ -294,6 +302,9 @@ function makeNumberValidator($input, minVal, maxVal, floating, sign, required) {
     
     $input.keyup(validate);
     $input.change(validate).change();
+    
+    $input.addClass('number-validator');
+    $input[0].validate = validate;
 }
 
 function makeTimeValidator($input) {
@@ -322,4 +333,7 @@ function makeTimeValidator($input) {
         selectOnBlur: true,
         timeFormat: 'H:i',
     });
+    
+    $input.addClass('time-validator');
+    $input[0].validate = validate;
 }
index 15b2c50b1f2aa42761b0c120435d156e2d403bfa..138c010cb855e7914a2729baa3342d482d0798d4 100644 (file)
                         <td class="settings-item-value"><input type="text" class="styled device" id="deviceNameEntry" placeholder="camera name..."></td>
                         <td><span class="help-mark" title="an alias for this camera device">?</span></td>
                     </tr>
+                    <tr class="settings-item advanced-setting">
+                        <td class="settings-item-label"><span class="settings-item-label">Camera Device</span></td>
+                        <td class="settings-item-value"><input type="text" class="styled device" id="deviceEntry" disabled="disabled"></td>
+                    </tr>
                     <tr class="settings-item advanced-setting">
                         <td colspan="100"><div class="settings-item-separator"></div></td>
                     </tr>
                         <td class="settings-item-value"><input type="text" class="range styled streaming" id="streamingQualitySlider"></td>
                         <td><span class="help-mark" title="sets the live streaming quality (higher values yield a better video quality but require more bandwidth)">?</span></td>
                     </tr>
+                    <tr class="settings-item advanced-setting">
+                        <td class="settings-item-label"><span class="settings-item-label">Streaming Port</span></td>
+                        <td class="settings-item-value"><input type="text" class="styled streaming" id="streamingPortEntry"></td>
+                        <td><span class="help-mark" title="sets the TCP port on which the webcam streaming server listens">?</span></td>
+                    </tr>
                     <tr class="settings-item advanced-setting">
                         <td class="settings-item-label"><span class="settings-item-label">Motion Optimization</span></td>
-                        <td class="settings-item-value"><input type="checkbox" class="styled streaming" id="motionOptimizationSwitch"></td>
-                        <td><span class="help-mark" title="enable this if you want a lower frame rate for the live streaming while no motion is detected">?</span></td>
+                        <td class="settings-item-value"><input type="checkbox" class="styled streaming" id="streamingMotion"></td>
+                        <td><span class="help-mark" title="enable this if you want a lower frame rate for the live streaming when no motion is detected">?</span></td>
                     </tr>
                 </table>
                 
                         <td class="settings-item-label"><span class="settings-item-label">Preserve Images</span></td>
                         <td class="settings-item-value">
                             <select class="styled still-images" id="preserveImagesSelect">
-                                <option>For One Day</option>
-                                <option>For One Week</option>
-                                <option>For One Month</option>
-                                <option>For One Year</option>
-                                <option>Forever</option>
+                                <option value="1">For One Day</option>
+                                <option value="7">For One Week</option>
+                                <option value="30">For One Month</option>
+                                <option value="365">For One Year</option>
+                                <option value="0">Forever</option>
                             </select>
                         </td>
                         <td><span class="help-mark" title="images older than the specified duration are automatically deleted to free storage space">?</span></td>
                         <td class="settings-item-label"><span class="settings-item-label">Preserve Movies</span></td>
                         <td class="settings-item-value">
                             <select class="styled motion-movies" id="preserveMoviesSelect">
-                                <option>For One Day</option>
-                                <option>For One Week</option>
-                                <option>For One Month</option>
-                                <option>For One Year</option>
-                                <option>Forever</option>
+                                <option value="1">For One Day</option>
+                                <option value="7">For One Week</option>
+                                <option value="30">For One Month</option>
+                                <option value="365">For One Year</option>
+                                <option value="0">Forever</option>
                             </select>
                         </td>
                         <td><span class="help-mark" title="movies older than the specified duration are automatically deleted to free storage space">?</span></td>
                 <div class="settings-section-title"><input type="checkbox" class="styled section notifications" id="motionNotificationsSwitch">Motion Notifications</div>
                 <table class="settings">
                     <tr class="settings-item">
-                        <td class="settings-item-label"><span class="settings-item-label">Email Address</span></td>
-                        <td class="settings-item-value"><input type="text" class="styled notifications" id="emailAddressEntry" placeholder="email address..."></td>
+                        <td class="settings-item-label"><span class="settings-item-label">Email Addresses</span></td>
+                        <td class="settings-item-value"><input type="text" class="styled notifications" id="emailAddressesEntry" placeholder="email addresses..."></td>
                         <td><span class="help-mark" title="email addresses (separated by comma) that are added here will receive notifications whenever a motion event is detected (leave empty to disable email notifications)">?</span></td>
                     </tr>
-                    <tr class="settings-item">
-                        <td class="settings-item-label"><span class="settings-item-label">Phone Number</span></td>
-                        <td class="settings-item-value"><input type="text" class="styled notifications" id="phoneNumberEntry" placeholder="phone number..."></td>
-                        <td><span class="help-mark" title="phone numbers (separated by comma) that are added here will receive SMS notifications whenever a motion event is detected (leave empty to disable SMS notifications)">?</span></td>
-                    </tr>
                 </table>
 
                 <div class="settings-section-title"><input type="checkbox" class="styled section working-schedule" id="workingScheduleSwitch">Working Schedule</div>