Project

General

Profile

« Previous | Next » 

Revision 8237727a

Added by Stephen Beaver over 9 years ago

Multiple changes to clean up the package installaion system by causing the installer to run in the background
whilst monitoring its output via AJAX to provide a better GUI display.
Work in progress !

View differences:

src/usr/local/sbin/pfSense-upgrade-GUI.sh
141 141
	rm $FIFO 2>/dev/null
142 142
fi
143 143

  
144
rm -f $JSONFILE
145
rm -f /cf/conf/$LOGFILE
146

  
144 147
mkfifo $FIFO
145 148

  
146 149
# Capture the JSON progress status and send it to the file we are watching
src/usr/local/www/pkg_mgr.php
212 212
				<?=$index['desc']?>
213 213
			</td>
214 214
			<td>
215
				<a title="<?=gettext("Click to install")?>" href="pkg_mgr_install.php?id=<?=$index['name']?>" class="btn btn-success">install</a>
215
				<a title="<?=gettext("Click to install")?>" href="pkg_mgr_install.php?id=<?=$index['name']?>" class="btn btn-success btn-sm">install</a>
216 216
<?php if(!$g['disablepackageinfo'] && $index['pkginfolink'] && $index['pkginfolink'] != $index['www']):?>
217
				<a target="_blank" title="<?=gettext("View more inforation")?>" href="<?=htmlspecialchars($index['pkginfolink'])?>" class="btn btn-default">info</a>
217
				<a target="_blank" title="<?=gettext("View more inforation")?>" href="<?=htmlspecialchars($index['pkginfolink'])?>" class="btn btn-default btn-sm">info</a>
218 218
<?php endif;?>
219 219
			</td>
220 220
		</tr>
src/usr/local/www/pkg_mgr_install.php
2 2
/* $Id$ */
3 3
/*
4 4
	pkg_mgr_install.php
5
	part of pfSense (https://www.pfsense.org)
6
	Copyright (C) 2013-2015 Electric Sheep Fencing, LP
7
	Copyright (C) 2004-2010 Scott Ullrich <sullrich@gmail.com>
8
	Copyright (C) 2005 Colin Smith
9
	All rights reserved.
10

  
11
	Redistribution and use in source and binary forms, with or without
12
	modification, are permitted provided that the following conditions are met:
13

  
14
	1. Redistributions of source code must retain the above copyright notice,
15
	   this list of conditions and the following disclaimer.
16

  
17
	2. Redistributions in binary form must reproduce the above copyright
18
	   notice, this list of conditions and the following disclaimer in the
19
	   documentation and/or other materials provided with the distribution.
20

  
21
	THIS SOFTWARE IS PROVIDED ``AS IS'' AND ANY EXPRESS OR IMPLIED WARRANTIES,
22
	INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY
23
	AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE
24
	AUTHOR BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY,
25
	OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF
26
	SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS
27
	INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN
28
	CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE)
29
	ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE
30
	POSSIBILITY OF SUCH DAMAGE.
31 5
*/
6
/* ====================================================================
7
 *	Copyright (c)  2004-2015  Electric Sheep Fencing, LLC. All rights reserved.
8
 *	Copyright (c)  2004, 2005 Scott Ullrich
9
 *	Copyright (c)  2005 Colin Smith
10
 *
11
 *	Redistribution and use in source and binary forms, with or without modification,
12
 *	are permitted provided that the following conditions are met:
13
 *
14
 *	1. Redistributions of source code must retain the above copyright notice,
15
 *		this list of conditions and the following disclaimer.
16
 *
17
 *	2. Redistributions in binary form must reproduce the above copyright
18
 *		notice, this list of conditions and the following disclaimer in
19
 *		the documentation and/or other materials provided with the
20
 *		distribution.
21
 *
22
 *	3. All advertising materials mentioning features or use of this software
23
 *		must display the following acknowledgment:
24
 *		"This product includes software developed by the pfSense Project
25
 *		 for use in the pfSense software distribution. (http://www.pfsense.org/).
26
 *
27
 *	4. The names "pfSense" and "pfSense Project" must not be used to
28
 *		 endorse or promote products derived from this software without
29
 *		 prior written permission. For written permission, please contact
30
 *		 coreteam@pfsense.org.
31
 *
32
 *	5. Products derived from this software may not be called "pfSense"
33
 *		nor may "pfSense" appear in their names without prior written
34
 *		permission of the Electric Sheep Fencing, LLC.
35
 *
36
 *	6. Redistributions of any form whatsoever must retain the following
37
 *		acknowledgment:
38
 *
39
 *	"This product includes software developed by the pfSense Project
40
 *	for use in the pfSense software distribution (http://www.pfsense.org/).
41
 *
42
 *	THIS SOFTWARE IS PROVIDED BY THE pfSense PROJECT ``AS IS'' AND ANY
43
 *	EXPRESSED OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
44
 *	IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR
45
 *	PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE pfSense PROJECT OR
46
 *	ITS CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
47
 *	SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT
48
 *	NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES;
49
 *	LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION)
50
 *	HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT,
51
 *	STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE)
52
 *	ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED
53
 *	OF THE POSSIBILITY OF SUCH DAMAGE.
54
 *
55
 *	====================================================================
56
 *
57
 */
32 58
/*
33 59
	pfSense_BUILDER_BINARIES:	/bin/rm
34 60
	pfSense_MODULE: pkgs
......
55 81
$static_status = "";
56 82
$sendto = "output";
57 83

  
84
//---------------------------------------------------------------------------------------------------------------------
85
// After an installation or removal has been started (mwexec(/usr/local/sbin/pfSense-upgrade-GUI.sh . . . )) AJAX calls
86
// are made to get status.
87
// The log file is read, the newest progress record retrieved, and the PID status obtained. The data is formatted
88
// as JSON before being returned to the AJAX caller (at the bottom of this file)
89
//
90
// Arguments received here:
91
//		logfilename = Passed to installation script to tell it how to name the log file we will parse
92
//		pid = PID of the background install/remove process
93
//		next_log_line = Send log file entries that come after this line number
94
//
95
// JSON items returned
96
//		log:
97
//		exitcode:
98
//		data:{current:, total}
99
//		pid:
100
//
101
// Todo:
102
//		Respect next_log_line and append log to output window rather than writing it
103

  
104
if($_REQUEST['ajax']) {
105
	$response = "";
106
	$code = 0;
107

  
108
	// Process log file -----------------------------------------------------------------------------------------------
109
	$logfile = fopen($_REQUEST['logfilename'] . '.txt', "r") or die("Unable to open " . $_REQUEST['logfilename']);
110

  
111
	if($logfile != FALSE) {
112
		$resparray = array();
113
		$statusarray = array('exitstatus' => 'success');
114

  
115
		// Log file is read a line at a time so that we can detect/modify certain entries
116
		while (($logline = fgets($logfile)) !== false) {
117
			// Check for return codes and replace with suitable strings
118
			if(strpos($logline, "_RC=") != false) {
119
				$code = str_replace("__RC=", "", $logline);
120

  
121
				if($code == 0) {
122
					$logline = gettext("Success"). "\n";
123
				} else {
124
					$logline = gettext("Failed") . "\n";
125
				}
126

  
127
				$response .= $logline;
128
				$statusarray = array('exitstatus' => $code);
129
			} else {
130
				$response .= htmlspecialchars($logline);
131
			}
132
		}
133

  
134
		fclose($logfile);
135
		$resparray['log'] = $response;
136
	} else {
137
		$resparray['log'] = "not_ready";
138
	}
139

  
140
	// Process progress file ------------------------------------------------------------------------------------------
141
	$progress = "";
142

  
143
	$JSONfile = fopen($_REQUEST['logfilename'] . '.json', "r") or die("Unable to open " . $_REQUEST['logfilename']);
144

  
145
	if($JSONfile != FALSE) {
146
		while (($logline = fgets($JSONfile)) !== false) {
147
			 if(strpos($logline, 'INFO_PROGRESS_TICK')) {
148
				$progress = $logline;
149
			 }
150
		}
151

  
152
		fclose($JSONfile);
153

  
154
		$progarray = array();
155
		if(strlen($progress) > 0)
156
			$progarray = json_decode($progress, true);
157

  
158
		if(!$progarray)
159
			$progarray = array();
160
	}
161

  
162
	// Check to see if our process is still running
163
	$pidarray = array('pid' => (file_exists("/proc/" . $_REQUEST['pid']) ? "running":"stopped"));
164

  
165
	// Glob all the arrays we have made togethr, and convert to JSON
166
	print(json_encode($progarray + $resparray + $pidarray + $statusarray));
167
	exit;
168
}
169

  
58 170
if ($_POST) {
59 171
	if (empty($_POST['id']) && $_POST['mode'] != 'reinstallall') {
60 172
		header("Location: pkg_mgr_installed.php");
......
71 183
		case 'showlog':
72 184
			break;
73 185
		case 'installedinfo':
74
		case 'reinstallxml':
75 186
		case 'reinstallpkg':
76 187
		case 'delete':
77 188
			if (empty($_GET['pkg'])) {
......
96 207
$tab_array[] = array(gettext("Installed packages"), false, "pkg_mgr_installed.php");
97 208
$tab_array[] = array(gettext("Package Installer"), true, "");
98 209
display_top_tabs($tab_array);
210

  
211
$start_polling = false;
212

  
99 213
?>
100 214
<form action="pkg_mgr_install.php" method="post" class="form-horizontal">
101 215
	<h2>Add / remove package</h2>
......
141 255
			<input type="submit" class="btn btn-default" name="pkgcancel" id="pkgcancel" value="Cancel"/>
142 256
		</div>
143 257
	</div>
144
<?php endif;?>
258
<?php endif;
145 259

  
146
<?php if (!empty($_POST['id']) || $_GET['mode'] == 'showlog' || ($_GET['mode'] == 'installedinfo' && !empty($_GET['pkg']))):?>
260
if($_POST['mode'] == 'delete') {
261
	$modetxt = gettext("removal");
262
} else if($_POST['mode'] == 'reinstallpkg') {
263
	$modetxt = gettext("reinstallation");
264
} else {
265
	$modetxt = gettext("installation");
266
}
267

  
268
if (!empty($_POST['id']) || $_GET['mode'] == 'showlog' || ($_GET['mode'] == 'installedinfo' && !empty($_GET['pkg']))):?>
269

  
270
	<div class="progress" style="display: none;">
271
		<div id="progressbar" class="progress-bar progress-bar-striped" role="progressbar" aria-valuemin="0" aria-valuemax="100" style="width: 1%"></div>
272
	</div>
273
	<br />
147 274
	<div class="panel panel-default">
148 275
		<div class="panel-heading">
149
			<h4 id="status"><?=gettext("Beginning package installation.")?></h4>
276
			<h2 class="panel-title" id="status"><?=gettext("Beginning package ") . $modetxt?>.</h2>
150 277
		</div>
151 278

  
152 279
		<div class="panel-body">
153
			<div class="content">
154
				<textarea rows="15" class="form-control" id="output"></textarea>
155
				<!--
156
				<div class="progress">
157
					<div id="progressbar" class="progress-bar progress-bar-striped" role="progressbar" aria-valuemin="0" aria-valuemax="100" style="width: 1%"><span class="sr-only">1% Complete</span></div>
158
				</div>
159
				-->
160
			</div>
280
			<textarea rows="15" class="form-control" id="output"></textarea>
161 281
		</div>
162 282
	</div>
283

  
284
	<div id="final" class="alert" role="alert" style=":display: none;"></div>
163 285
<?php endif?>
164 286
</form>
165 287
<?php
......
200 322

  
201 323
	switch ($_POST['mode']) {
202 324
		case 'delete':
203
			uninstall_package($pkgid);
204
			update_status(gettext("Package deleted."));
205
			$static_output .= "\n" . gettext("Package deleted.");
206
			update_output_window($static_output);
325
			$pid = mwexec('/usr/local/sbin/pfSense-upgrade-GUI.sh -r ' . $pkgid, false, false, true);
326
			$start_polling = true;
207 327
			filter_configure();
208 328
			break;
209
		case 'reinstallxml':
210
//			pkg_fetch_config_file($pkgid);
211
//			pkg_fetch_additional_files($pkgid);
212
			break;
329

  
213 330
		case 'reinstallpkg':
214 331
			delete_package_xml($pkgid);
215 332
			if (install_package($pkgid) != 0) {
......
252 369
			break;
253 370
		case 'installed':
254 371
		default:
255
			$status = install_package($pkgid);
372
			$pid = mwexec('/usr/local/sbin/pfSense-upgrade-GUI.sh -i ' . $pkgid, false, false, true);
373
			$start_polling = true;
374

  
256 375
			if ($status != 0) {
257 376
				update_status(gettext("Installation of") . " {$pkgid} " . gettext("FAILED! "));
258 377
				$static_output .= "\n" . gettext("Installation halted.");
259 378
				update_output_window($static_output);
260 379
			} else {
261 380
				$status_a = gettext(sprintf("Installation of %s completed.", $pkgid));
262
				update_status($status_a);
263
				$status = get_after_install_info($pkgid);
264
				if ($status) {
265
					$static_output .= "\n" . gettext("Installation completed.") . "\n{$pkgid} " . gettext("setup instructions") . ":\n{$status}";
266
				} else {
267
					$static_output .= "\n" . gettext("Installation completed. Please check to make sure that the package is configured from the respective menu then start the package.");
268
				}
269

  
270
				@file_put_contents("/tmp/{$pkgid}.info", $static_output);
271
				echo "<script type='text/javascript'>document.location=\"pkg_mgr_install.php?mode=installedinfo&pkg={$pkgid}\";</script>";
272 381
			}
382

  
273 383
			filter_configure();
274 384
			break;
275 385
	}
......
287 397
	conf_mount_ro();
288 398
}
289 399

  
290
include('foot.inc')?>
400
include('foot.inc')?>
401

  
402
<script>
403

  
404
//	Update the progress indicator
405
function setProgress(barName, percent) {
406
	$('.progress').show()
407
	$('#' + barName).css('width', percent + '%').attr('aria-valuenow', percent);
408
}
409

  
410
// Display a success banner
411
function show_success() {
412
	$('#final').removeClass("alert-info").addClass("alert-success");
413
	$('#final').html("<?=$pkgid?>" + " " + "<?=$modetxt?>" + " " + "<?=gettext(' successfully completed')?>");
414
	$('#final').show();
415
}
416

  
417
// Display a failure banner
418
function show_failure() {
419
	$('#final').addClass("alert-danger");
420
	$('#final').html("<?=$pkgid?>" + " " + "<?=$modetxt?>" + " " + "<?=gettext(' failed!')?>");
421
	$('#final').show();
422
}
423

  
424
// Ask the user to wait a bit
425
function show_info() {
426
	$('#final').addClass("alert-info");
427
	$('#final').html("Please wait while the " + "<?=$modetxt?>" + " of " + "<?=$pkgid?>" + " " + "completes." + "<br />(Some packages may take several minutes!)");
428
	$('#final').show();
429
}
430

  
431
function getLogsStatus() {
432
	var ajaxRequest;
433
	var repeat;
434
	var progress;
435

  
436
	repeat = true;
437

  
438
	ajaxRequest = $.ajax({
439
			url: "pkg_mgr_install.php",
440
			type: "post",
441
			data: { ajax: "ajax",
442
					pid: "<?=$pid?>",
443
					logfilename: "/cf/conf/webgui-log",
444
					next_log_line: "0"
445
				  }
446
		});
447

  
448
	// Deal with the results of the above ajax call
449
	ajaxRequest.done(function (response, textStatus, jqXHR) {
450
		var json = new Object;
451
		json = jQuery.parseJSON(response);
452

  
453
		if(json.log != "not ready") {
454
			// Write the log file to the "output" textarea
455
			$('#output').html(json.log);
456
			$('#output').scrollTop($('#output')[0].scrollHeight);
457

  
458
			// Update the progress bar
459
			progress = 0;
460
			if(json.data) {
461
				setProgress('progressbar', ((json.data.current * 100) / json.data.total));
462
				progress = json.data.total - json.data.current
463
			}
464

  
465
			// Now we need to determine if the installation/removal was successful, and tell the user. Not as easy as it sounds :)
466
			if((json.pid == "stopped") && (progress == 0) && (json.exitstatus == 0)) {
467
				show_success();
468
				repeat = false;
469
			}
470

  
471
			if((json.pid == "stopped") && ((progress != 0) || (json.exitstatus != 0))) {
472
				show_failure();
473
				repeat = false;
474
			}
475

  
476
			if((json.pid == "stopped") && ((progress != 0) || (json.exitstatus != 0))) {
477
				show_failure();
478
				repeat = false;
479
			}
480

  
481
			// ToDo: THere are more end conditions we need to catch
482
		}
483

  
484
		// And maybe do it again
485
		if(repeat)
486
			setTimeout(getLogsStatus, 500);
487
	});
488
}
489

  
490
if("<?=$start_polling?>") {
491
	setTimeout(getLogsStatus, 500);
492
	show_info();
493
}
494

  
495
</script>
src/usr/local/www/pkg_mgr_installed.php
2 2
/* $Id$ */
3 3
/*
4 4
	pkg_mgr_installed.php
5
	Copyright (C) 2013-2015 Electric Sheep Fencing, LP
6
	Copyright (C) 2004-2012 Scott Ullrich <sullrich@gmail.com>
7
	All rights reserved.
8

  
9
	Redistribution and use in source and binary forms, with or without
10
	modification, are permitted provided that the following conditions are met:
11

  
12
	1. Redistributions of source code must retain the above copyright notice,
13
	   this list of conditions and the following disclaimer.
14

  
15
	2. Redistributions in binary form must reproduce the above copyright
16
	   notice, this list of conditions and the following disclaimer in the
17
	   documentation and/or other materials provided with the distribution.
18

  
19
	THIS SOFTWARE IS PROVIDED ``AS IS'' AND ANY EXPRESS OR IMPLIED WARRANTIES,
20
	INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY
21
	AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE
22
	AUTHOR BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY,
23
	OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF
24
	SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS
25
	INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN
26
	CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE)
27
	ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE
28
	POSSIBILITY OF SUCH DAMAGE.
29 5
*/
6
/* ====================================================================
7
 *	Copyright (c)  2004-2015  Electric Sheep Fencing, LLC. All rights reserved.
8
 *	Copyright (c)  2004, 2005 Scott Ullrich
9
 *
10
 *	Redistribution and use in source and binary forms, with or without modification,
11
 *	are permitted provided that the following conditions are met:
12
 *
13
 *	1. Redistributions of source code must retain the above copyright notice,
14
 *		this list of conditions and the following disclaimer.
15
 *
16
 *	2. Redistributions in binary form must reproduce the above copyright
17
 *		notice, this list of conditions and the following disclaimer in
18
 *		the documentation and/or other materials provided with the
19
 *		distribution.
20
 *
21
 *	3. All advertising materials mentioning features or use of this software
22
 *		must display the following acknowledgment:
23
 *		"This product includes software developed by the pfSense Project
24
 *		 for use in the pfSense software distribution. (http://www.pfsense.org/).
25
 *
26
 *	4. The names "pfSense" and "pfSense Project" must not be used to
27
 *		 endorse or promote products derived from this software without
28
 *		 prior written permission. For written permission, please contact
29
 *		 coreteam@pfsense.org.
30
 *
31
 *	5. Products derived from this software may not be called "pfSense"
32
 *		nor may "pfSense" appear in their names without prior written
33
 *		permission of the Electric Sheep Fencing, LLC.
34
 *
35
 *	6. Redistributions of any form whatsoever must retain the following
36
 *		acknowledgment:
37
 *
38
 *	"This product includes software developed by the pfSense Project
39
 *	for use in the pfSense software distribution (http://www.pfsense.org/).
40
 *
41
 *	THIS SOFTWARE IS PROVIDED BY THE pfSense PROJECT ``AS IS'' AND ANY
42
 *	EXPRESSED OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
43
 *	IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR
44
 *	PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE pfSense PROJECT OR
45
 *	ITS CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
46
 *	SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT
47
 *	NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES;
48
 *	LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION)
49
 *	HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT,
50
 *	STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE)
51
 *	ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED
52
 *	OF THE POSSIBILITY OF SUCH DAMAGE.
53
 *
54
 *	====================================================================
55
 *
56
 */
30 57
/*
31 58
	pfSense_MODULE:	pkgs
32 59
*/
......
167 194
			<?=$pkgdescr?>
168 195
		</td>
169 196
		<td>
170
			<a href="pkg_mgr_install.php?mode=delete&amp;pkg=<?=$full_name?>" class="btn icon icon-remove" title="Remove"></a>
171
			<a href="pkg_mgr_install.php?mode=reinstallpkg&amp;pkg=<?=$full_name?>" class="btn icon icon-repeat" title="Reinstall"></a>
172
			<a href="pkg_mgr_install.php?mode=reinstallxml&amp;pkg=<?=$full_name?>" class="btn icon icon-refresh" title="<?=gettext("reinstall GUI")?>"></a>
197
			<a href="pkg_mgr_install.php?mode=delete&amp;pkg=<?=$full_name?>" class="btn btn-warning btn-xs">Remove</a>
198
			<a href="pkg_mgr_install.php?mode=reinstallpkg&amp;pkg=<?=$full_name?>" class="btn btn-info btn-xs">Reinstall</a>
199
<!--			<a href="pkg_mgr_install.php?mode=reinstallxml&amp;pkg=<?=$full_name?>" class="btn btn-info btn-xs"><?=gettext("reinstall GUI")?></a> 
173 200
<?php if(!$g['disablepackageinfo'] && $pkg['pkginfolink'] && $pkg['pkginfolink'] != $pkg['website']):?>
174
			<a target="_blank" title="<?=gettext("View more information")?>" href="<?=htmlspecialchars($pkg['pkginfolink'])?>" class="btn icon icon-info-sign"></a>
201
			<a target="_blank" title="<?=gettext("View more inforation")?>" href="<?=htmlspecialchars($pkg['pkginfolink'])?>" class="btn btn-info btn-xs">Info</a>
175 202
<?php endif;?>
203
-->
176 204
		</td>
177 205
	</tr>
178 206
<?php endforeach;?>

Also available in: Unified diff