getDockerContainers(); $images = $DockerClient->getDockerImages(); $user_prefs = $dockerManPaths['user-prefs']; $autostart_file = $dockerManPaths['autostart-file']; if (!$containers && !$images) { echo ""._('No Docker containers installed').""; return; } if (file_exists($user_prefs)) { $prefs = (array)@parse_ini_file($user_prefs); $sort = []; foreach ($containers as $ct) $sort[] = array_search($ct['Name'],$prefs); array_multisort($sort,SORT_NUMERIC,$containers); unset($sort); } // get host interface IP address $host = DockerUtil::host(); // Read container info $allInfo = $DockerTemplates->getAllInfo(); $docker = []; $null = '0.0.0.0'; $autostart = (array)@file($autostart_file,FILE_IGNORE_NEW_LINES); $names = array_map('var_split',$autostart); // Grab Tailscale json from container function tailscale_stats($name) { exec("docker exec -i ".$name." /bin/sh -c \"tailscale status --json | jq '{Self: .Self, ExitNodeStatus: .ExitNodeStatus, Version: .Version}'\" 2>/dev/null", $TS_stats); if (!empty($TS_stats)) { $TS_stats = implode("\n", $TS_stats); return json_decode($TS_stats, true); } return ''; } // Download Tailscal JSON and return Array, refresh file if older than 24 hours function tailscale_json_dl($file, $url) { $dl_status = 0; if (!is_dir('/tmp/tailscale')) { mkdir('/tmp/tailscale', 0777, true); } if (!file_exists($file)) { exec("wget -T 3 -q -O ".$file." ".$url, $output, $dl_status); } else { $fileage = time() - filemtime($file); if ($fileage > 86400) { unlink($file); exec("wget -T 3 -q -O ".$file." ".$url, $output, $dl_status); } } if ($dl_status === 0) { return json_decode(@file_get_contents($file), true); } elseif ($dl_status === 0 && is_file($file)) { return json_decode(@file_get_contents($file), true); } else { unlink($file); return ''; } } // Grab Tailscale DERP map JSON $TS_derp_url = 'https://login.tailscale.com/derpmap/default'; $TS_derp_file = '/tmp/tailscale/tailscale-derpmap.json'; $TS_derp_list = tailscale_json_dl($TS_derp_file, $TS_derp_url); // Grab Tailscale version JSON $TS_version_url = 'https://pkgs.tailscale.com/stable/?mode=json'; $TS_version_file = '/tmp/tailscale/tailscale-latest-version.json'; // Extract tarbal version string $TS_latest_version = tailscale_json_dl($TS_version_file, $TS_version_url); if (!empty($TS_latest_version)) { $TS_latest_version = $TS_latest_version["TarballsVersion"]; } function my_lang_time($text) { [$number, $text] = my_explode(' ',$text,2); return sprintf(_("%s $text"),$number); } function my_lang_log($text) { global $language; if (isset($language['healthy'])) $text = str_replace('healthy',$language['healthy'],$text); if (isset($language['Exited'])) $text = str_replace('Exited',$language['Exited'],$text); if (strpos($text,'ago')!==false) { [$t1,$t2] = my_explode(') ',$text); return $t1.'): '.my_lang_time($t2); } return _(_($text),2); } foreach ($containers as $ct) { $name = $ct['Name']; $id = $ct['Id']; $info = &$allInfo[$name]; $running = $info['running'] ? 1 : 0; $paused = $info['paused'] ? 1 : 0; $is_autostart = $info['autostart'] ? 'true':'false'; $composestack = isset($ct['ComposeProject']) ? $ct['ComposeProject'] : ''; $updateStatus = substr($ct['NetworkMode'], -4) == ':???' ? 2 : ($info['updated'] == 'true' ? 0 : ($info['updated'] == 'false' ? 1 : 3)); $template = $info['template']??''; $shell = $info['shell']??''; $webGui = html_entity_decode($info['url']??''); $TShostname = isset($ct['TSHostname']) ? $ct['TSHostname'] : ''; $TSwebGui = html_entity_decode($info['TSurl']??''); $support = html_entity_decode($info['Support']??''); $project = html_entity_decode($info['Project']??''); $registry = html_entity_decode($info['registry']??''); $donateLink = html_entity_decode($info['DonateLink']??''); $readme = html_entity_decode($info['ReadMe']??''); $menu = sprintf("onclick=\"addDockerContainerContext('%s','%s','%s',%s,%s,%s,%s,'%s','%s','%s','%s','%s','%s','%s', '%s','%s')\"", addslashes($name), addslashes($ct['ImageId']), addslashes($template), $running, $paused, $updateStatus, $is_autostart, addslashes($webGui), addslashes($TSwebGui), $shell, $id, addslashes($support), addslashes($project),addslashes($registry),addslashes($donateLink),addslashes($readme)); $docker[] = "docker.push({name:'$name',id:'$id',state:$running,pause:$paused,update:$updateStatus});"; $shape = $running ? ($paused ? 'pause' : 'play') : 'square'; $status = $running ? ($paused ? 'paused' : 'started') : 'stopped'; $color = $status=='started' ? 'green-text' : ($status=='paused' ? 'orange-text' : 'red-text'); $update = $updateStatus==1 && !empty($compose) ? 'blue-text' : ''; $icon = $info['icon'] ?: '/plugins/dynamix.docker.manager/images/question.png'; $image = substr($icon,-4)=='.png' ? "" : (substr($icon,0,5)=='icon-' ? "" : ""); $wait = var_split($autostart[array_search($name,$names)]??'',1); $networks = []; $network_ips = []; $ports_internal = []; $ports_external = []; if (isset($ct['Ports']['vlan'])) { foreach ($ct['Ports']['vlan'] as $i) { $ports_external[] = sprintf('%s', $i); } $ports_internal[0] = sprintf('%s', 'all'); } foreach($ct['Networks'] as $netName => $netVals) { $networks[] = $netName; $network_ips[] = $running ? $netVals['IPAddress'] : null; if (isset($ct['Networks']['host'])) { $ports_external[] = sprintf('%s', $netVals['IPAddress']); $ports_internal[0] = sprintf('%s', 'all'); } elseif (!isset($ct['Ports']['vlan']) || strpos($ct['NetworkMode'],'container:')!==false) { foreach ($ct['Ports'] as $port) { if (_var($port,'PublicPort') && _var($port,'Driver') == 'bridge') { $ports_external[] = sprintf('%s:%s', $host, strtoupper(_var($port,'PublicPort'))); } if ((!isset($ct['Networks']['host'])) || (!isset($ct['Networks']['vlan']))) { $ports_internal[] = sprintf('%s:%s', _var($port,'PrivatePort'), strtoupper(_var($port,'Type'))); } } } } $paths = []; $ct['Volumes'] = is_array($ct['Volumes']) ? $ct['Volumes'] : []; foreach ($ct['Volumes'] as $mount) { [$host_path,$container_path,$access_mode] = my_explode(':',$mount,3); $paths[] = sprintf('%s%s', htmlspecialchars($container_path), $access_mode=='ro'?'long-arrow-left':'arrows-h', htmlspecialchars($host_path)); } echo ""; if ($template && empty($composestack)) { $appname = "".htmlspecialchars($name).""; } else { $appname = htmlspecialchars($name); } echo "$image$appname
"._($status).(!empty($composestack) ? '
Compose Stack: '.$composestack : '')."
"; echo "
"._('Container ID').": $id
"; if ($ct['BaseImage']) echo "".htmlspecialchars($ct['BaseImage'])."
"; echo _('By').": "; $registry = $info['registry']; ['strRepo' => $author, 'strTag' => $version] = DockerUtil::parseImageTag($ct['Image']); if ($registry) { echo "".htmlspecialchars(compress($author,24)).""; } else { echo htmlspecialchars(compress($author,24)); } echo "
"; switch ($updateStatus) { case 0: if ($ct['Manager'] == "dockerman") { echo " "._('up-to-date').""; echo "
"._('force update')."
"; } elseif (!empty($composestack)) { echo "
"._("Compose")."
"; } else { echo "
"._("3rd Party")."
"; } break; case 1: echo "
"._('update ready')."
"; if ($ct['Manager'] == "dockerman") { echo " "._('apply update').""; } elseif (!empty($composestack)) { echo "
Compose
"; } else { echo "
3rd Party
"; } break; case 2: echo "
"._('rebuild ready')."
"; echo " "._('rebuilding').""; break; default: if ($ct['Manager'] == "dockerman") { echo " "._('not available').""; echo "
"._('force update')."
"; } elseif (!empty($composestack)) { echo "
"._("Compose")."
"; } else { echo "
"._("3rd Party")."
"; } break; } // Check if Tailscale for container is enabled by checking if TShostname is set $TS_status = ''; if (!empty($TShostname)) { if ($running) { // Get stats from container and check if they are not empty $TSstats = tailscale_stats($name); if (!empty($TSstats)) { // Construct TSinfo from TSstats $TSinfo = ''; if (!$TSstats["Self"]["Online"]) { $TSinfo .= "
"._("Online").":
"._("Please check the logs")."!
"; } else { $TS_version = explode('-', $TSstats["Version"])[0]; if (!empty($TS_version)) { if (!empty($TS_latest_version)) { if (version_compare($TS_version, $TS_latest_version, '<')) { $TSinfo .= "
"._("Tailscale").":v".$TS_version." ➔ v".$TS_latest_version." "._("available")."!
"; } else { $TSinfo .= "
"._("Tailscale").":v".$TS_version."
"; } } else { $TSinfo .= "
".("Tailscale").":v".$TS_version."
"; } } $TSinfo .= "
"._("Online").":
"; $TS_DNSName = $TSstats["Self"]["DNSName"]; $TS_HostNameActual = substr($TS_DNSName, 0, strpos($TS_DNSName, '.')); if (strcasecmp($TS_HostNameActual, $TShostname) !== 0 && !empty($TS_DNSName)) { $TSinfo .= "
"._("Hostname").":"._("Real Hostname")." ➔ ".$TS_HostNameActual."
"; } else { $TSinfo .= "
"._("Hostname").":".$TShostname."
"; } // Map region relay code to cleartext region if TS_derp_list is available if (!empty($TS_derp_list)) { foreach ($TS_derp_list['Regions'] as $region) { if ($region['RegionCode'] === $TSstats["Self"]["Relay"]) { $TSregion = $region['RegionName']; break; } } if (!empty($TSregion)) { $TSinfo .= "
"._("DERP Relay").":".$TSregion."
"; } else { $TSinfo .= "
"._("DERP Relay").":".$TSstats["Self"]["Relay"]."
"; } } else { $TSinfo .= "
"._("DERP Relay").":".$TSstats["Self"]["Relay"]."
"; } if (!empty($TSstats["Self"]["TailscaleIPs"])) { $TSinfo .= "
"._("Addresses").":".implode("
", $TSstats["Self"]["TailscaleIPs"])."
"; } if (!empty($TSstats["Self"]["PrimaryRoutes"])) { $TSinfo .= "
"._("Routes").":".implode("
", $TSstats["Self"]["PrimaryRoutes"])."
"; } if ($TSstats["Self"]["ExitNodeOption"]) { $TSinfo .= "
"._("Is Exit Node").":
"; } else { if (!empty($TSstats["ExitNodeStatus"])) { $TS_exit_node_status = ($TSstats["ExitNodeStatus"]["Online"]) ? "✅" : "❌"; $TSinfo .= "
"._("Exit Node").":".strstr($TSstats["ExitNodeStatus"]["TailscaleIPs"][0], '/', true)." | Status: ".$TS_exit_node_status ."
"; } else { $TSinfo .= "
"._("Is Exit Node").":
"; } } if (!empty($TSwebGui)) { $TSinfo .= "
"._("URL").":".$TSwebGui."
"; } if (!empty($TSstats["Self"]["KeyExpiry"])) { $TS_expiry = new DateTime($TSstats["Self"]["KeyExpiry"]); $current_Date = new DateTime(); $TS_expiry_formatted = $TS_expiry->format('Y-m-d'); $TS_expiry_diff = $current_Date->diff($TS_expiry); if ($TS_expiry_diff->invert) { $TSinfo .= "
"._("Key Expiry").":❌ "._("Expired! Renew/Disable key expiry!")."
"; } else { $TSinfo .= "
"._("Key Expiry").":".$TS_expiry_formatted." (".$TS_expiry_diff->days." days)
"; } } } // Display TSinfo if data was fetched correctly $TS_status = "
Tailscale
"; } else { // Display message to refresh page if Tailscale in the container wasn't maybe ready to get the data $TS_status = "
Tailscale
"; } } else { // Display message that container isn't running $TS_status = "
Tailscale
"; } } echo "
".compress(_($version),12,0)."
"; echo " ".implode('
',$networks).$TS_status."
"; echo " ".implode('
',$network_ips)."
"; echo "".implode('
',$ports_internal)."
"; echo "".implode('
',$ports_external)."
"; echo "".implode('
',$paths)."
"; echo "0%
"; echo "
0 / 0"; if (empty($composestack)) { if ($ct['Manager'] == "dockerman") { echo ""; } else { echo " 3rd Party"; } } else { echo " Compose"; } echo ""; echo "
".htmlspecialchars(str_replace('Up',_('Uptime').':',my_lang_log($ct['Status'])))."
"._('Created').": ".htmlspecialchars(my_lang_time($ct['Created']))."
"; } foreach ($images as $image) { if (count($image['usedBy'])) continue; $id = $image['Id']; $menu = sprintf("onclick=\"addDockerImageContext('%s','%s')\"", $id, implode(',',$image['Tags'])); echo ""; echo "("._('orphan image').")
"._('stopped')."
"; echo ""._('Image ID').": $id
"; echo implode(', ',$image['Tags']); echo ""._('Created')." ".htmlspecialchars(_($image['Created'],0)).""; } echo "\0".implode($docker)."\0".(pgrep('rc.docker')!==false ? 1:0); ?>