1 #============================================================= -*-perl-*-
3 # BackupPC::CGI::EditConfig package
7 # This module implements the EditConfig action for the CGI interface.
10 # Craig Barratt <cbarratt@users.sourceforge.net>
13 # Copyright (C) 2005 Craig Barratt
15 # This program is free software; you can redistribute it and/or modify
16 # it under the terms of the GNU General Public License as published by
17 # the Free Software Foundation; either version 2 of the License, or
18 # (at your option) any later version.
20 # This program is distributed in the hope that it will be useful,
21 # but WITHOUT ANY WARRANTY; without even the implied warranty of
22 # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
23 # GNU General Public License for more details.
25 # You should have received a copy of the GNU General Public License
26 # along with this program; if not, write to the Free Software
27 # Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA
29 #========================================================================
31 # Version 3.0.0beta0, released 11 Jul 2006.
33 # See http://backuppc.sourceforge.net.
35 #========================================================================
37 package BackupPC::CGI::EditConfig;
40 use BackupPC::CGI::Lib qw(:all);
41 use BackupPC::Config::Meta qw(:all);
42 use BackupPC::Storage;
48 text => "CfgEdit_Title_Server",
50 {text => "CfgEdit_Title_General_Parameters"},
51 {name => "ServerHost"},
52 {name => "BackupPCUser"},
53 {name => "BackupPCUserVerify"},
54 {name => "MaxOldLogFiles"},
55 {name => "TrashCleanSleepSec"},
57 {text => "CfgEdit_Title_Wakeup_Schedule"},
58 {name => "WakeupSchedule"},
60 {text => "CfgEdit_Title_Concurrent_Jobs"},
61 {name => "MaxBackups"},
62 {name => "MaxUserBackups"},
63 {name => "MaxPendingCmds"},
64 {name => "MaxBackupPCNightlyJobs"},
65 {name => "BackupPCNightlyPeriod"},
67 {text => "CfgEdit_Title_Pool_Filesystem_Limits"},
69 {name => "DfMaxUsagePct"},
70 {name => "HardLinkMax"},
72 {text => "CfgEdit_Title_Other_Parameters"},
73 {name => "UmaskMode"},
75 {name => "DHCPAddressRanges"},
76 {name => "PerlModuleLoad"},
77 {name => "ServerInitdPath"},
78 {name => "ServerInitdStartCmd"},
80 {text => "CfgEdit_Title_Remote_Apache_Settings"},
81 {name => "ServerPort"},
82 {name => "ServerMesgSecret"},
84 {text => "CfgEdit_Title_Program_Paths"},
86 {name => "NmbLookupPath"},
89 {name => "SplitPath"},
93 {name => "Bzip2Path"},
95 {text => "CfgEdit_Title_Install_Paths"},
100 {name => "InstallDir"},
104 text => "CfgEdit_Title_Email",
106 {text => "CfgEdit_Title_Email_settings"},
107 {name => "SendmailPath"},
108 {name => "EMailNotifyMinDays"},
109 {name => "EMailFromUserName"},
110 {name => "EMailAdminUserName"},
111 {name => "EMailUserDestDomain"},
113 {text => "CfgEdit_Title_Email_User_Messages"},
114 {name => "EMailNoBackupEverSubj"},
115 {name => "EMailNoBackupEverMesg"},
116 {name => "EMailNotifyOldBackupDays"},
117 {name => "EMailNoBackupRecentSubj"},
118 {name => "EMailNoBackupRecentMesg"},
119 {name => "EMailNotifyOldOutlookDays"},
120 {name => "EMailOutlookBackupSubj"},
121 {name => "EMailOutlookBackupMesg"},
122 {name => "EMailHeaders"},
126 text => "CfgEdit_Title_CGI",
128 {text => "CfgEdit_Title_Admin_Privileges"},
129 {name => "CgiAdminUserGroup"},
130 {name => "CgiAdminUsers"},
132 {text => "CfgEdit_Title_Page_Rendering"},
133 {name => "Language"},
134 {name => "CgiNavBarAdminAllHosts"},
135 {name => "CgiSearchBoxEnable"},
136 {name => "CgiNavBarLinks"},
137 {name => "CgiStatusHilightColor"},
138 {name => "CgiDateFormatMMDD"},
139 {name => "CgiHeaders"},
140 {name => "CgiExt2ContentType"},
141 {name => "CgiCSSFile"},
143 {text => "CfgEdit_Title_Paths"},
145 {name => "CgiImageDir"},
146 {name => "CgiImageDirURL"},
148 {text => "CfgEdit_Title_User_URLs"},
149 {name => "CgiUserHomePageCheck"},
150 {name => "CgiUserUrlCreate"},
152 {text => "CfgEdit_Title_User_Config_Editing"},
153 {name => "CgiUserConfigEditEnable"},
154 {name => "CgiUserConfigEdit"},
158 text => "CfgEdit_Title_Xfer",
160 {text => "CfgEdit_Title_Xfer_Settings"},
161 {name => "XferMethod", onchangeSubmit => 1},
162 {name => "XferLogLevel"},
163 {name => "ClientCharset"},
165 {text => "CfgEdit_Title_Smb_Settings",
166 visible => sub { return $_[0]->{XferMethod} eq "smb"; } },
167 {name => "SmbShareName",
168 visible => sub { return $_[0]->{XferMethod} eq "smb"; } },
169 {name => "SmbShareUserName",
170 visible => sub { return $_[0]->{XferMethod} eq "smb"; } },
171 {name => "SmbSharePasswd",
172 visible => sub { return $_[0]->{XferMethod} eq "smb"; } },
174 {text => "CfgEdit_Title_Tar_Settings",
175 visible => sub { return $_[0]->{XferMethod} eq "tar"; } },
176 {name => "TarShareName",
177 visible => sub { return $_[0]->{XferMethod} eq "tar"; } },
179 {text => "CfgEdit_Title_Rsync_Settings",
180 visible => sub { return $_[0]->{XferMethod} eq "rsync"; } },
181 {text => "CfgEdit_Title_Rsyncd_Settings",
182 visible => sub { return $_[0]->{XferMethod} eq "rsyncd"; } },
183 {name => "RsyncShareName",
184 visible => sub { return $_[0]->{XferMethod} =~ /rsync/; } },
185 {name => "RsyncdUserName",
186 visible => sub { return $_[0]->{XferMethod} eq "rsyncd"; } },
187 {name => "RsyncdPasswd",
188 visible => sub { return $_[0]->{XferMethod} eq "rsyncd"; } },
189 {name => "RsyncdAuthRequired",
190 visible => sub { return $_[0]->{XferMethod} eq "rsyncd"; } },
191 {name => "RsyncCsumCacheVerifyProb",
192 visible => sub { return $_[0]->{XferMethod} =~ /rsync/; } },
194 {text => "CfgEdit_Title_BackupPCd_Settings",
195 visible => sub { return $_[0]->{XferMethod} eq "backuppcd"; } },
196 {name => "BackupPCdShareName",
197 visible => sub { return $_[0]->{XferMethod} eq "backuppcd"; } },
198 {name => "BackupPCdPath",
199 visible => sub { return $_[0]->{XferMethod} eq "backuppcd"; } },
200 {name => "BackupPCdCmd",
201 visible => sub { return $_[0]->{XferMethod} eq "backuppcd"; } },
202 {name => "BackupPCdRestoreCmd",
203 visible => sub { return $_[0]->{XferMethod} eq "backuppcd"; } },
205 {text => "CfgEdit_Title_Archive_Settings",
206 visible => sub { return $_[0]->{XferMethod} eq "archive"; } },
207 {name => "ArchiveDest",
208 visible => sub { return $_[0]->{XferMethod} eq "archive"; } },
209 {name => "ArchiveComp",
210 visible => sub { return $_[0]->{XferMethod} eq "archive"; } },
211 {name => "ArchivePar",
212 visible => sub { return $_[0]->{XferMethod} eq "archive"; } },
213 {name => "ArchiveSplit",
214 visible => sub { return $_[0]->{XferMethod} eq "archive"; } },
216 {text => "CfgEdit_Title_Include_Exclude",
217 visible => sub { return $_[0]->{XferMethod} ne "archive"; } },
218 {name => "BackupFilesOnly",
219 visible => sub { return $_[0]->{XferMethod} ne "archive"; } },
220 {name => "BackupFilesExclude",
221 visible => sub { return $_[0]->{XferMethod} ne "archive"; } },
223 {text => "CfgEdit_Title_Smb_Paths_Commands",
224 visible => sub { return $_[0]->{XferMethod} eq "smb"; } },
225 {name => "SmbClientPath",
226 visible => sub { return $_[0]->{XferMethod} eq "smb"; } },
227 {name => "SmbClientFullCmd",
228 visible => sub { return $_[0]->{XferMethod} eq "smb"; } },
229 {name => "SmbClientIncrCmd",
230 visible => sub { return $_[0]->{XferMethod} eq "smb"; } },
231 {name => "SmbClientRestoreCmd",
232 visible => sub { return $_[0]->{XferMethod} eq "smb"; } },
234 {text => "CfgEdit_Title_Tar_Paths_Commands",
235 visible => sub { return $_[0]->{XferMethod} eq "tar"; } },
236 {name => "TarClientPath",
237 visible => sub { return $_[0]->{XferMethod} eq "tar"; } },
238 {name => "TarClientCmd",
239 visible => sub { return $_[0]->{XferMethod} eq "tar"; } },
240 {name => "TarFullArgs",
241 visible => sub { return $_[0]->{XferMethod} eq "tar"; } },
242 {name => "TarIncrArgs",
243 visible => sub { return $_[0]->{XferMethod} eq "tar"; } },
244 {name => "TarClientRestoreCmd",
245 visible => sub { return $_[0]->{XferMethod} eq "tar"; } },
247 {text => "CfgEdit_Title_Rsync_Paths_Commands_Args",
248 visible => sub { return $_[0]->{XferMethod} eq "rsync"; } },
249 {text => "CfgEdit_Title_Rsyncd_Port_Args",
250 visible => sub { return $_[0]->{XferMethod} eq "rsyncd"; } },
251 {name => "RsyncClientPath",
252 visible => sub { return $_[0]->{XferMethod} eq "rsync"; } },
253 {name => "RsyncClientCmd",
254 visible => sub { return $_[0]->{XferMethod} eq "rsync"; } },
255 {name => "RsyncClientRestoreCmd",
256 visible => sub { return $_[0]->{XferMethod} eq "rsync"; } },
257 {name => "RsyncdClientPort",
258 visible => sub { return $_[0]->{XferMethod} eq "rsyncd"; } },
259 {name => "RsyncArgs",
260 visible => sub { return $_[0]->{XferMethod} =~ /rsync/; } },
261 {name => "RsyncRestoreArgs",
262 visible => sub { return $_[0]->{XferMethod} =~ /rsync/; } },
264 {text => "CfgEdit_Title_Archive_Paths_Commands",
265 visible => sub { return $_[0]->{XferMethod} eq "archive"; } },
266 {name => "ArchiveClientCmd",
267 visible => sub { return $_[0]->{XferMethod} eq "archive"; } },
272 text => "CfgEdit_Title_Schedule",
274 {text => "CfgEdit_Title_Full_Backups"},
275 {name => "FullPeriod"},
276 {name => "FullKeepCnt"},
277 {name => "FullKeepCntMin"},
278 {name => "FullAgeMax"},
280 {text => "CfgEdit_Title_Incremental_Backups"},
281 {name => "IncrPeriod"},
282 {name => "IncrKeepCnt"},
283 {name => "IncrKeepCntMin"},
284 {name => "IncrAgeMax"},
285 {name => "IncrLevels"},
286 {name => "IncrFill"},
288 {text => "CfgEdit_Title_Blackouts"},
289 {name => "BackupsDisable"},
290 {name => "BlackoutBadPingLimit"},
291 {name => "BlackoutGoodCnt"},
292 {name => "BlackoutPeriods"},
294 {text => "CfgEdit_Title_Other"},
295 {name => "PartialAgeMax"},
296 {name => "RestoreInfoKeepCnt"},
297 {name => "ArchiveInfoKeepCnt"},
298 {name => "BackupZeroFilesIsFatal"},
302 text => "CfgEdit_Title_Backup_Settings",
304 {text => "CfgEdit_Title_Client_Lookup"},
305 {name => "ClientNameAlias"},
306 {name => "NmbLookupCmd"},
307 {name => "NmbLookupFindHostCmd"},
308 {name => "FixedIPNetBiosNameCheck"},
310 {name => "PingMaxMsec"},
312 {text => "CfgEdit_Title_Other"},
313 {name => "ClientTimeout"},
314 {name => "MaxOldPerPCLogFiles"},
315 {name => "CompressLevel"},
317 {text => "CfgEdit_Title_User_Commands"},
318 {name => "DumpPreUserCmd"},
319 {name => "DumpPostUserCmd"},
320 {name => "DumpPreShareCmd"},
321 {name => "DumpPostShareCmd"},
322 {name => "RestorePreUserCmd"},
323 {name => "RestorePostUserCmd"},
324 {name => "ArchivePreUserCmd"},
325 {name => "ArchivePostUserCmd"},
326 {name => "UserCmdCheckStatus"},
330 text => "CfgEdit_Title_Hosts",
332 {text => "CfgEdit_Title_Hosts"},
334 comment => "CfgEdit_Hosts_Comment"},
341 my $pc_dir = "$TopDir/pc";
342 my($content, $contentHidden, $newConf, $override, $mainConf, $hostConf);
345 my $host = $In{host};
346 my $menu = $In{menu} || "server";
347 my $hosts_path = $Hosts;
348 my $config_path = $host eq "" ? "$TopDir/conf/config.pl"
349 : "$TopDir/pc/$host/config.pl";
351 my $Privileged = CheckPermission($host)
352 && ($PrivAdmin || $Conf{CgiUserConfigEditEnable});
353 my $userHost = 1 if ( defined($host) );
356 if ( !$Privileged ) {
357 ErrorExit(eval("qq{$Lang->{Only_privileged_users_can_edit_config_files}}"));
360 if ( defined($In{menu}) || $In{editAction} eq $Lang->{CfgEdit_Button_Save} ) {
361 $errors = errorCheck();
364 # If there are errors, then go back to the same menu
366 $In{editAction} = "";
369 if ( (my $var = $In{overrideUncheck}) ne "" ) {
371 # a compound variable was unchecked; delete extra
372 # variables to make the shape the same.
374 #print STDERR Dumper(\%In);
375 foreach my $v ( keys(%In) ) {
376 next if ( $v !~ /^v_z_(\Q$var\E(_z_.*|$))/ );
377 delete($In{$v}) if ( !defined($In{"orig_z_$1"}) );
379 delete($In{"vflds.$var"});
382 ($newConf, $override) = inputParse($bpc, $userHost);
383 $override = undef if ( $host eq "" );
387 # First time: pick up the current config settings
389 $mainConf = $bpc->ConfigDataRead();
391 $hostConf = $bpc->ConfigDataRead($host);
393 foreach my $param ( keys(%$hostConf) ) {
394 $override->{$param} = 1;
397 my $hostInfo = $bpc->HostInfoRead();
399 $mainConf->{Hosts} = [map($hostInfo->{$_}, sort(keys(%$hostInfo)))];
401 $newConf = { %$mainConf, %$hostConf };
404 if ( $In{editAction} ne $Lang->{CfgEdit_Button_Save} && $In{newMenu} ne ""
405 && defined($ConfigMenu{$In{newMenu}}) ) {
406 $menu = $In{newMenu};
412 # For a non-admin user editing the host config, we need to
413 # figure out which subsets of the menu tree will be visible,
414 # based on what is enabled. Admin users can edit all the
415 # available per-host settings.
417 foreach my $m ( keys(%ConfigMenu) ) {
423 foreach my $paramInfo ( @{$ConfigMenu{$m}{param}} ) {
424 my $param = $paramInfo->{name};
425 if ( defined($paramInfo->{text}) ) {
429 if ( $bpc->{Conf}{CgiUserConfigEdit}{$param}
430 || (defined($bpc->{Conf}{CgiUserConfigEdit}{$param})
432 $mask[$text] = 0 if ( $text >= 0 );
441 $menuDisable{$m}{mask} = \@mask;
442 $menuDisable{$m}{top} = !$enabled;
444 if ( $menuDisable{$menu}{top} ) {
446 # Find an enabled menu if the current menu is empty
448 foreach my $m ( sort(keys(%menuDisable)) ) {
449 if ( !$menuDisable{$m}{top} ) {
458 foreach my $m ( keys(%ConfigMenu) ) {
459 next if ( $menuDisable{$m}{top} );
460 my $text = eval("qq($Lang->{$ConfigMenu{$m}{text}})");
463 <td class="editTabSel"><a href="javascript:menuSubmit('$m')"><b>$text</b></a></td>
467 <td class="editTabNoSel"><a href="javascript:menuSubmit('$m')">$text</a></td>
473 $content .= eval("qq($Lang->{CfgEdit_Header_Main})");
475 $content .= eval("qq($Lang->{CfgEdit_Header_Host})");
478 my $saveDisplay = "block";
479 $saveDisplay = "none" if ( !$In{modified}
480 || $In{editAction} eq $Lang->{CfgEdit_Button_Save} );
482 # Add action and host to the URL so the nav bar link is
485 my $url = "$MyURL?action=editConfig";
486 $url .= "&host=$host" if ( $host ne "" );
488 <table border="0" cellpadding="2">
491 <form method="post" name="form1" action="$url">
492 <input type="hidden" name="host" value="$host">
493 <input type="hidden" name="menu" value="$menu">
494 <input type="hidden" name="newMenu" value="">
495 <input type="hidden" name="modified" value="$In{modified}">
496 <input type="hidden" name="deleteVar" value="">
497 <input type="hidden" name="insertVar" value="">
498 <input type="hidden" name="overrideUncheck" value="">
499 <input type="hidden" name="addVar" value="">
500 <input type="hidden" name="action" value="editConfig">
501 <input type="submit" class="editSaveButton" style="display: $saveDisplay" name="editAction" value="${EscHTML($Lang->{CfgEdit_Button_Save})}">
503 <script language="javascript" type="text/javascript">
506 function deleteSubmit(varName)
508 document.form1.deleteVar.value = varName;
509 document.form1.modified.value = 1;
510 document.form1.submit();
514 function insertSubmit(varName)
516 document.form1.insertVar.value = varName;
517 document.form1.modified.value = 1;
518 document.form1.submit();
522 function addSubmit(varName, checkKey)
525 && eval("document.form1.addVarKey_" + varName + ".value") == "" ) {
526 alert("New key must be non-empty");
529 document.form1.addVar.value = varName;
530 document.form1.modified.value = 1;
531 document.form1.submit();
535 function menuSubmit(menuName)
537 document.form1.newMenu.value = menuName;
538 document.form1.submit();
541 function varChange(varName)
543 document.form1.editAction.style.display = "block";
544 document.form1.modified.value = 1;
547 function checkboxChange(varName)
549 document.form1.editAction.style.display = "block";
550 document.form1.modified.value = 1;
551 // Do nothing if the checkbox is now set
552 if ( eval("document.form1.override_" + varName + ".checked") ) {
556 var varRE = new RegExp("^v_z_(" + varName + ".*)");
557 var origRE = new RegExp("^orig_z_(" + varName + ".*)");
558 for ( var i = 0 ; i < document.form1.elements.length ; i++ ) {
559 var e = document.form1.elements[i];
561 if ( (re = varRE.exec(e.name)) != null ) {
562 if ( allVars[re[1]] == null ) {
566 //debugMsg("found v_z_ match with " + re[1]);
567 //debugMsg("allVars[" + re[1] + "] = " + allVars[re[1]]);
568 } else if ( (re = origRE.exec(e.name)) != null ) {
569 if ( allVars[re[1]] == null ) {
573 //debugMsg("allVars[" + re[1] + "] = " + allVars[re[1]]);
577 for ( v in allVars ) {
578 if ( allVars[v] != 0 ) {
579 //debugMsg("Not the same shape because of " + v);
582 // copy the original variable values
583 //debugMsg("setting " + v);
584 eval("document.form1.v_z_" + v + ".value = document.form1.orig_z_" + v + ".value");
590 // need to rebuild the form since the compound variable
592 document.form1.overrideUncheck.value = varName;
593 document.form1.submit();
598 function checkboxSet(varName)
600 document.form1.editAction.style.display = "block";
601 document.form1.modified.value = 1;
602 eval("document.form1.override_" + varName + ".checked = 1;");
606 var debugCounter = 0;
607 function debugMsg(msg)
610 var t = document.createTextNode(debugCounter + ": " + msg);
611 var br = document.createElement("br");
612 var debug = document.getElementById("debug");
613 debug.appendChild(t);
614 debug.appendChild(br);
617 function displayHelp(varName)
619 var help = document.getElementById("id_" + varName);
620 help.style.display = help.style.display == "block" ? "none" : "block";
626 <span id="debug">$debugText</span>
631 <table border="1" cellspacing="0">
637 # There is a special case of the user deleting just the field
638 # that has the error(s). So if the delete variable is a match
639 # or parent to all the errors then ignore the errors.
641 if ( $In{deleteVar} ne "" && %$errors > 0 ) {
643 foreach my $v ( keys(%$errors) ) {
644 if ( $v ne $In{deleteVar} && $v !~ /^\Q$In{deleteVar}_z_/ ) {
649 $errors = {} if ( $matchAll );
652 my $isError = %$errors;
654 if ( !$isError && $In{editAction} eq $Lang->{CfgEdit_Button_Save} ) {
657 $hostConf = $bpc->ConfigDataRead($host) if ( !defined($hostConf) );
658 my %hostConf2 = %$hostConf;
659 foreach my $param ( keys(%$newConf) ) {
660 if ( $override->{$param} ) {
661 $hostConf->{$param} = $newConf->{$param}
663 delete($hostConf->{$param});
666 $mesg = configDiffMesg($host, \%hostConf2, $hostConf);
667 $err .= $bpc->ConfigDataWrite($host, $hostConf);
669 $mainConf = $bpc->ConfigDataRead() if ( !defined($mainConf) );
672 my($hostsNew, $allHosts, $copyConf);
673 foreach my $entry ( @{$newConf->{Hosts}} ) {
674 next if ( $entry->{host} eq "" );
675 $allHosts->{$entry->{host}} = 1;
676 $allHosts->{$1} = 1 if ( $entry->{host} =~ /(.+?)\s*=/ );
678 foreach my $entry ( @{$newConf->{Hosts}} ) {
679 next if ( $entry->{host} eq ""
680 || defined($hostsNew->{$entry->{host}}) );
681 if ( $entry->{host} =~ /(.+?)\s*=\s*(.+)/ ) {
682 if ( defined($allHosts->{$2}) ) {
684 $copyConf->{$1} = $2;
686 my $fullHost = $entry->{host};
688 $err .= eval("qq($Lang->{CfgEdit_Error_Copy_host_does_not_exist})");
691 push(@$hostsSave, $entry);
692 $hostsNew->{$entry->{host}} = $entry;
694 ($mesg, my $hostChange) = hostsDiffMesg($hostsNew);
695 $bpc->HostInfoWrite($hostsNew) if ( $hostChange );
696 foreach my $host ( keys(%$copyConf) ) {
697 my $confData = $bpc->ConfigDataRead($copyConf->{$host});
698 my $fromHost = $copyConf->{$host};
699 $err .= $bpc->ConfigDataWrite($host, $confData);
700 $mesg .= eval("qq($Lang->{CfgEdit_Log_Copy_host_config})");
703 delete($newConf->{Hosts});
704 $mesg .= configDiffMesg(undef, $mainConf, $newConf);
705 $mainConf = { %$mainConf, %$newConf };
706 $err .= $bpc->ConfigDataWrite(undef, $mainConf);
707 $newConf->{Hosts} = $hostsSave;
709 if ( defined($err) ) {
711 <tr><td colspan="2" class="border"><span class="editError">$err</span></td></tr>
714 $bpc->ServerConnect();
716 (my $mesgBR = $mesg) =~ s/\n/<br>\n/g;
717 # uncomment this if you want the changes to be displayed
719 #<tr><td colspan="2" class="border"><span class="editComment">$mesgBR</span></td></tr>
721 foreach my $str ( split(/\n/, $mesg) ) {
722 $bpc->ServerMesg("log $str") if ( $str ne "" );
726 # Tell the server to reload, unless we only changed
729 $bpc->ServerMesg("server reload") if ( $host eq "" );
732 my @mask = @{$menuDisable{$menu}{mask} || []};
734 foreach my $paramInfo ( @{$ConfigMenu{$menu}{param}} ) {
736 my $param = $paramInfo->{name};
737 my $disabled = shift(@mask);
739 next if ( $disabled || $menuDisable{$menu}{top} );
740 if ( ref($paramInfo->{visible}) eq "CODE"
741 && !&{$paramInfo->{visible}}($newConf) ) {
745 if ( defined($paramInfo->{text}) ) {
746 my $text = eval("qq($Lang->{$paramInfo->{text}})");
748 <tr><td colspan="2" class="editHeader">$text</td></tr>
754 # TODO: get parameter documentation
757 #$comment =~ s/\'//g;
758 #$comment =~ s/\"//g;
759 #$comment =~ s/\n/ /g;
761 $doneParam->{$param} = 1;
763 $content .= fieldEditBuild($ConfigMeta{$param},
770 $paramInfo->{onchangeSubmit},
771 defined($override) ? $param : undef,
772 defined($override) ? $override->{$param} : undef
774 if ( defined($paramInfo->{comment}) ) {
775 my $topDir = $bpc->TopDir;
776 my $text = eval("qq($Lang->{$paramInfo->{comment}})");
778 <tr><td colspan="2" class="editComment">$text</td></tr>
784 # Emit any remaining errors - should not happen
786 foreach my $param ( sort(keys(%$errors)) ) {
788 <tr><td colspan="2" class="border"><span class="editError">$errors->{$param}</span></td></tr>
790 delete($errors->{$param});
798 # Emit all the remaining editable config settings as hidden values
800 foreach my $param ( keys(%ConfigMeta) ) {
801 next if ( $doneParam->{$param} );
803 && (!defined($bpc->{Conf}{CgiUserConfigEdit}{$param})
805 && !$bpc->{Conf}{CgiUserConfigEdit}{$param})) );
806 $content .= fieldHiddenBuild($ConfigMeta{$param},
811 if ( defined($override) ) {
813 <input type="hidden" name="override_$param" value="$override->{$param}">
816 $doneParam->{$param} = 1;
819 if ( defined($In{menu}) || $In{editAction} eq $Lang->{CfgEdit_Button_Save} ) {
820 if ( $In{editAction} eq $Lang->{CfgEdit_Button_Save}
823 # Emit the new settings as orig_z_ parameters
826 foreach my $param ( keys(%ConfigMeta) ) {
827 next if ( $doneParam->{$param} );
829 && (!defined($bpc->{Conf}{CgiUserConfigEdit}{$param})
831 && !$bpc->{Conf}{CgiUserConfigEdit}{$param})) );
832 $contentHidden .= fieldHiddenBuild($ConfigMeta{$param},
837 $doneParam->{$param} = 1;
842 # Just switching menus: copy all the orig_z_ input parameters
844 foreach my $var ( keys(%In) ) {
845 next if ( $var !~ /^orig_z_/ );
846 my $val = decode_utf8($In{$var});
847 $contentHidden .= <<EOF;
848 <input type="hidden" name="$var" value="${EscHTML($val)}">
854 # First time: emit all the original config settings
857 foreach my $param ( keys(%ConfigMeta) ) {
858 next if ( $doneParam->{$param} );
860 && (!defined($bpc->{Conf}{CgiUserConfigEdit}{$param})
862 && !$bpc->{Conf}{CgiUserConfigEdit}{$param})) );
863 $contentHidden .= fieldHiddenBuild($ConfigMeta{$param},
868 $doneParam->{$param} = 1;
879 Header("Config Edit", $content);
885 my($type, $varName, $varValue, $prefix) = @_;
888 $type = { type => $type } if ( ref($type) ne "HASH" );
890 if ( $type->{type} eq "list" ) {
891 $varValue = [] if ( !defined($varValue) );
892 $varValue = [$varValue] if ( ref($varValue) ne "ARRAY" );
894 for ( my $i = 0 ; $i < @$varValue ; $i++ ) {
895 $content .= fieldHiddenBuild($type->{child}, "${varName}_z_$i",
896 $varValue->[$i], $prefix);
898 } elsif ( $type->{type} eq "hash" || $type->{type} eq "horizHash" ) {
899 $varValue = {} if ( ref($varValue) ne "HASH" );
900 my(@order, $childType);
902 if ( defined($type->{order}) ) {
903 @order = @{$type->{order}};
904 } elsif ( defined($type->{child}) ) {
905 @order = sort(keys(%{$type->{child}}));
907 @order = sort(keys(%$varValue));
910 foreach my $fld ( @order ) {
911 if ( defined($type->{child}) ) {
912 $childType = $type->{child}{$fld};
914 $childType = $type->{childType};
916 # emit list of fields since they are user-defined
917 # rather than hard-coded
920 <input type="hidden" name="vflds.$varName" value="${EscHTML($fld)}">
923 $content .= fieldHiddenBuild($childType, "${varName}_z_$fld",
924 $varValue->{$fld}, $prefix);
926 } elsif ( $type->{type} eq "shortlist" ) {
927 $varValue = [$varValue] if ( ref($varValue) ne "ARRAY" );
928 $varValue = join(", ", @$varValue);
930 <input type="hidden" name="${prefix}_z_$varName" value="${EscHTML($varValue)}">
934 <input type="hidden" name="${prefix}_z_$varName" value="${EscHTML($varValue)}">
942 my($type, $varName, $varValue, $errors, $level, $comment, $isError,
943 $onchangeSubmit, $overrideVar, $overrideSet) = @_;
946 my $size = 50 - 10 * $level;
947 $type = { type => $type } if ( ref($type) ne "HASH" );
949 $size = $type->{size} if ( defined($type->{size}) );
952 # These fragments allow inline content to be turned on and off
954 # <tr><td colspan="2"><span id="id_$varName" style="display: none" class="editComment">$comment</span></td></tr>
955 # <tr><td class="border"><a href="javascript: displayHelp('$varName')">$varName</a>
959 my $lcVarName = lc($varName);
961 <tr><td class="border"><a href="?action=view&type=docs#item_%24conf%7b$lcVarName%7d">$varName</a>
963 if ( defined($overrideVar) ) {
964 my $override_checked = "";
965 if ( !$isError && $In{deleteVar} =~ /^\Q${varName}_z_/
966 || !$isError && $In{insertVar} =~ /^\Q${varName}\E(_z_|$)/
967 || !$isError && $In{addVar} =~ /^\Q${varName}\E(_z_|$)/ ) {
970 if ( $overrideSet ) {
971 $override_checked = "checked";
974 <br><input type="checkbox" name="override_$varName" $override_checked value="1" onClick="checkboxChange('$varName')">\ ${EscHTML($Lang->{CfgEdit_Button_Override})}
977 $content .= "</td>\n";
980 if ( $type->{type} eq "list" ) {
981 $content .= "<td class=\"border\">\n";
982 $varValue = [] if ( !defined($varValue) );
983 $varValue = [$varValue] if ( ref($varValue) ne "ARRAY" );
984 if ( !$isError && $In{deleteVar} =~ /^\Q${varName}_z_\E(\d+)$/
985 && $1 < @$varValue ) {
987 # User deleted entry in this array
989 splice(@$varValue, $1, 1) if ( @$varValue > 1 || $type->{emptyOk} );
992 if ( !$isError && $In{insertVar} =~ /^\Q${varName}_z_\E(\d+)$/
993 && $1 < @$varValue ) {
995 # User inserted entry in this array
997 splice(@$varValue, $1, 0, "")
998 if ( @$varValue > 1 || $type->{emptyOk} );
1001 if ( !$isError && $In{addVar} eq $varName ) {
1003 # User added entry to this array
1005 push(@$varValue, undef);
1008 $content .= "<table border=\"1\" cellspacing=\"0\">\n";
1010 if ( ref($type) eq "HASH" && ref($type->{child}) eq "HASH"
1011 && $type->{child}{type} eq "horizHash" ) {
1013 if ( defined($type->{child}{order}) ) {
1014 @order = @{$type->{child}{order}};
1016 @order = sort(keys(%{$type->{child}{child}}));
1018 $content .= "<tr><td class=\"border\"></td>\n";
1019 for ( my $i = 0 ; $i < @order ; $i++ ) {
1020 $content .= "<td>$order[$i]</td>\n";
1022 $content .= "</tr>\n";
1023 for ( my $i = 0 ; $i < @$varValue ; $i++ ) {
1024 if ( @$varValue > 1 || $type->{emptyOk} ) {
1027 <input type="button" name="del_${varName}_z_$i" value="${EscHTML($Lang->{CfgEdit_Button_Delete})}"
1028 onClick="deleteSubmit('${varName}_z_$i')">
1032 $content .= fieldEditBuild($type->{child}, "${varName}_z_$i",
1033 $varValue->[$i], $errors, $level + 1, undef,
1034 $isError, $onchangeSubmit,
1035 $overrideVar, $overrideSet);
1036 $content .= "</tr>\n";
1039 for ( my $i = 0 ; $i < @$varValue ; $i++ ) {
1041 <tr><td class="border">
1042 <input type="button" name="ins_${varName}_z_$i" value="${EscHTML($Lang->{CfgEdit_Button_Insert})}"
1043 onClick="insertSubmit('${varName}_z_$i')">
1045 if ( @$varValue > 1 || $type->{emptyOk} ) {
1047 <input type="button" name="del_${varName}_z_$i" value="${EscHTML($Lang->{CfgEdit_Button_Delete})}"
1048 onClick="deleteSubmit('${varName}_z_$i')">
1051 $content .= "</td>\n";
1052 $content .= fieldEditBuild($type->{child}, "${varName}_z_$i",
1053 $varValue->[$i], $errors, $level + 1, undef,
1054 $isError, $onchangeSubmit,
1055 $overrideVar, $overrideSet);
1056 $content .= "</tr>\n";
1060 <tr><td class="border"><input type="button" name="add_$varName" value="${EscHTML($Lang->{CfgEdit_Button_Add})}"
1061 onClick="addSubmit('$varName')"></td></tr>
1064 $content .= "</td>\n";
1065 } elsif ( $type->{type} eq "hash" ) {
1066 $content .= "<td class=\"border\">\n";
1067 $content .= "<table border=\"1\" cellspacing=\"0\">\n";
1068 $varValue = {} if ( ref($varValue) ne "HASH" );
1070 if ( !$isError && !$type->{noKeyEdit}
1071 && $In{deleteVar} !~ /^\Q${varName}_z_\E.*_z_/
1072 && $In{deleteVar} =~ /^\Q${varName}_z_\E(\w+)$/ ) {
1074 # User deleted entry in this hash
1076 delete($varValue->{$1}) if ( keys(%$varValue) > 1
1077 || $type->{emptyOk} );
1078 $In{deleteVar} = "";
1080 if ( !$isError && !defined($type->{child})
1081 && $In{addVar} eq $varName ) {
1083 # User added entry to this array
1085 $varValue->{$In{"addVarKey_$varName"}} = ""
1086 if ( !defined($varValue->{$In{"addVarKey_$varName"}}) );
1089 my(@order, $childType);
1091 if ( defined($type->{order}) ) {
1092 @order = @{$type->{order}};
1093 } elsif ( defined($type->{child}) ) {
1094 @order = sort(keys(%{$type->{child}}));
1096 @order = sort(keys(%$varValue));
1099 foreach my $fld ( @order ) {
1101 <tr><td class="border">$fld
1103 if ( !$type->{noKeyEdit}
1104 && (keys(%$varValue) > 1 || $type->{emptyOk}) ) {
1106 <input type="submit" name="del_${varName}_z_$fld" value="${EscHTML($Lang->{CfgEdit_Button_Delete})}"
1107 onClick="deleteSubmit('${varName}_z_$fld')">
1110 if ( defined($type->{child}) ) {
1111 $childType = $type->{child}{$fld};
1113 $childType = $type->{childType};
1115 # emit list of fields since they are user-defined
1116 # rather than hard-coded
1119 <input type="hidden" name="vflds.$varName" value="${EscHTML($fld)}">
1122 $content .= "</td>\n";
1123 $content .= fieldEditBuild($childType, "${varName}_z_$fld",
1124 $varValue->{$fld}, $errors, $level + 1, undef,
1125 $isError, $onchangeSubmit,
1126 $overrideVar, $overrideSet);
1127 $content .= "</tr>\n";
1130 if ( !$type->{noKeyEdit} ) {
1132 <tr><td class="border" colspan="2">
1133 $Lang->{CfgEdit_Button_New_Key}: <input type="text" name="addVarKey_$varName" size="20" maxlength="256" value="">
1134 <input type="button" name="add_$varName" value="${EscHTML($Lang->{CfgEdit_Button_Add})}" onClick="addSubmit('$varName', 1)">
1138 $content .= "</table>\n";
1139 $content .= "</td>\n";
1140 } elsif ( $type->{type} eq "horizHash" ) {
1141 $varValue = {} if ( ref($varValue) ne "HASH" );
1142 my(@order, $childType);
1144 if ( defined($type->{order}) ) {
1145 @order = @{$type->{order}};
1146 } elsif ( defined($type->{child}) ) {
1147 @order = sort(keys(%{$type->{child}}));
1149 @order = sort(keys(%$varValue));
1152 foreach my $fld ( @order ) {
1153 if ( defined($type->{child}) ) {
1154 $childType = $type->{child}{$fld};
1156 $childType = $type->{childType};
1158 # emit list of fields since they are user-defined
1159 # rather than hard-coded
1162 <input type="hidden" name="vflds.$varName" value="${EscHTML($fld)}">
1165 $content .= fieldEditBuild($childType, "${varName}_z_$fld",
1166 $varValue->{$fld}, $errors, $level + 1, undef,
1167 $isError, $onchangeSubmit,
1168 $overrideVar, $overrideSet);
1171 $content .= "<td class=\"border\">\n";
1174 # If there was an error, we use the original post values
1175 # in %In, rather than the parsed values in $varValue.
1176 # This is so that the user's erroneous input is preserved.
1178 $varValue = $In{"v_z_$varName"} if ( defined($In{"v_z_$varName"}) );
1180 if ( defined($errors->{$varName}) ) {
1182 <span class="editError">$errors->{$varName}</span><br>
1184 delete($errors->{$varName});
1187 if ( defined($overrideVar) ) {
1188 $onChange .= "checkboxSet('$overrideVar');";
1190 $onChange .= "varChange('$overrideVar');";
1192 if ( $onchangeSubmit ) {
1193 $onChange .= "document.form1.submit();";
1195 if ( $onChange ne "" ) {
1196 $onChange = " onChange=\"$onChange\"";
1198 if ( $varValue !~ /\n/ &&
1199 ($type->{type} eq "integer"
1200 || $type->{type} eq "string"
1201 || $type->{type} eq "execPath"
1202 || $type->{type} eq "shortlist"
1203 || $type->{type} eq "float") ) {
1205 if ( $type->{type} eq "shortlist" ) {
1206 $varValue = [$varValue] if ( ref($varValue) ne "ARRAY" );
1207 $varValue = join(", ", @$varValue);
1209 my $textType = ($varName =~ /Passwd/) ? "password" : "text";
1211 <input type="$textType" class="editTextInput" name="v_z_$varName" size="$size" maxlength="256" value="${EscHTML($varValue)}"$onChange>
1213 } elsif ( $type->{type} eq "boolean" ) {
1215 my $checked = "checked" if ( $varValue );
1217 <input type="checkbox" name="v_z_$varName" $checked value="1"$onChange>
1219 } elsif ( $type->{type} eq "select" ) {
1221 <select name="v_z_$varName"$onChange>
1223 foreach my $option ( @{$type->{values}} ) {
1224 my $sel = " selected" if ( $varValue eq $option );
1225 $content .= "<option$sel>$option</option>\n";
1227 $content .= "</select>\n";
1229 # multi-line text area - count number of lines
1230 my $rowCnt = $varValue =~ tr/\n//;
1231 $rowCnt = 1 if ( $rowCnt < 1 );
1233 <textarea name="v_z_$varName" class="editTextArea" cols="$size" rows="$rowCnt"$onChange>${EscHTML($varValue)}</textarea>
1236 $content .= "</td>\n";
1245 foreach my $param ( keys(%ConfigMeta) ) {
1246 fieldErrorCheck($ConfigMeta{$param}, $param, $errors);
1253 my($type, $varName, $errors) = @_;
1255 $type = { type => $type } if ( ref($type) ne "HASH" );
1257 if ( $type->{type} eq "list" ) {
1258 for ( my $i = 0 ; ; $i++ ) {
1259 last if ( fieldErrorCheck($type->{child}, "${varName}_z_$i", $errors) );
1261 } elsif ( $type->{type} eq "hash" || $type->{type} eq "horizHash" ) {
1262 my(@order, $childType);
1265 if ( defined($type->{order}) ) {
1266 @order = @{$type->{order}};
1267 } elsif ( defined($type->{child}) ) {
1268 @order = sort(keys(%{$type->{child}}));
1270 @order = split(/\0/, $In{"vflds.$varName"});
1272 foreach my $fld ( @order ) {
1273 if ( defined($type->{child}) ) {
1274 $childType = $type->{child}{$fld};
1276 $childType = $type->{childType};
1278 $ret ||= fieldErrorCheck($childType, "${varName}_z_$fld", $errors);
1282 $In{"v_z_$varName"} = "0" if ( $type->{type} eq "boolean"
1283 && $In{"v_z_$varName"} eq "" );
1285 return 1 if ( !exists($In{"v_z_$varName"}) );
1287 (my $var = $varName) =~ s/_z_/./g;
1289 if ( $type->{type} eq "integer"
1290 || $type->{type} eq "boolean" ) {
1291 if ( $In{"v_z_$varName"} !~ /^-?\d+\s*$/s
1292 && $In{"v_z_$varName"} ne "" ) {
1293 $errors->{$varName} = eval("qq{$Lang->{CfgEdit_Error__must_be_an_integer}}");
1295 } elsif ( $type->{type} eq "float" ) {
1296 if ( $In{"v_z_$varName"} !~ /^-?\d*(\.\d*)?\s*$/s
1297 && $In{"v_z_$varName"} ne "" ) {
1299 = eval("qq{$Lang->{CfgEdit_Error__must_be_real_valued_number}}");
1301 } elsif ( $type->{type} eq "shortlist" ) {
1302 my @vals = split(/[,\s]+/, $In{"v_z_$varName"});
1303 for ( my $i = 0 ; $i < @vals ; $i++ ) {
1304 if ( $type->{child} eq "integer"
1305 && $vals[$i] !~ /^-?\d+\s*$/s
1306 && $vals[$i] ne "" ) {
1308 $errors->{$varName} = eval("qq{$Lang->{CfgEdit_Error__entry__must_be_an_integer}}");
1309 } elsif ( $type->{child} eq "float"
1310 && $vals[$i] !~ /^-?\d*(\.\d*)?\s*$/s
1311 && $vals[$i] ne "" ) {
1313 $errors->{$varName} = eval("qq{$Lang->{CfgEdit_Error__entry__must_be_real_valued_number}}");
1316 } elsif ( $type->{type} eq "select" ) {
1318 foreach my $option ( @{$type->{values}} ) {
1319 if ( $In{"v_z_$varName"} eq $option ) {
1324 $errors->{$varName} = eval("qq{$Lang->{CfgEdit_Error__must_be_valid_option}}")
1326 } elsif ( $type->{type} eq "execPath" ) {
1327 if ( $In{"v_z_$varName"} ne "" && !-x $In{"v_z_$varName"} ) {
1328 $errors->{$varName} = eval("qq{$Lang->{CfgEdit_Error__must_be_executable_program}}");
1332 # $type->{type} eq "string": no error checking
1341 my($bpc, $userHost) = @_;
1345 foreach my $param ( keys(%ConfigMeta) ) {
1348 && (!defined($bpc->{Conf}{CgiUserConfigEdit}{$param})
1350 && !$bpc->{Conf}{CgiUserConfigEdit}{$param})) );
1351 fieldInputParse($ConfigMeta{$param}, $param, \$value);
1352 $conf->{$param} = $value;
1353 $override->{$param} = $In{"override_$param"};
1355 return ($conf, $override);
1360 my($type, $varName, $value) = @_;
1362 $type = { type => $type } if ( ref($type) ne "HASH" );
1364 if ( $type->{type} eq "list" ) {
1366 for ( my $i = 0 ; ; $i++ ) {
1368 last if ( fieldInputParse($type->{child}, "${varName}_z_$i", \$val) );
1369 push(@$$value, $val);
1371 $$value = undef if ( $type->{undefIfEmpty} && @$$value == 0 );
1372 } elsif ( $type->{type} eq "hash" || $type->{type} eq "horizHash" ) {
1373 my(@order, $childType);
1377 if ( defined($type->{order}) ) {
1378 @order = @{$type->{order}};
1379 } elsif ( defined($type->{child}) ) {
1380 @order = sort(keys(%{$type->{child}}));
1382 @order = split(/\0/, $In{"vflds.$varName"});
1385 foreach my $fld ( @order ) {
1387 if ( defined($type->{child}) ) {
1388 $childType = $type->{child}{$fld};
1390 $childType = $type->{childType};
1392 $ret ||= fieldInputParse($childType, "${varName}_z_$fld", \$val);
1394 $$value->{$fld} = $val;
1398 if ( $type->{type} eq "boolean" ) {
1399 $$value = 0 + $In{"v_z_$varName"};
1400 } elsif ( !exists($In{"v_z_$varName"}) ) {
1404 if ( $type->{type} eq "integer" ) {
1405 $$value = 0 + $In{"v_z_$varName"};
1406 } elsif ( $type->{type} eq "float" ) {
1407 $$value = 0 + $In{"v_z_$varName"};
1408 } elsif ( $type->{type} eq "shortlist" ) {
1409 $$value = [split(/[,\s]+/, $In{"v_z_$varName"})];
1410 if ( $type->{child} eq "float"
1411 || $type->{child} eq "integer"
1412 || $type->{child} eq "boolean" ) {
1413 foreach ( @$$value ) {
1418 $$value = decode_utf8($In{"v_z_$varName"});
1419 $$value =~ s/\r\n/\n/g;
1421 $$value = undef if ( $type->{undefIfEmpty} && $$value eq "" );
1428 my($host, $oldConf, $newConf) = @_;
1432 if ( $host ne "" ) {
1433 $conf = "host $host config";
1435 $conf = "main config";
1438 foreach my $p ( keys(%ConfigMeta) ) {
1439 if ( !exists($oldConf->{$p}) && !exists($newConf->{$p}) ) {
1441 } elsif ( exists($oldConf->{$p}) && !exists($newConf->{$p}) ) {
1442 $mesg .= eval("qq($Lang->{CfgEdit_Log_Delete_param})");
1443 } elsif ( !exists($oldConf->{$p}) && exists($newConf->{$p}) ) {
1444 my $dump = Data::Dumper->new([$newConf->{$p}]);
1448 my $value = $dump->Dump;
1449 $value =~ s/\n/\\n/g;
1450 $value =~ s/\r/\\r/g;
1451 $mesg .= eval("qq($Lang->{CfgEdit_Log_Add_param_value})");
1453 my $dump = Data::Dumper->new([$newConf->{$p}]);
1457 my $valueNew = $dump->Dump;
1459 my $v = $oldConf->{$p};
1460 if ( ref($newConf->{$p}) eq "ARRAY" && ref($v) eq "" ) {
1463 $dump = Data::Dumper->new([$v]);
1467 my $valueOld = $dump->Dump;
1469 (my $valueNew2 = $valueNew) =~ s/['\n\r]//g;
1470 (my $valueOld2 = $valueOld) =~ s/['\n\r]//g;
1472 next if ( $valueOld2 eq $valueNew2 );
1474 $valueNew =~ s/\n/\\n/g;
1475 $valueOld =~ s/\n/\\n/g;
1476 $valueNew =~ s/\r/\\r/g;
1477 $valueOld =~ s/\r/\\r/g;
1479 $mesg .= eval("qq($Lang->{CfgEdit_Log_Change_param_value})");
1488 my $hostsOld = $bpc->HostInfoRead();
1489 my($mesg, $hostChange);
1491 foreach my $host ( keys(%$hostsOld) ) {
1492 if ( !defined($hostsNew->{$host}) ) {
1493 $mesg .= eval("qq($Lang->{CfgEdit_Log_Host_Delete})");
1497 foreach my $key ( keys(%{$hostsNew->{$host}}) ) {
1498 next if ( $hostsNew->{$host}{$key} eq $hostsOld->{$host}{$key} );
1499 my $valueOld = $hostsOld->{$host}{$key};
1500 my $valueNew = $hostsNew->{$host}{$key};
1501 $mesg .= eval("qq($Lang->{CfgEdit_Log_Host_Change})");
1506 foreach my $host ( keys(%$hostsNew) ) {
1507 next if ( defined($hostsOld->{$host}) );
1508 my $dump = Data::Dumper->new([$hostsNew->{$host}]);
1512 my $value = $dump->Dump;
1513 $value =~ s/\n/\\n/g;
1514 $value =~ s/\r/\\r/g;
1515 $mesg .= eval("qq($Lang->{CfgEdit_Log_Host_Add})");
1518 return ($mesg, $hostChange);