From cb57198de0ae967ebfbc72a86365747c4c0f097c Mon Sep 17 00:00:00 2001 From: gfjardim Date: Sun, 20 Mar 2016 19:57:06 -0300 Subject: [PATCH 1/4] Set default port mode to TCP and path mode to RW --- .../include/CreateDocker.php | 19 ++++++++++++++++--- 1 file changed, 16 insertions(+), 3 deletions(-) diff --git a/plugins/dynamix.docker.manager/include/CreateDocker.php b/plugins/dynamix.docker.manager/include/CreateDocker.php index f884d2e97..edc6b7e13 100644 --- a/plugins/dynamix.docker.manager/include/CreateDocker.php +++ b/plugins/dynamix.docker.manager/include/CreateDocker.php @@ -248,7 +248,20 @@ function xmlToVar($xml) { foreach ($xml->Config as $config) { $c = []; $c['Value'] = strlen(xml_decode($config)) ? xml_decode($config) : xml_decode($config['Default']); - foreach ($config->attributes() as $key => $value) $c[$key] = xml_decode(xml_decode($value)); + foreach ($config->attributes() as $key => $value) { + $value = xml_decode($value); + if ($key == 'Mode') { + switch (xml_decode($config['Type'])) { + case 'Path': + $value = (strtolower($value) == 'rw' || strtolower($value) == 'rw,slave' || strtolower($value) == 'ro') ? $value : "rw"; + break; + case 'Port': + $value = (strtolower($value) == 'tcp' || strtolower($value) == 'udp' ) ? $value : "tcp"; + break; + } + } + $c[$key] = $value; + } $out['Config'][] = $c; } } @@ -272,7 +285,7 @@ function xmlToVar($xml) { 'Target' => xml_decode($port->ContainerPort), 'Default' => xml_decode($port->HostPort), 'Value' => xml_decode($port->HostPort), - 'Mode' => xml_decode($port->Protocol), + 'Mode' => xml_decode($port->Protocol) ? xml_decode($port->Protocol) : "tcp", 'Description' => '', 'Type' => 'Port', 'Display' => 'always', @@ -292,7 +305,7 @@ function xmlToVar($xml) { 'Target' => xml_decode($vol->ContainerDir), 'Default' => xml_decode($vol->HostDir), 'Value' => xml_decode($vol->HostDir), - 'Mode' => xml_decode($vol->Mode), + 'Mode' => xml_decode($vol->Mode) ? xml_decode($vol->Mode) : "rw", 'Description' => '', 'Type' => 'Path', 'Display' => 'always', From 357b0bb92ade7d6f48d9fabbe96022ede424dc46 Mon Sep 17 00:00:00 2001 From: gfjardim Date: Sun, 20 Mar 2016 20:01:48 -0300 Subject: [PATCH 2/4] Add the ability to keep templates in sync with the author's modifications --- .../include/CreateDocker.php | 14 +++ .../include/DockerClient.php | 96 +++++++++++++++++++ 2 files changed, 110 insertions(+) diff --git a/plugins/dynamix.docker.manager/include/CreateDocker.php b/plugins/dynamix.docker.manager/include/CreateDocker.php index edc6b7e13..1c4aa41eb 100644 --- a/plugins/dynamix.docker.manager/include/CreateDocker.php +++ b/plugins/dynamix.docker.manager/include/CreateDocker.php @@ -178,8 +178,10 @@ function postToXML($post, $setOwnership = false) { $xml->Overview = xml_encode($post['contOverview']); $xml->Category = xml_encode($post['contCategory']); $xml->WebUI = xml_encode($post['contWebUI']); + $xml->TemplateURL = xml_encode($post['contTemplateURL']); $xml->Icon = xml_encode($post['contIcon']); $xml->ExtraParams = xml_encode($post['contExtraParams']); + $xml->DateInstalled = xml_encode(strtotime("now")); # V1 compatibility $xml->Description = xml_encode($post['contOverview']); @@ -240,6 +242,7 @@ function xmlToVar($xml) { $out['Overview'] = stripslashes(xml_decode($xml->Overview)); $out['Category'] = xml_decode($xml->Category); $out['WebUI'] = xml_decode($xml->WebUI); + $out['TemplateURL'] = xml_decode($xml->TemplateURL); $out['Icon'] = xml_decode($xml->Icon); $out['ExtraParams'] = xml_decode($xml->ExtraParams); @@ -1203,6 +1206,17 @@ $showAdditionalInfo = ''; + + Template URL: + + + + +
+

This URL is used to keep the template updated.

+
+ + Icon URL: diff --git a/plugins/dynamix.docker.manager/include/DockerClient.php b/plugins/dynamix.docker.manager/include/DockerClient.php index 9f9f8fddc..a323f60ac 100644 --- a/plugins/dynamix.docker.manager/include/DockerClient.php +++ b/plugins/dynamix.docker.manager/include/DockerClient.php @@ -65,11 +65,13 @@ class DockerTemplates { if ($this->verbose) echo $m."\n"; } + public function download_url($url, $path = "", $bg = false) { exec("curl --max-time 60 --silent --insecure --location --fail ".($path ? " -o ".escapeshellarg($path) : "")." ".escapeshellarg($url)." ".($bg ? ">/dev/null 2>&1 &" : "2>/dev/null"), $out, $exit_code); return ($exit_code === 0) ? implode("\n", $out) : false; } + public function listDir($root, $ext = null) { $iter = new RecursiveIteratorIterator( new RecursiveDirectoryIterator($root, @@ -331,6 +333,10 @@ class DockerTemplates { $tmp['template'] = $this->getUserTemplate($name); } + if ($reload) { + $DockerUpdate->updateUserTemplate($ct); + } + $this->debug("\n$name"); foreach ($tmp as $c => $d) $this->debug(sprintf(" %-10s: %s", $c, $d)); $new_info[$name] = $tmp; @@ -379,6 +385,16 @@ class DockerUpdate{ } + private function xml_encode($string) { + return htmlspecialchars($string, ENT_XML1, 'UTF-8'); + } + + + private function xml_decode($string) { + return strval(html_entity_decode($string, ENT_XML1, 'UTF-8')); + } + + 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; @@ -503,6 +519,86 @@ class DockerUpdate{ $this->debug("Update status: Image='${image}', Local='${version}', Remote='${version}'"); file_put_contents($update_file, json_encode($updateStatus, JSON_PRETTY_PRINT | JSON_UNESCAPED_SLASHES)); } + + + public function updateUserTemplate($Container) { + $changed = false; + $DockerTemplates = new DockerTemplates(); + $validElements = array( 0 => "Support", + 1 => "Overview", + 2 => "Category", + 3 => "WebUI", + 4 => "Icon"); + + $validAttributes = array( 0 => "Name", + 1 => "Default", + 2 => "Description", + 3 => "Display", + 4 => "Required", + 5 => "Mask"); + // Get user template file and abort if fail + if ( ! $file = $DockerTemplates->getUserTemplate($Container) ) 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 ( $template && ! isset($template->TemplateURL) ) 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))) return null; + // Try to load the downloaded template and abort if fail. + if (! $remote_template = @simplexml_load_string($dl)) return null; + // Loop through remote template elements and compare them to local ones + foreach ($remote_template->children() as $name => $remote_element) { + $name = $this->xml_decode($name); + // Compare through validElements + if ($name != "Config" && in_array($name, $validElements)) { + $local_element = $template->xpath("//$name")[0]; + $rvalue = $this->xml_decode($remote_element); + $value = $this->xml_decode($local_element); + // Values changed, updating. + if ( $value != $rvalue) { + $local_element->{0} = $this->xml_encode($rvalue); + $changed = true; + } + // Compare atributes on Config if they are in the validAttributes list + } else if ($name == "Config"){ + $type = $this->xml_decode($remote_element['Type']); + $target = $this->xml_decode($remote_element['Target']); + if ($type == "Port") { + $mode = $this->xml_decode($remote_element['Mode']); + $local_element = $template->xpath("//Config[@Type='$type'][@Target='$target'][@Mode='$mode']")[0]; + } else { + $local_element = $template->xpath("//Config[@Type='$type'][@Target='$target']")[0]; + } + // If the local template already have the pertinent Config element, loop through it's attributes and update those on validAttributes + if (! empty($local_element)) { + foreach ($remote_element->attributes() as $key => $value) { + $rvalue = $this->xml_decode($value); + $value = $this->xml_decode($local_element[$key]); + // Values changed, updating. + if ($value != $rvalue && in_array($key, $validAttributes)) { + $local_element[$key] = $this->xml_encode($rvalue); + $changed = true; + } + } + // New Config element, add it to the local template + } else { + $dom_remote = dom_import_simplexml($remote_element); + $new_element = $dom_local->ownerDocument->importNode($dom_remote, TRUE); + $dom_local->appendChild($new_element); + $changed = true; + } + } + } + if ($changed) { + // Format output and save to file if there were any commited changes + $dom = new DOMDocument('1.0'); + $dom->preserveWhiteSpace = false; + $dom->formatOutput = true; + $dom->loadXML($template->asXML()); + file_put_contents($file, $dom->saveXML()); + } + } } From fc5ef34aaf745b4dd3d34dffd14c15ff5988175e Mon Sep 17 00:00:00 2001 From: gfjardim Date: Sun, 20 Mar 2016 20:07:24 -0300 Subject: [PATCH 3/4] Bug fixes MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit - Add: ability to hide edit/remove buttons in basic view; - Add: hide advanced configurations in a collapsable menu - easier than toggle Advanced View; - Fix: Load edited Overview when toggle Views; - Fix: Variable select box doesn’t have the same textPath width; - Fix: limit the height of Categories dropdown menu to 200px. - Fix: dropdownchecklist CSS for white theme --- .../include/CreateDocker.php | 68 +++++++++++++++---- .../styles/style-white.css | 50 ++++++++++++++ 2 files changed, 104 insertions(+), 14 deletions(-) diff --git a/plugins/dynamix.docker.manager/include/CreateDocker.php b/plugins/dynamix.docker.manager/include/CreateDocker.php index 1c4aa41eb..6afeaf9ae 100644 --- a/plugins/dynamix.docker.manager/include/CreateDocker.php +++ b/plugins/dynamix.docker.manager/include/CreateDocker.php @@ -701,6 +701,9 @@ $showAdditionalInfo = ''; .label-warning{background-color:#f89406;} .label-success{background-color:#468847;} .label-important{background-color:#b94a48;} + .selectVariable{ + width: 320px; + } @@ -724,9 +727,10 @@ $showAdditionalInfo = ''; $('.advanced-switch').switchButton({ labels_placement: "left", on_label: 'Advanced View', off_label: 'Basic View'}); $('.advanced-switch').change(function () { var status = $(this).is(':checked'); - toggleRows('advanced,.hidden', status, 'basic'); - $("#catSelect").dropdownchecklist("destroy"); - $("#catSelect").dropdownchecklist({emptyText:'Select categories...', maxDropHeight:150, width:300, explicitClose:'...close'}); + toggleRows('advanced', status, 'basic'); + load_contOverview(); + $("#catSelect").dropdownchecklist("destroy"); + $("#catSelect").dropdownchecklist({emptyText:'Select categories...', maxDropHeight:200, width:300, explicitClose:'...close'}); }); }); @@ -766,7 +770,7 @@ $showAdditionalInfo = ''; opts.Buttons, (opts.Required == "true") ? "required" : "" ); - newConfig = "
"+newConfig+"
"; + newConfig = "
"+newConfig+"
"; newConfig = $($.parseHTML(newConfig)); value = newConfig.find("input[name='confValue[]']"); if (opts.Type == "Path") { @@ -775,7 +779,7 @@ $showAdditionalInfo = ''; value.attr("onclick", "openFileBrowser(this,$(this).val(),'',false,true);") } else if (opts.Type == "Variable" && opts.Default.split("|").length > 1) { var valueOpts = opts.Default.split("|"); - var newValue = ""; for (var i = 0; i < valueOpts.length; i++) { newValue += ""; } @@ -902,17 +906,26 @@ $showAdditionalInfo = ''; Opts[e] = getVal(Element, e); }); Opts.Description = (Opts.Description.length) ? Opts.Description : "Container "+Opts.Type+": "+Opts.Target; - if (Opts.Required == "true") { + if (Opts.Display == "always-hide" || Opts.Display == "advanced-hide") { Opts.Buttons = " "; Opts.Buttons += ""; } else { Opts.Buttons = " "; Opts.Buttons += ""; } - Opts.Number = confNum; + Opts.Number = num; newConf = makeConfig(Opts); - config.removeClass("always advanced hidden").addClass(Opts.Display); - $("#ConfigNum" + num).html(newConf); + if (config.hasClass("config_"+Opts.Display)) { + config.html(newConf); + config.removeClass("config_always config_always-hide config_advanced config_advanced-hide").addClass("config_"+Opts.Display); + } else { + config.remove(); + if (Opts.Display == 'advanced' || Opts.Display == 'advanced-hide') { + $("#configLocationAdvanced").append(newConf); + } else { + $("#configLocation").append(newConf); + } + } reloadTriggers(); }, Cancel: function() { @@ -1282,6 +1295,13 @@ $showAdditionalInfo = '';
+ + + + + +
  Show advanced settings ...
+ @@ -1346,8 +1366,9 @@ $showAdditionalInfo = '';
Required:
@@ -1409,9 +1430,25 @@ $showAdditionalInfo = ''; function reloadTriggers() { $(".basic").toggle(!$(".advanced-switch:first").is(":checked")); $(".advanced").toggle($(".advanced-switch:first").is(":checked")); - $(".hidden").toggle($(".advanced-switch:first").is(":checked")); $(".numbersOnly").keypress(function(e){if(e.which != 45 && e.which != 8 && e.which != 0 && (e.which < 48 || e.which > 57)){return false;}}); } + function toggleReadmore() { + var readm = $('#readmore_toggle'); + if ( readm.hasClass('readmore_collapsed') ) { + readm.removeClass('readmore_collapsed').addClass('readmore_expanded'); + $('#configLocationAdvanced').slideDown('fast'); + readm.find('a').html(' Hide advanced settings ...'); + } else { + $('#configLocationAdvanced').slideUp('fast'); + readm.removeClass('readmore_expanded').addClass('readmore_collapsed'); + readm.find('a').html(' Show advanced settings ...'); + } + } + function load_contOverview() { + var new_overview = $("textarea[name='contOverview']").val(); + new_overview = new_overview.replaceAll("[","<").replaceAll("]",">"); + $("div[name='contDescription']").html(new_overview); + } $(function() { // Load container info on page load if (typeof Settings != 'undefined') { @@ -1447,7 +1484,7 @@ $showAdditionalInfo = ''; confNum += 1; Opts = Settings.Config[i]; Opts.Description = (Opts.Description.length) ? Opts.Description : "Container "+Opts.Type+": "+Opts.Target; - if (Opts.Required == "true") { + if (Opts.Display == "always-hide" || Opts.Display == "advanced-hide") { Opts.Buttons = " "; Opts.Buttons += ""; } else { @@ -1456,8 +1493,11 @@ $showAdditionalInfo = ''; } Opts.Number = confNum; newConf = makeConfig(Opts); - $("#configLocation").append(newConf); - reloadTriggers(); + if (Opts.Display == 'advanced' || Opts.Display == 'advanced-hide') { + $("#configLocationAdvanced").append(newConf); + } else { + $("#configLocation").append(newConf); + } } } else { $('#canvas').find('#Overview:first').hide(); diff --git a/plugins/dynamix.docker.manager/styles/style-white.css b/plugins/dynamix.docker.manager/styles/style-white.css index e69de29bb..6695618ff 100644 --- a/plugins/dynamix.docker.manager/styles/style-white.css +++ b/plugins/dynamix.docker.manager/styles/style-white.css @@ -0,0 +1,50 @@ +.ui-dropdownchecklist .ui-state-default { + background:-webkit-radial-gradient(#F4F4F4,#FCFCFC); + background:linear-gradient(#F4F4F4,#FCFCFC); + border:none; + box-shadow:0 2px 0 #E0E0E0,inset 0 -1px #FFFFFF; + border-radius:4px; + outline:none; + cursor:pointer; + height:22px; + line-height:22px; +} +.ui-dropdownchecklist-group { + font-weight:normal; + font-style:italic; + padding:1px 9px 1px 8px; +} +.ui-dropdownchecklist-selector { + border:1px solid #E0E0E0; + display:inline-block; + cursor:pointer; + padding:1px 9px 1px 8px; +} +.ui-dropdownchecklist-selector-wrapper { + vertical-align:middle; + font-size:0; +} +.ui-state-active { + background:linear-gradient(#E8E8E8,#F8F8F8); + background:-webkit-radial-gradient(#E8E8E8,#F8F8F8); +} +.ui-dropdownchecklist-dropcontainer { + background:#FFFFFF; + border:1px solid #E0E0E0; +} +.ui-state-disabled { + background:linear-gradient(#F0F0F0,#F8F8F8); + background:-webkit-radial-gradient(#F0F0F0,#F8F8F8); +} +.ui-dropdownchecklist-indent { + padding-left:7px; +} +.ui-dropdownchecklist-text { + color:#303030; + font-size:11px; +} +.ui-dropdownchecklist .ui-widget-content .ui-state-default { + background:#FFFFFF; + border:0px; +} + From 16a931b807791b069982bcb8ed2d857e0e48dc7c Mon Sep 17 00:00:00 2001 From: gfjardim Date: Sun, 20 Mar 2016 20:08:55 -0300 Subject: [PATCH 4/4] Add Template Authoring Mode. --- .../DockerSettings.page | 15 +++++ .../include/CreateDocker.php | 66 ++++++++++++------- 2 files changed, 56 insertions(+), 25 deletions(-) diff --git a/plugins/dynamix.docker.manager/DockerSettings.page b/plugins/dynamix.docker.manager/DockerSettings.page index 77853e9a6..9c56a13a8 100644 --- a/plugins/dynamix.docker.manager/DockerSettings.page +++ b/plugins/dynamix.docker.manager/DockerSettings.page @@ -129,6 +129,21 @@ if (file_exists($realfile)) { +
+
+
Template Authoring Mode:
+
+ +
+
+
+

If set to Yes, when creating/editing containers the interface will be present with some extra fields related to template authoring.

+
+
+
 
diff --git a/plugins/dynamix.docker.manager/include/CreateDocker.php b/plugins/dynamix.docker.manager/include/CreateDocker.php index 6afeaf9ae..1552d5dad 100644 --- a/plugins/dynamix.docker.manager/include/CreateDocker.php +++ b/plugins/dynamix.docker.manager/include/CreateDocker.php @@ -442,25 +442,9 @@ if (isset($_POST['contName'])) { // Get the command line list($cmd, $Name, $Repository) = xmlToCommand($postXML, $create_paths); - // Run dry - if ($dry_run) { - echo "

XML

"; - echo "
".htmlentities($postXML)."
"; - echo "

COMMAND:

"; - echo "
".htmlentities($cmd)."
"; - echo '

'; - goto END; - } - readfile("/usr/local/emhttp/plugins/dynamix.docker.manager/log.htm"); @flush(); - // Will only pull image if it's absent - if (!$DockerClient->doesImageExist($Repository)) { - // Pull image - pullImage($Name, $Repository); - } - // Saving the generated configuration file. $userTmplDir = $dockerManPaths['templates-user']; if (!is_dir($userTmplDir)) { @@ -471,6 +455,23 @@ if (isset($_POST['contName'])) { file_put_contents($filename, $postXML); } + // Run dry + if ($dry_run) { + echo "

XML

"; + echo "
".htmlentities($postXML)."
"; + echo "

COMMAND:

"; + echo "
".htmlentities($cmd)."
"; + echo "
"; + echo "

"; + goto END; + } + + // Will only pull image if it's absent + if (!$DockerClient->doesImageExist($Repository)) { + // Pull image + pullImage($Name, $Repository); + } + $startContainer = true; // Remove existing container @@ -628,8 +629,10 @@ if ($_GET['xmlTemplate']) { echo ""; } } - +$authoringMode = ($dockercfg["DOCKER_AUTHORING_MODE"] == "yes") ? true : false; +$authoring = $authoringMode ? 'advanced' : 'noshow'; $showAdditionalInfo = ''; + ?> @@ -672,6 +675,7 @@ $showAdditionalInfo = ''; .toggleMode:hover,.toggleMode:focus,.toggleMode:active,.toggleMode .active{color:#625D5D;} .basic{display:table-row;} .advanced{display:none;} + .noshow{display: none;} .required:after {content: " * ";color: #E80000} .switch-wrapper { @@ -752,7 +756,11 @@ $showAdditionalInfo = ''; }); }; } - + if (!String.prototype.replaceAll) { + String.prototype.replaceAll = function(str1, str2, ignore) { + return this.replace(new RegExp(str1.replace(/([\/\,\!\\\^\$\{\}\[\]\(\)\.\*\+\?\|\<\>\-\&])/g,"\\$&"),(ignore?"gi":"g")),(typeof(str2)=="string")?str2.replace(/\$/g,"$$$$"):str2); + }; + } // Create config nodes using templateDisplayConfig function makeConfig(opts) { confNum += 1; @@ -1152,7 +1160,7 @@ $showAdditionalInfo = '';
- + - + - + - + @@ -1313,8 +1321,10 @@ $showAdditionalInfo = ''; @@ -1505,8 +1515,14 @@ $showAdditionalInfo = ''; // Add switchButton $('.switch-on-off').each(function(){var checked = $(this).is(":checked");$(this).switchButton({labels_placement: "right", checked:checked});}); - $("#catSelect").dropdownchecklist({emptyText:'Select categories...', maxDropHeight:150, width:300, explicitClose:'...close'}); + // Add dropdownchecklist to Select Categories + $("#catSelect").dropdownchecklist({emptyText:'Select categories...', maxDropHeight:200, width:300, explicitClose:'...close'}); + + });
 
Categories: @@ -1197,11 +1205,11 @@ $showAdditionalInfo = '';
Support Thread:

Link to a support thread on Lime-Technology's forum.

@@ -1219,7 +1227,7 @@ $showAdditionalInfo = '';
Template URL:
  - - + + + +