Merge pull request #312 from bergware/master

Minor GUI adjustments
This commit is contained in:
tom mortensen
2018-03-26 13:54:20 -07:00
committed by GitHub
16 changed files with 239 additions and 413 deletions
@@ -40,6 +40,7 @@ th.eight{width:8%}
<div id="dialog-confirm" style="display:none;" title="Dialog Title"></div>
<div id="iframe-popup" style="display:none;-webkit-overflow-scrolling:touch;"></div>
<span class="status" style="margin-top:-44px"><span><input type="checkbox" class="advancedview"></span></span>
<div class="spinner fixed"></div>
<table class="tablesorter shift" id="docker_containers">
<thead><tr><th></th><th>Application</th><th>Version</th><th>Network</th><th>Port Mappings <small>(App to Host)</small></th><th>Volume Mappings <small>(App to Host)</small></th><th class="eight">Autostart</th><th class="five">Log</th><th class="five" style="text-align:right;padding-right:24px"><a href="#" style="cursor:hand" onclick="resetSorting()" title="Reset sorting"><i class="fa fa-sort"></i></a></th></tr></thead>
<tbody id="docker_list"><tr><td colspan='8'><div class="spinner"></div></td></tr></tbody>
@@ -80,6 +81,7 @@ function listview() {
function loadlist(update) {
$.get('/plugins/dynamix.docker.manager/include/DockerContainers.php',function(d) {
var data = d.split(/\0/);
$('div.spinner').hide('slow');
$('#docker_list').html(data[0]);
$('head').append('<script>'+data[1]+'<\/script>');
<?if ($display['resize']):?>
@@ -94,11 +96,7 @@ function loadlist(update) {
listview();
context.init({preventDoubleContext:false});
$('input[type=button]').prop('disabled',false).show('slow');
if (update) {
$('input#checkAll').hide(); $('input#updateAll').show();
} else {
$('input#checkAll').show(); $('input#updateAll').hide();
}
if (!update) $('input#updateAll').hide();
$('i.up,i.down').click(function() {
var row = $(this).parents('tr:first');
var color1 = row.css('background-color');
@@ -16,7 +16,7 @@ $user_prefs = '/boot/config/plugins/dockerMan/userprefs.cfg';
# controlled docker execution
function docker($cmd, &$var=null) {
return exec("timeout 20 /usr/bin/docker $cmd 2>/dev/null",$var);
return exec("docker $cmd 2>/dev/null", $var);
}
$action = $_POST['action'];
$status = $action=='start' ? 'exited' : 'running';
@@ -13,7 +13,7 @@
?>
<?
$docroot = $docroot ?? $_SERVER['DOCUMENT_ROOT'] ?: '/usr/local/emhttp';
$var = parse_ini_file('state/var.ini');
$var = parse_ini_file('state/var.ini');
extract(parse_ini_file('state/network.ini',true));
ignore_user_abort(true);
@@ -40,29 +40,23 @@ foreach ($custom as $network) $subnet[$network] = substr(docker("network inspect
function stopContainer($name) {
global $DockerClient;
$waitID = mt_rand();
echo "<p class=\"logLine\" id=\"logBody\"></p>";
echo "<script>addLog('<fieldset style=\"margin-top:1px;\" class=\"CMD\"><legend>Stopping container: ".addslashes(htmlspecialchars($name))."</legend><p class=\"logLine\" id=\"logBody\"></p><span id=\"wait{$waitID}\">Please wait </span></fieldset>');show_Wait($waitID);</script>\n";
@flush();
$retval = $DockerClient->stopContainer($name);
$out = ($retval === true) ? "Successfully stopped container '$name'" : "Error: ".$retval;
echo "<script>stop_Wait($waitID);addLog('<b>".addslashes(htmlspecialchars($out))."</b>');</script>\n";
@flush();
}
function removeContainer($name) {
function removeContainer($name, $remove=false) {
global $DockerClient;
$waitID = mt_rand();
echo "<p class=\"logLine\" id=\"logBody\"></p>";
echo "<script>addLog('<fieldset style=\"margin-top:1px;\" class=\"CMD\"><legend>Removing container: ".addslashes(htmlspecialchars($name))."</legend><p class=\"logLine\" id=\"logBody\"></p><span id=\"wait{$waitID}\">Please wait </span></fieldset>');show_Wait($waitID);</script>\n";
@flush();
$retval = $DockerClient->removeContainer($name);
$retval = $DockerClient->removeContainer($name, $remove);
$out = ($retval === true) ? "Successfully removed container '$name'" : "Error: ".$retval;
echo "<script>stop_Wait($waitID);addLog('<b>".addslashes(htmlspecialchars($out))."</b>');</script>\n";
@flush();
}
@@ -70,14 +64,11 @@ function removeContainer($name) {
function removeImage($image) {
global $DockerClient;
$waitID = mt_rand();
echo "<p class=\"logLine\" id=\"logBody\"></p>";
echo "<script>addLog('<fieldset style=\"margin-top:1px;\" class=\"CMD\"><legend>Removing orphan image: ".addslashes(htmlspecialchars($image))."</legend><p class=\"logLine\" id=\"logBody\"></p><span id=\"wait{$waitID}\">Please wait </span></fieldset>');show_Wait($waitID);</script>\n";
@flush();
$retval = $DockerClient->removeImage($image);
$out = ($retval === true) ? "Successfully removed image '$image'" : "Error: ".$retval;
echo "<script>stop_Wait($waitID);addLog('<b>".addslashes(htmlspecialchars($out))."</b>');</script>\n";
@flush();
}
@@ -86,32 +77,25 @@ function pullImage($name, $image) {
global $DockerClient, $DockerTemplates, $DockerUpdate;
$waitID = mt_rand();
if (!preg_match("/:\S+$/", $image)) $image .= ":latest";
echo "<p class=\"logLine\" id=\"logBody\"></p>";
echo "<script>addLog('<fieldset style=\"margin-top:1px;\" class=\"CMD\"><legend>Pulling image: ".addslashes(htmlspecialchars($image))."</legend><p class=\"logLine\" id=\"logBody\"></p><span id=\"wait{$waitID}\">Please wait </span></fieldset>');show_Wait($waitID);</script>\n";
@flush();
$alltotals = [];
$laststatus = [];
$strError = '';
$DockerClient->pullImage($image, function ($line) use (&$alltotals, &$laststatus, &$waitID, &$strError, $image, $DockerClient, $DockerUpdate) {
$cnt = json_decode($line, true);
$id = (isset($cnt['id'])) ? trim($cnt['id']) : '';
$status = (isset($cnt['status'])) ? trim($cnt['status']) : '';
if (isset($cnt['error'])) {
$strError = $cnt['error'];
}
if ($waitID !== false) {
echo "<script>stop_Wait($waitID);</script>\n";
@flush();
$waitID = false;
}
if (empty($status)) return;
if (!empty($id)) {
if (!empty($cnt['progressDetail']) && !empty($cnt['progressDetail']['total'])) {
$alltotals[$id] = $cnt['progressDetail']['total'];
@@ -119,13 +103,10 @@ function pullImage($name, $image) {
if (empty($laststatus[$id])) {
$laststatus[$id] = '';
}
switch ($status) {
case 'Waiting':
// Omit
break;
case 'Downloading':
if ($laststatus[$id] != $status) {
echo "<script>addToID('${id}','".addslashes(htmlspecialchars($status))."');</script>\n";
@@ -137,12 +118,11 @@ function pullImage($name, $image) {
echo "<script>progress('${id}',' ".$percentage."% of ".$DockerClient->formatBytes($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
// just show the current download progress without the percentage
$alltotals[$id] = $current;
echo "<script>progress('${id}',' ".$DockerClient->formatBytes($current)."');</script>\n";
}
break;
default:
if ($laststatus[$id] == "Downloading") {
echo "<script>progress('${id}',' 100% of ".$DockerClient->formatBytes($alltotals[$id])."');</script>\n";
@@ -152,9 +132,7 @@ function pullImage($name, $image) {
}
break;
}
$laststatus[$id] = $status;
} else {
if (strpos($status, 'Status: ') === 0) {
echo "<script>addLog('".addslashes(htmlspecialchars($status))."');</script>\n";
@@ -165,37 +143,35 @@ function pullImage($name, $image) {
}
@flush();
});
echo "<script>addLog('<br><b>TOTAL DATA PULLED:</b> " . $DockerClient->formatBytes(array_sum($alltotals)) . "');</script>\n";
@flush();
if (!empty($strError)) {
echo "<script>addLog('<br><span class=\"error\"><b>Error:</b> ".addslashes(htmlspecialchars($strError))."</span>');</script>\n";
@flush();
return false;
}
return true;
}
function xml_encode($string) {
return htmlspecialchars($string, ENT_XML1, 'UTF-8');
}
function xml_decode($string) {
return strval(html_entity_decode($string, ENT_XML1, 'UTF-8'));
}
function postToXML($post, $setOwnership = false) {
function postToXML($post, $setOwnership=false) {
$dom = new domDocument;
$dom->appendChild($dom->createElement("Container"));
$xml = simplexml_import_dom($dom);
$xml["version"] = 2;
$xml['version'] = 2;
$xml->Name = xml_encode(preg_replace('/\s+/', '', $post['contName']));
$xml->Repository = xml_encode(trim($post['contRepository']));
$xml->Registry = xml_encode(trim($post['contRegistry']));
$xml->Network = xml_encode($post['contNetwork']);
$xml->MyIP = xml_encode($post['contMyIP']);
$xml->Privileged = (strtolower($post["contPrivileged"]) == 'on') ? 'true' : 'false';
$xml->Privileged = strtolower($post['contPrivileged'])=='on' ? 'true' : 'false';
$xml->Support = xml_encode($post['contSupport']);
$xml->Project = xml_encode($post['contProject']);
$xml->Overview = xml_encode($post['contOverview']);
@@ -212,13 +188,13 @@ function postToXML($post, $setOwnership = false) {
$xml->MinVer = xml_encode($post['contMinVer']);
# V1 compatibility
$xml->Description = xml_encode($post['contOverview']);
$xml->Networking->Mode = xml_encode($post['contNetwork']);
$xml->Description = xml_encode($post['contOverview']);
$xml->Networking->Mode = xml_encode($post['contNetwork']);
$xml->Networking->addChild("Publish");
$xml->addChild("Data");
$xml->addChild("Environment");
for ($i = 0; $i < count($post["confName"]); $i++) {
for ($i = 0; $i < count($post['confName']); $i++) {
$Type = $post['confType'][$i];
$config = $xml->addChild('Config', xml_encode($post['confValue'][$i]));
$config['Name'] = xml_encode($post['confName'][$i]);
@@ -308,13 +284,11 @@ function xmlToVar($xml) {
}
# check if network exists
if (!key_exists($out['Network'],$subnet)) $out['Network'] = 'none';
# V1 compatibility
if ($xml["version"] != "2") {
if ($xml['version'] != '2') {
if (isset($xml->Description)) {
$out['Overview'] = stripslashes(xml_decode($xml->Description));
}
if (isset($xml->Networking->Publish->Port)) {
$portNum = 0;
foreach ($xml->Networking->Publish->Port as $port) {
@@ -457,13 +431,11 @@ function xmlToCommand($xml, $create_paths=false) {
function execCommand($command) {
// $command should have all its args already properly run through 'escapeshellarg'
$descriptorspec = [
0 => ["pipe", "r"], // stdin is a pipe that the child will read from
1 => ["pipe", "w"], // stdout is a pipe that the child will write to
2 => ["pipe", "w"] // stderr is a pipe that the child will write to
0 => ['pipe', 'r'], // stdin is a pipe that the child will read from
1 => ['pipe', 'w'], // stdout is a pipe that the child will write to
2 => ['pipe', 'w'] // stderr is a pipe that the child will write to
];
$id = mt_rand();
echo '<p class="logLine" id="logBody"></p>';
echo '<script>addLog(\'<fieldset style="margin-top:1px;" class="CMD"><legend>Command:</legend>';
@@ -484,13 +456,13 @@ function execCommand($command) {
return $retval===0;
}
function getXmlVal($xml, $element, $attr = null, $pos = 0) {
function getXmlVal($xml, $element, $attr=null, $pos=0) {
$xml = (is_file($xml)) ? simplexml_load_file($xml) : simplexml_load_string($xml);
$element = $xml->xpath("//$element")[$pos];
return isset($element) ? (isset($element[$attr]) ? strval($element[$attr]) : strval($element)) : "";
}
function setXmlVal(&$xml, $value, $el, $attr = null, $pos = 0) {
function setXmlVal(&$xml, $value, $el, $attr=null, $pos=0) {
global $echo;
$xml = (is_file($xml)) ? simplexml_load_file($xml) : simplexml_load_string($xml);
$element = $xml->xpath("//$el")[$pos];
@@ -507,34 +479,25 @@ function setXmlVal(&$xml, $value, $el, $attr = null, $pos = 0) {
$xml = $dom->saveXML();
}
function getUsedPorts() {
global $names;
$ports = [];
function getAllocations() {
global $eth0;
$names = $docker = $ports = [];
docker("ps --format='{{.Names}}'", $names);
docker("inspect --format='{{.HostConfig.NetworkMode}}#{{range .NetworkSettings.Networks}}{{.IPAddress}}{{end}}#{{range \$c := .HostConfig.PortBindings}}{{(index \$c 0).HostPort}}|{{end}}#{{range \$p,\$c := .Config.ExposedPorts}}{{\$p}}|{{end}}' ".implode(' ',$names),$docker); $n = 0;
natcasesort($names);
foreach ($names as $name) {
$list = [];
$list['Name'] = $name;
$port = explode('|',docker("inspect --format='{{range \$c := .HostConfig.PortBindings}}{{(index \$c 0).HostPort}}|{{end}}' $name"));
if (count($port)<2) $port = explode('|',str_replace(['/tcp','/udp'],'',docker("inspect --format='{{range \$p,\$c := .Config.ExposedPorts}}{{\$p}}|{{end}}' $name")));
natsort($port);
$list['Port'] = implode(' ',array_unique($port));
[$mode,$ip,$port1,$port2] = explode('#',$docker[$n++]);
$port = explode('|',$port1);
if (count($port)<2) $port = explode('|',str_replace(['/tcp','/udp'],'',$port2));
sort($port);
$list['Port'] = (strstr('host,bridge',$mode) ? $eth0['IPADDR:0'] : $ip).' : '.(implode(' ',array_unique($port)) ?: '???')." -- $mode";
$ports[] = $list;
}
return $ports;
}
function getUsedIPs() {
global $names, $eth0;
$ips = [];
foreach ($names as $name) {
$list = [];
$list['Name'] = $name;
$mode = docker("inspect --format='{{lower .HostConfig.NetworkMode}}' $name");
$list['ip'] = (docker("inspect --format='{{range .NetworkSettings.Networks}}{{.IPAddress}}{{end}}' $name") ?: $eth0['IPADDR:0'])." ($mode)";
$ips[] = $list;
}
return $ips;
}
# ██████╗ ██████╗ ██████╗ ███████╗
# ██╔════╝██╔═══██╗██╔══██╗██╔════╝
# ██║ ██║ ██║██║ ██║█████╗
@@ -542,21 +505,18 @@ function getUsedIPs() {
# ╚██████╗╚██████╔╝██████╔╝███████╗
# ╚═════╝ ╚═════╝ ╚═════╝ ╚══════╝
##
## CREATE CONTAINER
##
##########################
## CREATE CONTAINER ##
##########################
if (isset($_POST['contName'])) {
$postXML = postToXML($_POST, true);
$dry_run = $_POST['dryRun']=='true' ? true : false;
$existing = $_POST['existingContainer'] ?? false;
$create_paths = $dry_run ? false : true;
// Get the command line
list($cmd, $Name, $Repository) = xmlToCommand($postXML, $create_paths);
readfile("$docroot/plugins/dynamix.docker.manager/log.htm");
@flush();
// Saving the generated configuration file.
$userTmplDir = $dockerManPaths['templates-user'];
if (!is_dir($userTmplDir)) mkdir($userTmplDir, 0777, true);
@@ -564,7 +524,6 @@ if (isset($_POST['contName'])) {
$filename = sprintf('%s/my-%s.xml', $userTmplDir, $Name);
file_put_contents($filename, $postXML);
}
// Run dry
if ($dry_run) {
echo "<h2>XML</h2>";
@@ -575,7 +534,6 @@ if (isset($_POST['contName'])) {
echo "<button type='button' onclick='done()'>Done</button></div><br>";
goto END;
}
// Will only pull image if it's absent
if (!$DockerClient->doesImageExist($Repository)) {
// Pull image
@@ -584,9 +542,7 @@ if (isset($_POST['contName'])) {
goto END;
}
}
$startContainer = true;
// Remove existing container
if ($DockerClient->doesContainerExist($Name)) {
// attempt graceful stop of container first
@@ -595,11 +551,9 @@ if (isset($_POST['contName'])) {
// attempt graceful stop of container first
stopContainer($Name);
}
// force kill container if still running after 10 seconds
removeContainer($Name);
}
// Remove old container if renamed
if ($existing && $DockerClient->doesContainerExist($existing)) {
// determine if the container is still running
@@ -611,30 +565,25 @@ if (isset($_POST['contName'])) {
// old container was stopped already, ensure newly created container doesn't start up automatically
$startContainer = false;
}
// force kill container if still running after 10 seconds
removeContainer($existing);
removeContainer($existing,1);
// remove old template
@unlink("$userTmplDir/my-$existing.xml");
}
if ($startContainer) {
$cmd = str_replace('/plugins/dynamix.docker.manager/scripts/docker create ', '/plugins/dynamix.docker.manager/scripts/docker run -d ', $cmd);
}
execCommand($cmd);
echo '<div style="text-align:center"><button type="button" onclick="done()">Done</button></div><br>';
goto END;
}
##
## UPDATE CONTAINER
##
##########################
## UPDATE CONTAINER ##
##########################
if ($_GET['updateContainer']){
readfile("$docroot/plugins/dynamix.docker.manager/log.htm");
@flush();
foreach ($_GET['ct'] as $value) {
$tmpl = $DockerTemplates->getUserTemplate(urldecode($value));
if (!$tmpl) {
@@ -642,70 +591,55 @@ if ($_GET['updateContainer']){
@flush();
continue;
}
$xml = file_get_contents($tmpl);
list($cmd, $Name, $Repository) = xmlToCommand($tmpl);
$Registry = getXmlVal($xml, "Registry");
$oldImageID = $DockerClient->getImageID($Repository);
// Pull image
if (!pullImage($Name, $Repository)) {
continue;
}
if (!pullImage($Name, $Repository)) continue;
$oldContainerDetails = $DockerClient->getContainerDetails($Name);
// determine if the container is still running
if (!empty($oldContainerDetails) && !empty($oldContainerDetails['State']) && !empty($oldContainerDetails['State']['Running'])) {
// since container was already running, put it back it to a running state after update
$cmd = str_replace('/plugins/dynamix.docker.manager/scripts/docker create ', '/plugins/dynamix.docker.manager/scripts/docker run -d ', $cmd);
// attempt graceful stop of container first
stopContainer($Name);
}
// force kill container if still running after 10 seconds
removeContainer($Name);
execCommand($cmd);
$DockerClient->flushCaches();
$newImageID = $DockerClient->getImageID($Repository);
if ($oldImageID && $oldImageID != $newImageID) {
// remove old orphan image since it's no longer used by this container
removeImage($oldImageID);
}
}
echo '<div style="text-align:center"><button type="button" onclick="window.parent.jQuery(\'#iframe-popup\').dialog(\'close\');">Done</button></div><br>';
goto END;
}
##
## REMOVE TEMPLATE
##
#########################
## REMOVE TEMPLATE ##
#########################
if ($_GET['rmTemplate']) {
unlink($_GET['rmTemplate']);
}
##
## LOAD TEMPLATE
##
#########################
## LOAD TEMPLATE ##
#########################
if ($_GET['xmlTemplate']) {
list($xmlType, $xmlTemplate) = explode(':', urldecode($_GET['xmlTemplate']));
if (is_file($xmlTemplate)) {
$xml = xmlToVar($xmlTemplate);
$templateName = $xml["Name"];
$templateName = $xml['Name'];
if ($xmlType == "default") {
if (!empty($dockercfg["DOCKER_APP_CONFIG_PATH"]) && file_exists($dockercfg["DOCKER_APP_CONFIG_PATH"])) {
if (!empty($dockercfg['DOCKER_APP_CONFIG_PATH']) && file_exists($dockercfg['DOCKER_APP_CONFIG_PATH'])) {
// override /config
foreach ($xml['Config'] as &$arrConfig) {
if ($arrConfig['Type'] == 'Path' && strtolower($arrConfig['Target']) == '/config') {
$arrConfig['Default'] = $arrConfig['Value'] = realpath($dockercfg["DOCKER_APP_CONFIG_PATH"]).'/'.$xml["Name"];
$arrConfig['Default'] = $arrConfig['Value'] = realpath($dockercfg['DOCKER_APP_CONFIG_PATH']).'/'.$xml['Name'];
if (empty($arrConfig['Display']) || preg_match("/^Host Path\s\d/", $arrConfig['Name'])) {
$arrConfig['Display'] = 'advanced-hide';
}
@@ -715,12 +649,12 @@ if ($_GET['xmlTemplate']) {
}
}
}
if (!empty($dockercfg["DOCKER_APP_UNRAID_PATH"]) && file_exists($dockercfg["DOCKER_APP_UNRAID_PATH"])) {
if (!empty($dockercfg['DOCKER_APP_UNRAID_PATH']) && file_exists($dockercfg['DOCKER_APP_UNRAID_PATH'])) {
// override /unraid
$boolFound = false;
foreach ($xml['Config'] as &$arrConfig) {
if ($arrConfig['Type'] == 'Path' && strtolower($arrConfig['Target']) == '/unraid') {
$arrConfig['Default'] = $arrConfig['Value'] = realpath($dockercfg["DOCKER_APP_UNRAID_PATH"]);
$arrConfig['Default'] = $arrConfig['Value'] = realpath($dockercfg['DOCKER_APP_UNRAID_PATH']);
$arrConfig['Display'] = 'hidden';
$arrConfig['Name'] = 'unRAID Share Path';
$boolFound = true;
@@ -730,8 +664,8 @@ if ($_GET['xmlTemplate']) {
$xml['Config'][] = [
'Name' => 'unRAID Share Path',
'Target' => '/unraid',
'Default' => realpath($dockercfg["DOCKER_APP_UNRAID_PATH"]),
'Value' => realpath($dockercfg["DOCKER_APP_UNRAID_PATH"]),
'Default' => realpath($dockercfg['DOCKER_APP_UNRAID_PATH']),
'Value' => realpath($dockercfg['DOCKER_APP_UNRAID_PATH']),
'Mode' => 'rw',
'Description' => '',
'Type' => 'Path',
@@ -746,10 +680,8 @@ if ($_GET['xmlTemplate']) {
echo "<script>var Settings=".json_encode($xml).";</script>";
}
}
$names=[]; docker("ps --format='{{.Names}}'", $names);
echo "<script>var UsedPorts=".json_encode(getUsedPorts()).";</script>";
echo "<script>var UsedIPs=".json_encode(getUsedIPs()).";</script>";
$authoringMode = $dockercfg["DOCKER_AUTHORING_MODE"] == "yes" ? true : false;
echo "<script>var Allocations=".json_encode(getAllocations()).";</script>";
$authoringMode = $dockercfg['DOCKER_AUTHORING_MODE'] == "yes" ? true : false;
$authoring = $authoringMode ? 'advanced' : 'noshow';
$disableEdit = $authoringMode ? 'false' : 'true';
$showAdditionalInfo = '';
@@ -869,20 +801,11 @@ optgroup.title{background-color:#625D5D;color:#FFFFFF;text-align:center;margin-t
return newConfig.prop('outerHTML');
}
function makeUsedPorts(container,current) {
function makeAllocations(container,current) {
var html = [];
for (var i=0; i < container.length; i++) {
var highlight = container[i].Name.toLowerCase()==current.toLowerCase() ? "font-weight:bold" : "";
html.push($("#templateUsedPorts").html().format(highlight,container[i].Name,container[i].Port));
}
return html.join('');
}
function makeUsedIPs(container,current) {
var html = [];
for (var i=0; i < container.length; i++) {
var highlight = container[i].Name.toLowerCase()==current.toLowerCase() ? "font-weight:bold" : "";
html.push($("#templateUsedIPs").html().format(highlight,container[i].Name,container[i].ip));
for (var i=0,ct; ct=container[i]; i++) {
var highlight = ct.Name.toLowerCase()==current.toLowerCase() ? "font-weight:bold" : "";
html.push($("#templateAllocations").html().format(highlight,ct.Name,ct.Port));
}
return html.join('');
}
@@ -1528,17 +1451,10 @@ optgroup.title{background-color:#625D5D;color:#FFFFFF;text-align:center;margin-t
<table class="settings wide">
<tr>
<td></td>
<td id="portsused_toggle" class="readmore_collapsed"><a onclick="togglePortsUsed()" style="cursor:pointer"><i class="fa fa-chevron-down"></i> Show exposed host ports ...</a></td>
<td id="allocations_toggle" class="readmore_collapsed"><a onclick="toggleAllocations()" style="cursor:pointer"><i class="fa fa-chevron-down"></i> Show docker allocations ...</a></td>
</tr>
</table>
<div id="configLocationPorts" style="display:none"></div><br>
<table class="settings wide">
<tr>
<td></td>
<td id="ipsused_toggle" class="readmore_collapsed"><a onclick="toggleIPsUsed()" style="cursor:pointer"><i class="fa fa-chevron-down"></i> Show assigned IP addresses ...</a></td>
</tr>
</table>
<div id="configLocationIPs" style="display:none"></div><br>
<div id="dockerAllocations" style="display:none"></div><br>
<table class="settings wide">
<tr>
<td></td>
@@ -1648,13 +1564,7 @@ optgroup.title{background-color:#625D5D;color:#FFFFFF;text-align:center;margin-t
</table>
</div>
<div id="templateUsedPorts" style="display:none">
<table class='settings wide'>
<tr><td></td><td style="{0}"><span style="width:160px;display:inline-block;padding-left:20px">{1}</span>{2}</td></tr>
</table>
</div>
<div id="templateUsedIPs" style="display:none">
<div id="templateAllocations" style="display:none">
<table class='settings wide'>
<tr><td></td><td style="{0}"><span style="width:160px;display:inline-block;padding-left:20px">{1}</span>{2}</td></tr>
</table>
@@ -1692,28 +1602,16 @@ optgroup.title{background-color:#625D5D;color:#FFFFFF;text-align:center;margin-t
readm.find('a').html('<i class="fa fa-chevron-down"></i> Show more settings ...');
}
}
function togglePortsUsed() {
var readm = $('#portsused_toggle');
function toggleAllocations() {
var readm = $('#allocations_toggle');
if ( readm.hasClass('readmore_collapsed') ) {
readm.removeClass('readmore_collapsed').addClass('readmore_expanded');
$('#configLocationPorts').slideDown('fast');
readm.find('a').html('<i class="fa fa-chevron-up"></i> Hide exposed host ports ...');
$('#dockerAllocations').slideDown('fast');
readm.find('a').html('<i class="fa fa-chevron-up"></i> Hide docker allocations ...');
} else {
$('#configLocationPorts').slideUp('fast');
$('#dockerAllocations').slideUp('fast');
readm.removeClass('readmore_expanded').addClass('readmore_collapsed');
readm.find('a').html('<i class="fa fa-chevron-down"></i> Show exposed host ports ...');
}
}
function toggleIPsUsed() {
var readm = $('#ipsused_toggle');
if ( readm.hasClass('readmore_collapsed') ) {
readm.removeClass('readmore_collapsed').addClass('readmore_expanded');
$('#configLocationIPs').slideDown('fast');
readm.find('a').html('<i class="fa fa-chevron-up"></i> Hide assigned IP addresses ...');
} else {
$('#configLocationIPs').slideUp('fast');
readm.removeClass('readmore_expanded').addClass('readmore_collapsed');
readm.find('a').html('<i class="fa fa-chevron-down"></i> Show assigned IP addresses ...');
readm.find('a').html('<i class="fa fa-chevron-down"></i> Show docker allocations ...');
}
}
function load_contOverview() {
@@ -1745,12 +1643,10 @@ optgroup.title{background-color:#625D5D;color:#FFFFFF;text-align:center;margin-t
for (var i = 0; i < categories.length; i++) {
$("#catSelect option[value='"+categories[i]+"']").prop("selected", true);
}
// Remove empty description
if (!Settings.Description.length) {
$('#canvas').find('#Overview:first').hide();
}
// Load config info
var network = $('select[name="contNetwork"]')[0].selectedIndex;
for (var i = 0; i < Settings.Config.length; i++) {
@@ -1774,22 +1670,14 @@ optgroup.title{background-color:#625D5D;color:#FFFFFF;text-align:center;margin-t
} else {
$('#canvas').find('#Overview:first').hide();
}
// Show associated subnet with fixed IP (if existing)
showSubnet($('select[name="contNetwork"]').val());
// Add list of exposed host ports
$("#configLocationPorts").html(makeUsedPorts(UsedPorts,$('input[name="contName"]').val()));
// Add list of assigned IP addresses
$("#configLocationIPs").html(makeUsedIPs(UsedIPs,$('input[name="contName"]').val()));
// Add list of docker allocations
$("#dockerAllocations").html(makeAllocations(Allocations,$('input[name="contName"]').val()));
// Add switchButton
$('.switch-on-off').each(function(){var checked = $(this).is(":checked");$(this).switchButton({labels_placement: "right", checked:checked});});
// Add dropdownchecklist to Select Categories
$("#catSelect").dropdownchecklist({emptyText:'Select categories...', maxDropHeight:200, width:300, explicitClose:'...close'});
<?if ($authoringMode){
echo "$('.advanced-switch').prop('checked','true'); $('.advanced-switch').change();";
echo "$('.advanced-switch').siblings('.switch-button-background').click();";
@@ -1800,4 +1688,3 @@ optgroup.title{background-color:#625D5D;color:#FFFFFF;text-align:center;margin-t
}
</script>
<?END:?>
@@ -15,33 +15,35 @@
$docroot = $docroot ?? $_SERVER['DOCUMENT_ROOT'] ?: '/usr/local/emhttp';
$dockerManPaths = [
'plugin' => '/usr/local/emhttp/plugins/dynamix.docker.manager',
'autostart-file' => '/var/lib/docker/unraid-autostart',
'update-status' => '/var/lib/docker/unraid-update-status.json',
'template-repos' => '/boot/config/plugins/dockerMan/template-repos',
'templates-user' => '/boot/config/plugins/dockerMan/templates-user',
'templates-storage' => '/boot/config/plugins/dockerMan/templates',
'user-prefs' => '/boot/config/plugins/dockerMan/userprefs.cfg',
'images-ram' => '/usr/local/emhttp/state/plugins/dynamix.docker.manager/images',
'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'
'user-prefs' => '/boot/config/plugins/dockerMan/userprefs.cfg',
'plugin' => '/usr/local/emhttp/plugins/dynamix.docker.manager',
'images-ram' => '/usr/local/emhttp/state/plugins/dynamix.docker.manager/images',
'webui-info' => '/usr/local/emhttp/state/plugins/dynamix.docker.manager/docker.json'
];
# load network variables if needed.
if (!isset($eth0) && is_file("$docroot/state/network.ini")) extract(parse_ini_file("$docroot/state/network.ini",true));
if (!isset($eth0) && is_file("$docroot/state/network.ini")) {
extract(parse_ini_file("$docroot/state/network.ini",true));
}
# controlled docker execution
function docker($cmd, &$var=null) {
return exec("timeout 20 /usr/bin/docker $cmd 2>/dev/null",$var);
return exec("docker $cmd 2>/dev/null", $var);
}
# Docker configuration file - guaranteed to exist
$docker_cfgfile = '/boot/config/docker.cfg';
$dockercfg = parse_ini_file($docker_cfgfile);
######################################
## DOCKERTEMPLATES CLASS ##
######################################
#######################################
## DOCKERTEMPLATES CLASS ##
#######################################
class DockerTemplates {
@@ -51,12 +53,12 @@ class DockerTemplates {
if ($this->verbose) echo $m."\n";
}
public function download_url($url, $path = '', $bg = 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;
return $exit_code===0 ? implode("\n", $out) : false;
}
public function listDir($root, $ext = null) {
public function listDir($root, $ext=null) {
$iter = new RecursiveIteratorIterator(
new RecursiveDirectoryIterator($root,
RecursiveDirectoryIterator::SKIP_DOTS),
@@ -65,7 +67,7 @@ class DockerTemplates {
$paths = [];
foreach ($iter as $path => $fileinfo) {
$fext = $fileinfo->getExtension();
if ($ext && ($ext != $fext)) continue;
if ($ext && $ext != $fext) continue;
if ($fileinfo->isFile()) $paths[] = ['path' => $path, 'prefix' => basename(dirname($path)), 'name' => $fileinfo->getBasename(".$fext")];
}
return $paths;
@@ -74,14 +76,18 @@ class DockerTemplates {
public function getTemplates($type) {
global $dockerManPaths;
$tmpls = $dirs = [];
if ($type == 'all') {
switch ($type) {
case 'all':
$dirs[] = $dockerManPaths['templates-user'];
$dirs[] = $dockerManPaths['templates-storage'];
} elseif ($type == 'user') {
break;
case 'user':
$dirs[] = $dockerManPaths['templates-user'];
} elseif ($type == 'default') {
break;
case 'default':
$dirs[] = $dockerManPaths['templates-storage'];
} else {
break;
default:
$dirs[] = $type;
}
foreach ($dirs as $dir) {
@@ -98,18 +104,15 @@ class DockerTemplates {
$this->removeDir(realpath($path).'/'.$file);
}
return rmdir($path);
} elseif (is_file($path)) {
return unlink($path);
}
} elseif (is_file($path)) return unlink($path);
return false;
}
public function downloadTemplates($Dest = null, $Urls = null) {
public function downloadTemplates($Dest=null, $Urls=null) {
global $dockerManPaths;
$Dest = $Dest ?: $dockerManPaths['templates-storage'];
$Urls = $Urls ?: $dockerManPaths['template-repos'];
$repotemplates = [];
$output = [];
$repotemplates = $output = [];
$tmp_dir = '/tmp/tmp-'.mt_rand();
if (!file_exists($dockerManPaths['template-repos'])) {
@mkdir(dirname($dockerManPaths['template-repos']), 0777, true);
@@ -212,12 +215,11 @@ class DockerTemplates {
return $output;
}
public function getTemplateValue($Repository, $field, $scope = 'all') {
public function getTemplateValue($Repository, $field, $scope='all') {
foreach ($this->getTemplates($scope) as $file) {
$doc = new DOMDocument();
$doc->load($file['path']);
$TemplateRepository = DockerUtil::ensureImageTag($doc->getElementsByTagName('Repository')->item(0)->nodeValue);
if ($Repository == $TemplateRepository) {
$TemplateField = $doc->getElementsByTagName($field)->item(0)->nodeValue;
return trim($TemplateField);
@@ -231,7 +233,7 @@ class DockerTemplates {
$doc = new DOMDocument('1.0', 'utf-8');
$doc->load($file['path']);
$Name = $doc->getElementsByTagName('Name')->item(0)->nodeValue;
if ($Name == $Container) return $file['path'];
if ($Name==$Container) return $file['path'];
}
return false;
}
@@ -260,9 +262,7 @@ class DockerTemplates {
$ConfigPort = $matches[1];
if ($ct['NetworkMode'] == 'bridge') {
foreach ($Ports as $key) {
if ($key['PrivatePort'] == $ConfigPort) {
$ConfigPort = $key['PublicPort'];
}
if ($key['PrivatePort'] == $ConfigPort) $ConfigPort = $key['PublicPort'];
}
}
$WebUI = preg_replace("%\[PORT:\d+\]%", $ConfigPort, $WebUI);
@@ -272,7 +272,6 @@ class DockerTemplates {
public function removeContainerInfo($container) {
global $dockerManPaths;
$info = DockerUtil::loadJSON($dockerManPaths['webui-info']);
if (isset($info[$container])) {
unset($info[$container]);
@@ -283,7 +282,6 @@ class DockerTemplates {
public function removeImageInfo($image) {
global $dockerManPaths;
$image = DockerUtil::ensureImageTag($image);
$updateStatus = DockerUtil::loadJSON($dockerManPaths['update-status']);
if (isset($updateStatus[$image])) {
unset($updateStatus[$image]);
@@ -291,14 +289,13 @@ class DockerTemplates {
}
}
public function getAllInfo($reload = false) {
public function getAllInfo($reload=false) {
global $dockerManPaths;
$DockerClient = new DockerClient();
$DockerUpdate = new DockerUpdate();
$DockerUpdate->verbose = $this->verbose;
//$DockerUpdate->verbose = $this->verbose;
$info = DockerUtil::loadJSON($dockerManPaths['webui-info']);
$allAutoStart = @file($dockerManPaths['autostart-file'], FILE_IGNORE_NEW_LINES) ?: [];
foreach ($DockerClient->getDockerContainers() as $ct) {
$name = $ct['Name'];
$image = $ct['Image'];
@@ -306,18 +303,17 @@ class DockerTemplates {
$tmp['running'] = $ct['Running'];
$tmp['autostart'] = in_array($name, $allAutoStart);
if (!$tmp['icon'] || $reload) $tmp['icon'] = $this->getIcon($image) ?: null;
$tmp['url'] = $this->getControlURL($name) ?: null;
$tmp['registry'] = $this->getTemplateValue($image, 'Registry') ?: null;
$tmp['Support'] = $this->getTemplateValue($image, 'Support') ?: null;
$tmp['Project'] = $this->getTemplateValue($image, 'Project') ?: null;
$tmp['url'] = $tmp['url'] ?? $this->getControlURL($name) ?: null;
$tmp['registry'] = $tmp['registry'] ?? $this->getTemplateValue($image, 'Registry') ?: null;
$tmp['Support'] = $tmp['Support'] ?? $this->getTemplateValue($image, 'Support') ?: null;
$tmp['Project'] = $tmp['Project'] ?? $this->getTemplateValue($image, 'Project') ?: null;
if (!$tmp['updated'] || $reload) {
if ($reload) $DockerUpdate->reloadUpdateStatus($image);
$vs = $DockerUpdate->getUpdateStatus($image);
$tmp['updated'] = ($vs === null) ? null : (($vs === true) ? 'true' : 'false');
$tmp['updated'] = $vs===null ? null : ($vs===true ? 'true' : 'false');
}
if (!$tmp['template'] || $reload) $tmp['template'] = $this->getUserTemplate($name);
if ($reload) $DockerUpdate->updateUserTemplate($name);
//$this->debug("\n$name");
//foreach ($tmp as $c => $d) $this->debug(sprintf(' %-10s: %s', $c, $d));
}
@@ -327,13 +323,11 @@ class DockerTemplates {
public function getIcon($Repository) {
global $docroot, $dockerManPaths;
$imgUrl = $this->getTemplateValue($Repository, 'Icon');
preg_match_all("/(.*?):([\w]*$)/i", $Repository, $matches);
$name = preg_replace("%\/|\\\%", '-', $matches[1][0]);
$version = $matches[2][0];
$tempPath = sprintf('%s/%s-%s-%s.png', $dockerManPaths['images-ram'], $name, $version, 'icon');
$tempPath = sprintf('%s/%s-%s-%s.png', $dockerManPaths['images-ram'], $name, $version, 'icon');
$storagePath = sprintf('%s/%s-%s-%s.png', $dockerManPaths['images-storage'], $name, $version, 'icon');
if (!is_dir(dirname($tempPath))) @mkdir(dirname($tempPath), 0755, true);
if (!is_dir(dirname($storagePath))) @mkdir(dirname($storagePath), 0755, true);
@@ -345,9 +339,9 @@ class DockerTemplates {
}
}
######################################
## DOCKERUPDATE CLASS ##
######################################
####################################
## DOCKERUPDATE CLASS ##
####################################
class DockerUpdate{
public $verbose = false;
@@ -363,18 +357,18 @@ class DockerUpdate{
return strval(html_entity_decode($string, ENT_XML1, 'UTF-8'));
}
public function download_url($url, $path = '', $bg = 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;
return ($exit_code===0) ? implode("\n", $out) : false;
}
public function download_url_and_headers($url, $headers = [], $path = '', $bg = false) {
public function download_url_and_headers($url, $headers=[], $path='', $bg=false) {
$strHeaders = '';
foreach ($headers as $header) {
$strHeaders .= ' -H '.escapeshellarg($header);
}
exec('curl --max-time 30 --silent --insecure --location --fail -i '.$strHeaders.($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;
return ($exit_code===0) ? implode("\n", $out) : false;
}
// DEPRECATED: Only used for Docker Index V1 type update checks
@@ -383,12 +377,11 @@ class DockerUpdate{
$apiUrl = sprintf('http://index.docker.io/v1/repositories/%s/tags/%s', $strRepo, $strTag);
//$this->debug("API URL: $apiUrl");
$apiContent = $this->download_url($apiUrl);
return ($apiContent === false) ? null : substr(json_decode($apiContent, true)[0]['id'], 0, 8);
return ($apiContent===false) ? null : substr(json_decode($apiContent, true)[0]['id'], 0, 8);
}
public function getRemoteVersionV2($image) {
list($strRepo, $strTag) = explode(':', DockerUtil::ensureImageTag($image));
// First - get auth token:
// https://auth.docker.io/token?service=registry.docker.io&scope=repository:needo/nzbget:pull
$strAuthURL = sprintf('https://auth.docker.io/token?service=registry.docker.io&scope=repository:%s:pull', $strRepo);
@@ -399,7 +392,6 @@ class DockerUpdate{
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 = sprintf('https://registry-1.docker.io/v2/%s/manifests/%s', $strRepo, $strTag);
@@ -409,7 +401,6 @@ class DockerUpdate{
//$this->debug("Error: Manifest response was empty");
return null;
}
// Look for 'Docker-Content-Digest' header in response:
// Docker-Content-Digest: sha256:2070d781fc5f98f12e752b75cf39d03b7a24b9d298718b1bbb73e67f0443062d
$strDigest = '';
@@ -423,7 +414,6 @@ class DockerUpdate{
//$this->debug("Error: Remote Digest was missing/empty");
return null;
}
//$this->debug("Remote Digest: $strDigest");
return $strDigest;
}
@@ -445,7 +435,7 @@ class DockerUpdate{
return null;
}
public function reloadUpdateStatus($image = null) {
public function reloadUpdateStatus($image=null) {
global $dockerManPaths;
$DockerClient = new DockerClient();
$updateStatus = DockerUtil::loadJSON($dockerManPaths['update-status']);
@@ -456,13 +446,9 @@ class DockerUpdate{
$localVersion = $updateStatus[$img]['local'];
}
$remoteVersion = $this->getRemoteVersionV2($img);
$status = ($localVersion && $remoteVersion) ? (($remoteVersion == $localVersion) ? 'true' : 'false') : 'undef';
$updateStatus[$img] = [
'local' => $localVersion,
'remote' => $remoteVersion,
'status' => $status
];
//$this->debug("Update status: Image='${img}', Local='${localVersion}', Remote='${remoteVersion}', Status='${status}'");
$status = ($localVersion && $remoteVersion) ? (($remoteVersion == $localVersion) ? 'true' : 'false') : 'undef';
$updateStatus[$img] = ['local' => $localVersion, 'remote' => $remoteVersion, 'status' => $status];
//$this->debug("Update status: Image='$img', Local='$localVersion', Remote='$remoteVersion', Status='$status'");
}
DockerUtil::saveJSON($dockerManPaths['update-status'], $updateStatus);
}
@@ -471,12 +457,8 @@ class DockerUpdate{
global $dockerManPaths;
$image = DockerUtil::ensureImageTag($image);
$updateStatus = DockerUtil::loadJSON($dockerManPaths['update-status']);
$updateStatus[$image] = [
'local' => $version,
'remote' => $version,
'status' => 'true'
];
//$this->debug("Update status: Image='${image}', Local='${version}', Remote='${version}', Status='true'");
$updateStatus[$image] = ['local' => $version, 'remote' => $version, 'status' => 'true'];
//$this->debug("Update status: Image='$image', Local='$version', Remote='$version', Status='true'");
DockerUtil::saveJSON($dockerManPaths['update-status'], $updateStatus);
}
@@ -485,7 +467,6 @@ class DockerUpdate{
$DockerTemplates = new DockerTemplates();
$validElements = ['Support', 'Overview', 'Category', 'Project', 'Icon'];
$validAttributes = ['Name', 'Default', 'Description', 'Display', 'Required', 'Mask'];
// Get user template file and abort if fail
if ( ! $file = $DockerTemplates->getUserTemplate($Container) ) {
//$this->debug("User template for container '$Container' not found, aborting.");
@@ -568,31 +549,30 @@ class DockerUpdate{
}
}
######################################
## DOCKERCLIENT CLASS ##
######################################
####################################
## DOCKERCLIENT CLASS ##
####################################
class DockerClient {
private static $allContainersCache = null;
private static $allImagesCache = null;
private static $containersCache = null;
private static $imagesCache = null;
private static $codes = [
'200' => true, // No error
'204' => true, // No error
'304' => 'Container already started',
'400' => 'Bad parameter',
'404' => 'No such container',
'409' => 'Image can not be deleted<br><i>In use by other container(s)</i>',
'500' => 'Server error'
];
private function build_sorter($key) {
return function ($a, $b) use ($key) {
return strnatcasecmp($a[$key], $b[$key]);
};
private function flushCache(&$cache) {
$cache = null;
}
public function humanTiming($time) {
$time = time() - $time; // to get the time since that moment
$tokens = [
31536000 => 'year',
2592000 => 'month',
604800 => 'week',
86400 => 'day',
3600 => 'hour',
60 => 'minute',
1 => 'second'
];
$tokens = [31536000 => 'year', 2592000 => 'month', 604800 => 'week', 86400 => 'day',3600 => 'hour', 60 => 'minute', 1 => 'second'];
foreach ($tokens as $unit => $text) {
if ($time < $unit) continue;
$numberOfUnits = floor($time / $unit);
@@ -607,30 +587,25 @@ class DockerClient {
return round(pow(1024, $base - floor($base)), 0) .' '. $suffix[floor($base)];
}
public function getDockerJSON($url, $method = 'GET', &$code = null, $callback = null, $unchunk = false) {
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;
}
$protocol = $unchunk ? 'HTTP/1.0' : 'HTTP/1.1';
$out = "${method} {$url} ${protocol}\r\nHost:127.0.0.1\r\nConnection:Close\r\n\r\n";
$out = "$method $url $protocol\r\nHost:127.0.0.1\r\nConnection:Close\r\n\r\n";
fwrite($fp, $out);
// Strip headers out
$headers = '';
while (($line = fgets($fp)) !== false) {
if (strpos($line, 'HTTP/1') !== false) {
$code = vsprintf('%2$s',preg_split("#\s+#", $line));
}
if (strpos($line, 'HTTP/1') !== false) $code = vsprintf('%2$s',preg_split("#\s+#", $line));
$headers .= $line;
if (rtrim($line) == '') break;
if (rtrim($line)=='') break;
}
$data = [];
while (($line = fgets($fp)) !== false) {
if (is_array($j = json_decode($line, true))) {
$data = array_merge($data, $j);
}
if (is_array($j = json_decode($line, true))) $data = array_merge($data, $j);
if ($callback) $callback($line);
}
fclose($fp);
@@ -639,14 +614,14 @@ class DockerClient {
function doesContainerExist($container) {
foreach ($this->getDockerContainers() as $ct) {
if ($ct['Name'] == $container) return true;
if ($ct['Name']==$container) return true;
}
return false;
}
function doesImageExist($image) {
foreach ($this->getDockerImages() as $img) {
if (strpos($img['Tags'][0], $image) !== false) return true;
if (strpos($img['Tags'][0], $image)!==false) return true;
}
return false;
}
@@ -657,78 +632,55 @@ class DockerClient {
return array_merge($info, $version);
}
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 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) {
return $this->getDockerJSON("/containers/${id}/json");
return $this->getDockerJSON("/containers/$id/json");
}
public function startContainer($id) {
$this->getDockerJSON("/containers/${id}/start", 'POST', $code);
$this::$allContainersCache = null; // flush cache
$codes = [
'204' => true, // No error
'304' => 'Container already started',
'404' => 'No such container',
'500' => 'Server error'
];
return (array_key_exists($code, $codes)) ? $codes[$code] : 'Error code '.$code;
$this->getDockerJSON("/containers/$id/start", 'POST', $code);
$this->flushCache($this::$containersCache);
return $this::$codes[$code] ?: 'Error code '.$code;
}
public function stopContainer($id) {
$this->getDockerJSON("/containers/${id}/stop?t=10", 'POST', $code);
$this::$allContainersCache = null; // flush cache
$codes = [
'204' => true, // No error
'304' => 'Container already stopped',
'404' => 'No such container',
'500' => 'Server error'
];
return (array_key_exists($code, $codes)) ? $codes[$code] : 'Error code '.$code;
$this->getDockerJSON("/containers/$id/stop?t=10", 'POST', $code);
$this->flushCache($this::$containersCache);
return $this::$codes[$code] ?: 'Error code '.$code;
}
public function restartContainer($id) {
$this->getDockerJSON("/containers/${id}/restart?t=10", 'POST', $code);
$this::$allContainersCache = null; // flush cache
$codes = [
'204' => true, // No error
'404' => 'No such container',
'500' => 'Server error'
];
return (array_key_exists($code, $codes)) ? $codes[$code] : 'Error code '.$code;
$this->getDockerJSON("/containers/$id/restart?t=10", 'POST', $code);
$this->flushCache($this::$containersCache);
return $this::$codes[$code] ?: 'Error code '.$code;
}
public function removeContainer($id) {
public function removeContainer($id, $remove=false) {
global $docroot, $dockerManPaths;
// Purge cached container information
$info = DockerUtil::loadJSON($dockerManPaths['webui-info']);
if (isset($info[$id])) {
if (isset($info[$id]['icon'])) {
if ($remove && isset($info[$id]['icon'])) {
$iconRam = $docroot.$info[$id]['icon'];
$iconFlash = str_replace($dockerManPaths['images-ram'], $dockerManPaths['images-storage'], $iconRam);
if (is_file($iconRam)) unlink($iconRam);
if (is_file($iconFlash)) unlink($iconFlash);
if ($remove>=1 && is_file($iconRam)) unlink($iconRam);
if ($remove==2 && is_file($iconFlash)) unlink($iconFlash);
}
unset($info[$id]);
DockerUtil::saveJSON($dockerManPaths['webui-info'], $info);
}
// Attempt to remove container
$this->getDockerJSON("/containers/${id}?force=1", 'DELETE', $code);
$this::$allContainersCache = null; // flush cache
$codes = [
'204' => true, // No error
'400' => 'Bad parameter',
'404' => 'No such container',
'500' => 'Server error'
];
return (array_key_exists($code, $codes)) ? $codes[$code] : 'Error code '.$code;
$this->getDockerJSON("/containers/$id?force=1", 'DELETE', $code);
$this->flushCache($this::$containersCache);
return $this::$codes[$code] ?: 'Error code '.$code;
}
public function pullImage($image, $callback = null) {
public function pullImage($image, $callback=null) {
$ret = $this->getDockerJSON("/images/create?fromImage=".urlencode($image), 'POST', $code, $callback);
$this::$allImagesCache = null; // flush cache
$this->flushCache($this::$imagesCache);
return $ret;
}
@@ -736,8 +688,8 @@ class DockerClient {
global $dockerManPaths;
$image = $this->getImageName($id);
// Attempt to remove image
$this->getDockerJSON("/images/${id}?force=1", 'DELETE', $code);
$this::$allImagesCache = null; // flush cache
$this->getDockerJSON("/images/$id?force=1", 'DELETE', $code);
$this->flushCache($this::$imagesCache);
if (in_array($code, ['200', '404'])) {
// Purge cached image information (only if delete was successful)
$image = DockerUtil::ensureImageTag($image);
@@ -747,23 +699,17 @@ class DockerClient {
DockerUtil::saveJSON($dockerManPaths['update-status'], $updateStatus);
}
}
$codes = [
'200' => true, // No error
'404' => 'No such image',
'409' => 'Conflict: image used by container(s): '.implode(', ', $this->usedBy($id)),
'500' => 'Server error'
];
return (array_key_exists($code, $codes)) ? $codes[$code] : 'Error code '.$code;
return $this::$codes[$code] ?: 'Error code '.$code;
}
private function getImageDetails($id) {
return $this->getDockerJSON("/images/${id}/json");
return $this->getDockerJSON("/images/$id/json");
}
public function getDockerContainers() {
// Return cached values
if (is_array($this::$allContainersCache)) return $this::$allContainersCache;
$this::$allContainersCache = [];
if (is_array($this::$containersCache)) return $this::$containersCache;
$this::$containersCache = [];
foreach ($this->getDockerJSON("/containers/json?all=1") as $obj) {
$details = $this->getContainerDetails($obj['Id']);
$c = [];
@@ -785,10 +731,10 @@ class DockerClient {
$c['Ports'][] = ['IP' => $value[0]['HostIP'] ?? '0.0.0.0', 'PrivatePort' => $PrivatePort, 'PublicPort' => $value[0]['HostPort'], 'Type' => $Type ];
}
}
$this::$allContainersCache[] = $c;
$this::$containersCache[] = $c;
}
usort($this::$allContainersCache, $this->build_sorter('Name'));
return $this::$allContainersCache;
array_multisort(array_column($this::$containersCache,'Name'), SORT_NATURAL|SORT_FLAG_CASE, $this::$containersCache);
return $this::$containersCache;
}
public function getContainerID($Container) {
@@ -802,7 +748,7 @@ class DockerClient {
if (!strpos($Image,':')) $Image .= ':latest';
foreach ($this->getDockerImages() as $img) {
foreach ($img['Tags'] as $tag) {
if ( $Image == $tag ) return $img['Id'];
if ($Image==$tag) return $img['Id'];
}
}
return null;
@@ -810,7 +756,7 @@ class DockerClient {
public function getImageName($id) {
foreach ($this->getDockerImages() as $img) {
if ($img['Id'] == $id) return $img['Tags'][0];
if ($img['Id']==$id) return $img['Tags'][0];
}
return null;
}
@@ -818,15 +764,15 @@ class DockerClient {
private function usedBy($imageId) {
$out = [];
foreach ($this->getDockerContainers() as $ct) {
if ($ct['ImageId'] == $imageId) $out[] = $ct['Name'];
if ($ct['ImageId']==$imageId) $out[] = $ct['Name'];
}
return $out;
}
public function getDockerImages() {
// Return cached values
if (is_array($this::$allImagesCache)) return $this::$allImagesCache;
$this::$allImagesCache = [];
if (is_array($this::$imagesCache)) return $this::$imagesCache;
$this::$imagesCache = [];
foreach ($this->getDockerJSON('/images/json?all=0') as $obj) {
$c = [];
$c['Created'] = $this->humanTiming($obj['Created']);
@@ -837,20 +783,20 @@ class DockerClient {
$c['Tags'] = array_map('htmlspecialchars', $obj['RepoTags'] ?? []);
$c['Repository'] = vsprintf('%1$s/%2$s', preg_split("#[:\/]#", DockerUtil::ensureImageTag($obj['RepoTags'][0])));
$c['usedBy'] = $this->usedBy($c['Id']);
$this::$allImagesCache[$c['Id']] = $c;
$this::$imagesCache[$c['Id']] = $c;
}
return $this::$allImagesCache;
return $this::$imagesCache;
}
public function flushCaches() {
$this::$allContainersCache = null;
$this::$allImagesCache = null;
$this->flushCache($this::$containersCache);
$this->flushCache($this::$imagesCache);
}
}
######################################
## DOCKERUTIL CLASS ##
######################################
##################################
## DOCKERUTIL CLASS ##
##################################
class DockerUtil {
public static function ensureImageTag($image) {
@@ -868,14 +814,14 @@ class DockerUtil {
}
public static function loadJSON($path) {
$objContent = (is_file($path)) ? json_decode(file_get_contents($path), true) : [];
$objContent = (file_exists($path)) ? json_decode(file_get_contents($path), true) : [];
if (empty($objContent)) $objContent = [];
return $objContent;
}
public static function saveJSON($path, $content) {
if (!is_dir(dirname($path))) @mkdir(dirname($path), 0755, true);
return file_put_contents($path, json_encode($content, JSON_PRETTY_PRINT | JSON_UNESCAPED_SLASHES));
return file_put_contents($path, json_encode($content, JSON_PRETTY_PRINT|JSON_UNESCAPED_SLASHES));
}
}
?>
@@ -31,6 +31,7 @@ if (file_exists($user_prefs)) {
$prefs = parse_ini_file($user_prefs); $sort = [];
foreach ($all_containers as $ct) $sort[] = array_search($ct['Name'],$prefs) ?? 999;
array_multisort($sort,SORT_NUMERIC,$all_containers);
unset($sort);
}
// Read network settings
@@ -38,9 +39,13 @@ extract(parse_ini_file('state/network.ini',true));
// Read container info
$all_info = $DockerTemplates->getAllInfo();
$menu = [];
$docker = ['var docker=[];'];
$menu = $ids = $names = [];
foreach ($all_containers as $ct) $ids[] = $ct['Name'];
docker("inspect --format='{{.State.Running}}#{{range \$p,\$c := .HostConfig.PortBindings}}{{\$p}}:{{(index \$c 0).HostPort}}|{{end}}#{{range \$p,\$c := .Config.ExposedPorts}}{{\$p}}|{{end}}#{{range .NetworkSettings.Networks}}{{.IPAddress}}{{end}}#{{range \$c := .HostConfig.Binds}}{{\$c}}|{{end}}' ".implode(' ',$ids),$names);
unset($ids);
$n = 0;
foreach ($all_containers as $ct) {
$name = $ct['Name'];
$info = &$all_info[$name];
@@ -49,36 +54,34 @@ foreach ($all_containers as $ct) {
$imageID = $ct['ImageId'];
$is_autostart = $info['autostart'] ? 'true':'false';
$updateStatus = $info['updated']=='true'||$info['updated']=='undef' ? 'true':'false';
$running = $ct['Running'] ? 'true':'false';
$template = $info['template'];
$webGui = html_entity_decode($info['url']);
$support = html_entity_decode($info['Support']);
$project = html_entity_decode($info['Project']);
list($running,$bind1,$bind2,$ip,$mounts) = explode('#',$names[$n++]);
$menu[] = sprintf("addDockerContainerContext('%s','%s','%s',%s,%s,%s,'%s','%s','%s','%s');",addslashes($name),addslashes($imageID),addslashes($template),$running,$updateStatus,$is_autostart,addslashes($webGui),$id,addslashes($support),addslashes($project));
$docker[] = "docker.push({name:'$name',id:'$id',state:'$running',update:'$updateStatus'});";
$shape = $ct['Running'] ? 'play':'square';
$status = $ct['Running'] ? 'started':'stopped';
$running = $running=='true';
$shape = $running ? 'play':'square';
$status = $running ? 'started':'stopped';
$icon = $info['icon'] ?: '/plugins/dynamix.docker.manager/images/question.png';
$ports = [];
$binds = explode('|',docker("inspect --format='{{range \$p,\$c := .HostConfig.PortBindings}}{{\$p}}:{{(index \$c 0).HostPort}}|{{end}}' $name"));
if (count($binds)>1) {
$ip = $ct['Running'] ? docker("inspect --format='{{range .NetworkSettings.Networks}}{{.IPAddress}}{{end}}' $name") : '0.0.0.0';
foreach ($binds as $bind) {
if ($bind1) {
$ip = $running ? $ip : '0.0.0.0';
foreach (explode('|',$bind1) as $bind) {
if (!$bind) continue;
list($container_port,$host_port) = explode(':',$bind);
$ports[] = sprintf('%s:%s<i class="fa fa-arrows-h" style="margin:0 6px"></i>%s:%s',$ip, $container_port, $eth0['IPADDR:0'], $host_port);
}
} else {
$binds = explode('|',docker("inspect --format='{{range \$p,\$c := .Config.ExposedPorts}}{{\$p}}|{{end}}' $name"));
$ip = $ct['Running'] ? (docker("inspect --format='{{range .NetworkSettings.Networks}}{{.IPAddress}}{{end}}' $name") ?: $eth0['IPADDR:0']) : '0.0.0.0';
foreach ($binds as $bind) {
} elseif ($bind2) {
$ip = $running ? ($ip ?: $eth0['IPADDR:0']) : '0.0.0.0';
foreach (explode('|',$bind2) as $bind) {
if (!$bind) continue;
$ports[] = sprintf('%s:%s<i class="fa fa-arrows-h" style="margin:0 6px"></i>%s:%s',$ip, $bind, $ip, str_replace(['/tcp','/udp'],'',$bind));
}
}
$paths = [];
$mounts = explode('|',docker("inspect --format='{{range \$c := .HostConfig.Binds}}{{\$c}}|{{end}}' $name"));
foreach ($mounts as $mount) {
foreach (explode('|',$mounts) as $mount) {
if (!$mount) continue;
list($host_path,$container_path,$access_mode) = explode(':',$mount);
$paths[] = sprintf('%s<i class="fa fa-%s" style="margin:0 6px"></i>%s', htmlspecialchars($container_path), $access_mode=='ro'?'long-arrow-left':'arrows-h', htmlspecialchars($host_path));
@@ -40,7 +40,7 @@ switch ($action) {
break;
case 'remove_container':
if ($container) $arrResponse = ['success' => $DockerClient->removeContainer($container)];
if ($container) $arrResponse = ['success' => $DockerClient->removeContainer($container,1)];
break;
case 'remove_image':
@@ -50,7 +50,7 @@ switch ($action) {
case 'remove_all':
if ($container && $image) {
// first: try to remove container
$ret = $DockerClient->removeContainer($container);
$ret = $DockerClient->removeContainer($container,2);
if ($ret === true) {
// next: try to remove image
$arrResponse = ['success' => $DockerClient->removeImage($image)];
@@ -141,7 +141,7 @@ function eventControl(params, spin) {
if (spin) setTimeout(spin+'()',500); else location=window.location.href;
} else {
swal({
title:"Execution error",
title:"Execution error", html:true,
text:data.success, type:"error"
},function(){
if (spin) setTimeout(spin+'()',500); else location=window.location.href;
@@ -166,6 +166,7 @@ function checkAll() {
}
function updateAll() {
$('input[type=button]').prop('disabled',true);
$('div.spinner').show('slow');
var list = '';
for (var i=0,ct; ct=docker[i]; i++) if (ct.update=='false') list += '&ct[]='+ct.name;
var address = "/plugins/dynamix.docker.manager/include/CreateDocker.php?updateContainer=true"+list;
+3 -13
View File
@@ -56,32 +56,22 @@ function loadlist(id) {
} else {
list.html(data[0]);
}
if (data[1]>0) {
$('#updateall').show();
<?if ($check):?>
$('#checkall').hide();
<?endif;?>
} else {
$('#updateall').hide();
<?if ($check):?>
$('#checkall').show();
<?endif;?>
}
<?if ($display['resize']):?>
resize();
$(window).bind('resize',function(){resize(true);});
<?endif;?>
$('#plugin_table').tablesorter({sortList:[[4,0],[1,0]],sortAppend:[[1,0]],headers:{0:{sorter:false},5:{sorter:false}},textAttribute:'data'});
$('.desc_readmore').readmore({maxHeight:66,moreLink:"<a href='#'><i class='fa fa-chevron-down'></i></a>",lessLink:"<a href='#'><i class='fa fa-chevron-up'></i></a>"});
if (data[1]>0) $('#updateall').show(); else $('#updateall').hide();
<?if ($check):?>
$('#checkall').find('input').prop('disabled',false);
$('#checkall').find('input').prop('disabled',false).show();
<?endif;?>
});
}
$(function() {
loadlist();
$('#plugin_tree').fileTree({root:'/boot/',filter:'plg'}, function(file) {$('#plugin_file').val(file);});
$('.tabs').append("<span id='updateall' class='status vhshift' style='display:none'><input type='button' value='Update all Plugins' onclick='$(\"div.spinner\").show(\"slow\");openBox(\"/plugins/dynamix.plugin.manager/scripts/plugin&arg1=updateall\",\"Update All Plugins\",490,430,true,\"loadlist\",\":return\")'></span>");
$('.tabs').append("<span id='updateall' class='status vhshift' style='display:none;margin-left:12px'><input type='button' value='Update all Plugins' onclick='$(\"div.spinner\").show(\"slow\");openBox(\"/plugins/dynamix.plugin.manager/scripts/plugin&arg1=updateall\",\"Update All Plugins\",490,430,true,\"loadlist\",\":return\")'></span>");
<?if ($check):?>
$('.tabs').append("<span id='checkall' class='status vhshift'><input type='button' value='Check for Updates' onclick='$(\"div.spinner\").show(\"slow\");openBox(\"/plugins/dynamix.plugin.manager/scripts/plugin&arg1=checkall\",\"Plugin Update Check\",490,430,true,\"loadlist\",\":return\")' disabled></span>");
<?endif;?>
+3 -1
View File
@@ -110,6 +110,7 @@ div.Panel:hover{background-color:unset;}
div.PanelText{display:none;}
th.five{width:5%}
</style>
<div class="spinner fixed"></div>
<table id="kvm_table" class="tablesorter shift">
<thead><tr><th><i class="fa fa-th-list"></i></th><th>Name</th><th>Description</th><th>CPUs</th><th>Memory</th><th>vDisks</th><th>Graphics</th><th class="five">Autostart</th><th class="five" style="text-align:right;padding-right:24px"><a href="#" style="cursor:hand" onclick="resetSorting()" title="Reset sorting"><i class="fa fa-sort"></i></a></th></tr></thead>
<tbody id="kvm_list"><tr><td colspan='9'><div class="spinner"></div></td></tr></tbody>
@@ -141,8 +142,9 @@ function resetSorting() {
$.post('/plugins/dynamix.vm.manager/include/UserPrefs.php',{reset:true},function(){loadlist();});
}
function loadlist() {
$.get('/plugins/dynamix.vm.manager/include/VMMachines.php',function(d) {
$.get('/plugins/dynamix.vm.manager/include/VMMachines.php',{show:$.cookie('vmshow')},function(d) {
var data = d.split(/\0/);
$('div.spinner').hide('slow');
$('#kvm_list').html(data[0]);
$('head').append('<script>'+data[1]+'<\/script>');
<?if ($display['resize']):?>
@@ -32,6 +32,8 @@ if (file_exists($user_prefs)) {
$i = 0;
$menu = [];
$kvm = ['var kvm=[];'];
$show = explode(',',$_GET['show']) ?? [];
foreach ($vms as $vm) {
$res = $lv->get_domain_by_name($vm);
$desc = $lv->domain_get_description($res);
@@ -92,7 +94,7 @@ foreach ($vms as $vm) {
echo "<td style='text-align:right;padding-right:12px'><a href='#' title='Move row up'><i class='fa fa-arrow-up up'></i></a>&nbsp;<a href='#' title='Move row down'><i class='fa fa-arrow-down down'></i></a></td></tr>";
/* Disk device information */
echo "<tr id='name".($i++)."' style='display:none'>";
echo "<tr id='name{$i}".(in_array('name'.$i++,$show) ? "'>" : "' style='display:none'>");
echo "<td colspan='7' style='overflow:hidden'>";
echo "<table class='tablesorter domdisk' id='domdisk_table'>";
echo "<thead><tr><th><i class='fa fa-hdd-o'></i><b> Disk devices &nbsp;</b></th><th>Bus</th><th>Capacity</th><th>Allocation</th><th>Actions</th></tr></thead>";
@@ -102,7 +104,7 @@ foreach ($vms as $vm) {
foreach ($lv->get_disk_stats($res) as $arrDisk) {
$capacity = $lv->format_size($arrDisk['capacity'], 0);
$allocation = $lv->format_size($arrDisk['allocation'], 0);
$disk = (array_key_exists('file', $arrDisk)) ? $arrDisk['file'] : $arrDisk['partition'];
$disk = array_key_exists('file', $arrDisk) ? $arrDisk['file'] : $arrDisk['partition'];
$dev = $arrDisk['device'];
$bus = $arrDisk['bus'];
echo "<tr><td>$disk</td><td>{$arrValidDiskBuses[$bus]}</td>";
@@ -118,7 +120,7 @@ foreach ($vms as $vm) {
echo "<input class='input' type='text' style='width:46px' name='cap' value='$capacity' val='diskresize' hidden>";
echo "</span></form></td>";
echo "<td>$allocation</td>";
echo "<td>detach <a href='#' onclick=\"swal({title:'Are you sure?',text:'Detach ".basename($disk)." from VM: $vm',type:'warning',showCancelButton:true},function(){ajaxVMDispatch('attached',{action:'disk-remove',uuid:'$uuid',dev:'$dev'});});return false;\" title='detach disk from VM'><i class='fa fa-eject blue'></i></a></td>";
echo "<td>detach <i class='fa fa-eject blue' style='cursor:pointer' onclick=\"swal({title:'Are you sure?',text:'Detach: ".basename($disk)."<br><i>from VM: $vm</i>',type:'warning',html:'true',showCancelButton:true},function(){\$('div.spinner').show('slow');setTimeout(function(){ajaxVMDispatch({action:'disk-remove',uuid:'$uuid',dev:'$dev'},'loadlist');},100);})\" title='detach disk from VM'></i></td>";
} else {
echo "<td>$capacity</td><td>$allocation</td><td>N/A</td>";
}
@@ -135,7 +137,7 @@ foreach ($vms as $vm) {
$bus = $arrCD['bus'];
echo "<tr><td>$disk</td><td>{$arrValidDiskBuses[$bus]}</td><td>$capacity</td><td>$allocation</td><td>";
if ($state == 'shutoff')
echo "detach <a href='#' onclick=\"swal({title:'Are you sure?',text:'Detach ".basename($disk)." from VM: $vm',type:'warning',showCancelButton:true},function(){ajaxVMDispatch('attached',{action:'disk-remove',uuid:'$uuid',dev:'$dev'});});return false;\" title='detach disk from VM'><i class='fa fa-eject blue'></i></a>";
echo "detach <i class='fa fa-eject blue' style='cursor:pointer' onclick=\"swal({title:'Are you sure?',text:'Detach: ".basename($disk)."<br><i>from VM: $vm</i>',type:'warning',html:'true',showCancelButton:true},function(){\$('div.spinner').show('slow');setTimeout(function(){ajaxVMDispatch({action:'disk-remove',uuid:'$uuid',dev:'$dev'},'loadlist');},100);})\" title='detach disk from VM'></i>";
else
echo "N/A";
echo "</td></tr>";
+3 -10
View File
@@ -25,19 +25,12 @@ function requireLibvirt() {
}
}
$arrSizePrefix = [
0 => '',
1 => 'K',
2 => 'M',
3 => 'G',
4 => 'T',
5 => 'P'
];
$arrSizePrefix = [0 => '', 1 => 'K', 2 => 'M', 3 => 'G', 4 => 'T', 5 => 'P'];
$_REQUEST = array_merge($_GET, $_POST);
$action = array_key_exists('action', $_REQUEST) ? $_REQUEST['action'] : '';
$uuid = array_key_exists('uuid', $_REQUEST) ? $_REQUEST['uuid'] : '';
$action = $_REQUEST['action'] ?: '';
$uuid = $_REQUEST['uuid'] ?: '';
if ($uuid) {
requireLibvirt();
@@ -122,10 +122,13 @@ function vncOpen() {
});
}
function toggle_id(itemID){
var cookie = $.cookie('vmshow')||'';
if ((document.getElementById(itemID).style.display == 'none')) {
slideDownRows($('#'+itemID));
if (cookie.indexOf(itemID)<0) $.cookie('vmshow',cookie+itemID+',',{path:'/'});
} else {
slideUpRows($('#'+itemID));
if (cookie.indexOf(itemID)>=0) $.cookie('vmshow',cookie.replace(itemID+',',''),{path:'/'});
}
return false;
}
+1 -1
View File
@@ -29,7 +29,7 @@ function toggle_state(device,name,action) {
button = '[id^=button-]';
}
pauseEvents(event);
$.post('/webGui/include/ToggleState.php',{device:device,name:name,action:action,state:'<?=$var['mdState']?>',csrf:'<?=$var['csrf_token']?>'},function(){resumeEvents(event);if (button) $(button).prop('disabled',false);});
$.post('/webGui/include/ToggleState.php',{device:device,name:name,action:action,state:'<?=$var['mdState']?>',csrf:'<?=$var['csrf_token']?>'},function(){resumeEvents(event,500);if (button) $(button).prop('disabled',false);});
}
function display_diskio() {
if ($.cookie('diskio')===undefined) {
+1 -3
View File
@@ -69,9 +69,7 @@ foreach ($file as $row) {
'disk' => my_name($disk).$luks
];
}
$sort = [];
foreach ($list as $row) $sort[] = $row['type'];
array_multisort($sort,$list);
array_multisort(array_column($list,'type'),$list);
echo "<tbody>";
$dirs=0; $files=0; $total=0;
+1 -1
View File
@@ -18,6 +18,7 @@ require_once "$docroot/plugins/dynamix.docker.manager/include/DockerClient.php";
require_once "$docroot/plugins/dynamix.vm.manager/include/libvirt_helpers.php";
$display = $_POST['display'];
$menu = [];
if (pgrep('dockerd')!==false && ($display=='icons' || $display=='docker')) {
$user_prefs = $dockerManPaths['user-prefs'];
@@ -25,7 +26,6 @@ if (pgrep('dockerd')!==false && ($display=='icons' || $display=='docker')) {
$DockerTemplates = new DockerTemplates();
$all_containers = $DockerClient->getDockerContainers();
$all_info = $DockerTemplates->getAllInfo();
$menu = [];
if (file_exists($user_prefs)) {
$prefs = parse_ini_file($user_prefs); $sort = [];
+7 -4
View File
@@ -22,6 +22,12 @@ $diskio= @parse_ini_file('state/diskload.ini');
$sum = ['count'=>0, 'temp'=>0, 'fsSize'=>0, 'fsUsed'=>0, 'fsFree'=>0, 'ioReads'=>0, 'ioWrites'=>0, 'numReads'=>0, 'numWrites'=>0, 'numErrors'=>0];
extract(parse_plugin_cfg('dynamix',true));
function model($id) {
return substr($id,0,strrpos($id,'_'));
}
// sort unassigned devices on disk identification
if (count($devs)>1) array_multisort(array_column($devs,'sectors'),SORT_DESC,array_map('model',array_column($devs,'id')),SORT_NATURAL|SORT_FLAG_CASE,array_column($devs,'device'),$devs);
require_once "$docroot/webGui/include/CustomMerge.php";
function in_parity_log($log,$timestamp) {
@@ -109,10 +115,7 @@ function assignment(&$disk) {
$out .= "<option value=''>$empty</option>";
} else
$out .= "<option value='' selected>$empty</option>";
if ($disk['type']=='Cache')
foreach ($devs as $dev) {$out .= "<option value=\"{$dev['id']}\">".device_desc($dev)."</option>";}
else
foreach ($devs as $dev) if ($dev['tag']==0) {$out .= "<option value=\"{$dev['id']}\">".device_desc($dev)."</option>";}
foreach ($devs as $dev) if ($disk['type']=='Cache' || $dev['tag']==0) {$out .= "<option value=\"{$dev['id']}\">".device_desc($dev)."</option>";}
return "$out</select></form>";
}
function vfs_type($fs) {