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) 2004 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.0alpha, released 23 Jan 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;
47 text => "CfgEdit_Title_Server",
49 {text => "CfgEdit_Title_General_Parameters"},
50 {name => "ServerHost"},
51 {name => "BackupPCUser"},
52 {name => "BackupPCUserVerify"},
53 {name => "MaxOldLogFiles"},
54 {name => "TrashCleanSleepSec"},
56 {text => "CfgEdit_Title_Wakeup_Schedule"},
57 {name => "WakeupSchedule"},
59 {text => "CfgEdit_Title_Concurrent_Jobs"},
60 {name => "MaxBackups"},
61 {name => "MaxUserBackups"},
62 {name => "MaxPendingCmds"},
63 {name => "MaxBackupPCNightlyJobs"},
64 {name => "BackupPCNightlyPeriod"},
66 {text => "CfgEdit_Title_Pool_Filesystem_Limits"},
68 {name => "DfMaxUsagePct"},
69 {name => "HardLinkMax"},
71 {text => "CfgEdit_Title_Other_Parameters"},
72 {name => "UmaskMode"},
74 {name => "DHCPAddressRanges"},
75 {name => "PerlModuleLoad"},
76 {name => "ServerInitdPath"},
77 {name => "ServerInitdStartCmd"},
79 {text => "CfgEdit_Title_Remote_Apache_Settings"},
80 {name => "ServerPort"},
81 {name => "ServerMesgSecret"},
83 {text => "CfgEdit_Title_Program_Paths"},
85 {name => "NmbLookupPath"},
88 {name => "SplitPath"},
92 {name => "Bzip2Path"},
94 {text => "CfgEdit_Title_Install_Paths"},
99 {name => "InstallDir"},
103 text => "CfgEdit_Title_Email",
105 {text => "CfgEdit_Title_Email_settings"},
106 {name => "SendmailPath"},
107 {name => "EMailNotifyMinDays"},
108 {name => "EMailFromUserName"},
109 {name => "EMailAdminUserName"},
110 {name => "EMailUserDestDomain"},
112 {text => "CfgEdit_Title_Email_User_Messages"},
113 {name => "EMailNoBackupEverSubj"},
114 {name => "EMailNoBackupEverMesg"},
115 {name => "EMailNotifyOldBackupDays"},
116 {name => "EMailNoBackupRecentSubj"},
117 {name => "EMailNoBackupRecentMesg"},
118 {name => "EMailNotifyOldOutlookDays"},
119 {name => "EMailOutlookBackupSubj"},
120 {name => "EMailOutlookBackupMesg"},
121 {name => "EMailHeaders"},
125 text => "CfgEdit_Title_CGI",
127 {text => "CfgEdit_Title_Admin_Privileges"},
128 {name => "CgiAdminUserGroup"},
129 {name => "CgiAdminUsers"},
131 {text => "CfgEdit_Title_Page_Rendering"},
132 {name => "Language"},
133 {name => "CgiNavBarAdminAllHosts"},
134 {name => "CgiSearchBoxEnable"},
135 {name => "CgiNavBarLinks"},
136 {name => "CgiStatusHilightColor"},
137 {name => "CgiDateFormatMMDD"},
138 {name => "CgiHeaders"},
139 {name => "CgiExt2ContentType"},
140 {name => "CgiCSSFile"},
142 {text => "CfgEdit_Title_Paths"},
144 {name => "CgiImageDir"},
145 {name => "CgiImageDirURL"},
147 {text => "CfgEdit_Title_User_URLs"},
148 {name => "CgiUserHomePageCheck"},
149 {name => "CgiUserUrlCreate"},
151 {text => "CfgEdit_Title_User_Config_Editing"},
152 {name => "CgiUserConfigEditEnable"},
153 {name => "CgiUserConfigEdit"},
157 text => "CfgEdit_Title_Xfer",
159 {text => "CfgEdit_Title_Xfer_Settings"},
160 {name => "XferMethod", onchangeSubmit => 1},
161 {name => "XferLogLevel"},
162 {name => "ClientCharset"},
164 {text => "CfgEdit_Title_Smb_Settings",
165 visible => sub { return $_[0]->{XferMethod} eq "smb"; } },
166 {name => "SmbShareName",
167 visible => sub { return $_[0]->{XferMethod} eq "smb"; } },
168 {name => "SmbShareUserName",
169 visible => sub { return $_[0]->{XferMethod} eq "smb"; } },
170 {name => "SmbSharePasswd",
171 visible => sub { return $_[0]->{XferMethod} eq "smb"; } },
173 {text => "CfgEdit_Title_Tar_Settings",
174 visible => sub { return $_[0]->{XferMethod} eq "tar"; } },
175 {name => "TarShareName",
176 visible => sub { return $_[0]->{XferMethod} eq "tar"; } },
178 {text => "CfgEdit_Title_Rsync_Settings",
179 visible => sub { return $_[0]->{XferMethod} eq "rsync"; } },
180 {text => "CfgEdit_Title_Rsyncd_Settings",
181 visible => sub { return $_[0]->{XferMethod} eq "rsyncd"; } },
182 {name => "RsyncShareName",
183 visible => sub { return $_[0]->{XferMethod} =~ /rsync/; } },
184 {name => "RsyncdUserName",
185 visible => sub { return $_[0]->{XferMethod} eq "rsyncd"; } },
186 {name => "RsyncdPasswd",
187 visible => sub { return $_[0]->{XferMethod} eq "rsyncd"; } },
188 {name => "RsyncdAuthRequired",
189 visible => sub { return $_[0]->{XferMethod} eq "rsyncd"; } },
190 {name => "RsyncCsumCacheVerifyProb",
191 visible => sub { return $_[0]->{XferMethod} =~ /rsync/; } },
193 {text => "CfgEdit_Title_BackupPCd_Settings",
194 visible => sub { return $_[0]->{XferMethod} eq "backuppcd"; } },
195 {name => "BackupPCdShareName",
196 visible => sub { return $_[0]->{XferMethod} eq "backuppcd"; } },
197 {name => "BackupPCdPath",
198 visible => sub { return $_[0]->{XferMethod} eq "backuppcd"; } },
199 {name => "BackupPCdCmd",
200 visible => sub { return $_[0]->{XferMethod} eq "backuppcd"; } },
201 {name => "BackupPCdRestoreCmd",
202 visible => sub { return $_[0]->{XferMethod} eq "backuppcd"; } },
204 {text => "CfgEdit_Title_Archive_Settings",
205 visible => sub { return $_[0]->{XferMethod} eq "archive"; } },
206 {name => "ArchiveDest",
207 visible => sub { return $_[0]->{XferMethod} eq "archive"; } },
208 {name => "ArchiveComp",
209 visible => sub { return $_[0]->{XferMethod} eq "archive"; } },
210 {name => "ArchivePar",
211 visible => sub { return $_[0]->{XferMethod} eq "archive"; } },
212 {name => "ArchiveSplit",
213 visible => sub { return $_[0]->{XferMethod} eq "archive"; } },
215 {text => "CfgEdit_Title_Include_Exclude",
216 visible => sub { return $_[0]->{XferMethod} ne "archive"; } },
217 {name => "BackupFilesOnly",
218 visible => sub { return $_[0]->{XferMethod} ne "archive"; } },
219 {name => "BackupFilesExclude",
220 visible => sub { return $_[0]->{XferMethod} ne "archive"; } },
222 {text => "CfgEdit_Title_Smb_Paths_Commands",
223 visible => sub { return $_[0]->{XferMethod} eq "smb"; } },
224 {name => "SmbClientPath",
225 visible => sub { return $_[0]->{XferMethod} eq "smb"; } },
226 {name => "SmbClientFullCmd",
227 visible => sub { return $_[0]->{XferMethod} eq "smb"; } },
228 {name => "SmbClientIncrCmd",
229 visible => sub { return $_[0]->{XferMethod} eq "smb"; } },
230 {name => "SmbClientRestoreCmd",
231 visible => sub { return $_[0]->{XferMethod} eq "smb"; } },
233 {text => "CfgEdit_Title_Tar_Paths_Commands",
234 visible => sub { return $_[0]->{XferMethod} eq "tar"; } },
235 {name => "TarClientPath",
236 visible => sub { return $_[0]->{XferMethod} eq "tar"; } },
237 {name => "TarClientCmd",
238 visible => sub { return $_[0]->{XferMethod} eq "tar"; } },
239 {name => "TarFullArgs",
240 visible => sub { return $_[0]->{XferMethod} eq "tar"; } },
241 {name => "TarIncrArgs",
242 visible => sub { return $_[0]->{XferMethod} eq "tar"; } },
243 {name => "TarClientRestoreCmd",
244 visible => sub { return $_[0]->{XferMethod} eq "tar"; } },
246 {text => "CfgEdit_Title_Rsync_Paths_Commands_Args",
247 visible => sub { return $_[0]->{XferMethod} eq "rsync"; } },
248 {text => "CfgEdit_Title_Rsyncd_Port_Args",
249 visible => sub { return $_[0]->{XferMethod} eq "rsyncd"; } },
250 {name => "RsyncClientPath",
251 visible => sub { return $_[0]->{XferMethod} eq "rsync"; } },
252 {name => "RsyncClientCmd",
253 visible => sub { return $_[0]->{XferMethod} eq "rsync"; } },
254 {name => "RsyncClientRestoreCmd",
255 visible => sub { return $_[0]->{XferMethod} eq "rsync"; } },
256 {name => "RsyncdClientPort",
257 visible => sub { return $_[0]->{XferMethod} eq "rsyncd"; } },
258 {name => "RsyncArgs",
259 visible => sub { return $_[0]->{XferMethod} =~ /rsync/; } },
260 {name => "RsyncRestoreArgs",
261 visible => sub { return $_[0]->{XferMethod} =~ /rsync/; } },
263 {text => "CfgEdit_Title_Archive_Paths_Commands",
264 visible => sub { return $_[0]->{XferMethod} eq "archive"; } },
265 {name => "ArchiveClientCmd",
266 visible => sub { return $_[0]->{XferMethod} eq "archive"; } },
271 text => "CfgEdit_Title_Schedule",
273 {text => "CfgEdit_Title_Full_Backups"},
274 {name => "FullPeriod"},
275 {name => "FullKeepCnt"},
276 {name => "FullKeepCntMin"},
277 {name => "FullAgeMax"},
279 {text => "CfgEdit_Title_Incremental_Backups"},
280 {name => "IncrPeriod"},
281 {name => "IncrKeepCnt"},
282 {name => "IncrKeepCntMin"},
283 {name => "IncrAgeMax"},
284 {name => "IncrFill"},
286 {text => "CfgEdit_Title_Blackouts"},
287 {name => "BlackoutBadPingLimit"},
288 {name => "BlackoutGoodCnt"},
289 {name => "BlackoutPeriods"},
291 {text => "CfgEdit_Title_Other"},
292 {name => "PartialAgeMax"},
293 {name => "RestoreInfoKeepCnt"},
294 {name => "ArchiveInfoKeepCnt"},
295 {name => "BackupZeroFilesIsFatal"},
299 text => "CfgEdit_Title_Backup_Settings",
301 {text => "CfgEdit_Title_Client_Lookup"},
302 {name => "ClientNameAlias"},
303 {name => "NmbLookupCmd"},
304 {name => "NmbLookupFindHostCmd"},
305 {name => "FixedIPNetBiosNameCheck"},
307 {name => "PingMaxMsec"},
309 {text => "CfgEdit_Title_Other"},
310 {name => "ClientTimeout"},
311 {name => "MaxOldPerPCLogFiles"},
312 {name => "CompressLevel"},
314 {text => "CfgEdit_Title_User_Commands"},
315 {name => "DumpPreUserCmd"},
316 {name => "DumpPostUserCmd"},
317 {name => "DumpPreShareCmd"},
318 {name => "DumpPostShareCmd"},
319 {name => "RestorePreUserCmd"},
320 {name => "RestorePostUserCmd"},
321 {name => "ArchivePreUserCmd"},
322 {name => "ArchivePostUserCmd"},
326 text => "CfgEdit_Title_Hosts",
328 {text => "CfgEdit_Title_Hosts"},
330 comment => "CfgEdit_Hosts_Comment"},
337 my $pc_dir = "$TopDir/pc";
338 my($content, $contentHidden, $newConf, $override, $mainConf, $hostConf);
341 my $host = $In{host};
342 my $menu = $In{menu} || "server";
343 my $hosts_path = $Hosts;
344 my $config_path = $host eq "" ? "$TopDir/conf/config.pl"
345 : "$TopDir/pc/$host/config.pl";
347 my $Privileged = CheckPermission($host)
348 && ($PrivAdmin || $Conf{CgiUserConfigEditEnable});
349 my $userHost = 1 if ( defined($host) );
352 if ( !$Privileged ) {
353 ErrorExit(eval("qq{$Lang->{Only_privileged_users_can_edit_config_files}}"));
356 if ( defined($In{menu}) || $In{editAction} eq $Lang->{CfgEdit_Button_Save} ) {
357 $errors = errorCheck();
360 # If there are errors, then go back to the same menu
362 $In{editAction} = "";
365 if ( (my $var = $In{overrideUncheck}) ne "" ) {
367 # a compound variable was unchecked; delete extra
368 # variables to make the shape the same.
370 #print STDERR Dumper(\%In);
371 foreach my $v ( keys(%In) ) {
372 next if ( $v !~ /^v_z_(\Q$var\E(_z_.*|$))/ );
373 delete($In{$v}) if ( !defined($In{"orig_z_$1"}) );
375 delete($In{"vflds.$var"});
378 ($newConf, $override) = inputParse($bpc, $userHost);
379 $override = undef if ( $host eq "" );
383 # First time: pick up the current config settings
385 $mainConf = $bpc->ConfigDataRead();
387 $hostConf = $bpc->ConfigDataRead($host);
389 foreach my $param ( keys(%$hostConf) ) {
390 $override->{$param} = 1;
393 my $hostInfo = $bpc->HostInfoRead();
395 $mainConf->{Hosts} = [map($hostInfo->{$_}, sort(keys(%$hostInfo)))];
397 $newConf = { %$mainConf, %$hostConf };
400 if ( $In{editAction} ne $Lang->{CfgEdit_Button_Save} && $In{newMenu} ne ""
401 && defined($ConfigMenu{$In{newMenu}}) ) {
402 $menu = $In{newMenu};
408 # For a non-admin user editing the host config, we need to
409 # figure out which subsets of the menu tree will be visible,
410 # based on what is enabled. Admin users can edit all the
411 # available per-host settings.
413 foreach my $m ( keys(%ConfigMenu) ) {
419 foreach my $paramInfo ( @{$ConfigMenu{$m}{param}} ) {
420 my $param = $paramInfo->{name};
421 if ( defined($paramInfo->{text}) ) {
425 if ( $bpc->{Conf}{CgiUserConfigEdit}{$param}
426 || (defined($bpc->{Conf}{CgiUserConfigEdit}{$param})
428 $mask[$text] = 0 if ( $text >= 0 );
437 $menuDisable{$m}{mask} = \@mask;
438 $menuDisable{$m}{top} = !$enabled;
440 if ( $menuDisable{$menu}{top} ) {
442 # Find an enabled menu if the current menu is empty
444 foreach my $m ( sort(keys(%menuDisable)) ) {
445 if ( !$menuDisable{$m}{top} ) {
454 foreach my $m ( keys(%ConfigMenu) ) {
455 next if ( $menuDisable{$m}{top} );
456 my $text = eval("qq($Lang->{$ConfigMenu{$m}{text}})");
459 <td class="editTabSel"><a href="javascript:menuSubmit('$m')"><b>$text</b></a></td>
463 <td class="editTabNoSel"><a href="javascript:menuSubmit('$m')">$text</a></td>
469 $content .= eval("qq($Lang->{CfgEdit_Header_Main})");
471 $content .= eval("qq($Lang->{CfgEdit_Header_Host})");
474 my $saveDisplay = "block";
475 $saveDisplay = "none" if ( !$In{modified}
476 || $In{editAction} eq $Lang->{CfgEdit_Button_Save} );
478 # Add action and host to the URL so the nav bar link is
481 my $url = "$MyURL?action=editConfig";
482 $url .= "&host=$host" if ( $host ne "" );
484 <table border="0" cellpadding="2">
487 <form method="post" name="form1" action="$url">
488 <input type="hidden" name="host" value="$host">
489 <input type="hidden" name="menu" value="$menu">
490 <input type="hidden" name="newMenu" value="">
491 <input type="hidden" name="modified" value="$In{modified}">
492 <input type="hidden" name="deleteVar" value="">
493 <input type="hidden" name="insertVar" value="">
494 <input type="hidden" name="overrideUncheck" value="">
495 <input type="hidden" name="addVar" value="">
496 <input type="hidden" name="action" value="editConfig">
497 <input type="submit" class="editSaveButton" style="display: $saveDisplay" name="editAction" value="${EscHTML($Lang->{CfgEdit_Button_Save})}">
499 <script language="javascript" type="text/javascript">
502 function deleteSubmit(varName)
504 document.form1.deleteVar.value = varName;
505 document.form1.modified.value = 1;
506 document.form1.submit();
510 function insertSubmit(varName)
512 document.form1.insertVar.value = varName;
513 document.form1.modified.value = 1;
514 document.form1.submit();
518 function addSubmit(varName, checkKey)
521 && eval("document.form1.addVarKey_" + varName + ".value") == "" ) {
522 alert("New key must be non-empty");
525 document.form1.addVar.value = varName;
526 document.form1.modified.value = 1;
527 document.form1.submit();
531 function menuSubmit(menuName)
533 document.form1.newMenu.value = menuName;
534 document.form1.submit();
537 function varChange(varName)
539 document.form1.editAction.style.display = "block";
540 document.form1.modified.value = 1;
543 function checkboxChange(varName)
545 document.form1.editAction.style.display = "block";
546 document.form1.modified.value = 1;
547 // Do nothing if the checkbox is now set
548 if ( eval("document.form1.override_" + varName + ".checked") ) {
552 var varRE = new RegExp("^v_z_(" + varName + ".*)");
553 var origRE = new RegExp("^orig_z_(" + varName + ".*)");
554 for ( var i = 0 ; i < document.form1.elements.length ; i++ ) {
555 var e = document.form1.elements[i];
557 if ( (re = varRE.exec(e.name)) != null ) {
558 if ( allVars[re[1]] == null ) {
562 //debugMsg("found v_z_ match with " + re[1]);
563 //debugMsg("allVars[" + re[1] + "] = " + allVars[re[1]]);
564 } else if ( (re = origRE.exec(e.name)) != null ) {
565 if ( allVars[re[1]] == null ) {
569 //debugMsg("allVars[" + re[1] + "] = " + allVars[re[1]]);
573 for ( v in allVars ) {
574 if ( allVars[v] != 0 ) {
575 //debugMsg("Not the same shape because of " + v);
578 // copy the original variable values
579 //debugMsg("setting " + v);
580 eval("document.form1.v_z_" + v + ".value = document.form1.orig_z_" + v + ".value");
586 // need to rebuild the form since the compound variable
588 document.form1.overrideUncheck.value = varName;
589 document.form1.submit();
594 function checkboxSet(varName)
596 document.form1.editAction.style.display = "block";
597 document.form1.modified.value = 1;
598 eval("document.form1.override_" + varName + ".checked = 1;");
602 var debugCounter = 0;
603 function debugMsg(msg)
606 var t = document.createTextNode(debugCounter + ": " + msg);
607 var br = document.createElement("br");
608 var debug = document.getElementById("debug");
609 debug.appendChild(t);
610 debug.appendChild(br);
613 function displayHelp(varName)
615 var help = document.getElementById("id_" + varName);
616 help.style.display = help.style.display == "block" ? "none" : "block";
622 <span id="debug">$debugText</span>
627 <table border="1" cellspacing="0">
633 # There is a special case of the user deleting just the field
634 # that has the error(s). So if the delete variable is a match
635 # or parent to all the errors then ignore the errors.
637 if ( $In{deleteVar} ne "" && %$errors > 0 ) {
639 foreach my $v ( keys(%$errors) ) {
640 if ( $v ne $In{deleteVar} && $v !~ /^\Q$In{deleteVar}_z_/ ) {
645 $errors = {} if ( $matchAll );
648 my $isError = %$errors;
650 if ( !$isError && $In{editAction} eq $Lang->{CfgEdit_Button_Save} ) {
653 $hostConf = $bpc->ConfigDataRead($host) if ( !defined($hostConf) );
654 my %hostConf2 = %$hostConf;
655 foreach my $param ( keys(%$newConf) ) {
656 if ( $override->{$param} ) {
657 $hostConf->{$param} = $newConf->{$param}
659 delete($hostConf->{$param});
662 $mesg = configDiffMesg($host, \%hostConf2, $hostConf);
663 $err .= $bpc->ConfigDataWrite($host, $hostConf);
665 $mainConf = $bpc->ConfigDataRead() if ( !defined($mainConf) );
668 my($hostsNew, $allHosts, $copyConf);
669 foreach my $entry ( @{$newConf->{Hosts}} ) {
670 next if ( $entry->{host} eq "" );
671 $allHosts->{$entry->{host}} = 1;
672 $allHosts->{$1} = 1 if ( $entry->{host} =~ /(.+?)\s*=/ );
674 foreach my $entry ( @{$newConf->{Hosts}} ) {
675 next if ( $entry->{host} eq ""
676 || defined($hostsNew->{$entry->{host}}) );
677 if ( $entry->{host} =~ /(.+?)\s*=\s*(.+)/ ) {
678 if ( defined($allHosts->{$2}) ) {
680 $copyConf->{$1} = $2;
682 my $fullHost = $entry->{host};
684 $err .= eval("qq($Lang->{CfgEdit_Error_Copy_host_does_not_exist})");
687 push(@$hostsSave, $entry);
688 $hostsNew->{$entry->{host}} = $entry;
690 ($mesg, my $hostChange) = hostsDiffMesg($hostsNew);
691 $bpc->HostInfoWrite($hostsNew) if ( $hostChange );
692 foreach my $host ( keys(%$copyConf) ) {
693 my $confData = $bpc->ConfigDataRead($copyConf->{$host});
694 my $fromHost = $copyConf->{$host};
695 $err .= $bpc->ConfigDataWrite($host, $confData);
696 $mesg .= eval("qq($Lang->{CfgEdit_Log_Copy_host_config})");
699 delete($newConf->{Hosts});
700 $mesg .= configDiffMesg(undef, $mainConf, $newConf);
701 $mainConf = { %$mainConf, %$newConf };
702 $err .= $bpc->ConfigDataWrite(undef, $mainConf);
703 $newConf->{Hosts} = $hostsSave;
705 if ( defined($err) ) {
707 <tr><td colspan="2" class="border"><span class="editError">$err</span></td></tr>
710 $bpc->ServerConnect();
712 (my $mesgBR = $mesg) =~ s/\n/<br>\n/g;
714 <tr><td colspan="2" class="border"><span class="editComment">$mesgBR</span></td></tr>
716 foreach my $str ( split(/\n/, $mesg) ) {
717 $bpc->ServerMesg("log $str") if ( $str ne "" );
721 # Tell the server to reload, unless we only changed
724 $bpc->ServerMesg("server reload") if ( $host eq "" );
727 my @mask = @{$menuDisable{$menu}{mask} || []};
729 foreach my $paramInfo ( @{$ConfigMenu{$menu}{param}} ) {
731 my $param = $paramInfo->{name};
732 my $disabled = shift(@mask);
734 next if ( $disabled || $menuDisable{$menu}{top} );
735 if ( ref($paramInfo->{visible}) eq "CODE"
736 && !&{$paramInfo->{visible}}($newConf) ) {
740 if ( defined($paramInfo->{text}) ) {
741 my $text = eval("qq($Lang->{$paramInfo->{text}})");
743 <tr><td colspan="2" class="editHeader">$text</td></tr>
749 # TODO: get parameter documentation
752 #$comment =~ s/\'//g;
753 #$comment =~ s/\"//g;
754 #$comment =~ s/\n/ /g;
756 $doneParam->{$param} = 1;
758 $content .= fieldEditBuild($ConfigMeta{$param},
765 $paramInfo->{onchangeSubmit},
766 defined($override) ? $param : undef,
767 defined($override) ? $override->{$param} : undef
769 if ( defined($paramInfo->{comment}) ) {
770 my $topDir = $bpc->TopDir;
771 my $text = eval("qq($Lang->{$paramInfo->{comment}})");
773 <tr><td colspan="2" class="editComment">$text</td></tr>
779 # Emit any remaining errors - should not happen
781 foreach my $param ( sort(keys(%$errors)) ) {
783 <tr><td colspan="2" class="border"><span class="editError">$errors->{$param}</span></td></tr>
785 delete($errors->{$param});
793 # Emit all the remaining editable config settings as hidden values
795 foreach my $param ( keys(%ConfigMeta) ) {
796 next if ( $doneParam->{$param} );
798 && (!defined($bpc->{Conf}{CgiUserConfigEdit}{$param})
800 && !$bpc->{Conf}{CgiUserConfigEdit}{$param})) );
801 $content .= fieldHiddenBuild($ConfigMeta{$param},
806 if ( defined($override) ) {
808 <input type="hidden" name="override_$param" value="$override->{$param}">
811 $doneParam->{$param} = 1;
814 if ( defined($In{menu}) || $In{editAction} eq $Lang->{CfgEdit_Button_Save} ) {
815 if ( $In{editAction} eq $Lang->{CfgEdit_Button_Save}
818 # Emit the new settings as orig_z_ parameters
821 foreach my $param ( keys(%ConfigMeta) ) {
822 next if ( $doneParam->{$param} );
824 && (!defined($bpc->{Conf}{CgiUserConfigEdit}{$param})
826 && !$bpc->{Conf}{CgiUserConfigEdit}{$param})) );
827 $contentHidden .= fieldHiddenBuild($ConfigMeta{$param},
832 $doneParam->{$param} = 1;
837 # Just switching menus: copy all the orig_z_ input parameters
839 foreach my $var ( keys(%In) ) {
840 next if ( $var !~ /^orig_z_/ );
841 $contentHidden .= <<EOF;
842 <input type="hidden" name="$var" value="${EscHTML($In{$var})}">
848 # First time: emit all the original config settings
851 foreach my $param ( keys(%ConfigMeta) ) {
852 next if ( $doneParam->{$param} );
854 && (!defined($bpc->{Conf}{CgiUserConfigEdit}{$param})
856 && !$bpc->{Conf}{CgiUserConfigEdit}{$param})) );
857 $contentHidden .= fieldHiddenBuild($ConfigMeta{$param},
862 $doneParam->{$param} = 1;
873 Header("Config Edit", $content);
879 my($type, $varName, $varValue, $prefix) = @_;
882 $type = { type => $type } if ( ref($type) ne "HASH" );
884 if ( $type->{type} eq "list" ) {
885 $varValue = [] if ( !defined($varValue) );
886 $varValue = [$varValue] if ( ref($varValue) ne "ARRAY" );
888 for ( my $i = 0 ; $i < @$varValue ; $i++ ) {
889 $content .= fieldHiddenBuild($type->{child}, "${varName}_z_$i",
890 $varValue->[$i], $prefix);
892 } elsif ( $type->{type} eq "hash" || $type->{type} eq "horizHash" ) {
893 $varValue = {} if ( ref($varValue) ne "HASH" );
894 my(@order, $childType);
896 if ( defined($type->{order}) ) {
897 @order = @{$type->{order}};
898 } elsif ( defined($type->{child}) ) {
899 @order = sort(keys(%{$type->{child}}));
901 @order = sort(keys(%$varValue));
904 foreach my $fld ( @order ) {
905 if ( defined($type->{child}) ) {
906 $childType = $type->{child}{$fld};
908 $childType = $type->{childType};
910 # emit list of fields since they are user-defined
911 # rather than hard-coded
914 <input type="hidden" name="vflds.$varName" value="${EscHTML($fld)}">
917 $content .= fieldHiddenBuild($childType, "${varName}_z_$fld",
918 $varValue->{$fld}, $prefix);
920 } elsif ( $type->{type} eq "shortlist" ) {
921 $varValue = [$varValue] if ( ref($varValue) ne "ARRAY" );
922 $varValue = join(", ", @$varValue);
924 <input type="hidden" name="${prefix}_z_$varName" value="${EscHTML($varValue)}">
928 <input type="hidden" name="${prefix}_z_$varName" value="${EscHTML($varValue)}">
936 my($type, $varName, $varValue, $errors, $level, $comment, $isError,
937 $onchangeSubmit, $overrideVar, $overrideSet) = @_;
940 my $size = 50 - 10 * $level;
941 $type = { type => $type } if ( ref($type) ne "HASH" );
943 $size = $type->{size} if ( defined($type->{size}) );
946 # These fragments allow inline conent to be turned on and off
948 # <tr><td colspan="2"><span id="id_$varName" style="display: none" class="editComment">$comment</span></td></tr>
949 # <tr><td class="border"><a href="javascript: displayHelp('$varName')">$varName</a>
953 my $lcVarName = lc($varName);
955 <tr><td class="border"><a href="?action=view&type=docs#item_%24conf%7b$lcVarName%7d">$varName</a>
957 if ( defined($overrideVar) ) {
958 my $override_checked = "";
959 if ( !$isError && $In{deleteVar} =~ /^\Q${varName}_z_/
960 || !$isError && $In{insertVar} =~ /^\Q${varName}\E(_z_|$)/
961 || !$isError && $In{addVar} =~ /^\Q${varName}\E(_z_|$)/ ) {
964 if ( $overrideSet ) {
965 $override_checked = "checked";
968 <br><input type="checkbox" name="override_$varName" $override_checked value="1" onClick="checkboxChange('$varName')">\ ${EscHTML($Lang->{CfgEdit_Button_Override})}
971 $content .= "</td>\n";
974 if ( $type->{type} eq "list" ) {
975 $content .= "<td class=\"border\">\n";
976 $varValue = [] if ( !defined($varValue) );
977 $varValue = [$varValue] if ( ref($varValue) ne "ARRAY" );
978 if ( !$isError && $In{deleteVar} =~ /^\Q${varName}_z_\E(\d+)$/
979 && $1 < @$varValue ) {
981 # User deleted entry in this array
983 splice(@$varValue, $1, 1) if ( @$varValue > 1 || $type->{emptyOk} );
986 if ( !$isError && $In{insertVar} =~ /^\Q${varName}_z_\E(\d+)$/
987 && $1 < @$varValue ) {
989 # User inserted entry in this array
991 splice(@$varValue, $1, 0, "")
992 if ( @$varValue > 1 || $type->{emptyOk} );
995 if ( !$isError && $In{addVar} eq $varName ) {
997 # User added entry to this array
999 push(@$varValue, undef);
1002 $content .= "<table border=\"1\" cellspacing=\"0\">\n";
1004 if ( ref($type) eq "HASH" && ref($type->{child}) eq "HASH"
1005 && $type->{child}{type} eq "horizHash" ) {
1007 if ( defined($type->{child}{order}) ) {
1008 @order = @{$type->{child}{order}};
1010 @order = sort(keys(%{$type->{child}{child}}));
1012 $content .= "<tr><td class=\"border\"></td>\n";
1013 for ( my $i = 0 ; $i < @order ; $i++ ) {
1014 $content .= "<td>$order[$i]</td>\n";
1016 $content .= "</tr>\n";
1017 for ( my $i = 0 ; $i < @$varValue ; $i++ ) {
1018 if ( @$varValue > 1 || $type->{emptyOk} ) {
1021 <input type="button" name="del_${varName}_z_$i" value="${EscHTML($Lang->{CfgEdit_Button_Delete})}"
1022 onClick="deleteSubmit('${varName}_z_$i')">
1026 $content .= fieldEditBuild($type->{child}, "${varName}_z_$i",
1027 $varValue->[$i], $errors, $level + 1, undef,
1028 $isError, $onchangeSubmit,
1029 $overrideVar, $overrideSet);
1030 $content .= "</tr>\n";
1033 for ( my $i = 0 ; $i < @$varValue ; $i++ ) {
1035 <tr><td class="border">
1036 <input type="button" name="ins_${varName}_z_$i" value="${EscHTML($Lang->{CfgEdit_Button_Insert})}"
1037 onClick="insertSubmit('${varName}_z_$i')">
1039 if ( @$varValue > 1 || $type->{emptyOk} ) {
1041 <input type="button" name="del_${varName}_z_$i" value="${EscHTML($Lang->{CfgEdit_Button_Delete})}"
1042 onClick="deleteSubmit('${varName}_z_$i')">
1045 $content .= "</td>\n";
1046 $content .= fieldEditBuild($type->{child}, "${varName}_z_$i",
1047 $varValue->[$i], $errors, $level + 1, undef,
1048 $isError, $onchangeSubmit,
1049 $overrideVar, $overrideSet);
1050 $content .= "</tr>\n";
1054 <tr><td class="border"><input type="button" name="add_$varName" value="${EscHTML($Lang->{CfgEdit_Button_Add})}"
1055 onClick="addSubmit('$varName')"></td></tr>
1058 $content .= "</td>\n";
1059 } elsif ( $type->{type} eq "hash" ) {
1060 $content .= "<td class=\"border\">\n";
1061 $content .= "<table border=\"1\" cellspacing=\"0\">\n";
1062 $varValue = {} if ( ref($varValue) ne "HASH" );
1064 if ( !$isError && !$type->{noKeyEdit}
1065 && $In{deleteVar} !~ /^\Q${varName}_z_\E.*_z_/
1066 && $In{deleteVar} =~ /^\Q${varName}_z_\E(\w+)$/ ) {
1068 # User deleted entry in this hash
1070 delete($varValue->{$1}) if ( keys(%$varValue) > 1
1071 || $type->{emptyOk} );
1072 $In{deleteVar} = "";
1074 if ( !$isError && !defined($type->{child})
1075 && $In{addVar} eq $varName ) {
1077 # User added entry to this array
1079 $varValue->{$In{"addVarKey_$varName"}} = ""
1080 if ( !defined($varValue->{$In{"addVarKey_$varName"}}) );
1083 my(@order, $childType);
1085 if ( defined($type->{order}) ) {
1086 @order = @{$type->{order}};
1087 } elsif ( defined($type->{child}) ) {
1088 @order = sort(keys(%{$type->{child}}));
1090 @order = sort(keys(%$varValue));
1093 foreach my $fld ( @order ) {
1095 <tr><td class="border">$fld
1097 if ( !$type->{noKeyEdit}
1098 && (keys(%$varValue) > 1 || $type->{emptyOk}) ) {
1100 <input type="submit" name="del_${varName}_z_$fld" value="${EscHTML($Lang->{CfgEdit_Button_Delete})}"
1101 onClick="deleteSubmit('${varName}_z_$fld')">
1104 if ( defined($type->{child}) ) {
1105 $childType = $type->{child}{$fld};
1107 $childType = $type->{childType};
1109 # emit list of fields since they are user-defined
1110 # rather than hard-coded
1113 <input type="hidden" name="vflds.$varName" value="${EscHTML($fld)}">
1116 $content .= "</td>\n";
1117 $content .= fieldEditBuild($childType, "${varName}_z_$fld",
1118 $varValue->{$fld}, $errors, $level + 1, undef,
1119 $isError, $onchangeSubmit,
1120 $overrideVar, $overrideSet);
1121 $content .= "</tr>\n";
1124 if ( !$type->{noKeyEdit} ) {
1126 <tr><td class="border" colspan="2">
1127 New key: <input type="text" name="addVarKey_$varName" size="20" maxlength="256" value="">
1128 <input type="button" name="add_$varName" value="${EscHTML($Lang->{CfgEdit_Button_Add})}" onClick="addSubmit('$varName', 1)">
1132 $content .= "</table>\n";
1133 $content .= "</td>\n";
1134 } elsif ( $type->{type} eq "horizHash" ) {
1135 $varValue = {} if ( ref($varValue) ne "HASH" );
1136 my(@order, $childType);
1138 if ( defined($type->{order}) ) {
1139 @order = @{$type->{order}};
1140 } elsif ( defined($type->{child}) ) {
1141 @order = sort(keys(%{$type->{child}}));
1143 @order = sort(keys(%$varValue));
1146 foreach my $fld ( @order ) {
1147 if ( defined($type->{child}) ) {
1148 $childType = $type->{child}{$fld};
1150 $childType = $type->{childType};
1152 # emit list of fields since they are user-defined
1153 # rather than hard-coded
1156 <input type="hidden" name="vflds.$varName" value="${EscHTML($fld)}">
1159 $content .= fieldEditBuild($childType, "${varName}_z_$fld",
1160 $varValue->{$fld}, $errors, $level + 1, undef,
1161 $isError, $onchangeSubmit,
1162 $overrideVar, $overrideSet);
1165 $content .= "<td class=\"border\">\n";
1168 # If there was an error, we use the original post values
1169 # in %In, rather than the parsed values in $varValue.
1170 # This is so that the user's erroneous input is preserved.
1172 $varValue = $In{"v_z_$varName"} if ( defined($In{"v_z_$varName"}) );
1174 if ( defined($errors->{$varName}) ) {
1176 <span class="editError">$errors->{$varName}</span><br>
1178 delete($errors->{$varName});
1181 if ( defined($overrideVar) ) {
1182 $onChange .= "checkboxSet('$overrideVar');";
1184 $onChange .= "varChange('$overrideVar');";
1186 if ( $onchangeSubmit ) {
1187 $onChange .= "document.form1.submit();";
1189 if ( $onChange ne "" ) {
1190 $onChange = " onChange=\"$onChange\"";
1192 if ( $varValue !~ /\n/ &&
1193 ($type->{type} eq "integer"
1194 || $type->{type} eq "string"
1195 || $type->{type} eq "execPath"
1196 || $type->{type} eq "shortlist"
1197 || $type->{type} eq "float") ) {
1199 if ( $type->{type} eq "shortlist" ) {
1200 $varValue = [$varValue] if ( ref($varValue) ne "ARRAY" );
1201 $varValue = join(", ", @$varValue);
1203 my $textType = ($varName =~ /Passwd/) ? "password" : "text";
1205 <input type="$textType" name="v_z_$varName" size="$size" maxlength="256" value="${EscHTML($varValue)}"$onChange>
1207 } elsif ( $type->{type} eq "boolean" ) {
1209 my $checked = "checked" if ( $varValue );
1211 <input type="checkbox" name="v_z_$varName" $checked value="1"$onChange>
1213 } elsif ( $type->{type} eq "select" ) {
1215 <select name="v_z_$varName"$onChange>
1217 foreach my $option ( @{$type->{values}} ) {
1218 my $sel = " selected" if ( $varValue eq $option );
1219 $content .= "<option$sel>$option</option>\n";
1221 $content .= "</select>\n";
1223 # multi-line text area - count number of lines
1224 my $rowCnt = $varValue =~ tr/\n//;
1225 $rowCnt = 1 if ( $rowCnt < 1 );
1227 <textarea name="v_z_$varName" cols="$size" rows="$rowCnt"$onChange>${EscHTML($varValue)}</textarea>
1230 $content .= "</td>\n";
1239 foreach my $param ( keys(%ConfigMeta) ) {
1240 fieldErrorCheck($ConfigMeta{$param}, $param, $errors);
1247 my($type, $varName, $errors) = @_;
1249 $type = { type => $type } if ( ref($type) ne "HASH" );
1251 if ( $type->{type} eq "list" ) {
1252 for ( my $i = 0 ; ; $i++ ) {
1253 last if ( fieldErrorCheck($type->{child}, "${varName}_z_$i", $errors) );
1255 } elsif ( $type->{type} eq "hash" || $type->{type} eq "horizHash" ) {
1256 my(@order, $childType);
1259 if ( defined($type->{order}) ) {
1260 @order = @{$type->{order}};
1261 } elsif ( defined($type->{child}) ) {
1262 @order = sort(keys(%{$type->{child}}));
1264 @order = split(/\0/, $In{"vflds.$varName"});
1266 foreach my $fld ( @order ) {
1267 if ( defined($type->{child}) ) {
1268 $childType = $type->{child}{$fld};
1270 $childType = $type->{childType};
1272 $ret ||= fieldErrorCheck($childType, "${varName}_z_$fld", $errors);
1276 $In{"v_z_$varName"} = "0" if ( $type->{type} eq "boolean"
1277 && $In{"v_z_$varName"} eq "" );
1279 return 1 if ( !exists($In{"v_z_$varName"}) );
1281 (my $var = $varName) =~ s/_z_/./g;
1283 if ( $type->{type} eq "integer"
1284 || $type->{type} eq "boolean" ) {
1285 if ( $In{"v_z_$varName"} !~ /^-?\d+\s*$/s
1286 && $In{"v_z_$varName"} ne "" ) {
1287 $errors->{$varName} = eval("qq{$Lang->{CfgEdit_Error__must_be_an_integer}}");
1289 } elsif ( $type->{type} eq "float" ) {
1290 if ( $In{"v_z_$varName"} !~ /^-?\d*(\.\d*)?\s*$/s
1291 && $In{"v_z_$varName"} ne "" ) {
1293 = eval("qq{$Lang->{CfgEdit_Error__must_be_real_valued_number}}");
1295 } elsif ( $type->{type} eq "shortlist" ) {
1296 my @vals = split(/[,\s]+/, $In{"v_z_$varName"});
1297 for ( my $i = 0 ; $i < @vals ; $i++ ) {
1298 if ( $type->{child} eq "integer"
1299 && $vals[$i] !~ /^-?\d+\s*$/s
1300 && $vals[$i] ne "" ) {
1302 $errors->{$varName} = eval("qq{$Lang->{CfgEdit_Error__entry__must_be_an_integer}}");
1303 } elsif ( $type->{child} eq "float"
1304 && $vals[$i] !~ /^-?\d*(\.\d*)?\s*$/s
1305 && $vals[$i] ne "" ) {
1307 $errors->{$varName} = eval("qq{$Lang->{CfgEdit_Error__entry__must_be_real_valued_number}}");
1310 } elsif ( $type->{type} eq "select" ) {
1312 foreach my $option ( @{$type->{values}} ) {
1313 if ( $In{"v_z_$varName"} eq $option ) {
1318 $errors->{$varName} = eval("qq{$Lang->{CfgEdit_Error__must_be_valid_option}}")
1320 } elsif ( $type->{type} eq "execPath" ) {
1321 if ( $In{"v_z_$varName"} ne "" && !-x $In{"v_z_$varName"} ) {
1322 $errors->{$varName} = eval("qq{$Lang->{CfgEdit_Error__must_be_executable_program}}");
1326 # $type->{type} eq "string": no error checking
1335 my($bpc, $userHost) = @_;
1339 foreach my $param ( keys(%ConfigMeta) ) {
1342 && (!defined($bpc->{Conf}{CgiUserConfigEdit}{$param})
1344 && !$bpc->{Conf}{CgiUserConfigEdit}{$param})) );
1345 fieldInputParse($ConfigMeta{$param}, $param, \$value);
1346 $conf->{$param} = $value;
1347 $override->{$param} = $In{"override_$param"};
1349 return ($conf, $override);
1354 my($type, $varName, $value) = @_;
1356 $type = { type => $type } if ( ref($type) ne "HASH" );
1358 if ( $type->{type} eq "list" ) {
1360 for ( my $i = 0 ; ; $i++ ) {
1362 last if ( fieldInputParse($type->{child}, "${varName}_z_$i", \$val) );
1363 push(@$$value, $val);
1365 $$value = undef if ( $type->{undefIfEmpty} && @$$value == 0 );
1366 } elsif ( $type->{type} eq "hash" || $type->{type} eq "horizHash" ) {
1367 my(@order, $childType);
1371 if ( defined($type->{order}) ) {
1372 @order = @{$type->{order}};
1373 } elsif ( defined($type->{child}) ) {
1374 @order = sort(keys(%{$type->{child}}));
1376 @order = split(/\0/, $In{"vflds.$varName"});
1379 foreach my $fld ( @order ) {
1381 if ( defined($type->{child}) ) {
1382 $childType = $type->{child}{$fld};
1384 $childType = $type->{childType};
1386 $ret ||= fieldInputParse($childType, "${varName}_z_$fld", \$val);
1388 $$value->{$fld} = $val;
1392 if ( $type->{type} eq "boolean" ) {
1393 $$value = 0 + $In{"v_z_$varName"};
1394 } elsif ( !exists($In{"v_z_$varName"}) ) {
1398 if ( $type->{type} eq "integer" ) {
1399 $$value = 0 + $In{"v_z_$varName"};
1400 } elsif ( $type->{type} eq "float" ) {
1401 $$value = 0 + $In{"v_z_$varName"};
1402 } elsif ( $type->{type} eq "shortlist" ) {
1403 $$value = [split(/[,\s]+/, $In{"v_z_$varName"})];
1404 if ( $type->{child} eq "float"
1405 || $type->{child} eq "integer"
1406 || $type->{child} eq "boolean" ) {
1407 foreach ( @$$value ) {
1412 $$value = $In{"v_z_$varName"};
1413 $$value =~ s/\r\n/\n/g;
1415 $$value = undef if ( $type->{undefIfEmpty} && $$value eq "" );
1422 my($host, $oldConf, $newConf) = @_;
1426 if ( $host ne "" ) {
1427 $conf = "host $host config";
1429 $conf = "main config";
1432 foreach my $p ( keys(%ConfigMeta) ) {
1433 if ( !exists($oldConf->{$p}) && !exists($newConf->{$p}) ) {
1435 } elsif ( exists($oldConf->{$p}) && !exists($newConf->{$p}) ) {
1436 $mesg .= eval("qq($Lang->{CfgEdit_Log_Delete_param})");
1437 } elsif ( !exists($oldConf->{$p}) && exists($newConf->{$p}) ) {
1438 my $dump = Data::Dumper->new([$newConf->{$p}]);
1442 my $value = $dump->Dump;
1443 $value =~ s/\n/\\n/g;
1444 $value =~ s/\r/\\r/g;
1445 $mesg .= eval("qq($Lang->{CfgEdit_Log_Add_param_value})");
1447 my $dump = Data::Dumper->new([$newConf->{$p}]);
1451 my $valueNew = $dump->Dump;
1453 my $v = $oldConf->{$p};
1454 if ( ref($newConf->{$p}) eq "ARRAY" && ref($v) eq "" ) {
1457 $dump = Data::Dumper->new([$v]);
1461 my $valueOld = $dump->Dump;
1463 (my $valueNew2 = $valueNew) =~ s/['\n\r]//g;
1464 (my $valueOld2 = $valueOld) =~ s/['\n\r]//g;
1465 $valueNew =~ s/\n/\\n/g;
1466 $valueOld =~ s/\n/\\n/g;
1467 $valueNew =~ s/\r/\\r/g;
1468 $valueOld =~ s/\r/\\r/g;
1469 $mesg .= eval("qq($Lang->{CfgEdit_Log_Change_param_value})")
1470 if ( $valueOld2 ne $valueNew2 );
1479 my $hostsOld = $bpc->HostInfoRead();
1480 my($mesg, $hostChange);
1482 foreach my $host ( keys(%$hostsOld) ) {
1483 if ( !defined($hostsNew->{$host}) ) {
1484 $mesg .= eval("qq($Lang->{CfgEdit_Log_Host_Delete})");
1488 foreach my $key ( keys(%{$hostsNew->{$host}}) ) {
1489 next if ( $hostsNew->{$host}{$key} eq $hostsOld->{$host}{$key} );
1490 my $valueOld = $hostsOld->{$host}{$key};
1491 my $valueNew = $hostsNew->{$host}{$key};
1492 $mesg .= eval("qq($Lang->{CfgEdit_Log_Host_Change})");
1497 foreach my $host ( keys(%$hostsNew) ) {
1498 next if ( defined($hostsOld->{$host}) );
1499 my $dump = Data::Dumper->new([$hostsNew->{$host}]);
1503 my $value = $dump->Dump;
1504 $value =~ s/\n/\\n/g;
1505 $value =~ s/\r/\\r/g;
1506 $mesg .= eval("qq($Lang->{CfgEdit_Log_Host_Add})");
1509 return ($mesg, $hostChange);