diff --git a/db/zm_create.sql.in b/db/zm_create.sql.in index 2685e27d1..eb2e6a9a4 100644 --- a/db/zm_create.sql.in +++ b/db/zm_create.sql.in @@ -233,6 +233,8 @@ CREATE TABLE `Filters` ( `AutoExecute` tinyint(3) unsigned NOT NULL default '0', `AutoExecuteCmd` tinytext, `AutoDelete` tinyint(3) unsigned NOT NULL default '0', + `AutoMove` tinyint(3) unsigned NOT NULL default '0', + `AutoMoveTo` smallint(5) unsigned NOT NULL default 0, `UpdateDiskSpace` tinyint(3) unsigned NOT NULL default '0', `Background` tinyint(1) unsigned NOT NULL default '0', `Concurrent` tinyint(1) unsigned NOT NULL default '0', @@ -268,10 +270,24 @@ CREATE TABLE `Groups` ( `Id` int(10) unsigned NOT NULL auto_increment, `Name` varchar(64) NOT NULL default '', `ParentId` int(10) unsigned, - `MonitorIds` text NOT NULL, PRIMARY KEY (`Id`) ) ENGINE=@ZM_MYSQL_ENGINE@; +-- +--Table structure for table `Groups_Monitors` +-- + +DROP TABLE IF EXISTS `Groups_Monitors` +CREATE TABLE `Groups_Monitors` ( + `Id` INT(10) unsigned NOT NULL auto_increment, + `GroupId` int(10) unsigned NOT NULL, + `MonitorId` int(10) unsigned NOT NULL, + PRIMARY KEY (`Id`) +) ENGINE=@ZM_MYSQL_ENGINE@; + +CREATE INDEX `Groups_Monitors_GroupId_idx` ON `Groups` (`GroupId`); +CREATE INDEX `Groups_Monitors_MonitorId_idx` ON `Groups` (`MonitorId`); + -- -- Table structure for table `Logs` -- diff --git a/db/zm_update-1.31.16.sql b/db/zm_update-1.31.16.sql new file mode 100644 index 000000000..eedd60119 --- /dev/null +++ b/db/zm_update-1.31.16.sql @@ -0,0 +1,95 @@ +-- +-- Add UpdateDiskSpace action to Filters +-- + +SET @s = (SELECT IF( + (SELECT COUNT(*) FROM INFORMATION_SCHEMA.COLUMNS WHERE table_schema = DATABASE() + AND table_name = 'Filters' + AND column_name = 'AutoMove' + ) > 0, +"SELECT 'Column AutoMove already exists in Filters'", +"ALTER TABLE Filters ADD `AutoMove` tinyint(3) unsigned NOT NULL default '0' AFTER `AutoDelete`" +)); + +PREPARE stmt FROM @s; +EXECUTE stmt; + +SET @s = (SELECT IF( + (SELECT COUNT(*) FROM INFORMATION_SCHEMA.COLUMNS WHERE table_schema = DATABASE() + AND table_name = 'Filters' + AND column_name = 'AutoMoveTo' + ) > 0, +"SELECT 'Column AutoMoveTo already exists in Filters'", +"ALTER TABLE Filters ADD `AutoMoveTo` smallint(5) unsigned NOT NULL default '0' AFTER `AutoMove`" +)); + +PREPARE stmt FROM @s; +EXECUTE stmt; + +SET @s = (SELECT IF( + (SELECT COUNT(*) + FROM INFORMATION_SCHEMA.TABLES + WHERE table_name = 'Groups_Monitors' + AND table_schema = DATABASE() + ) > 0, + "SELECT 'Groups_Monitors table exists'", + "CREATE TABLE `Groups_Monitors` ( + `Id` INT(10) unsigned NOT NULL auto_increment, + `GroupId` int(10) unsigned NOT NULL, + `MonitorId` int(10) unsigned NOT NULL, + PRIMARY KEY (`Id`) + )" + )); + +PREPARE stmt FROM @s; +EXECUTE stmt; + +SET @s = (SELECT IF( + (SELECT COUNT(*) + FROM INFORMATION_SCHEMA.STATISTICS + WHERE table_name = 'Groups_Monitors' + AND table_schema = DATABASE() + AND index_name = 'Groups_Monitors_GroupId_idx' + ) > 0, + "SELECT 'Groups_Monitors_GroupId_idx already exists on Groups table'", + "CREATE INDEX `Groups_Monitors_GroupId_idx` ON `Groups_Monitors` (`GroupId`)" + )); + +PREPARE stmt FROM @s; +EXECUTE stmt; + +SET @s = (SELECT IF( + (SELECT COUNT(*) + FROM INFORMATION_SCHEMA.STATISTICS + WHERE table_name = 'Groups_Monitors' + AND table_schema = DATABASE() + AND index_name = 'Groups_Monitors_MonitorId_idx' + ) > 0, + "SELECT 'Groups_Monitors_MonitorId_idx already exists on Groups table'", + "CREATE INDEX `Groups_Monitors_MonitorId_idx` ON `Groups_Monitors` (`MonitorId`)" + )); + +PREPARE stmt FROM @s; +EXECUTE stmt; + +SET @s = (SELECT IF( + (SELECT COUNT(*) FROM INFORMATION_SCHEMA.COLUMNS WHERE table_schema = DATABASE() + AND table_name = 'Groups' + AND column_name = 'MonitorIds' + ) > 0, + "REPLACE INTO Groups_Monitors (GroupId,MonitorId) SELECT Id,SUBSTRING_INDEX(SUBSTRING_INDEX(t.MonitorIds, ',', n.n), ',', -1) value FROM Groups t CROSS JOIN ( SELECT a.N + b.N * 10 + 1 n FROM (SELECT 0 AS N UNION ALL SELECT 1 UNION ALL SELECT 2 UNION ALL SELECT 3 UNION ALL SELECT 4 UNION ALL SELECT 5 UNION ALL SELECT 6 UNION ALL SELECT 7 UNION ALL SELECT 8 UNION ALL SELECT 9) a ,(SELECT 0 AS N UNION ALL SELECT 1 UNION ALL SELECT 2 UNION ALL SELECT 3 UNION ALL SELECT 4 UNION ALL SELECT 5 UNION ALL SELECT 6 UNION ALL SELECT 7 UNION ALL SELECT 8 UNION ALL SELECT 9) b ORDER BY n ) n WHERE t.MonitorIds != '' AND n.n <= 1 + (LENGTH(t.MonitorIds) - LENGTH(REPLACE(t.MonitorIds, ',', ''))) ORDER BY value;", + "SELECT 'MonitorIds has already been removed.'" +)); + +PREPARE stmt FROM @s; +EXECUTE stmt; +SET @s = (SELECT IF( + (SELECT COUNT(*) FROM INFORMATION_SCHEMA.COLUMNS WHERE table_schema = DATABASE() + AND table_name = 'Groups' + AND column_name = 'MonitorIds' + ) > 0, +"ALTER TABLE Groups DROP MonitorIds", +"SELECT 'MonitorIds has already been removed.'" +)); +PREPARE stmt FROM @s; +EXECUTE stmt; diff --git a/scripts/ZoneMinder/lib/ZoneMinder/Config.pm.in b/scripts/ZoneMinder/lib/ZoneMinder/Config.pm.in index f69e95b18..7a86b51a8 100644 --- a/scripts/ZoneMinder/lib/ZoneMinder/Config.pm.in +++ b/scripts/ZoneMinder/lib/ZoneMinder/Config.pm.in @@ -79,13 +79,13 @@ BEGIN { # Search for user created config files. If one or more are found then # update the Config hash with those values if ( -d ZM_CONFIG_SUBDIR ) { - if ( -R ZM_CONFIG_SUBDIR ) { - foreach my $filename ( glob ZM_CONFIG_SUBDIR."/*.conf" ) { - process_configfile($filename); - } - } else { - print( STDERR "WARNING: ZoneMinder configuration subfolder found but is not readable. Check folder permissions on ".ZM_CONFIG_SUBDIR.".\n" ); + if ( -R ZM_CONFIG_SUBDIR ) { + foreach my $filename ( glob ZM_CONFIG_SUBDIR."/*.conf" ) { + process_configfile($filename); } + } else { + print( STDERR "WARNING: ZoneMinder configuration subfolder found but is not readable. Check folder permissions on ".ZM_CONFIG_SUBDIR.".\n" ); + } } use DBI; @@ -94,14 +94,14 @@ BEGIN { if ( defined($portOrSocket) ) { if ( $portOrSocket =~ /^\// ) { - $socket = ";mysql_socket=".$portOrSocket; + $socket = ';mysql_socket='.$portOrSocket; } else { - $socket = ";host=".$host.";port=".$portOrSocket; + $socket = ';host='.$host.';port='.$portOrSocket; } } else { - $socket = ";host=".$Config{ZM_DB_HOST}; + $socket = ';host='.$Config{ZM_DB_HOST}; } - my $sslOptions = ""; + my $sslOptions = ''; if ( $Config{ZM_DB_SSL_CA_CERT} ) { $sslOptions = ';'.join(';', "mysql_ssl=1", @@ -123,7 +123,8 @@ BEGIN { } $sth->finish(); #$dbh->disconnect(); - if ( ! exists $Config{ZM_SERVER_ID} ) { + # + if ( ! $Config{ZM_SERVER_ID} ) { $Config{ZM_SERVER_ID} = undef; $sth = $dbh->prepare_cached( 'SELECT * FROM Servers WHERE Name=?' ); if ( $Config{ZM_SERVER_NAME} ) { @@ -140,32 +141,32 @@ BEGIN { # This subroutine must be inside the BEGIN block sub process_configfile { - my $config_file = shift; + my $config_file = shift; - if ( -R $config_file ) { - open( my $CONFIG, "<", $config_file ) - or croak( "Can't open config file '$config_file': $!" ); - foreach my $str ( <$CONFIG> ) { - next if ( $str =~ /^\s*$/ ); - next if ( $str =~ /^\s*#/ ); - my ( $name, $value ) = $str =~ /^\s*([^=\s]+)\s*=\s*(.*?)\s*$/; - if ( ! $name ) { - print( STDERR "Warning, bad line in $config_file: $str\n" ); - next; - } # end if - $name =~ tr/a-z/A-Z/; - $Config{$name} = $value; - } - close( $CONFIG ); - } else { - print( STDERR "WARNING: ZoneMinder configuration file found but is not readable. Check file permissions on $config_file\n" ); + if ( -R $config_file ) { + open( my $CONFIG, '<', $config_file ) + or croak( "Can't open config file '$config_file': $!" ); + foreach my $str ( <$CONFIG> ) { + next if ( $str =~ /^\s*$/ ); + next if ( $str =~ /^\s*#/ ); + my ( $name, $value ) = $str =~ /^\s*([^=\s]+)\s*=\s*(.*?)\s*$/; + if ( ! $name ) { + print( STDERR "Warning, bad line in $config_file: $str\n" ); + next; + } # end if + $name =~ tr/a-z/A-Z/; + $Config{$name} = $value; } + close( $CONFIG ); + } else { + print( STDERR "WARNING: ZoneMinder configuration file found but is not readable. Check file permissions on $config_file\n" ); + } } } # end BEGIN sub loadConfigFromDB { - print( "Loading config from DB" ); + print( 'Loading config from DB' ); my $dbh = ZoneMinder::Database::zmDbConnect(); if ( !$dbh ) { print( "Error: unable to load options from database: $DBI::errstr\n" ); @@ -188,7 +189,7 @@ sub loadConfigFromDB { #next if ( $option->{category} eq 'hidden' ); if ( defined($value) ) { if ( $option->{type} == $types{boolean} ) { - $option->{value} = $value?"yes":"no"; + $option->{value} = $value?'yes':'no'; } else { $option->{value} = $value; } @@ -201,7 +202,7 @@ sub loadConfigFromDB { } # end sub loadConfigFromDB sub saveConfigToDB { - print( "Saving config to DB " . @options . " entries\n" ); + print( 'Saving config to DB ' . @options . " entries\n" ); my $dbh = ZoneMinder::Database::zmDbConnect(); if ( !$dbh ) { print( "Error: unable to save options to database: $DBI::errstr\n" ); @@ -214,7 +215,7 @@ sub saveConfigToDB { $dbh->do('LOCK TABLE Config WRITE') or croak( "Can't lock Config table: " . $dbh->errstr() ); - my $sql = "delete from Config"; + my $sql = 'DELETE FROM Config'; my $res = $dbh->do( $sql ) or croak( "Can't do '$sql': ".$dbh->errstr() ); @@ -228,8 +229,8 @@ sub saveConfigToDB { $option->{db_hint} = $option->{type}->{hint}; $option->{db_pattern} = $option->{type}->{pattern}; $option->{db_format} = $option->{type}->{format}; - if ( $option->{db_type} eq "boolean" ) { - $option->{db_value} = ($option->{value} eq "yes") ? "1" : "0"; + if ( $option->{db_type} eq 'boolean' ) { + $option->{db_value} = ($option->{value} eq 'yes') ? '1' : '0'; } else { $option->{db_value} = $option->{value}; } @@ -237,7 +238,7 @@ sub saveConfigToDB { $option->{db_requires} = join( ";", map { my $value = $_->{value}; - $value = ($value eq "yes") ? 1 : 0 if ( $options_hash{$_->{name}}->{db_type} eq "boolean" ); + $value = ($value eq 'yes') ? 1 : 0 if ( $options_hash{$_->{name}}->{db_type} eq 'boolean' ); ( "$_->{name}=$value" ) } @$requires ); diff --git a/scripts/ZoneMinder/lib/ZoneMinder/Event.pm b/scripts/ZoneMinder/lib/ZoneMinder/Event.pm index 8c4eba093..88a2af1e3 100644 --- a/scripts/ZoneMinder/lib/ZoneMinder/Event.pm +++ b/scripts/ZoneMinder/lib/ZoneMinder/Event.pm @@ -33,6 +33,8 @@ require ZoneMinder::Object; require ZoneMinder::Storage; require Date::Manip; require File::Find; +require File::Path; +require File::Copy; #our @ISA = qw(ZoneMinder::Object); use parent qw(ZoneMinder::Object); @@ -401,6 +403,54 @@ sub DiskSpace { } } +sub MoveTo { + my ( $self, $NewStorage ) = @_; + my ( $OldPath ) = ( $self->Path() =~ /^(.*)$/ ); # De-taint + my ( $NewPath ) = ( $NewStorage->Path() =~ /^(.*)$/ ); # De-taint + if ( ! $$NewStorage{Id} ) { + return "New storage does not have an id. Moving will not happen."; + } elsif ( !$NewPath) { + return "$NewPath is empty."; + }elsif ( $NewPath eq $OldPath ) { + return "New path and old path are the same! $NewPath"; + } elsif ( ! -e $NewPath ) { + return "New path $NewPath does not exist."; + }elsif ( ! -e $OldPath ) { + return "Old path $OldPath does not exist."; + } + + my $error = ''; + File::Path::make_path( $NewPath, {error => \my $err} ); + if ( @$err ) { + for my $diag (@$err) { + my ($file, $message) = %$diag; + if ($file eq '') { + $error .= "general error: $message\n"; + } else { + $error .= "problem unlinking $file: $message\n"; + } + } + } + return $error if $error; + my @files = glob("$OldPath/*"); + + for my $file (@files) { + next if $file =~ /^\./; + ( $file ) = ( $file =~ /^(.*)$/ ); # De-taint + if ( ! File::Copy::copy( $file, $NewPath ) ) { + $error .= "Copy failed: for $file to $NewPath: $!"; + last; + } + } # end foreach file. + + if ( ! $error ) { + # Succeeded in copying all files, so we may now update the Event. + $$self{StorageId} = $$NewStorage{Id}; + $error .= $self->save(); + } + return $error; +} # end sub MoveTo + 1; __END__ diff --git a/scripts/ZoneMinder/lib/ZoneMinder/Filter.pm b/scripts/ZoneMinder/lib/ZoneMinder/Filter.pm index 2048a7ba8..346aaa985 100644 --- a/scripts/ZoneMinder/lib/ZoneMinder/Filter.pm +++ b/scripts/ZoneMinder/lib/ZoneMinder/Filter.pm @@ -207,10 +207,11 @@ sub Sql { if ( $term->{attr} =~ /^Monitor/ ) { $value = "'$temp_value'"; } elsif ( $term->{attr} eq 'ServerId' ) { + Debug("ServerId, temp_value is ($temp_value) ($ZoneMinder::Config::Config{ZM_SERVER_ID})"); if ( $temp_value eq 'ZM_SERVER_ID' ) { - $value = "'$Config{ZM_SERVER_ID}'"; + $value = "'$ZoneMinder::Config::Config{ZM_SERVER_ID}'"; # This gets used later, I forget for what - $$self{Server} = new ZoneMinder::Server( $Config{ZM_SERVER_ID} ); + $$self{Server} = new ZoneMinder::Server( $ZoneMinder::Config::Config{ZM_SERVER_ID} ); } else { $value = "'$temp_value'"; # This gets used later, I forget for what @@ -225,7 +226,7 @@ sub Sql { ) { $value = "'$temp_value'"; } elsif ( $term->{attr} eq 'DateTime' or $term->{attr} eq 'StartDateTime' or $term->{attr} eq 'EndDateTime' ) { - if ( $temp_value == 'NULL' ) { + if ( $temp_value eq 'NULL' ) { $value = $temp_value; } else { $value = DateTimeToSQL( $temp_value ); @@ -237,7 +238,7 @@ sub Sql { $value = "'$value'"; } } elsif ( $term->{attr} eq 'Date' or $term->{attr} eq 'StartDate' or $term->{attr} eq 'EndDate' ) { - if ( $temp_value == 'NULL' ) { + if ( $temp_value eq 'NULL' ) { $value = $temp_value; } else { $value = DateTimeToSQL( $temp_value ); @@ -249,7 +250,7 @@ sub Sql { $value = "to_days( '$value' )"; } } elsif ( $term->{attr} eq 'Time' or $term->{attr} eq 'StartTime' or $term->{attr} eq 'EndTime' ) { - if ( $temp_value == 'NULL' ) { + if ( $temp_value eq 'NULL' ) { $value = $temp_value; } else { $value = DateTimeToSQL( $temp_value ); diff --git a/scripts/zmfilter.pl.in b/scripts/zmfilter.pl.in index efe988882..9dcd3548e 100644 --- a/scripts/zmfilter.pl.in +++ b/scripts/zmfilter.pl.in @@ -207,7 +207,7 @@ sub getFilters { } else { $sql .= ' Background = 1 AND'; } - $sql .= '( AutoArchive = 1 + $sql .= '( AutoArchive = 1 or AutoVideo = 1 or AutoUpload = 1 or AutoEmail = 1 @@ -215,16 +215,17 @@ sub getFilters { or AutoExecute = 1 or AutoDelete = 1 or UpdateDiskSpace = 1 + or AutoMove = 1 ) ORDER BY Name'; my $sth = $dbh->prepare_cached( $sql ) - or Fatal( "Unable toprepare '$sql': ".$dbh->errstr() ); + or Fatal( "Unable to prepare '$sql': ".$dbh->errstr() ); my $res; if ( $filter_name ) { $res = $sth->execute( $filter_name ) - or Fatal( "Unable toexecute '$sql': ".$sth->errstr() ); + or Fatal( "Unable to execute '$sql': ".$sth->errstr() ); } else { $res = $sth->execute() - or Fatal( "Unable toexecute '$sql': ".$sth->errstr() ); + or Fatal( "Unable to execute '$sql': ".$sth->errstr() ); } FILTER: while( my $db_filter = $sth->fetchrow_hashref() ) { my $filter = new ZoneMinder::Filter( $$db_filter{Id}, $db_filter ); @@ -263,6 +264,7 @@ sub checkFilter { ($filter->{AutoEmail}?'email':()), ($filter->{AutoMessage}?'message':()), ($filter->{AutoExecute}?'execute':()), + ($filter->{AutoMove}?'move':()), ($filter->{UpdateDiskSpace}?'update disk space':()), ), 'returned' , scalar @Events , 'events', @@ -270,12 +272,12 @@ sub checkFilter { ) ); foreach my $event ( @Events ) { - Debug( "Checking event $event->{Id}\n" ); + Debug( "Checking event $event->{Id}" ); my $delete_ok = !undef; $dbh->ping(); if ( $filter->{AutoArchive} ) { - Info( "Archiving event $event->{Id}\n" ); -# Do it individually to avoid locking up the table for new events + Info( "Archiving event $event->{Id}" ); + # Do it individually to avoid locking up the table for new events my $sql = 'UPDATE Events SET Archived = 1 WHERE Id = ?'; my $sth = $dbh->prepare_cached( $sql ) or Fatal( "Unable toprepare '$sql': ".$dbh->errstr() ); @@ -315,11 +317,17 @@ sub checkFilter { Error( "Unable toto delete event $event->{Id} as previous operations failed\n" ); } } # end if AutoDelete + if ( $filter->{AutoMove} ) { + my $Event = new ZoneMinder::Event( $$event{Id}, $event ); + my $NewStorage = new ZoneMinder::Storage( $filter->{AutoMoveTo} ); + $_ = $Event->MoveTo( $NewStorage ); + Error($_) if $_; + } + if ( $filter->{UpdateDiskSpace} ) { - my $Event = new ZoneMinder::Event( $$event{Id}, $event ); - $Event->DiskSpace(undef); - $Event->save(); - + my $Event = new ZoneMinder::Event( $$event{Id}, $event ); + $Event->DiskSpace(undef); + $Event->save(); } # end if UpdateDiskSpace } # end foreach event } diff --git a/src/zm_db.cpp b/src/zm_db.cpp index ac09dca13..c2c3fc5ef 100644 --- a/src/zm_db.cpp +++ b/src/zm_db.cpp @@ -28,7 +28,7 @@ MYSQL dbconn; bool zmDbConnected = false; void zmDbConnect() { - // If these lines are uncommented, we get memory corruption and crashes + // For some reason having these lines causes memory corruption and crashing on newer debian/ubuntu //if ( zmDbConnected ) //return; diff --git a/version b/version index 604636091..d1a8f5341 100644 --- a/version +++ b/version @@ -1 +1 @@ -1.31.15 +1.31.16 diff --git a/web/ajax/event.php b/web/ajax/event.php index 0920a9423..04ebca9ad 100644 --- a/web/ajax/event.php +++ b/web/ajax/event.php @@ -76,6 +76,19 @@ if ( canView( 'Events' ) ) { ajaxError( 'Export Failed' ); break; } + case 'download' : + { + require_once( ZM_SKIN_PATH.'/includes/export_functions.php' ); + $exportVideo = 1; + $exportFormat = $_REQUEST['exportFormat']; + $exportStructure = 'flat'; + $exportIds = !empty($_REQUEST['eids'])?$_REQUEST['eids']:$_REQUEST['id']; + if ( $exportFile = exportEvents( $exportIds, false, false, false, $exportVideo, false, $exportFormat, $exportStructure ) ) + ajaxResponse( array( 'exportFile'=>$exportFile ) ); + else + ajaxError( 'Export Failed' ); + break; + } } } diff --git a/web/api/app/Controller/EventsController.php b/web/api/app/Controller/EventsController.php index b5a17d461..97498db9a 100644 --- a/web/api/app/Controller/EventsController.php +++ b/web/api/app/Controller/EventsController.php @@ -14,16 +14,14 @@ class EventsController extends AppController { */ public $components = array('RequestHandler', 'Scaler', 'Image', 'Paginator'); -public function beforeFilter() { - parent::beforeFilter(); - $canView = $this->Session->Read('eventPermission'); - if ($canView =='None') - { - throw new UnauthorizedException(__('Insufficient Privileges')); - return; - } - -} + public function beforeFilter() { + parent::beforeFilter(); + $canView = $this->Session->Read('eventPermission'); + if ($canView =='None') { + throw new UnauthorizedException(__('Insufficient Privileges')); + return; + } + } /** * index method @@ -48,14 +46,7 @@ public function beforeFilter() { } else { $conditions = array(); } - - // How many events to return - $this->loadModel('Config'); - $limit = $this->Config->find('list', array( - 'conditions' => array('Name' => 'ZM_WEB_EVENTS_PER_PAGE'), - 'fields' => array('Name', 'Value') - )); - $this->Paginator->settings = array( + $settings = array( // https://github.com/ZoneMinder/ZoneMinder/issues/995 // 'limit' => $limit['ZM_WEB_EVENTS_PER_PAGE'], // 25 events per page which is what the above @@ -65,11 +56,31 @@ public function beforeFilter() { // make a nice ZM_API_ITEMS_PER_PAGE for all pagination // API - 'limit' => '100', + 'limit' => '100', 'order' => array('StartTime', 'MaxScore'), 'paramType' => 'querystring', - 'conditions' => array (array($conditions, $mon_options)) - ); + ); + //if ( $this->request->params['GroupId'] ) { + $settings['joins'] = array( + array( + 'table' => 'Groups_Monitors', + 'type' => 'inner', + 'conditions' => array( + 'Groups_Monitors.MonitorId = Event.MonitorId' + ), + ), + ); + $settings['contain'] = array('Group'); + //} + $settings['conditions'] = array($conditions, $mon_options); + + // How many events to return + $this->loadModel('Config'); + $limit = $this->Config->find('list', array( + 'conditions' => array('Name' => 'ZM_WEB_EVENTS_PER_PAGE'), + 'fields' => array('Name', 'Value') + )); + $this->Paginator->settings = $settings; $events = $this->Paginator->paginate('Event'); // For each event, get its thumbnail data (path, width, height) @@ -89,282 +100,281 @@ public function beforeFilter() { * @param string $id * @return void */ - public function view($id = null) -{ - $this->loadModel('Config'); - $configs = $this->Config->find('list', array( - 'fields' => array('Name', 'Value'), - 'conditions' => array('Name' => array('ZM_DIR_EVENTS')) - )); + public function view($id = null) { + $this->loadModel('Config'); + $configs = $this->Config->find('list', array( + 'fields' => array('Name', 'Value'), + 'conditions' => array('Name' => array('ZM_DIR_EVENTS')) + )); - $this->Event->recursive = 1; - if (!$this->Event->exists($id)) { - throw new NotFoundException(__('Invalid event')); - } + $this->Event->recursive = 1; + if (!$this->Event->exists($id)) { + throw new NotFoundException(__('Invalid event')); + } - $allowedMonitors=preg_split ('@,@', $this->Session->Read('allowedMonitors'),NULL, PREG_SPLIT_NO_EMPTY); + $allowedMonitors=preg_split ('@,@', $this->Session->Read('allowedMonitors'),NULL, PREG_SPLIT_NO_EMPTY); - if (!empty($allowedMonitors)) - { - $mon_options = array('Event.MonitorId' => $allowedMonitors); - } - else - { - $mon_options=''; - } - - $options = array('conditions' => array(array('Event.' . $this->Event->primaryKey => $id), $mon_options)); - $event = $this->Event->find('first', $options); + if (!empty($allowedMonitors)) + { + $mon_options = array('Event.MonitorId' => $allowedMonitors); + } + else + { + $mon_options=''; + } - $path = $configs['ZM_DIR_EVENTS'].'/'.$this->Image->getEventPath($event).'/'; - $event['Event']['BasePath'] = $path; + $options = array('conditions' => array(array('Event.' . $this->Event->primaryKey => $id), $mon_options)); + $event = $this->Event->find('first', $options); - # Get the previous and next events for any monitor - $this->Event->id = $id; - $event_neighbors = $this->Event->find('neighbors'); - $event['Event']['Next'] = $event_neighbors['next']['Event']['Id']; - $event['Event']['Prev'] = $event_neighbors['prev']['Event']['Id']; + $path = $configs['ZM_DIR_EVENTS'].'/'.$this->Image->getEventPath($event).'/'; + $event['Event']['BasePath'] = $path; - # Also get the previous and next events for the same monitor - $event_monitor_neighbors = $this->Event->find('neighbors', array( - 'conditions'=>array('Event.MonitorId'=>$event['Event']['MonitorId']) - )); - $event['Event']['NextOfMonitor'] = $event_monitor_neighbors['next']['Event']['Id']; - $event['Event']['PrevOfMonitor'] = $event_monitor_neighbors['prev']['Event']['Id']; + # Get the previous and next events for any monitor + $this->Event->id = $id; + $event_neighbors = $this->Event->find('neighbors'); + $event['Event']['Next'] = $event_neighbors['next']['Event']['Id']; + $event['Event']['Prev'] = $event_neighbors['prev']['Event']['Id']; - $this->set(array( - 'event' => $event, - '_serialize' => array('event') - )); - } + # Also get the previous and next events for the same monitor + $event_monitor_neighbors = $this->Event->find('neighbors', array( + 'conditions'=>array('Event.MonitorId'=>$event['Event']['MonitorId']) + )); + $event['Event']['NextOfMonitor'] = $event_monitor_neighbors['next']['Event']['Id']; + $event['Event']['PrevOfMonitor'] = $event_monitor_neighbors['prev']['Event']['Id']; + + $this->set(array( + 'event' => $event, + '_serialize' => array('event') + )); + } -/** - * add method - * - * @return void - */ - public function add() { + /** + * add method + * + * @return void + */ + public function add() { - if ($this->Session->Read('eventPermission') != 'Edit') - { - throw new UnauthorizedException(__('Insufficient privileges')); - return; - } + if ($this->Session->Read('eventPermission') != 'Edit') + { + throw new UnauthorizedException(__('Insufficient privileges')); + return; + } - if ($this->request->is('post')) { - $this->Event->create(); - if ($this->Event->save($this->request->data)) { - return $this->flash(__('The event has been saved.'), array('action' => 'index')); - } - } - $monitors = $this->Event->Monitor->find('list'); - $this->set(compact('monitors')); - } + if ($this->request->is('post')) { + $this->Event->create(); + if ($this->Event->save($this->request->data)) { + return $this->flash(__('The event has been saved.'), array('action' => 'index')); + } + } + $monitors = $this->Event->Monitor->find('list'); + $this->set(compact('monitors')); + } -/** - * edit method - * - * @throws NotFoundException - * @param string $id - * @return void - */ - public function edit($id = null) { + /** + * edit method + * + * @throws NotFoundException + * @param string $id + * @return void + */ + public function edit($id = null) { - if ($this->Session->Read('eventPermission') != 'Edit') - { - throw new UnauthorizedException(__('Insufficient privileges')); - return; - } + if ($this->Session->Read('eventPermission') != 'Edit') + { + throw new UnauthorizedException(__('Insufficient privileges')); + return; + } - $this->Event->id = $id; + $this->Event->id = $id; - if (!$this->Event->exists($id)) { - throw new NotFoundException(__('Invalid event')); - } + if (!$this->Event->exists($id)) { + throw new NotFoundException(__('Invalid event')); + } - if ($this->Event->save($this->request->data)) { - $message = 'Saved'; - } else { - $message = 'Error'; - } + if ($this->Event->save($this->request->data)) { + $message = 'Saved'; + } else { + $message = 'Error'; + } - $this->set(array( - 'message' => $message, - '_serialize' => array('message') - )); - } + $this->set(array( + 'message' => $message, + '_serialize' => array('message') + )); + } -/** - * delete method - * - * @throws NotFoundException - * @param string $id - * @return void - */ - public function delete($id = null) { - if ($this->Session->Read('eventPermission') != 'Edit') - { - throw new UnauthorizedException(__('Insufficient privileges')); - return; - } - $this->Event->id = $id; - if (!$this->Event->exists()) { - throw new NotFoundException(__('Invalid event')); - } - $this->request->allowMethod('post', 'delete'); - if ($this->Event->delete()) { - //$this->loadModel('Frame'); - //$this->Event->Frame->delete(); - return $this->flash(__('The event has been deleted.'), array('action' => 'index')); - } else { - return $this->flash(__('The event could not be deleted. Please, try again.'), array('action' => 'index')); - } - } + /** + * delete method + * + * @throws NotFoundException + * @param string $id + * @return void + */ + public function delete($id = null) { + if ($this->Session->Read('eventPermission') != 'Edit') + { + throw new UnauthorizedException(__('Insufficient privileges')); + return; + } + $this->Event->id = $id; + if (!$this->Event->exists()) { + throw new NotFoundException(__('Invalid event')); + } + $this->request->allowMethod('post', 'delete'); + if ($this->Event->delete()) { + //$this->loadModel('Frame'); + //$this->Event->Frame->delete(); + return $this->flash(__('The event has been deleted.'), array('action' => 'index')); + } else { + return $this->flash(__('The event could not be deleted. Please, try again.'), array('action' => 'index')); + } + } - public function search() { - $this->Event->recursive = -1; - $conditions = array(); + public function search() { + $this->Event->recursive = -1; + $conditions = array(); - foreach ($this->params['named'] as $param_name => $value) { - // Transform params into mysql - if (preg_match("/interval/i", $value, $matches)) { - $condition = array("$param_name >= (date_sub(now(), $value))"); - } else { - $condition = array($param_name => $value); - } - array_push($conditions, $condition); - } + foreach ($this->params['named'] as $param_name => $value) { + // Transform params into mysql + if (preg_match("/interval/i", $value, $matches)) { + $condition = array("$param_name >= (date_sub(now(), $value))"); + } else { + $condition = array($param_name => $value); + } + array_push($conditions, $condition); + } - $results = $this->Event->find('all', array( - 'conditions' => $conditions - )); + $results = $this->Event->find('all', array( + 'conditions' => $conditions + )); - $this->set(array( - 'results' => $results, - '_serialize' => array('results') - )); + $this->set(array( + 'results' => $results, + '_serialize' => array('results') + )); - - } - // format expected: - // you can changed AlarmFrames to any other named params - // consoleEvents/1 hour/AlarmFrames >=: 1/AlarmFrames <=: 20.json + } - public function consoleEvents($interval = null) { - $this->Event->recursive = -1; - $results = array(); + // format expected: + // you can changed AlarmFrames to any other named params + // consoleEvents/1 hour/AlarmFrames >=: 1/AlarmFrames <=: 20.json - $moreconditions =""; - foreach ($this->request->params['named'] as $name => $param) { - $moreconditions = $moreconditions . " AND ".$name.$param; - } - - $query = $this->Event->query("select MonitorId, COUNT(*) AS Count from Events WHERE (StartTime >= (DATE_SUB(NOW(), interval $interval)) $moreconditions) GROUP BY MonitorId;"); + public function consoleEvents($interval = null) { + $this->Event->recursive = -1; + $results = array(); - foreach ($query as $result) { - $results[$result['Events']['MonitorId']] = $result[0]['Count']; - } + $moreconditions =""; + foreach ($this->request->params['named'] as $name => $param) { + $moreconditions = $moreconditions . " AND ".$name.$param; + } - $this->set(array( - 'results' => $results, - '_serialize' => array('results') - )); - } + $query = $this->Event->query("select MonitorId, COUNT(*) AS Count from Events WHERE (StartTime >= (DATE_SUB(NOW(), interval $interval)) $moreconditions) GROUP BY MonitorId;"); - // Create a thumbnail and return the thumbnail's data for a given event id. - public function createThumbnail($id = null) { - $this->Event->recursive = -1; + foreach ($query as $result) { + $results[$result['Events']['MonitorId']] = $result[0]['Count']; + } - if (!$this->Event->exists($id)) { - throw new NotFoundException(__('Invalid event')); - } + $this->set(array( + 'results' => $results, + '_serialize' => array('results') + )); + } - $event = $this->Event->find('first', array( - 'conditions' => array('Id' => $id) - )); + // Create a thumbnail and return the thumbnail's data for a given event id. + public function createThumbnail($id = null) { + $this->Event->recursive = -1; - // Find the max Frame for this Event. Error out otherwise. - $this->loadModel('Frame'); - if (! $frame = $this->Frame->find('first', array( - 'conditions' => array( - 'EventId' => $event['Event']['Id'], - 'Score' => $event['Event']['MaxScore'] - ) - ))) { - throw new NotFoundException(__("Can not find Frame for Event " . $event['Event']['Id'])); - } + if (!$this->Event->exists($id)) { + throw new NotFoundException(__('Invalid event')); + } - $this->loadModel('Config'); + $event = $this->Event->find('first', array( + 'conditions' => array('Id' => $id) + )); - // Get the config options required for reScale and getImageSrc - // The $bw, $thumbs and unset() code is a workaround / temporary - // until I have a better way of handing per-bandwidth config options - $bw = (isset($_COOKIE['zmBandwidth']) ? strtoupper(substr($_COOKIE['zmBandwidth'], 0, 1)) : 'L'); - $thumbs = "ZM_WEB_${bw}_SCALE_THUMBS"; + // Find the max Frame for this Event. Error out otherwise. + $this->loadModel('Frame'); + if (! $frame = $this->Frame->find('first', array( + 'conditions' => array( + 'EventId' => $event['Event']['Id'], + 'Score' => $event['Event']['MaxScore'] + ) + ))) { + throw new NotFoundException(__("Can not find Frame for Event " . $event['Event']['Id'])); + } - $config = $this->Config->find('list', array( - 'conditions' => array('OR' => array( - 'Name' => array('ZM_WEB_LIST_THUMB_WIDTH', - 'ZM_WEB_LIST_THUMB_HEIGHT', - 'ZM_EVENT_IMAGE_DIGITS', - 'ZM_DIR_IMAGES', - "$thumbs", - 'ZM_DIR_EVENTS' - ) - )), - 'fields' => array('Name', 'Value') - )); - $config['ZM_WEB_SCALE_THUMBS'] = $config[$thumbs]; - unset($config[$thumbs]); + $this->loadModel('Config'); - // reScale based on either the width, or the hight, of the event. - if ( $config['ZM_WEB_LIST_THUMB_WIDTH'] ) { - $thumbWidth = $config['ZM_WEB_LIST_THUMB_WIDTH']; - $scale = (100 * $thumbWidth) / $event['Event']['Width']; - $thumbHeight = $this->Scaler->reScale( $event['Event']['Height'], $scale ); - } - elseif ( $config['ZM_WEB_LIST_THUMB_HEIGHT'] ) { - $thumbHeight = $config['ZM_WEB_LIST_THUMB_HEIGHT']; - $scale = (100*$thumbHeight)/$event['Event']['Height']; - $thumbWidth = $this->Scaler->reScale( $event['Event']['Width'], $scale ); - } - else { - throw new NotFoundException(__('No thumbnail width or height specified, please check in Options->Web')); - } + // Get the config options required for reScale and getImageSrc + // The $bw, $thumbs and unset() code is a workaround / temporary + // until I have a better way of handing per-bandwidth config options + $bw = (isset($_COOKIE['zmBandwidth']) ? strtoupper(substr($_COOKIE['zmBandwidth'], 0, 1)) : 'L'); + $thumbs = "ZM_WEB_${bw}_SCALE_THUMBS"; - $imageData = $this->Image->getImageSrc( $event, $frame, $scale, $config ); - $thumbData['Path'] = $imageData['thumbPath']; - $thumbData['Width'] = (int)$thumbWidth; - $thumbData['Height'] = (int)$thumbHeight; - - return( $thumbData ); + $config = $this->Config->find('list', array( + 'conditions' => array('OR' => array( + 'Name' => array('ZM_WEB_LIST_THUMB_WIDTH', + 'ZM_WEB_LIST_THUMB_HEIGHT', + 'ZM_EVENT_IMAGE_DIGITS', + 'ZM_DIR_IMAGES', + "$thumbs", + 'ZM_DIR_EVENTS' + ) + )), + 'fields' => array('Name', 'Value') + )); + $config['ZM_WEB_SCALE_THUMBS'] = $config[$thumbs]; + unset($config[$thumbs]); - } + // reScale based on either the width, or the hight, of the event. + if ( $config['ZM_WEB_LIST_THUMB_WIDTH'] ) { + $thumbWidth = $config['ZM_WEB_LIST_THUMB_WIDTH']; + $scale = (100 * $thumbWidth) / $event['Event']['Width']; + $thumbHeight = $this->Scaler->reScale( $event['Event']['Height'], $scale ); + } + elseif ( $config['ZM_WEB_LIST_THUMB_HEIGHT'] ) { + $thumbHeight = $config['ZM_WEB_LIST_THUMB_HEIGHT']; + $scale = (100*$thumbHeight)/$event['Event']['Height']; + $thumbWidth = $this->Scaler->reScale( $event['Event']['Width'], $scale ); + } + else { + throw new NotFoundException(__('No thumbnail width or height specified, please check in Options->Web')); + } - public function archive($id = null) { - $this->Event->recursive = -1; - if (!$this->Event->exists($id)) { - throw new NotFoundException(__('Invalid event')); - } + $imageData = $this->Image->getImageSrc( $event, $frame, $scale, $config ); + $thumbData['Path'] = $imageData['thumbPath']; + $thumbData['Width'] = (int)$thumbWidth; + $thumbData['Height'] = (int)$thumbHeight; - // Get the current value of Archive - $archived = $this->Event->find('first', array( - 'fields' => array('Event.Archived'), - 'conditions' => array('Event.Id' => $id) - )); - // If 0, 1, if 1, 0 - $archiveVal = (($archived['Event']['Archived'] == 0) ? 1 : 0); + return( $thumbData ); - // Save the new value - $this->Event->id = $id; - $this->Event->saveField('Archived', $archiveVal); + } - $this->set(array( - 'archived' => $archiveVal, - '_serialize' => array('archived') - )); - } + public function archive($id = null) { + $this->Event->recursive = -1; + if (!$this->Event->exists($id)) { + throw new NotFoundException(__('Invalid event')); + } + + // Get the current value of Archive + $archived = $this->Event->find('first', array( + 'fields' => array('Event.Archived'), + 'conditions' => array('Event.Id' => $id) + )); + // If 0, 1, if 1, 0 + $archiveVal = (($archived['Event']['Archived'] == 0) ? 1 : 0); + + // Save the new value + $this->Event->id = $id; + $this->Event->saveField('Archived', $archiveVal); + + $this->set(array( + 'archived' => $archiveVal, + '_serialize' => array('archived') + )); + } } diff --git a/web/api/app/Controller/GroupsController.php b/web/api/app/Controller/GroupsController.php new file mode 100644 index 000000000..525d500bf --- /dev/null +++ b/web/api/app/Controller/GroupsController.php @@ -0,0 +1,108 @@ +Group->recursive = -1; + $groups = $this->Group->find('all'); + $this->set(array( + 'groups' => $groups, + '_serialize' => array('groups') + )); + } + +/** + * view method + * + * @throws NotFoundException + * @param string $id + * @return void + */ + public function view($id = null) { + $this->Group->recursive = -1; + if (!$this->Group->exists($id)) { + throw new NotFoundException(__('Invalid group')); + } + $options = array('conditions' => array('Group.' . $this->Group->primaryKey => $id)); + $group = $this->Group->find('first', $options); + $this->set(array( + 'group' => $group, + '_serialize' => array('group') + )); + } + +/** + * add method + * + * @return void + */ + public function add() { + if ($this->request->is('post')) { + $this->Group->create(); + if ($this->Group->save($this->request->data)) { + return $this->flash(__('The group has been saved.'), array('action' => 'index')); + } + } + $monitors = $this->Group->Monitor->find('list'); + $this->set(compact('monitors')); + } + +/** + * edit method + * + * @throws NotFoundException + * @param string $id + * @return void + */ + public function edit($id = null) { + if (!$this->Group->exists($id)) { + throw new NotFoundException(__('Invalid group')); + } + if ($this->request->is(array('post', 'put'))) { + if ($this->Group->save($this->request->data)) { + return $this->flash(__('The group has been saved.'), array('action' => 'index')); + } + } else { + $options = array('conditions' => array('Group.' . $this->Group->primaryKey => $id)); + $this->request->data = $this->Group->find('first', $options); + } + $monitors = $this->Group->Monitor->find('list'); + $this->set(compact('monitors')); + } + +/** + * delete method + * + * @throws NotFoundException + * @param string $id + * @return void + */ + public function delete($id = null) { + $this->Group->id = $id; + if (!$this->Group->exists()) { + throw new NotFoundException(__('Invalid group')); + } + $this->request->allowMethod('post', 'delete'); + if ($this->Group->delete()) { + return $this->flash(__('The group has been deleted.'), array('action' => 'index')); + } else { + return $this->flash(__('The group could not be deleted. Please, try again.'), array('action' => 'index')); + } + }} diff --git a/web/api/app/Controller/MonitorsController.php b/web/api/app/Controller/MonitorsController.php index 9ad487917..a49b022dc 100644 --- a/web/api/app/Controller/MonitorsController.php +++ b/web/api/app/Controller/MonitorsController.php @@ -8,7 +8,6 @@ App::uses('AppController', 'Controller'); */ class MonitorsController extends AppController { - /** * Components * @@ -16,18 +15,14 @@ class MonitorsController extends AppController { */ public $components = array('Paginator', 'RequestHandler'); - -public function beforeFilter() { - parent::beforeFilter(); - $canView = $this->Session->Read('monitorPermission'); - if ($canView =='None') - { - throw new UnauthorizedException(__('Insufficient Privileges')); - return; - } - -} - + public function beforeFilter() { + parent::beforeFilter(); + $canView = $this->Session->Read('monitorPermission'); + if ($canView =='None') { + throw new UnauthorizedException(__('Insufficient Privileges')); + return; + } + } /** * index method @@ -36,7 +31,7 @@ public function beforeFilter() { */ public function index() { $this->Monitor->recursive = 0; - + if ($this->request->params['named']) { $this->FilterComponent = $this->Components->load('Filter'); $conditions = $this->FilterComponent->buildFilter($this->request->params['named']); @@ -48,7 +43,28 @@ public function beforeFilter() { if (!empty($allowedMonitors)) { $conditions['Monitor.Id' ] = $allowedMonitors; } - $monitors = $this->Monitor->find('all',array('conditions'=>$conditions)); + $find_array = array('conditions'=>$conditions,'contain'=>array('Group')); + + //if ( $this->request->params['GroupId'] ) { + $find_array['joins'] = array( + array( + 'table' => 'Groups_Monitors', + 'type' => 'inner', + 'conditions' => array( + 'Groups_Monitors.MonitorId = Monitor.Id' + ), + ), + //array( + //'table' => 'Groups', + //'type' => 'inner', + //'conditions' => array( + //'Groups.Id = Groups_Monitors.GroupId', + //'Groups.Id' => $this->request->params['GroupId'], + //), + //) + ); + //} + $monitors = $this->Monitor->find('all',$find_array); $this->set(array( 'monitors' => $monitors, '_serialize' => array('monitors') diff --git a/web/api/app/Model/Event.php b/web/api/app/Model/Event.php index 8bf6be518..3ce1b93bb 100644 --- a/web/api/app/Model/Event.php +++ b/web/api/app/Model/Event.php @@ -68,4 +68,28 @@ class Event extends AppModel { ) ); + /** + * * * hasMany associations + * * * + * * * @var array + * * */ + public $hasAndBelongsToMany = array( + 'Group' => array( + 'className' => 'Group', + 'joinTable' => 'Groups_Monitors', + 'foreignKey' => 'MonitorId', + 'associationForeignKey' => 'GroupId', + 'unique' => true, + 'dependent' => false, + 'conditions' => 'Groups_Monitors.MonitorId=Event.MonitorId', + 'fields' => '', + 'order' => '', + 'limit' => '', + 'offset' => '', + 'exclusive' => '', + 'finderQuery' => '', + 'counterQuery' => '' + ), + ); + } diff --git a/web/api/app/Model/Group.php b/web/api/app/Model/Group.php new file mode 100644 index 000000000..595bc3b3a --- /dev/null +++ b/web/api/app/Model/Group.php @@ -0,0 +1,68 @@ + array( + 'notEmpty' => array( + 'rule' => array('notEmpty'), + //'message' => 'Your custom message here', + //'allowEmpty' => false, + //'required' => false, + //'last' => false, // Stop validation after this rule + //'on' => 'create', // Limit validation to 'create' or 'update' operations + ), + ), + ); + + public $recursive = -1; + //The Associations below have been created with all possible keys, those that are not needed can be removed + +/** + * hasMany associations + * + * @var array + */ + public $hasMany = array( + 'Monitor' => array( + 'className' => 'Monitor', + 'joinTable' => 'Groups_Monitors', + 'foreignKey' => 'GroupId', + 'associationForeignKey' => 'MonitorId', + 'unique'=>true, + 'dependent' => false, + 'conditions' => '', + 'fields' => '', + 'order' => '', + 'limit' => '', + 'offset' => '', + 'exclusive' => '', + 'finderQuery' => '', + 'counterQuery' => '' + ), + ); +} diff --git a/web/api/app/Model/Monitor.php b/web/api/app/Model/Monitor.php index 775be74c1..0504dd22a 100644 --- a/web/api/app/Model/Monitor.php +++ b/web/api/app/Model/Monitor.php @@ -85,4 +85,28 @@ class Monitor extends AppModel { ) ); + /** + * * hasMany associations + * * + * * @var array + * */ + public $hasAndBelongsToMany = array( + 'Group' => array( + 'className' => 'Group', + 'joinTable' => 'Groups_Monitors', + 'foreignKey' => 'MonitorId', + 'associationForeignKey' => 'GroupId', + 'unique' => true, + 'dependent' => false, + 'conditions' => '', + 'fields' => '', + 'order' => '', + 'limit' => '', + 'offset' => '', + 'exclusive' => '', + 'finderQuery' => '', + 'counterQuery' => '' + ), + ); + } diff --git a/web/api/app/View/Servers/xml/index.ctp b/web/api/app/View/Servers/xml/index.ctp index 37afc918b..9bb514fff 100644 --- a/web/api/app/View/Servers/xml/index.ctp +++ b/web/api/app/View/Servers/xml/index.ctp @@ -1,2 +1,2 @@ -$xml = Xml::fromArray(array('response' => $monitors)); +$xml = Xml::fromArray(array('response' => $servers)); echo $xml->asXML(); diff --git a/web/api/app/View/Servers/xml/view.ctp b/web/api/app/View/Servers/xml/view.ctp index b33c6e79a..3b2a3fdad 100644 --- a/web/api/app/View/Servers/xml/view.ctp +++ b/web/api/app/View/Servers/xml/view.ctp @@ -1,2 +1,2 @@ -$xml = Xml::fromArray(array('response' => $monitor)); +$xml = Xml::fromArray(array('response' => $server)); echo $xml->asXML(); diff --git a/web/includes/Filter.php b/web/includes/Filter.php index 11d47973a..5c7fc7b39 100644 --- a/web/includes/Filter.php +++ b/web/includes/Filter.php @@ -12,6 +12,8 @@ public $defaults = array( 'AutoArchive' => 0, 'AutoVideo' => 0, 'AutoMessage' => 0, + 'AutoMove' => 0, + 'AutoMoveTo' => 0, 'UpdateDiskSpace' => 0, 'Background' => 0, 'Concurrent' => 0, diff --git a/web/includes/Group.php b/web/includes/Group.php index 7db8486d2..ca3557076 100644 --- a/web/includes/Group.php +++ b/web/includes/Group.php @@ -6,7 +6,6 @@ public $defaults = array( 'Id' => null, 'Name' => '', 'ParentId' => null, - 'MonitorIds' => '', ); public function __construct( $IdOrRow=NULL ) { @@ -88,7 +87,8 @@ public $defaults = array( public function delete() { if ( array_key_exists( 'Id', $this ) ) { - dbQuery( 'DELETE FROM Groups WHERE Id = ?', array($this->{'Id'}) ); + dbQuery( 'DELETE FROM Groups WHERE Id=?', array($this->{'Id'}) ); + dbQuery( 'DELETE FROM Groups_Monitors WHERE GroupId=?', array($this->{'Id'}) ); if ( isset($_COOKIE['zmGroup']) ) { if ( $this->{'Id'} == $_COOKIE['zmGroup'] ) { unset( $_COOKIE['zmGroup'] ); @@ -128,6 +128,13 @@ public $defaults = array( return $this->{'depth'}; } // end public function depth + public function MonitorIds( ) { + if ( ! array_key_exists( 'MonitorIds', $this ) ) { + $this->{'MonitorIds'} = dbFetchAll( 'SELECT MonitorId FROM Groups_Monitors WHERE GroupId=?', 'MonitorId', array($this->{'Id'}) ); + } + return $this->{'MonitorIds'}; + } + public static function get_group_dropdowns() { # This will end up with the group_id of the deepest selection $group_id = 0; @@ -164,19 +171,14 @@ public $defaults = array( return $group_id; } # end public static function get_group_dropdowns() + public static function get_group_sql( $group_id ) { $groupSql = ''; if ( $group_id ) { - if ( $group = dbFetchOne( 'SELECT MonitorIds FROM Groups WHERE Id=?', NULL, array($group_id) ) ) { - $groupIds = array(); - if ( $group['MonitorIds'] ) - $groupIds = explode( ',', $group['MonitorIds'] ); + $MonitorIds = dbFetchAll( 'SELECT MonitorId FROM Groups_Monitors WHERE GroupId=?', 'MonitorId', array($group_id) ); - foreach ( dbFetchAll( 'SELECT MonitorIds FROM Groups WHERE ParentId = ?', NULL, array($group_id) ) as $group ) - if ( $group['MonitorIds'] ) - $groupIds = array_merge( $groupIds, explode( ',', $group['MonitorIds'] ) ); - } - $groupSql = " find_in_set( Id, '".implode( ',', $groupIds )."' )"; + $MonitorIds = array_merge( $MonitorIds, dbFetchAll( 'SELECT MonitorId FROM Groups_Monitors WHERE GroupId IN (SELECT Id FROM Groups WHERE ParentId = ?)', 'MonitorId', array($group_id) ) ); + $groupSql = " find_in_set( Id, '".implode( ',', $MonitorIds )."' )"; } return $groupSql; } # end public static function get_group_sql( $group_id ) diff --git a/web/includes/actions.php b/web/includes/actions.php index 1a2a13d56..91c5cacf9 100644 --- a/web/includes/actions.php +++ b/web/includes/actions.php @@ -142,7 +142,6 @@ if ( canView( 'Events' ) ) { if ( isset( $_REQUEST['object'] ) and ( $_REQUEST['object'] == 'filter' ) ) { if ( $action == 'addterm' ) { -Warning("Addterm"); $_REQUEST['filter'] = addFilterTerm( $_REQUEST['filter'], $_REQUEST['line'] ); } elseif ( $action == 'delterm' ) { $_REQUEST['filter'] = delFilterTerm( $_REQUEST['filter'], $_REQUEST['line'] ); @@ -173,6 +172,11 @@ Warning("Addterm"); $sql .= ', AutoExecute = '. ( !empty($_REQUEST['filter']['AutoExecute']) ? 1 : 0); $sql .= ', AutoExecuteCmd = '.dbEscape($_REQUEST['filter']['AutoExecuteCmd']); $sql .= ', AutoDelete = '. ( !empty($_REQUEST['filter']['AutoDelete']) ? 1 : 0); + if ( !empty($_REQUEST['filter']['AutoMove']) ? 1 : 0) { + $sql .= ', AutoMove = 1, AutoMoveTo='. validInt($_REQUEST['filter']['AutoMoveTo']); + } else { + $sql .= ', AutoMove = 0'; + } $sql .= ', UpdateDiskSpace = '. ( !empty($_REQUEST['filter']['UpdateDiskSpace']) ? 1 : 0); $sql .= ', Background = '. ( !empty($_REQUEST['filter']['Background']) ? 1 : 0); $sql .= ', Concurrent = '. ( !empty($_REQUEST['filter']['Concurrent']) ? 1 : 0); @@ -542,7 +546,7 @@ if ( canEdit( 'Monitors' ) ) { $saferName = basename($_REQUEST['newMonitor']['Name']); symlink( $mid, ZM_DIR_EVENTS.'/'.$saferName ); if ( isset($_COOKIE['zmGroup']) ) { - dbQuery( "UPDATE Groups SET MonitorIds = concat(MonitorIds,',".$mid."') WHERE Id=?", array($_COOKIE['zmGroup']) ); + dbQuery( 'INSERT INTO Groups_Monitors (GroupId,MonitorId) VALUES (?,?)', array($_COOKIE['zmGroup'],$mid) ); } } else { Error("Users with Monitors restrictions cannot create new monitors."); @@ -662,12 +666,16 @@ if ( canEdit( 'Groups' ) ) { if ( $action == 'group' ) { $monitors = empty( $_POST['newGroup']['MonitorIds'] ) ? '' : implode(',', $_POST['newGroup']['MonitorIds']); if ( !empty($_POST['gid']) ) { - dbQuery( 'UPDATE Groups SET Name=?, ParentId=?, MonitorIds=? WHERE Id=?', - array($_POST['newGroup']['Name'], ( $_POST['newGroup']['ParentId'] == '' ? null : $_POST['newGroup']['ParentId'] ), $monitors, $_POST['gid']) ); + dbQuery( 'UPDATE Groups SET Name=?, ParentId=? WHERE Id=?', + array($_POST['newGroup']['Name'], ( $_POST['newGroup']['ParentId'] == '' ? null : $_POST['newGroup']['ParentId'] ), $_POST['gid']) ); + dbQuery( 'DELETE FROM Groups_Monitors WHERE GroupId=?', array($_POST['gid']) ); } else { dbQuery( 'INSERT INTO Groups SET Name=?, ParentId=?, MonitorIds=?', array( $_POST['newGroup']['Name'], ( $_POST['newGroup']['ParentId'] == '' ? null : $_POST['newGroup']['ParentId'] ), $monitors ) ); } + foreach ( $_POST['newGroup']['MonitorIds'] as $mid ) { + dbQuery( 'INSERT INTO Groups_Monitors (GroupId,MonitorId) VALUES (?,?)', array($_POST['gid'], $mid) ); + } $view = 'none'; $refreshParent = true; } else if ( $action == 'delete' ) { diff --git a/web/includes/config.php.in b/web/includes/config.php.in index 66ab5e517..3b6209803 100644 --- a/web/includes/config.php.in +++ b/web/includes/config.php.in @@ -137,19 +137,19 @@ require_once( 'database.php' ); loadConfig(); $GLOBALS['defaultUser'] = array( - "Username" => "admin", - "Password" => "", - "Language" => "", - "Enabled" => 1, - "Stream" => 'View', - "Events" => 'Edit', - "Control" => 'Edit', - "Monitors" => 'Edit', - "Groups" => 'Edit', - "Devices" => 'Edit', - "System" => 'Edit', - "MaxBandwidth" => "", - "MonitorIds" => false + 'Username' => "admin", + 'Password' => "", + 'Language' => "", + 'Enabled' => 1, + 'Stream' => 'View', + 'Events' => 'Edit', + 'Control' => 'Edit', + 'Monitors' => 'Edit', + 'Groups' => 'Edit', + 'Devices' => 'Edit', + 'System' => 'Edit', + 'MaxBandwidth' => '', + 'MonitorIds' => false ); function loadConfig( $defineConsts=true ) { @@ -179,7 +179,7 @@ function loadConfig( $defineConsts=true ) { } require_once( 'logger.php' ); -// For Human-readability, user ZM_SERVER in zm.conf, and convert it here to a ZM_SERVER_ID +// For Human-readability, use ZM_SERVER_HOST or ZM_SERVER_NAME in zm.conf, and convert it here to a ZM_SERVER_ID if ( ! defined('ZM_SERVER_ID') ) { if ( defined('ZM_SERVER_NAME') and ZM_SERVER_NAME ) { $server_id = dbFetchOne('SELECT Id FROM Servers WHERE Name=?', 'Id', array(ZM_SERVER_NAME)); diff --git a/web/includes/database.php b/web/includes/database.php index a1bcf66c3..29ec1fd77 100644 --- a/web/includes/database.php +++ b/web/includes/database.php @@ -134,7 +134,7 @@ function dbQuery( $sql, $params=NULL ) { } else { $result = $dbConn->query( $sql ); } -if ( 1 ) { +if ( defined('ZM_DB_DEBUG') ) { if ( $params ) Warning("SQL: $sql" . implode(',',$params) . ' rows: '.$result->rowCount() ); else diff --git a/web/includes/functions.php b/web/includes/functions.php index 7cb81c23e..77111c5b5 100644 --- a/web/includes/functions.php +++ b/web/includes/functions.php @@ -1872,7 +1872,10 @@ function monitorIdsToNames( $ids ) { } } $names = array(); - foreach ( preg_split( '/\s*,\s*/', $ids ) as $id ) { + if ( ! is_array($ids) ) { + $ids = preg_split( '/\s*,\s*/', $ids ); + } + foreach ( $ids as $id ) { if ( visibleMonitor( $id ) ) { if ( isset($mITN_monitors[$id]) ) { $names[] = $mITN_monitors[$id]['Name']; diff --git a/web/lang/en_gb.php b/web/lang/en_gb.php index 4d00798be..45d0daef6 100644 --- a/web/lang/en_gb.php +++ b/web/lang/en_gb.php @@ -322,6 +322,8 @@ $SLANG = array( 'ExportDetails' => 'Export Event Details', 'Exif' => 'Embed EXIF data into image', 'Export' => 'Export', + 'DownloadVideo' => 'Download Video', + 'GenerateDownload' => 'Generate Download', 'ExportFailed' => 'Export Failed', 'ExportFormat' => 'Export File Format', 'ExportFormatTar' => 'Tar', diff --git a/web/skins/classic/css/classic/skin.css b/web/skins/classic/css/classic/skin.css index 218f42e7a..5248edc9d 100644 --- a/web/skins/classic/css/classic/skin.css +++ b/web/skins/classic/css/classic/skin.css @@ -1,3 +1,4 @@ + /* * ZoneMinder Base Stylesheet, $Date$, $Revision$ * Copyright (C) 2001-2008 Philip Coombes @@ -346,9 +347,11 @@ th.table-th-sort-rev span.table-th-sort-span { #header { width: 100%; - line-height: 24px; - margin: 8px auto; + margin: 0 auto 4px auto; + line-height: 1; text-align: left; + padding: 3px 0; + border-bottom: 1px solid #555555; } #header h2 { @@ -378,7 +381,7 @@ th.table-th-sort-rev span.table-th-sort-span { #content { width: 96%; - margin: 8px auto; + margin: 0 auto 8px auto; line-height: 130%; text-align: center; } diff --git a/web/skins/classic/css/classic/views/event.css b/web/skins/classic/css/classic/views/event.css index dbcc30835..803315798 100644 --- a/web/skins/classic/css/classic/views/event.css +++ b/web/skins/classic/css/classic/views/event.css @@ -29,89 +29,42 @@ span.noneCue { background: none; } +#header { + display: flex; + justify-content: space-between; + flex-direction: column; +} + +#header h2, #header a { + line-height: 1.1; + margin:5px 0 0 0; +} + #dataBar { - width: 100%; - margin: 2px auto; - text-align: center; -} - -#dataBar #dataTable { - width: 100%; - table-layout: fixed; -} - -#dataBar #dataTable td { - text-align: center; - padding: 2px; + display: flex; + flex-wrap:wrap; + justify-content: space-between; } #menuBar1 { + display: flex; + flex-wrap: wrap; + justify-content: flex-start; width: 100%; - padding: 3px 0; text-align: center; - clear: both; -} - -#menuBar1 #nameControl { - float: left; -} - -#menuBar1 #nameControl #eventName { - margin-right: 4px; + margin: 4px 0 0 0; } #menuBar1 #replayControl { - float: right; - margin-left: 8px; + margin: 0 4px 0 auto; } -#menuBar1 #scaleControl { - float: right; - margin-left: 8px; +#menuBar1 div { +margin: auto 5px; } -#menuBar2 { - width: 100%; - padding: 3px 0; - margin-bottom: 4px; -} - -#menuBar2 div { - text-align: left; - float: left; - padding: 0 12px; -} - -#menuBar2 #closeWindow { - float: right; - text-align: right; -} - -#menuBar1:after, -#menuBar2:after { - content: "."; - display: block; - height: 0; - font-size: 0; - clear: both; - visibility: hidden; -} - -#videoBar1 div { - text-align: center; - float: center; -} - -#videoBar1 #prevEvent { - float: left; -} - -#videoBar1 #dlEvent { - float: center; -} - -#videoBar1 #nextEvent { - float: right; +#nameControl input[type="button"]{ +height: 100%; } #eventVideo { diff --git a/web/skins/classic/css/classic/views/events.css b/web/skins/classic/css/classic/views/events.css index ec4fd1c8a..c167fc92d 100644 --- a/web/skins/classic/css/classic/views/events.css +++ b/web/skins/classic/css/classic/views/events.css @@ -2,36 +2,6 @@ background-color: #f8f8f8;; } -#controls { - height: 16px; - width: 100%; - text-align: center; - margin: 0 auto; - position: relative; -} - -#controls a { - width: 32%; -} - -#controls #refreshLink { - position: absolute; - left: 0%; - text-align: left; -} - -#controls #filterLink { - position: absolute; - left: 34%; - text-align: center; -} - -#controls #timelineLink { - position: absolute; - left: 68%; - text-align: right; -} - #contentTable.major .colTime { white-space: nowrap; } @@ -44,3 +14,26 @@ text-align: right; padding-right: 8px; } + +#header { + display: flex; + justify-content: space-between; +} + +#header h2, #header a { + line-height: 1.1; + margin:5px 0 0 0; +} + +#header #info, #header #pagination, #header #controls { + display: flex; + flex-direction: column; +} + +#header #controls { + align-items: flex-end; +} + +#header #pagination { + align-items: center; +} diff --git a/web/skins/classic/css/classic/views/montagereview.css b/web/skins/classic/css/classic/views/montagereview.css index 84109fbd1..7e6696de1 100644 --- a/web/skins/classic/css/classic/views/montagereview.css +++ b/web/skins/classic/css/classic/views/montagereview.css @@ -36,3 +36,7 @@ input[type=range]::-ms-tooltip { display: none; } + +#downloadVideo { + margin-left: 5px; +} diff --git a/web/skins/classic/css/classic/views/timeline.css b/web/skins/classic/css/classic/views/timeline.css index 6b66ea4e1..fd9832568 100644 --- a/web/skins/classic/css/classic/views/timeline.css +++ b/web/skins/classic/css/classic/views/timeline.css @@ -5,6 +5,25 @@ margin: 0 auto; } +#header { + display: flex; + justify-content: space-between; +} + +#header h2, #header a { + line-height: 1.1; + margin:5px 0 0 0; +} + +#header #info, #header #headerButtons { + display: flex; + flex-direction: column; +} + +#header #headerButtons { + align-items: flex-end; +} + #title { position: relative; margin: 0 auto; @@ -15,20 +34,6 @@ line-height: 20px; } -#listLink { - position: absolute; - top: 5px; - left: 20px; - height: 15px; -} - -#closeLink { - position: absolute; - top: 5px; - right: 20px; - height: 15px; -} - #topPanel { position: relative; height: 220px; diff --git a/web/skins/classic/css/classic/views/watch.css b/web/skins/classic/css/classic/views/watch.css index d1f435f7d..28bda54ef 100644 --- a/web/skins/classic/css/classic/views/watch.css +++ b/web/skins/classic/css/classic/views/watch.css @@ -1,37 +1,17 @@ @import url(../control.css); -#menuBar { - margin: 6px auto 4px; - text-align: center; +#header { + display: flex; + justify-content: space-between; } -#menuBar #monitorName { - float: left; +#menuControls { + display: flex; + align-items: center; } -#menuBar #closeControl { - float: right; -} - -#menuBar #menuControls { - margin: 0 auto; - width: 60%; -} - -#menuBar #menuControls #controlControl { - float: left; -} - -#menuBar #menuControls #eventsControl { - float: left; -} - -#menuBar #menuControls #settingsControl { - float: right; -} - -#menuBar #menuControls #scaleControl { - margin: 0 auto; +#menuControls div { + margin: 0 0 0 1em; } #imageFeed{ diff --git a/web/skins/classic/css/dark/skin.css b/web/skins/classic/css/dark/skin.css index 3bd2ad1e3..c094b570f 100644 --- a/web/skins/classic/css/dark/skin.css +++ b/web/skins/classic/css/dark/skin.css @@ -373,11 +373,12 @@ th.table-th-sort-rev span.table-th-sort-span { #header { width: 100%; - line-height: 24px; + line-height: 1; text-align: left; - margin-bottom: 10px; - padding: 10px 20px; + padding: 5px 20px; + margin: 0 auto 4px auto; font-weight: 300; + border-bottom: 1px solid #000000; } #header h2 { @@ -407,7 +408,7 @@ th.table-th-sort-rev span.table-th-sort-span { #content { width: 96%; - margin: 8px auto; + margin: 0 auto 8px auto; line-height: 130%; text-align: center; } diff --git a/web/skins/classic/css/dark/views/event.css b/web/skins/classic/css/dark/views/event.css index a156c2438..139b5ca19 100644 --- a/web/skins/classic/css/dark/views/event.css +++ b/web/skins/classic/css/dark/views/event.css @@ -29,72 +29,42 @@ span.noneCue { background: none; } +#header { + display: flex; + justify-content: space-between; + flex-direction: column; +} + +#header h2, #header a { + line-height: 1.1; + margin:5px 0 0 0; +} + #dataBar { - width: 100%; - margin: 2px auto; - text-align: center; -} - -#dataBar #dataTable { - width: 100%; - table-layout: fixed; -} - -#dataBar #dataTable td { - text-align: center; - padding: 2px; + display: flex; + flex-wrap:wrap; + justify-content: space-between; } #menuBar1 { + display: flex; + flex-wrap: wrap; + justify-content: flex-start; width: 100%; - padding: 3px 0; text-align: center; - clear: both; -} - -#menuBar1 #nameControl { - float: left; -} - -#menuBar1 #nameControl #eventName { - margin-right: 4px; + margin: 4px 0 0 0; } #menuBar1 #replayControl { - float: right; - margin-left: 8px; + margin: 0 4px 0 auto; } -#menuBar1 #scaleControl { - float: right; - margin-left: 8px; +#menuBar1 div { +margin: auto 5px; } -#menuBar2 { - width: 100%; - padding: 3px 0; - margin-bottom: 4px; -} - -#menuBar2 div { - text-align: left; - float: left; - padding: 0 12px; -} - -#menuBar2 #closeWindow { - float: right; - text-align: right; -} - -#menuBar1:after, -#menuBar2:after { - content: "."; - display: block; - height: 0; - font-size: 0; - clear: both; - visibility: hidden; +#nameControl input[type="button"]{ +height: 100%; } #eventVideo { diff --git a/web/skins/classic/css/dark/views/events.css b/web/skins/classic/css/dark/views/events.css index 3e3d1b620..03b1e0a76 100644 --- a/web/skins/classic/css/dark/views/events.css +++ b/web/skins/classic/css/dark/views/events.css @@ -2,36 +2,6 @@ background-color: #2e2e2e; } -#controls { - height: 16px; - width: 100%; - text-align: center; - margin: 0 auto; - position: relative; -} - -#controls a { - width: 32%; -} - -#controls #refreshLink { - position: absolute; - left: 0%; - text-align: left; -} - -#controls #filterLink { - position: absolute; - left: 34%; - text-align: center; -} - -#controls #timelineLink { - position: absolute; - left: 68%; - text-align: right; -} - #contentTable.major .colTime { white-space: nowrap; } @@ -44,3 +14,26 @@ text-align: right; padding-right: 8px; } + +#header { + display: flex; + justify-content: space-between; +} + +#header h2, #header a { + line-height: 1.1; + margin:5px 0 0 0; +} + +#header #info, #header #pagination, #header #controls { + display: flex; + flex-direction: column; +} + +#header #controls { + align-items: flex-end; +} + +#header #pagination { + align-items: center; +} diff --git a/web/skins/classic/css/dark/views/montagereview.css b/web/skins/classic/css/dark/views/montagereview.css index 84109fbd1..7e6696de1 100644 --- a/web/skins/classic/css/dark/views/montagereview.css +++ b/web/skins/classic/css/dark/views/montagereview.css @@ -36,3 +36,7 @@ input[type=range]::-ms-tooltip { display: none; } + +#downloadVideo { + margin-left: 5px; +} diff --git a/web/skins/classic/css/dark/views/timeline.css b/web/skins/classic/css/dark/views/timeline.css index bb1053c68..09aca0eba 100644 --- a/web/skins/classic/css/dark/views/timeline.css +++ b/web/skins/classic/css/dark/views/timeline.css @@ -5,6 +5,25 @@ margin: 0 auto; } +#header { + display: flex; + justify-content: space-between; +} + +#header h2, #header a { + line-height: 1.1; + margin:5px 0 0 0; +} + +#header #info, #header #headerButtons { + display: flex; + flex-direction: column; +} + +#header #headerButtons { + align-items: flex-end; +} + #title { position: relative; margin: 0 auto; @@ -15,20 +34,6 @@ line-height: 20px; } -#listLink { - position: absolute; - top: 5px; - left: 20px; - height: 15px; -} - -#closeLink { - position: absolute; - top: 5px; - right: 20px; - height: 15px; -} - #topPanel { position: relative; height: 220px; diff --git a/web/skins/classic/css/dark/views/watch.css b/web/skins/classic/css/dark/views/watch.css index d1f435f7d..28bda54ef 100644 --- a/web/skins/classic/css/dark/views/watch.css +++ b/web/skins/classic/css/dark/views/watch.css @@ -1,37 +1,17 @@ @import url(../control.css); -#menuBar { - margin: 6px auto 4px; - text-align: center; +#header { + display: flex; + justify-content: space-between; } -#menuBar #monitorName { - float: left; +#menuControls { + display: flex; + align-items: center; } -#menuBar #closeControl { - float: right; -} - -#menuBar #menuControls { - margin: 0 auto; - width: 60%; -} - -#menuBar #menuControls #controlControl { - float: left; -} - -#menuBar #menuControls #eventsControl { - float: left; -} - -#menuBar #menuControls #settingsControl { - float: right; -} - -#menuBar #menuControls #scaleControl { - margin: 0 auto; +#menuControls div { + margin: 0 0 0 1em; } #imageFeed{ diff --git a/web/skins/classic/css/flat/skin.css b/web/skins/classic/css/flat/skin.css index 2752d0056..1a3bc375f 100644 --- a/web/skins/classic/css/flat/skin.css +++ b/web/skins/classic/css/flat/skin.css @@ -368,11 +368,11 @@ th.table-th-sort-rev span.table-th-sort-span { #header { width: 100%; - line-height: 24px; + line-height: 1; text-align: left; background-color: #383836; - margin-bottom: 10px; - padding: 10px 20px; + padding: 5px 20px; + margin: 0 auto 4px auto; color: #f2f2f2; font-weight: 300; } @@ -410,7 +410,7 @@ th.table-th-sort-rev span.table-th-sort-span { #content { width: 96%; - margin: 8px auto; + margin: 0 auto 8px auto; line-height: 130%; text-align: center; } diff --git a/web/skins/classic/css/flat/views/event.css b/web/skins/classic/css/flat/views/event.css index 30758b573..637e76402 100644 --- a/web/skins/classic/css/flat/views/event.css +++ b/web/skins/classic/css/flat/views/event.css @@ -29,72 +29,47 @@ span.noneCue { background: none; } +#header { + display: flex; + justify-content: space-between; + flex-direction: column; + margin: 0 0 4px 0; +} + +#header h2, #header a { + line-height: 1.1; + margin:5px 0 0 0; +} + #dataBar { - width: 100%; - margin: 2px auto; - text-align: center; -} - -#dataBar #dataTable { - width: 100%; - table-layout: fixed; -} - -#dataBar #dataTable td { - text-align: center; - padding: 2px; + display: flex; + flex-wrap:wrap; + justify-content: space-between; } #menuBar1 { + display: flex; + flex-wrap: wrap; + justify-content: flex-start; width: 100%; - padding: 3px 0; text-align: center; - clear: both; + margin: 4px 0 0 0; } -#menuBar1 #nameControl { - float: left; -} - -#menuBar1 #nameControl #eventName { - margin-right: 4px; +#menuBar1 input, #menuBar1 select { + padding: 2px 5px; } #menuBar1 #replayControl { - float: right; - margin-left: 8px; + margin: 0 4px 0 auto; } -#menuBar1 #scaleControl { - float: right; - margin-left: 8px; +#menuBar1 div { +margin: auto 5px; } -#menuBar2 { - width: 100%; - padding: 3px 0; - margin-bottom: 4px; -} - -#menuBar2 div { - text-align: left; - float: left; - padding: 0 12px; -} - -#menuBar2 #closeWindow { - float: right; - text-align: right; -} - -#menuBar1:after, -#menuBar2:after { - content: "."; - display: block; - height: 0; - font-size: 0; - clear: both; - visibility: hidden; +#nameControl input[type="button"]{ +height: 100%; } #eventVideo { diff --git a/web/skins/classic/css/flat/views/events.css b/web/skins/classic/css/flat/views/events.css index ec4fd1c8a..c167fc92d 100644 --- a/web/skins/classic/css/flat/views/events.css +++ b/web/skins/classic/css/flat/views/events.css @@ -2,36 +2,6 @@ background-color: #f8f8f8;; } -#controls { - height: 16px; - width: 100%; - text-align: center; - margin: 0 auto; - position: relative; -} - -#controls a { - width: 32%; -} - -#controls #refreshLink { - position: absolute; - left: 0%; - text-align: left; -} - -#controls #filterLink { - position: absolute; - left: 34%; - text-align: center; -} - -#controls #timelineLink { - position: absolute; - left: 68%; - text-align: right; -} - #contentTable.major .colTime { white-space: nowrap; } @@ -44,3 +14,26 @@ text-align: right; padding-right: 8px; } + +#header { + display: flex; + justify-content: space-between; +} + +#header h2, #header a { + line-height: 1.1; + margin:5px 0 0 0; +} + +#header #info, #header #pagination, #header #controls { + display: flex; + flex-direction: column; +} + +#header #controls { + align-items: flex-end; +} + +#header #pagination { + align-items: center; +} diff --git a/web/skins/classic/css/flat/views/montagereview.css b/web/skins/classic/css/flat/views/montagereview.css index 6f8feddf3..e82e5ae95 100644 --- a/web/skins/classic/css/flat/views/montagereview.css +++ b/web/skins/classic/css/flat/views/montagereview.css @@ -42,3 +42,7 @@ input[type=range]::-ms-tooltip { color: white; font-size: 40px; } + +#downloadVideo { + margin-left: 5px; +} diff --git a/web/skins/classic/css/flat/views/timeline.css b/web/skins/classic/css/flat/views/timeline.css index 7ea3403c7..4e78e82cf 100644 --- a/web/skins/classic/css/flat/views/timeline.css +++ b/web/skins/classic/css/flat/views/timeline.css @@ -5,6 +5,25 @@ margin: 0 auto; } +#header { + display: flex; + justify-content: space-between; +} + +#header h2, #header a { + line-height: 1.1; + margin:5px 0 0 0; +} + +#header #info, #header #headerButtons { + display: flex; + flex-direction: column; +} + +#header #headerButtons { + align-items: flex-end; +} + #title { position: relative; margin: 0 auto; @@ -15,20 +34,6 @@ line-height: 20px; } -#listLink { - position: absolute; - top: 5px; - left: 20px; - height: 15px; -} - -#closeLink { - position: absolute; - top: 5px; - right: 20px; - height: 15px; -} - #topPanel { position: relative; height: 220px; diff --git a/web/skins/classic/css/flat/views/watch.css b/web/skins/classic/css/flat/views/watch.css index 5fec8ec82..ef7937db2 100644 --- a/web/skins/classic/css/flat/views/watch.css +++ b/web/skins/classic/css/flat/views/watch.css @@ -1,37 +1,17 @@ @import url(../control.css); -#menuBar { - margin: 6px auto 4px; - text-align: center; +#header { + display: flex; + justify-content: space-between; } -#menuBar #monitorName { - float: left; +#menuControls { + display: flex; + align-items: center; } -#menuBar #closeControl { - float: right; -} - -#menuBar #menuControls { - margin: 0 auto; - width: 60%; -} - -#menuBar #menuControls #controlControl { - float: left; -} - -#menuBar #menuControls #eventsControl { - float: left; -} - -#menuBar #menuControls #settingsControl { - float: right; -} - -#menuBar #menuControls #scaleControl { - margin: 0 auto; +#menuControls div { + margin: 0 0 0 1em; } #imageFeed{ diff --git a/web/skins/classic/includes/export_functions.php b/web/skins/classic/includes/export_functions.php index 2414e391f..604dcba4e 100644 --- a/web/skins/classic/includes/export_functions.php +++ b/web/skins/classic/includes/export_functions.php @@ -853,7 +853,7 @@ function exportFileList( $eid, $exportDetail, $exportFrames, $exportImages, $exp return( array_values( $exportFileList ) ); } -function exportEvents( $eids, $exportDetail, $exportFrames, $exportImages, $exportVideo, $exportMisc, $exportFormat ) +function exportEvents( $eids, $exportDetail, $exportFrames, $exportImages, $exportVideo, $exportMisc, $exportFormat, $exportStructure = false ) { if ( canView( 'Events' ) && !empty($eids) ) @@ -907,8 +907,12 @@ function exportEvents( $eids, $exportDetail, $exportFrames, $exportImages, $expo { $archive = ZM_DIR_EXPORTS."/".$export_root.".tar.gz"; @unlink( $archive ); - $command = "tar --create --gzip --file=$archive --files-from=$listFile"; - exec( escapeshellcmd( $command ), $output, $status ); + if ($exportStructure == 'flat') { //strip file paths if we choose + $command = "tar --create --gzip --file=".escapeshellarg($archive)." --files-from=".escapeshellarg($listFile)." --xform='s#^.+/##x'"; + } else { + $command = "tar --create --gzip --file=".escapeshellarg($archive)." --files-from=".escapeshellarg($listFile); + } + exec( $command, $output, $status ); if ( $status ) { Error( "Command '$command' returned with status $status" ); @@ -921,7 +925,11 @@ function exportEvents( $eids, $exportDetail, $exportFrames, $exportImages, $expo { $archive = ZM_DIR_EXPORTS."/".$export_root.".zip"; @unlink( $archive ); - $command = "cat ".escapeshellarg($listFile)." | zip -q ".escapeshellarg($archive)." -@"; + if ($exportStructure == 'flat') { + $command = "cat ".escapeshellarg($listFile)." | zip -q -j ".escapeshellarg($archive)." -@"; + } else { + $command = "cat ".escapeshellarg($listFile)." | zip -q ".escapeshellarg($archive)." -@"; + } //cat zmFileList.txt | zip -q zm_export.zip -@ //-bash: zip: command not found diff --git a/web/skins/classic/includes/functions.php b/web/skins/classic/includes/functions.php index b2e6d17c9..803916162 100644 --- a/web/skins/classic/includes/functions.php +++ b/web/skins/classic/includes/functions.php @@ -166,7 +166,12 @@ function getNavBarHTML($reload = null) { global $user; global $bandwidth_options; global $view; -if ($reload === null) { + global $filterQuery; + if (!$filterQuery) { + parseFilter( $_REQUEST['filter'] ); + $filterQuery = $_REQUEST['filter']['query']; + } + if ($reload === null) { ob_start(); if ( $running == null ) $running = daemonCheck(); @@ -210,7 +215,7 @@ if ( ZM_OPT_X10 && canView( 'Devices' ) ) { ?>