Docker Manager: updates from gfjardim and revamped vm settings

This commit is contained in:
Eric Schultz
2015-11-25 22:30:49 -06:00
parent 0767c99ec9
commit 86814f808d
11 changed files with 1564 additions and 950 deletions

View File

@@ -84,12 +84,12 @@ img.stopped{opacity:0.3;}
$updateStatus = ($updateStatus == "true" or $updateStatus == "undef" ) ? 'true' : 'false';
$running = ($ct['Running']) ? 'true' : 'false';
$webGuiUrl = $info[$name]['url'];
$contextMenus[] = sprintf("addDockerContainerContext('%s', '%s', '%s', %s, %s, %s, '%s');", addslashes($ct['Name']), addslashes($ct['ImageId']), addslashes($info[$name]['template']), $running, $updateStatus, $is_autostart, addslashes($webGuiUrl));
$contextMenus[] = sprintf("addDockerContainerContext('%s', '%s', '%s', %s, %s, %s, '%s', '%s');", addslashes($ct['Name']), addslashes($ct['ImageId']), addslashes($info[$name]['template']), $running, $updateStatus, $is_autostart, addslashes($webGuiUrl), $ct["Id"]);
$shape = ($ct["Running"]) ? "play" : "square";
$status = ($ct["Running"]) ? "started" : "stopped";
$Icon = $info[$name]['icon'];
if ( $Icon == "#" ){
if ( ! $Icon ){
$Icon = "/plugins/dynamix.docker.manager/images/question.png";
}
@@ -144,6 +144,7 @@ img.stopped{opacity:0.3;}
echo htmlspecialchars($ct['Image']);
}
?>
<? if($ct['BaseImage']) echo "<div class='advanced' style='color:#888; width: 160px;'><i class='fa fa-cubes' style='margin-right: 5px'></i>${ct[BaseImage]}</div>";?>
</td>
<td class="updatecolumn">
<?
@@ -159,10 +160,10 @@ img.stopped{opacity:0.3;}
}
?>
</td>
<td style="white-space:nowrap;"><?= implode("<br>", $ports); ?></td>
<td style="word-break:break-all;"><?= implode("<br>", $paths); ?></td>
<td style="white-space:nowrap;"><span class="docker_readmore"><?= implode("<br>", $ports); ?></span></td>
<td style="word-break:break-all;"><span class="docker_readmore"><?= implode("<br>", $paths); ?></span></td>
<td data-sort-value="<?=$is_autostart?>"><input type="checkbox" class="autostart" container="<?=htmlspecialchars($ct['Name']);?>" <?=$info[$ct['Name']]['autostart'] ? "checked" : ""?>></td>
<td><a class="log" onclick="containerLogs('<?=addslashes($ct['Name']);?>','Log for <?=addslashes($ct['Name']);?>', false, false)"><img class="basic" src="/plugins/dynamix/icons/log.png"/><div class="advanced" style="width: 124px;"><?=htmlspecialchars($ct['Status'])?></div><div class="advanced" style="color:#888;">Created <?=htmlspecialchars($ct['Created'])?></div></a></td>
<td><a class="log" onclick="containerLogs('<?=addslashes($ct['Name']);?>','<?=$ct['Id'];?>', false, false)"><img class="basic" src="/plugins/dynamix/icons/log.png"/><div class="advanced" style="width: 124px;"><?=htmlspecialchars($ct['Status'])?></div><div class="advanced" style="color:#888;">Created <?=htmlspecialchars($ct['Created'])?></div></a></td>
</tr>
<?}?>
@@ -243,5 +244,7 @@ $(function() {
context.init({ preventDoubleContext: false });
<?=implode("\n\t", $contextMenus);?>
$('.docker_readmore').readmore({maxHeight:48, moreLink: '<a href="#" style="text-align:center;"><i class="fa fa-chevron-down"></i></a>', lessLink: '<a href="#" style="text-align:center;"><i class="fa fa-chevron-up"></i></a>'});
});
</script>

View File

@@ -1,6 +1,7 @@
Menu="OtherSettings"
Title="Docker"
Icon="dynamix.docker.manager.png"
Markdown="false"
---
<?PHP
/* Copyright 2015, Lime Technology
@@ -26,18 +27,6 @@ $docker = new DockerClient();
$DockerUpdate = new DockerUpdate();
$DockerTemplates = new DockerTemplates();
// Docker configuration file
$cfgfile = "/boot/config/docker.cfg";
if (!file_exists($cfgfile)) {
echo "<p class='notice'>Missing docker.cfg file!</p>";
return;
}
$dockercfg = parse_ini_file($cfgfile);
if (!array_key_exists('DOCKER_ENABLED', $dockercfg)) {
$dockercfg['DOCKER_ENABLED'] = 'no';
}
// Check for nodatacow flag on Docker file; display warning
$realfile = $dockercfg['DOCKER_IMAGE_FILE'];
if (file_exists($realfile)) {
@@ -54,112 +43,158 @@ if (file_exists($realfile)) {
}
?>
<link type="text/css" rel="stylesheet" href="/webGui/styles/jquery.filetree.css">
<link type="text/css" rel="stylesheet" href="/webGui/styles/jquery.switchbutton.css">
<style>
.errortext{color:#EF3D47;display:none;}
.fileTree{width:305px;max-height:150px;overflow:scroll;position:absolute;z-index:100;display:none;}
.errortext{color:#EF3D47;display:none;}
.fileTree{width:305px;max-height:150px;overflow:scroll;position:absolute;z-index:100;display:none;}
.basic{display: block;}
.advanced{display:none;white-space: nowrap;}
.switch-button-label.off{color: inherit;}
</style>
<span class="status" style="margin-top: -44px;"><input type="checkbox" class="advancedview"></span>
<form id="settingsForm" markdown="1" method="POST" action="/update.php" target="progressFrame">
<input type="hidden" name="#file" value="<?=$docker_cfgfile;?>" />
<input type="hidden" id="command" name="#command" value="" />
<dl>
<dt>Enable Docker:</dt>
<dd>
<select id="DOCKER_ENABLED" name="DOCKER_ENABLED" class="narrow">
<?= mk_option($dockercfg['DOCKER_ENABLED'], 'no', 'No'); ?>
<?= mk_option($dockercfg['DOCKER_ENABLED'], 'yes', 'Yes'); ?>
</select>
</dd>
</dl>
<blockquote class="inline_help">
<p>Before you can start the Docker service for the first time, please specify an image
file for Docker to install to. Once started, Docker will always automatically start
after the array has been started.</p>
</blockquote>
<?if (pgrep('docker') === false):?>
<form id="settingsForm" markdown="1" method="POST" action="/update.php" target="progressFrame">
<input type="hidden" name="#file" value="<?=$cfgfile;?>" />
<input type="hidden" id="command" name="#command" value="" />
Enable Docker:
: <select id="DOCKER_ENABLED" name="DOCKER_ENABLED" class="narrow">
<?= mk_option($dockercfg['DOCKER_ENABLED'], 'no', 'No'); ?>
<?= mk_option($dockercfg['DOCKER_ENABLED'], 'yes', 'Yes'); ?>
</select>
<dl>
<dt>Docker vdisk size:</dt>
<dd><input id="DOCKER_IMAGE_SIZE" type="number" name="DOCKER_IMAGE_SIZE" value="<?=$dockercfg['DOCKER_IMAGE_SIZE'];?>" class="narrow" required="required" />GB <span id="SIZE_ERROR" class="errortext"></span></dd>
</dl>
<blockquote class="inline_help">
<p>If the system needs to create a new docker image file, this is the default size to use specified in GB.</p>
<p>To resize an existing image file, specify the new size here. Next time the Docker service is started the file (and file system) will increased to the new size (but never decreased).</p>
</blockquote>
> Before you can start the Docker service for the first time, please specify an image
> file for Docker to install to. Once started, Docker will always automatically start
> after the array has been started.
<div class="advanced">
<dl>
<dt>Docker storage location:</dt>
<dd><input id="DOCKER_IMAGE_FILE" type="text" name="DOCKER_IMAGE_FILE" value="<?=$dockercfg['DOCKER_IMAGE_FILE'];?>" placeholder="e.g. /mnt/disk1/docker.img" data-pickcloseonfile="true" data-pickfilter="img" data-pickroot="/mnt/" data-pickfolders="true" required="required" /> <?php if (file_exists($dockercfg['DOCKER_IMAGE_FILE'])) { ?><span id="deletePanel"><label><input type="checkbox" id="deleteCheckbox" /> Delete Image File</label></span><?php } ?> <span id="IMAGE_ERROR" class="errortext"></span></dd>
</dl>
<blockquote class="inline_help">
<p>You must specify an image file for Docker. The system will automatically create this file when the Docker service is first started.</p>
</blockquote>
</div>
Default image size:
: <input id="DOCKER_IMAGE_SIZE" type="number" name="DOCKER_IMAGE_SIZE" value="<?=$dockercfg['DOCKER_IMAGE_SIZE'];?>" class="narrow" required="required" />GB <span id="SIZE_ERROR" class="errortext"></span>
<?else: /* IF DOCKER STARTED */?>
> If the system needs to create a new docker image file, this is the default size to use
> specified in GB.
>
> To resize an existing image file, specify the new size here. Next time the Docker service is
> started the file (and file system) will increased to the new size (but never decreased).
<div class="advanced">
<dl>
<dt>Docker version:</dt>
<dd><? $arrInfo = $docker->getInfo(); echo $arrInfo['Version']; ?></dd>
</dl>
<blockquote class="inline_help">
<p>This is the docker version.</p>
</blockquote>
Docker image:
: <input id="DOCKER_IMAGE_FILE" type="text" name="DOCKER_IMAGE_FILE" value="<?=$dockercfg['DOCKER_IMAGE_FILE'];?>" placeholder="e.g. /mnt/disk1/docker.img" data-pickcloseonfile="true" data-pickfilter="img" data-pickroot="/mnt/" data-pickfolders="true" required="required" /> <?php if (file_exists($dockercfg['DOCKER_IMAGE_FILE'])) { ?><span id="deletePanel"><label><input type="checkbox" id="deleteCheckbox" /> Delete Image File</label></span><?php } ?> <span id="IMAGE_ERROR" class="errortext"></span>
<dl>
<dt>Docker storage location:</dt>
<dd><?=$dockercfg['DOCKER_IMAGE_FILE'];?></dd>
</dl>
<blockquote class="inline_help">
<p>This is the docker volume.</p>
</blockquote>
</div>
> You must specify an image file for Docker. The system will automatically
> create this file when the Docker service is first started. If you do not want Docker
> to run at all, set this field blank and click **Start**.
&nbsp;
: <input id="applyBtn" type="button" value="Apply"/><input type="button" value="Done" onclick="done()">
</form>
<?else:
// IF DOCKER STARTED
?>
<form id="settingsForm" markdown="1" method="POST" action="/update.php" target="progressFrame">
<input type="hidden" name="#file" value="<?=$cfgfile;?>" />
<input type="hidden" id="command" name="#command" value="" />
Enable Docker:
: <select id="DOCKER_ENABLED" name="DOCKER_ENABLED" class="narrow">
<?= mk_option($dockercfg['DOCKER_ENABLED'], 'no', 'No'); ?>
<?= mk_option($dockercfg['DOCKER_ENABLED'], 'yes', 'Yes'); ?>
</select>
> Stopping the Docker service will first stop all the running containers.
Docker version:
: <? $arrInfo = $docker->getInfo(); echo $arrInfo['Version']; ?>
> This is the docker version.
Docker image:
: <?=$dockercfg['DOCKER_IMAGE_FILE'];?>
> This is the docker volume.
&nbsp;
: <input type="button" id="applyBtn" value="Apply"><input type="button" value="Done" onclick="done()">
</form>
<div id="title"><span class="left"><img src="/plugins/dynamix.docker.manager/icons/vcard.png" class="icon">Docker volume info</span></div>
btrfs filesystem show:
: <?echo "<pre>".shell_exec("btrfs filesystem show /var/lib/docker")."</pre>";?>
<form markdown="1" method="POST" action="/update.php" target="progressFrame">
<?exec("/usr/local/emhttp/webGui/scripts/btrfs_scrub status /var/lib/docker", $scrub_status, $retval);?>
btrfs scrub status:
: <?echo "<pre>" . implode("\n", $scrub_status) . "</pre>";?>
<?if ($retval != 0):?>
<input type="hidden" name="#command" value="/webGui/scripts/btrfs_scrub">
<input type="hidden" name="#arg[1]" value="start">
<input type="hidden" name="#arg[2]" value="/var/lib/docker">
&nbsp;
: <input type="submit" value="Scrub"><label><input type="checkbox" name="#arg[3]" value="-r" checked> Don't fix file system errors</label>
> **Scrub** runs the *btrfs scrub* program to check file system integrity.
>
> If repair is needed you should uncheck the *Don't fix file system errors* option and
> run a second Scrub pass; this will permit *btrfs scrub* to fix the file system.
<?else:?>
<input type="hidden" name="#command" value="/webGui/scripts/btrfs_scrub">
<input type="hidden" name="#arg[1]" value="cancel">
<input type="hidden" name="#arg[2]" value="/var/lib/docker">
&nbsp;
: <input type="button" value="Refresh" onclick="refresh()"><input type="submit" value="Cancel"> *Running*
> **Cancel** will cancel the Scrub operation in progress.
</form>
<?endif;?>
<div class="advanced">
<dl>
<dt>Default appdata storage location:</dt>
<dd><input id="DOCKER_APP_CONFIG_PATH" type="text" name="DOCKER_APP_CONFIG_PATH" value="<?=$dockercfg['DOCKER_APP_CONFIG_PATH'];?>" placeholder="e.g. /mnt/user/appdata" data-pickfilter="HIDE_FILES_FILTER" data-pickroot="<?= (is_dir('/mnt/user/') ? '/mnt/user/' : '/mnt/') ?>" data-pickfolders="true" /></dd>
</dl>
<blockquote class="inline_help">
<p>You can specify a folder to automatically generate and store subfolders containing configuration files for each Docker app (via the /config mapped volume).</p>
<p>Only used when adding new Docker apps. Editing existing Docker apps will not be affected by this setting.</p>
</blockquote>
<dl>
<dt>Auto-map user shares to containers as /unraid:</dt>
<dd>
<select id="DOCKER_APP_UNRAID_PATH" name="DOCKER_APP_UNRAID_PATH" class="narrow">
<?= mk_option($dockercfg['DOCKER_APP_UNRAID_PATH'], ($dockercfg['DOCKER_APP_UNRAID_PATH'] != '' ? $dockercfg['DOCKER_APP_UNRAID_PATH'] : '/mnt/user'), 'Yes'); ?>
<?= mk_option($dockercfg['DOCKER_APP_UNRAID_PATH'], '', 'No'); ?>
</select>
</dd>
</dl>
<blockquote class="inline_help">
<p>You can expose all of your user shares (/mnt/user) to a folder named /unraid within Docker containers.</p>
<p>Only used when adding new Docker apps. Editing existing Docker apps will not be affected by this setting.</p>
</blockquote>
</div>
<dl>
<dt>&nbsp;</dt>
<dd><input id="applyBtn" type="button" value="Apply"><input type="button" value="Done" onclick="done()"></dd>
</dl>
</form>
<?if (pgrep('docker') !== false):?>
<div class="advanced">
<div id="title"><span class="left"><img src="/plugins/dynamix.docker.manager/icons/vcard.png" class="icon">Docker volume info</span></div>
<dl>
<dt>btrfs filesystem show:</dt>
<dd><?="<pre>".shell_exec("btrfs filesystem show /var/lib/docker")."</pre>"?></dd>
</dl>
<form markdown="1" method="POST" action="/update.php" target="progressFrame">
<?exec("/usr/local/emhttp/webGui/scripts/btrfs_scrub status /var/lib/docker", $scrub_status, $retval);?>
<dl>
<dt>btrfs scrub status:</dt>
<dd><?="<pre>".implode("\n", $scrub_status)."</pre>"?></dd>
</dl>
<?if ($retval != 0):?>
<input type="hidden" name="#command" value="/webGui/scripts/btrfs_scrub">
<input type="hidden" name="#arg[1]" value="start">
<input type="hidden" name="#arg[2]" value="/var/lib/docker">
<dl>
<dt>&nbsp;</dt>
<dd><input type="submit" value="Scrub"><label><input type="checkbox" name="#arg[3]" value="-r" checked> Don't fix file system errors</label></dd>
</dl>
<blockquote class="inline_help">
<p><b>Scrub</b> runs the <i>btrfs scrub</i> program to check file system integrity.</p>
<p>If repair is needed you should uncheck the <i>Don't fix file system errors</i> option and run a second Scrub pass; this will permit <i>btrfs scrub</i> to fix the file system.</p>
</blockquote>
<?else:?>
<input type="hidden" name="#command" value="/webGui/scripts/btrfs_scrub">
<input type="hidden" name="#arg[1]" value="cancel">
<input type="hidden" name="#arg[2]" value="/var/lib/docker">
<dl>
<dt>&nbsp;</dt>
<dd><input type="button" value="Refresh" onclick="refresh()"><input type="submit" value="Cancel"> <i>Running</i></dd>
</dl>
<blockquote class="inline_help">
<p><b>Cancel</b> will cancel the Scrub operation in progress.</p>
</blockquote>
</form>
<?endif;?>
</div>
<?endif;?>
<script src="/webGui/javascript/jquery.filetree.js"></script>
<script src="/webGui/javascript/jquery.switchbutton.js"></script>
<script>
$(function() {
//$.ajaxSetup({ async: false });
@@ -203,7 +238,7 @@ $(function() {
});
if ($("#DOCKER_IMAGE_FILE").length) {
$("#DOCKER_IMAGE_FILE").on("change keyup", function(){
$("#DOCKER_IMAGE_FILE").on("input change", function(){
$("#IMAGE_ERROR").fadeOut();
$("#applyBtn").prop("disabled", false);
<? if (file_exists($dockercfg['DOCKER_IMAGE_FILE'])) { ?>
@@ -226,8 +261,28 @@ $(function() {
$("#DOCKER_ENABLED").prop("disabled", checked).val('no');
$("#DOCKER_IMAGE_SIZE").prop("disabled", checked);
$("#DOCKER_IMAGE_FILE").prop("disabled", checked).val("<?=$dockercfg['DOCKER_IMAGE_FILE']?>");
$("#applyBtn").val(checked ? "Delete" : "Apply");
$("#DOCKER_APP_CONFIG_PATH").prop("disabled", checked);
$("#DOCKER_APP_UNRAID_PATH").prop("disabled", checked);
$("#applyBtn").val(checked ? "Delete" : "Apply").removeAttr('disabled');
});
}
$("#DOCKER_APP_CONFIG_PATH").fileTreeAttach();
if ($.cookie('dockersettings_view_mode') == 'advanced') {
$('.advanced').show();
$('.basic').hide();
}
$('.advancedview').switchButton({
labels_placement: "left",
on_label: 'Advanced View',
off_label: 'Basic View',
checked: $.cookie('dockersettings_view_mode') == 'advanced'
});
$('.advancedview').change(function () {
$('.advanced').toggle('slow');
$('.basic').toggle('slow');
$.cookie('dockersettings_view_mode', $('.advancedview').is(':checked') ? 'advanced' : 'basic', { expires: 3650 });
});
});
</script>

File diff suppressed because it is too large Load Diff

View File

@@ -55,6 +55,29 @@ if (! isset($var)){
$var = @parse_ini_file("/usr/local/emhttp/state/var.ini");
}
$docker_cfg_defaults = [
"DOCKER_ENABLED" => "no",
"DOCKER_OPTS" => "--storage-driver=btrfs",
"DOCKER_IMAGE_SIZE" => "20",
"DOCKER_IMAGE_FILE" => "/mnt/user/system/docker/docker.img",
"DOCKER_APP_CONFIG_PATH" => "/mnt/user/appdata/",
"DOCKER_APP_UNRAID_PATH" => ""
];
$dockercfg = $docker_cfg_defaults;
// Docker configuration file - create if needed
$docker_cfgfile = "/boot/config/docker.cfg";
if (!file_exists($docker_cfgfile)) {
$tmp = '';
foreach ($docker_cfg_defaults as $key => $value) $tmp .= "$key=\"$value\"\n";
file_put_contents($docker_cfgfile, $tmp);
} else {
$docker_cfg_existing = parse_ini_file($docker_cfgfile);
if (!empty($docker_cfg_existing)) {
$dockercfg = array_merge($docker_cfg_defaults, $docker_cfg_existing);
}
}
######################################
## DOCKERTEMPLATES CLASS ##
######################################
@@ -67,10 +90,10 @@ class DockerTemplates {
if($this->verbose) echo $m."\n";
}
public function download_url($url, $path = "", $bg = FALSE){
exec("curl --max-time 60 --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 60 --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 listDir($root, $ext=NULL) {
$iter = new RecursiveIteratorIterator(
@@ -133,10 +156,10 @@ class DockerTemplates {
$repotemplates = array();
$output = "";
$tmp_dir = "/tmp/tmp-".mt_rand();
if (!file_exists($dockerManPaths['template-repos'])) {
@mkdir(dirname($dockerManPaths['template-repos']), 0777, true);
@file_put_contents($dockerManPaths['template-repos'], "https://github.com/limetech/docker-templates");
}
if (!file_exists($dockerManPaths['template-repos'])) {
@mkdir(dirname($dockerManPaths['template-repos']), 0777, true);
@file_put_contents($dockerManPaths['template-repos'], "https://github.com/limetech/docker-templates");
}
$urls = @file($Urls, FILE_IGNORE_NEW_LINES);
if ( ! is_array($urls)) return false;
$this->debug("\nURLs:\n " . implode("\n ", $urls));
@@ -275,8 +298,9 @@ class DockerTemplates {
}
public function removeInfo($container){
public function removeInfo($container, $image){
global $dockerManPaths;
$image = ($image && count(preg_split("#[:\/]#", $image)) < 3) ? "${image}:latest" : $image;
$dockerIni = $dockerManPaths['webui-info'];
if (! is_dir( dirname( $dockerIni ))) @mkdir( dirname( $dockerIni ), 0770, true);
$info = (is_file($dockerIni)) ? json_decode(file_get_contents($dockerIni), TRUE) : array();
@@ -287,7 +311,7 @@ class DockerTemplates {
$update_file = $dockerManPaths['update-status'];
$updateStatus = (is_file($update_file)) ? json_decode(file_get_contents($update_file), TRUE) : array();
if (isset($updateStatus[$container])) unset($updateStatus[$container]);
if (isset($updateStatus[$image])) unset($updateStatus[$image]);
file_put_contents($update_file, json_encode($updateStatus, JSON_PRETTY_PRINT | JSON_UNESCAPED_SLASHES));
}
@@ -296,6 +320,7 @@ class DockerTemplates {
global $dockerManPaths;
$DockerClient = new DockerClient();
$DockerUpdate = new DockerUpdate();
$DockerUpdate->verbose = $this->verbose;
$new_info = array();
$dockerIni = $dockerManPaths['webui-info'];
@@ -310,9 +335,6 @@ class DockerTemplates {
$allAutoStart = @file($autostart_file, FILE_IGNORE_NEW_LINES);
if ($allAutoStart===FALSE) $allAutoStart = array();
$update_file = $dockerManPaths['update-status'];
$updateStatus = (is_file($update_file)) ? json_decode(file_get_contents($update_file), TRUE) : array();
foreach ($containers as $ct) {
$name = $ct['Name'];
$image = $ct['Image'];
@@ -321,34 +343,33 @@ class DockerTemplates {
$tmp['running'] = $ct['Running'];
$tmp['autostart'] = in_array($name, $allAutoStart);
$img = $this->getBannerIcon( $image );
$tmp['banner'] = ( $img['banner'] ) ? $img['banner'] : "#";
$tmp['icon'] = ( $img['icon'] ) ? $img['icon'] : "#";
$WebUI = $this->getControlURL($name);
$tmp['url'] = ($WebUI) ? $WebUI : "#";
if (! $tmp['icon'] || ! $tmp['banner'] || $reload) {
$img = $this->getBannerIcon( $image );
$tmp['banner'] = ( $img['banner'] ) ? $img['banner'] : null;
$tmp['icon'] = ( $img['icon'] ) ? $img['icon'] : null;
}
if (! $tmp['url'] || $reload) {
$WebUI = $this->getControlURL($name);
$tmp['url'] = ($WebUI) ? $WebUI : null;
}
$Registry = $this->getTemplateValue($image, "Registry");
$tmp['registry'] = ( $Registry ) ? $Registry : "#";
$tmp['registry'] = ( $Registry ) ? $Registry : null;
if ($reload) {
$nv = $DockerUpdate->getUpdateStatus($name, $image);
if ($nv != 'undef'){
$updateStatus[$name] = $nv;
}
if (! $tmp['updated'] || $reload) {
if ($reload) $DockerUpdate->reloadUpdateStatus($image);
$vs = $DockerUpdate->getUpdateStatus($image);
$tmp['updated'] = ($vs === NULL) ? null : ( ($vs === TRUE) ? 'true' : 'false' );
}
$tmp['updated'] = (array_key_exists($name, $updateStatus)) ? $updateStatus[$name] : 'undef';
$tmp['template'] = $this->getUserTemplate($name);
if (! $tmp['template'] || $reload){
$tmp['template'] = $this->getUserTemplate($name);
}
$this->debug("\n$name");foreach ($tmp as $c => $d) $this->debug(sprintf(" %-10s: %s", $c, $d));
$this->debug("\n$name");foreach ($tmp as $c => $d) $this->debug(sprintf(" %-10s: %s", $c, $d));
$new_info[$name] = $tmp;
}
file_put_contents($dockerIni, json_encode($new_info, JSON_PRETTY_PRINT | JSON_UNESCAPED_SLASHES));
if($reload) {
foreach ($updateStatus as $ct => $update) if (!isset($new_info[$ct])) unset($updateStatus[$ct]);
file_put_contents($update_file, json_encode($updateStatus, JSON_PRETTY_PRINT | JSON_UNESCAPED_SLASHES));
}
return $new_info;
}
@@ -358,12 +379,9 @@ class DockerTemplates {
$out = array();
$Images = array();
$Images = array('banner' => $this->getTemplateValue($Repository, "Banner"),
$Images = array('banner' => $this->getTemplateValue($Repository, "Banner"),
'icon' => $this->getTemplateValue($Repository, "Icon") );
$defaultImages = array('banner' => '/plugins/dynamix.docker.manager/images/spacer.png',
'icon' => '/plugins/dynamix.docker.manager/images/question.png');
foreach ($Images as $type => $imgUrl) {
preg_match_all("/(.*?):([\w]*$)/i", $Repository, $matches);
$tempPath = sprintf("%s/%s-%s-%s.png", $dockerManPaths[ 'images-ram' ], preg_replace('%\/|\\\%', '-', $matches[1][0]), $matches[2][0], $type);
@@ -389,6 +407,12 @@ class DockerTemplates {
## DOCKERUPDATE CLASS ##
######################################
class DockerUpdate{
public $verbose = false;
private function debug($m) {
if($this->verbose) echo $m."\n";
}
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 );
@@ -396,37 +420,58 @@ class DockerUpdate{
}
public function getRemoteVersion($RegistryUrl, $image){
preg_match_all("/:([\w]*$)/i", $image, $matches);
$tag = isset($matches[1][0]) ? $matches[1][0] : "latest";
preg_match("#/u/([^/]*)/([^/]*)#", $RegistryUrl, $matches);
$apiUrl = sprintf("http://index.docker.io/v1/repositories/%s/%s/tags/%s", $matches[1], $matches[2], $tag);
public function getRemoteVersion($image){
$apiUrl = vsprintf("http://index.docker.io/v1/repositories/%s/%s/tags/%s", preg_split("#[:\/]#", $image));
$this->debug("API URL: $apiUrl");
$apiContent = $this->download_url($apiUrl);
return ( $apiContent === FALSE ) ? NULL : substr(json_decode($apiContent, TRUE)[0]['id'],0,16);
return ( $apiContent === FALSE ) ? NULL : substr(json_decode($apiContent, TRUE)[0]['id'],0,8);
}
public function getLocalVersion($file){
if(is_file($file)){
$doc = new DOMDocument();
$doc->load($file);
if ( ! $doc->getElementsByTagName( "Version" )->length == 0 ) {
return $doc->getElementsByTagName( "Version" )->item(0)->nodeValue;
public function getLocalVersion($image){
$DockerClient = new DockerClient();
return substr($DockerClient->getImageID($image), 0, 8);
}
public function getUpdateStatus($image) {
global $dockerManPaths;
$DockerClient = new DockerClient();
$update_file = $dockerManPaths['update-status'];
$updateStatus = (is_file($update_file)) ? json_decode(file_get_contents($update_file), TRUE) : array();
// Add :latest tag to image if it's absent
$image = ($image && count(preg_split("#[:\/]#", $image)) < 3) ? "${image}:latest" : $image;
if(isset($updateStatus[$image])) {
if ($updateStatus[$image]['local'] && $updateStatus[$image]['remote']) {
return ($updateStatus[$image]['local'] == $updateStatus[$image]['remote']) ? true : false;
} else {
return NULL;
return null;
}
} else {
return null;
}
}
public function getUpdateStatus($container, $image) {
$DockerTemplates = new DockerTemplates();
$RegistryUrl = $DockerTemplates->getTemplateValue($image, "Registry");
$userFile = $DockerTemplates->getUserTemplate($container);
$localVersion = $this->getLocalVersion($userFile);
$remoteVersion = $this->getRemoteVersion($RegistryUrl, $image);
// echo "\n $localVersion => $remoteVersion";
return ($localVersion && $remoteVersion) ? (($remoteVersion == $localVersion) ? "true" : "false") : "undef" ;
public function reloadUpdateStatus($image = null) {
global $dockerManPaths;
$DockerClient = new DockerClient();
$update_file = $dockerManPaths['update-status'];
$updateStatus = (is_file($update_file)) ? json_decode(file_get_contents($update_file), TRUE) : array();
// Add :latest tag to image if it's absent
$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);
$status = ($localVersion && $remoteVersion) ? (($remoteVersion == $localVersion) ? "true" : "false") : "undef";
$updateStatus[$img] = array('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));
}
@@ -445,6 +490,12 @@ class DockerUpdate{
######################################
class DockerClient {
private $allContainersCache = null;
private $allImagesCache = null;
private function build_sorter($key) {
return function ($a, $b) use ($key) {
return strnatcmp(strtolower($a[$key]), strtolower($b[$key]));
@@ -486,65 +537,114 @@ class DockerClient {
}
private function getDockerJSON($url, $method = "GET"){
public function getDockerJSON($url, $method = "GET", &$code = null, $callback = null, $unchunk = false){
$fp = stream_socket_client('unix:///var/run/docker.sock', $errno, $errstr);
if ($fp === false) {
echo "Couldn't create socket: [$errno] $errstr";
return NULL;
}
$out="$method {$url} HTTP/1.1\r\nConnection: Close\r\n\r\n";
$protocol = ($unchunk) ? "HTTP/1.0" : "HTTP/1.1";
$out="${method} {$url} ${protocol}\r\nConnection: Close\r\n\r\n";
fwrite($fp, $out);
// Strip headers out
$headers = '';
while (($line = fgets($fp)) !== false) {
if (! is_bool(strpos($line, "HTTP/1"))) {
$code = vsprintf('%2$s',preg_split("#\s+#", $line));
}
$headers .= $line;
if (rtrim($line) == '') {
break;
}
}
$data = '';
$data = array();
while (($line = fgets($fp)) !== false) {
$data .= $line;
if (is_array($j = json_decode($line, true))) {
$data = array_merge($data, $j);
}
if ($callback) $callback($line);
}
fclose($fp);
$data = $this->unchunk($data);
$json = json_decode( $data, true );
if ($json === null) {
$json = array();
} else if (!array_key_exists(0, $json) && !empty($json)) {
$json = [ $json ];
}
return $json;
return $data;
}
public function createDockerContainer() {
return false;
}
public function getInfo(){
$info = $this->getDockerJSON("/info");
$version = $this->getDockerJSON("/version");
return array_merge($info[0], $version[0]);
return array_merge($info, $version);
}
private function getContainerDetails($id){
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 getContainerDetails($id){
$json = $this->getDockerJSON("/containers/{$id}/json");
return $json;
}
public function startContainer($id){
$json = $this->getDockerJSON("/containers/${id}/start", "POST");
return $json;
}
public function removeImage($id){
$json = $this->getDockerJSON("/images/{$id}", "DELETE");
return $json;
$this->getDockerJSON("/containers/${id}/start", "POST", $code);
$codes = array("204" => "No error",
"304" => "Container already started",
"404" => "No such container",
"500" => "Server error");
return ($code == "204") ? true : $codes[$code];
}
public function stopContainer($id){
$json = $this->getDockerJSON("/containers/${id}/stop", "POST");
return $json;
$this->getDockerJSON("/containers/${id}/stop", "POST", $code);
$codes = array("204" => "No error.",
"304" => "Container already started.",
"404" => "No such container.",
"500" => "Server error.");
return ($code == "204") ? true : $codes[$code];
}
public function restartContainer($id){
$json = $this->getDockerJSON("/containers/${id}/restart", "POST", $code);
$codes = array("204" => "No error.",
"404" => "No such container.",
"500" => "Server error.");
return ($code == "204") ? true : $codes[$code];
}
public function removeContainer($id){
$json = $this->getDockerJSON("/containers/{$id}?force=1", "DELETE", $code);
$codes = array("204" => "No error.",
"400" => "Bad parameter.",
"404" => "No such container.",
"500" => "Server error.");
return ($code == "204") ? true : $codes[$code];
}
public function pullImage($image, $callback = null) {
return $this->getDockerJSON("/images/create?fromImage=$image", "POST", $code, $callback);
}
public function removeImage($id){
$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].".",
"500" => "Server error.");
return ($code == "200") ? true : $codes[$code];
}
@@ -555,6 +655,11 @@ class DockerClient {
public function getDockerContainers(){
// Return cached values
if (is_array($this->allContainersCache)){
return $this->allContainersCache;
}
$containers = array();
$json = $this->getDockerJSON("/containers/json?all=1");
@@ -566,24 +671,24 @@ class DockerClient {
preg_match("/\b^Up\b/", $status, $matches);
$running = $matches ? TRUE : FALSE;
$details = $this->getContainerDetails($obj['Id']);
// echo "<pre>".print_r($obj,TRUE)."</pre>";
// echo "<pre>".print_r($details,TRUE)."</pre>";
// Docker 1.7 uses full image ID when there aren't tags, so lets crop it
$Image = (strlen($obj['Image']) == 64) ? substr($obj['Image'],0,12) : $obj['Image'];
// Docker 1.7 doesn't automatically append the tag 'latest', so we do that now if there's no tag
preg_match_all("/:([\w]*$)/i", $obj['Image'], $matches2);
$c["Image"] = $obj['Image'] . (isset($matches2[1][0]) ? "" : ":latest");
$c["ImageId"] = substr($details[0]["Image"],0,12);
$c["Name"] = substr($details[0]['Name'], 1);
$c["Image"] = ($Image && count(preg_split("#[:\/]#", $Image)) < 3) ? "${Image}:latest" : $Image;
$c["ImageId"] = substr($details["Image"],0,12);
$c["Name"] = substr($details['Name'], 1);
$c["Status"] = $status;
$c["Running"] = $running;
$c["Cmd"] = $obj['Command'];
$c["Id"] = substr($obj['Id'],0,12);
$c['Volumes'] = $details[0]["HostConfig"]['Binds'];
$c['Volumes'] = $details["HostConfig"]['Binds'];
$c["Created"] = $this->humanTiming($obj['Created']);
$c["NetworkMode"] = $details[0]['HostConfig']['NetworkMode'];
$c["NetworkMode"] = $details['HostConfig']['NetworkMode'];
$c["BaseImage"] = isset($obj["Labels"]["BASEIMAGE"]) ? $obj["Labels"]["BASEIMAGE"] : false;
$Ports = $details[0]['HostConfig']['PortBindings'];
$Ports = $details['HostConfig']['PortBindings'];
$Ports = (count ( $Ports )) ? $Ports : array();
$c["Ports"] = array();
if ($c["NetworkMode"] != 'host'){
@@ -599,10 +704,24 @@ class DockerClient {
$containers[] = $c;
}
usort($containers, $this->build_sorter('Name'));
$this->allContainersCache = $containers;
return $containers;
}
public function getContainerID($Container){
$allContainers = $this->getDockerContainers();
foreach ($allContainers as $ct) {
preg_match("%" . preg_quote($Container, "%") ."%", $ct["Name"], $matches);
if( $matches){
return $ct["Id"];
}
}
return NULL;
}
public function getImageID($Image){
$allImages = $this->getDockerImages();
foreach ($allImages as $img) {
@@ -629,7 +748,10 @@ class DockerClient {
public function getDockerImages(){
// Return cached values
if (is_array($this->allImagesCache)){
return $this->allImagesCache;
}
$images = array();
$c = array();
$json = $this->getDockerJSON("/images/json?all=0");
@@ -642,6 +764,7 @@ 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);
@@ -649,17 +772,12 @@ class DockerClient {
$c["Size"] = $this->formatBytes($obj['Size']);
$c["VirtualSize"] = $this->formatBytes($obj['VirtualSize']);
$c["Tags"] = $tags;
$c["Repository"] = vsprintf('%1$s/%2$s',preg_split("#[:\/]#", $tags[0]));
$c["usedBy"] = $this->usedBy($c["Id"]);
$imgDetails = $this->getImageDetails($obj['Id']);
// echo "<pre>".print_r($imgDetails,TRUE)."</pre>";
$a = $imgDetails[0]['Config']['Volumes'];
$b = $imgDetails[0]['Config']['ExposedPorts'];
$c['ImageType'] = (! count($a) && ! count($b)) ? 'base' : 'user';
$images[] = $c;
$images[$c["Id"]] = $c;
}
$this->allImagesCache = $images;
return $images;
}
}

View File

@@ -0,0 +1,49 @@
<?PHP
require_once("/usr/local/emhttp/plugins/dynamix.docker.manager/include/DockerClient.php");
$DockerClient = new DockerClient();
$container = $_POST['container'];
$image = $_POST['image'];
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;
}
$container = $_GET['container'];
$since = $_GET['since'];
$title = $_GET['title'];
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;
}
?>

View File

@@ -1,4 +1,6 @@
function addDockerContainerContext(container, image, template, started, update, autostart, webui){
var eventURL = "/plugins/dynamix.docker.manager/include/Events.php";
function addDockerContainerContext(container, image, template, started, update, autostart, webui, id){
var opts = [{header: container, image: "/plugins/dynamix.docker.manager/images/dynamix.docker.manager.png"}];
if (started && (webui != "#")) {
opts.push({text: 'WebUI', icon:'fa-globe', href: webui, target: '_blank' });
@@ -9,20 +11,20 @@ function addDockerContainerContext(container, image, template, started, update,
opts.push({divider: true});
}
if (started){
opts.push({text: 'Stop', icon:'fa-stop', action: function(e){ e.preventDefault(); containerControl(container, 'stop'); }});
opts.push({text: 'Restart', icon:'fa-refresh', action: function(e){ e.preventDefault(); containerControl(container, 'restart'); }});
opts.push({text: 'Stop', icon:'fa-stop', action: function(e){ e.preventDefault(); containerControl(id, 'stop', true); }});
opts.push({text: 'Restart', icon:'fa-refresh', action: function(e){ e.preventDefault(); containerControl(id, 'restart', true); }});
} else {
opts.push({text: 'Start', icon:'fa-play', action: function(e){ e.preventDefault(); containerControl(container, 'start'); }});
opts.push({text: 'Start', icon:'fa-play', action: function(e){ e.preventDefault(); containerControl(id, 'start', true); }});
}
opts.push({divider: true});
if (location.pathname.indexOf("/Dashboard") === 0) {
opts.push({text: 'Logs', icon:'fa-navicon', action: function(e){ e.preventDefault(); containerLogs(container); }});
opts.push({text: 'Logs', icon:'fa-navicon', action: function(e){ e.preventDefault(); containerLogs(container, id); }});
}
if (template) {
opts.push({text: 'Edit', icon:'fa-wrench', action: function(e){ e.preventDefault(); editContainer(container, template); }});
}
opts.push({divider: true});
opts.push({text: 'Remove', icon:'fa-trash', action: function(e){ e.preventDefault(); rmContainer(container, image); }});
opts.push({text: 'Remove', icon:'fa-trash', action: function(e){ e.preventDefault(); rmContainer(container, image, id); }});
context.attach('#context-'+container, opts);
}
@@ -86,23 +88,9 @@ function editContainer(container, template) {
location = path + '/UpdateContainer?xmlTemplate=edit:' + template;
}
function rmContainer(containers, images){
var ctCmd = "/plugins/dynamix.docker.manager/scripts/docker rm -f";
var imgCmd = "/plugins/dynamix.docker.manager/scripts/docker rmi";
var ctTitle = "";
if (typeof containers === "object") {
for (var i = 0; i < containers.length; i++) {
ctCmd += " " + containers[i];
imgCmd += " " + images[i];
ctTitle += containers[i] + "<br>";
}
} else {
ctCmd += " " + containers;
imgCmd += " " + images;
ctTitle += containers + "<br>";
}
var title = 'Removing container';
$( "#dialog-confirm" ).html(ctTitle);
function rmContainer(container, image, id){
var title = 'Removing container: '+ container;
$( "#dialog-confirm" ).html('');
$( "#dialog-confirm" ).append( "<br><span style='color: #E80000;'>Are you sure?</span>" );
$( "#dialog-confirm" ).dialog({
title: title,
@@ -114,13 +102,12 @@ function rmContainer(containers, images){
buttons: {
"Just the container": function() {
$( this ).dialog( "close" );
var cmd = '/plugins/dynamix.docker.manager/include/Exec.php?cmd=' + encodeURIComponent(ctCmd);
popupWithIframe(title, cmd, true);
containerControl(id, 'remove_container', true);
},
"Container and image": function() {
$( this ).dialog( "close" );
var cmd = '/plugins/dynamix.docker.manager/include/Exec.php?cmd=' + encodeURIComponent(ctCmd + ";" + imgCmd);
popupWithIframe(title, cmd, true);
containerControl(id, 'remove_container', false);
imageControl(image, "remove_image", true);
},
Cancel: function() {
$( this ).dialog( "close" );
@@ -134,20 +121,12 @@ function rmContainer(containers, images){
$(".ui-button-text").css('padding','0px 5px');
}
function updateContainer(containers){
var ctCmd ="";
function updateContainer(container){
var ctCmd = "&ct[]=" + encodeURIComponent(container);
var ctTitle = "";
if (typeof containers === "object") {
for (var i = 0; i < containers.length; i++) {
ctCmd += "&ct[]=" + encodeURIComponent(containers[i]);
ctTitle += containers[i] + "<br>";
}
} else {
ctCmd += "&ct[]=" + encodeURIComponent(containers);
ctTitle += containers + "<br>";
}
var title = 'Updating container';
$( "#dialog-confirm" ).html(ctTitle);
var title = 'Updating container: '+container;
$( "#dialog-confirm" ).html('');
$( "#dialog-confirm" ).append( "<br><span style='color: #E80000;'>Are you sure?</span>" );
$( "#dialog-confirm" ).dialog({
title: title,
@@ -175,20 +154,10 @@ function updateContainer(containers){
$( ".ui-dialog .ui-dialog-title" ).css( 'width', "100%");
}
function rmImage(images, imageName){
var imgCmd = "/plugins/dynamix.docker.manager/scripts/docker rmi";
var imgTitle = "";
if (typeof images === "object") {
for (var i = 0; i < images.length; i++) {
imgCmd += " " + images[i];
imgTitle += imageName[i] + "<br>";
}
} else {
imgCmd += " " + images;
imgTitle += imageName + "<br>";
}
var title = "Removing image";
$( "#dialog-confirm" ).html(imgTitle);
function rmImage(image, imageName){
var imageName = $('<textarea />').html(imageName).text();
var title = "Removing image: "+imageName;
$( "#dialog-confirm" ).html('');
$( "#dialog-confirm" ).append( "<br><span style='color: #E80000;'>Are you sure?</span>" );
$( "#dialog-confirm" ).dialog({
title: title,
@@ -201,8 +170,7 @@ function rmImage(images, imageName){
buttons: {
"Just do it!": function() {
$( this ).dialog( "close" );
var cmd = '/plugins/dynamix.docker.manager/include/Exec.php?cmd=' + encodeURIComponent(imgCmd);
popupWithIframe(title, cmd, true);
imageControl(image, "remove_image", true);
},
Cancel: function() {
$( this ).dialog( "close" );
@@ -217,11 +185,24 @@ function rmImage(images, imageName){
$( ".ui-dialog .ui-dialog-title" ).css( 'width', "100%");
}
function containerControl(container, action){
$("#cmdStartStop").val("/plugins/dynamix.docker.manager/scripts/docker");
$("#cmdArg1").val(action);
$("#cmdArg2").val(container);
$("#formStartStop").submit();
function imageControl(image, action, reload){
if (typeof reload == undefined) reload = true
$.post(eventURL,{action:action, image:image},function(data){
if(data.success === true) {
if (reload) location.reload();
} else {
alert("Error:\n\n"+data.success);
}},"json");
}
function containerControl(container, action, reload){
if (typeof reload == undefined) reload = true
$.post(eventURL,{action:action, container:container},function(data){
if(data.success === true) {
if (reload) location.reload();
} else {
alert("Error:\n\n"+data.success);
}},"json");
}
function reloadUpdate(){
@@ -237,6 +218,13 @@ function autoStart(container, event){
$("#formStartStop").submit();
}
function containerLogs(container){
openWindow('/plugins/dynamix.docker.manager/scripts/docker&arg1=logs&arg2=--tail=350&arg3=-f&arg4=' + container, 'Log for: ' + container, 600, 900);
function containerLogs(container, id){
var height = 600;
var width = 900;
var run = eventURL+'?action=log&container='+id+'&title=Log for: '+container;
var top = (screen.height-height)/2;
var left = (screen.width-width)/2;
var options = 'resizeable=yes,scrollbars=yes,height='+height+',width='+width+',top='+top+',left='+left;
window.open(run, 'log', options);
}

View File

@@ -31,8 +31,10 @@ function addLog(logLine) {
window.scrollTo(0,document.body.scrollHeight);
}
function show_Prog(prog) {
$(".progress:last").text(prog);
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);
});
}
</script>
</head>

View File

@@ -12,38 +12,40 @@
*/
?>
<?
require_once("/usr/local/emhttp/plugins/dynamix.docker.manager/include/DockerClient.php");
exec("pgrep docker",$pid);
if (count($pid)==1) exit(0);
$docker = new DockerTemplates();
require_once("/usr/local/emhttp/plugins/dynamix.docker.manager/include/DockerClient.php");
$DockerClient = new DockerClient();
$DockerTemplates = new DockerTemplates();
foreach ($argv as $arg) {
switch ($arg) {
case '-v' : $docker->verbose = true; break;
case '-v' : $DockerTemplates->verbose = true; break;
case 'check': $check = true; break;}
}
if (!isset($check)) {
echo " Updating templates... ";
$docker->downloadTemplates();
$DockerTemplates->downloadTemplates();
echo " Updating info... ";
$docker->getAllInfo(true);
$DockerTemplates->getAllInfo(true);
echo " Done.";
} else {
require_once("/usr/local/emhttp/webGui/include/Wrappers.php");
$client = new DockerClient();
$update = new DockerUpdate();
$notify = "/usr/local/emhttp/webGui/scripts/notify";
$unraid = parse_plugin_cfg("dynamix",true);
$server = strtoupper($var['NAME']);
$output = $unraid['notify']['docker_notify'];
$list = $client->getDockerContainers();
$info = $docker->getAllInfo();
$list = $DockerClient->getDockerContainers();
$info = $DockerTemplates->getAllInfo(true);
foreach ($list as $ct) {
$name = $ct['Name'];
$image = $ct['Image'];
if ($info[$name]['updated'] == "false") {
$new = $update->getRemoteVersion($docker->getTemplateValue($image, "Registry"), $image);
$updateStatus = (is_file($dockerManPaths['update-status'])) ? json_decode(file_get_contents($dockerManPaths['update-status']), TRUE) : array();
$new = $updateStatus[$image]['remote'];
exec("$notify -e 'Docker - $name [$new]' -s 'Notice [$server] - Docker update $new' -d 'A new version of $name is available' -i 'normal $output' -x");
}
}

View File

@@ -78,7 +78,7 @@ foreach ($allContainers as $ct) {
$updateStatus = ($updateStatus == "true" or $updateStatus == "undef" ) ? 'true' : 'false';
$running = ($ct['Running']) ? 'true' : 'false';
$webGuiUrl = $info['url'];
$contextMenus[] = sprintf("addDockerContainerContext('%s', '%s', '%s', %s, %s, %s, '%s');", addslashes($ct['Name']), addslashes($ct['ImageId']), addslashes($info['template']), $running, $updateStatus, $is_autostart, addslashes($webGuiUrl));
$contextMenus[] = sprintf("addDockerContainerContext('%s', '%s', '%s', %s, %s, %s, '%s', '%s');", addslashes($ct['Name']), addslashes($ct['ImageId']), addslashes($info['template']), $running, $updateStatus, $is_autostart, addslashes($webGuiUrl), $ct["Id"] );
$shape = ($ct["Running"]) ? "play" : "square";
$status = ($ct["Running"]) ? "started" : "stopped";
@@ -134,7 +134,7 @@ foreach ($allVMs as $name) {
$vncport = ($vncport < 0) ? "auto" : "";
}
$template = $lv->_get_single_xpath_result($res, '//domain/metadata/vmtemplate/@name');
$template = $lv->_get_single_xpath_result($res, '//domain/metadata/*[local-name()=\'vmtemplate\']/@name');
if (empty($template)) {
$template = 'Custom';
}
@@ -146,7 +146,7 @@ foreach ($allVMs as $name) {
// fallback icon for users that created VMs before metadata support was added
$vmicon = '/plugins/dynamix.vm.manager/templates/images/' . ($lv->domain_get_clock_offset($res) == 'localtime' ? 'windows.png' : 'linux.png');
$vmtemplateicon = $lv->_get_single_xpath_result($res, '//domain/metadata/vmtemplate/@icon');
$vmtemplateicon = $lv->_get_single_xpath_result($res, '//domain/metadata/*[local-name()=\'vmtemplate\']/@icon');
if (!empty($vmtemplateicon)) {
if (file_exists($vmtemplateicon)) {
$vmicon = $vmtemplateicon;

View File

@@ -27,28 +27,34 @@ $root = '/';
if( !$root ) exit("ERROR: Root filesystem directory not set in jqueryFileTree.php");
$postDir = $root.(isset($_POST['dir']) ? $_POST['dir'] : '' );
if (substr($postDir, -1) != '/') {
$postDir .= '/';
}
$postDir = preg_replace("#[\/]+#", "/", $postDir);
$filters = (array)(isset($_POST['filter']) ? $_POST['filter'] : '');
// set checkbox if multiSelect set to true
$checkbox = ( isset($_POST['multiSelect']) && $_POST['multiSelect'] == 'true' ) ? "<input type='checkbox' />" : null;
$returnDir = $postDir;
echo "<ul class='jqueryFileTree'>";
// Parent dirs
if ($_POST['show_parent'] == "true" ) {
echo "<li class='directory collapsed'>{$checkbox}<a href='#' rel='" . htmlentities(dirname($postDir), ENT_QUOTES) . "/'>..</a></li>";
}
if( file_exists($postDir) ) {
$files = scandir($postDir);
$returnDir = substr($postDir, strlen($root));
$files = scandir($postDir);
natcasesort($files);
if( count($files) > 2 ) { // The 2 accounts for . and ..
echo "<ul class='jqueryFileTree'>";
// All dirs
if ($_POST['show_parent'] == "true" ) echo "<li class='directory collapsed'>{$checkbox}<a href='#' rel='" . htmlentities(dirname($returnDir), ENT_QUOTES) . "/'>..</a></li>";
foreach( $files as $file ) {
if( file_exists($postDir . $file) && $file != '.' && $file != '..' ) {
if( is_dir($postDir . $file) ) {
@@ -77,8 +83,8 @@ if( file_exists($postDir) ) {
}
}
echo "</ul>";
}
}
echo "</ul>";
?>

View File

@@ -84,6 +84,8 @@ if(jQuery) (function($){
if ($(this).text() == "..") {
// Restart fileTree with the parent dir as root
options.root = data.rel;
if (folder) folder($(this).attr('rel'));
_trigger($(this), 'filetreefolderclicked', data);
root = $(this).closest('ul.jqueryFileTree');
root.html('<ul class="jqueryFileTree start"><li class="wait">' + options.loadMessage + '<li></ul>');
showTree( $(root), options.root, options.allowBrowsing );