mirror of
https://github.com/unraid/webgui.git
synced 2026-03-13 22:40:17 -05:00
Docker enhancements and corrections
- delete cache files when container is deleted - improved handling of user defined networks - optimized reading of container and image settings - improved handling of container IP address assignment - code optimization and consistency
This commit is contained in:
@@ -15,18 +15,20 @@
|
||||
$user_prefs = '/boot/config/plugins/dockerMan/userprefs.cfg';
|
||||
|
||||
# controlled docker execution
|
||||
function docker($cmd, &$var=null) {
|
||||
return exec("docker $cmd 2>/dev/null", $var);
|
||||
function docker($cmd) {
|
||||
$data = exec("docker $cmd 2>/dev/null", $array);
|
||||
return count($array)>1 ? $array : $data;
|
||||
}
|
||||
|
||||
$action = $_POST['action'];
|
||||
$status = $action=='start' ? 'exited' : 'running';
|
||||
$all_containers=[]; docker("ps -a --filter status='$status' --format='{{.Names}}'", $all_containers);
|
||||
$containers = docker("ps -a --filter status='$status' --format='{{.Names}}'");
|
||||
|
||||
if (file_exists($user_prefs)) {
|
||||
$prefs = parse_ini_file($user_prefs); $sort = [];
|
||||
foreach ($all_containers as $ct) $sort[] = array_search($ct,$prefs) ?? 999;
|
||||
array_multisort($sort, ($action=='start'?SORT_ASC:SORT_DESC), SORT_NUMERIC, $all_containers);
|
||||
foreach ($containers as $ct) $sort[] = array_search($ct,$prefs) ?? 999;
|
||||
array_multisort($sort, ($action=='start'?SORT_ASC:SORT_DESC), SORT_NUMERIC, $containers);
|
||||
}
|
||||
|
||||
foreach ($all_containers as $ct) docker("$action $ct >/dev/null");
|
||||
foreach ($containers as $ct) docker("$action $ct >/dev/null");
|
||||
?>
|
||||
|
||||
@@ -13,15 +13,14 @@
|
||||
?>
|
||||
<?
|
||||
$docroot = $docroot ?? $_SERVER['DOCUMENT_ROOT'] ?: '/usr/local/emhttp';
|
||||
$var = parse_ini_file('state/var.ini');
|
||||
extract(parse_ini_file('state/network.ini',true));
|
||||
|
||||
ignore_user_abort(true);
|
||||
|
||||
require_once "$docroot/plugins/dynamix.docker.manager/include/DockerClient.php";
|
||||
|
||||
$DockerClient = new DockerClient();
|
||||
$DockerUpdate = new DockerUpdate();
|
||||
$var = parse_ini_file('state/var.ini');
|
||||
extract(parse_ini_file('state/network.ini',true));
|
||||
ignore_user_abort(true);
|
||||
|
||||
$DockerClient = new DockerClient();
|
||||
$DockerUpdate = new DockerUpdate();
|
||||
$DockerTemplates = new DockerTemplates();
|
||||
|
||||
# ███████╗██╗ ██╗███╗ ██╗ ██████╗████████╗██╗ ██████╗ ███╗ ██╗███████╗
|
||||
@@ -31,11 +30,9 @@ $DockerTemplates = new DockerTemplates();
|
||||
# ██║ ╚██████╔╝██║ ╚████║╚██████╗ ██║ ██║╚██████╔╝██║ ╚████║███████║
|
||||
# ╚═╝ ╚═════╝ ╚═╝ ╚═══╝ ╚═════╝ ╚═╝ ╚═╝ ╚═════╝ ╚═╝ ╚═══╝╚══════╝
|
||||
|
||||
$echo = function($m){echo "<pre>".print_r($m, true)."</pre>";};
|
||||
|
||||
$custom=[]; docker("network ls --filter driver='macvlan' --format='{{.Name}}'", $custom);
|
||||
$custom = $DockerClient->docker("network ls --filter driver='macvlan' --format='{{.Name}}'");
|
||||
$subnet = ['bridge'=>'', 'host'=>'', 'none'=>''];
|
||||
foreach ($custom as $network) $subnet[$network] = substr(docker("network inspect --format='{{range .IPAM.Config}}{{.Subnet}}, {{end}}' $network"),0,-1);
|
||||
foreach ($custom as $network) $subnet[$network] = substr($DockerClient->docker("network inspect --format='{{range .IPAM.Config}}{{.Subnet}}, {{end}}' $network"),0,-1);
|
||||
|
||||
function stopContainer($name) {
|
||||
global $DockerClient;
|
||||
@@ -49,13 +46,13 @@ function stopContainer($name) {
|
||||
@flush();
|
||||
}
|
||||
|
||||
function removeContainer($name, $remove=false) {
|
||||
function removeContainer($name, $cache=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, $remove);
|
||||
$retval = $DockerClient->removeContainer($name, false, $cache);
|
||||
$out = ($retval === true) ? "Successfully removed container '$name'" : "Error: ".$retval;
|
||||
echo "<script>stop_Wait($waitID);addLog('<b>".addslashes(htmlspecialchars($out))."</b>');</script>\n";
|
||||
@flush();
|
||||
@@ -187,7 +184,7 @@ function postToXML($post, $setOwnership=false) {
|
||||
$xml->DonateImg = xml_encode($post['contDonateImg']);
|
||||
$xml->MinVer = xml_encode($post['contMinVer']);
|
||||
|
||||
# V1 compatibility
|
||||
// V1 compatibility
|
||||
$xml->Description = xml_encode($post['contOverview']);
|
||||
$xml->Networking->Mode = xml_encode($post['contNetwork']);
|
||||
$xml->Networking->addChild("Publish");
|
||||
@@ -207,7 +204,7 @@ function postToXML($post, $setOwnership=false) {
|
||||
$config['Display'] = xml_encode($post['confDisplay'][$i]);
|
||||
$config['Required'] = xml_encode($post['confRequired'][$i]);
|
||||
$config['Mask'] = xml_encode($post['confMask'][$i]);
|
||||
# V1 compatibility
|
||||
// V1 compatibility
|
||||
if ($Type == 'Port') {
|
||||
$port = $xml->Networking->Publish->addChild("Port");
|
||||
$port->HostPort = $post['confValue'][$i];
|
||||
@@ -253,7 +250,7 @@ function xmlToVar($xml) {
|
||||
$out['PostArgs'] = xml_decode($xml->PostArgs);
|
||||
$out['DonateText'] = xml_decode($xml->DonateText);
|
||||
$out['DonateLink'] = xml_decode($xml->DonateLink);
|
||||
$out['DonateImg'] = xml_decode($xml->DonateImg ?? $xml->DonateImage); # Various authors use different tags. DonateImg is the official spec
|
||||
$out['DonateImg'] = xml_decode($xml->DonateImg ?? $xml->DonateImage); // Various authors use different tags. DonateImg is the official spec
|
||||
$out['MinVer'] = xml_decode($xml->MinVer);
|
||||
$out['Config'] = [];
|
||||
if (isset($xml->Config)) {
|
||||
@@ -278,14 +275,14 @@ function xmlToVar($xml) {
|
||||
$out['Config'][] = $c;
|
||||
}
|
||||
}
|
||||
# some xml templates advertise as V2 but omit the new <Network> element
|
||||
# check for and use the V1 <Networking> element when this occurs
|
||||
// some xml templates advertise as V2 but omit the new <Network> element
|
||||
// check for and use the V1 <Networking> element when this occurs
|
||||
if (empty($out['Network']) && isset($xml->Networking->Mode)) {
|
||||
$out['Network'] = xml_decode($xml->Networking->Mode);
|
||||
}
|
||||
# check if network exists
|
||||
// check if network exists
|
||||
if (!key_exists($out['Network'],$subnet)) $out['Network'] = 'none';
|
||||
# V1 compatibility
|
||||
// V1 compatibility
|
||||
if ($xml['version'] != '2') {
|
||||
if (isset($xml->Description)) {
|
||||
$out['Overview'] = stripslashes(xml_decode($xml->Description));
|
||||
@@ -381,9 +378,9 @@ function xmlToCommand($xml, $create_paths=false) {
|
||||
$Ports = [''];
|
||||
$Variables = [''];
|
||||
$Devices = [''];
|
||||
# Bind Time
|
||||
// Bind Time
|
||||
$Variables[] = 'TZ="' . $var['timeZone'] . '"';
|
||||
# Add HOST_OS variable
|
||||
// Add HOST_OS variable
|
||||
$Variables[] = 'HOST_OS="unRAID"';
|
||||
|
||||
foreach ($xml['Config'] as $key => $config) {
|
||||
@@ -400,13 +397,13 @@ function xmlToCommand($xml, $create_paths=false) {
|
||||
@chgrp($hostConfig, 100);
|
||||
}
|
||||
} elseif ($confType == 'port') {
|
||||
# Export ports as variable if Network is set to host
|
||||
// Export ports as variable if Network is set to host
|
||||
if (preg_match('/^(host|eth[0-9]|br[0-9]|bond[0-9])/',strtolower($xml['Network']))) {
|
||||
$Variables[] = strtoupper(escapeshellarg($Mode.'_PORT_'.$containerConfig).'='.escapeshellarg($hostConfig));
|
||||
# Export ports as port if Network is set to bridge
|
||||
// Export ports as port if Network is set to bridge
|
||||
} elseif (strtolower($xml['Network'])== 'bridge') {
|
||||
$Ports[] = escapeshellarg($hostConfig.':'.$containerConfig.'/'.$Mode);
|
||||
# No export of ports if Network is set to none
|
||||
// No export of ports if Network is set to none
|
||||
}
|
||||
} elseif ($confType == "variable") {
|
||||
$Variables[] = escapeshellarg($containerConfig).'='.escapeshellarg($hostConfig);
|
||||
@@ -416,17 +413,7 @@ function xmlToCommand($xml, $create_paths=false) {
|
||||
}
|
||||
$postArgs = explode(";",$xml['PostArgs']);
|
||||
$cmd = sprintf($docroot.'/plugins/dynamix.docker.manager/scripts/docker create %s %s %s %s %s %s %s %s %s %s %s',
|
||||
$cmdName,
|
||||
$cmdNetwork,
|
||||
$cmdMyIP,
|
||||
$cmdPrivileged,
|
||||
implode(' -e ', $Variables),
|
||||
implode(' -p ', $Ports),
|
||||
implode(' -v ', $Volumes),
|
||||
implode(' --device=', $Devices),
|
||||
$xml['ExtraParams'],
|
||||
escapeshellarg($xml['Repository']),
|
||||
$postArgs[0]);
|
||||
$cmdName, $cmdNetwork, $cmdMyIP, $cmdPrivileged, implode(' -e ', $Variables), implode(' -p ', $Ports), implode(' -v ', $Volumes), implode(' --device=', $Devices), $xml['ExtraParams'], escapeshellarg($xml['Repository']), $postArgs[0]);
|
||||
return [preg_replace('/\s+/', ' ', $cmd), $xml['Name'], $xml['Repository']];
|
||||
}
|
||||
|
||||
@@ -464,7 +451,6 @@ function getXmlVal($xml, $element, $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];
|
||||
if (!isset($element)) $element = $xml->addChild($el);
|
||||
@@ -481,19 +467,19 @@ function setXmlVal(&$xml, $value, $el, $attr=null, $pos=0) {
|
||||
}
|
||||
|
||||
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;
|
||||
[$mode,$ip,$port1,$port2] = explode('#',$docker[$n++]);
|
||||
$port = explode('|',$port1);
|
||||
if (count($port)<2) $port = explode('|',str_replace(['/tcp','/udp'],'',$port2));
|
||||
global $DockerClient, $host;
|
||||
foreach ($DockerClient->getDockerContainers() as $ct) {
|
||||
$list = $port = [];
|
||||
$nat = $ip = false;
|
||||
$list['Name'] = $ct['Name'];
|
||||
foreach ($ct['Ports'] as $tmp) {
|
||||
$nat = $tmp['NAT'];
|
||||
$ip = $tmp['IP'];
|
||||
$port[] = $tmp['PublicPort'];
|
||||
}
|
||||
sort($port);
|
||||
$list['Port'] = (strstr('host,bridge',$mode) ? $eth0['IPADDR:0'] : $ip).' : '.(implode(' ',array_unique($port)) ?: '???')." -- $mode";
|
||||
$ip = $nat ? $host : ($ip ?: $DockerClient->myIP($ct['Name']) ?: $host);
|
||||
$list['Port'] = "$ip : ".(implode(' ',array_unique($port)) ?: '???')." -- {$ct['NetworkMode']}";
|
||||
$ports[] = $list;
|
||||
}
|
||||
return $ports;
|
||||
@@ -509,6 +495,7 @@ function getAllocations() {
|
||||
##########################
|
||||
## CREATE CONTAINER ##
|
||||
##########################
|
||||
|
||||
if (isset($_POST['contName'])) {
|
||||
$postXML = postToXML($_POST, true);
|
||||
$dry_run = $_POST['dryRun']=='true' ? true : false;
|
||||
@@ -547,8 +534,8 @@ if (isset($_POST['contName'])) {
|
||||
// Remove existing container
|
||||
if ($DockerClient->doesContainerExist($Name)) {
|
||||
// attempt graceful stop of container first
|
||||
$oldContainerDetails = $DockerClient->getContainerDetails($Name);
|
||||
if (!empty($oldContainerDetails) && !empty($oldContainerDetails['State']) && !empty($oldContainerDetails['State']['Running'])) {
|
||||
$oldContainerInfo = $DockerClient->getContainerDetails($Name);
|
||||
if (!empty($oldContainerInfo) && !empty($oldContainerInfo['State']) && !empty($oldContainerInfo['State']['Running'])) {
|
||||
// attempt graceful stop of container first
|
||||
stopContainer($Name);
|
||||
}
|
||||
@@ -558,8 +545,8 @@ if (isset($_POST['contName'])) {
|
||||
// Remove old container if renamed
|
||||
if ($existing && $DockerClient->doesContainerExist($existing)) {
|
||||
// determine if the container is still running
|
||||
$oldContainerDetails = $DockerClient->getContainerDetails($existing);
|
||||
if (!empty($oldContainerDetails) && !empty($oldContainerDetails['State']) && !empty($oldContainerDetails['State']['Running'])) {
|
||||
$oldContainerInfo = $DockerClient->getContainerDetails($existing);
|
||||
if (!empty($oldContainerInfo) && !empty($oldContainerInfo['State']) && !empty($oldContainerInfo['State']['Running'])) {
|
||||
// attempt graceful stop of container first
|
||||
stopContainer($existing);
|
||||
} else {
|
||||
@@ -582,6 +569,7 @@ if (isset($_POST['contName'])) {
|
||||
##########################
|
||||
## UPDATE CONTAINER ##
|
||||
##########################
|
||||
|
||||
if ($_GET['updateContainer']){
|
||||
readfile("$docroot/plugins/dynamix.docker.manager/log.htm");
|
||||
@flush();
|
||||
@@ -598,9 +586,9 @@ if ($_GET['updateContainer']){
|
||||
$oldImageID = $DockerClient->getImageID($Repository);
|
||||
// Pull image
|
||||
if (!pullImage($Name, $Repository)) continue;
|
||||
$oldContainerDetails = $DockerClient->getContainerDetails($Name);
|
||||
$oldContainerInfo = $DockerClient->getContainerDetails($Name);
|
||||
// determine if the container is still running
|
||||
if (!empty($oldContainerDetails) && !empty($oldContainerDetails['State']) && !empty($oldContainerDetails['State']['Running'])) {
|
||||
if (!empty($oldContainerInfo) && !empty($oldContainerInfo['State']) && !empty($oldContainerInfo['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
|
||||
@@ -623,6 +611,7 @@ if ($_GET['updateContainer']){
|
||||
#########################
|
||||
## REMOVE TEMPLATE ##
|
||||
#########################
|
||||
|
||||
if ($_GET['rmTemplate']) {
|
||||
unlink($_GET['rmTemplate']);
|
||||
}
|
||||
@@ -630,6 +619,7 @@ if ($_GET['rmTemplate']) {
|
||||
#########################
|
||||
## LOAD TEMPLATE ##
|
||||
#########################
|
||||
|
||||
if ($_GET['xmlTemplate']) {
|
||||
list($xmlType, $xmlTemplate) = explode(':', urldecode($_GET['xmlTemplate']));
|
||||
if (is_file($xmlTemplate)) {
|
||||
@@ -708,6 +698,7 @@ optgroup.title{background-color:#625D5D;color:#FFFFFF;text-align:center;margin-t
|
||||
.switch-wrapper{display:inline-block;position:relative;top:3px;vertical-align:middle;}
|
||||
.switch-button-label.off{color:inherit;}
|
||||
.selectVariable{width:320px}
|
||||
.fa.button{color:maroon;font-size:24px;position:relative;top:4px;cursor:pointer}
|
||||
</style>
|
||||
<script src="<?autov('/webGui/javascript/jquery.switchbutton.js')?>"></script>
|
||||
<script src="<?autov('/webGui/javascript/jquery.filetree.js')?>"></script>
|
||||
@@ -1127,34 +1118,27 @@ optgroup.title{background-color:#625D5D;color:#FFFFFF;text-align:center;margin-t
|
||||
<option value="">Select a template</option>
|
||||
<?
|
||||
$rmadd = '';
|
||||
$all_templates = [];
|
||||
$all_templates['user'] = $DockerTemplates->getTemplates("user");
|
||||
$all_templates['default'] = $DockerTemplates->getTemplates("default");
|
||||
foreach ($all_templates as $key => $templates) {
|
||||
if ($key == "default") $title = "Default templates";
|
||||
if ($key == "user") $title = "User defined templates";
|
||||
printf("\t\t\t\t\t<optgroup class=\"title bold\" label=\"[ %s ]\"></optgroup>\n", htmlspecialchars($title));
|
||||
$prefix = '';
|
||||
foreach ($templates as $value){
|
||||
if ($value["prefix"] != $prefix) {
|
||||
if ($prefix != '') {
|
||||
printf("\t\t\t\t\t</optgroup>\n");
|
||||
}
|
||||
$prefix = $value["prefix"];
|
||||
printf("\t\t\t\t\t<optgroup class=\"bold\" label=\"[ %s ]\">\n", htmlspecialchars($prefix));
|
||||
}
|
||||
//$value['name'] = str_replace("my-", '', $value['name']);
|
||||
$selected = (isset($xmlTemplate) && $value['path'] == $xmlTemplate) ? ' selected ' : '';
|
||||
if ($selected && ($key == "default")) $showAdditionalInfo = 'class="advanced"';
|
||||
if (strlen($selected) && $key == 'user' ){$rmadd = $value['path'];}
|
||||
printf("\t\t\t\t\t\t<option class=\"list\" value=\"%s:%s\" {$selected} >%s</option>\n", htmlspecialchars($key), htmlspecialchars($value['path']), htmlspecialchars($value['name']));
|
||||
$templates = [];
|
||||
$templates['default'] = $DockerTemplates->getTemplates('default');
|
||||
$templates['user'] = $DockerTemplates->getTemplates('user');
|
||||
foreach ($templates as $section => $template) {
|
||||
$title = ucfirst($section)." templates";
|
||||
printf("<optgroup class='title bold' label='[ %s ]'>", htmlspecialchars($title));
|
||||
foreach ($template as $value){
|
||||
$name = str_replace('my-', '', $value['name']);
|
||||
$selected = (isset($xmlTemplate) && $value['path']==$xmlTemplate) ? ' selected ' : '';
|
||||
if ($selected && $section=='default') $showAdditionalInfo = 'class="advanced"';
|
||||
if ($selected && $section=='user') $rmadd = $value['path'];
|
||||
printf("<option class='list' value='%s:%s' $selected>%s</option>", htmlspecialchars($section), htmlspecialchars($value['path']), htmlspecialchars($name));
|
||||
}
|
||||
printf("\t\t\t\t\t</optgroup>\n");
|
||||
if (!$template) echo("<option class='list' disabled><none></option>");
|
||||
printf("</optgroup>");
|
||||
}
|
||||
?>
|
||||
</select>
|
||||
<? if (!empty($rmadd)) {
|
||||
echo "<a onclick=\"rmTemplate('".addslashes(htmlspecialchars($rmadd))."');\" style=\"cursor:pointer;\"><i class='fa fa-window-close' aria-hidden='true' style='color:maroon; font-size:20px; position:relative;top:5px;' title=\"".htmlspecialchars($rmadd)."\"></i></a>"; }?>
|
||||
<?if ($rmadd) {
|
||||
echo "<i class='fa fa-window-close button' title=\"".htmlspecialchars($rmadd)."\" onclick='rmTemplate(\"".addslashes(htmlspecialchars($rmadd))."\")'></i>";
|
||||
}?>
|
||||
</td>
|
||||
</tr>
|
||||
<tr>
|
||||
|
||||
@@ -15,29 +15,23 @@
|
||||
$docroot = $docroot ?? $_SERVER['DOCUMENT_ROOT'] ?: '/usr/local/emhttp';
|
||||
|
||||
$dockerManPaths = [
|
||||
'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',
|
||||
'images-storage' => '/boot/config/plugins/dockerMan/images',
|
||||
'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'
|
||||
'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-usb' => "/boot/config/plugins/dockerMan/templates",
|
||||
'images-usb' => "/boot/config/plugins/dockerMan/images",
|
||||
'user-prefs' => "/boot/config/plugins/dockerMan/userprefs.cfg",
|
||||
'plugin' => "$docroot/plugins/dynamix.docker.manager",
|
||||
'images-ram' => "$docroot/state/plugins/dynamix.docker.manager/images",
|
||||
'webui-info' => "$docroot/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));
|
||||
}
|
||||
// load network variables if needed.
|
||||
if (!isset($eth0) && is_file("$docroot/state/network.ini")) extract(parse_ini_file("$docroot/state/network.ini",true));
|
||||
$host = $eth0['IPADDR:0'] ?? '0.0.0.0';
|
||||
|
||||
# controlled docker execution
|
||||
function docker($cmd, &$var=null) {
|
||||
return exec("docker $cmd 2>/dev/null", $var);
|
||||
}
|
||||
|
||||
# Docker configuration file - guaranteed to exist
|
||||
// Docker configuration file - guaranteed to exist
|
||||
$docker_cfgfile = '/boot/config/docker.cfg';
|
||||
$dockercfg = parse_ini_file($docker_cfgfile);
|
||||
|
||||
@@ -46,13 +40,23 @@ $dockercfg = parse_ini_file($docker_cfgfile);
|
||||
#######################################
|
||||
|
||||
class DockerTemplates {
|
||||
|
||||
public $verbose = false;
|
||||
|
||||
private function debug($m) {
|
||||
if ($this->verbose) echo $m."\n";
|
||||
}
|
||||
|
||||
private function removeDir($path) {
|
||||
if (is_dir($path)) {
|
||||
$files = array_diff(scandir($path), ['.', '..']);
|
||||
foreach ($files as $file) {
|
||||
$this->removeDir(realpath($path).'/'.$file);
|
||||
}
|
||||
return rmdir($path);
|
||||
} elseif (is_file($path)) return unlink($path);
|
||||
return 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;
|
||||
@@ -79,13 +83,13 @@ class DockerTemplates {
|
||||
switch ($type) {
|
||||
case 'all':
|
||||
$dirs[] = $dockerManPaths['templates-user'];
|
||||
$dirs[] = $dockerManPaths['templates-storage'];
|
||||
$dirs[] = $dockerManPaths['templates-usb'];
|
||||
break;
|
||||
case 'user':
|
||||
$dirs[] = $dockerManPaths['templates-user'];
|
||||
break;
|
||||
case 'default':
|
||||
$dirs[] = $dockerManPaths['templates-storage'];
|
||||
$dirs[] = $dockerManPaths['templates-usb'];
|
||||
break;
|
||||
default:
|
||||
$dirs[] = $type;
|
||||
@@ -94,23 +98,13 @@ class DockerTemplates {
|
||||
if (!is_dir($dir)) @mkdir($dir, 0755, true);
|
||||
$tmpls = array_merge($tmpls, $this->listDir($dir, 'xml'));
|
||||
}
|
||||
array_multisort(array_column($tmpls,'name'), SORT_NATURAL|SORT_FLAG_CASE, $tmpls);
|
||||
return $tmpls;
|
||||
}
|
||||
|
||||
private function removeDir($path) {
|
||||
if (is_dir($path)) {
|
||||
$files = array_diff(scandir($path), ['.', '..']);
|
||||
foreach ($files as $file) {
|
||||
$this->removeDir(realpath($path).'/'.$file);
|
||||
}
|
||||
return rmdir($path);
|
||||
} elseif (is_file($path)) return unlink($path);
|
||||
return false;
|
||||
}
|
||||
|
||||
public function downloadTemplates($Dest=null, $Urls=null) {
|
||||
global $dockerManPaths;
|
||||
$Dest = $Dest ?: $dockerManPaths['templates-storage'];
|
||||
$Dest = $Dest ?: $dockerManPaths['templates-usb'];
|
||||
$Urls = $Urls ?: $dockerManPaths['template-repos'];
|
||||
$repotemplates = $output = [];
|
||||
$tmp_dir = '/tmp/tmp-'.mt_rand();
|
||||
@@ -239,56 +233,30 @@ class DockerTemplates {
|
||||
}
|
||||
|
||||
public function getControlURL($name) {
|
||||
global $eth0;
|
||||
global $host;
|
||||
$DockerClient = new DockerClient();
|
||||
$Repository = '';
|
||||
foreach ($DockerClient->getDockerContainers() as $ct) {
|
||||
if ($ct['Name'] == $name) {
|
||||
$Repository = $ct['Image'];
|
||||
$Ports = $ct['Ports'];
|
||||
break;
|
||||
foreach ($DockerClient->getDockerContainers() as $ct) if ($ct['Name']==$name) break;
|
||||
$WebUI = $this->getTemplateValue($ct['Image'], 'WebUI');
|
||||
$myIP = $this->getTemplateValue($ct['Image'], 'MyIP');
|
||||
if (!$myIP) {
|
||||
foreach ($ct['Ports'] as $port) {
|
||||
$myIP = $port['NAT'] ? $host : $port['IP'];
|
||||
if ($myIP) break;
|
||||
}
|
||||
}
|
||||
$WebUI = $this->getTemplateValue($Repository, 'WebUI');
|
||||
$myIP = $this->getTemplateValue($Repository, 'MyIP');
|
||||
$network = $this->getTemplateValue($Repository, 'Network');
|
||||
if (!$myIP && preg_match("%^(br|eth|bond)[0-9]%",$network)) {
|
||||
$myIP = docker("inspect --format='{{range .NetworkSettings.Networks}}{{.IPAddress}}{{end}}' $name");
|
||||
}
|
||||
if (preg_match("%\[IP\]%", $WebUI)) {
|
||||
$WebUI = preg_replace("%\[IP\]%", $myIP ?: $eth0['IPADDR:0'], $WebUI);
|
||||
$WebUI = preg_replace("%\[IP\]%", $myIP ?: $DockerClient->myIP($name) ?: $host, $WebUI);
|
||||
}
|
||||
if (preg_match("%\[PORT:(\d+)\]%", $WebUI, $matches)) {
|
||||
$ConfigPort = $matches[1];
|
||||
if ($ct['NetworkMode'] == 'bridge') {
|
||||
foreach ($Ports as $key) {
|
||||
if ($key['PrivatePort'] == $ConfigPort) $ConfigPort = $key['PublicPort'];
|
||||
}
|
||||
foreach ($ct['Ports'] as $port) {
|
||||
if ($port['NAT'] && $port['PrivatePort']==$ConfigPort) {$ConfigPort = $port['PublicPort']; break;}
|
||||
}
|
||||
$WebUI = preg_replace("%\[PORT:\d+\]%", $ConfigPort, $WebUI);
|
||||
}
|
||||
return $WebUI;
|
||||
}
|
||||
|
||||
public function removeContainerInfo($container) {
|
||||
global $dockerManPaths;
|
||||
$info = DockerUtil::loadJSON($dockerManPaths['webui-info']);
|
||||
if (isset($info[$container])) {
|
||||
unset($info[$container]);
|
||||
DockerUtil::saveJSON($dockerManPaths['webui-info'], $info);
|
||||
}
|
||||
}
|
||||
|
||||
public function removeImageInfo($image) {
|
||||
global $dockerManPaths;
|
||||
$image = DockerUtil::ensureImageTag($image);
|
||||
$updateStatus = DockerUtil::loadJSON($dockerManPaths['update-status']);
|
||||
if (isset($updateStatus[$image])) {
|
||||
unset($updateStatus[$image]);
|
||||
DockerUtil::saveJSON($dockerManPaths['update-status'], $updateStatus);
|
||||
}
|
||||
}
|
||||
|
||||
public function getAllInfo($reload=false) {
|
||||
global $dockerManPaths;
|
||||
$DockerClient = new DockerClient();
|
||||
@@ -302,11 +270,11 @@ class DockerTemplates {
|
||||
$tmp = &$info[$name] ?? [];
|
||||
$tmp['running'] = $ct['Running'];
|
||||
$tmp['autostart'] = in_array($name, $allAutoStart);
|
||||
if (!$tmp['icon'] || $reload) $tmp['icon'] = $this->getIcon($image) ?: 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 (!is_file($tmp['icon']) || $reload) $tmp['icon'] = $this->getIcon($image);
|
||||
$tmp['url'] = $tmp['url'] ?? $this->getControlURL($name);
|
||||
$tmp['registry'] = $tmp['registry'] ?? $this->getTemplateValue($image, 'Registry');
|
||||
$tmp['Support'] = $tmp['Support'] ?? $this->getTemplateValue($image, 'Support');
|
||||
$tmp['Project'] = $tmp['Project'] ?? $this->getTemplateValue($image, 'Project');
|
||||
if (!$tmp['updated'] || $reload) {
|
||||
if ($reload) $DockerUpdate->reloadUpdateStatus($image);
|
||||
$vs = $DockerUpdate->getUpdateStatus($image);
|
||||
@@ -327,21 +295,22 @@ class DockerTemplates {
|
||||
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');
|
||||
$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);
|
||||
if (!is_file($tempPath)) {
|
||||
if (!is_file($storagePath)) $this->download_url($imgUrl, $storagePath);
|
||||
@copy($storagePath, $tempPath);
|
||||
$iconRAM = sprintf('%s/%s-%s-%s.png', $dockerManPaths['images-ram'], $name, $version, 'icon');
|
||||
$iconUSB = sprintf('%s/%s-%s-%s.png', $dockerManPaths['images-usb'], $name, $version, 'icon');
|
||||
if (!is_dir(dirname($iconRAM))) mkdir(dirname($iconRAM), 0755, true);
|
||||
if (!is_dir(dirname($iconUSB))) mkdir(dirname($iconUSB), 0755, true);
|
||||
if (!is_file($iconRAM)) {
|
||||
if (!is_file($iconUSB)) $this->download_url($imgUrl, $iconUSB);
|
||||
@copy($iconUSB, $iconRAM);
|
||||
}
|
||||
return (is_file($tempPath)) ? str_replace($docroot, '', $tempPath) : '';
|
||||
return (is_file($iconRAM)) ? str_replace($docroot, '', $iconRAM) : '';
|
||||
}
|
||||
}
|
||||
|
||||
####################################
|
||||
## DOCKERUPDATE CLASS ##
|
||||
####################################
|
||||
|
||||
class DockerUpdate{
|
||||
public $verbose = false;
|
||||
|
||||
@@ -468,25 +437,25 @@ class DockerUpdate{
|
||||
$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) ) {
|
||||
if (!$file = $DockerTemplates->getUserTemplate($Container)) {
|
||||
//$this->debug("User template for container '$Container' not found, aborting.");
|
||||
return null;
|
||||
}
|
||||
// Load user template XML, verify if it's valid and abort if doesn't have TemplateURL element
|
||||
$template = simplexml_load_file($file);
|
||||
if ( empty($template->TemplateURL) ) {
|
||||
if (empty($template->TemplateURL)) {
|
||||
//$this->debug("Template doesn't have TemplateURL element, aborting.");
|
||||
return null;
|
||||
}
|
||||
// Load a user template DOM for import remote template new Config
|
||||
$dom_local = dom_import_simplexml($template);
|
||||
// Try to download the remote template and abort if it fail.
|
||||
if (! $dl = $this->download_url($this->xml_decode($template->TemplateURL))) {
|
||||
if (!$dl = $this->download_url($this->xml_decode($template->TemplateURL))) {
|
||||
//$this->debug("Download of remote template failed, aborting.");
|
||||
return null;
|
||||
}
|
||||
// Try to load the downloaded template and abort if fail.
|
||||
if (! $remote_template = @simplexml_load_string($dl)) {
|
||||
if (!$remote_template = @simplexml_load_string($dl)) {
|
||||
//$this->debug("The downloaded template is not a valid XML file, aborting.");
|
||||
return null;
|
||||
}
|
||||
@@ -552,8 +521,8 @@ class DockerUpdate{
|
||||
####################################
|
||||
## DOCKERCLIENT CLASS ##
|
||||
####################################
|
||||
class DockerClient {
|
||||
|
||||
class DockerClient {
|
||||
private static $containersCache = null;
|
||||
private static $imagesCache = null;
|
||||
private static $codes = [
|
||||
@@ -566,10 +535,40 @@ class DockerClient {
|
||||
'500' => 'Server error'
|
||||
];
|
||||
|
||||
private function extractID($object) {
|
||||
return substr(str_replace('sha256:', '', $object), 0, 12);
|
||||
}
|
||||
|
||||
private function usedBy($imageId) {
|
||||
$out = [];
|
||||
foreach ($this->getDockerContainers() as $ct) {
|
||||
if ($ct['ImageId']==$imageId) $out[] = $ct['Name'];
|
||||
}
|
||||
return $out;
|
||||
}
|
||||
|
||||
private function flushCache(&$cache) {
|
||||
$cache = null;
|
||||
}
|
||||
|
||||
public function flushCaches() {
|
||||
$this->flushCache($this::$containersCache);
|
||||
$this->flushCache($this::$imagesCache);
|
||||
}
|
||||
|
||||
public function docker($cmd) {
|
||||
$data = exec("docker $cmd 2>/dev/null", $array);
|
||||
return count($array)>1 ? $array : $data;
|
||||
}
|
||||
|
||||
public function myIP($name, $version=4) {
|
||||
$networks = explode('|', $this->docker("inspect --format='{{range .NetworkSettings.Networks}}{{.IPAddress}}|{{end}}' $name"));
|
||||
foreach ($networks as $network) {
|
||||
if ($version==4 && strpos($network,'.')!==false) return $network;
|
||||
if ($version==6 && strpos($network,':')!==false) return $network;
|
||||
}
|
||||
}
|
||||
|
||||
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'];
|
||||
@@ -584,7 +583,7 @@ class DockerClient {
|
||||
if ($size == 0) return '0 B';
|
||||
$base = log($size) / log(1024);
|
||||
$suffix = ['B', 'KB', 'MB', 'GB', 'TB', 'PB', 'EB', 'ZB', 'YB'];
|
||||
return round(pow(1024, $base - floor($base)), 0) .' '. $suffix[floor($base)];
|
||||
return round(pow(1024, $base - floor($base)), 0).' '.$suffix[floor($base)];
|
||||
}
|
||||
|
||||
public function getDockerJSON($url, $method='GET', &$code=null, $callback=null, $unchunk=false) {
|
||||
@@ -599,7 +598,7 @@ class DockerClient {
|
||||
// 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) {$tmp = vsprintf('%2$s',preg_split("#\s+#", $line)); $code = $this::$codes[$tmp] ?? "Error code $tmp";}
|
||||
$headers .= $line;
|
||||
if (rtrim($line)=='') break;
|
||||
}
|
||||
@@ -612,14 +611,14 @@ class DockerClient {
|
||||
return $data;
|
||||
}
|
||||
|
||||
function doesContainerExist($container) {
|
||||
public function doesContainerExist($container) {
|
||||
foreach ($this->getDockerContainers() as $ct) {
|
||||
if ($ct['Name']==$container) return true;
|
||||
}
|
||||
return false;
|
||||
}
|
||||
|
||||
function doesImageExist($image) {
|
||||
public function doesImageExist($image) {
|
||||
foreach ($this->getDockerImages() as $img) {
|
||||
if (strpos($img['Tags'][0], $image)!==false) return true;
|
||||
}
|
||||
@@ -643,39 +642,39 @@ class DockerClient {
|
||||
public function startContainer($id) {
|
||||
$this->getDockerJSON("/containers/$id/start", 'POST', $code);
|
||||
$this->flushCache($this::$containersCache);
|
||||
return $this::$codes[$code] ?: 'Error code '.$code;
|
||||
return $code;
|
||||
}
|
||||
|
||||
public function stopContainer($id) {
|
||||
$this->getDockerJSON("/containers/$id/stop?t=10", 'POST', $code);
|
||||
$this->flushCache($this::$containersCache);
|
||||
return $this::$codes[$code] ?: 'Error code '.$code;
|
||||
return $code;
|
||||
}
|
||||
|
||||
public function restartContainer($id) {
|
||||
$this->getDockerJSON("/containers/$id/restart?t=10", 'POST', $code);
|
||||
$this->flushCache($this::$containersCache);
|
||||
return $this::$codes[$code] ?: 'Error code '.$code;
|
||||
return $code;
|
||||
}
|
||||
|
||||
public function removeContainer($id, $remove=false) {
|
||||
public function removeContainer($name, $id, $cache=false) {
|
||||
global $docroot, $dockerManPaths;
|
||||
// Purge cached container information
|
||||
$id = $id ?: $name;
|
||||
$info = DockerUtil::loadJSON($dockerManPaths['webui-info']);
|
||||
if (isset($info[$id])) {
|
||||
if ($remove && isset($info[$id]['icon'])) {
|
||||
$iconRam = $docroot.$info[$id]['icon'];
|
||||
$iconFlash = str_replace($dockerManPaths['images-ram'], $dockerManPaths['images-storage'], $iconRam);
|
||||
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->flushCache($this::$containersCache);
|
||||
return $this::$codes[$code] ?: 'Error code '.$code;
|
||||
if (isset($info[$name])) {
|
||||
if (isset($info[$name]['icon'])) {
|
||||
$iconRAM = $docroot.$info[$name]['icon'];
|
||||
$iconUSB = str_replace($dockerManPaths['images-ram'], $dockerManPaths['images-usb'], $iconRAM);
|
||||
if ($cache>=1 && is_file($iconRAM)) unlink($iconRAM);
|
||||
if ($cache==2 && $code===true && is_file($iconUSB)) unlink($iconUSB);
|
||||
}
|
||||
unset($info[$name]);
|
||||
DockerUtil::saveJSON($dockerManPaths['webui-info'], $info);
|
||||
}
|
||||
$this->flushCaches();
|
||||
return $code;
|
||||
}
|
||||
|
||||
public function pullImage($image, $callback=null) {
|
||||
@@ -689,8 +688,7 @@ class DockerClient {
|
||||
$image = $this->getImageName($id);
|
||||
// Attempt to remove image
|
||||
$this->getDockerJSON("/images/$id?force=1", 'DELETE', $code);
|
||||
$this->flushCache($this::$imagesCache);
|
||||
if (in_array($code, ['200', '404'])) {
|
||||
if ($code===true) {
|
||||
// Purge cached image information (only if delete was successful)
|
||||
$image = DockerUtil::ensureImageTag($image);
|
||||
$updateStatus = DockerUtil::loadJSON($dockerManPaths['update-status']);
|
||||
@@ -699,37 +697,40 @@ class DockerClient {
|
||||
DockerUtil::saveJSON($dockerManPaths['update-status'], $updateStatus);
|
||||
}
|
||||
}
|
||||
return $this::$codes[$code] ?: 'Error code '.$code;
|
||||
}
|
||||
|
||||
private function getImageDetails($id) {
|
||||
return $this->getDockerJSON("/images/$id/json");
|
||||
$this->flushCache($this::$imagesCache);
|
||||
return $code;
|
||||
}
|
||||
|
||||
public function getDockerContainers() {
|
||||
// Return cached values
|
||||
if (is_array($this::$containersCache)) return $this::$containersCache;
|
||||
$this::$containersCache = [];
|
||||
foreach ($this->getDockerJSON("/containers/json?all=1") as $obj) {
|
||||
$details = $this->getContainerDetails($obj['Id']);
|
||||
foreach ($this->getDockerJSON("/containers/json?all=1") as $ct) {
|
||||
$info = $this->getContainerDetails($ct['Id']);
|
||||
$c = [];
|
||||
$c['Image'] = DockerUtil::ensureImageTag($obj['Image']);
|
||||
$c['ImageId'] = substr(str_replace('sha256:', '', $details['Image']), 0, 12);
|
||||
$c['Name'] = substr($details['Name'], 1);
|
||||
$c['Status'] = $obj['Status'] ?: 'None';
|
||||
$c['Running'] = $details['State']['Running'];
|
||||
$c['Cmd'] = $obj['Command'];
|
||||
$c['Id'] = substr($obj['Id'], 0, 12);
|
||||
$c['Volumes'] = $details['HostConfig']['Binds'];
|
||||
$c['Created'] = $this->humanTiming($obj['Created']);
|
||||
$c['NetworkMode'] = $details['HostConfig']['NetworkMode'];
|
||||
$c['BaseImage'] = $obj['Labels']['BASEIMAGE'] ?? false;
|
||||
$c['Image'] = DockerUtil::ensureImageTag($ct['Image']);
|
||||
$c['ImageId'] = $this->extractID($ct['ImageID']);
|
||||
$c['Name'] = substr($info['Name'], 1);
|
||||
$c['Status'] = $ct['Status'] ?: 'None';
|
||||
$c['Running'] = $info['State']['Running'];
|
||||
$c['Cmd'] = $ct['Command'];
|
||||
$c['Id'] = $this->extractID($ct['Id']);
|
||||
$c['Volumes'] = $info['HostConfig']['Binds'];
|
||||
$c['Created'] = $this->humanTiming($ct['Created']);
|
||||
$c['NetworkMode'] = $ct['HostConfig']['NetworkMode'];
|
||||
$c['BaseImage'] = $ct['Labels']['BASEIMAGE'] ?? false;
|
||||
$c['Ports'] = [];
|
||||
if ($c['NetworkMode'] != 'host' && !empty($details['HostConfig']['PortBindings'])) {
|
||||
foreach ($details['HostConfig']['PortBindings'] as $port => $value) {
|
||||
list($PrivatePort, $Type) = explode('/', $port);
|
||||
$c['Ports'][] = ['IP' => $value[0]['HostIP'] ?? '0.0.0.0', 'PrivatePort' => $PrivatePort, 'PublicPort' => $value[0]['HostPort'], 'Type' => $Type ];
|
||||
}
|
||||
if (!empty($info['HostConfig']['PortBindings'])) {
|
||||
$ports = &$info['HostConfig']['PortBindings'];
|
||||
$nat = true;
|
||||
} else {
|
||||
$ports = &$info['Config']['ExposedPorts'];
|
||||
$nat = false;
|
||||
}
|
||||
$ip = $ct['NetworkSettings']['Networks'][$c['NetworkMode']]['IPAddress'];
|
||||
foreach ($ports as $port => $value) {
|
||||
list($PrivatePort, $Type) = explode('/', $port);
|
||||
$c['Ports'][] = ['IP' => $ip, 'PrivatePort' => $PrivatePort, 'PublicPort' => $nat ? $value[0]['HostPort']:$PrivatePort, 'NAT' => $nat, 'Type' => $Type ];
|
||||
}
|
||||
$this::$containersCache[] = $c;
|
||||
}
|
||||
@@ -761,56 +762,43 @@ class DockerClient {
|
||||
return null;
|
||||
}
|
||||
|
||||
private function usedBy($imageId) {
|
||||
$out = [];
|
||||
foreach ($this->getDockerContainers() as $ct) {
|
||||
if ($ct['ImageId']==$imageId) $out[] = $ct['Name'];
|
||||
}
|
||||
return $out;
|
||||
}
|
||||
|
||||
public function getDockerImages() {
|
||||
// Return cached values
|
||||
if (is_array($this::$imagesCache)) return $this::$imagesCache;
|
||||
$this::$imagesCache = [];
|
||||
foreach ($this->getDockerJSON('/images/json?all=0') as $obj) {
|
||||
foreach ($this->getDockerJSON('/images/json?all=0') as $ct) {
|
||||
$c = [];
|
||||
$c['Created'] = $this->humanTiming($obj['Created']);
|
||||
$c['Id'] = substr(str_replace('sha256:', '', $obj['Id']), 0, 12);
|
||||
$c['ParentId'] = substr(str_replace('sha256:', '', $obj['ParentId']), 0, 12);
|
||||
$c['Size'] = $this->formatBytes($obj['Size']);
|
||||
$c['VirtualSize'] = $this->formatBytes($obj['VirtualSize']);
|
||||
$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']);
|
||||
$c['Created'] = $this->humanTiming($ct['Created']);
|
||||
$c['Id'] = $this->extractID($ct['Id']);
|
||||
$c['ParentId'] = $this->extractID($ct['ParentId']);
|
||||
$c['Size'] = $this->formatBytes($ct['Size']);
|
||||
$c['VirtualSize'] = $this->formatBytes($ct['VirtualSize']);
|
||||
$c['Tags'] = array_map('htmlspecialchars', $ct['RepoTags'] ?? []);
|
||||
$c['Repository'] = vsprintf('%1$s/%2$s', preg_split("#[:\/]#", DockerUtil::ensureImageTag($ct['RepoTags'][0])));
|
||||
$c['usedBy'] = $this->usedBy($c['Id']);
|
||||
$this::$imagesCache[$c['Id']] = $c;
|
||||
}
|
||||
return $this::$imagesCache;
|
||||
}
|
||||
|
||||
public function flushCaches() {
|
||||
$this->flushCache($this::$containersCache);
|
||||
$this->flushCache($this::$imagesCache);
|
||||
}
|
||||
}
|
||||
|
||||
##################################
|
||||
## DOCKERUTIL CLASS ##
|
||||
##################################
|
||||
class DockerUtil {
|
||||
|
||||
class DockerUtil {
|
||||
public static function ensureImageTag($image) {
|
||||
list($strRepo, $strTag) = explode(':', $image.':');
|
||||
list($strRepo, $strTag) = array_map('trim', explode(':', $image.':'));
|
||||
if (strpos($strRepo, 'sha256:') === 0) {
|
||||
// sha256 was provided instead of actual repo name so truncate it for display:
|
||||
$strRepo = substr(str_replace('sha256:', '', $strRepo), 0, 12);
|
||||
$strRepo = substr($strRepo, 7, 12);
|
||||
} elseif (strpos($strRepo, '/') === false) {
|
||||
// Prefix library/ if there's no author (maybe a Docker offical image?)
|
||||
$strRepo = 'library/'.$strRepo;
|
||||
$strRepo = "library/$strRepo";
|
||||
}
|
||||
// Add :latest tag to image if it's absent
|
||||
if (empty($strTag)) $strTag = 'latest';
|
||||
return trim($strRepo).':'.trim($strTag);
|
||||
return "$strRepo:$strTag";
|
||||
}
|
||||
|
||||
public static function loadJSON($path) {
|
||||
@@ -820,7 +808,7 @@ class DockerUtil {
|
||||
}
|
||||
|
||||
public static function saveJSON($path, $content) {
|
||||
if (!is_dir(dirname($path))) @mkdir(dirname($path), 0755, true);
|
||||
if (!is_dir(dirname($path))) mkdir(dirname($path), 0755, true);
|
||||
return file_put_contents($path, json_encode($content, JSON_PRETTY_PRINT|JSON_UNESCAPED_SLASHES));
|
||||
}
|
||||
}
|
||||
|
||||
@@ -13,75 +13,56 @@
|
||||
?>
|
||||
<?
|
||||
$docroot = $docroot ?? $_SERVER['DOCUMENT_ROOT'] ?: '/usr/local/emhttp';
|
||||
|
||||
// Add the Docker JSON client
|
||||
require_once "$docroot/plugins/dynamix.docker.manager/include/DockerClient.php";
|
||||
|
||||
$user_prefs = $dockerManPaths['user-prefs'];
|
||||
$DockerClient = new DockerClient();
|
||||
$DockerClient = new DockerClient();
|
||||
$DockerTemplates = new DockerTemplates();
|
||||
$containers = $DockerClient->getDockerContainers();
|
||||
$user_prefs = $dockerManPaths['user-prefs'];
|
||||
|
||||
$all_containers = $DockerClient->getDockerContainers();
|
||||
if (!$all_containers) {
|
||||
if (!$containers) {
|
||||
echo "<tr><td colspan='8' style='text-align:center;padding-top:12px'>No Docker containers installed</td></tr>";
|
||||
return;
|
||||
}
|
||||
|
||||
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);
|
||||
foreach ($containers as $ct) $sort[] = array_search($ct['Name'],$prefs) ?? 999;
|
||||
array_multisort($sort,SORT_NUMERIC,$containers);
|
||||
unset($sort);
|
||||
}
|
||||
|
||||
// Read network settings
|
||||
extract(parse_ini_file('state/network.ini',true));
|
||||
|
||||
// Read container info
|
||||
$all_info = $DockerTemplates->getAllInfo();
|
||||
$allInfo = $DockerTemplates->getAllInfo();
|
||||
$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);
|
||||
$null = '0.0.0.0';
|
||||
$menu = [];
|
||||
|
||||
$n = 0;
|
||||
foreach ($all_containers as $ct) {
|
||||
foreach ($containers as $ct) {
|
||||
$name = $ct['Name'];
|
||||
$info = &$all_info[$name];
|
||||
$mode = $ct['NetworkMode'];
|
||||
$id = $ct['Id'];
|
||||
$imageID = $ct['ImageId'];
|
||||
$running = $ct['Running'] ? 1 : 0;
|
||||
$info = &$allInfo[$name];
|
||||
$is_autostart = $info['autostart'] ? 'true':'false';
|
||||
$updateStatus = $info['updated']=='true'||$info['updated']=='undef' ? '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'});";
|
||||
$running = $running=='true';
|
||||
$menu[] = sprintf("addDockerContainerContext('%s','%s','%s',%s,%s,%s,'%s','%s','%s','%s');", addslashes($name), addslashes($ct['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 = $running ? 'play':'square';
|
||||
$status = $running ? 'started':'stopped';
|
||||
$icon = $info['icon'] ?: '/plugins/dynamix.docker.manager/images/question.png';
|
||||
$ports = [];
|
||||
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);
|
||||
}
|
||||
} 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));
|
||||
}
|
||||
foreach ($ct['Ports'] as $port) {
|
||||
$intern = $running ? ($ct['NetworkMode']=='host' ? $host : $port['IP']) : $null;
|
||||
$extern = $running ? ($port['NAT'] ? $host : $intern) : $null;
|
||||
$ports[] = sprintf('%s:%s<i class="fa fa-arrows-h" style="margin:0 6px"></i>%s:%s', $intern, $port['PrivatePort'], $extern, $port['PublicPort']);
|
||||
}
|
||||
$paths = [];
|
||||
foreach (explode('|',$mounts) as $mount) {
|
||||
foreach ($ct['Volumes'] 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));
|
||||
@@ -115,7 +96,7 @@ foreach ($all_containers as $ct) {
|
||||
echo "<span style='color:#FF2400;white-space:nowrap;'><i class='fa fa-exclamation-triangle'></i> not available</span>";
|
||||
echo "<div class='advanced'><a class='exec' onclick=\"updateContainer('".addslashes(htmlspecialchars($name))."');\"><span style='white-space:nowrap;'><i class='fa fa-cloud-download'></i> force update</span></a></div>";
|
||||
}
|
||||
echo "</td><td>$mode</td>";
|
||||
echo "</td><td>{$ct['NetworkMode']}</td>";
|
||||
echo "<td style='white-space:nowrap'><span class='docker_readmore'>".implode('<br>',$ports)."</span></td>";
|
||||
echo "<td style='word-break:break-all'><span class='docker_readmore'>".implode('<br>',$paths)."</span></td>";
|
||||
echo "<td><input type='checkbox' class='autostart' container='".htmlspecialchars($name)."'".($info['autostart'] ? ' checked':'')."></td>";
|
||||
@@ -126,7 +107,7 @@ foreach ($all_containers as $ct) {
|
||||
foreach ($DockerClient->getDockerImages() as $image) {
|
||||
if (count($image['usedBy'])) continue;
|
||||
$id = $image['Id'];
|
||||
$menu[] = sprintf("addDockerImageContext('%s','%s');",$id,implode(',',$image['Tags']));
|
||||
$menu[] = sprintf("addDockerImageContext('%s','%s');", $id, implode(',',$image['Tags']));
|
||||
echo "<tr class='advanced'><td style='width:48px;padding:4px'>";
|
||||
echo "<div id='$id' style='display:block;cursor:pointer'>";
|
||||
echo "<div style='position:relative;width:48px;height:48px;margin:0 auto'>";
|
||||
|
||||
@@ -1,6 +1,7 @@
|
||||
<?PHP
|
||||
/* Copyright 2005-2017, Lime Technology
|
||||
* Copyright 2014-2017, Guilherme Jardim, Eric Schultz, Jon Panozzo.
|
||||
/* Copyright 2005-2018, Lime Technology
|
||||
* Copyright 2014-2018, Guilherme Jardim, Eric Schultz, Jon Panozzo.
|
||||
* Copyright 2012-2018, Bergware International.
|
||||
*
|
||||
* This program is free software; you can redistribute it and/or
|
||||
* modify it under the terms of the GNU General Public License version 2,
|
||||
@@ -12,45 +13,36 @@
|
||||
?>
|
||||
<?
|
||||
$docroot = $docroot ?? $_SERVER['DOCUMENT_ROOT'] ?: '/usr/local/emhttp';
|
||||
|
||||
require_once "$docroot/plugins/dynamix.docker.manager/include/DockerClient.php";
|
||||
|
||||
$DockerClient = new DockerClient();
|
||||
|
||||
$_REQUEST = array_merge($_GET, $_POST);
|
||||
|
||||
$action = array_key_exists('action', $_REQUEST) ? $_REQUEST['action'] : '';
|
||||
$container = array_key_exists('container', $_REQUEST) ? $_REQUEST['container'] : '';
|
||||
$image = array_key_exists('image', $_REQUEST) ? $_REQUEST['image'] : '';
|
||||
|
||||
$arrResponse = ['error' => 'Missing parameters'];
|
||||
$_REQUEST = array_merge($_GET, $_POST);
|
||||
$action = $_REQUEST['action'] ?? '';
|
||||
$container = $_REQUEST['container'] ?? '';
|
||||
$name = $_REQUEST['name'] ?? '';
|
||||
$image = $_REQUEST['image'] ?? '';
|
||||
$arrResponse = ['error' => 'Missing parameters'];
|
||||
|
||||
switch ($action) {
|
||||
|
||||
case 'start':
|
||||
if ($container) $arrResponse = ['success' => $DockerClient->startContainer($container)];
|
||||
break;
|
||||
|
||||
case 'stop':
|
||||
if ($container) $arrResponse = ['success' => $DockerClient->stopContainer($container)];
|
||||
break;
|
||||
|
||||
case 'restart':
|
||||
if ($container) $arrResponse = ['success' => $DockerClient->restartContainer($container)];
|
||||
break;
|
||||
|
||||
case 'remove_container':
|
||||
if ($container) $arrResponse = ['success' => $DockerClient->removeContainer($container,1)];
|
||||
if ($container) $arrResponse = ['success' => $DockerClient->removeContainer($name, $container, 1)];
|
||||
break;
|
||||
|
||||
case 'remove_image':
|
||||
if ($image) $arrResponse = ['success' => $DockerClient->removeImage($image)];
|
||||
break;
|
||||
|
||||
case 'remove_all':
|
||||
if ($container && $image) {
|
||||
// first: try to remove container
|
||||
$ret = $DockerClient->removeContainer($container,2);
|
||||
$ret = $DockerClient->removeContainer($name, $container, 2);
|
||||
if ($ret === true) {
|
||||
// next: try to remove image
|
||||
$arrResponse = ['success' => $DockerClient->removeImage($image)];
|
||||
@@ -60,11 +52,10 @@ switch ($action) {
|
||||
}
|
||||
}
|
||||
break;
|
||||
|
||||
case 'log':
|
||||
if ($container) {
|
||||
$since = array_key_exists('since', $_REQUEST) ? $_REQUEST['since'] : '';
|
||||
$title = array_key_exists('title', $_REQUEST) ? $_REQUEST['title'] : '';
|
||||
$since = $_REQUEST['since'] ?? '';
|
||||
$title = $_REQUEST['title'] ?? '';
|
||||
require_once "$docroot/webGui/include/ColorCoding.php";
|
||||
if (!$since) {
|
||||
readfile("$docroot/plugins/dynamix.docker.manager/log.htm");
|
||||
@@ -85,7 +76,6 @@ switch ($action) {
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
echo "<script>addLog('".addslashes("<$span>".htmlspecialchars($line)."</span>")."');</script>";
|
||||
@flush();
|
||||
};
|
||||
@@ -95,11 +85,10 @@ switch ($action) {
|
||||
exit;
|
||||
}
|
||||
break;
|
||||
|
||||
default:
|
||||
$arrResponse = ['error' => 'Unknown action \'' . $action . '\''];
|
||||
break;
|
||||
}
|
||||
|
||||
header('Content-Type: application/json');
|
||||
die(json_encode($arrResponse));
|
||||
die(json_encode($arrResponse));
|
||||
|
||||
@@ -1,8 +1,8 @@
|
||||
var eventURL = "/plugins/dynamix.docker.manager/include/Events.php";
|
||||
var eventURL = '/plugins/dynamix.docker.manager/include/Events.php';
|
||||
|
||||
function addDockerContainerContext(container, image, template, started, update, autostart, webui, id, Support, Project) {
|
||||
var opts = [{header:container, image:"/plugins/dynamix.docker.manager/images/dynamix.docker.manager.png"}];
|
||||
if (started && (webui !== "" && webui != "#")) {
|
||||
var opts = [{header:container, image:'/plugins/dynamix.docker.manager/images/dynamix.docker.manager.png'}];
|
||||
if (started && (webui !== '' && webui != '#')) {
|
||||
opts.push({text:'WebUI', icon:'fa-globe', href:webui, target:'_blank'});
|
||||
opts.push({divider:true});
|
||||
}
|
||||
@@ -11,13 +11,13 @@ 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(); eventControl({action:"stop", container:id}, 'loadlist');}});
|
||||
opts.push({text:'Restart', icon:'fa-refresh', action:function(e){e.preventDefault(); eventControl({action:"restart", container:id}, 'loadlist');}});
|
||||
opts.push({text:'Stop', icon:'fa-stop', action:function(e){e.preventDefault(); eventControl({action:'stop', container:id}, 'loadlist');}});
|
||||
opts.push({text:'Restart', icon:'fa-refresh', action:function(e){e.preventDefault(); eventControl({action:'restart', container:id}, 'loadlist');}});
|
||||
} else {
|
||||
opts.push({text:'Start', icon:'fa-play', action:function(e){e.preventDefault(); eventControl({action:"start", container:id}, 'loadlist');}});
|
||||
opts.push({text:'Start', icon:'fa-play', action:function(e){e.preventDefault(); eventControl({action:'start', container:id}, 'loadlist');}});
|
||||
}
|
||||
opts.push({divider:true});
|
||||
if (location.pathname.indexOf("/Dashboard") === 0) {
|
||||
if (location.pathname.indexOf('/Dashboard') === 0) {
|
||||
opts.push({text:'Logs', icon:'fa-navicon', action:function(e){e.preventDefault(); containerLogs(container, id);}});
|
||||
}
|
||||
if (template) {
|
||||
@@ -36,18 +36,18 @@ function addDockerContainerContext(container, image, template, started, update,
|
||||
}
|
||||
function addDockerImageContext(image, imageTag) {
|
||||
var opts = [{header:'(orphan image)'}];
|
||||
opts.push({text:"Remove", icon:"fa-trash", action:function(e){e.preventDefault(); rmImage(image, imageTag);}});
|
||||
opts.push({text:'Remove', icon:'fa-trash', action:function(e){e.preventDefault(); rmImage(image, imageTag);}});
|
||||
context.attach('#'+image, opts);
|
||||
}
|
||||
function execUpContainer(container) {
|
||||
var title = "Updating the container: "+container;
|
||||
var address = "/plugins/dynamix.docker.manager/include/CreateDocker.php?updateContainer=true&ct[]="+encodeURIComponent(container);
|
||||
var title = 'Updating the container: '+container;
|
||||
var address = '/plugins/dynamix.docker.manager/include/CreateDocker.php?updateContainer=true&ct[]='+encodeURIComponent(container);
|
||||
popupWithIframe(title, address, true, 'loadlist');
|
||||
}
|
||||
function popupWithIframe(title, cmd, reload, func) {
|
||||
pauseEvents();
|
||||
$("#iframe-popup").html('<iframe id="myIframe" frameborder="0" scrolling="yes" width="100%" height="99%"></iframe>');
|
||||
$("#iframe-popup").dialog({
|
||||
$('#iframe-popup').html('<iframe id="myIframe" frameborder="0" scrolling="yes" width="100%" height="99%"></iframe>');
|
||||
$('#iframe-popup').dialog({
|
||||
autoOpen:true,
|
||||
title:title,
|
||||
draggable:true,
|
||||
@@ -55,83 +55,83 @@ function popupWithIframe(title, cmd, reload, func) {
|
||||
height:((screen.height / 5) * 4) || 0,
|
||||
resizable:true,
|
||||
modal:true,
|
||||
show:{effect:"fade", duration:250},
|
||||
hide:{effect:"fade", duration:250},
|
||||
show:{effect:'fade', duration:250},
|
||||
hide:{effect:'fade', duration:250},
|
||||
open:function(ev, ui) {
|
||||
$("#myIframe").attr("src", cmd);
|
||||
$('#myIframe').attr('src', cmd);
|
||||
},
|
||||
close:function(event, ui) {
|
||||
if (reload && !$("#myIframe").contents().find("#canvas").length) {
|
||||
if (reload && !$('#myIframe').contents().find('#canvas').length) {
|
||||
if (func) setTimeout(func+'()',0); else location = window.location.href;
|
||||
} else {
|
||||
resumeEvents();
|
||||
}
|
||||
}
|
||||
});
|
||||
$(".ui-dialog .ui-dialog-titlebar").addClass("menu");
|
||||
$(".ui-dialog .ui-dialog-title").css("text-align", "center").css("width", "100%");
|
||||
$(".ui-dialog .ui-dialog-content").css("padding", "12");
|
||||
$('.ui-dialog .ui-dialog-titlebar').addClass('menu');
|
||||
$('.ui-dialog .ui-dialog-title').css('text-align', 'center').css('width', '100%');
|
||||
$('.ui-dialog .ui-dialog-content').css('padding', '12');
|
||||
}
|
||||
function addContainer() {
|
||||
var path = location.pathname;
|
||||
var x = path.indexOf("?");
|
||||
var x = path.indexOf('?');
|
||||
if (x!=-1) path = path.substring(0,x);
|
||||
location = path+"/AddContainer";
|
||||
location = path+'/AddContainer';
|
||||
}
|
||||
function editContainer(container, template) {
|
||||
var path = location.pathname;
|
||||
var x = path.indexOf("?");
|
||||
var x = path.indexOf('?');
|
||||
if (x!=-1) path = path.substring(0, x);
|
||||
location = path+"/UpdateContainer?xmlTemplate=edit:"+template;
|
||||
location = path+'/UpdateContainer?xmlTemplate=edit:'+template;
|
||||
}
|
||||
function updateContainer(container) {
|
||||
var body = "Update container: "+container;
|
||||
var body = 'Update container: '+container;
|
||||
swal({
|
||||
title:"Are you sure?",
|
||||
title:'Are you sure?',
|
||||
text:body,
|
||||
type:"warning",
|
||||
type:'warning',
|
||||
showCancelButton:true,
|
||||
confirmButtonColor:"#8CD4F5",
|
||||
confirmButtonText:"Yes, update it!"
|
||||
confirmButtonColor:'#8CD4F5',
|
||||
confirmButtonText:'Yes, update it!'
|
||||
},function(){
|
||||
execUpContainer(container);
|
||||
});
|
||||
}
|
||||
function rmContainer(container, image, id) {
|
||||
var body = "Remove container: "+container+"<br><br><label><input id=\"removeimagechk\" type=\"checkbox\" checked style=\"display:inline; width:unset; height:unset; margin-top:unset; margin-bottom:unset\">also remove image</label>";
|
||||
var body = 'Remove container: '+container+'<br><br><label><input id="removeimagechk" type="checkbox" checked style="display:inline;width:unset;height:unset;margin-top:unset;margin-bottom:unset">also remove image</label>';
|
||||
$('input[type=button]').prop('disabled',true);
|
||||
swal({
|
||||
title:"Are you sure?",
|
||||
title:'Are you sure?',
|
||||
text:body,
|
||||
type:"warning",
|
||||
type:'warning',
|
||||
html:true,
|
||||
showCancelButton:true,
|
||||
confirmButtonColor:"#DD6B55",
|
||||
confirmButtonText:"Yes, delete it!",
|
||||
confirmButtonColor:'#DD6B55',
|
||||
confirmButtonText:'Yes, delete it!',
|
||||
showLoaderOnConfirm:true
|
||||
},function(){
|
||||
$('#'+id).find('i').removeClass('fa-play fa-square').addClass('fa-refresh fa-spin');
|
||||
if ($("#removeimagechk").prop('checked')) {
|
||||
eventControl({action:"remove_all", container:container, image:image},'loadlist');
|
||||
},function(c){
|
||||
if (!c) {setTimeout(loadlist,0); return;}
|
||||
if ($('#removeimagechk').prop('checked')) {
|
||||
eventControl({action:'remove_all', container:id, name:container, image:image},'loadlist');
|
||||
} else {
|
||||
eventControl({action:"remove_container", container:container},'loadlist');
|
||||
eventControl({action:'remove_container', container:id, name:container},'loadlist');
|
||||
}
|
||||
});
|
||||
}
|
||||
function rmImage(image, imageName) {
|
||||
var body = "Remove image: "+$('<textarea />').html(imageName).text();
|
||||
var body = 'Remove image: '+$('<textarea />').html(imageName).text();
|
||||
$('input[type=button]').prop('disabled',true);
|
||||
swal({
|
||||
title:"Are you sure?",
|
||||
title:'Are you sure?',
|
||||
text:body,
|
||||
type:"warning",
|
||||
type:'warning',
|
||||
showCancelButton:true,
|
||||
confirmButtonColor:"#DD6B55",
|
||||
confirmButtonText:"Yes, delete it!",
|
||||
confirmButtonColor:'#DD6B55',
|
||||
confirmButtonText:'Yes, delete it!',
|
||||
showLoaderOnConfirm:true
|
||||
},function(){
|
||||
$('#'+image).find('i').removeClass('fa-play fa-square').addClass('fa-refresh fa-spin');
|
||||
eventControl({action:"remove_image", image:image},'loadlist');
|
||||
},function(c){
|
||||
if (!c) {setTimeout(loadlist,0); return;}
|
||||
eventControl({action:'remove_image', image:image},'loadlist');
|
||||
});
|
||||
}
|
||||
function eventControl(params, spin) {
|
||||
@@ -141,8 +141,8 @@ function eventControl(params, spin) {
|
||||
if (spin) setTimeout(spin+'()',500); else location=window.location.href;
|
||||
} else {
|
||||
swal({
|
||||
title:"Execution error", html:true,
|
||||
text:data.success, type:"error"
|
||||
title:'Execution error', html:true,
|
||||
text:data.success, type:'error'
|
||||
},function(){
|
||||
if (spin) setTimeout(spin+'()',500); else location=window.location.href;
|
||||
});
|
||||
@@ -151,17 +151,17 @@ function eventControl(params, spin) {
|
||||
}
|
||||
function startAll() {
|
||||
$('input[type=button]').prop('disabled',true);
|
||||
for (var i=0,ct; ct=docker[i]; i++) if (ct.state=='false') $('#'+ct.id).find('i').removeClass('fa-square').addClass('fa-refresh fa-spin');
|
||||
for (var i=0,ct; ct=docker[i]; i++) if (ct.state==0) $('#'+ct.id).find('i').removeClass('fa-square').addClass('fa-refresh fa-spin');
|
||||
$.post('/plugins/dynamix.docker.manager/include/ContainerManager.php',{action:'start'},function(){loadlist();});
|
||||
}
|
||||
function stopAll() {
|
||||
$('input[type=button]').prop('disabled',true);
|
||||
for (var i=0,ct; ct=docker[i]; i++) if (ct.state=='true') $('#'+ct.id).find('i').removeClass('fa-play').addClass('fa-refresh fa-spin');
|
||||
for (var i=0,ct; ct=docker[i]; i++) if (ct.state==1) $('#'+ct.id).find('i').removeClass('fa-play').addClass('fa-refresh fa-spin');
|
||||
$.post('/plugins/dynamix.docker.manager/include/ContainerManager.php',{action:'stop'},function(){loadlist();});
|
||||
}
|
||||
function checkAll() {
|
||||
$('input[type=button]').prop('disabled',true);
|
||||
$(".updatecolumn").html("<span style='color:#267CA8'><i class='fa fa-refresh fa-spin'></i> checking...</span>");
|
||||
$('.updatecolumn').html('<span style="color:#267CA8"><i class="fa fa-refresh fa-spin"></i> checking...</span>');
|
||||
$.post('/plugins/dynamix.docker.manager/include/DockerUpdate.php',{check:true},function(u){loadlist(u);});
|
||||
}
|
||||
function updateAll() {
|
||||
@@ -169,7 +169,7 @@ function updateAll() {
|
||||
$('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;
|
||||
var address = '/plugins/dynamix.docker.manager/include/CreateDocker.php?updateContainer=true'+list;
|
||||
popupWithIframe('Updating all Containers', address, true, 'loadlist');
|
||||
}
|
||||
function containerLogs(container, id) {
|
||||
|
||||
@@ -24,30 +24,29 @@ if (pgrep('dockerd')!==false && ($display=='icons' || $display=='docker')) {
|
||||
$user_prefs = $dockerManPaths['user-prefs'];
|
||||
$DockerClient = new DockerClient();
|
||||
$DockerTemplates = new DockerTemplates();
|
||||
$all_containers = $DockerClient->getDockerContainers();
|
||||
$containers = $DockerClient->getDockerContainers();
|
||||
$all_info = $DockerTemplates->getAllInfo();
|
||||
|
||||
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);
|
||||
foreach ($containers as $ct) $sort[] = array_search($ct['Name'],$prefs) ?? 999;
|
||||
array_multisort($sort,SORT_NUMERIC,$containers);
|
||||
}
|
||||
|
||||
foreach ($all_containers as $ct) {
|
||||
foreach ($containers as $ct) {
|
||||
$name = $ct['Name'];
|
||||
$info = &$all_info[$name];
|
||||
$id = $ct['Id'];
|
||||
$imageID = $ct['ImageId'];
|
||||
$running = $ct['Running'] ? 1:0;
|
||||
$info = &$all_info[$name];
|
||||
$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']);
|
||||
$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));
|
||||
$shape = $ct['Running'] ? 'play':'square';
|
||||
$status = $ct['Running'] ? 'started':'stopped';
|
||||
$menu[] = sprintf("addDockerContainerContext('%s','%s','%s',%s,%s,%s,'%s','%s','%s','%s');", addslashes($name), addslashes($ct['ImageId']), addslashes($template), $running, $updateStatus, $is_autostart, addslashes($webGui), $id, addslashes($support), addslashes($project));
|
||||
$shape = $running ? 'play':'square';
|
||||
$status = $running ? 'started':'stopped';
|
||||
$icon = $info['icon'] ?: '/plugins/dynamix.docker.manager/images/question.png';
|
||||
echo "<div class='Panel $status'>";
|
||||
echo "<div id='$id' style='display:block; cursor:pointer'>";
|
||||
@@ -84,7 +83,7 @@ if (pgrep('libvirtd')!==false && ($display=='icons' || $display=='vms')) {
|
||||
$template = $lv->_get_single_xpath_result($res, '//domain/metadata/*[local-name()=\'vmtemplate\']/@name');
|
||||
if (empty($template)) $template = 'Custom';
|
||||
$log = (is_file("/var/log/libvirt/qemu/$vm.log") ? "libvirt/qemu/$vm.log" : '');
|
||||
$menu[] = sprintf("addVMContext('%s','%s','%s','%s','%s','%s');",addslashes($vm),addslashes($uuid),addslashes($template),$state,addslashes($vnc),addslashes($log));
|
||||
$menu[] = sprintf("addVMContext('%s','%s','%s','%s','%s','%s');", addslashes($vm), addslashes($uuid), addslashes($template), $state, addslashes($vnc), addslashes($log));
|
||||
$vmicon = $lv->domain_get_icon_url($res);
|
||||
echo renderVMContentIcon($uuid, $vm, $vmicon, $state);
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user