mirror of
https://github.com/unraid/webgui.git
synced 2026-03-05 08:00:49 -06:00
Docker: resolve progress hangs, Docker Index V2 api "updates available" compatibility, code cleanup
This commit is contained in:
@@ -26,17 +26,6 @@ $DockerTemplates = new DockerTemplates();
|
||||
|
||||
$echo = function($m){echo "<pre>".print_r($m,true)."</pre>";};
|
||||
|
||||
function prepareDir($dir){
|
||||
if (strlen($dir)){
|
||||
if ( ! is_dir($dir) && ! is_file($dir)){
|
||||
mkdir($dir, 0777, true);
|
||||
chown($dir, 'nobody');
|
||||
chgrp($dir, 'users');
|
||||
sleep(1);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
function ContainerExist($container){
|
||||
global $DockerClient;
|
||||
$all_containers = $DockerClient->getDockerContainers();
|
||||
@@ -44,7 +33,6 @@ function ContainerExist($container){
|
||||
foreach ($all_containers as $ct) {
|
||||
if ($ct['Name'] == $container){
|
||||
return True;
|
||||
break;
|
||||
}
|
||||
}
|
||||
return False;
|
||||
@@ -57,7 +45,6 @@ function ImageExist($image){
|
||||
foreach ($all_images as $img) {
|
||||
if ( ! is_bool(strpos($img['Tags'][0], $image)) ){
|
||||
return True;
|
||||
break;
|
||||
}
|
||||
}
|
||||
return False;
|
||||
@@ -67,51 +54,53 @@ function trimLine($text){
|
||||
return preg_replace("/([\n^])[\s]+/", '$1', $text);
|
||||
}
|
||||
|
||||
$pullecho = function($line) {
|
||||
global $alltotals;
|
||||
$cnt = json_decode( $line, TRUE );
|
||||
$id = ( isset( $cnt['id'] )) ? $cnt['id'] : "";
|
||||
$status = ( isset( $cnt['status'] )) ? $cnt['status'] : "";
|
||||
if (strlen(trim($status)) && strlen(trim($id))) {
|
||||
if ( isset($cnt['progressDetail']['total']) && $cnt['progressDetail']['total'] > 0 ) {
|
||||
$alltotals[$cnt['id']] = $cnt['progressDetail']['total'];
|
||||
}
|
||||
echo "<script>addToID('${id}','${status}');</script>\n";
|
||||
@flush();
|
||||
}
|
||||
if ($status == "Downloading") {
|
||||
$total = $cnt['progressDetail']['total'];
|
||||
$current = $cnt['progressDetail']['current'];
|
||||
$alltotals[$cnt['id']] = $cnt['progressDetail']['current'];
|
||||
if ($total > 0) {
|
||||
$percentage = round(($current/$total) * 100);
|
||||
echo "<script>progress('${id}',' ". $percentage ."% of " . sizeToHuman($total) . "');</script>\n";
|
||||
} else {
|
||||
// Docker must not know the total download size (http-chunked or something?)
|
||||
// just show the current download progress without the percentage
|
||||
echo "<script>progress('${id}',' " . sizeToHuman($current) . "');</script>\n";
|
||||
}
|
||||
@flush();
|
||||
}
|
||||
};
|
||||
|
||||
|
||||
function pullImage($image) {
|
||||
global $DockerClient, $pullecho, $alltotals;
|
||||
$alltotals = array();
|
||||
function pullImage($name, $image) {
|
||||
global $DockerClient, $DockerTemplates, $DockerUpdate;
|
||||
if (! preg_match("/:[\w]*$/i", $image)) $image .= ":latest";
|
||||
readfile("/usr/local/emhttp/plugins/dynamix.docker.manager/log.htm");
|
||||
echo "<script>
|
||||
addLog('<fieldset style=\"margin-top:1px;\" class=\"CMD\"><legend>Pulling image: ${image}</legend><p class=\"logLine\" id=\"logBody\"></p></fieldset>');
|
||||
function progress(id, prog){ $('.'+id+'_progress:last').text(prog);}
|
||||
function addToID(id, m) {
|
||||
if ($('#'+id).length === 0){ addLog('<span id=\"'+id+'\">IMAGE ID ['+id+']: </span>');}
|
||||
if ($('#'+id).find('.content:last').text() != m){ $('#'+id).append('<span class=\"content\">'+m+'</span><span class=\"'+id+'_progress\"></span>. ');}
|
||||
}</script>";
|
||||
|
||||
echo "<script>addLog('<fieldset style=\"margin-top:1px;\" class=\"CMD\"><legend>Pulling image: ${image}</legend><p class=\"logLine\" id=\"logBody\"></p></fieldset>');</script>\n";
|
||||
@flush();
|
||||
|
||||
$DockerClient->pullImage($image, $pullecho);
|
||||
$alltotals = array();
|
||||
|
||||
// Force information reload
|
||||
$DockerTemplates->removeInfo($name, $image);
|
||||
|
||||
$DockerClient->pullImage($image, function ($line) use (&$alltotals, $DockerUpdate, $image) {
|
||||
$cnt = json_decode( $line, TRUE );
|
||||
$id = ( isset( $cnt['id'] )) ? $cnt['id'] : "";
|
||||
$status = ( isset( $cnt['status'] )) ? $cnt['status'] : "";
|
||||
if (strlen(trim($status)) && strlen(trim($id))) {
|
||||
if (!empty($cnt['progressDetail']) && !empty($cnt['progressDetail']['total'])) {
|
||||
$alltotals[$cnt['id']] = $cnt['progressDetail']['total'];
|
||||
}
|
||||
echo "<script>addToID('${id}','${status}');</script>\n";
|
||||
}
|
||||
if ($status == "Downloading") {
|
||||
$total = $cnt['progressDetail']['total'];
|
||||
$current = $cnt['progressDetail']['current'];
|
||||
$alltotals[$cnt['id']] = $cnt['progressDetail']['current'];
|
||||
if ($total > 0) {
|
||||
$percentage = round(($current/$total) * 100);
|
||||
echo "<script>progress('${id}',' ". $percentage ."% of " . sizeToHuman($total) . "');</script>\n";
|
||||
} else {
|
||||
// Docker must not know the total download size (http-chunked or something?)
|
||||
// just show the current download progress without the percentage
|
||||
echo "<script>progress('${id}',' " . sizeToHuman($current) . "');</script>\n";
|
||||
}
|
||||
} else if (!empty($status) && empty($id)) {
|
||||
if (strpos($status, 'Status: ') === 0) {
|
||||
echo "<script>addLog('${status}');</script>\n";
|
||||
}
|
||||
if (strpos($status, 'Digest: ') === 0) {
|
||||
$DockerUpdate->setUpdateStatus($image, substr($status, 8));
|
||||
}
|
||||
}
|
||||
@flush();
|
||||
});
|
||||
|
||||
echo "<script>addLog('<br><b>TOTAL DATA PULLED:</b> " . sizeToHuman(array_sum($alltotals)) . "<span class=\"progress\"></span>');</script>\n";
|
||||
@flush();
|
||||
}
|
||||
|
||||
function sizeToHuman($size) {
|
||||
@@ -233,6 +222,7 @@ function xmlToVar($xml) {
|
||||
if (isset($xml->Networking->Publish->Port)) {
|
||||
$portNum = 0;
|
||||
foreach ($xml->Networking->Publish->Port as $port) {
|
||||
if (empty(xml_decode($port->ContainerPort))) continue;
|
||||
$portNum += 1;
|
||||
$out['Config'][] = array('Name' => "Port ${portNum}",
|
||||
'Target' => xml_decode($port->ContainerPort),
|
||||
@@ -251,6 +241,7 @@ function xmlToVar($xml) {
|
||||
if (isset($xml->Data->Volume)) {
|
||||
$volNum = 0;
|
||||
foreach ($xml->Data->Volume as $vol) {
|
||||
if (empty(xml_decode($vol->ContainerDir))) continue;
|
||||
$volNum += 1;
|
||||
$out['Config'][] = array('Name' => "Path ${volNum}",
|
||||
'Target' => xml_decode($vol->ContainerDir),
|
||||
@@ -269,6 +260,7 @@ function xmlToVar($xml) {
|
||||
if (isset($xml->Environment->Variable)) {
|
||||
$varNum = 0;
|
||||
foreach ($xml->Environment->Variable as $var) {
|
||||
if (empty(xml_decode($var->Name))) continue;
|
||||
$varNum += 1;
|
||||
$out['Config'][] = array('Name' => "Variable ${varNum}",
|
||||
'Target' => xml_decode($var->Name),
|
||||
@@ -386,41 +378,44 @@ if (isset($_POST['contName'])) {
|
||||
goto END;
|
||||
}
|
||||
|
||||
include("/usr/local/emhttp/plugins/dynamix.docker.manager/log.php");
|
||||
@flush();
|
||||
|
||||
// Will only pull image if it's absent
|
||||
if (! ImageExist($Repository)) {
|
||||
// Pull image
|
||||
pullImage($Repository);
|
||||
pullImage($Name, $Repository);
|
||||
}
|
||||
|
||||
// Saving the generated configuration file.
|
||||
$userTmplDir = $dockerManPaths['templates-user'];
|
||||
if(is_dir($userTmplDir) === FALSE){
|
||||
if (!is_dir($userTmplDir)) {
|
||||
mkdir($userTmplDir, 0777, true);
|
||||
}
|
||||
if(strlen($Name)) {
|
||||
if (!empty($Name)) {
|
||||
$filename = sprintf('%s/my-%s.xml', $userTmplDir, $Name);
|
||||
file_put_contents($filename, $postXML);
|
||||
}
|
||||
|
||||
// Remove existing container
|
||||
if (ContainerExist($Name)){
|
||||
if (ContainerExist($Name)) {
|
||||
// attempt graceful stop of container first
|
||||
$_GET['cmd'] = "/plugins/dynamix.docker.manager/scripts/docker stop $Name";
|
||||
include($dockerManPaths['plugin'] . "/include/Exec.php");
|
||||
|
||||
// force kill container if still running after 30 seconds
|
||||
// force kill container if still running after 10 seconds
|
||||
$_GET['cmd'] = "/plugins/dynamix.docker.manager/scripts/docker rm -f $Name";
|
||||
include($dockerManPaths['plugin'] . "/include/Exec.php");
|
||||
}
|
||||
|
||||
// Remove old container if renamed
|
||||
$existing = isset($_POST['existingContainer']) ? $_POST['existingContainer'] : FALSE;
|
||||
if ($existing && ContainerExist($existing)){
|
||||
if ($existing && ContainerExist($existing)) {
|
||||
// attempt graceful stop of container first
|
||||
$_GET['cmd'] = "/plugins/dynamix.docker.manager/scripts/docker stop $existing";
|
||||
include($dockerManPaths['plugin'] . "/include/Exec.php");
|
||||
|
||||
// force kill container if still running after 30 seconds
|
||||
// force kill container if still running after 10 seconds
|
||||
$_GET['cmd'] = "/plugins/dynamix.docker.manager/scripts/docker rm -f $existing";
|
||||
include($dockerManPaths['plugin'] . "/include/Exec.php");
|
||||
}
|
||||
@@ -429,10 +424,6 @@ if (isset($_POST['contName'])) {
|
||||
$_GET['cmd'] = $cmd;
|
||||
include($dockerManPaths['plugin'] . "/include/Exec.php");
|
||||
|
||||
// Force information reload
|
||||
$DockerTemplates->removeInfo($Name, $Repository);
|
||||
$DockerUpdate->reloadUpdateStatus($Repository);
|
||||
|
||||
echo '<center><input type="button" value="Done" onclick="done()"></center><br>';
|
||||
goto END;
|
||||
}
|
||||
@@ -441,12 +432,16 @@ if (isset($_POST['contName'])) {
|
||||
## UPDATE CONTAINER
|
||||
##
|
||||
if ($_GET['updateContainer']){
|
||||
include("/usr/local/emhttp/plugins/dynamix.docker.manager/log.php");
|
||||
@flush();
|
||||
|
||||
foreach ($_GET['ct'] as $value) {
|
||||
$Name = urldecode($value);
|
||||
$tmpl = $DockerTemplates->getUserTemplate($Name);
|
||||
|
||||
if (! $tmpl){
|
||||
echo 'Configuration not found. Was this container created using this plugin?';
|
||||
echo "<script>addLog('<p>Configuration not found. Was this container created using this plugin?</p>');</script>";
|
||||
@flush();
|
||||
continue;
|
||||
}
|
||||
|
||||
@@ -454,15 +449,13 @@ if ($_GET['updateContainer']){
|
||||
list($cmd, $Name, $Repository) = xmlToCommand($tmpl);
|
||||
$Registry = getXmlVal($xml, "Registry");
|
||||
|
||||
readfile("/usr/local/emhttp/plugins/dynamix.docker.manager/log.htm");
|
||||
echo "<script>addLog('<p>Preparing to update: " . $Repository . "</p>');</script>";
|
||||
@flush();
|
||||
|
||||
$oldContainerID = $DockerClient->getImageID($Repository);
|
||||
|
||||
// Pull image
|
||||
flush();
|
||||
pullImage($Repository);
|
||||
pullImage($Name, $Repository);
|
||||
|
||||
// attempt graceful stop of container first
|
||||
$_GET['cmd'] = "/plugins/dynamix.docker.manager/scripts/docker stop $Name";
|
||||
@@ -480,10 +473,6 @@ if ($_GET['updateContainer']){
|
||||
$_GET['cmd'] = sprintf("/plugins/dynamix.docker.manager/scripts/docker rmi %s", $oldContainerID);
|
||||
include($dockerManPaths['plugin'] . "/include/Exec.php");
|
||||
}
|
||||
|
||||
// Force information reload
|
||||
$DockerTemplates->removeInfo($Name, $Repository);
|
||||
$DockerUpdate->reloadUpdateStatus($Repository);
|
||||
}
|
||||
|
||||
echo '<center><input type="button" value="Done" onclick="window.parent.jQuery(\'#iframe-popup\').dialog(\'close\');"></center><br>';
|
||||
@@ -514,6 +503,7 @@ if ($_GET['xmlTemplate']) {
|
||||
if ($arrConfig['Type'] == 'Path' && strtolower($arrConfig['Target']) == '/config') {
|
||||
$arrConfig['Default'] = $arrConfig['Value'] = realpath($dockercfg["DOCKER_APP_CONFIG_PATH"]).'/'.$xml["Name"];
|
||||
$arrConfig['Display'] = 'hidden';
|
||||
$arrConfig['Name'] = 'AppData Config Path';
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -524,6 +514,7 @@ if ($_GET['xmlTemplate']) {
|
||||
if ($arrConfig['Type'] == 'Path' && strtolower($arrConfig['Target']) == '/unraid') {
|
||||
$arrConfig['Default'] = $arrConfig['Value'] = realpath($dockercfg["DOCKER_APP_UNRAID_PATH"]);
|
||||
$arrConfig['Display'] = 'hidden';
|
||||
$arrConfig['Name'] = 'unRAID Share Path';
|
||||
$boolFound = true;
|
||||
}
|
||||
}
|
||||
@@ -562,10 +553,26 @@ $showAdditionalInfo = '';
|
||||
optgroup.title{background-color:#625D5D;color:#FFFFFF;text-align:center;margin-top:10px;}
|
||||
.textPath{width:270px;}
|
||||
|
||||
table.Preferences{width:100%;}
|
||||
table.Preferences tr>td{font-weight: bold;padding-right: 10px;}
|
||||
table.Preferences tr>td+td{font-weight: normal;}
|
||||
table td{font-size:14px;vertical-align:bottom;text-align:left;}
|
||||
table {
|
||||
margin-top: 0;
|
||||
}
|
||||
table tr {
|
||||
vertical-align:top;
|
||||
line-height:24px;
|
||||
}
|
||||
table tr td:nth-child(odd) {
|
||||
width: 150px;
|
||||
text-align: right;
|
||||
padding-right: 10px;
|
||||
white-space: nowrap;
|
||||
}
|
||||
table tr td:nth-child(even) {
|
||||
width: 80px;
|
||||
}
|
||||
table tr td:last-child {
|
||||
width: inherit;
|
||||
}
|
||||
|
||||
.show{display:block;}
|
||||
.inline_help{font-size:12px;font-weight: normal;}
|
||||
.desc{padding:6px;line-height:15px;width:inherit;}
|
||||
@@ -681,7 +688,7 @@ $showAdditionalInfo = '';
|
||||
opts.Buttons,
|
||||
(opts.Required == "true") ? "required" : ""
|
||||
);
|
||||
newConfig = "<div id='ConfigNum"+opts.Number+"' style='margin-top: 30px;' class='"+opts.Display+"'>"+newConfig+"</div>";
|
||||
newConfig = "<div id='ConfigNum"+opts.Number+"' class='"+opts.Display+"'>"+newConfig+"</div>";
|
||||
newConfig = $($.parseHTML(newConfig));
|
||||
value = newConfig.find("input[name='confValue[]']");
|
||||
if (opts.Type == "Path") {
|
||||
@@ -931,16 +938,16 @@ $showAdditionalInfo = '';
|
||||
}
|
||||
}
|
||||
</script>
|
||||
<div style='display: inline; float: right; margin: -47px -5px;' id='docker_tabbed'></div>
|
||||
<div id="docker_tabbed" style="display: inline; float: right; margin: -47px 0px;"></div>
|
||||
<div id="dialogAddConfig" style="display: none"></div>
|
||||
<form method="GET" id="formTemplate">
|
||||
<input type="hidden" id="xmlTemplate" name="xmlTemplate" value="" />
|
||||
<input type="hidden" id="rmTemplate" name="rmTemplate" value="" />
|
||||
</form>
|
||||
|
||||
<form method="POST" autocomplete="off">
|
||||
<div id="canvas" style="z-index:1;">
|
||||
<table class="Preferences">
|
||||
<div id="canvas" style="z-index:1;margin-top:-21px;">
|
||||
<form method="POST" autocomplete="off">
|
||||
<table>
|
||||
<? if($xmlType == "edit"):
|
||||
if (ContainerExist($templateName)): echo "<input type='hidden' name='existingContainer' value='${templateName}'>\n"; endif;
|
||||
else:?>
|
||||
@@ -1001,7 +1008,7 @@ $showAdditionalInfo = '';
|
||||
</tr>
|
||||
<?endif;?>
|
||||
<tr <?=$showAdditionalInfo?>>
|
||||
<td style="width: 150px; vertical-align: top;">Name:</td>
|
||||
<td>Name:</td>
|
||||
<td><input type="text" name="contName" class="textPath" required></td>
|
||||
</tr>
|
||||
<tr <?=$showAdditionalInfo?>>
|
||||
@@ -1012,13 +1019,20 @@ $showAdditionalInfo = '';
|
||||
</td>
|
||||
</tr>
|
||||
<tr id="Overview" class="basic">
|
||||
<td style="width: 150px; vertical-align: top;">Overview:</td>
|
||||
<td><div style="color: #3B5998; width:50%;" name="contDescription"></div></td>
|
||||
<td>Overview:</td>
|
||||
<td><div style="color: #3B5998; width:50%; line-height: 16px; padding-top: 4px;" name="contDescription"></div></td>
|
||||
</tr>
|
||||
<tr id="Overview" class="advanced">
|
||||
<td style="width: 150px; vertical-align: top;">Overview:</td>
|
||||
<td>Overview:</td>
|
||||
<td><textarea name="contOverview" rows="10" cols="71" class="textTemplate"></textarea></td>
|
||||
</tr>
|
||||
<tr>
|
||||
<td colspan="2" class="inline_help">
|
||||
<blockquote class="inline_help">
|
||||
<p>A description for the application container. Supports basic HTML mark-up.</p>
|
||||
</blockquote>
|
||||
</td>
|
||||
</tr>
|
||||
<tr <?=$showAdditionalInfo?>>
|
||||
<td>Repository:</td>
|
||||
<td><input type="text" name="contRepository" class="textPath" required></td>
|
||||
@@ -1030,6 +1044,65 @@ $showAdditionalInfo = '';
|
||||
</blockquote>
|
||||
</td>
|
||||
</tr>
|
||||
<tr class="advanced">
|
||||
<td>Categories:</td>
|
||||
<td><input type="text" name="contCategory" class="textPath"></td>
|
||||
</tr>
|
||||
<tr class="advanced">
|
||||
<td>Support Thread:</td>
|
||||
<td><input type="text" name="contSupport" class="textPath"></td>
|
||||
</tr>
|
||||
<tr class="advanced">
|
||||
<td colspan="2" class="inline_help">
|
||||
<blockquote class="inline_help">
|
||||
<p>Link to a support thread on Lime-Technology's forum.</p>
|
||||
</blockquote>
|
||||
</td>
|
||||
</tr>
|
||||
<tr class="advanced">
|
||||
<td>Docker Hub URL:</td>
|
||||
<td><input type="text" name="contRegistry" class="textPath"></td>
|
||||
</tr>
|
||||
<tr class="advanced">
|
||||
<td colspan="2" class="inline_help">
|
||||
<blockquote class="inline_help">
|
||||
<p>The path to the container's repository location on the Docker Hub.</p>
|
||||
</blockquote>
|
||||
</td>
|
||||
</tr>
|
||||
<tr class="advanced">
|
||||
<td>Icon URL:</td>
|
||||
<td><input type="text" name="contIcon" class="textPath"></td>
|
||||
</tr>
|
||||
<tr class="advanced">
|
||||
<td colspan="2" class="inline_help">
|
||||
<blockquote class="inline_help">
|
||||
<p>Link to the icon image for your application (only displayed on dashboard if Show Dashboard apps under Display Settings is set to Icons).</p>
|
||||
</blockquote>
|
||||
</td>
|
||||
</tr>
|
||||
<tr class="advanced">
|
||||
<td>WebUI:</td>
|
||||
<td><input type="text" name="contWebUI" class="textPath"></td>
|
||||
</tr>
|
||||
<tr class="advanced">
|
||||
<td colspan="2" class="inline_help">
|
||||
<blockquote class="inline_help">
|
||||
<p>When you click on an application icon from the Docker Containers page, the WebUI option will link to the path in this field. Use [IP} to identify the IP of your host and [PORT:####] replacing the #'s for your port.</p>
|
||||
</blockquote>
|
||||
</td>
|
||||
</tr>
|
||||
<tr class="advanced">
|
||||
<td>Extra Parameters:</td>
|
||||
<td><input type="text" name="contExtraParams" class="textPath"></td>
|
||||
</tr>
|
||||
<tr class="advanced">
|
||||
<td colspan="2" class="inline_help">
|
||||
<blockquote class="inline_help">
|
||||
<p>If you wish to append additional commands to your Docker container at run-time, you can specify them here. For example, if you wish to pin an application to live on a specific CPU core, you can enter "--cpuset=0" in this field. Change 0 to the core # on your system (starting with 0). You can pin multiple cores by separation with a comma or a range of cores by separation with a dash. For all possible Docker run-time commands, see here: <a href="https://docs.docker.com/reference/run/" target="_blank">https://docs.docker.com/reference/run/</a></p>
|
||||
</blockquote>
|
||||
</td>
|
||||
</tr>
|
||||
<tr <?=$showAdditionalInfo?>>
|
||||
<td>Network Type:</td>
|
||||
<td>
|
||||
@@ -1050,7 +1123,7 @@ $showAdditionalInfo = '';
|
||||
</tr>
|
||||
<tr <?=$showAdditionalInfo?>>
|
||||
<td>Privileged:</td>
|
||||
<td><input type="checkbox" name="contPrivileged" class="switch-on-off">
|
||||
<td style="line-height: 16px; vertical-align: middle;"><input type="checkbox" name="contPrivileged" class="switch-on-off">
|
||||
</td>
|
||||
</tr>
|
||||
<tr <?=$showAdditionalInfo?>>
|
||||
@@ -1066,92 +1139,14 @@ $showAdditionalInfo = '';
|
||||
<div id="configLocation"></div>
|
||||
<table class="advanced">
|
||||
<tr>
|
||||
<td style="vertical-align: top; width: 150px; white-space:nowrap; "> </td>
|
||||
<td><button type="button" onclick="addConfigPopup();" style="margin-top: 25px">Add Config</button></td>
|
||||
<td> </td>
|
||||
<td><button type="button" onclick="addConfigPopup();">Add Config</button></td>
|
||||
</tr>
|
||||
</table>
|
||||
|
||||
<div class="advanced">
|
||||
<!--div id="title">
|
||||
<span class="left"><img src="/plugins/dynamix.docker.manager/icons/vcard.png" class="icon">Additional Fields</span>
|
||||
</div-->
|
||||
<table class="Preferences">
|
||||
<!--tr>
|
||||
<td style="width: 150px; vertical-align: top;">Overview:</td>
|
||||
<td><textarea name="contOverview" rows="10" cols="71" class="textTemplate"></textarea></td>
|
||||
</tr-->
|
||||
<tr>
|
||||
<td colspan="2" class="inline_help">
|
||||
<blockquote class="inline_help">
|
||||
<p>A description for the application container. Supports basic HTML mark-up.</p>
|
||||
</blockquote>
|
||||
</td>
|
||||
</tr>
|
||||
<tr>
|
||||
<td>Categories:</td>
|
||||
<td><input type="text" name="contCategory" class="textPath"></td>
|
||||
</tr>
|
||||
<tr>
|
||||
<td>Support Thread:</td>
|
||||
<td><input type="text" name="contSupport" class="textPath"></td>
|
||||
</tr>
|
||||
<tr>
|
||||
<td colspan="2" class="inline_help">
|
||||
<blockquote class="inline_help">
|
||||
<p>Lnk to a support thread on Lime-Technology's forum.</p>
|
||||
</blockquote>
|
||||
</td>
|
||||
</tr>
|
||||
<tr>
|
||||
<td>Docker Hub URL:</td>
|
||||
<td><input type="text" name="contRegistry" class="textPath"></td>
|
||||
</tr>
|
||||
<tr>
|
||||
<td colspan="2" class="inline_help">
|
||||
<blockquote class="inline_help">
|
||||
<p>The path to the container's repository location on the Docker Hub.</p>
|
||||
</blockquote>
|
||||
</td>
|
||||
</tr>
|
||||
<tr>
|
||||
<td>Icon URL:</td>
|
||||
<td><input type="text" name="contIcon" class="textPath"></td>
|
||||
</tr>
|
||||
<tr>
|
||||
<td colspan="2" class="inline_help">
|
||||
<blockquote class="inline_help">
|
||||
<p>Link to the icon image for your application (only displayed on dashboard if Show Dashboard apps under Display Settings is set to Icons).</p>
|
||||
</blockquote>
|
||||
</td>
|
||||
</tr>
|
||||
<tr>
|
||||
<td>WebUI:</td>
|
||||
<td><input type="text" name="contWebUI" class="textPath"></td>
|
||||
</tr>
|
||||
<tr>
|
||||
<td colspan="2" class="inline_help">
|
||||
<blockquote class="inline_help">
|
||||
<p>When you click on an application icon from the Docker Containers page, the WebUI option will link to the path in this field. Use [IP} to identify the IP of your host and [PORT:####] replacing the #'s for your port.</p>
|
||||
</blockquote>
|
||||
</td>
|
||||
</tr>
|
||||
<tr>
|
||||
<td>Extra Parameters:</td>
|
||||
<td><input type="text" name="contExtraParams" class="textPath"></td>
|
||||
</tr>
|
||||
<tr>
|
||||
<td colspan="2" class="inline_help">
|
||||
<blockquote class="inline_help">
|
||||
<p>If you wish to append additional commands to your Docker container at run-time, you can specify them here. For example, if you wish to pin an application to live on a specific CPU core, you can enter "--cpuset=0" in this field. Change 0 to the core # on your system (starting with 0). You can pin multiple cores by separation with a comma or a range of cores by separation with a dash. For all possible Docker run-time commands, see here: <a href="https://docs.docker.com/reference/run/" target="_blank">https://docs.docker.com/reference/run/</a></p>
|
||||
</blockquote>
|
||||
</td>
|
||||
</tr>
|
||||
</table>
|
||||
</div>
|
||||
<br>
|
||||
<table>
|
||||
<tr>
|
||||
<td style="width: 160px;"> </td>
|
||||
<td> </td>
|
||||
<td>
|
||||
<input type="submit" value="<?= ($xmlType != 'edit') ? 'Create' : 'Save' ?>">
|
||||
<button class="advanced" type="submit" name="dryRun" value="true" onclick="$('*[required]').prop( 'required', null );">Dry Run</button>
|
||||
@@ -1160,8 +1155,8 @@ $showAdditionalInfo = '';
|
||||
</tr>
|
||||
</table>
|
||||
<br><br><br>
|
||||
</div>
|
||||
</form>
|
||||
</form>
|
||||
</div>
|
||||
|
||||
<?
|
||||
# ██╗███████╗ ████████╗███████╗███╗ ███╗██████╗ ██╗ █████╗ ████████╗███████╗███████╗
|
||||
@@ -1241,18 +1236,15 @@ $showAdditionalInfo = '';
|
||||
<input type="hidden" name="confDisplay[]" value="{6}">
|
||||
<input type="hidden" name="confRequired[]" value="{7}">
|
||||
<input type="hidden" name="confMask[]" value="{8}">
|
||||
<table class="Preferences">
|
||||
<table>
|
||||
<tr>
|
||||
<td style="vertical-align: top; min-width: 150px; white-space: nowrap; padding-top: 17px;" class="{11}"><b>{0}: </b></td>
|
||||
<td style="vertical-align: top; min-width: 150px; white-space: nowrap; padding-top: 17px;" class="{11}">{0}:</td>
|
||||
<td style="width: 100%">
|
||||
<input type="text" class="textPath" name="confValue[]" default="{2}" value="{9}" autocomplete="off" {11} > <button type="button" onclick="resetField(this);">Default</button>
|
||||
{10}
|
||||
<div style='color: #C98C21;line-height: 1.4em'>{4}</div>
|
||||
</td>
|
||||
</tr>
|
||||
<tr>
|
||||
<td> </td>
|
||||
<td style="padding-top: 0px;"><div style='color: #b94a48;line-height: 1.6em'>{4}</div></td>
|
||||
</tr>
|
||||
<!-- <tr class='advanced'>
|
||||
<td style="padding-top: 0px;">
|
||||
<div style="color: #3B5998;">
|
||||
|
||||
@@ -12,8 +12,7 @@
|
||||
?>
|
||||
<?
|
||||
|
||||
## BETA 11
|
||||
$dockerManPaths = array(
|
||||
$dockerManPaths = [
|
||||
'plugin' => '/usr/local/emhttp/plugins/dynamix.docker.manager',
|
||||
'autostart-file' => '/var/lib/docker/unraid-autostart',
|
||||
'template-repos' => '/boot/config/plugins/dockerMan/template-repos',
|
||||
@@ -23,31 +22,7 @@ $dockerManPaths = array(
|
||||
'images-storage' => '/boot/config/plugins/dockerMan/images',
|
||||
'webui-info' => '/usr/local/emhttp/state/plugins/dynamix.docker.manager/docker.json',
|
||||
'update-status' => '/var/lib/docker/unraid-update-status.json',
|
||||
);
|
||||
|
||||
//## BETA 9
|
||||
// $dockerManPaths = array(
|
||||
// 'plugin' => '/usr/local/emhttp/plugins/dockerMan',
|
||||
// 'autostart-file' => '/var/lib/docker/unraid-autostart',
|
||||
// 'template-repos' => '/boot/config/plugins/dockerMan/template-repos',
|
||||
// 'templates-user' => '/boot/config/plugins/dockerMan/templates-user',
|
||||
// 'templates-storage' => '/boot/config/plugins/dockerMan/templates',
|
||||
// 'images-ram' => '/usr/local/emhttp/state/plugins/dockerMan/images',
|
||||
// 'images-storage' => '/boot/config/plugins/dockerMan/images',
|
||||
// 'webui-info' => '/usr/local/emhttp/state/plugins/dockerMan/docker.json',
|
||||
// );
|
||||
|
||||
//## BETA 8
|
||||
// $dockerManPaths = array(
|
||||
// 'plugin' => '/usr/local/emhttp/plugins/dockerMan',
|
||||
// 'autostart-file' => '/var/lib/docker/unraid-autostart',
|
||||
// 'template-repos' => '/boot/config/plugins/dockerMan/template-repos',
|
||||
// 'templates-user' => '/var/lib/docker/unraid-templates',
|
||||
// 'templates-storage' => '/boot/config/plugins/dockerMan/templates',
|
||||
// 'images-ram' => '/usr/local/emhttp/state/plugins/dockerMan/images',
|
||||
// 'images-storage' => '/boot/config/plugins/dockerMan/images',
|
||||
// 'webui-info' => '/usr/local/emhttp/state/plugins/dockerMan/docker.json',
|
||||
// );
|
||||
];
|
||||
|
||||
#load emhttp variables if needed.
|
||||
if (! isset($var)){
|
||||
@@ -107,7 +82,7 @@ class DockerTemplates {
|
||||
if ($ext && ( $ext != $fext )) continue;
|
||||
if ( $fileinfo->isFile()) $paths[] = array('path' => $path, 'prefix' => basename(dirname($path)), 'name' => $fileinfo->getBasename(".$fext"));
|
||||
}
|
||||
return $paths;
|
||||
return $paths;
|
||||
}
|
||||
|
||||
|
||||
@@ -414,12 +389,19 @@ class DockerUpdate{
|
||||
}
|
||||
|
||||
|
||||
public function download_url($url, $path = "", $bg = FALSE){
|
||||
exec("curl --max-time 30 --silent --insecure --location --fail ".($path ? " -o " . escapeshellarg($path) : "")." " . escapeshellarg($url) . " ".($bg ? ">/dev/null 2>&1 &" : "2>/dev/null"), $out, $exit_code );
|
||||
return ($exit_code === 0 ) ? implode("\n", $out) : FALSE;
|
||||
}
|
||||
public function download_url($url, $path = "", $bg = FALSE){
|
||||
exec("curl --max-time 30 --silent --insecure --location --fail ".($path ? " -o " . escapeshellarg($path) : "")." " . escapeshellarg($url) . " ".($bg ? ">/dev/null 2>&1 &" : "2>/dev/null"), $out, $exit_code );
|
||||
return ($exit_code === 0 ) ? implode("\n", $out) : FALSE;
|
||||
}
|
||||
|
||||
|
||||
public function download_url_and_headers($url, $headers = "", $path = "", $bg = FALSE){
|
||||
exec("curl --max-time 30 --silent --insecure --location --fail -i ".($headers ? " -H ".escapeshellarg($headers) : "").($path ? " -o " . escapeshellarg($path) : "")." " . escapeshellarg($url) . " ".($bg ? ">/dev/null 2>&1 &" : "2>/dev/null"), $out, $exit_code );
|
||||
return ($exit_code === 0 ) ? implode("\n", $out) : FALSE;
|
||||
}
|
||||
|
||||
|
||||
// DEPRECATED: Only used for Docker Index V1 type update checks
|
||||
public function getRemoteVersion($image){
|
||||
$apiUrl = vsprintf("http://index.docker.io/v1/repositories/%s/%s/tags/%s", preg_split("#[:\/]#", $image));
|
||||
$this->debug("API URL: $apiUrl");
|
||||
@@ -428,6 +410,48 @@ class DockerUpdate{
|
||||
}
|
||||
|
||||
|
||||
public function getRemoteVersionV2($image){
|
||||
// First - get auth token:
|
||||
// https://auth.docker.io/token?service=registry.docker.io&scope=repository:needo/nzbget:pull
|
||||
$strAuthURL = vsprintf("https://auth.docker.io/token?service=registry.docker.io&scope=repository:%s:pull", strstr($image.':', ':', true));
|
||||
$this->debug("Auth URL: $strAuthURL");
|
||||
$arrAuth = json_decode($this->download_url($strAuthURL), TRUE);
|
||||
if (empty($arrAuth) || empty($arrAuth['token'])) {
|
||||
$this->debug("Error: Auth Token was missing/empty");
|
||||
return null;
|
||||
}
|
||||
$this->debug("Auth Token: ".$arrAuth['token']);
|
||||
|
||||
// Next - get manifest:
|
||||
// curl -H "Authorization: Bearer <TOKEN>" https://registry-1.docker.io/v2/needo/nzbget/manifests/latest
|
||||
$strManifestURL = vsprintf("https://registry-1.docker.io/v2/%s/%s/manifests/%s", preg_split("#[:\/]#", $image));
|
||||
$this->debug("Manifest URL: $strManifestURL");
|
||||
$strManifest = $this->download_url_and_headers($strManifestURL, "Authorization: Bearer ".$arrAuth['token']);
|
||||
if (empty($strManifest)) {
|
||||
$this->debug("Error: Manifest response was empty");
|
||||
return null;
|
||||
}
|
||||
|
||||
// Look for 'Docker-Content-Digest' header in response:
|
||||
// Docker-Content-Digest: sha256:2070d781fc5f98f12e752b75cf39d03b7a24b9d298718b1bbb73e67f0443062d
|
||||
$strDigest = '';
|
||||
foreach (preg_split('/\r\n|\r|\n/', $strManifest) as $strLine) {
|
||||
if (strpos($strLine, 'Docker-Content-Digest: ') === 0) {
|
||||
$strDigest = substr($strLine, 23);
|
||||
break;
|
||||
}
|
||||
}
|
||||
if (empty($strDigest)) {
|
||||
$this->debug("Error: Remote Digest was missing/empty");
|
||||
return null;
|
||||
}
|
||||
|
||||
$this->debug("Remote Digest: $strDigest");
|
||||
return $strDigest;
|
||||
}
|
||||
|
||||
|
||||
// DEPRECATED: Only used for Docker Index V1 type update checks
|
||||
public function getLocalVersion($image){
|
||||
$DockerClient = new DockerClient();
|
||||
return substr($DockerClient->getImageID($image), 0, 8);
|
||||
@@ -444,12 +468,9 @@ class DockerUpdate{
|
||||
if(isset($updateStatus[$image])) {
|
||||
if ($updateStatus[$image]['local'] && $updateStatus[$image]['remote']) {
|
||||
return ($updateStatus[$image]['local'] == $updateStatus[$image]['remote']) ? true : false;
|
||||
} else {
|
||||
return null;
|
||||
}
|
||||
} else {
|
||||
return null;
|
||||
}
|
||||
return null;
|
||||
}
|
||||
|
||||
|
||||
@@ -463,23 +484,34 @@ class DockerUpdate{
|
||||
$image = ($image && count(preg_split("#[:\/]#", $image)) < 3) ? "${image}:latest" : $image;
|
||||
$images = ($image) ? array($image) : array_map(function($ar){return $ar['Tags'][0];}, $DockerClient->getDockerImages());
|
||||
foreach ($images as $img) {
|
||||
$localVersion = $this->getLocalVersion($img);
|
||||
$remoteVersion = $this->getRemoteVersion($img);
|
||||
$localVersion = null;
|
||||
if (!empty($updateStatus[$img]) && array_key_exists('local', $updateStatus[$img])) {
|
||||
$localVersion = $updateStatus[$img]['local'];
|
||||
}
|
||||
$remoteVersion = $this->getRemoteVersionV2($img);
|
||||
$status = ($localVersion && $remoteVersion) ? (($remoteVersion == $localVersion) ? "true" : "false") : "undef";
|
||||
$updateStatus[$img] = array('local' => $localVersion,
|
||||
'remote' => $remoteVersion,
|
||||
'status' => $status);
|
||||
$updateStatus[$img] = [
|
||||
'local' => $localVersion,
|
||||
'remote' => $remoteVersion,
|
||||
'status' => $status
|
||||
];
|
||||
$this->debug("Update status: Image='${img}', Local='${localVersion}', Remote='${remoteVersion}'");
|
||||
}
|
||||
file_put_contents($update_file, json_encode($updateStatus, JSON_PRETTY_PRINT | JSON_UNESCAPED_SLASHES));
|
||||
}
|
||||
|
||||
|
||||
public function syncVersions($container) {
|
||||
public function setUpdateStatus($image, $version) {
|
||||
global $dockerManPaths;
|
||||
$update_file = $dockerManPaths['update-status'];
|
||||
$updateStatus = (is_file($update_file)) ? json_decode(file_get_contents($update_file), TRUE) : array();
|
||||
$updateStatus[$container] = 'true';
|
||||
$DockerClient = new DockerClient();
|
||||
$update_file = $dockerManPaths['update-status'];
|
||||
$updateStatus = (is_file($update_file)) ? json_decode(file_get_contents($update_file), TRUE) : array();
|
||||
$updateStatus[$image] = [
|
||||
'local' => $version,
|
||||
'remote' => $version,
|
||||
'status' => 'true'
|
||||
];
|
||||
$this->debug("Update status: Image='${image}', Local='${version}', Remote='${version}'");
|
||||
file_put_contents($update_file, json_encode($updateStatus, JSON_PRETTY_PRINT | JSON_UNESCAPED_SLASHES));
|
||||
}
|
||||
}
|
||||
@@ -503,7 +535,7 @@ class DockerClient {
|
||||
}
|
||||
|
||||
|
||||
private function humanTiming ($time){
|
||||
private function humanTiming($time){
|
||||
$time = time() - $time; // to get the time since that moment
|
||||
$tokens = array (31536000 => 'year',
|
||||
2592000 => 'month',
|
||||
@@ -521,14 +553,6 @@ class DockerClient {
|
||||
}
|
||||
|
||||
|
||||
private function unchunk($result) {
|
||||
return preg_replace_callback(
|
||||
'/(?:(?:\r\n|\n)|^)([0-9A-F]+)(?:\r\n|\n){1,2}(.*?)'
|
||||
.'((?:\r\n|\n)(?:[0-9A-F]+(?:\r\n|\n))|$)/si',
|
||||
create_function('$matches','return hexdec($matches[1]) == strlen($matches[2]) ? $matches[2] :$matches[0];'), $result);
|
||||
}
|
||||
|
||||
|
||||
private function formatBytes($size){
|
||||
if ($size == 0){ return "0 B";}
|
||||
$base = log($size) / log(1024);
|
||||
@@ -583,14 +607,13 @@ class DockerClient {
|
||||
}
|
||||
|
||||
|
||||
public function getContainetLog($id, $callback, $tail = null, $since = null) {
|
||||
$this->getDockerJSON("/containers/${id}/logs?stderr=1&stdout=1&tail=${tail}&since=${since}", "GET", $code, $callback, true);
|
||||
public function getContainerLog($id, $callback, $tail = null, $since = null) {
|
||||
$this->getDockerJSON("/containers/${id}/logs?stderr=1&stdout=1&tail=".urlencode($tail)."&since=".urlencode($since), "GET", $code, $callback, true);
|
||||
}
|
||||
|
||||
|
||||
public function getContainerDetails($id){
|
||||
$json = $this->getDockerJSON("/containers/{$id}/json");
|
||||
return $json;
|
||||
return $this->getDockerJSON("/containers/${id}/json");
|
||||
}
|
||||
|
||||
|
||||
@@ -624,7 +647,7 @@ class DockerClient {
|
||||
|
||||
|
||||
public function removeContainer($id){
|
||||
$json = $this->getDockerJSON("/containers/{$id}?force=1", "DELETE", $code);
|
||||
$json = $this->getDockerJSON("/containers/${id}?force=1", "DELETE", $code);
|
||||
$codes = array("204" => "No error.",
|
||||
"400" => "Bad parameter.",
|
||||
"404" => "No such container.",
|
||||
@@ -634,12 +657,12 @@ class DockerClient {
|
||||
|
||||
|
||||
public function pullImage($image, $callback = null) {
|
||||
return $this->getDockerJSON("/images/create?fromImage=$image", "POST", $code, $callback);
|
||||
return $this->getDockerJSON("/images/create?fromImage=".urlencode($image), "POST", $code, $callback);
|
||||
}
|
||||
|
||||
|
||||
public function removeImage($id){
|
||||
$json = $this->getDockerJSON("/images/{$id}?force=1", "DELETE", $code);
|
||||
$json = $this->getDockerJSON("/images/${id}?force=1", "DELETE", $code);
|
||||
$codes = array("200" => "No error.",
|
||||
"404" => "No such image.",
|
||||
"409" => "Conflict: image used by container ".$this->usedBy($id)[0].".",
|
||||
@@ -649,8 +672,7 @@ class DockerClient {
|
||||
|
||||
|
||||
private function getImageDetails($id){
|
||||
$json = $this->getDockerJSON("/images/$id/json");
|
||||
return $json;
|
||||
return $this->getDockerJSON("/images/${id}/json");
|
||||
}
|
||||
|
||||
|
||||
@@ -764,7 +786,6 @@ class DockerClient {
|
||||
foreach($obj['RepoTags'] as $t){
|
||||
$tags[] = htmlentities($t);
|
||||
}
|
||||
// echo "<pre>".print_r($obj,TRUE)."</pre>";
|
||||
|
||||
$c["Created"] = $this->humanTiming($obj['Created']);//date('Y-m-d H:i:s', $obj['Created']);
|
||||
$c["Id"] = substr($obj['Id'],0,12);
|
||||
|
||||
@@ -1,49 +1,63 @@
|
||||
<?PHP
|
||||
/* Copyright 2015, Lime Technology
|
||||
* Copyright 2015, Guilherme Jardim, Eric Schultz, Jon Panozzo.
|
||||
*
|
||||
* This program is free software; you can redistribute it and/or
|
||||
* modify it under the terms of the GNU General Public License version 2,
|
||||
* as published by the Free Software Foundation.
|
||||
*
|
||||
* The above copyright notice and this permission notice shall be included in
|
||||
* all copies or substantial portions of the Software.
|
||||
*/
|
||||
?>
|
||||
<?
|
||||
require_once("/usr/local/emhttp/plugins/dynamix.docker.manager/include/DockerClient.php");
|
||||
$DockerClient = new DockerClient();
|
||||
|
||||
$container = $_POST['container'];
|
||||
$image = $_POST['image'];
|
||||
$_REQUEST = array_merge($_GET, $_POST);
|
||||
|
||||
switch ($_POST['action']) {
|
||||
case 'start':
|
||||
if ($container) echo json_encode(array('success' => $DockerClient->startContainer($container) ));
|
||||
break;
|
||||
case 'stop':
|
||||
if ($container) echo json_encode(array('success' => $DockerClient->stopContainer($container) ));
|
||||
break;
|
||||
case 'restart':
|
||||
if ($container) echo json_encode(array('success' => $DockerClient->restartContainer($container) ));
|
||||
break;
|
||||
case 'remove_container':
|
||||
if ($container) echo json_encode(array('success' => $DockerClient->removeContainer($container) ));
|
||||
break;
|
||||
case 'remove_image':
|
||||
if ($image) echo json_encode(array('success' => $DockerClient->removeImage($image) ));
|
||||
break;
|
||||
}
|
||||
$action = array_key_exists('action', $_REQUEST) ? $_REQUEST['action'] : '';
|
||||
$container = array_key_exists('container', $_REQUEST) ? $_REQUEST['container'] : '';
|
||||
|
||||
$container = $_GET['container'];
|
||||
$since = $_GET['since'];
|
||||
$title = $_GET['title'];
|
||||
switch ($action) {
|
||||
|
||||
switch ($_GET['action']) {
|
||||
case 'log':
|
||||
if ($container) {
|
||||
$echo = function($s){$s=addslashes(substr(trim($s),8));echo "<script>addLog('".$s."');</script>";@flush();};
|
||||
if (!$since) {
|
||||
readfile("/usr/local/emhttp/plugins/dynamix.docker.manager/log.htm");
|
||||
echo "<script>document.title = '$title';</script>";
|
||||
$tail = 350;
|
||||
} else {
|
||||
$tail = null;
|
||||
}
|
||||
$DockerClient->getContainetLog($container, $echo, $tail, $since);
|
||||
echo "<script>setTimeout(\"loadLog('${container}','".time()."')\",2000);</script>";
|
||||
@flush();
|
||||
}
|
||||
break;
|
||||
case 'start':
|
||||
if ($container) echo json_encode(array('success' => $DockerClient->startContainer($container) ));
|
||||
break;
|
||||
|
||||
case 'stop':
|
||||
if ($container) echo json_encode(array('success' => $DockerClient->stopContainer($container) ));
|
||||
break;
|
||||
|
||||
case 'restart':
|
||||
if ($container) echo json_encode(array('success' => $DockerClient->restartContainer($container) ));
|
||||
break;
|
||||
|
||||
case 'remove_container':
|
||||
if ($container) echo json_encode(array('success' => $DockerClient->removeContainer($container) ));
|
||||
break;
|
||||
|
||||
case 'remove_image':
|
||||
$image = array_key_exists('image', $_REQUEST) ? $_REQUEST['image'] : '';
|
||||
if ($image) echo json_encode([ 'success' => $DockerClient->removeImage($image) ]);
|
||||
break;
|
||||
|
||||
case 'log':
|
||||
if ($container) {
|
||||
$since = array_key_exists('since', $_REQUEST) ? $_REQUEST['since'] : '';
|
||||
$title = array_key_exists('title', $_REQUEST) ? $_REQUEST['title'] : '';
|
||||
if (!$since) {
|
||||
readfile("/usr/local/emhttp/plugins/dynamix.docker.manager/log.htm");
|
||||
echo "<script>document.title = '$title';</script>";
|
||||
$tail = 350;
|
||||
} else {
|
||||
$tail = null;
|
||||
}
|
||||
$echo = function($s){$s=addslashes(substr(trim($s),8));echo "<script>addLog('".$s."');</script>";@flush();};
|
||||
$DockerClient->getContainerLog($container, $echo, $tail, $since);
|
||||
echo "<script>setTimeout(\"loadLog('".addslashes($container)."','".time()."')\",2000);</script>";
|
||||
@flush();
|
||||
}
|
||||
break;
|
||||
|
||||
}
|
||||
?>
|
||||
|
||||
|
||||
@@ -11,8 +11,6 @@
|
||||
*/
|
||||
?>
|
||||
<?
|
||||
readfile("/usr/local/emhttp/plugins/dynamix.docker.manager/log.htm");
|
||||
|
||||
if ( isset( $_GET['cmd'] )) {
|
||||
$commands = urldecode(($_GET['cmd']));
|
||||
$descriptorspec = array(
|
||||
|
||||
@@ -1,44 +1,117 @@
|
||||
<!DOCTYPE HTML>
|
||||
<html>
|
||||
<head>
|
||||
<link type="text/css" rel="stylesheet" href="/webGui/styles/default-fonts.css">
|
||||
|
||||
<script src="/webGui/javascript/dynamix.js"></script>
|
||||
|
||||
<style >
|
||||
<style>
|
||||
@font-face{
|
||||
font-family:'arimo';font-weight:normal;font-style:normal;
|
||||
src:url('/webGui/styles/arimo.eot');src:url('/webGui/styles/arimo.eot?#iefix') format('embedded-opentype'),url('/webGui/styles/arimo.woff') format('woff'),url('/webGui/styles/arimo.ttf') format('truetype'),url('/webGui/styles/arimo.svg#arimo') format('svg');
|
||||
}
|
||||
@font-face{
|
||||
font-family:'arimo';font-weight:bold;font-style:normal;
|
||||
src:url('/webGui/styles/arimo-bold.eot');src:url('/webGui/styles/arimo-bold.eot?#iefix') format('embedded-opentype'),url('/webGui/styles/arimo-bold.woff') format('woff'),url('/webGui/styles/arimo-bold.ttf') format('truetype'),url('/webGui/styles/arimo-bold.svg#arimo-bold') format('svg');
|
||||
}
|
||||
@font-face{
|
||||
font-family:'arimo';font-weight:normal;font-style:italic;
|
||||
src:url('/webGui/styles/arimo-italic.eot');src:url('/webGui/styles/arimo-italic.eot?#iefix') format('embedded-opentype'),url('/webGui/styles/arimo-italic.woff') format('woff'),url('/webGui/styles/arimo-italic.ttf') format('truetype'),url('/webGui/styles/arimo-italic.svg#arimo-italic') format('svg');
|
||||
}
|
||||
@font-face{
|
||||
font-family:'arimo';font-weight:bold;font-style:italic;
|
||||
src:url('/webGui/styles/arimo-bold-italic.eot');src:url('/webGui/styles/arimo-bold-italic.eot?#iefix') format('embedded-opentype'),url('/webGui/styles/arimo-bold-italic.woff') format('woff'),url('/webGui/styles/arimo-bold-italic.ttf') format('truetype'),url('/webGui/styles/arimo-bold-italic.svg#arimo-bold-italic') format('svg');
|
||||
}
|
||||
@font-face{
|
||||
font-family:'bitstream';font-weight:normal;font-style:normal;
|
||||
src:url('/webGui/styles/bitstream.eot');src:url('/webGui/styles/bitstream.eot?#iefix') format('embedded-opentype'),url('/webGui/styles/bitstream.woff') format('woff'),url('/webGui/styles/bitstream.ttf') format('truetype'),url('/webGui/styles/bitstream.svg#bitstream') format('svg');
|
||||
}
|
||||
.logLine{font-family:bitstream;font-size:11px;text-align:left;}
|
||||
legend{font-size:12px;font-weight:bold;}
|
||||
#content{margin:10;padding:0;}
|
||||
</style>
|
||||
|
||||
<script>
|
||||
var dots = [];
|
||||
var span = [];
|
||||
function show_Wait(id){
|
||||
|
||||
function show_Wait(id) {
|
||||
span[id] = document.getElementById("wait" + id);
|
||||
dots[id] = setInterval(function() {
|
||||
if ((span[id].innerHTML += '.').length == 18) {
|
||||
span[id].innerHTML = 'Please wait ';
|
||||
}}, 500);
|
||||
dots[id] = setInterval(function () {
|
||||
if ((span[id].innerHTML += '.').length == 18) {
|
||||
span[id].innerHTML = 'Please wait ';
|
||||
}
|
||||
}, 500);
|
||||
}
|
||||
|
||||
function stop_Wait(id){
|
||||
function stop_Wait(id) {
|
||||
span[id].innerHTML = '';
|
||||
clearInterval( dots[id] );
|
||||
}
|
||||
|
||||
function addLog(logLine) {
|
||||
$(".logLine:last").append(logLine + "<br>");
|
||||
window.scrollTo(0,document.body.scrollHeight);
|
||||
var elms = document.getElementsByClassName('logLine');
|
||||
if (elms.length) {
|
||||
var el = elms[elms.length-1];
|
||||
el.innerHTML += logLine + "<br>";
|
||||
}
|
||||
window.scrollTo(0, document.body.scrollHeight);
|
||||
}
|
||||
|
||||
function loadLog(container, since) {
|
||||
$.get(location.protocol+'//'+location.host+location.pathname,{action:'log',container:container,since:since},function(data){
|
||||
if(data) $('.logLine:last').append(data);
|
||||
});
|
||||
var httpRequest = new XMLHttpRequest();
|
||||
httpRequest.onreadystatechange = function () {
|
||||
if (httpRequest.readyState === 4 && httpRequest.status === 200) {
|
||||
parseScript(httpRequest.responseText);
|
||||
}
|
||||
};
|
||||
httpRequest.open('GET', location.protocol+'//'+location.host+location.pathname+'?action=log&container='+encodeURIComponent(container)+'&since='+encodeURIComponent(since));
|
||||
httpRequest.send();
|
||||
}
|
||||
|
||||
function parseScript(_source) {
|
||||
var source = _source;
|
||||
var scripts = [];
|
||||
|
||||
while(source.indexOf("<script") > -1 || source.indexOf("</script") > -1) {
|
||||
var s = source.indexOf("<script");
|
||||
var s_e = source.indexOf(">", s);
|
||||
var e = source.indexOf("</script", s);
|
||||
var e_e = source.indexOf(">", e);
|
||||
|
||||
scripts.push(source.substring(s_e+1, e));
|
||||
source = source.substring(0, s) + source.substring(e_e+1);
|
||||
}
|
||||
|
||||
for(var i=0; i<scripts.length; i++) {
|
||||
try {
|
||||
eval(scripts[i]);
|
||||
} catch(ex) {}
|
||||
}
|
||||
|
||||
return source;
|
||||
}
|
||||
|
||||
function progress(id, prog) {
|
||||
var elms = document.getElementsByClassName(id+'_progress');
|
||||
if (elms.length) {
|
||||
var el = elms[elms.length-1];
|
||||
el.innerHTML = prog;
|
||||
}
|
||||
}
|
||||
|
||||
function addToID(id, m) {
|
||||
var elms = document.getElementById(id);
|
||||
if (elms === null) {
|
||||
addLog('<span id=\"'+id+'\">IMAGE ID ['+id+']: </span><span class=\"content\">'+m+'</span><span class=\"'+id+'_progress\"></span>.');
|
||||
} else {
|
||||
var elms_content = elms.getElementsByClassName("content");
|
||||
if (elms_content.length) {
|
||||
var el = elms_content[elms_content.length - 1];
|
||||
if (el.innerText != m) {
|
||||
elms.innerHTML += '<span class=\"content\">'+m+'</span><span class=\"'+id+'_progress\"></span>. ';
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
</script>
|
||||
</head>
|
||||
<body>
|
||||
<div style="margin:10;padding:0" id="content"><p class="logLine" id="logBody"></p></div>
|
||||
<div id="content"><p class="logLine" id="logBody"></p></div>
|
||||
</body>
|
||||
</html>
|
||||
|
||||
Reference in New Issue
Block a user