]> git.ipfire.org Git - ipfire-2.x.git/blame - html/cgi-bin/zoneconf.cgi
zoneconf.cgi: Add Javascript for new GUI elements
[ipfire-2.x.git] / html / cgi-bin / zoneconf.cgi
CommitLineData
1dcf513a
FB
1#!/usr/bin/perl
2###############################################################################
3# #
4# VLAN Management for IPFire #
5# Copyright (C) 2019 Florian Bührle <fbuehrle@ipfire.org> #
6# #
7# This program is free software: you can redistribute it and/or modify #
8# it under the terms of the GNU General Public License as published by #
9# the Free Software Foundation, either version 3 of the License, or #
10# (at your option) any later version. #
11# #
12# This program is distributed in the hope that it will be useful, #
13# but WITHOUT ANY WARRANTY; without even the implied warranty of #
14# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the #
15# GNU General Public License for more details. #
16# #
17# You should have received a copy of the GNU General Public License #
18# along with this program. If not, see <http://www.gnu.org/licenses/>. #
19# #
20###############################################################################
21
22use strict;
23use Scalar::Util qw(looks_like_number);
24
25require '/var/ipfire/general-functions.pl';
26require "${General::swroot}/lang.pl";
27require "${General::swroot}/header.pl";
28
63a1c81a 29###--- HTML HEAD ---###
5c33a761 30my $extraHead = <<END
1dcf513a 31<style>
fc31c28d 32 table#zoneconf {
1dcf513a 33 width: 100%;
23b26ce5 34 border-collapse: collapse;
5e6eba88 35 border-style: hidden;
23b26ce5 36 table-layout: fixed;
1dcf513a
FB
37 }
38
5e6eba88 39 /* row height */
fc31c28d 40 #zoneconf tr {
1dcf513a
FB
41 height: 4em;
42 }
8de94a23
LAH
43 #zoneconf tr.half-height {
44 height: 2em;
45 }
46 #zoneconf tr.half-height > td {
47 padding: 2px 10px;
48 }
49
5e6eba88
LAH
50 /* section separators */
51 #zoneconf tr.divider-top {
52 border-top: 2px solid $Header::bordercolour;
53 }
54 #zoneconf tr.divider-bottom {
55 border-bottom: 2px solid $Header::bordercolour;
56 }
1dcf513a 57
5e6eba88 58 /* table cells */
fc31c28d
LAH
59 #zoneconf td {
60 padding: 5px 10px;
5e6eba88 61 border-left: 0.5px solid $Header::bordercolour;
fc31c28d 62 text-align: center;
1dcf513a
FB
63 }
64
5e6eba88 65 /* grey header cells */
fc31c28d 66 #zoneconf td.heading {
5e6eba88 67 background-color: lightgrey;
fc31c28d
LAH
68 color: white;
69 }
5e6eba88 70 #zoneconf td.heading.bold::first-line {
fc31c28d
LAH
71 font-weight: bold;
72 line-height: 1.6;
1dcf513a
FB
73 }
74
5e6eba88 75 /* narrow left column with background color */
fc31c28d
LAH
76 #zoneconf tr > td:first-child {
77 width: 11em;
23b26ce5 78 }
5e6eba88
LAH
79 #zoneconf tr.nic-row > td:first-child {
80 background-color: darkgray;
81 }
82 #zoneconf tr.nic-row {
83 border-bottom: 0.5px solid $Header::bordercolour;
84 }
8de94a23
LAH
85 #zoneconf tr.option-row > td:first-child {
86 background-color: gray;
87 }
7478903f 88
fc31c28d 89 /* alternating row background color */
5e6eba88
LAH
90 #zoneconf tr {
91 background-color: $Header::table2colour;
92 }
fc31c28d 93 #zoneconf tr:nth-child(2n+3) {
5e6eba88 94 background-color: $Header::table1colour;
1dcf513a
FB
95 }
96
5e6eba88 97 /* special cell colors */
fc31c28d 98 #zoneconf td.green {
1dcf513a
FB
99 background-color: $Header::colourgreen;
100 }
101
fc31c28d 102 #zoneconf td.red {
1dcf513a
FB
103 background-color: $Header::colourred;
104 }
105
fc31c28d 106 #zoneconf td.blue {
1dcf513a
FB
107 background-color: $Header::colourblue;
108 }
109
fc31c28d 110 #zoneconf td.orange {
1dcf513a
FB
111 background-color: $Header::colourorange;
112 }
113
fc31c28d
LAH
114 #zoneconf td.topleft {
115 background-color: $Header::pagecolour;
1dcf513a
FB
116 }
117
7f44ec04
AK
118 input.vlanid {
119 width: 4em;
120 }
8de94a23
LAH
121 input.stp-priority {
122 width: 5em;
123 }
7f44ec04 124
1dcf513a 125 #submit-container {
1dcf513a 126 width: 100%;
23b26ce5 127 padding-top: 20px;
1d6bc7a0 128 text-align: right;
23b26ce5 129 color: red;
1dcf513a
FB
130 }
131
132 #submit-container.input {
133 margin-left: auto;
134 }
1dcf513a 135</style>
5c33a761
LAH
136
137<script src="/include/zoneconf.js"></script>
1dcf513a
FB
138END
139;
63a1c81a 140###--- END HTML HEAD ---###
1dcf513a 141
63a1c81a 142### Read configuration ###
1dcf513a
FB
143my %ethsettings = ();
144my %vlansettings = ();
145my %cgiparams = ();
146
7478903f
FB
147my $restart_notice = "";
148
1dcf513a
FB
149&General::readhash("${General::swroot}/ethernet/settings",\%ethsettings);
150&General::readhash("${General::swroot}/ethernet/vlans",\%vlansettings);
151
152&Header::getcgihash(\%cgiparams);
153&Header::showhttpheaders();
154
155# Define all zones we will check for NIC assignment
63a1c81a 156my @zones = ("red", "green", "orange", "blue");
1dcf513a
FB
157
158# Get all physical NICs present
159opendir(my $dh, "/sys/class/net/");
160my @nics = ();
161
162while (my $nic = readdir($dh)) {
163 if (-e "/sys/class/net/$nic/device") { # Indicates that the NIC is physical
164 push(@nics, [&Network::get_nic_property($nic, "address"), $nic, 0]);
165 }
166}
167
168closedir($dh);
169
170@nics = sort {$a->[0] cmp $b->[0]} @nics; # Sort nics by their MAC address
171
172# Name the physical NICs
173# Even though they may not be really named like this, we will name them ethX or wlanX
174my $ethcount = 0;
175my $wlancount = 0;
176
177foreach (@nics) {
178 my $nic = $_->[1];
179
180 if (-e "/sys/class/net/$nic/wireless") {
181 $_->[1] = "wlan$wlancount";
182 $_->[2] = 1;
183 $wlancount++;
184 } else {
185 $_->[1] = "eth$ethcount";
186 $ethcount++;
187 }
188}
189
63a1c81a
LAH
190### Functions ###
191
192# Check if a zone is in IP mode or in PPP, PPPoE, VDSL, ... mode
193sub is_zonetype_ip {
194 my $zone_type = shift;
195 return ($zone_type eq "STATIC" || $zone_type eq "DHCP");
196}
197
198# Check if a zone is activated (device assigned)
199sub is_zone_activated {
200 my $zone = uc shift;
201 return ($ethsettings{"${zone}_DEV"} ne "");
202}
203
204### START PAGE ###
5c33a761 205&Header::openpage($Lang::tr{"zoneconf title"}, 1, $extraHead);
1dcf513a
FB
206&Header::openbigbox('100%', 'center');
207
208### Evaluate POST parameters ###
209
210if ($cgiparams{"ACTION"} eq $Lang::tr{"save"}) {
211 my %VALIDATE_nic_check = ();
212 my $VALIDATE_error = "";
213
214 foreach (@zones) {
215 my $uc = uc $_;
216 my $slave_string = "";
217 my $zone_mode = $cgiparams{"MODE $uc"};
218 my $VALIDATE_vlancount = 0;
f60b61e0
FB
219 my $VALIDATE_zoneslaves = 0;
220
1dcf513a
FB
221 $ethsettings{"${uc}_MACADDR"} = "";
222 $ethsettings{"${uc}_MODE"} = "";
223 $ethsettings{"${uc}_SLAVES"} = "";
224 $vlansettings{"${uc}_PARENT_DEV"} = "";
225 $vlansettings{"${uc}_VLAN_ID"} = "";
226 $vlansettings{"${uc}_MAC_ADDRESS"} = "";
227
228 # If RED is not in DHCP or static mode, we only set its MACADDR property
229 if ($uc eq "RED" && ! $cgiparams{"PPPACCESS"} eq "") {
230 foreach (@nics) {
231 my $mac = $_->[0];
232
233 if ($mac eq $cgiparams{"PPPACCESS"}) {
234 $ethsettings{"${uc}_MACADDR"} = $mac;
235
236 # Check if this interface is already accessed by any other zone
237 # If this is the case, show an error message
238 if ($VALIDATE_nic_check{"ACC $mac"}) {
239 $VALIDATE_error = $Lang::tr{"zoneconf val ppp assignment error"};
240 }
241
242 $VALIDATE_nic_check{"RESTRICT $mac"} = 1;
243 last;
244 }
245 }
246
63a1c81a 247 # skip NIC/VLAN assignment and additional zone options for RED in PPP mode
1dcf513a
FB
248 next;
249 }
250
251 foreach (@nics) {
252 my $mac = $_->[0];
253 my $nic_access = $cgiparams{"ACCESS $uc $mac"};
254
bb90622c
MT
255 next unless ($nic_access);
256
a6695868 257 if ($nic_access ne "NONE") {
1dcf513a
FB
258 if ($VALIDATE_nic_check{"RESTRICT $mac"}) { # If this interface is already assigned to RED in PPP mode, throw an error
259 $VALIDATE_error = $Lang::tr{"zoneconf val ppp assignment error"};
f60b61e0
FB
260 last;
261 }
262
a6695868 263 if ($zone_mode ne "BRIDGE" && $VALIDATE_zoneslaves > 0 && $nic_access ne "") {
f60b61e0
FB
264 $VALIDATE_error = $Lang::tr{"zoneconf val zoneslave amount error"};
265 last;
1dcf513a
FB
266 }
267
268 $VALIDATE_nic_check{"ACC $mac"} = 1;
f60b61e0 269 $VALIDATE_zoneslaves++;
1dcf513a
FB
270 }
271
272 if ($nic_access eq "NATIVE") {
273 if ($VALIDATE_nic_check{"NATIVE $mac"}) {
274 $VALIDATE_error = $Lang::tr{"zoneconf val native assignment error"};
f60b61e0 275 last;
1dcf513a
FB
276 }
277
278 $VALIDATE_nic_check{"NATIVE $mac"} = 1;
279
280 if ($zone_mode eq "BRIDGE") {
281 $slave_string = "${slave_string}${mac} ";
282 } else {
283 $ethsettings{"${uc}_MACADDR"} = $mac;
284 }
285 } elsif ($nic_access eq "VLAN") {
286 my $vlan_tag = $cgiparams{"TAG $uc $mac"};
287
288 if ($VALIDATE_nic_check{"VLAN $mac $vlan_tag"}) {
289 $VALIDATE_error = $Lang::tr{"zoneconf val vlan tag assignment error"};
f60b61e0 290 last;
1dcf513a
FB
291 }
292
293 $VALIDATE_nic_check{"VLAN $mac $vlan_tag"} = 1;
294
295 if (! looks_like_number($vlan_tag)) {
f60b61e0 296 last;
1dcf513a
FB
297 }
298 if ($vlan_tag < 1 || $vlan_tag > 4095) {
f60b61e0 299 last;
1dcf513a
FB
300 }
301
302 my $rnd_mac = &Network::random_mac();
303
304 $vlansettings{"${uc}_PARENT_DEV"} = $mac;
305 $vlansettings{"${uc}_VLAN_ID"} = $vlan_tag;
306 $vlansettings{"${uc}_MAC_ADDRESS"} = $rnd_mac;
307
308 if ($zone_mode eq "BRIDGE") {
309 $slave_string = "${slave_string}${rnd_mac} ";
310 }
311
312 $VALIDATE_vlancount++; # We can't allow more than one VLAN per zone
313 }
314 }
315
316 if ($VALIDATE_vlancount > 1) {
317 $VALIDATE_error = $Lang::tr{"zoneconf val vlan amount assignment error"};
f60b61e0 318 last;
1dcf513a
FB
319 }
320
321 chop($slave_string);
322
323 if ($zone_mode eq "BRIDGE") {
324 $ethsettings{"${uc}_MODE"} = "bridge";
325 $ethsettings{"${uc}_SLAVES"} = $slave_string;
326 } elsif ($zone_mode eq "MACVTAP") {
327 $ethsettings{"${uc}_MODE"} = "macvtap";
328 }
8de94a23
LAH
329
330 # STP options
331 # (this has already been skipped when RED is in PPP mode, so we don't need to check for PPP here)
332 $ethsettings{"${uc}_STP"} = "";
333 my $stp_enabled = $cgiparams{"STP-$uc"} eq "on";
334 my $stp_priority = $cgiparams{"STP-PRIORITY-$uc"};
335
336 if($stp_enabled) {
337 unless($ethsettings{"${uc}_MODE"} eq "bridge") { # STP is only available in bridge mode
338 $VALIDATE_error = $Lang::tr{"zoneconf val stp zone mode error"};
339 last;
340 }
341 unless (looks_like_number($stp_priority) && ($stp_priority >= 1) && ($stp_priority <= 65535)) { # STP bridge priority range: 1..65535
342 $VALIDATE_error = $Lang::tr{"zoneconf val stp priority range error"};
343 last;
344 }
345 $ethsettings{"${uc}_STP"} = "on"; # network-hotplug-bridges expects "on"
346 $ethsettings{"${uc}_STP_PRIORITY"} = $stp_priority;
347 }
1dcf513a
FB
348 }
349
63a1c81a 350 # validation failed, show error message and exit
1dcf513a
FB
351 if ($VALIDATE_error) {
352 &Header::openbox('100%', 'left', $Lang::tr{"error"});
353
8797526d 354 print "$VALIDATE_error<br><br><a href='$ENV{'SCRIPT_NAME'}'>$Lang::tr{'back'}</a>\n";
1dcf513a
FB
355
356 &Header::closebox();
357 &Header::closebigbox();
358 &Header::closepage();
359
360 exit 0;
361 }
362
63a1c81a 363 # new settings are valid, write configuration files
1dcf513a
FB
364 &General::writehash("${General::swroot}/ethernet/settings",\%ethsettings);
365 &General::writehash("${General::swroot}/ethernet/vlans",\%vlansettings);
7478903f 366
23b26ce5 367 $restart_notice = $Lang::tr{'zoneconf notice reboot'};
1dcf513a
FB
368}
369
1dcf513a
FB
370### START OF TABLE ###
371
63a1c81a
LAH
372&Header::openbox('100%', 'left', $Lang::tr{"zoneconf nic assignment"});
373
1dcf513a 374print <<END
0ec8e31a 375<form method='post' enctype='multipart/form-data'>
fc31c28d 376 <table id="zoneconf">
5e6eba88 377 <tr class="divider-bottom">
fc31c28d 378 <td class="topleft"></td>
1dcf513a
FB
379END
380;
381
0ec8e31a 382# Fill the table header with all activated zones
1dcf513a 383foreach (@zones) {
1dcf513a 384 my $uc = uc $_;
1dcf513a 385
63a1c81a
LAH
386 # If the zone is not activated, don't show it
387 next unless is_zone_activated($_);
f60b61e0 388
63a1c81a 389 # If the red zone is in PPP mode, don't show a mode dropdown
23b26ce5 390 if ($uc eq "RED") {
1dcf513a 391 my $red_type = $ethsettings{"RED_TYPE"};
1dcf513a 392
63a1c81a
LAH
393 unless (is_zonetype_ip($red_type)) {
394 print "\t\t<td class='heading bold $_'>$uc ($red_type)</td>\n";
1dcf513a 395
1dcf513a
FB
396 next; # We're done here
397 }
398 }
399
400 my %mode_selected = ();
401 my $zone_mode = $ethsettings{"${uc}_MODE"};
402
403 if ($zone_mode eq "") {
404 $mode_selected{"DEFAULT"} = "selected";
405 } elsif ($zone_mode eq "bridge") {
406 $mode_selected{"BRIDGE"} = "selected";
407 } elsif ($zone_mode eq "macvtap") {
408 $mode_selected{"MACVTAP"} = "selected";
409 }
410
411 print <<END
5e6eba88 412 <td class='heading bold $_'>$uc<br>
b4434345 413 <select name="MODE $uc" data-zone="$uc" onchange="changeZoneMode(this)">
1dcf513a
FB
414 <option value="DEFAULT" $mode_selected{"DEFAULT"}>$Lang::tr{"zoneconf nicmode default"}</option>
415 <option value="BRIDGE" $mode_selected{"BRIDGE"}>$Lang::tr{"zoneconf nicmode bridge"}</option>
416 <option value="MACVTAP" $mode_selected{"MACVTAP"}>$Lang::tr{"zoneconf nicmode macvtap"}</option>
417 </select>
418 </td>
419END
420;
0ec8e31a
FB
421}
422
96d0c761 423print "\t</tr>\n";
0ec8e31a 424
63a1c81a 425# NIC assignment matrix
0ec8e31a 426foreach (@nics) {
23b26ce5 427 my $mac = $_->[0];
0ec8e31a 428 my $nic = $_->[1];
23b26ce5 429 my $wlan = $_->[2];
0ec8e31a 430
5e6eba88
LAH
431 print "\t<tr class='nic-row'>\n";
432 print "\t\t<td class='heading bold'>$nic<br>$mac</td>\n";
0ec8e31a 433
23b26ce5
MT
434 # Iterate through all zones and check if the current NIC is assigned to it
435 foreach (@zones) {
436 my $uc = uc $_;
5c33a761 437 my $highlight = "";
0ec8e31a 438
63a1c81a
LAH
439 # If the zone is not activated, don't show it
440 next unless is_zone_activated($_);
1dcf513a 441
23b26ce5 442 if ($uc eq "RED") {
23b26ce5 443 # VLANs/Bridging is not possible if the RED interface is set to PPP, PPPoE, VDSL, ...
63a1c81a 444 unless (is_zonetype_ip($ethsettings{"RED_TYPE"})) {
23b26ce5 445 my $checked = "";
1dcf513a 446
0ec8e31a
FB
447 if ($mac eq $ethsettings{"${uc}_MACADDR"}) {
448 $checked = "checked";
5c33a761 449 $highlight = $_;
0ec8e31a
FB
450 }
451
96d0c761 452 print <<END
5c33a761
LAH
453 <td class="$highlight">
454 <input type="radio" name="PPPACCESS" value="$mac" data-zone="RED" data-mac="$mac" onchange="highlightAccess(this)" $checked>
96d0c761
LAH
455 </td>
456END
457;
23b26ce5
MT
458 next; # We're done here
459 }
460 }
461
462 my %access_selected = ();
463 my $zone_mode = $ethsettings{"${uc}_MODE"};
464 my $zone_parent_dev = $vlansettings{"${uc}_PARENT_DEV"}; # ZONE_PARENT_DEV is set if this zone accesses any interface via a VLAN
465 my $field_disabled = "disabled"; # Only enable the VLAN ID input field if the current access mode is VLAN
1dcf513a
FB
466 my $zone_vlan_id = "";
467
23b26ce5
MT
468 # If ZONE_PARENT_DEV is set to a NICs name (e.g. green0 or eth0) instead of a MAC address, we have to find out this NICs MAC address
469 $zone_parent_dev = &Network::get_mac_by_name($zone_parent_dev);
0ec8e31a 470
23b26ce5
MT
471 # If the current NIC is accessed by the current zone via a VLAN, the ZONE_PARENT_DEV option corresponds to the current NIC
472 if ($mac eq $zone_parent_dev) {
1dcf513a
FB
473 $access_selected{"VLAN"} = "selected";
474 $field_disabled = "";
475 $zone_vlan_id = $vlansettings{"${uc}_VLAN_ID"};
0ec8e31a 476 } elsif ($zone_mode eq "bridge") { # If the current zone is in bridge mode, all corresponding NICs (Native as well as VLAN) are set via the ZONE_SLAVES option
1dcf513a
FB
477 my @slaves = split(/ /, $ethsettings{"${uc}_SLAVES"});
478
479 foreach (@slaves) {
480 # Slaves can be set to a NICs name so we have to find out its MAC address
481 $_ = &Network::get_mac_by_name($_);
482
483 if ($_ eq $mac) {
484 $access_selected{"NATIVE"} = "selected";
485 last;
486 }
487 }
0ec8e31a
FB
488 } elsif ($mac eq $ethsettings{"${uc}_MACADDR"}) { # Native access via ZONE_MACADDR is only set if the zone does not access a NIC via a VLAN and the zone is not in bridge mode
489 $access_selected{"NATIVE"} = "selected";
1dcf513a
FB
490 }
491
23b26ce5 492 $access_selected{"NONE"} = ($access_selected{"NATIVE"} eq "") && ($access_selected{"VLAN"} eq "") ? "selected" : "";
1dcf513a
FB
493 my $vlan_disabled = ($wlan) ? "disabled" : "";
494
5c33a761
LAH
495 # If the interface is assigned, hightlight table cell
496 if ($access_selected{"NONE"} eq "") {
497 $highlight = $_;
498 }
499
23b26ce5 500 print <<END
5c33a761
LAH
501 <td class="$highlight">
502 <select name="ACCESS $uc $mac" data-zone="$uc" data-mac="$mac" onchange="highlightAccess(this)">
96d0c761
LAH
503 <option value="NONE" $access_selected{"NONE"}>- $Lang::tr{"zoneconf access none"} -</option>
504 <option value="NATIVE" $access_selected{"NATIVE"}>$Lang::tr{"zoneconf access native"}</option>
505 <option value="VLAN" $access_selected{"VLAN"} $vlan_disabled>$Lang::tr{"zoneconf access vlan"}</option>
506 </select>
8797526d 507 <input type="number" class="vlanid" id="TAG-$uc-$mac" name="TAG $uc $mac" min="1" max="4095" value="$zone_vlan_id" $field_disabled>
96d0c761 508 </td>
1dcf513a
FB
509END
510;
23b26ce5 511 }
1dcf513a 512
96d0c761 513 print "\t</tr>\n";
1dcf513a
FB
514}
515
8de94a23
LAH
516# STP options
517my @stp_html = (); # form fields buffer (two rows)
518
519foreach (@zones) { # load settings and prepare form elements for each zone
520 my $uc = uc $_;
521
522 # skip if zone is not activated
523 next unless is_zone_activated($_);
524
525 # STP is not available if the RED interface is set to PPP, PPPoE, VDSL, ...
526 if ($uc eq "RED") {
527 unless (is_zonetype_ip($ethsettings{"RED_TYPE"})) {
528 push(@stp_html, ["\t\t<td></td>\n", "\t\t<td></td>\n"]); # print empty cell
529 next;
530 }
531 }
532
533 # load configuration
534 my $stp_available = $ethsettings{"${uc}_MODE"} eq "bridge"; # STP is only available in bridge mode
535 my $stp_enabled = $ethsettings{"${uc}_STP"} eq "on";
536 my $stp_priority = $ethsettings{"${uc}_STP_PRIORITY"};
537
538 # form element modifiers
539 my $checked = "";
540 my $disabled = "";
541 $checked = "checked" if ($stp_available && $stp_enabled);
542 $disabled = "disabled" unless $stp_available;
543
544 # enable checkbox HTML
545 my $row_1 = <<END
546 <td>
b4434345 547 <input type="checkbox" id="STP-$uc" name="STP-$uc" data-zone="$uc" onchange="changeEnableSTP(this)" $disabled $checked>
8de94a23
LAH
548 </td>
549END
550;
551 $disabled = "disabled" unless $stp_enabled; # STP priority can't be entered if STP is disabled
552
553 # priority input box HTML
554 my $row_2 = <<END
555 <td>
b4434345 556 <input type="number" class="stp-priority" id="STP-PRIORITY-$uc" name="STP-PRIORITY-$uc" min="1" max="65535" value="$stp_priority" $disabled>
8de94a23
LAH
557 </td>
558END
559;
560 # add fields to buffer
561 push(@stp_html, [$row_1, $row_2]);
562}
563
564# print two rows of prepared form elements
565print <<END
566 <tr class="half-height divider-top option-row">
567 <td class="heading bold">$Lang::tr{"zoneconf stp enable"}</td>
568END
569;
570foreach (@stp_html) {
571 print $_->[0]; # row 1
572}
573print <<END
574 </tr>
575 <tr class="half-height option-row">
576 <td class="heading">$Lang::tr{"zoneconf stp priority"}</td>
577END
578;
579foreach (@stp_html) {
580 print $_->[1]; # row 2
581}
582print "\t</tr>\n";
583
63a1c81a 584# footer and submit button
1dcf513a
FB
585print <<END
586 </table>
1d6bc7a0
MT
587
588 <div id="submit-container">
23b26ce5 589 $restart_notice
1d6bc7a0
MT
590 <input type="submit" name="ACTION" value="$Lang::tr{"save"}">
591 </div>
592</form>
1dcf513a
FB
593END
594;
595
596### END OF TABLE ###
597
598&Header::closebox();
599&Header::closebigbox();
600&Header::closepage();