diff --git a/bin/mh b/bin/mh index 48e9de26d..0e85956c4 100755 --- a/bin/mh +++ b/bin/mh @@ -849,6 +849,7 @@ sub setup { # require 'console_utils.pl'; require 'http_server.pl'; + require 'ia7_utilities.pl'; require 'xml_server.pl'; require 'menu_code.pl'; require 'trigger_code.pl'; diff --git a/code/common/ia7_collection_upgrader.pl b/code/common/ia7_collection_upgrader.pl deleted file mode 100644 index 8a6b37e04..000000000 --- a/code/common/ia7_collection_upgrader.pl +++ /dev/null @@ -1,51 +0,0 @@ -# Category = IA7 - -#@ IA7 Collection Updater v1.3 : This is a helper utility that can find and update collections.json files -#@ if any structural changes are required. -#@ -#@ v1.2 - add in new login system id 700 - -my $ia7_coll_current_ver = 1.2; - -if ($Startup) { - - my @collection_files = ("$Pgm_Root/data/web/collections.json", - "$config_parms{data_dir}/web/collections.json", - "$config_parms{ia7_data_dir}/collections.json"); - for my $file (@collection_files) { - if (-e $file) { - print_log "[IA7_Collection_Updater] : Checking $file to current version $ia7_coll_current_ver"; - my $json_data; - my $file_data; - eval { - $file_data = file_read($file); - $json_data = decode_json($file_data); #HP, wrap this in eval to prevent MH crashes - }; - - if ($@) { - print_log "[IA7_Collection_Updater] : WARNING: decode_json failed for $file. Please check this file!"; - } else { - my $updated = 0; - - if ((! defined $json_data->{meta}->{version}) or ($json_data->{meta}->{version} < 1.2)) { #IA7 v1.2 required change - $json_data->{700}->{user} = '$Authorized' unless (defined $json_data->{700}->{user}); - my $found = 0; - foreach my $i (@{$json_data->{500}->{children}}) { - $found = 1 if ($i == 700); - } - push (@{$json_data->{500}->{children}},700) unless ($found); - $json_data->{meta}->{version} = "1.2"; - print_log "[IA7_Collection_Updater] : Updating $file to version 1.2"; - $updated = 1; - } - if ($updated) { - my $json_newdata = to_json($json_data, {utf8 => 1, pretty => 1}); - my $backup_file = $file . ".t" . int( ::get_tickcount() / 1000 ) . ".backup"; - file_write($backup_file,$file_data); - print_log "[IA7_Collection_Updater] : Saved backup " . $file . ".t" . int( ::get_tickcount() / 1000 ) . ".backup"; - file_write($file,$json_newdata); - } - } - } - } -} \ No newline at end of file diff --git a/lib/Generic_Item.pm b/lib/Generic_Item.pm index 6b4c6c2f0..df8c590e6 100644 --- a/lib/Generic_Item.pm +++ b/lib/Generic_Item.pm @@ -1,5 +1,6 @@ use strict; - +#hp 1133 +# data/object_logs//YYYY/MM.log package Generic_Item_Hash; require Tie::Hash; @@ -132,6 +133,10 @@ sub new { $$self{state_now} = undef; $$self{state_changed} = undef; $self->restore_data('sort_order'); + $self->{logger_enable} = $main::config_parms{object_logger_enable} if (defined $main::config_parms{object_logger_enable}); + $self->{logger_mintime} = 1; + $self->{logger_updatetime} = 0; + return $self; } @@ -1130,6 +1135,9 @@ sub set_state_log { $state = '' unless defined $state; $set_by_name = '' unless defined $set_by_name; $target = '' unless defined $target; +# + $self->logger($state,$set_by_name,$target) if ($self->{logger_enable}); + unshift( @{ $$self{state_log} }, "$main::Time_Date $state set_by=$set_by_name" @@ -1144,6 +1152,30 @@ sub set_state_log { return ( $set_by, $target ); } +=item C + +TODO + +=cut +sub logger { + my ($self,$state,$set_by_name,$target) = @_; + my $object_name = $self->{object_name}; + $object_name =~ s/^\$//; + return if ($object_name eq ""); + return if ($state eq ""); + my $tickcount = int(&::get_tickcount()); #log in milliseconds + return if ($tickcount < ($self->{logger_updatetime} + $self->{logger_mintime})); + + #create directory structure if it doesn't exist + mkdir ($::config_parms{data_dir} . "/object_logs") unless (-d $::config_parms{data_dir} . "/object_logs"); + mkdir ($::config_parms{data_dir} . "/object_logs/" . $object_name) unless (-d $::config_parms{data_dir} . "/object_logs/" . $object_name); + mkdir ($::config_parms{data_dir} . "/object_logs/" . $object_name . "/" . $::Year) unless (-d $::config_parms{data_dir} . "/object_logs/" . $object_name . "/" . $::Year); + mkdir ($::config_parms{data_dir} . "/object_logs/" . $object_name . "/" . $::Year . "/" . $::Month) unless (-d $::config_parms{data_dir} . "/object_logs/" . $object_name . "/" . $::Year . "/" . $::Month); + #write the data to the log; time, ticks (milliseconds), object, state, set_by, target + &::logit ($::config_parms{data_dir} . "/object_logs/" . $object_name . "/" . $::Year . "/" . $::Month . "/" . $::Mday . ".log", "$main::Time_Date,$tickcount,$object_name,$state,$set_by_name," . ( ($target) ? "$target" : '') ."\n",0); + $self->{logger_updatetime} = $tickcount; +} + =item C TODO @@ -1244,6 +1276,82 @@ sub xPL_enable { $self->{xpl_enable} = $enable; } +=item C + +Will start logging state changes to a historical log file + +=cut + +sub logger_enable { + my ( $self, $enable ) = @_; + if ($self->isa('Group') and (defined $main::config_parms{object_logger_group})) { + $self->{logger_enable} = $main::config_parms{object_logger_group}; + } else { + $self->{logger_enable} = 1; + } +} + +=item C + +Will stop logging state changes to a historical log file + +=cut + +sub logger_disable { + my ( $self, $enable ) = @_; + $self->{logger_enable} = 0; +} + +=item C + +Set the minimum number of seconds to log updates. Useful to 'throttle' noisey objects such as AD2 motion sensors + +=cut + +sub logger_mintime { + my ( $self, $mintime ) = @_; + $self->{logger_mintime} = $mintime; +} + +=item C + +Returns 1 if logger is enabled on the object. Otherwise 0. + +=cut + +sub get_logger_status { + my ( $self, $enable ) = @_; + return ($self->{logger_enable} ? 1 : 0); +} + +=item C + +Returns logged data at date, back days number of days +Date format is epoch +=cut + +sub get_logger_data { + my ( $self, $epoch, $days ) = @_; + $days = 0 unless (defined $days); + my $object_name = $self->{object_name}; + $object_name =~ s/^\$//; + my $data = ""; + $epoch = $epoch - ($days * 60 * 60 * 24); + for (my $i = 0; $i <= $days; $i++) { + print "db i=$i, days=$days, epoch=$epoch\n"; + my ($sec,$min,$hour,$mday,$mon,$year,$wday,$yday,$isdst) = localtime($epoch + ($i * 60 * 60 * 24)); + print "Epoch: $epoch is " . $mday . "/" . ($mon + 1) . "/" . ($year+1900) . "\n"; + print "Checking " . $::config_parms{data_dir} . "/object_logs/" . $object_name . "/" . ($year + 1900) . "/" . ($mon + 1) . "/" . $mday . "\n"; + print "Reading " . $::config_parms{data_dir} . "/object_logs/" . $object_name . "/" . ($year + 1900) . "/" . ($mon + 1) . "/" . $mday . "\n" if ( -e $::config_parms{data_dir} . "/object_logs/" . $object_name . "/" . ($year + 1900) . "/" . ($mon + 1) . "/" . $mday . ".log"); + $data .= ::file_read($::config_parms{data_dir} . "/object_logs/" . $object_name . "/" . ($year + 1900) . "/" . ($mon + 1) . "/" . $mday . ".log") if ( -e $::config_parms{data_dir} . "/object_logs/" . $object_name . "/" . ($year + 1900) . "/" . ($mon + 1) . "/" . $mday . ".log"); +# $epoch = $epoch + (60*60*24); + } + + return $data; +} + + + =item C If the state of the generic_item changes, then code will trigger, with the lexical variables $state and $object getting set. The code is a string that will be eval'd and the variables are available to it, but not to any subroutines called by it unless you pass them. You can also set the state variable explicitly since you usually know the item. The code is a string that will be eval'd. diff --git a/lib/Group.pm b/lib/Group.pm index 92d9de888..ca0ca185e 100644 --- a/lib/Group.pm +++ b/lib/Group.pm @@ -58,6 +58,7 @@ sub new { my $self = new Generic_Item(); $$self{members} = []; &add( $self, @items ) if @items; + $self->{logger_enable} = $main::config_parms{object_logger_group} if (defined $main::config_parms{object_logger_group}); bless $self, $class; return $self; } diff --git a/lib/Insteon/BaseInsteon.pm b/lib/Insteon/BaseInsteon.pm index a868a01f1..9e1190259 100644 --- a/lib/Insteon/BaseInsteon.pm +++ b/lib/Insteon/BaseInsteon.pm @@ -130,7 +130,9 @@ sub new { $$self{default_hop_count} = 0; $$self{timeout_factor} = 1.0; $$self{is_deaf} = 0; - + $$self{logger_enable} = $main::config_parms{object_logger_enable} if (defined $main::config_parms{object_logger_enable}); + $$self{logger_mintime} = 1; + $$self{logger_updatetime} = 0; &Insteon::add($self); return $self; } diff --git a/lib/SCHEDULE.pm b/lib/SCHEDULE.pm new file mode 100644 index 000000000..e0687e3d9 --- /dev/null +++ b/lib/SCHEDULE.pm @@ -0,0 +1,438 @@ +package SCHEDULE; +@SCHEDULE::ISA = ('Generic_Item'); + + +sub new +{ + my ($class, $instance) = @_; + my $self = new Generic_Item(); + $$self{instance} = $instance; + bless $self, $class; + @{$$self{states}} = ('ON','OFF'); + $self->restore_data('active_object', 'active_action', 'schedule_count'); + #for my $index (1..$self->{'schedule_count'}) { + for my $index (1..10) { + $self->restore_data('schedule_'.$index); + $self->restore_data('schedule_label_'.$index); + } + return $self; +} + + + +sub set { + my ($self, $p_state, $p_setby, $p_response) = @_; + $self->SUPER::set($p_state,$p_setby,1); + } + +#sub set_schedule { +# my ($self, $type, $p_state) = @_; +# $$self{'schedule'}{'type'} = lc($type); +# my @cals; +# #$self{'type'} = 'calendar'; + #$self{'schedule'}{'7'}{'28'}{'20'}{'41'}{'action'} = 'start'; + #$self{'schedule'}{'7'}{'28'}{'20'}{'42'}{'action'} = 'stop'; +# if ($p_state =~ /-/) { @cals = split /-/, $p_state } +# else { @cals = ($p_state) } +# foreach my $values (@cals) { +# my @calvals = split /,/, $values; +# $$self{'schedule'}{$calvals[1]}{$calvals[2]}{$calvals[3]}{$calvals[4]}{'action'} = lc($calvals[0]) if ($type eq 'calendar'); +# $$self{'schedule'}{lc($calvals[1])}{$calvals[2]}{$calvals[3]}{'action'} = lc($calvals[0]) if ($type eq 'daily'); +# $$self{'schedule'}{lc($calvals[1])}{$calvals[2]}{$calvals[3]}{'action'} = lc($calvals[0]) if ($type eq 'wdwe'); +# $$self{'schedule'}{$calvals[1]}{$calvals[2]}{'action'} = lc($calvals[0]) if ($type eq 'time'); +# } +# } + +sub set_schedule { + my ($self,$index,$entry,$label) = @_; + ::print_log("[SCHEDULE] - set_schedule - Index " . $index . " Schedule: ". $entry ." Label ". $label); + if ($index > $self->{'schedule_count'}) { $self->{'schedule_count'} = $index } + $self->{'schedule_'.$index} = $entry if (defined($entry)); + $self->{'schedule_label_'.$index} = $label if (defined($label)); + unless ($entry) { + undef $self->{'schedule_label_'.$index}; + undef $self->{'schedule_'.$index}; + } + $self->{set_time} = $main::Time; +} + + +sub delete_schedule { + my ($self,$index) = @_; + $self->set_schedule($index); +} + + +sub get_schedule{ + my ($self) = @_; + my @schedule; + my $count; + my @states; + my $object = @{$self->{generic_object}}[0] if (@{$self->{generic_object}}[0]); + if (defined($object->{state_count})) { + $count = $object->{state_count}; + if ($count eq 0) { + @states = $object->{child}->get_states; + #unshift @states, 0; + } else { + for my $index (1..$count) { + $states[$index-1] = $object->{$index}; + } + } + } + else { $states[0]=undef } + + #if ((defined($object->{state_count})) && ($object->{state_count} > $self->{'schedule_count'})) { $count = $object->{state_count} } + #else { $count = $self->{'schedule_count'} } + $count = $self->{'schedule_count'}; + $schedule[0][0] = 0; #Index + $schedule[0][1] = '0 0 5 1 1'; #schedule + $schedule[0][2] = 0; #Label + $schedule[0][3] = \@states; + for my $index (1..$count) { + $schedule[$index][0] = $index; + $schedule[$index][1] = $self->{'schedule_'.$index}; + if (defined($self->{'schedule_label_'.$index}) ) { $schedule[$index][2] = $self->{'schedule_label_'.$index} } + #elsif (defined($object->{$index})) { $schedule[$index][2] = $object->{$index}; } + else { $schedule[$index][2] = $index; } + #$schedule[$index][3] = \@states; + } + return \@schedule; +} + +sub am_i_active_object{ + my ($self,$instance) = @_; + unless (defined($instance)) { return 1 } + ::print_log("[SCHEDULE] - am_i_active_object - active object: ".$Interfaces{$instance}->get_object_name." check object: ".$self->get_object_name) if (defined($Interfaces{$instance})); + if (defined($Interfaces{$instance})) { + if ($Interfaces{$instance}->get_object_name eq $self->get_object_name) { return 1 } + else { $self->{'active_object'} = 0; return 0; } + } elsif ($self->{'active_object'}) { #This is for a restart to get the saved active object. + my $action = $self->{'active_action'} if defined($self->{'active_action'}); + $self->_set_instance_active_object($$self{instance},$action); + return 1; + } else { return 0 } +} + +sub get_instance_active_object{ + my ($instance) = @_; + return $Interfaces{$instance} if defined($instance); +} + + +sub get_instance_active_action{ + my ($instance) = @_; + return $Interfaces{$instance}{'action'} if defined($instance); +} + + +sub _set_instance_active_object{ + my ($self, $instance, $action) = @_; + $self->{'active_object'} = 1; + $self->{'active_action'} = $action if defined($action); + $Interfaces{$instance} = $self if defined($instance); + $Interfaces{$instance}{'action'} = $action if defined($action); +} + + +sub get_objects_for_instance { + my ($self) = @_; + my $instance = $$self{instance}; + return \@{$Shedule_objects{$instance}} if defined($instance); +} + + +=item C + +Used to associate child objects with the interface. + +=cut + +sub register { + my ($self, $object, $child, $state1, $state2) = @_; + if ($object->isa('SCHEDULE_Generic')) { + ::print_log("Registering a SCHEDULE Child Object type SCHEDULE_Generic" ); + push @{$self->{generic_object}}, $object; + ::MainLoop_pre_add_hook( sub {SCHEDULE::check_date($self,$object);}, 'persistent'); + } + if ($object->isa('SCHEDULE_Temp')) { + my $HorC = $child; + ::print_log("Registering a SCHEDULE Child Object type SCHEDULE_Temp" ); + $self->{temp_object}{$HorC} = $object; + if ((defined($self->{temp_object}{'cool'})) && (defined($self->{temp_object}{'heat'}))) { + ::MainLoop_pre_add_hook( sub {SCHEDULE::check_date($self,$self->{temp_object}{'cool'});}, 'persistent' ); + } + } +} + + + +#sub check_date { +# my ($self,$object) = @_; +# my $occupied_state = ($$self{occupied}->state_now) if (defined($$self{occupied})); +# if ($occupied_state) { $self->ChangeACSetpoint if (($self->am_i_active_object($$self{instance})) && (lc(state $self) eq 'on')) } + +# if ($::New_Minute) { +# ::print_log("[SCHEDULE] Checking schedule for ". $self->get_object_name." Sate is ". (state $self) . " Child object is ". $object->get_object_name); +# if (lc(state $self) eq 'on') { +# my $Week; +# if ($::Weekday) { $Week = 'weekday' } elsif ($::Weekend) { $Week = 'weekend' } +# if (($$self{'schedule'}{'type'} eq 'calendar') && +# (defined(my $action = $$self{'schedule'}{$::Month}{$::Mday}{$::Hour}{$::Minute}{'action'}))) { +# &set_action($self,$object,$action); +# } +# elsif (($$self{'schedule'}{'type'} eq 'daily') && +# (defined(my $action = $$self{'schedule'}{lc($::Day)}{$::Hour}{$::Minute}{'action'}))) { +# &set_action($self,$object,$action); +# } +# elsif (($$self{'schedule'}{'type'} eq 'wdwe') && +# (defined(my $action = $$self{'schedule'}{$Week}{$::Hour}{$::Minute}{'action'}))) { +# &set_action($self,$object,$action); +# } +# elsif (($$self{'schedule'}{'type'} eq 'time') && +# (defined(my $action = $$self{'schedule'}{$::Hour}{$::Minute}{'action'}))) { +# &set_action($self,$object,$action); +# } +# } +# +# } +#} + + +sub check_date { + my ($self,$object) = @_; + if ($::Startup or $::Reload) { $self->{'reloaded'} = 1 } + my $occupied_state = ($$self{occupied}->state_now) if (defined($$self{occupied})); + if ($occupied_state) { $self->ChangeACSetpoint if (($self->am_i_active_object($$self{instance})) && (lc(state $self) eq 'on')) } + + if ($::New_Minute) { + $self->am_i_active_object($$self{instance}) if (defined($$self{instance}));; + ::print_log("[SCHEDULE] Checking schedule for ". $self->get_object_name." Sate is ". (state $self) . " Child object is ". $object->get_object_name); + if (lc(state $self) eq 'on') { + #foreach my $values (@{$self->{'schedule'}}) { + for my $index (1..$self->{'schedule_count'}) { + #my @calvals = split /,/, $self->{'schedule_'.$index}; + # if (&::time_cron($calvals[1])) { &set_action($self,$object,$calvals[0]) } + if (&::time_cron($self->{'schedule_'.$index})) { &set_action($self,$object,$index) } + } + } + + } +} + + +sub setACSetpoint { + my ($self,$object) = @_; + my $cool_sp = $object->{temp_object}{'cool'}->state; + my $cool_temp_control = $object->{temp_object}{'cool'}{child}; + my $cool_temp_control_sub = $object->{temp_object}{'cool'}{sub}; + my $heat_sp = $object->{temp_object}{'heat'}->state; + my $heat_temp_control = $object->{temp_object}{'heat'}{child}; + my $heat_temp_control_sub = $object->{temp_object}{'heat'}{sub}; + $cool_temp_control->$cool_temp_control_sub($cool_sp); + $heat_temp_control->$heat_temp_control_sub($heat_sp); + + + ::print_log("[SCHEDULE] running ".$cool_temp_control->get_object_name."->".$cool_temp_control_sub."(".$cool_sp.")"); + ::print_log("[SCHEDULE] running ".$heat_temp_control->get_object_name."->".$heat_temp_control_sub."(".$heat_sp.")"); +} + +sub set_action { + my ($self,$object,$index) = @_; + if ($object->isa('SCHEDULE_Generic')) { + ::print_log("[SCHEDULE] Setting ".$object->{child}->get_object_name." state to ".$self->{'schedule_label_'.$index}); + $self->_set_instance_active_object($$self{instance},$index) if (defined($$self{instance})); + $object->{child}->SUPER::set($self->{'schedule_label_'.$index},$self->get_object_name,1); + } + elsif ($object->isa('SCHEDULE_Temp')) { + ::print_log("[SCHEDULE] set_action - Temp object: ".$object->get_object_name." Parent object: ".$self->get_object_name); + $self->_set_instance_active_object($$self{instance}) if (defined($$self{instance})); + $self->ChangeACSetpoint; + } +} + +sub set_occpuancy { + my ($self, $normal_state, $setback_state, $setback_object, $delay, $delay_setback, $tracked_object) = @_; + $$self{occ_state} = $normal_state; + $$self{occ_setback_state} = $setback_state; + $$self{occ_setback_object} = $setback_object; + $$self{thermo_timer_delay} = '60'; + $$self{thermo_timer_delay} = $delay if (defined $delay); + $$self{thermo_timer_delay_setback} = '60'; + $$self{thermo_timer_delay_setback} = $delay_setback if (defined $delay_setback); + $$self{occupied} = $::mode_occupied unless (defined $$self{occupied}); + $$self{occupied} = $tracked_object if (defined $tracked_object); + $$self{thermo_timer} = ::Timer::new(); +} + + +sub set_winter { + my ($self, $object, $temp) = @_; + $$self{winter_mode_object} = $object; + $$self{winter_mode_temp} = $temp +} + + +sub set_vacation { + my ($self, $object, $state) = @_; + $$self{vacation_mode_object} = $object; + $$self{vacation_mode_state} = $state; +} + + +sub ChangeACSetpoint { +my ($self,$object) = @_; +unless ($self->am_i_active_object($$self{instance})) { return 0 } +my $action = $self->get_instance_active_action($$self{instance}); +my $occ_setback_object = $$self{occ_setback_object}; +my $occ_setback_state = $$self{occ_setback_state}; +my $occ_state = $$self{occ_state}; +my $object = $self; +my $occupied_state = ($$self{occupied}->state) if (defined($$self{occupied})); + +print_log("[THERMO] - DEBUG --- in ChangeACSetpoint") if ($config_parms{"thermo_schedule"} eq 'debug'); + + if (&OverRide($self)) { + $occ_setback_object = $$self{override_mode_setback_object} if defined($$self{override_mode_setback_object}); + $occ_setback_state = $$self{override_mode_setback_state} if defined($$self{override_mode_setback_state}); + $occ_state = $$self{override_mode_occ_state} if defined($$self{occ_state}); + $object = $$self{override_mode_object} if defined($$self{override_mode_object}); + } + &main::print_log("[THERMO] - INFO - ChangeACSetpoint - " . $occupied_state ." ". $object->get_object_name ." state match: $occ_state"); + if ((defined($$self{occupied})) && ($$self{occupied}->state eq $occ_state)) { + &main::print_log("[THERMO] - INFO - ChangeACSetpoint - occ state match ". $object->get_object_name ." setpoints, you are now $occ_state"); + if ($$self{thermo_timer}->expired) { + &main::print_log("[THERMO] - INFO - setting ". $object->get_object_name ." setpoints, you are now $occ_state"); + $self->setACSetpoint($object); + } else { + $$self{thermo_timer}->set($$self{thermo_timer_delay}, sub { + $self->ChangeACSetpoint; + }); + } + } elsif ((defined($$self{occupied})) && ($$self{occupied}->state eq $occ_setback_state)) { + if ($$self{thermo_timer}->expired) { + &main::print_log("[THERMO] - INFO - setting setback ". $occ_setback_object->get_object_name ."setpoints, you are now $occ_setback_state"); + $self->setACSetpoint($occ_setback_object); + } else { + $$self{thermo_timer}->set($$self{thermo_timer_delay_setback}, sub { + $self->ChangeACSetpoint; + }); + } + } else { + $self->setACSetpoint($object); + } +} + + +sub OverRide { +my ($self) = @_; +my $occupied_state = ($$self{occupied}->state) if (defined($$self{occupied})); +undef $$self{override_mode_setback_object}; +undef $$self{override_mode_setback_state}; +undef $$self{override_mode_occ_state}; +undef $$self{override_mode_object}; + print_log("[THERMO] - DEBUG --- IN OVERRIDE") if ($config_parms{"thermo_schedule"} eq 'debug'); + if ($occupied_state eq 'vacation') { + print_log("[THERMO] - DEBUG --- IN OVERRIDE --- VACATION") if ($config_parms{"thermo_schedule"} eq 'debug'); + $$self{override_mode_setback_object} = $$self{vacation_mode_object}; # override the setpoint if in vacation mode + $$self{override_mode_setback_state} = $$self{vacation_mode_state}; # override the setback state if in vacation mode + return 1; + } elsif ($self->WinterMode) { + print_log("[THERMO] - DEBUG --- IN OVERRIDE --- WINTERMODE") if ($config_parms{"thermo_schedule"} eq 'debug'); + $$self{override_mode_object} = $$self{winter_mode_object}; # override the setpoint if forcast temp is below config + return 1; + } + return 0; +} + + +sub WinterMode { +#return 1; # temp for testing +my ($self) = @_; + print_log("[THERMO] - DEBUG --- IN WINTERMODE") if ($config_parms{"thermo_schedule"} eq 'debug'); + if ($::Weather{'Forecast Tonight'} =~ /lows in the ([\w ]+) (\d+)/i) { + print_log("[THERMO] - DEBUG --- IN WINTERMODE --- FORCAST --- $1 $2") if ($config_parms{"thermo_schedule"} eq 'debug'); + my $fc = $2; + if (lc($1) =~ /mid/) { $fc = $fc + 3 } # Translate low, mid, and upper to a value + if (lc($1) =~ /up/) { $fc = $fc + 6 } + ##if the value we got from the weather script is equal + #or lower than our defined value, return the defined winter mode + if ($fc <= ($$self{winter_mode_temp})) { + ::print_log("[THERMO] - DEBUG --- IN WINTERMODE ---- M1 --- LOWS -- $fc -- $$self{winter_mode_temp}") if ($config_parms{"thermo_schedule"} eq 'debug'); + return 1; + } + return 0; + } + } + + +package SCHEDULE_Generic; +@SCHEDULE_Generic::ISA = ('Generic_Item'); + + +sub new +{ + my $class = @_[0]; + my $self = new Generic_Item(); + bless $self, $class; + $$self{parent} = @_[1]; + $$self{child} = @_[2]; + $$self{state_count} = ((scalar @_) - 3); + my @states; + for my $i (3..(scalar @_)) { if (defined @_[$i]) { $self->{$i-2}=@_[$i]; push (@states, @_[$i]); } } + @{$$self{states}} = @states if (defined @states); + $$self{parent}->register($self,$child); + return $self; +} + + + +sub set { + my ($self, $p_state, $p_setby, $p_response) = @_; + $self->SUPER::set($p_state,$p_setby,1); +} + + +package SCHEDULE_Temp; +@SCHEDULE_Temp::ISA = ('Generic_Item'); + + +sub new +{ + my ($class, $parent, $HorC, $child, $sub) = @_; + my $self = new Generic_Item(); + bless $self, $class; + $$self{parent} = $parent; + $$self{HorC} = $HorC; + $$self{child} = $child; + $$self{sub} = $sub; + $$self{state_count} = 7; + @{$$self{states}} = ('up','down'); + $parent->register($self,$HorC); + return $self; +} + + + +sub set { + my ($self, $p_state, $p_setby, $p_response) = @_; + my $current_state = $self->state; + unless (defined($current_state)) { $current_state = '70' } + if ($p_state eq 'up') { + ::print_log("[SCHEDULE::TEMP] Received request " + . $p_state ." for ". $self->get_object_name); + $p_state = $current_state + 1; + $self->SUPER::set($p_state,$p_setby,1); + } + if ($p_state eq 'down') { + ::print_log("[SCHEDULE::TEMP] Received request " + . $p_state ." for ". $self->get_object_name); + $p_state = $current_state - 1; + $self->SUPER::set($p_state,$p_setby,1); + } + if ($p_state =~ /(\d+)/) { + ::print_log("[SCHEDULE::TEMP] Received request " + . $p_state ." for ". $self->get_object_name,1); + $self->SUPER::set($p_state,$p_setby,1);; + } +} diff --git a/lib/ia7_utilities.pl b/lib/ia7_utilities.pl new file mode 100644 index 000000000..8b29a1a87 --- /dev/null +++ b/lib/ia7_utilities.pl @@ -0,0 +1,68 @@ +package ia7_utilities; +use strict; +use Data::Dumper; + +sub main::ia7_update_schedule { + my ($object,@schedules) = @_; + + &main::print_log("Updating Schedule for object $object, schedule size is " . scalar(@schedules)); + + my $obj = &main::get_object_by_name($object); + my $s=0; + my $index; + my @curr_schedule = $obj->get_schedule; + $obj->reset_schedule(); + for (my $i = 1; $i <= (scalar(@schedules)/3); $i++) { + &main::print_log("Adding Schedule (id=" . $schedules[$i*3 - 3] . " cron=" . $schedules[$i*3 - 2] . " label=" . $schedules[$i*3 -1] . ")"); + $obj->set_schedule($schedules[$i*3 - 3],$schedules[$i*3 - 2],$schedules[$i*3 -1]); + } +} + + + +if ($main::Startup) { + + my $ia7_coll_current_ver = 1.2; + + my @collection_files = ("$main::Pgm_Root/data/web/collections.json", + "$main::config_parms{data_dir}/web/collections.json", + "$main::config_parms{ia7_data_dir}/collections.json"); + for my $file (@collection_files) { + if (-e $file) { + &main::print_log("[IA7_Collection_Updater] : Checking $file to current version $ia7_coll_current_ver"); + my $json_data; + my $file_data; + eval { + $file_data = &main::file_read($file); + $json_data = decode_json($file_data); #HP, wrap this in eval to prevent MH crashes + }; + + if ($@) { + &main::print_log("[IA7_Collection_Updater] : WARNING: decode_json failed for $file. Please check this file!"); + } else { + my $updated = 0; + + if ((! defined $json_data->{meta}->{version}) or ($json_data->{meta}->{version} < 1.2)) { #IA7 v1.2 required change + $json_data->{700}->{user} = '$Authorized' unless (defined $json_data->{700}->{user}); + my $found = 0; + foreach my $i (@{$json_data->{500}->{children}}) { + $found = 1 if ($i == 700); + } + push (@{$json_data->{500}->{children}},700) unless ($found); + $json_data->{meta}->{version} = "1.2"; + &main::print_log("[IA7_Collection_Updater] : Updating $file to version 1.2"); + $updated = 1; + } + if ($updated) { + my $json_newdata = to_json($json_data, {utf8 => 1, pretty => 1}); + my $backup_file = $file . ".t" . int( ::get_tickcount() / 1000 ) . ".backup"; + &main::file_write($backup_file,$file_data); + &main::print_log("[IA7_Collection_Updater] : Saved backup " . $file . ".t" . int( ::get_tickcount() / 1000 ) . ".backup"); + &main::file_write($file,$json_newdata); + } + } + } + } +} + +1; \ No newline at end of file diff --git a/lib/json_server.pl b/lib/json_server.pl index 3235573fa..b4cf561a1 100644 --- a/lib/json_server.pl +++ b/lib/json_server.pl @@ -414,6 +414,87 @@ sub json_get { $json_data{'rrd'} = \%data; } + # List object history + if ( $path[0] eq 'history') { + + if ( $args{items} && $args{items}[0] ne "" ) { + my %statemaps = ('on' => 100, + 'open' => 100, + 'opened' => 100, + 'motion' => 100, + 'enable' => 100, + 'enabled' => 100, + 'online' => 100, + 'off' => -100, + 'close' => -100, + 'closed' => -100, + 'still' => -100, + 'disable' => -100, + 'disabled' => -100, + 'offline' => -100, + 'dim' => 50, + ); + my $unknown_value = 40; + my @dataset = (); + my %states; + my %data; + my $index = 0; + my $start = time; + $start = $args{start}[0] if (defined $args{start}[0]); + my $graph = 0; + $graph = $args{graph}[0] if (defined $args{graph}[0]); + my $days = 0; + $days = $args{days}[0] if (defined $args{days}[0]); + foreach my $name ( @{ $args{items} } ) { + my $o = &get_object_by_name($name); + next unless (defined $o); + next unless $o->get_logger_status(); + my $label = $o->set_label(); + $label = $name unless (defined $label); + my $logger_data = $o->get_logger_data($start,$days); + #alert if any anomalous states are detected + my @lines = split /\n/, $logger_data; + foreach my $line (@lines) { + my $value; + my ($time1,$time2,$obj,$state,$setby,$target) = split ",",$line; + if ($graph) { + if (defined $statemaps{$state}) { + $value = $statemaps{$state}; + } else { + if ($state =~ /(\d+)\%/) { + $value = $_; + } else { + &main::print_log("json_server.pl: WARNING. object history state $state not found in mapping"); + $value = $unknown_value++; + } + } + $states{$value} = $state; + push @{$dataset[$index]->{data}}, [ int($time2), int($value) ]; + } else { + push @dataset, [int($time2), $state, $setby]; + } + } + push @{$dataset[$index]->{label}}, $label if ($graph); + $index++; + } + if ($graph) { + #flot expects to see an array + my @yaxticks = (); + for my $j (sort keys %states) { + push @yaxticks, [ $j, $states{$j} ]; + } + $data{'options'}->{'yaxis'}->{'ticks'} = \@yaxticks; #\%states; + $data{'options'}->{'legend'}->{'show'} = "true"; + $data{'options'}->{'xaxis'}->{'mode'} = "time"; + $data{'options'}->{'points'}->{'show'} = "true"; + $data{'options'}->{'xaxis'}->{'timezone'} = "browser"; + $data{'options'}->{'grid'}->{'hoverable'} = "true"; + } + $data{'data'} = \@dataset; + $json_data{'history'} = \%data; + } + } + # List objects if ( $path[0] eq 'objects' || $path[0] eq '' ) { $json_data{objects} = {}; @@ -884,7 +965,7 @@ sub json_object_detail { my %json_objects; my %json_complete_object; my @f = qw( category filename measurement rf_id set_by members - state states state_log type label sort_order groups hidden parents + state states state_log type label sort_order groups hidden parents schedule logger_status idle_time text html seconds_remaining fp_location fp_icons fp_icon_set img link level); # Build list of fields based on those requested. diff --git a/web/ia7/house/main.shtml b/web/ia7/house/main.shtml index fbb530279..7f917f592 100644 --- a/web/ia7/house/main.shtml +++ b/web/ia7/house/main.shtml @@ -82,7 +82,7 @@

MisterHouse was created by Bruce Winter. Ron Klinkien developed the v2.3 web interface. Kevin Robert Keegan - developed the v4 web interface, updates by H.Plato. IA7 v1.2.350 Font Awesome by Dave Gandy - http://fontawesome.io

+ developed the v4 web interface, updates by H.Plato. IA7 v1.3.560 Font Awesome by Dave Gandy - http://fontawesome.io

diff --git a/web/ia7/include/bootstrap-datepicker.min.js b/web/ia7/include/bootstrap-datepicker.min.js new file mode 100755 index 000000000..69663f0b6 --- /dev/null +++ b/web/ia7/include/bootstrap-datepicker.min.js @@ -0,0 +1,10 @@ +/*! + * Datepicker for Bootstrap v1.7.0-dev (https://github.com/eternicode/bootstrap-datepicker) + * + * Copyright 2012 Stefan Petre + * Improvements by Andrew Rowls + * Licensed under the Apache License v2.0 (http://www.apache.org/licenses/LICENSE-2.0) + */ + +!function(a){"function"==typeof define&&define.amd?define(["jquery"],a):a("object"==typeof exports?require("jquery"):jQuery)}(function(a,b){function c(){return new Date(Date.UTC.apply(Date,arguments))}function d(){var a=new Date;return c(a.getFullYear(),a.getMonth(),a.getDate())}function e(a,b){return a.getUTCFullYear()===b.getUTCFullYear()&&a.getUTCMonth()===b.getUTCMonth()&&a.getUTCDate()===b.getUTCDate()}function f(a){return function(){return this[a].apply(this,arguments)}}function g(a){return a&&!isNaN(a.getTime())}function h(b,c){function d(a,b){return b.toLowerCase()}var e,f=a(b).data(),g={},h=new RegExp("^"+c.toLowerCase()+"([A-Z])");c=new RegExp("^"+c.toLowerCase());for(var i in f)c.test(i)&&(e=i.replace(h,d),g[e]=f[i]);return g}function i(b){var c={};if(q[b]||(b=b.split("-")[0],q[b])){var d=q[b];return a.each(p,function(a,b){b in d&&(c[b]=d[b])}),c}}var j=function(){var b={get:function(a){return this.slice(a)[0]},contains:function(a){for(var b=a&&a.valueOf(),c=0,d=this.length;d>c;c++)if(0<=this[c].valueOf()-b&&this[c].valueOf()-b<864e5)return c;return-1},remove:function(a){this.splice(a,1)},replace:function(b){b&&(a.isArray(b)||(b=[b]),this.clear(),this.push.apply(this,b))},clear:function(){this.length=0},copy:function(){var a=new j;return a.replace(this),a}};return function(){var c=[];return c.push.apply(c,arguments),a.extend(c,b),c}}(),k=function(b,c){a.data(b,"datepicker",this),this._process_options(c),this.dates=new j,this.viewDate=this.o.defaultViewDate,this.focusDate=null,this.element=a(b),this.isInput=this.element.is("input"),this.inputField=this.isInput?this.element:this.element.find("input"),this.component=this.element.hasClass("date")?this.element.find(".add-on, .input-group-addon, .btn"):!1,this.component&&0===this.component.length&&(this.component=!1),this.isInline=!this.component&&this.element.is("div"),this.picker=a(r.template),this._check_template(this.o.templates.leftArrow)&&this.picker.find(".prev").html(this.o.templates.leftArrow),this._check_template(this.o.templates.rightArrow)&&this.picker.find(".next").html(this.o.templates.rightArrow),this._buildEvents(),this._attachEvents(),this.isInline?this.picker.addClass("datepicker-inline").appendTo(this.element):this.picker.addClass("datepicker-dropdown dropdown-menu"),this.o.rtl&&this.picker.addClass("datepicker-rtl"),this.o.calendarWeeks&&this.picker.find(".datepicker-days .datepicker-switch, thead .datepicker-title, tfoot .today, tfoot .clear").attr("colspan",function(a,b){return Number(b)+1}),this._allow_update=!1,this.setStartDate(this._o.startDate),this.setEndDate(this._o.endDate),this.setDaysOfWeekDisabled(this.o.daysOfWeekDisabled),this.setDaysOfWeekHighlighted(this.o.daysOfWeekHighlighted),this.setDatesDisabled(this.o.datesDisabled),this.setViewMode(this.o.startView),this.fillDow(),this.fillMonths(),this._allow_update=!0,this.update(),this.isInline&&this.show()};k.prototype={constructor:k,_resolveViewName:function(b){return a.each(r.viewModes,function(c,d){return b===c||-1!==a.inArray(b,d.names)?(b=c,!1):void 0}),b},_resolveDaysOfWeek:function(b){return a.isArray(b)||(b=b.split(/[,\s]*/)),a.map(b,Number)},_check_template:function(c){try{if(c===b||""===c)return!1;if((c.match(/[<>]/g)||[]).length<=0)return!0;var d=a(c);return d.length>0}catch(e){return!1}},_process_options:function(b){this._o=a.extend({},this._o,b);var e=this.o=a.extend({},this._o),f=e.language;q[f]||(f=f.split("-")[0],q[f]||(f=o.language)),e.language=f,e.startView=this._resolveViewName(e.startView),e.minViewMode=this._resolveViewName(e.minViewMode),e.maxViewMode=this._resolveViewName(e.maxViewMode),e.startView=Math.max(this.o.minViewMode,Math.min(this.o.maxViewMode,e.startView)),e.multidate!==!0&&(e.multidate=Number(e.multidate)||!1,e.multidate!==!1&&(e.multidate=Math.max(0,e.multidate))),e.multidateSeparator=String(e.multidateSeparator),e.weekStart%=7,e.weekEnd=(e.weekStart+6)%7;var g=r.parseFormat(e.format);e.startDate!==-(1/0)&&(e.startDate?e.startDate instanceof Date?e.startDate=this._local_to_utc(this._zero_time(e.startDate)):e.startDate=r.parseDate(e.startDate,g,e.language,e.assumeNearbyYear):e.startDate=-(1/0)),e.endDate!==1/0&&(e.endDate?e.endDate instanceof Date?e.endDate=this._local_to_utc(this._zero_time(e.endDate)):e.endDate=r.parseDate(e.endDate,g,e.language,e.assumeNearbyYear):e.endDate=1/0),e.daysOfWeekDisabled=this._resolveDaysOfWeek(e.daysOfWeekDisabled||[]),e.daysOfWeekHighlighted=this._resolveDaysOfWeek(e.daysOfWeekHighlighted||[]),e.datesDisabled=e.datesDisabled||[],a.isArray(e.datesDisabled)||(e.datesDisabled=e.datesDisabled.split(",")),e.datesDisabled=a.map(e.datesDisabled,function(a){return r.parseDate(a,g,e.language,e.assumeNearbyYear)});var h=String(e.orientation).toLowerCase().split(/\s+/g),i=e.orientation.toLowerCase();if(h=a.grep(h,function(a){return/^auto|left|right|top|bottom$/.test(a)}),e.orientation={x:"auto",y:"auto"},i&&"auto"!==i)if(1===h.length)switch(h[0]){case"top":case"bottom":e.orientation.y=h[0];break;case"left":case"right":e.orientation.x=h[0]}else i=a.grep(h,function(a){return/^left|right$/.test(a)}),e.orientation.x=i[0]||"auto",i=a.grep(h,function(a){return/^top|bottom$/.test(a)}),e.orientation.y=i[0]||"auto";else;if(e.defaultViewDate){var j=e.defaultViewDate.year||(new Date).getFullYear(),k=e.defaultViewDate.month||0,l=e.defaultViewDate.day||1;e.defaultViewDate=c(j,k,l)}else e.defaultViewDate=d()},_events:[],_secondaryEvents:[],_applyEvents:function(a){for(var c,d,e,f=0;ff?(this.picker.addClass("datepicker-orient-right"),n+=m-b):this.o.rtl?this.picker.addClass("datepicker-orient-right"):this.picker.addClass("datepicker-orient-left");var p,q=this.o.orientation.y;if("auto"===q&&(p=-g+o-c,q=0>p?"bottom":"top"),this.picker.addClass("datepicker-orient-"+q),"top"===q?o-=c+parseInt(this.picker.css("padding-top")):o+=l,this.o.rtl){var r=f-(n+m);this.picker.css({top:o,right:r,zIndex:j})}else this.picker.css({top:o,left:n,zIndex:j});return this},_allow_update:!0,update:function(){if(!this._allow_update)return this;var b=this.dates.copy(),c=[],d=!1;return arguments.length?(a.each(arguments,a.proxy(function(a,b){b instanceof Date&&(b=this._local_to_utc(b)),c.push(b)},this)),d=!0):(c=this.isInput?this.element.val():this.element.data("date")||this.inputField.val(),c=c&&this.o.multidate?c.split(this.o.multidateSeparator):[c],delete this.element.data().date),c=a.map(c,a.proxy(function(a){return r.parseDate(a,this.o.format,this.o.language,this.o.assumeNearbyYear)},this)),c=a.grep(c,a.proxy(function(a){return!this.dateWithinRange(a)||!a},this),!0),this.dates.replace(c),this.dates.length?this.viewDate=new Date(this.dates.get(-1)):this.viewDatethis.o.endDate?this.viewDate=new Date(this.o.endDate):this.viewDate=this.o.defaultViewDate,d?(this.setValue(),this.element.change()):this.dates.length&&String(b)!==String(this.dates)&&d&&(this._trigger("changeDate"),this.element.change()),!this.dates.length&&b.length&&(this._trigger("clearDate"),this.element.change()),this.fill(),this},fillDow:function(){var b=this.o.weekStart,c="";for(this.o.calendarWeeks&&(c+=' ');b";c+="",this.picker.find(".datepicker-days thead").append(c)},fillMonths:function(){for(var a=this._utc_to_local(this.viewDate),b="",c=0;12>c;){var d=a&&a.getMonth()===c?" focused":"";b+=''+q[this.o.language].monthsShort[c++]+""}this.picker.find(".datepicker-months td").html(b)},setRange:function(b){b&&b.length?this.range=a.map(b,function(a){return a.valueOf()}):delete this.range,this.fill()},getClassNames:function(b){var c=[],f=this.viewDate.getUTCFullYear(),g=this.viewDate.getUTCMonth(),h=d();return b.getUTCFullYear()f||b.getUTCFullYear()===f&&b.getUTCMonth()>g)&&c.push("new"),this.focusDate&&b.valueOf()===this.focusDate.valueOf()&&c.push("focused"),this.o.todayHighlight&&e(b,h)&&c.push("today"),-1!==this.dates.contains(b)&&c.push("active"),this.dateWithinRange(b)||c.push("disabled"),this.dateIsDisabled(b)&&c.push("disabled","disabled-date"),-1!==a.inArray(b.getUTCDay(),this.o.daysOfWeekHighlighted)&&c.push("highlighted"),this.range&&(b>this.range[0]&&br;r+=1)s=[d],t=null,-1===r?s.push("old"):10===r&&s.push("new"),-1!==a.inArray(q,n)&&s.push("active"),(o>q||q>p)&&s.push("disabled"),q===this.viewDate.getFullYear()&&s.push("focused"),j!==a.noop&&(u=j(new Date(q,0,1)),u===b?u={}:"boolean"==typeof u?u={enabled:u}:"string"==typeof u&&(u={classes:u}),u.enabled===!1&&s.push("disabled"),u.classes&&(s=s.concat(u.classes.split(/\s+/))),u.tooltip&&(t=u.tooltip)),k+='"+q+"",q+=f;l.find("td").html(k)},fill:function(){var d,e,f=new Date(this.viewDate),g=f.getUTCFullYear(),h=f.getUTCMonth(),i=this.o.startDate!==-(1/0)?this.o.startDate.getUTCFullYear():-(1/0),j=this.o.startDate!==-(1/0)?this.o.startDate.getUTCMonth():-(1/0),k=this.o.endDate!==1/0?this.o.endDate.getUTCFullYear():1/0,l=this.o.endDate!==1/0?this.o.endDate.getUTCMonth():1/0,m=q[this.o.language].today||q.en.today||"",n=q[this.o.language].clear||q.en.clear||"",o=q[this.o.language].titleFormat||q.en.titleFormat;if(!isNaN(g)&&!isNaN(h)){this.picker.find(".datepicker-days .datepicker-switch").text(r.formatDate(f,o,this.o.language)),this.picker.find("tfoot .today").text(m).toggle(this.o.todayBtn!==!1),this.picker.find("tfoot .clear").text(n).toggle(this.o.clearBtn!==!1),this.picker.find("thead .datepicker-title").text(this.o.title).toggle(""!==this.o.title),this.updateNavArrows(),this.fillMonths();var p=c(g,h,0),s=p.getUTCDate();p.setUTCDate(s-(p.getUTCDay()-this.o.weekStart+7)%7);var t=new Date(p);p.getUTCFullYear()<100&&t.setUTCFullYear(p.getUTCFullYear()),t.setUTCDate(t.getUTCDate()+42),t=t.valueOf();for(var u,v,w=[];p.valueOf()"),this.o.calendarWeeks)){var x=new Date(+p+(this.o.weekStart-u-7)%7*864e5),y=new Date(Number(x)+(11-x.getUTCDay())%7*864e5),z=new Date(Number(z=c(y.getUTCFullYear(),0,1))+(11-z.getUTCDay())%7*864e5),A=(y-z)/864e5/7+1;w.push(''+A+"")}v=this.getClassNames(p),v.push("day"),this.o.beforeShowDay!==a.noop&&(e=this.o.beforeShowDay(this._utc_to_local(p)),e===b?e={}:"boolean"==typeof e?e={enabled:e}:"string"==typeof e&&(e={classes:e}),e.enabled===!1&&v.push("disabled"),e.classes&&(v=v.concat(e.classes.split(/\s+/))),e.tooltip&&(d=e.tooltip)),v=a.isFunction(a.uniqueSort)?a.uniqueSort(v):a.unique(v),w.push('"+p.getUTCDate()+""),d=null,u===this.o.weekEnd&&w.push(""),p.setUTCDate(p.getUTCDate()+1)}this.picker.find(".datepicker-days tbody").html(w.join(""));var B=q[this.o.language].monthsTitle||q.en.monthsTitle||"Months",C=this.picker.find(".datepicker-months").find(".datepicker-switch").text(this.o.maxViewMode<2?B:g).end().find("tbody span").removeClass("active");if(a.each(this.dates,function(a,b){b.getUTCFullYear()===g&&C.eq(b.getUTCMonth()).addClass("active")}),(i>g||g>k)&&C.addClass("disabled"),g===i&&C.slice(0,j).addClass("disabled"),g===k&&C.slice(l+1).addClass("disabled"),this.o.beforeShowMonth!==a.noop){var D=this;a.each(C,function(c,d){var e=new Date(g,c,1),f=D.o.beforeShowMonth(e);f===b?f={}:"boolean"==typeof f?f={enabled:f}:"string"==typeof f&&(f={classes:f}),f.enabled!==!1||a(d).hasClass("disabled")||a(d).addClass("disabled"),f.classes&&a(d).addClass(f.classes),f.tooltip&&a(d).prop("title",f.tooltip)})}this._fill_yearsView(".datepicker-years","year",10,1,g,i,k,this.o.beforeShowYear),this._fill_yearsView(".datepicker-decades","decade",100,10,g,i,k,this.o.beforeShowDecade),this._fill_yearsView(".datepicker-centuries","century",1e3,100,g,i,k,this.o.beforeShowCentury)}},updateNavArrows:function(){if(this._allow_update){var a,b,c=new Date(this.viewDate),d=c.getUTCFullYear(),e=c.getUTCMonth();switch(this.viewMode){case 0:a=this.o.startDate!==-(1/0)&&d<=this.o.startDate.getUTCFullYear()&&e<=this.o.startDate.getUTCMonth(),b=this.o.endDate!==1/0&&d>=this.o.endDate.getUTCFullYear()&&e>=this.o.endDate.getUTCMonth();break;case 1:case 2:case 3:case 4:a=this.o.startDate!==-(1/0)&&d<=this.o.startDate.getUTCFullYear(),b=this.o.endDate!==1/0&&d>=this.o.endDate.getUTCFullYear()}this.picker.find(".prev").toggleClass("disabled",a),this.picker.find(".next").toggleClass("disabled",b)}},click:function(b){b.preventDefault(),b.stopPropagation();var e,f,g,h,i;e=a(b.target),e.hasClass("datepicker-switch")&&this.viewMode!==this.o.maxViewMode&&this.setViewMode(this.viewMode+1),e.hasClass("today")&&!e.hasClass("day")&&(this.setViewMode(0),this._setDate(d(),"linked"===this.o.todayBtn?null:"view")),e.hasClass("clear")&&this.clearDates(),e.hasClass("disabled")||(e.hasClass("day")&&(g=Number(e.text()),h=this.viewDate.getUTCFullYear(),i=this.viewDate.getUTCMonth(),(e.hasClass("old")||e.hasClass("new"))&&(f=e.hasClass("old")?-1:1,i=(i+f+12)%12,(-1===f&&11===i||1===f&&0===i)&&(h+=f,this._trigger("changeYear",this.viewDate)),this._trigger("changeMonth",this.viewDate)),this._setDate(c(h,i,g))),(e.hasClass("month")||e.hasClass("year")||e.hasClass("decade")||e.hasClass("century"))&&(this.viewDate.setUTCDate(1),g=1,1===this.viewMode?(i=e.parent().find("span").index(e),h=this.viewDate.getUTCFullYear(),this.viewDate.setUTCMonth(i)):(i=0,h=Number(e.text()),this.viewDate.setUTCFullYear(h)),this._trigger(r.viewModes[this.viewMode-1].e,this.viewDate),this.viewMode===this.o.minViewMode?this._setDate(c(h,i,g)):(this.setViewMode(this.viewMode-1),this.fill()))),this.picker.is(":visible")&&this._focused_from&&this._focused_from.focus(),delete this._focused_from},navArrowsClick:function(b){var c=a(b.target),d=c.hasClass("prev")?-1:1;0!==this.viewMode&&(d*=12*r.viewModes[this.viewMode].navStep),this.viewDate=this.moveMonth(this.viewDate,d),this._trigger(r.viewModes[this.viewMode].e,this.viewDate),this.fill()},_toggle_multidate:function(a){var b=this.dates.contains(a);if(a||this.dates.clear(),-1!==b?(this.o.multidate===!0||this.o.multidate>1||this.o.toggleActive)&&this.dates.remove(b):this.o.multidate===!1?(this.dates.clear(),this.dates.push(a)):this.dates.push(a),"number"==typeof this.o.multidate)for(;this.dates.length>this.o.multidate;)this.dates.remove(0)},_setDate:function(a,b){b&&"date"!==b||this._toggle_multidate(a&&new Date(a)),b&&"view"!==b||(this.viewDate=a&&new Date(a)),this.fill(),this.setValue(),b&&"view"===b||this._trigger("changeDate"),this.inputField.trigger("change"),!this.o.autoclose||b&&"date"!==b||this.hide()},moveDay:function(a,b){var c=new Date(a);return c.setUTCDate(a.getUTCDate()+b),c},moveWeek:function(a,b){return this.moveDay(a,7*b)},moveMonth:function(a,b){if(!g(a))return this.o.defaultViewDate;if(!b)return a;var c,d,e=new Date(a.valueOf()),f=e.getUTCDate(),h=e.getUTCMonth(),i=Math.abs(b);if(b=b>0?1:-1,1===i)d=-1===b?function(){return e.getUTCMonth()===h}:function(){return e.getUTCMonth()!==c},c=h+b,e.setUTCMonth(c),c=(c+12)%12;else{for(var j=0;i>j;j++)e=this.moveMonth(e,b);c=e.getUTCMonth(),e.setUTCDate(f),d=function(){return c!==e.getUTCMonth()}}for(;d();)e.setUTCDate(--f),e.setUTCMonth(c);return e},moveYear:function(a,b){return this.moveMonth(a,12*b)},moveAvailableDate:function(a,b,c){do{if(a=this[c](a,b),!this.dateWithinRange(a))return!1;c="moveDay"}while(this.dateIsDisabled(a));return a},weekOfDateIsDisabled:function(b){return-1!==a.inArray(b.getUTCDay(),this.o.daysOfWeekDisabled)},dateIsDisabled:function(b){return this.weekOfDateIsDisabled(b)||a.grep(this.o.datesDisabled,function(a){return e(b,a)}).length>0},dateWithinRange:function(a){return a>=this.o.startDate&&a<=this.o.endDate},keydown:function(a){if(!this.picker.is(":visible"))return void((40===a.keyCode||27===a.keyCode)&&(this.show(),a.stopPropagation()));var b,c,d=!1,e=this.focusDate||this.viewDate;switch(a.keyCode){case 27:this.focusDate?(this.focusDate=null,this.viewDate=this.dates.get(-1)||this.viewDate,this.fill()):this.hide(),a.preventDefault(),a.stopPropagation();break;case 37:case 38:case 39:case 40:if(!this.o.keyboardNavigation||7===this.o.daysOfWeekDisabled.length)break;b=37===a.keyCode||38===a.keyCode?-1:1,0===this.viewMode?a.ctrlKey?(c=this.moveAvailableDate(e,b,"moveYear"),c&&this._trigger("changeYear",this.viewDate)):a.shiftKey?(c=this.moveAvailableDate(e,b,"moveMonth"),c&&this._trigger("changeMonth",this.viewDate)):37===a.keyCode||39===a.keyCode?c=this.moveAvailableDate(e,b,"moveDay"):this.weekOfDateIsDisabled(e)||(c=this.moveAvailableDate(e,b,"moveWeek")):1===this.viewMode?((38===a.keyCode||40===a.keyCode)&&(b=4*b),c=this.moveAvailableDate(e,b,"moveMonth")):2===this.viewMode&&((38===a.keyCode||40===a.keyCode)&&(b=4*b),c=this.moveAvailableDate(e,b,"moveYear")),c&&(this.focusDate=this.viewDate=c,this.setValue(),this.fill(),a.preventDefault());break;case 13:if(!this.o.forceParse)break;e=this.focusDate||this.dates.get(-1)||this.viewDate,this.o.keyboardNavigation&&(this._toggle_multidate(e),d=!0),this.focusDate=null,this.viewDate=this.dates.get(-1)||this.viewDate,this.setValue(),this.fill(),this.picker.is(":visible")&&(a.preventDefault(),a.stopPropagation(),this.o.autoclose&&this.hide());break;case 9:this.focusDate=null,this.viewDate=this.dates.get(-1)||this.viewDate,this.fill(),this.hide()}d&&(this.dates.length?this._trigger("changeDate"):this._trigger("clearDate"),this.inputField.trigger("change"))},setViewMode:function(a){this.viewMode=a,this.picker.children("div").hide().filter(".datepicker-"+r.viewModes[this.viewMode].clsName).show(),this.updateNavArrows(),this._trigger("changeViewMode",new Date(this.viewDate))}};var l=function(b,c){a.data(b,"datepicker",this),this.element=a(b),this.inputs=a.map(c.inputs,function(a){return a.jquery?a[0]:a}),delete c.inputs,this.keepEmptyValues=c.keepEmptyValues,delete c.keepEmptyValues,n.call(a(this.inputs),c).on("changeDate",a.proxy(this.dateUpdated,this)),this.pickers=a.map(this.inputs,function(b){return a.data(b,"datepicker")}),this.updateDates()};l.prototype={updateDates:function(){this.dates=a.map(this.pickers,function(a){return a.getUTCDate()}),this.updateRanges()},updateRanges:function(){var b=a.map(this.dates,function(a){return a.valueOf()});a.each(this.pickers,function(a,c){c.setRange(b)})},dateUpdated:function(c){if(!this.updating){this.updating=!0;var d=a.data(c.target,"datepicker");if(d!==b){var e=d.getUTCDate(),f=this.keepEmptyValues,g=a.inArray(c.target,this.inputs),h=g-1,i=g+1,j=this.inputs.length;if(-1!==g){if(a.each(this.pickers,function(a,b){b.getUTCDate()||b!==d&&f||b.setUTCDate(e)}),e=0&&ethis.dates[i])for(;j>i&&e>this.dates[i];)this.pickers[i++].setUTCDate(e);this.updateDates(),delete this.updating}}}},destroy:function(){a.map(this.pickers,function(a){a.destroy()}),a(this.inputs).off("changeDate",this.dateUpdated),delete this.element.data().datepicker},remove:f("destroy")};var m=a.fn.datepicker,n=function(c){var d=Array.apply(null,arguments);d.shift();var e;if(this.each(function(){var b=a(this),f=b.data("datepicker"),g="object"==typeof c&&c;if(!f){var j=h(this,"date"),m=a.extend({},o,j,g),n=i(m.language),p=a.extend({},o,n,j,g);b.hasClass("input-daterange")||p.inputs?(a.extend(p,{inputs:p.inputs||b.find("input").toArray()}),f=new l(this,p)):f=new k(this,p),b.data("datepicker",f)}"string"==typeof c&&"function"==typeof f[c]&&(e=f[c].apply(f,d))}),e===b||e instanceof k||e instanceof l)return this;if(this.length>1)throw new Error("Using only allowed for the collection of a single element ("+c+" function)");return e};a.fn.datepicker=n;var o=a.fn.datepicker.defaults={assumeNearbyYear:!1,autoclose:!1,beforeShowDay:a.noop,beforeShowMonth:a.noop,beforeShowYear:a.noop,beforeShowDecade:a.noop,beforeShowCentury:a.noop,calendarWeeks:!1,clearBtn:!1,toggleActive:!1,daysOfWeekDisabled:[],daysOfWeekHighlighted:[],datesDisabled:[],endDate:1/0,forceParse:!0,format:"mm/dd/yyyy",keepEmptyValues:!1,keyboardNavigation:!0,language:"en",minViewMode:0,maxViewMode:4,multidate:!1,multidateSeparator:",",orientation:"auto",rtl:!1,startDate:-(1/0),startView:0,todayBtn:!1,todayHighlight:!1,weekStart:0,disableTouchKeyboard:!1,enableOnReadonly:!0,showOnFocus:!0,zIndexOffset:10,container:"body",immediateUpdates:!1,dateCells:!1,title:"",templates:{leftArrow:"«",rightArrow:"»"}},p=a.fn.datepicker.locale_opts=["format","rtl","weekStart"];a.fn.datepicker.Constructor=k;var q=a.fn.datepicker.dates={en:{days:["Sunday","Monday","Tuesday","Wednesday","Thursday","Friday","Saturday"],daysShort:["Sun","Mon","Tue","Wed","Thu","Fri","Sat"],daysMin:["Su","Mo","Tu","We","Th","Fr","Sa"],months:["January","February","March","April","May","June","July","August","September","October","November","December"],monthsShort:["Jan","Feb","Mar","Apr","May","Jun","Jul","Aug","Sep","Oct","Nov","Dec"],today:"Today",clear:"Clear",titleFormat:"MM yyyy"}},r={viewModes:[{names:["days","month"],clsName:"days",e:"changeMonth"},{names:["months","year"],clsName:"months",e:"changeYear",navStep:1},{names:["years","decade"],clsName:"years",e:"changeDecade",navStep:10},{names:["decades","century"],clsName:"decades",e:"changeCentury",navStep:100},{names:["centuries","millennium"],clsName:"centuries",e:"changeMillennium",navStep:1e3}],validParts:/dd?|DD?|mm?|MM?|yy(?:yy)?/g,nonpunctuation:/[^ -\/:-@\u5e74\u6708\u65e5\[-`{-~\t\n\r]+/g,parseFormat:function(a){if("function"==typeof a.toValue&&"function"==typeof a.toDisplay)return a;var b=a.replace(this.validParts,"\x00").split("\x00"),c=a.match(this.validParts);if(!b||!b.length||!c||0===c.length)throw new Error("Invalid date format.");return{separators:b,parts:c}},parseDate:function(e,f,g,h){function i(a,b){return b===!0&&(b=10),100>a&&(a+=2e3,a>(new Date).getFullYear()+b&&(a-=100)),a}function j(){var a=this.slice(0,l[o].length),b=l[o].slice(0,a.length);return a.toLowerCase()===b.toLowerCase()}if(!e)return b;if(e instanceof Date)return e;if("string"==typeof f&&(f=r.parseFormat(f)),f.toValue)return f.toValue(e,f,g);var l,m,n,o,p,s={d:"moveDay",m:"moveMonth",w:"moveWeek",y:"moveYear"},t={yesterday:"-1d",today:"+0d",tomorrow:"+1d"};if(e in t&&(e=t[e]),/^[\-+]\d+[dmwy]([\s,]+[\-+]\d+[dmwy])*$/i.test(e)){for(l=e.match(/([\-+]\d+)([dmwy])/gi),e=new Date,o=0;ob;)b+=12;for(b%=12,a.setUTCMonth(b);a.getUTCMonth()!==b;)a.setUTCDate(a.getUTCDate()-1);return a},d:function(a,b){return a.setUTCDate(b)}};y.yy=y.yyyy,y.M=y.MM=y.mm=y.m,y.dd=y.d,e=d();var z=f.parts.slice();if(l.length!==z.length&&(z=a(z).filter(function(b,c){return-1!==a.inArray(c,x)}).toArray()),l.length===z.length){var A;for(o=0,A=z.length;A>o;o++){if(u=parseInt(l[o],10),m=z[o],isNaN(u))switch(m){case"MM":v=a(q[g].months).filter(j),u=a.inArray(v[0],q[g].months)+1;break;case"M":v=a(q[g].monthsShort).filter(j),u=a.inArray(v[0],q[g].monthsShort)+1}w[m]=u}var B,C;for(o=0;o=g;g++)f.length&&b.push(f.shift()),b.push(e[c.parts[g]]);return b.join("")},headTemplate:'«»',contTemplate:'',footTemplate:''};r.template='
'+r.headTemplate+""+r.footTemplate+'
'+r.headTemplate+r.contTemplate+r.footTemplate+'
'+r.headTemplate+r.contTemplate+r.footTemplate+'
'+r.headTemplate+r.contTemplate+r.footTemplate+'
'+r.headTemplate+r.contTemplate+r.footTemplate+"
", +a.fn.datepicker.DPGlobal=r,a.fn.datepicker.noConflict=function(){return a.fn.datepicker=m,this},a.fn.datepicker.version="1.7.0-dev",a(document).on("focus.datepicker.data-api click.datepicker.data-api",'[data-provide="datepicker"]',function(b){var c=a(this);c.data("datepicker")||(b.preventDefault(),n.call(c,"show"))}),a(function(){n.call(a('[data-provide="datepicker-inline"]'))})}); \ No newline at end of file diff --git a/web/ia7/include/bootstrap-datepicker3.standalone.min.css b/web/ia7/include/bootstrap-datepicker3.standalone.min.css new file mode 100755 index 000000000..c0870cddb --- /dev/null +++ b/web/ia7/include/bootstrap-datepicker3.standalone.min.css @@ -0,0 +1,9 @@ +/*! + * Datepicker for Bootstrap v1.7.0-dev (https://github.com/eternicode/bootstrap-datepicker) + * + * Copyright 2012 Stefan Petre + * Improvements by Andrew Rowls + * Licensed under the Apache License v2.0 (http://www.apache.org/licenses/LICENSE-2.0) + */ + +.datepicker{border-radius:4px;direction:ltr}.datepicker-inline{width:220px}.datepicker.datepicker-rtl{direction:rtl}.datepicker.datepicker-rtl.dropdown-menu{left:auto}.datepicker.datepicker-rtl table tr td span{float:right}.datepicker-dropdown{top:0;left:0;padding:4px}.datepicker-dropdown:before{content:'';display:inline-block;border-left:7px solid transparent;border-right:7px solid transparent;border-bottom:7px solid rgba(0,0,0,.15);border-top:0;border-bottom-color:rgba(0,0,0,.2);position:absolute}.datepicker-dropdown:after{content:'';display:inline-block;border-left:6px solid transparent;border-right:6px solid transparent;border-bottom:6px solid #fff;border-top:0;position:absolute}.datepicker-dropdown.datepicker-orient-left:before{left:6px}.datepicker-dropdown.datepicker-orient-left:after{left:7px}.datepicker-dropdown.datepicker-orient-right:before{right:6px}.datepicker-dropdown.datepicker-orient-right:after{right:7px}.datepicker-dropdown.datepicker-orient-bottom:before{top:-7px}.datepicker-dropdown.datepicker-orient-bottom:after{top:-6px}.datepicker-dropdown.datepicker-orient-top:before{bottom:-7px;border-bottom:0;border-top:7px solid rgba(0,0,0,.15)}.datepicker-dropdown.datepicker-orient-top:after{bottom:-6px;border-bottom:0;border-top:6px solid #fff}.datepicker table{margin:0;-webkit-touch-callout:none;-webkit-user-select:none;-khtml-user-select:none;-moz-user-select:none;-ms-user-select:none;user-select:none}.datepicker table tr td,.datepicker table tr th{text-align:center;width:30px;height:30px;border-radius:4px;border:none}.table-striped .datepicker table tr td,.table-striped .datepicker table tr th{background-color:transparent}.datepicker table tr td.new,.datepicker table tr td.old{color:#777}.datepicker table tr td.day:hover,.datepicker table tr td.focused{background:#eee;cursor:pointer}.datepicker table tr td.disabled,.datepicker table tr td.disabled:hover{background:0 0;color:#777;cursor:default}.datepicker table tr td.highlighted{color:#000;background-color:#d9edf7;border-color:#85c5e5;border-radius:0}.datepicker table tr td.highlighted.focus,.datepicker table tr td.highlighted:focus{color:#000;background-color:#afd9ee;border-color:#298fc2}.datepicker table tr td.highlighted:hover{color:#000;background-color:#afd9ee;border-color:#52addb}.datepicker table tr td.highlighted.active,.datepicker table tr td.highlighted:active{color:#000;background-color:#afd9ee;border-color:#52addb}.datepicker table tr td.highlighted.active.focus,.datepicker table tr td.highlighted.active:focus,.datepicker table tr td.highlighted.active:hover,.datepicker table tr td.highlighted:active.focus,.datepicker table tr td.highlighted:active:focus,.datepicker table tr td.highlighted:active:hover{color:#000;background-color:#91cbe8;border-color:#298fc2}.datepicker table tr td.highlighted.disabled.focus,.datepicker table tr td.highlighted.disabled:focus,.datepicker table tr td.highlighted.disabled:hover,.datepicker table tr td.highlighted[disabled].focus,.datepicker table tr td.highlighted[disabled]:focus,.datepicker table tr td.highlighted[disabled]:hover,fieldset[disabled] .datepicker table tr td.highlighted.focus,fieldset[disabled] .datepicker table tr td.highlighted:focus,fieldset[disabled] .datepicker table tr td.highlighted:hover{background-color:#d9edf7;border-color:#85c5e5}.datepicker table tr td.highlighted.focused{background:#afd9ee}.datepicker table tr td.highlighted.disabled,.datepicker table tr td.highlighted.disabled:active{background:#d9edf7;color:#777}.datepicker table tr td.today{color:#000;background-color:#ffdb99;border-color:#ffb733}.datepicker table tr td.today.focus,.datepicker table tr td.today:focus{color:#000;background-color:#ffc966;border-color:#b37400}.datepicker table tr td.today:hover{color:#000;background-color:#ffc966;border-color:#f59e00}.datepicker table tr td.today.active,.datepicker table tr td.today:active{color:#000;background-color:#ffc966;border-color:#f59e00}.datepicker table tr td.today.active.focus,.datepicker table tr td.today.active:focus,.datepicker table tr td.today.active:hover,.datepicker table tr td.today:active.focus,.datepicker table tr td.today:active:focus,.datepicker table tr td.today:active:hover{color:#000;background-color:#ffbc42;border-color:#b37400}.datepicker table tr td.today.disabled.focus,.datepicker table tr td.today.disabled:focus,.datepicker table tr td.today.disabled:hover,.datepicker table tr td.today[disabled].focus,.datepicker table tr td.today[disabled]:focus,.datepicker table tr td.today[disabled]:hover,fieldset[disabled] .datepicker table tr td.today.focus,fieldset[disabled] .datepicker table tr td.today:focus,fieldset[disabled] .datepicker table tr td.today:hover{background-color:#ffdb99;border-color:#ffb733}.datepicker table tr td.today.focused{background:#ffc966}.datepicker table tr td.today.disabled,.datepicker table tr td.today.disabled:active{background:#ffdb99;color:#777}.datepicker table tr td.range{color:#000;background-color:#eee;border-color:#bbb;border-radius:0}.datepicker table tr td.range.focus,.datepicker table tr td.range:focus{color:#000;background-color:#d5d5d5;border-color:#7c7c7c}.datepicker table tr td.range:hover{color:#000;background-color:#d5d5d5;border-color:#9d9d9d}.datepicker table tr td.range.active,.datepicker table tr td.range:active{color:#000;background-color:#d5d5d5;border-color:#9d9d9d}.datepicker table tr td.range.active.focus,.datepicker table tr td.range.active:focus,.datepicker table tr td.range.active:hover,.datepicker table tr td.range:active.focus,.datepicker table tr td.range:active:focus,.datepicker table tr td.range:active:hover{color:#000;background-color:#c3c3c3;border-color:#7c7c7c}.datepicker table tr td.range.disabled.focus,.datepicker table tr td.range.disabled:focus,.datepicker table tr td.range.disabled:hover,.datepicker table tr td.range[disabled].focus,.datepicker table tr td.range[disabled]:focus,.datepicker table tr td.range[disabled]:hover,fieldset[disabled] .datepicker table tr td.range.focus,fieldset[disabled] .datepicker table tr td.range:focus,fieldset[disabled] .datepicker table tr td.range:hover{background-color:#eee;border-color:#bbb}.datepicker table tr td.range.focused{background:#d5d5d5}.datepicker table tr td.range.disabled,.datepicker table tr td.range.disabled:active{background:#eee;color:#777}.datepicker table tr td.range.highlighted{color:#000;background-color:#e4eef3;border-color:#9dc1d3}.datepicker table tr td.range.highlighted.focus,.datepicker table tr td.range.highlighted:focus{color:#000;background-color:#c1d7e3;border-color:#4b88a6}.datepicker table tr td.range.highlighted:hover{color:#000;background-color:#c1d7e3;border-color:#73a6c0}.datepicker table tr td.range.highlighted.active,.datepicker table tr td.range.highlighted:active{color:#000;background-color:#c1d7e3;border-color:#73a6c0}.datepicker table tr td.range.highlighted.active.focus,.datepicker table tr td.range.highlighted.active:focus,.datepicker table tr td.range.highlighted.active:hover,.datepicker table tr td.range.highlighted:active.focus,.datepicker table tr td.range.highlighted:active:focus,.datepicker table tr td.range.highlighted:active:hover{color:#000;background-color:#a8c8d8;border-color:#4b88a6}.datepicker table tr td.range.highlighted.disabled.focus,.datepicker table tr td.range.highlighted.disabled:focus,.datepicker table tr td.range.highlighted.disabled:hover,.datepicker table tr td.range.highlighted[disabled].focus,.datepicker table tr td.range.highlighted[disabled]:focus,.datepicker table tr td.range.highlighted[disabled]:hover,fieldset[disabled] .datepicker table tr td.range.highlighted.focus,fieldset[disabled] .datepicker table tr td.range.highlighted:focus,fieldset[disabled] .datepicker table tr td.range.highlighted:hover{background-color:#e4eef3;border-color:#9dc1d3}.datepicker table tr td.range.highlighted.focused{background:#c1d7e3}.datepicker table tr td.range.highlighted.disabled,.datepicker table tr td.range.highlighted.disabled:active{background:#e4eef3;color:#777}.datepicker table tr td.range.today{color:#000;background-color:#f7ca77;border-color:#f1a417}.datepicker table tr td.range.today.focus,.datepicker table tr td.range.today:focus{color:#000;background-color:#f4b747;border-color:#815608}.datepicker table tr td.range.today:hover{color:#000;background-color:#f4b747;border-color:#bf800c}.datepicker table tr td.range.today.active,.datepicker table tr td.range.today:active{color:#000;background-color:#f4b747;border-color:#bf800c}.datepicker table tr td.range.today.active.focus,.datepicker table tr td.range.today.active:focus,.datepicker table tr td.range.today.active:hover,.datepicker table tr td.range.today:active.focus,.datepicker table tr td.range.today:active:focus,.datepicker table tr td.range.today:active:hover{color:#000;background-color:#f2aa25;border-color:#815608}.datepicker table tr td.range.today.disabled.focus,.datepicker table tr td.range.today.disabled:focus,.datepicker table tr td.range.today.disabled:hover,.datepicker table tr td.range.today[disabled].focus,.datepicker table tr td.range.today[disabled]:focus,.datepicker table tr td.range.today[disabled]:hover,fieldset[disabled] .datepicker table tr td.range.today.focus,fieldset[disabled] .datepicker table tr td.range.today:focus,fieldset[disabled] .datepicker table tr td.range.today:hover{background-color:#f7ca77;border-color:#f1a417}.datepicker table tr td.range.today.disabled,.datepicker table tr td.range.today.disabled:active{background:#f7ca77;color:#777}.datepicker table tr td.selected,.datepicker table tr td.selected.highlighted{color:#fff;background-color:#777;border-color:#555;text-shadow:0 -1px 0 rgba(0,0,0,.25)}.datepicker table tr td.selected.focus,.datepicker table tr td.selected.highlighted.focus,.datepicker table tr td.selected.highlighted:focus,.datepicker table tr td.selected:focus{color:#fff;background-color:#5e5e5e;border-color:#161616}.datepicker table tr td.selected.highlighted:hover,.datepicker table tr td.selected:hover{color:#fff;background-color:#5e5e5e;border-color:#373737}.datepicker table tr td.selected.active,.datepicker table tr td.selected.highlighted.active,.datepicker table tr td.selected.highlighted:active,.datepicker table tr td.selected:active{color:#fff;background-color:#5e5e5e;border-color:#373737}.datepicker table tr td.selected.active.focus,.datepicker table tr td.selected.active:focus,.datepicker table tr td.selected.active:hover,.datepicker table tr td.selected.highlighted.active.focus,.datepicker table tr td.selected.highlighted.active:focus,.datepicker table tr td.selected.highlighted.active:hover,.datepicker table tr td.selected.highlighted:active.focus,.datepicker table tr td.selected.highlighted:active:focus,.datepicker table tr td.selected.highlighted:active:hover,.datepicker table tr td.selected:active.focus,.datepicker table tr td.selected:active:focus,.datepicker table tr td.selected:active:hover{color:#fff;background-color:#4c4c4c;border-color:#161616}.datepicker table tr td.selected.disabled.focus,.datepicker table tr td.selected.disabled:focus,.datepicker table tr td.selected.disabled:hover,.datepicker table tr td.selected.highlighted.disabled.focus,.datepicker table tr td.selected.highlighted.disabled:focus,.datepicker table tr td.selected.highlighted.disabled:hover,.datepicker table tr td.selected.highlighted[disabled].focus,.datepicker table tr td.selected.highlighted[disabled]:focus,.datepicker table tr td.selected.highlighted[disabled]:hover,.datepicker table tr td.selected[disabled].focus,.datepicker table tr td.selected[disabled]:focus,.datepicker table tr td.selected[disabled]:hover,fieldset[disabled] .datepicker table tr td.selected.focus,fieldset[disabled] .datepicker table tr td.selected.highlighted.focus,fieldset[disabled] .datepicker table tr td.selected.highlighted:focus,fieldset[disabled] .datepicker table tr td.selected.highlighted:hover,fieldset[disabled] .datepicker table tr td.selected:focus,fieldset[disabled] .datepicker table tr td.selected:hover{background-color:#777;border-color:#555}.datepicker table tr td.active,.datepicker table tr td.active.highlighted{color:#fff;background-color:#337ab7;border-color:#2e6da4;text-shadow:0 -1px 0 rgba(0,0,0,.25)}.datepicker table tr td.active.focus,.datepicker table tr td.active.highlighted.focus,.datepicker table tr td.active.highlighted:focus,.datepicker table tr td.active:focus{color:#fff;background-color:#286090;border-color:#122b40}.datepicker table tr td.active.highlighted:hover,.datepicker table tr td.active:hover{color:#fff;background-color:#286090;border-color:#204d74}.datepicker table tr td.active.active,.datepicker table tr td.active.highlighted.active,.datepicker table tr td.active.highlighted:active,.datepicker table tr td.active:active{color:#fff;background-color:#286090;border-color:#204d74}.datepicker table tr td.active.active.focus,.datepicker table tr td.active.active:focus,.datepicker table tr td.active.active:hover,.datepicker table tr td.active.highlighted.active.focus,.datepicker table tr td.active.highlighted.active:focus,.datepicker table tr td.active.highlighted.active:hover,.datepicker table tr td.active.highlighted:active.focus,.datepicker table tr td.active.highlighted:active:focus,.datepicker table tr td.active.highlighted:active:hover,.datepicker table tr td.active:active.focus,.datepicker table tr td.active:active:focus,.datepicker table tr td.active:active:hover{color:#fff;background-color:#204d74;border-color:#122b40}.datepicker table tr td.active.disabled.focus,.datepicker table tr td.active.disabled:focus,.datepicker table tr td.active.disabled:hover,.datepicker table tr td.active.highlighted.disabled.focus,.datepicker table tr td.active.highlighted.disabled:focus,.datepicker table tr td.active.highlighted.disabled:hover,.datepicker table tr td.active.highlighted[disabled].focus,.datepicker table tr td.active.highlighted[disabled]:focus,.datepicker table tr td.active.highlighted[disabled]:hover,.datepicker table tr td.active[disabled].focus,.datepicker table tr td.active[disabled]:focus,.datepicker table tr td.active[disabled]:hover,fieldset[disabled] .datepicker table tr td.active.focus,fieldset[disabled] .datepicker table tr td.active.highlighted.focus,fieldset[disabled] .datepicker table tr td.active.highlighted:focus,fieldset[disabled] .datepicker table tr td.active.highlighted:hover,fieldset[disabled] .datepicker table tr td.active:focus,fieldset[disabled] .datepicker table tr td.active:hover{background-color:#337ab7;border-color:#2e6da4}.datepicker table tr td span{display:block;width:23%;height:54px;line-height:54px;float:left;margin:1%;cursor:pointer;border-radius:4px}.datepicker table tr td span.focused,.datepicker table tr td span:hover{background:#eee}.datepicker table tr td span.disabled,.datepicker table tr td span.disabled:hover{background:0 0;color:#777;cursor:default}.datepicker table tr td span.active,.datepicker table tr td span.active.disabled,.datepicker table tr td span.active.disabled:hover,.datepicker table tr td span.active:hover{color:#fff;background-color:#337ab7;border-color:#2e6da4;text-shadow:0 -1px 0 rgba(0,0,0,.25)}.datepicker table tr td span.active.disabled.focus,.datepicker table tr td span.active.disabled:focus,.datepicker table tr td span.active.disabled:hover.focus,.datepicker table tr td span.active.disabled:hover:focus,.datepicker table tr td span.active.focus,.datepicker table tr td span.active:focus,.datepicker table tr td span.active:hover.focus,.datepicker table tr td span.active:hover:focus{color:#fff;background-color:#286090;border-color:#122b40}.datepicker table tr td span.active.disabled:hover,.datepicker table tr td span.active.disabled:hover:hover,.datepicker table tr td span.active:hover,.datepicker table tr td span.active:hover:hover{color:#fff;background-color:#286090;border-color:#204d74}.datepicker table tr td span.active.active,.datepicker table tr td span.active.disabled.active,.datepicker table tr td span.active.disabled:active,.datepicker table tr td span.active.disabled:hover.active,.datepicker table tr td span.active.disabled:hover:active,.datepicker table tr td span.active:active,.datepicker table tr td span.active:hover.active,.datepicker table tr td span.active:hover:active{color:#fff;background-color:#286090;border-color:#204d74}.datepicker table tr td span.active.active.focus,.datepicker table tr td span.active.active:focus,.datepicker table tr td span.active.active:hover,.datepicker table tr td span.active.disabled.active.focus,.datepicker table tr td span.active.disabled.active:focus,.datepicker table tr td span.active.disabled.active:hover,.datepicker table tr td span.active.disabled:active.focus,.datepicker table tr td span.active.disabled:active:focus,.datepicker table tr td span.active.disabled:active:hover,.datepicker table tr td span.active.disabled:hover.active.focus,.datepicker table tr td span.active.disabled:hover.active:focus,.datepicker table tr td span.active.disabled:hover.active:hover,.datepicker table tr td span.active.disabled:hover:active.focus,.datepicker table tr td span.active.disabled:hover:active:focus,.datepicker table tr td span.active.disabled:hover:active:hover,.datepicker table tr td span.active:active.focus,.datepicker table tr td span.active:active:focus,.datepicker table tr td span.active:active:hover,.datepicker table tr td span.active:hover.active.focus,.datepicker table tr td span.active:hover.active:focus,.datepicker table tr td span.active:hover.active:hover,.datepicker table tr td span.active:hover:active.focus,.datepicker table tr td span.active:hover:active:focus,.datepicker table tr td span.active:hover:active:hover{color:#fff;background-color:#204d74;border-color:#122b40}.datepicker table tr td span.active.disabled.disabled.focus,.datepicker table tr td span.active.disabled.disabled:focus,.datepicker table tr td span.active.disabled.disabled:hover,.datepicker table tr td span.active.disabled.focus,.datepicker table tr td span.active.disabled:focus,.datepicker table tr td span.active.disabled:hover,.datepicker table tr td span.active.disabled:hover.disabled.focus,.datepicker table tr td span.active.disabled:hover.disabled:focus,.datepicker table tr td span.active.disabled:hover.disabled:hover,.datepicker table tr td span.active.disabled:hover[disabled].focus,.datepicker table tr td span.active.disabled:hover[disabled]:focus,.datepicker table tr td span.active.disabled:hover[disabled]:hover,.datepicker table tr td span.active.disabled[disabled].focus,.datepicker table tr td span.active.disabled[disabled]:focus,.datepicker table tr td span.active.disabled[disabled]:hover,.datepicker table tr td span.active:hover.disabled.focus,.datepicker table tr td span.active:hover.disabled:focus,.datepicker table tr td span.active:hover.disabled:hover,.datepicker table tr td span.active:hover[disabled].focus,.datepicker table tr td span.active:hover[disabled]:focus,.datepicker table tr td span.active:hover[disabled]:hover,.datepicker table tr td span.active[disabled].focus,.datepicker table tr td span.active[disabled]:focus,.datepicker table tr td span.active[disabled]:hover,fieldset[disabled] .datepicker table tr td span.active.disabled.focus,fieldset[disabled] .datepicker table tr td span.active.disabled:focus,fieldset[disabled] .datepicker table tr td span.active.disabled:hover,fieldset[disabled] .datepicker table tr td span.active.disabled:hover.focus,fieldset[disabled] .datepicker table tr td span.active.disabled:hover:focus,fieldset[disabled] .datepicker table tr td span.active.disabled:hover:hover,fieldset[disabled] .datepicker table tr td span.active.focus,fieldset[disabled] .datepicker table tr td span.active:focus,fieldset[disabled] .datepicker table tr td span.active:hover,fieldset[disabled] .datepicker table tr td span.active:hover.focus,fieldset[disabled] .datepicker table tr td span.active:hover:focus,fieldset[disabled] .datepicker table tr td span.active:hover:hover{background-color:#337ab7;border-color:#2e6da4}.datepicker table tr td span.new,.datepicker table tr td span.old{color:#777}.datepicker .datepicker-switch{width:145px}.datepicker .datepicker-switch,.datepicker .next,.datepicker .prev,.datepicker tfoot tr th{cursor:pointer}.datepicker .datepicker-switch:hover,.datepicker .next:hover,.datepicker .prev:hover,.datepicker tfoot tr th:hover{background:#eee}.datepicker .next.disabled,.datepicker .prev.disabled{visibility:hidden}.datepicker .cw{font-size:10px;width:12px;padding:0 2px 0 5px;vertical-align:middle}.input-group.date .input-group-addon{cursor:pointer}.input-daterange{width:100%}.input-daterange input{text-align:center}.input-daterange input:first-child{border-radius:3px 0 0 3px}.input-daterange input:last-child{border-radius:0 3px 3px 0}.input-daterange .input-group-addon{width:auto;min-width:16px;padding:4px 5px;line-height:1.42857143;text-shadow:0 1px 0 #fff;border-width:1px 0;margin-left:-5px;margin-right:-5px}.datepicker.dropdown-menu{position:absolute;top:100%;left:0;z-index:1000;display:none;float:left;min-width:160px;list-style:none;background-color:#fff;border:1px solid #ccc;border:1px solid rgba(0,0,0,.15);border-radius:4px;-webkit-box-shadow:0 6px 12px rgba(0,0,0,.175);-moz-box-shadow:0 6px 12px rgba(0,0,0,.175);box-shadow:0 6px 12px rgba(0,0,0,.175);-webkit-background-clip:padding-box;-moz-background-clip:padding;background-clip:padding-box;color:#333;font-size:13px;line-height:1.42857143}.datepicker.datepicker-inline td,.datepicker.datepicker-inline th,.datepicker.dropdown-menu td,.datepicker.dropdown-menu th{padding:0 5px} \ No newline at end of file diff --git a/web/ia7/include/javascript.js b/web/ia7/include/javascript.js index f94874e7a..c06d61b55 100644 --- a/web/ia7/include/javascript.js +++ b/web/ia7/include/javascript.js @@ -1,4 +1,4 @@ -// v1.2 +// v1.3 var entity_store = {}; //global storage of entities var json_store = {}; @@ -238,7 +238,11 @@ function changePage (){ else if(path.indexOf('rrd') === 0){ var path_arg = path.split('?'); graph_rrd(path_arg[1],path_arg[2]); - } + } + else if(path.indexOf('history') === 0){ + var path_arg = path.split('?'); + object_history(path_arg[1],undefined,path_arg[2]); + } else if(URLHash._request == 'trigger'){ trigger(); } @@ -247,6 +251,7 @@ function changePage (){ } //update the breadcrumb: // Weird end-case, The Group from browse items is broken with parents on the URL + // Also have to change parents to type if the ending collection_keys are $, $('#nav').html(''); var collection_keys_arr = URLHash._collection_key; if (collection_keys_arr === undefined) collection_keys_arr = '0'; @@ -259,7 +264,8 @@ function changePage (){ //group objects can be browsed recursively. Possibly use different //prefix if other recursively browsable formats are later added nav_name = collection_keys_arr[i].replace("$", ''); - nav_link = '#path=/objects&parents='+nav_name; + nav_link = '#path=/objects&parents='+nav_name; + if (collection_keys_arr.length > 2 && collection_keys_arr[collection_keys_arr.length-2].substring(0,1) == "$") nav_link = '#path=/objects&type='+nav_name; if (nav_name == "Group") nav_link = '#path=objects&type=Group'; //Hardcode this use case if (json_store.objects[nav_name].label !== undefined) nav_name = (json_store.objects[nav_name].label); @@ -461,21 +467,14 @@ function parseLinkData (link,data) { }); $('#mhresponse :input:not(:text)').change(function() { //TODO - don't submit when a text field changes - console.log("in input not text"); +// console.log("in input not text"); $('#mhresponse').submit(); }); $('#mhexec a').click( function (e) { e.preventDefault(); var url = $(this).attr('href'); url = url.replace(/;(.*?)\?/,'?'); -// console.log("MHExec " + url); $.get( url, function(data) { -// var start = data.toLowerCase().indexOf('') + 6; -// var end = data.toLowerCase().indexOf(''); -// $('#lastResponse').find('.modal-body').html(data.substring(start, end)); -// $('#lastResponse').modal({ -// show: true -// }); }); changePage(); }); @@ -552,7 +551,7 @@ var loadList = function() { var button_text = ''; var button_html = ''; var entity_arr = []; - URLHash.fields = "category,label,sort_order,members,state,states,state_log,hidden,type,text"; + URLHash.fields = "category,label,sort_order,members,state,states,state_log,hidden,type,text,schedule,logger_status"; $.ajax({ type: "GET", url: "/json/"+HashtoJSONArgs(URLHash), @@ -761,6 +760,7 @@ var loadList = function() { var getButtonColor = function (state) { var color = "default"; + if (state !== undefined) state = state.toLowerCase(); if (state == "on" || state == "open" || state == "disarmed" || state == "unarmed" || state == "ready" || state == "dry" || state == "up" || state == "100%" || state == "online" || state == "unlocked") { color = "success"; } else if (state == "motion" || state == "closed" || state == "armed" || state == "wet" || state == "fault" || state == "down" || state == "offline" || state == "locked") { @@ -801,7 +801,7 @@ var filterSubstate = function (state) { filter = 1 } } - + if (state !== undefined) state = state.toLowerCase(); if (state == "manual" || state == "double on" || state == "double off" || @@ -857,7 +857,7 @@ var sortArrayByArray = function (listArray, sortArray){ //Used to dynamically update the state of objects var updateList = function(path) { var URLHash = URLToHash(); - URLHash.fields = "state,state_log,type"; + URLHash.fields = "state,state_log,schedule,logger_status,type"; URLHash.long_poll = 'true'; URLHash.time = json_store.meta.time; if (updateSocket !== undefined && updateSocket.readyState != 4){ @@ -918,7 +918,7 @@ var updateItem = function(item,link,time) { time = ""; } var path_str = "/objects" // override, for now, would be good to add voice_cmds - var arg_str = "fields=state,states,label,state_log&long_poll=true&items="+item+"&time="+time; + var arg_str = "fields=state,states,label,state_log,schedule,logger_status&long_poll=true&items="+item+"&time="+time; updateSocket = $.ajax({ type: "GET", url: "/LONG_POLL?json('GET','"+path_str+"','"+arg_str+"')", @@ -966,7 +966,7 @@ var updateStaticPage = function(link,time) { } }) var URLHash = URLToHash(); - URLHash.fields = "state,states,state_log,label,type"; + URLHash.fields = "state,states,state_log,schedule,logger_status,label,type"; URLHash.long_poll = 'true'; URLHash.time = json_store.meta.time; if (updateSocket !== undefined && updateSocket.readyState != 4){ @@ -975,7 +975,7 @@ var updateStaticPage = function(link,time) { } var path_str = "/objects" // override, for now, would be good to add voice_cmds - var arg_str = "fields=state%2Cstates%2Cstate_log%2Clabel&long_poll=true&items="+items+"&time="+time; + var arg_str = "fields=state%2Cstates%2Cstate_log%2Cschedule%2Clogger_status%2Clabel&long_poll=true&items="+items+"&time="+time; updateSocket = $.ajax({ type: "GET", @@ -1080,7 +1080,7 @@ var loadCollection = function(collection_keys) { if (item !== undefined) { if (json_store.objects[item] === undefined) { var path_str = "/objects"; - var arg_str = "fields=state,states,label,state_log&items="+item; + var arg_str = "fields=state,states,label,state_log,schedule,logger_status,&items="+item; $.ajax({ type: "GET", url: "/json"+path_str+"?"+arg_str, @@ -1617,18 +1617,234 @@ var graph_rrd = function(start,group,time) { }); }; +var object_history = function(items,start,days,time) { + + var URLHash = URLToHash(); + var graph = 0; + if (developer) graph = 1; //right now only show the graph if in developer mode + if (typeof time === 'undefined'){ + if (graph) { + $('#list_content').html("
"); + $('#top-graph').append("
"); + $('#top-graph').append("
"); + $('#top-graph').append("

"); + } else { + $('#list_content').html("
"); + $('#hist-table').append("
"); + $('#hist-table').append("
"); + } + time = 0; + } + + URLHash.time = time; + URLHash.long_poll = 'true'; + if (updateSocket !== undefined && updateSocket.readyState != 4){ + // Only allow one update thread to run at once + updateSocket.abort(); + } + var path_str = "/history" + arg_str = "&items="+items+"&days="+days+"&graph="+graph+"&time="+time; + if (start !== undefined) arg_str += "&start="+start; + updateSocket = $.ajax({ + type: "GET", + //url: "/LONG_POLL?json('GET','"+path_str+"','"+arg_str+"')", + url: "/json"+path_str+"?"+arg_str, + dataType: "json", + success: function( json, statusText, jqXHR ) { + var requestTime = time; + if (jqXHR.status == 200) { + JSONStore(json); + // HP should probably use jquery, but couldn't get sequencing right. + // HP jquery would allow selected values to be replaced in the future. + + if (json.data.data !== undefined) { //If no data, at least show the header and an error +//TODO + } + if (start == undefined) { + start = new Date().getTime(); + } else { + start = start * 1000; //js works in ms + } + if (days == undefined) days = 0; + var end = start - (days * 24 * 60 * 60 * 1000); + var start_date = new Date(start); + var end_date = new Date(end); + +// var start_value = (start_date.getMonth() + 1)+"/"+start_date.getDate()+"/"+start_date.getFullYear(); +// var end_value = (end_date.getMonth() + 1)+"/"+end_date.getDate()+"/"+end_date.getFullYear(); + var start_value = start_date.getFullYear()+"-"+(start_date.getMonth() + 1)+"-"+start_date.getDate(); + var end_value = end_date.getFullYear()+"-"+(end_date.getMonth() + 1)+"-"+end_date.getDate(); + + var datepicker_html = '
'; + datepicker_html += ''; + datepicker_html += 'to'; + datepicker_html += ''; + datepicker_html += '
'; + $('#hist-periods').append('
'+datepicker_html+'
'); + //$('.input-daterange input').each(function() { + // $(this).datepicker(); + //}); + $('#datepicker').datepicker({ + format: "yyyy-m-d" + }); + + $('.update_history').click(function() { + console.log ("start="+$('.hist_start').val()+" end="+$('.hist_end').val()); +// var new_start = new Date($('.hist_start').val()).getTime(); +// var new_end = new Date($('.hist_end').val()).getTime(); + var new_start = new Date($('.hist_start').val().split('-')).getTime(); + var new_end = new Date($('.hist_end').val().split('-')).getTime(); + var end_days = (new_start - new_end) / (24 * 60 * 60 * 1000) + new_start = new_start / 1000; + object_history(items,new_start,end_days); + }); + // graphing view + if (graph) { + //sort the legend + json.data.data.sort(function(a, b){ + if(a.label < b.label) return -1; + if(a.label > b.label) return 1; + return 0; + }) + // put the selection list on the side. + for (var i = 0; i < json.data.data.length; i++){ + var legli = $('
  • ').appendTo('#hist-legend'); + $('').appendTo(legli); + $('
  • "; + $('#rtable').html(html); + } + requestTime = json.meta.time; + + } + if (jqXHR.status == 200 || jqXHR.status == 204) { + //Call update again, if page is still here + //KRK best way to handle this is likely to check the URL hash +//TODO live updates + } + } + }); +}; + + + /////////////// Floorplan ////////////////////////////////////////////////////////// var fp_display_width=0; // updated by fp_resize_floorplan_image var fp_display_height=0; // updated by fp_resize_floorplan_image var fp_scale = 100; // updated by fp_reposition_entities var fp_grabbed_entity = null; // store item for drag & drop var fp_icon_select_item_id = null; // store item id on right click for icon set selection +var fp_icon_image_size = 48; var noDragDrop = function() { return false; }; -var fp_getOrCreateIcon = function (json, entity, i, coords, show_pos){ +var fp_getOrCreateIcon = function (json, entity, i, coords){ var popover = 0; if ((json.data[entity].type === "FPCamera_Item") || (json_store.ia7_config.prefs.fp_state_popovers === "yes")) popover = 1; @@ -1643,7 +1859,7 @@ var fp_getOrCreateIcon = function (json, entity, i, coords, show_pos){ ''+ + '>'+ ''; if (coords !== ""){ $('#graphic').append(html); @@ -1656,7 +1872,7 @@ var fp_getOrCreateIcon = function (json, entity, i, coords, show_pos){ E.bind("dragstart", noDragDrop); var image = get_fp_image(json.data[entity]); E.attr('src',"/ia7/graphics/"+image); - if (show_pos) + if (developer) E.css("border","1px solid black"); return E; @@ -1674,7 +1890,8 @@ var fp_resize_floorplan_image = function(){ var fp_reposition_entities = function(){ var t0 = performance.now(); - var offset = $("#fp_graphic").offset(); + var fp_graphic_offset = $("#fp_graphic").offset(); + console.log("fp_graphic_offset: "+ JSON.stringify(fp_graphic_offset)); var width = fp_display_width; var hight = fp_display_height; var onePercentWidthInPx = width/100; @@ -1682,8 +1899,8 @@ var fp_reposition_entities = function(){ var fp_get_offset_from_location = function(item) { var y = item[0]; var x = item[1]; - var newy = offset.top + y * onePercentHeightInPx; - var newx = offset.left + x * onePercentWidthInPx; + var newy = fp_graphic_offset.top + y * onePercentHeightInPx; + var newx = fp_graphic_offset.left + x * onePercentWidthInPx; return { "top": newy, "left": newx @@ -1708,21 +1925,22 @@ var fp_reposition_entities = function(){ } else { nwidth = 790; } - - fp_scale = Math.round( width/nwidth * 100); + var fp_scale = width/nwidth; + var fp_scale_percent = Math.round( fp_scale * 100); - console.log("width="+width+" nwidth="+nwidth+" scale="+fp_scale); + console.log("width="+width+" nwidth="+nwidth+" scale="+fp_scale_percent); // update the location of all the objects... $(".floorplan_item").each(function(index) { var classstr = $(this).attr("class"); var coords = classstr.split(/coords=/)[1]; - $(this).width(fp_scale + "%"); + $(this).width(fp_scale_percent + "%"); if (coords.length === 0){ return; } var fp_location = coords.split(/x/); var fp_offset = fp_get_offset_from_location(fp_location); + console.log("coords="+coords); // this seems to make the repositioning slow // ~ 300+ms on my nexus7 firefox-beta vs <100ms with this code commented out @@ -1732,13 +1950,14 @@ var fp_reposition_entities = function(){ // } else { // $(this).attr('src',$(this).attr('src').replace('32.png','48.png')); // } - - var adjust = $(this).width()/2; + var element_id = $(this).attr('id'); + var adjust = fp_icon_image_size*fp_scale/2; + console.log("adjust="+adjust+" fp_offset.top="+fp_offset.top+" fp_offset.left="+fp_offset.left); var fp_off_center = { "top": fp_offset.top - adjust, "left": fp_offset.left - adjust }; - fp_set_pos($(this).attr('id'), fp_off_center); + fp_set_pos(element_id, fp_off_center); }); $('.icon_select img').each(function(){ @@ -1752,7 +1971,23 @@ var fp_set_pos = function(id, offset){ var item = $('#' + id); // do not move the span, this make the popup to narrow somehow // item.closest("span").offset(offset); + var left11 = item.css("left"); + var left12 = item[0].style.left; + var top11 = item.css("top"); + var top12 = item[0].style.top; + var before = item.offset(); + var init = false + if (item.css("left") == "auto") { + console.log("auto found, fixing left property"); + offset.left = 0 - fp_icon_image_size/2; + } item.offset(offset); + var after = item.offset(); + var left21 = item.css("left"); + var left22 = item[0].style.left; + console.log("offset.top="+offset.top+" offset.left="+offset.left+" before.top="+before.top+" before.left="+before.left+" after.top="+after.top+" after.left="+after.left); + console.log("top11="+top11+" top12="+top12); + console.log("left11="+left11+" left12="+left12+" left21="+left21+" left22="+left22); }; var fp_is_point_on_fp = function (p){ @@ -1792,7 +2027,7 @@ var floorplan = function(group,time) { $('#floorplan').append("
    "); time = 0; $('#graphic').prepend('
    '); - if (URLHash.show_pos){ + if (developer){ $('#fp_graphic').css("border","1px solid black"); $('#list_content').append("
    "); $('#list_content').append("
    ");
    @@ -1802,9 +2037,9 @@ var floorplan = function(group,time) {
                 fp_resize_floorplan_image();
                 floorplan(group, time);
             });
    -        var base_img_dir = '/ia7/graphics/floorplan';
    -		if (json_store.ia7_config.prefs.floorplan_basedir !== undefined) base_img_dir = json_store.ia7_config.prefs.floorplan_basedir;
    -        $('#fp_graphic').attr("src", base_img_dir+'-'+group+'.png');
    +        var base_img_dir = '/ia7/graphics';
    +		if (json_store.ia7_config.prefs.floorplan_baseimg_dir !== undefined) base_img_dir = json_store.ia7_config.prefs.floorplan_baseimg_dir;
    +        $('#fp_graphic').attr("src", base_img_dir+'/floorplan-'+group+'.png');
             return;
         }
     
    @@ -1852,14 +2087,14 @@ var floorplan = function(group,time) {
                 if (fp_grabbed_entity === null)
                     return;
     
    -            set_set_coordinates_from_offset(fp_grabbed_entity.id);
    +            set_coordinates_from_offset(fp_grabbed_entity.id);
                 fp_reposition_entities();
                 fp_grabbed_entity = null;
             });
     
         }
     
    -    var set_set_coordinates_from_offset = function (id)
    +    var set_coordinates_from_offset = function (id)
         {
             var E = $('#'+id);
             var offsetE = E.offset();
    @@ -1936,7 +2171,7 @@ var floorplan = function(group,time) {
         };
     
         var path_str = "/objects";
    -    var fields = "fields=fp_location,state,states,fp_icons,fp_icon_set,img,link,label,type";
    +    var fields = "fields=fp_location,state,states,fp_icons,schedule,logger_status,fp_icon_set,img,link,label,type";
         if (json_store.ia7_config.prefs.state_log_show === "yes")
             fields += ",state_log";
     
    @@ -1956,7 +2191,7 @@ var floorplan = function(group,time) {
                     var t0 = performance.now();
                     JSONStore(json);
                     for (var entity in json.data) {
    -                    if (URLHash.show_pos && requestTime === 0){
    +                    if (developer && requestTime === 0){
                             perl_pos_coords = "";
                         }
                         for (var i=0 ; i < json.data[entity].fp_location.length-1; i=i+2){ //allow for multiple graphics
    @@ -1964,7 +2199,7 @@ var floorplan = function(group,time) {
                             if ((json.data[entity].type === "FPCamera_Item") || (json_store.ia7_config.prefs.fp_state_popovers === "yes"))
                                 popover = 1;
     
    -                        if (URLHash.show_pos && requestTime === 0){
    +                        if (developer && requestTime === 0){
                                 if (perl_pos_coords.length !== 0){
                                     perl_pos_coords += ", ";
                                 }
    @@ -1972,9 +2207,9 @@ var floorplan = function(group,time) {
                             }
     
                             var coords= json.data[entity].fp_location[i]+'x'+json.data[entity].fp_location[i+1];
    -                        var E = fp_getOrCreateIcon(json, entity, i, coords, URLHash.show_pos);
    +                        var E = fp_getOrCreateIcon(json, entity, i, coords, developer);
     
    -                        if (URLHash.show_pos === undefined)
    +                        if (developer === false)
                             {
                                 // create unique popovers for Camera items
                                 if (json.data[entity].type === "FPCamera_Item") {
    @@ -2061,10 +2296,10 @@ var floorplan = function(group,time) {
                             }
                         }
     
    -                    if (URLHash.show_pos && requestTime === 0){
    +                    if (developer && requestTime === 0){
                             if (perl_pos_coords.length===0)
                             {
    -                            fp_getOrCreateIcon(json, entity, 0, "", URLHash.show_pos);
    +                            fp_getOrCreateIcon(json, entity, 0, "");
                             }
                             else{
                                 var oldCode = $('#fp_pos_perl_code').text();
    @@ -2086,7 +2321,7 @@ var floorplan = function(group,time) {
                         }
                     }
                     fp_reposition_entities();
    -                if (requestTime === 0 && URLHash.show_pos){
    +                if (requestTime === 0 && developer){
                         $('#list_content').append("

     

    "); $.ajax({ type: "GET", @@ -2182,7 +2417,7 @@ var floorplan = function(group,time) { if ($('#floorplan').length !== 0){ //If the floorplan page is still active request more data // and we are not editing the fp - if (URLHash.show_pos === undefined) + if (developer === false) floorplan(group,requestTime); } } @@ -2193,14 +2428,13 @@ var get_fp_image = function(item,size,orientation) { var image_name; var image_color = getButtonColor(item.state); var baseimg_width = $(window).width(); - var image_size = "48"; - // if (baseimg_width < 500) image_size = "32" // iphone scaling - //kvar image_size = "32" + // if (baseimg_width < 500) fp_icon_image_size = "32" // iphone scaling + //kvar fp_icon_image_size = "32" if (item.fp_icons !== undefined) { if (item.fp_icons[item.state] !== undefined) return item.fp_icons[item.state]; } if (item.fp_icon_set !== undefined) { - return "fp_"+item.fp_icon_set+"_"+image_color+"_"+image_size+".png"; + return "fp_"+item.fp_icon_set+"_"+image_color+"_"+fp_icon_image_size+".png"; } // if item.fp_icons.return item.fp_icons[state]; if(item.type === "Light_Item" || item.type === "Fan_Light" || @@ -2212,25 +2446,25 @@ var get_fp_image = function(item,size,orientation) { item.type === "UIO_Item" || item.type === "X10_Item" || item.type === "xPL_Plugwise" || item.type === "X10_Appliance") { - return "fp_light_"+image_color+"_"+image_size+".png"; + return "fp_light_"+image_color+"_"+fp_icon_image_size+".png"; } if(item.type === "Motion_Item" || item.type === "X10_Sensor" || item.type === "Insteon::MotionSensor" ) { - return "fp_motion_"+image_color+"_"+image_size+".png"; + return "fp_motion_"+image_color+"_"+fp_icon_image_size+".png"; } if(item.type === "Door_Item" || item.type === "Insteon::IOLinc_door") { - return "fp_door_"+image_color+"_"+image_size+".png"; + return "fp_door_"+image_color+"_"+fp_icon_image_size+".png"; } if(item.type === "FPCamera_Item" ) { - return "fp_camera_default_"+image_size+".png"; + return "fp_camera_default_"+fp_icon_image_size+".png"; } - return "fp_unknown_info_"+image_size+".png"; + return "fp_unknown_info_"+fp_icon_image_size+".png"; }; var create_img_popover = function(entity) { @@ -2315,12 +2549,170 @@ var create_state_modal = function(entity) { //remove states from anything that doesn't have more than 1 state $('#control').find('.states').find('.btn-group').remove(); } + + $('#control').find('.modal-body').find('.sched_control').remove(); + $('#control').find('.modal-footer').find('.sched_submit').remove(); + // Unique Schedule Data here + if (json_store.objects[entity].schedule !== undefined) { + + var modify_jqcon_dow = function(cronstr,offset) { + var cron = cronstr.split(/\s+/); + console.log("dow="+cron[cron.length-1]); + cron[cron.length-1] = cron[cron.length-1].replace(/\d/gi, function adjust(x) { + console.log("x="+x+" offset="+offset); + return parseInt(x) + parseInt(offset); + });; + console.log("dow="+cron[cron.length-1]); + return cron.join(" "); + } + + var add_schedule = function(index,cron,label,state_sets) { + if (cron === null) return; + if (label == undefined) label = index; + var sched_label_html = "
    " + if (state_sets[0] !== null) { + var display_label = label + if (display_label.length > 7) display_label = display_label.substring(0,6)+".."; + sched_label_html = ""; + } + var sched_row_html = "
    "+sched_label_html+"
    " + $('#control').find('.sched_control').append("
    "); + $('#control').find('.sched_control').append(sched_row_html); + + $('.schedule'+index).jqCron({ + enabled_minute: true, + multiple_dom: true, + multiple_month: false, + multiple_mins: true, + multiple_dow: true, + multiple_time_hours: true, + multiple_time_minutes: true, + default_period: 'week', + default_value : cron, + no_reset_button: true, + numeric_zero_pad: true, + label: index, + bind_to: $('.schedule'+index+'value'), + bind_method: { + set: function($element, value) { + $element.html(value); + $('.sched_submit').removeClass('disabled'); + $('.sched_submit').removeClass('btn-default'); + $('.sched_submit').addClass('btn-success'); + } + }, + lang: 'en' + }); + $('#control').find('.form-control').change(function() { + $('.schedule'+$(this).attr("id")+'value').attr("label",$(this).val()); + $('.sched_submit').removeClass('disabled'); + $('.sched_submit').removeClass('btn-default'); + $('.sched_submit').addClass('btn-success'); + }); + // So that the label and cron cell row heights line up + $('.cron-data').resize(function() { + $(".sched"+$(this).attr("id")+"label").height($(this).height()-12); + }); + $('.dropdown-menu li a').on('click',function() { + if (display_mode == "simple") return; + $('.schedule'+$(this).attr("id")+'value').attr("label",$(this).text()); + var display_label = $(this).text(); + if (display_label.length > 7) display_label = display_label.substring(0,6)+".."; + $(".sched"+$(this).attr("id")+"label").text(display_label); + $(".sched"+$(this).attr("id")+"label").val($(this).text()); + $('.sched_submit').removeClass('disabled'); + $('.sched_submit').removeClass('btn-default'); + $('.sched_submit').addClass('btn-success'); + }); + $('#control').find('.schedule'+index).find('.schedule_row').append(""); + $('.schedrm').on('click', function(){ + var sched_id = $( this ).attr("id") + $('.'+sched_id+'entry').remove(); + $('.'+sched_id+'value').remove(); + $('.sched_submit').removeClass('disabled'); + $('.sched_submit').removeClass('btn-default'); + $('.sched_submit').addClass('btn-success'); + }); + } + + $('#control').find('.modal-body').append("

    Schedule Control

    "); + var sched_states = json_store.objects[entity].schedule[0][3]; + console.log("schedule.length="+json_store.objects[entity].schedule.length); + for (var i = 1; i < json_store.objects[entity].schedule.length; i++){ + var sched_index = json_store.objects[entity].schedule[i][0]; + var sched_cron = modify_jqcon_dow(json_store.objects[entity].schedule[i][1],1); + var sched_label = json_store.objects[entity].schedule[i][2]; + console.log("si="+sched_index+",sc="+sched_cron+",sl="+sched_label+",ss="+sched_states); + add_schedule(sched_index,sched_cron,sched_label,sched_states); + } + + $('#control').find('.modal-footer').prepend(''); + + $('.schedadd').on('click', function(){ + var newid = Number($('.cron_entry:last').attr("id"))+1; + if (isNaN(newid)) newid=1; + var newlabel = newid; + if (sched_states[0] !== null) newlabel=sched_states[0]; + console.log("add new schedule, index should be "+newid+" states are"+sched_states); + add_schedule(newid,'0 0 * * 1-7',newlabel,sched_states); + $('.sched_submit').removeClass('disabled'); + $('.sched_submit').removeClass('btn-default'); + $('.sched_submit').addClass('btn-success'); + }); + + $('.sched_submit').on('click', function(){ + if ($(this).hasClass("disabled")) return; + var string = ""; + $('.mhsched').each(function(index,value) { + console.log("string="+string); +// string += $( this ).attr("id") + ',"' + $( this ).text() + '",' + $( this ).attr("label") + ','; + string += $( this ).attr("id") + ',"' + modify_jqcon_dow($(this).text(),"-1") + '",' + $( this ).attr("label") + ','; + console.log("string="+string); + }); + string = string.replace(/,\s*$/, ""); //remove the last comma + var url="/SUB?ia7_update_schedule"+encodeURI("("+$(this).parents('.control-dialog').attr("entity")+","+string+")"); + console.log("url="+url); + $.get(url); + $('.sched_submit').addClass('disabled'); + $('.sched_submit').removeClass('btn-success'); + $('.sched_submit').addClass('btn-default'); + //emtpy the array since the long_poll should get the updated schedules. + json_store.objects[entity].schedule.length = 0; + }); + //hide the schedule controls if in simple mode + if (display_mode == "simple") { + $('.sched_submit').hide(); + $('.schedrm').hide(); + $('.schedadd').hide(); + $('#control').find('.modal-footer').hide(); + } + + } + + //no schedule data so no button needed. + if ($('.sched_control').length == 0) { + $('#control').find('.modal-footer').hide(); + } else { + $('#control').find('.modal-footer').show(); + } + if (json_store.ia7_config.prefs.state_log_show !== "no") { //state log show last 4 (separate out set_by as advanced) - keeps being added to each time it opens // could load all log items, and only unhide the last 4 -- maybe later $('#control').find('.modal-body').find('.obj_log').remove(); - - $('#control').find('.modal-body').append("

    Object Log

    "); + var object_log_header = "

    Object Log"; + if (json_store.objects[entity].logger_status == "1") { + var collid = $(location).attr('href').split("_collection_key="); + var link = "/ia7/#path=/history?"+entity+"?1&_collection_key="+collid[1]+","; + object_log_header += ""; + } + object_log_header += "

    " +// $('#control').find('.modal-body').append("

    Object Log

    "); + $('#control').find('.modal-body').append(object_log_header); for (var i = 0; i < json_store.ia7_config.prefs.state_log_entries; i++) { if (json_store.objects[entity].state_log[i] == undefined) continue; var slog = json_store.objects[entity].state_log[i].split("set_by="); @@ -2331,6 +2723,10 @@ var create_state_modal = function(entity) { $('#control').find('.states').find('.btn').removeClass('hidden'); $('#control').find('.mh_set_by').removeClass('hidden'); }); + $('.logger_data').on('click',function() { + $('#control').modal('hide'); + console.log('url='+$(location).attr('href')); + }); } var authorize_modal = function(user) { @@ -2661,4 +3057,4 @@ $(document).ready(function() { // // You should have received a copy of the GNU General Public License along with this program; // if not, write to the Free Software Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. -// +// \ No newline at end of file diff --git a/web/ia7/include/jqCron.css b/web/ia7/include/jqCron.css new file mode 100644 index 000000000..77f95e702 --- /dev/null +++ b/web/ia7/include/jqCron.css @@ -0,0 +1,126 @@ + +/* + * This file is part of the Arnapou jqCron package. + * + * (c) Arnaud Buathier + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ +.cron-data { +} + +.jqCron-addon { +} + +.jqCron-container { + +} +.sched_cron { + display: flex; + justify-content: center; + align-items: center; +} + +.jqCron-selector { + position: relative; +} +.jqCron-cross, +.jqCron-selector-title { + cursor: pointer; + border-radius: 3px; + border: 1px solid #ddd; + margin: 0 0.2em; + padding: 0 0.5em; + +} +.jqCron-container.disable .jqCron-cross:hover, +.jqCron-container.disable .jqCron-selector-title:hover, +.jqCron-cross, +.jqCron-selector-title { + background: #eee; + border-color: #ddd; +} +.jqCron-cross:hover, +.jqCron-selector-title:hover { + background-color: #ddd; + border-color: #aaa; +} +.jqCron-cross { + border-radius: 1em; + font-size: 80%; + padding: 0 0.3em; +} +.jqCron-selector-list { + background: #eee; + border: 1px solid #aaa; + -webkit-box-shadow: 2px 2px 3px #ccc; + box-shadow: 2px 2px 3px #ccc; + left: 0.2em; + list-style: none; + margin: 0; + padding: 0; + position: absolute; + top: 1.5em; + z-index: 1; +/* white-space: normal !important; */ + +} +.jqCron-selector-list li { + -webkit-box-sizing: border-box; + -moz-box-sizing: border-box; + -ms-box-sizing: border-box; + box-sizing: border-box; + cursor: default; + display: inline-block; + margin: 0; + padding: 0.1em 0.4em; + width: 100%; + +} +.jqCron-selector-list li.selected { + background: #0088cc; + color: white; +} +.jqCron-selector-list li:hover { + background: #5fb9e7; + color: white; +} +.jqCron-selector-list.cols2 { + width: 4em; +} +.jqCron-selector-list.cols2 li { + width: 50%; +} +.jqCron-selector-list.cols3 { + width: 6em; +} +.jqCron-selector-list.cols3 li { + width: 33%; +} +.jqCron-selector-list.cols4 { + width: 8em; +} +.jqCron-selector-list.cols4 li { + width: 25%; +} +.jqCron-selector-list.cols5 { + width: 10em; +} +.jqCron-selector-list.cols5 li { + width: 20%; +} +.jqCron-error .jqCron-selector-title { + background: #fee; + border: 1px solid #fdd; + color: red; +} +.jqCron-container.disable * { + color: #888; +} +.jqCron-container.disable .jqCron-selector-title { + background: #eee !important; +} + + + diff --git a/web/ia7/include/jqCron.en.js b/web/ia7/include/jqCron.en.js new file mode 100644 index 000000000..9823c5bd4 --- /dev/null +++ b/web/ia7/include/jqCron.en.js @@ -0,0 +1,40 @@ +/* + * This file is part of the Arnapou jqCron package. + * + * (c) Arnaud Buathier + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +//empty should be label +//name_* should be recycle icon + +jqCronDefaultSettings.texts.en = { + empty: 'every1', + empty_minutes: 'every2', + empty_time_hours: 'every hour', + empty_time_minutes: 'every minute', + empty_day_of_week: 'every day', + empty_day_of_month: 'every day of the month', + empty_month: 'every month', + name_minute: 'minute', + name_hour: 'hour', + name_day: 'day', + name_week: 'week', + name_month: 'month', + name_year: 'year', + text_period: '', + text_mins: ' at minute(s) past the hour', + text_time: ':', + text_dow: '', + text_month: ' of ', + text_dom: ' on ', + error1: 'The tag %s is not supported !', + error2: 'Bad number of elements', + error3: 'The jquery_element should be set into jqCron settings', + error4: 'Unrecognized expression', + weekdays_short: ['Su','M','Tu','W','Th','F','Sa'], + weekdays: ['sunday', 'monday', 'tuesday', 'wednesday', 'thursday', 'friday', 'saturday'], + months: ['january', 'february', 'march', 'april', 'may', 'june', 'july', 'august', 'september', 'october', 'november', 'december'] +}; diff --git a/web/ia7/include/jqCron.js b/web/ia7/include/jqCron.js new file mode 100644 index 000000000..e68e2adae --- /dev/null +++ b/web/ia7/include/jqCron.js @@ -0,0 +1,853 @@ +/* + * This file is part of the Arnapou jqCron package. + * + * (c) Arnaud Buathier + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +/** + * Default settings + */ +var jqCronDefaultSettings = { + texts: {}, + monthdays: [1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13, 14, 15, 16, 17, 18, 19, 20, 21, 22, 23, 24, 25, 26, 27, 28, 29, 30, 31], + hours: [0, 1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13, 14, 15, 16, 17, 18, 19, 20, 21, 22, 23], + minutes: [0, 1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13, 14, 15, 16, 17, 18, 19, 20, 21, 22, 23, 24, 25, 26, 27, 28, 29, 30, 31, 32, 33, 34, 35, 36, 37, 38, 39, 40, 41, 42, 43, 44, 45, 46, 47, 48, 49, 50, 51, 52, 53, 54, 55, 56, 57, 58, 59], + lang: 'en', + enabled_minute: false, + enabled_hour: true, + enabled_day: true, + enabled_week: true, + enabled_month: true, + enabled_year: true, + multiple_dom: false, + multiple_month: false, + multiple_mins: false, + multiple_dow: false, + multiple_time_hours: false, + multiple_time_minutes: false, + numeric_zero_pad: false, + default_period: 'day', + default_value: '', + no_reset_button: true, + disabled: false, + bind_to: null, + bind_method: { + set: function($element, value) { + $element.is(':input') ? $element.val(value) : $element.data('jqCronValue', value); + }, + get: function($element) { + return $element.is(':input') ? $element.val() : $element.data('jqCronValue'); + } + } +}; + +/** + * Custom extend of json for jqCron settings. + * We don't use jQuery.extend because simple extend does not fit our needs, and deep extend has a bad + * feature for us : it replaces keys of "Arrays" instead of replacing the full array. + */ +(function($){ + var extend = function(dst, src) { + for(var i in src) { + if($.isPlainObject(src[i])) { + dst[i] = extend(dst[i] && $.isPlainObject(dst[i]) ? dst[i] : {}, src[i]); + } + else if($.isArray(src[i])) { + dst[i] = src[i].slice(0); + } + else if(src[i] !== undefined) { + dst[i] = src[i]; + } + } + return dst; + }; + this.jqCronMergeSettings = function(obj) { + return extend(extend({}, jqCronDefaultSettings), obj || {}); + }; +}).call(this, jQuery); + +/** + * Shortcut to get the instance of jqCron instance from one jquery object + */ +(function($){ + $.fn.jqCronGetInstance = function() { + return this.data('jqCron'); + }; +}).call(this, jQuery); + +/** + * Main plugin + */ +(function($){ + $.fn.jqCron = function(settings) { + var saved_settings = settings; + return this.each(function() { + var cron, saved; + var $this = $(this); + var settings = jqCronMergeSettings(saved_settings); // clone settings + var translations = settings.texts[settings.lang]; + + if (typeof(translations) !== 'object' || $.isEmptyObject(translations)) { + console && console.error( + 'Missing translations for language "' + settings.lang + '". ' + + 'Please include jqCron.' + settings.lang + '.js or manually provide ' + + 'the necessary translations when calling $.fn.jqCron().' + ); + return; + } + + if(!settings.jquery_container) { + if($this.is(':container')) { + settings.jquery_element = $this.uniqueId('jqCron'); + } + else if($this.is(':autoclose')) { + // delete already generated dom if exists + if($this.next('.jqCron').length == 1) { + $this.next('.jqCron').remove(); + } + // generate new + settings.jquery_element = $('').uniqueId('jqCron').insertAfter($this); + } + else { + console && console.error(settings.texts[settings.lang].error1.replace('%s', this.tagName)); + return; + } + } + + // autoset bind_to if it is an input + if($this.is(':input')) { + settings.bind_to = settings.bind_to || $this; + } + + // init cron object + if(settings.bind_to){ + if(settings.bind_to.is(':input')) { + // auto bind from input to object if an input, textarea ... + settings.bind_to.blur(function(){ + var value = settings.bind_method.get(settings.bind_to); + $this.jqCronGetInstance().setCron(value); + }); + } + saved = settings.bind_method.get(settings.bind_to); + cron = new jqCron(settings); + cron.setCron(saved); + } + else { + cron = new jqCron(settings); + } + $(this).data('jqCron', cron); + }); + }; +}).call(this, jQuery); + +/** + * jqCron class + */ +(function($){ + var jqCronInstances = []; + + function jqCron(settings) { + var _initialized = false; + var _self = this; + var _$elt = this; + var _$obj = $(''); + var _$blocks = $(''); + var _$blockPERIOD = $(''); + var _$blockDOM = $(''); + var _$blockMONTH = $(''); + var _$blockMINS = $(''); + var _$blockDOW = $(''); + var _$blockTIME = $(''); + var _$cross = $(''); + var _selectors = []; + var _selectorPeriod, _selectorMins, _selectorTimeH, _selectorTimeM, _selectorDow, _selectorDom, _selectorMonth; + + // instanciate a new selector + function newSelector($block, multiple, type){ + var selector = new jqCronSelector(_self, $block, multiple, type); + selector.$.bind('selector:open', function(){ + // we close all opened selectors of all other jqCron + for(var n = jqCronInstances.length; n--; ){ + if(jqCronInstances[n] != _self) { + jqCronInstances[n].closeSelectors(); + } + else { + // we close all other opened selectors of this jqCron + for(var o = _selectors.length; o--; ){ + if(_selectors[o] != selector) { + _selectors[o].close(); + } + } + } + } + }); + selector.$.bind('selector:change', function(){ + var boundChanged = false; + // don't propagate if not initialized + if(!_initialized) return; + // bind data between two minute selectors (only if they have the same multiple settings) + if(settings.multiple_mins == settings.multiple_time_minutes) { + if(selector == _selectorMins) { + boundChanged = _selectorTimeM.setValue(_selectorMins.getValue()); + } + else if(selector == _selectorTimeM) { + boundChanged = _selectorMins.setValue(_selectorTimeM.getValue()); + } + } + // we propagate the change event to the main object + boundChanged || _$obj.trigger('cron:change', _self.getCron()); + }); + _selectors.push(selector); + return selector; + } + + // disable the selector + this.disable = function(){ + _$obj.addClass('disable'); + settings.disable = true; + _self.closeSelectors(); + }; + + // return if the selector is disabled + this.isDisabled = function() { + return settings.disable == true; + }; + + // enable the selector + this.enable = function(){ + _$obj.removeClass('disable'); + settings.disable = false; + }; + + // get cron value + this.getCron = function(){ + var period = _selectorPeriod.getValue(); + var items = ['*', '*', '*', '*', '*']; + if(period == 'hour') { + items[0] = _selectorMins.getCronValue(); + } + if(period == 'day' || period == 'week' || period == 'month' || period == 'year') { + items[0] = _selectorTimeM.getCronValue(); + items[1] = _selectorTimeH.getCronValue(); + } + if(period == 'month' || period == 'year') { + items[2] = _selectorDom.getCronValue(); + } + if(period == 'year') { + items[3] = _selectorMonth.getCronValue(); + } + if(period == 'week') { + items[4] = _selectorDow.getCronValue(); + } + return items.join(' '); + }; + + // set cron (string like * * * * *) + this.setCron = function(str) { + if(!str) return; + try { + str = str.replace(/\s+/g, ' ').replace(/^ +/, '').replace(/ +$/, ''); // sanitize + var mask = str.replace(/[^\* ]/g, '-').replace(/-+/g, '-').replace(/ +/g, ''); + var items = str.split(' '); + if (items.length != 5) _self.error(_self.getText('error2')); + if(mask == '*****') { // 1 possibility + _selectorPeriod.setValue('minute'); + } + else if(mask == '-****') { // 1 possibility + _selectorPeriod.setValue('hour'); + _selectorMins.setCronValue(items[0]); + _selectorTimeM.setCronValue(items[0]); + } + else if(mask.substring(2, mask.length) == '***') { // 4 possibilities + _selectorPeriod.setValue('day'); + _selectorMins.setCronValue(items[0]); + _selectorTimeM.setCronValue(items[0]); + _selectorTimeH.setCronValue(items[1]); + } + else if(mask.substring(2, mask.length) == '-**') { // 4 possibilities + _selectorPeriod.setValue('month'); + _selectorMins.setCronValue(items[0]); + _selectorTimeM.setCronValue(items[0]); + _selectorTimeH.setCronValue(items[1]); + _selectorDom.setCronValue(items[2]); + } + else if(mask.substring(2, mask.length) == '**-') { // 4 possibilities + _selectorPeriod.setValue('week'); + _selectorMins.setCronValue(items[0]); + _selectorTimeM.setCronValue(items[0]); + _selectorTimeH.setCronValue(items[1]); + _selectorDow.setCronValue(items[4]); + } + else if (mask.substring(3, mask.length) == '-*') { // 8 possibilities + _selectorPeriod.setValue('year'); + _selectorMins.setCronValue(items[0]); + _selectorTimeM.setCronValue(items[0]); + _selectorTimeH.setCronValue(items[1]); + _selectorDom.setCronValue(items[2]); + _selectorMonth.setCronValue(items[3]); + } + else { + _self.error(_self.getText('error4')); + } + _self.clearError(); + } catch(e) {} + }; + + // close all child selectors + this.closeSelectors = function(){ + for(var n = _selectors.length; n--; ){ + _selectors[n].close(); + } + }; + + // get the main element id + this.getId = function(){ + return _$elt.attr('id'); + } + + // get the translated text + this.getText = function(key) { + var text = settings.texts[settings.lang][key] || null; + if(typeof(text) == "string" && text.match(')/gi, ''); + text = '' + text + ''; + } + return text; + }; + + // get the human readable text + this.getHumanText = function() { + var texts=[]; + _$obj + .find('> span > span:visible') + .find('.jqCron-text, .jqCron-selector > span') + .each(function() { + var text = $(this).text().replace(/\s+$/g, '').replace(/^\s+/g, ''); + text && texts.push(text); + }); + return texts.join(' ').replace(/\s:\s/g, ':'); + } + + // get settings + this.getSettings = function(){ + return settings; + }; + + // display an error + this.error = function(msg) { + console && console.error('[jqCron Error] ' + msg); + _$obj.addClass('jqCron-error').attr('title', msg); + throw msg; + }; + + // clear error + this.clearError = function(){ + _$obj.attr('title', '').removeClass('jqCron-error'); + }; + + // clear + this.clear = function() { + _selectorDom.setValue([]); + _selectorDow.setValue([]); + _selectorMins.setValue([]); + _selectorMonth.setValue([]); + _selectorTimeH.setValue([]); + _selectorTimeM.setValue([]); + _self.triggerChange(); + }; + + // init (called in constructor) + this.init = function(){ + var n,i,list; + if(_initialized) return; + + settings = jqCronMergeSettings(settings); + settings.jquery_element || _self.error(_self.getText('error3')); + _$elt = settings.jquery_element; + _$elt.append(_$obj); + _$obj.data('id', settings.id); + _$obj.data('jqCron', _self); + _$obj.append(_$blocks); + settings.no_reset_button || _$obj.append(_$cross); + (!settings.disable) || _$obj.addClass('disable'); + _$blocks.append(_$blockPERIOD); + _$blocks.append(_$blockDOM); + _$blocks.append(_$blockMONTH); + _$blocks.append(_$blockMINS); + _$blocks.append(_$blockDOW); + _$blocks.append(_$blockTIME); + + // various binding + _$cross.click(function(){ + _self.isDisabled() || _self.clear(); + }); + + // binding from cron to target + _$obj.bind('cron:change', function(evt, value){ + if(!settings.bind_to) return; + settings.bind_method.set && settings.bind_method.set(settings.bind_to, value); + _self.clearError(); + }); + + // PERIOD + _$blockPERIOD.append(_self.getText('text_period')); + _selectorPeriod = newSelector(_$blockPERIOD, false, 'period'); + settings.enabled_minute && _selectorPeriod.add('minute', _self.getText('name_minute')); + settings.enabled_hour && _selectorPeriod.add('hour', _self.getText('name_hour')); + settings.enabled_day && _selectorPeriod.add('day', _self.getText('name_day')); + settings.enabled_week && _selectorPeriod.add('week', _self.getText('name_week')); + settings.enabled_month && _selectorPeriod.add('month', _self.getText('name_month')); + settings.enabled_year && _selectorPeriod.add('year', _self.getText('name_year')); + _selectorPeriod.$.bind('selector:change', function(e, value){ + _$blockDOM.hide(); + _$blockMONTH.hide(); + _$blockMINS.hide(); + _$blockDOW.hide(); + _$blockTIME.hide(); + if(value == 'hour') { + _$blockMINS.show(); + } + else if(value == 'day') { + _$blockTIME.show(); + } + else if(value == 'week') { + _$blockDOW.show(); + _$blockTIME.show(); + } + else if(value == 'month') { + _$blockDOM.show(); + _$blockTIME.show(); + } + else if(value == 'year') { + _$blockDOM.show(); + _$blockMONTH.show(); + _$blockTIME.show(); + } + }); + _selectorPeriod.setValue(settings.default_period); + + // MINS (minutes) + _$blockMINS.append(_self.getText('text_mins')); + _selectorMins = newSelector(_$blockMINS, settings.multiple_mins, 'minutes'); + for(i=0, list=settings.minutes; i'); + var _$title = $(''); + var _$selector = $(''); + var _values = {}; + var _value = []; + var _hasNumericTexts = true; + var _numeric_zero_pad = _cron.getSettings().numeric_zero_pad; + + // return an array without doublon + function array_unique(l){ + var i=0,n=l.length,k={},a=[]; + while(i' + value + ''); + _$list.append($item); + _values[key] = $item; + $item.click(function(){ + if(_multiple && $(this).hasClass('selected')) { + _self.removeValue(key); + } + else { + _self.addValue(key); + if(!_multiple) _self.close(); + } + }); + }; + + // expose main jquery object + this.$ = _$selector; + + // constructor + _$block.find('b:eq(0)').after(_$selector).remove(); + _$selector + .addClass('jqCron-selector-' + _$block.find('.jqCron-selector').length) + .append(_$title) + .append(_$list) + .bind('selector:open', function(){ + if(_hasNumericTexts) { + var nbcols = 1, n = _$list.find('li').length; + if(n > 5 && n <= 16) nbcols = 2; + else if(n > 16 && n <= 23) nbcols = 3; + else if(n > 23 && n <= 40) nbcols = 4; + else if(n > 40) nbcols = 5; + _$list.addClass('cols'+nbcols); + } + _$list.show(); + }) + .bind('selector:close', function(){ + _$list.hide(); + }) + .bind('selector:change', function(){ + _$title.html(_self.getTitleText()); + }) + .click(function(e){ + e.stopPropagation(); + }) + .trigger('selector:change') + ; + $.fn.disableSelection && _$selector.disableSelection(); // only work with jQuery UI + _$title.click(function(e){ + (_self.isOpened() || _cron.isDisabled()) ? _self.close() : _self.open(); + }); + _self.close(); + _self.clear(); + } + this.jqCronSelector = jqCronSelector; +}).call(this, jQuery); + +/** + * Generate unique id for each element. + * Skip elements which have already an id. + */ +(function($){ + var jqUID = 0; + var jqGetUID = function(prefix){ + var id; + while(1) { + jqUID++; + id = ((prefix || 'JQUID')+'') + jqUID; + if(!document.getElementById(id)) return id; + } + }; + $.fn.uniqueId = function(prefix) { + return this.each(function(){ + if($(this).attr('id')) return; + var id = jqGetUID(prefix); + $(this).attr('id', id); + }); + }; +}).call(this, jQuery); + + +/** + * Extends jQuery selectors with new block selector + */ +(function($){ + $.extend($.expr[':'], { + container: function(a) { + return (a.tagName+'').toLowerCase() in { + a:1, + abbr:1, + acronym:1, + address:1, + b:1, + big:1, + blockquote:1, + button:1, + cite:1, + code:1, + dd: 1, + del:1, + dfn:1, + div:1, + dt:1, + em:1, + fieldset:1, + form:1, + h1:1, + h2:1, + h3:1, + h4:1, + h5:1, + h6: 1, + i:1, + ins:1, + kbd:1, + label:1, + li:1, + p:1, + pre:1, + q:1, + samp:1, + small:1, + span:1, + strong:1, + sub: 1, + sup:1, + td:1, + tt:1 + }; + }, + autoclose: function(a) { + return (a.tagName+'').toLowerCase() in { + area:1, + base:1, + basefont:1, + br:1, + col:1, + frame:1, + hr:1, + img:1, + input:1, + link:1, + meta:1, + param:1 + }; + } + }); +}).call(this, jQuery); diff --git a/web/ia7/include/tables.css b/web/ia7/include/tables.css index 9ed17528e..7df71c7d1 100644 --- a/web/ia7/include/tables.css +++ b/web/ia7/include/tables.css @@ -73,10 +73,38 @@ (min-device-width: 768px) and (max-device-width: 1024px) { /* Force table to not be like tables anymore */ - #rtable table, thead, tbody, th, td, tr { - display: block; +/* #rtable table, thead, tbody, th, td, tr { */ + +/* #rtable table thead tbody th td tr { */ +/* display: block; */ +/* } */ + + #rtable table { + display: block; + } + + #rtable thead { + display: block; } + #rtable tbody { + display: block; + } + + #rtable th { + display: block; + } + + #rtable td { + display: block; + } + + #rtable tr { + display: block; + } + + + /* Hide table headers (but not display: none;, for accessibility) */ #rtable thead tr { position: absolute; diff --git a/web/ia7/index.shtml b/web/ia7/index.shtml index f5fcfc9a8..9ea21d99f 100644 --- a/web/ia7/index.shtml +++ b/web/ia7/index.shtml @@ -54,9 +54,17 @@ + + + + + + + + - + @@ -236,6 +285,8 @@
    +