aboutsummaryrefslogtreecommitdiff
diff options
context:
space:
mode:
-rw-r--r--daemon.php4
-rw-r--r--web/index.php10
-rw-r--r--web/js/gentoaster.js102
-rw-r--r--web/process.php61
-rw-r--r--web/status.php135
-rw-r--r--web/testdrive.php256
6 files changed, 347 insertions, 221 deletions
diff --git a/daemon.php b/daemon.php
index a25861e..6570185 100644
--- a/daemon.php
+++ b/daemon.php
@@ -3,11 +3,11 @@
// Gentoaster build daemon worker
// Licensed under GPL v3, see COPYING file
- $configurationsPath = "/var/www/gentoaster";
+ $configurationsPath = "/var/www/gentoaster/images";
$gentoasterPath = "/usr/share/gentoaster";
$buildToolName = "create_image.sh";
$wrapToolName = "wrap.sh";
- $externalHost = "192.168.2.167";
+ $externalHost = "192.168.2.169";
$lowPort = 5900;
$highPort = 5999;
diff --git a/web/index.php b/web/index.php
index 181d07b..1fd4d7d 100644
--- a/web/index.php
+++ b/web/index.php
@@ -3,10 +3,9 @@
$timezones = array();
$zonetab = file(ZONETAB);
- foreach ($zonetab as $buf)
- {
- if (substr($buf,0,1)=='#') continue;
- $rec = preg_split('/\s+/',$buf);
+ foreach ($zonetab as $buf) {
+ if (substr($buf, 0, 1)=='#') continue;
+ $rec = preg_split('/\s+/', $buf);
$key = $rec[2];
$val = $rec[2];
$c = count($rec);
@@ -15,8 +14,7 @@
ksort($timezones);
}
$timezoneOption = "";
- foreach ($timezones as $timezone => $description)
- {
+ foreach ($timezones as $timezone => $description) {
$timezoneOption .= "<option>".$timezone."</option>\n";
}
?>
diff --git a/web/js/gentoaster.js b/web/js/gentoaster.js
new file mode 100644
index 0000000..99cd514
--- /dev/null
+++ b/web/js/gentoaster.js
@@ -0,0 +1,102 @@
+$(function(){
+ $("#wizard").formwizard({
+ validationEnabled: true,
+ focusFirstInput: true,
+ disableUIStyles: true,
+ //historyEnabled: true,
+ validationOptions: {
+ rules: {
+ username: {
+ minlength: 2
+ },
+ password: {
+ minlength: 5
+ },
+ confirmpassword: {
+ minlength: 5,
+ equalTo: "#user_password"
+ },
+ boot_size: {
+ min: 32
+ },
+ swap_size: {
+ min: 16
+ },
+ root_size: {
+ min: 3072
+ },
+ size: {
+ min: 4020
+ }
+ },
+ messages: {
+ username: {
+ minlength: "Your username must consist of at least 2 characters"
+ },
+ password: {
+ minlength: "Your password must be at least 5 characters long"
+ },
+ confirmpassword: {
+ minlength: "Your password must be at least 5 characters long",
+ equalTo: "Please enter the same password in both boxes"
+ },
+ boot_size: {
+ min: "Your boot partition must be at least 32MB"
+ },
+ swap_size: {
+ min: "Your swap partition must be at least 128MB"
+ },
+ root_size: {
+ min: "Your root partition must be at least 3072MB"
+ },
+ size: {
+ min: "Your disk image must be at least 4020MB"
+ }
+ }
+ }
+ });
+
+ function partitioning_update(event, ui) {
+ if(!ui.values) {
+ ui.values = ui;
+ }
+ $('.partitionrange').css('width', $('.ui-slider-range').css('left'));
+ var boot_size = ui.values[0];
+ var swap_size = ui.values[1]-ui.values[0];
+ var root_size = $("#partitioning_split").slider( "option", "max" )-ui.values[1];
+ var image_size = boot_size+swap_size+root_size;
+ $("#partitioning_display").html("Disk image size: "+image_size+"MB<br>Boot partition size: "+boot_size+"MB<br>Swap partition size: "+swap_size+"MB<br>Root partition size: "+root_size+"MB");
+ $("#partitioning_boot").attr("value", boot_size);
+ $("#partitioning_swap").attr("value", swap_size);
+ $("#partitioning_root").attr("value", root_size);
+
+ //since it doesn't seem to redraw by itself, hacky fix!
+ $("#partitioning_split").slider("option", "values", $("#partitioning_split").slider("option", "values"));
+ }
+
+ $("#partitioning_split").slider({
+ range: true,
+ min: 0,
+ max: 4096,
+ values: [128, 640],
+ slide: partitioning_update,
+ change: partitioning_update,
+ create: function(event, ui) {
+ var slider = $(event.target);
+ var range = slider.find('.ui-slider-range');
+ var newrange = $('<div />');
+ newrange.appendTo(slider).addClass('ui-slider-range partitionrange').css('width', range.css('left'));
+ }
+ });
+
+ function update_slider() {
+ var new_size = $("#partitioning_size").val();
+ $("#partitioning_split").slider("option", "max", new_size);
+ partitioning_update(null, $("#partitioning_split").slider("option", "values"));
+ }
+
+
+ $("#partitioning_size").change(update_slider);
+
+ partitioning_update(null, $("#partitioning_split").slider("option", "values"));
+}); \ No newline at end of file
diff --git a/web/process.php b/web/process.php
index 1b68d03..93c5d68 100644
--- a/web/process.php
+++ b/web/process.php
@@ -1,46 +1,47 @@
<?php
- $build_id = uniqid();
- $boot_megabytes = intval($_POST["boot_size"]);
- $swap_megabytes = intval($_POST["swap_size"]);
- $root_megabytes = intval($_POST["root_size"]);
- $timezone = escapeshellarg($_POST["timezone"]);
- $hostname = escapeshellarg($_POST["hostname"]);
- $username = escapeshellarg($_POST["username"]);
- $password = escapeshellarg($_POST["password"]);
- $root_password = escapeshellarg($_POST["rootpassword"]);
- $packages_list = escapeshellarg($_POST["packages"]);
- $output_format = escapeshellarg($_POST["format"]);
+ $buildID = uniqid();
+ $bootMegabytes = intval($_POST["boot_size"]);
+ $swapMegabytes = intval($_POST["swap_size"]);
+ $rootMegabytes = intval($_POST["root_size"]);
+ $timezone = escapeshellarg($_POST["timezone"]);
+ $hostname = escapeshellarg($_POST["hostname"]);
+ $username = escapeshellarg($_POST["username"]);
+ $password = escapeshellarg($_POST["password"]);
+ $rootPassword = escapeshellarg($_POST["rootpassword"]);
+ $packagesList = escapeshellarg($_POST["packages"]);
+ $outputFormat = escapeshellarg($_POST["format"]);
- $packages_list = str_replace("\r\n", " ", $packages_list);
- $packages_list = str_replace("\n", " ", $packages_list);
+ $packagesList = str_replace("\r\n", " ", $packagesList);
+ $packagesList = str_replace("\n", " ", $packagesList);
-$ini_string = "[vmconfig]
+$iniString = "[vmconfig]
-BUILD_ID='$build_id'
-BOOT_MEGABYTES='$boot_megabytes'
-SWAP_MEGABYTES='$swap_megabytes'
-ROOT_MEGABYTES='$root_megabytes'
+BUILD_ID='$buildID'
+BOOT_MEGABYTES='$bootMegabytes'
+SWAP_MEGABYTES='$swapMegabytes'
+ROOT_MEGABYTES='$rootMegabytes'
TIMEZONE=$timezone
HOSTNAME=$hostname
-ROOT_PASSWORD=$root_password
+ROOT_PASSWORD=$rootPassword
DEFAULT_USERNAME=$username
DEFAULT_PASSWORD=$password
USE_FLAGS=''
PACKAGE_USE=''
FEATURES='parallel-fetch userfetch userpriv getbinpkg'
PACKAGE_ACCEPT_KEYWORDS=''
-PACKAGES_LIST=$packages_list
-OUTPUT_FORMAT=$output_format";
+PACKAGES_LIST=$packagesList
+OUTPUT_FORMAT=$outputFormat";
- $client = new GearmanClient();
- $client->addServer();
- $handle = $client->doBackground("invoke_image_build", $ini_string);
+ $client = new GearmanClient();
+ $client->addServer();
+ $handle = $client->doBackground("invoke_image_build", $iniString);
- $db = mysql_connect("localhost","gentoaster","");
- if(!$db) die("Could not connect to database ".mysql_error());
- mysql_select_db("gentoaster");
- mysql_query("INSERT INTO builds (id, handle) VALUES('".$build_id."','".$handle."')");
+ $db = mysql_connect("localhost", "gentoaster", "");
+ if(!$db) die("Could not connect to database ".mysql_error());
+ mysql_select_db("gentoaster");
+ $query = "INSERT INTO builds (id, handle) ".
+ "VALUES('".$buildID."','".$handle."')";
+ mysql_query($query);
- header("Location: finished.php?uuid=".$build_id);
-?>
+ header("Location: finished.php?uuid=".$buildID); \ No newline at end of file
diff --git a/web/status.php b/web/status.php
index 7371c16..20aacec 100644
--- a/web/status.php
+++ b/web/status.php
@@ -1,38 +1,55 @@
<?php
- $build_id = $_GET["uuid"];
- $buildresult = "Unknown!";
- $inprogress = false;
- $builddone = false;
+ $buildID = $_GET["uuid"];
+ $buildresult = "Unknown!";
+ $inprogress = false;
+ $builddone = false;
- $db = mysql_connect("localhost","gentoaster","");
- if(!$db) die("Could not connect to database ".mysql_error()."\n");
+ $db = mysql_connect("localhost", "gentoaster", "");
+ if (!$db) die("Could not connect to database ".mysql_error()."\n");
mysql_select_db("gentoaster");
- $result = mysql_query("SELECT handle FROM builds WHERE id = '".mysql_real_escape_string($build_id)."'");
- if(mysql_num_rows($result) == 1) {
+ $query = "SELECT handle FROM builds ".
+ "WHERE id = '".mysql_real_escape_string($buildID)."'";
+ $result = mysql_query($query);
+ if (mysql_num_rows($result) == 1) {
$handles = mysql_fetch_array($result);
$handle = $handles[0];
$client = new GearmanClient();
$client->addServer();
$status = $client->jobStatus($handle);
- if($status[0]) {
- if($status[3] != 0) {
+ if ($status[0]) {
+ if ($status[3] != 0) {
$percentage = ceil($status[2]/$status[3]*100);
- $buildresult = "Your build is currently running and is ".$percentage."% complete";
+ $buildresult = "Your build is currently running".
+ " and is ".$percentage."% complete";
$inprogress = true;
} else {
$buildresult = "Task has not yet been processed";
}
} else {
- $result = mysql_query("SELECT returncode, result FROM builds WHERE id = '".mysql_real_escape_string($build_id)."'");
+ $cleanBuildID = mysql_real_escape_string($buildID);
+ $query = "SELECT returncode, result FROM builds ".
+ "WHERE id = '".$cleanBuildID."'";
+ $result = mysql_query($query);
$jobres = mysql_fetch_array($result);
- if($jobres[0] !== NULL) {
- if($jobres[0] == 0) {
- $buildresult = "Your build is complete! What would you like to do now?<br /><br /><center><table><tr><td><a href=\"/gentoaster/".$build_id."/".$build_id.".tar.gz\"><img style=\"padding: 10px;\" src=\"img/icons/download.png\"></a></td><td><a href=\"testdrive.php?uuid=".$build_id."\"><img style=\"padding: 10px;\" src=\"img/icons/testdrive.png\"></a></td></tr><tr><td>Download</td><td>Testdrive</td></tr></table></center>";
- $builddone = true;
+ if ($jobres[0] !== NULL) {
+ if ($jobres[0] == 0) {
+ $buildresult = "Your build is complete! ".
+ "What would you like to do now?".
+ "<br /><br /><center>".
+ "<table><tr><td>".
+ "<a href=\"/gentoaster/".$buildID."/".$buildID.".tar.gz\">".
+ "<img style=\"padding: 10px;\" src=\"img/icons/download.png\">".
+ "</a></td><td>".
+ "<a href=\"testdrive.php?uuid=".$buildID."\">".
+ "<img style=\"padding: 10px;\" src=\"img/icons/testdrive.png\">".
+ "</a></td></tr>".
+ "<tr><td>Download</td><td>Testdrive</td></tr>".
+ "</table></center>";
+ $builddone = true;
} else {
- $buildresult = "Job returned with code ".$jobres[0].": ".$jobres[1];
+ $buildresult = "Job returned with code ".$jobres[0].": ".$jobres[1];
}
} else {
$buildresult = "Job failed";
@@ -44,44 +61,50 @@
?>
<html>
- <head>
- <title>Gentoaster</title>
- <link rel="stylesheet" type="text/css" href="css/style.css">
- <link rel="stylesheet" type="text/css" href="css/ui-lightness/jquery-ui-1.8.14.custom.css">
- <script type="text/javascript" src="/js/jquery-1.5.1.min.js"></script>
- <script type="text/javascript" src="/js/jquery-ui-1.8.14.custom.min.js"></script>
- <?php
- if($inprogress) {
- echo '<script>
- $(document).ready(function() {
- $("#progressbar").progressbar({ value: '.$percentage.' });
- });
- </script>';
- }
- ?>
- </head>
- <body>
- <div id="container">
- <div id="header"></div>
- <div id="content">
- <div id="main">
- <div id="status" class="step">
- <?php if(!$builddone) { ?>
- <h1>How's things?</h1>
- <?php } else { ?>
- <h1>It's showtime!</h1>
- <?php } ?>
- <p>
- <?php echo $buildresult; ?>
- <div id="progressbar"></div>
- </p>
- </div>
- </div>
- <div id="navigation">
+ <head>
+ <title>Gentoaster</title>
+ <link rel="stylesheet" type="text/css" href="css/style.css">
+ <link rel="stylesheet" type="text/css" href="css/ui-lightness/jquery-ui-1.8.14.custom.css">
+ <script type="text/javascript" src="/js/jquery-1.5.1.min.js"></script>
+ <script type="text/javascript" src="/js/jquery-ui-1.8.14.custom.min.js"></script>
+ <?php
+ if ($inprogress) {
+ echo '<script>
+ $(document).ready(function() {
+ $("#progressbar").progressbar({ value: '.$percentage.' });
+ });
+ </script>';
+ }
+ ?>
+ </head>
+ <body>
+ <div id="container">
+ <div id="header"></div>
+ <div id="content">
+ <div id="main">
+ <div id="status" class="step">
+ <?php
+ if (!$builddone) {
+ ?>
+ <h1>How's things?</h1>
+ <?php
+ } else {
+ ?>
+ <h1>It's showtime!</h1>
+ <?php
+ }
+ ?>
+ <p>
+ <?php echo $buildresult; ?>
+ <div id="progressbar"></div>
+ </p>
+ </div>
+ </div>
+ <div id="navigation">
- </div>
- </div>
- </div>
- </script>
- </body>
+ </div>
+ </div>
+ </div>
+ </script>
+ </body>
</html> \ No newline at end of file
diff --git a/web/testdrive.php b/web/testdrive.php
index b6e65b8..a313f39 100644
--- a/web/testdrive.php
+++ b/web/testdrive.php
@@ -1,37 +1,39 @@
<?php
- $build_id = $_GET["uuid"];
- $buildresult = "Unknown!";
- $inprogress = false;
+ $buildID = $_GET["uuid"];
+ $buildresult = "Unknown!";
+ $inprogress = false;
- $db = mysql_connect("localhost","gentoaster","");
- if(!$db) die("Could not connect to database ".mysql_error()."\n");
+ $db = mysql_connect("localhost", "gentoaster", "");
+ if (!$db) die("Could not connect to database ".mysql_error()."\n");
mysql_select_db("gentoaster");
- $result = mysql_query("SELECT handle FROM builds WHERE id = '".mysql_real_escape_string($build_id)."'");
- if(mysql_num_rows($result) == 1) {
+ $result = mysql_query("SELECT handle FROM builds WHERE id = '".mysql_real_escape_string($buildID)."'");
+ if (mysql_num_rows($result) == 1) {
$handles = mysql_fetch_array($result);
$handle = $handles[0];
$client = new GearmanClient();
$client->addServer();
$status = $client->jobStatus($handle);
- if($status[0]) {
- header("Location: status.php?uuid=".$build_id);
+ if ($status[0]) {
+ header("Location: status.php?uuid=".$buildID);
} else {
- $result = mysql_query("SELECT returncode, result FROM builds WHERE id = '".mysql_real_escape_string($build_id)."'");
+ $cleanBuildID = mysql_real_escape_string($buildID);
+ $query = "SELECT returncode, result FROM builds WHERE id = '".$cleanBuildID."'";
+ $result = mysql_query();
$jobres = mysql_fetch_array($result);
- if($jobres[0] !== NULL) {
- if($jobres[0] == 0) {
- // we're built, let's do this
- $client = new GearmanClient();
- $client->addServer();
- $server = $client->do("invoke_start_image", $build_id);
- $server = unserialize($server);
+ if ($jobres[0] !== NULL) {
+ if ($jobres[0] == 0) {
+ // we're built, let's do this
+ $client = new GearmanClient();
+ $client->addServer();
+ $server = $client->do("invoke_start_image", $buildID);
+ $server = unserialize($server);
} else {
- header("Location: status.php?uuid=".$build_id);
+ header("Location: status.php?uuid=".$buildID);
}
} else {
- header("Location: status.php?uuid=".$build_id);
+ header("Location: status.php?uuid=".$buildID);
}
}
} else {
@@ -40,114 +42,114 @@
?>
<html>
- <head>
- <title>Gentoaster</title>
- <link rel="stylesheet" type="text/css" href="css/style.css">
- <link rel="stylesheet" type="text/css" href="css/ui-lightness/jquery-ui-1.8.14.custom.css">
- <script type="text/javascript" src="/js/jquery-1.5.1.min.js"></script>
- <script type="text/javascript" src="/js/jquery-ui-1.8.14.custom.min.js"></script>
+ <head>
+ <title>Gentoaster</title>
+ <link rel="stylesheet" type="text/css" href="css/style.css">
+ <link rel="stylesheet" type="text/css" href="css/ui-lightness/jquery-ui-1.8.14.custom.css">
+ <script type="text/javascript" src="/js/jquery-1.5.1.min.js"></script>
+ <script type="text/javascript" src="/js/jquery-ui-1.8.14.custom.min.js"></script>
<script type="text/javascript" src="include/vnc.js"></script>
- </head>
- <body>
- <div id="container">
- <div id="header"></div>
- <div id="content">
- <div id="main">
- <div id="status" class="step">
- <h1>Let's fire her up!</h1>
- <center>
- <div id="VNC_screen">
- <div id="VNC_status_bar" class="VNC_status_bar" style="margin-top: -25px;">
- <table border=0 width="100%"><tr>
- <td><div id="VNC_status" style="display: none;">Loading</div></td>
- <td width="1%"><div id="VNC_buttons">
- <input type=button value="Send CtrlAltDel"
- id="sendCtrlAltDelButton">
- </div></td>
- </tr></table>
- </div>
- <canvas id="VNC_canvas" width="640px" height="20px">
- Canvas not supported.
- </canvas>
- </div>
- </center>
-
- <script>
- /*jslint white: false */
- /*global window, $, Util, RFB, */
- "use strict";
-
- var rfb;
-
- function passwordRequired(rfb) {
- var msg;
- msg = '<form onsubmit="return setPassword();"';
- msg += ' style="margin-bottom: 0px">';
- msg += 'Password Required: ';
- msg += '<input type=password size=10 id="password_input" class="VNC_status">';
- msg += '<\/form>';
- $D('VNC_status_bar').setAttribute("class", "VNC_status_warn");
- $D('VNC_status').innerHTML = msg;
- }
- function setPassword() {
- rfb.sendPassword($D('password_input').value);
- return false;
- }
- function sendCtrlAltDel() {
- rfb.sendCtrlAltDel();
- return false;
- }
- function updateState(rfb, state, oldstate, msg) {
- var s, sb, cad, level;
- s = $D('VNC_status');
- sb = $D('VNC_status_bar');
- cad = $D('sendCtrlAltDelButton');
- switch (state) {
- case 'failed': level = "error"; break;
- case 'fatal': level = "error"; break;
- case 'normal': level = "normal"; break;
- case 'disconnected': level = "normal"; break;
- case 'loaded': level = "normal"; break;
- default: level = "warn"; break;
- }
-
- if (state === "normal") { cad.disabled = false; }
- else { cad.disabled = true; }
-
- if (typeof(msg) !== 'undefined') {
- sb.setAttribute("class", "VNC_status_" + level);
- s.innerHTML = msg;
- }
- }
-
- function connect() {
- var host, port, password;
-
- $D('sendCtrlAltDelButton').onclick = sendCtrlAltDel;
-
- host = "<?php echo $server[0]; ?>";
- port = <?php echo $server[1]; ?>;
- password = "";
-
- rfb = new RFB({'target': $D('VNC_canvas'),
- 'encrypt': WebUtil.getQueryVar('encrypt', false),
- 'true_color': WebUtil.getQueryVar('true_color', true),
- 'local_cursor': WebUtil.getQueryVar('cursor', true),
- 'shared': WebUtil.getQueryVar('shared', true),
- 'updateState': updateState,
- 'onPasswordRequired': passwordRequired});
- rfb.connect(host, port, password);
- };
-
- setTimeout("connect()", 2000);
- </script>
- </div>
- </div>
- <div id="navigation">
+ </head>
+ <body>
+ <div id="container">
+ <div id="header"></div>
+ <div id="content">
+ <div id="main">
+ <div id="status" class="step">
+ <h1>Let's fire her up!</h1>
+ <center>
+ <div id="VNC_screen">
+ <div id="VNC_status_bar" class="VNC_status_bar" style="margin-top: -25px;">
+ <table border=0 width="100%"><tr>
+ <td><div id="VNC_status" style="display: none;">Loading</div></td>
+ <td width="1%"><div id="VNC_buttons">
+ <input type=button value="Send CtrlAltDel"
+ id="sendCtrlAltDelButton">
+ </div></td>
+ </tr></table>
+ </div>
+ <canvas id="VNC_canvas" width="640px" height="20px">
+ Canvas not supported.
+ </canvas>
+ </div>
+ </center>
+
+ <script>
+ /*jslint white: false */
+ /*global window, $, Util, RFB, */
+ "use strict";
+
+ var rfb;
+
+ function passwordRequired(rfb) {
+ var msg;
+ msg = '<form onsubmit="return setPassword();"';
+ msg += ' style="margin-bottom: 0px">';
+ msg += 'Password Required: ';
+ msg += '<input type=password size=10 id="password_input" class="VNC_status">';
+ msg += '<\/form>';
+ $D('VNC_status_bar').setAttribute("class", "VNC_status_warn");
+ $D('VNC_status').innerHTML = msg;
+ }
+ function setPassword() {
+ rfb.sendPassword($D('password_input').value);
+ return false;
+ }
+ function sendCtrlAltDel() {
+ rfb.sendCtrlAltDel();
+ return false;
+ }
+ function updateState(rfb, state, oldstate, msg) {
+ var s, sb, cad, level;
+ s = $D('VNC_status');
+ sb = $D('VNC_status_bar');
+ cad = $D('sendCtrlAltDelButton');
+ switch (state) {
+ case 'failed': level = "error"; break;
+ case 'fatal': level = "error"; break;
+ case 'normal': level = "normal"; break;
+ case 'disconnected': level = "normal"; break;
+ case 'loaded': level = "normal"; break;
+ default: level = "warn"; break;
+ }
+
+ if (state === "normal") { cad.disabled = false; }
+ else { cad.disabled = true; }
+
+ if (typeof(msg) !== 'undefined') {
+ sb.setAttribute("class", "VNC_status_" + level);
+ s.innerHTML = msg;
+ }
+ }
+
+ function connect() {
+ var host, port, password;
+
+ $D('sendCtrlAltDelButton').onclick = sendCtrlAltDel;
+
+ host = "<?php echo $server[0]; ?>";
+ port = <?php echo $server[1]; ?>;
+ password = "";
+
+ rfb = new RFB({'target': $D('VNC_canvas'),
+ 'encrypt': WebUtil.getQueryVar('encrypt', false),
+ 'true_color': WebUtil.getQueryVar('true_color', true),
+ 'local_cursor': WebUtil.getQueryVar('cursor', true),
+ 'shared': WebUtil.getQueryVar('shared', true),
+ 'updateState': updateState,
+ 'onPasswordRequired': passwordRequired});
+ rfb.connect(host, port, password);
+ };
+
+ setTimeout("connect()", 2000);
+ </script>
+ </div>
+ </div>
+ <div id="navigation">
- </div>
- </div>
- </div>
- </script>
- </body>
+ </div>
+ </div>
+ </div>
+ </script>
+ </body>
</html> \ No newline at end of file