Files
webgui/plugins/dynamix.docker.manager/include/CreateDocker.php
bergware a59cc208ee Docker: add route for remote WireGuard access
Containers with network 'br0' can be remotely accessed by WireGuard without the need to configure static router on the home router (gateway)

"Host access to custom networks" must be enabled to allow access
2022-04-17 22:56:48 +02:00

1160 lines
47 KiB
PHP

<?PHP
/* Copyright 2005-2022, Lime Technology
* Copyright 2015-2022, Guilherme Jardim, Eric Schultz, Jon Panozzo.
* Copyright 2012-2022, 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,
* as published by the Free Software Foundation.
*
* The above copyright notice and this permission notice shall be included in
* all copies or substantial portions of the Software.
*/
?>
<?
$docroot = $docroot ?? $_SERVER['DOCUMENT_ROOT'] ?: '/usr/local/emhttp';
require_once "$docroot/plugins/dynamix.docker.manager/include/DockerClient.php";
libxml_use_internal_errors(true);
require_once "$docroot/webGui/include/Helpers.php";
extract(parse_plugin_cfg('dynamix',true));
$var = parse_ini_file('state/var.ini');
ignore_user_abort(true);
$DockerClient = new DockerClient();
$DockerUpdate = new DockerUpdate();
$DockerTemplates = new DockerTemplates();
# ███████╗██╗ ██╗███╗ ██╗ ██████╗████████╗██╗ ██████╗ ███╗ ██╗███████╗
# ██╔════╝██║ ██║████╗ ██║██╔════╝╚══██╔══╝██║██╔═══██╗████╗ ██║██╔════╝
# █████╗ ██║ ██║██╔██╗ ██║██║ ██║ ██║██║ ██║██╔██╗ ██║███████╗
# ██╔══╝ ██║ ██║██║╚██╗██║██║ ██║ ██║██║ ██║██║╚██╗██║╚════██║
# ██║ ╚██████╔╝██║ ╚████║╚██████╗ ██║ ██║╚██████╔╝██║ ╚████║███████║
# ╚═╝ ╚═════╝ ╚═╝ ╚═══╝ ╚═════╝ ╚═╝ ╚═╝ ╚═════╝ ╚═╝ ╚═══╝╚══════╝
$custom = DockerUtil::custom();
$subnet = DockerUtil::network($custom);
$cpus = DockerUtil::cpus();
function cpu_pinning() {
global $xml,$cpus;
$vcpu = explode(',',$xml['CPUset'] ?? '');
$total = count($cpus);
$loop = floor(($total-1)/16)+1;
for ($c = 0; $c < $loop; $c++) {
$row1 = $row2 = [];
$max = ($c == $loop-1 ? ($total%16?:16) : 16);
for ($n = 0; $n < $max; $n++) {
unset($cpu1,$cpu2);
[$cpu1, $cpu2] = my_preg_split('/[,-]/',$cpus[$c*16+$n]);
$check1 = in_array($cpu1, $vcpu) ? ' checked':'';
$check2 = $cpu2 ? (in_array($cpu2, $vcpu) ? ' checked':''):'';
$row1[] = "<label id='cpu$cpu1' class='checkbox'>$cpu1<input type='checkbox' id='box$cpu1'$check1><span class='checkmark'></span></label>";
if ($cpu2) $row2[] = "<label id='cpu$cpu2' class='checkbox'>$cpu2<input type='checkbox' id='box$cpu2'$check2><span class='checkmark'></span></label>";
}
if ($c) echo '<hr>';
echo "<span class='cpu'>"._('CPU').":</span>".implode($row1);
if ($row2) echo "<br><span class='cpu'>"._('HT').":</span>".implode($row2);
}
}
# ██████╗ ██████╗ ██████╗ ███████╗
# ██╔════╝██╔═══██╗██╔══██╗██╔════╝
# ██║ ██║ ██║██║ ██║█████╗
# ██║ ██║ ██║██║ ██║██╔══╝
# ╚██████╗╚██████╔╝██████╔╝███████╗
# ╚═════╝ ╚═════╝ ╚═════╝ ╚══════╝
##########################
## CREATE CONTAINER ##
##########################
if (isset($_POST['contName'])) {
$postXML = postToXML($_POST, true);
$dry_run = $_POST['dryRun']=='true' ? true : false;
$existing = $_POST['existingContainer'] ?? false;
$create_paths = $dry_run ? false : true;
// Get the command line
[$cmd, $Name, $Repository] = xmlToCommand($postXML, $create_paths);
readfile("$docroot/plugins/dynamix.docker.manager/log.htm");
@flush();
// Saving the generated configuration file.
$userTmplDir = $dockerManPaths['templates-user'];
if (!is_dir($userTmplDir)) mkdir($userTmplDir, 0777, true);
if ($Name) {
$filename = sprintf('%s/my-%s.xml', $userTmplDir, $Name);
if (is_file($filename)) {
$oldXML = simplexml_load_file($filename);
if ($oldXML->Icon != $_POST['contIcon']) {
if (!strpos($Repository,":")) $Repository .= ":latest";
$iconPath = $DockerTemplates->getIcon($Repository,$Name);
@unlink("$docroot/$iconPath");
@unlink("{$dockerManPaths['images']}/".basename($iconPath));
}
}
file_put_contents($filename, $postXML);
}
// Run dry
if ($dry_run) {
echo "<h2>XML</h2>";
echo "<pre>".htmlspecialchars($postXML)."</pre>";
echo "<h2>COMMAND:</h2>";
echo "<pre>".htmlspecialchars($cmd)."</pre>";
echo "<div style='text-align:center'><button type='button' onclick='window.location=window.location.pathname+window.location.hash+\"?xmlTemplate=edit:$filename\"'>"._('Back')."</button>";
echo "<button type='button' onclick='done()'>"._('Done')."</button></div><br>";
goto END;
}
// Will only pull image if it's absent
if (!$DockerClient->doesImageExist($Repository)) {
// Pull image
if (!pullImage($Name, $Repository)) {
echo '<div style="text-align:center"><button type="button" onclick="done()">'._('Done').'</button></div><br>';
goto END;
}
}
$startContainer = true;
// Remove existing container
if ($DockerClient->doesContainerExist($Name)) {
// attempt graceful stop of container first
$oldContainerInfo = $DockerClient->getContainerDetails($Name);
if (!empty($oldContainerInfo) && !empty($oldContainerInfo['State']) && !empty($oldContainerInfo['State']['Running'])) {
// attempt graceful stop of container first
stopContainer($Name);
}
// force kill container if still running after 10 seconds
removeContainer($Name);
}
// Remove old container if renamed
if ($existing && $DockerClient->doesContainerExist($existing)) {
// determine if the container is still running
$oldContainerInfo = $DockerClient->getContainerDetails($existing);
if (!empty($oldContainerInfo) && !empty($oldContainerInfo['State']) && !empty($oldContainerInfo['State']['Running'])) {
// attempt graceful stop of container first
stopContainer($existing);
} else {
// old container was stopped already, ensure newly created container doesn't start up automatically
$startContainer = false;
}
// force kill container if still running after 10 seconds
removeContainer($existing,1);
// remove old template
if (strtolower($filename) != strtolower("$userTmplDir/my-$existing.xml")) {
@unlink("$userTmplDir/my-$existing.xml");
}
}
if ($startContainer) $cmd = str_replace('/docker create ', '/docker run -d ', $cmd);
execCommand($cmd);
if ($startContainer) addRoute($Name); // add route for remote WireGuard access
echo '<div style="text-align:center"><button type="button" onclick="done()">'._('Done').'</button></div><br>';
goto END;
}
##########################
## UPDATE CONTAINER ##
##########################
if (isset($_GET['updateContainer'])){
$echo = empty($_GET['mute']);
if ($echo) {
readfile("$docroot/plugins/dynamix.docker.manager/log.htm");
@flush();
}
foreach ($_GET['ct'] as $value) {
$tmpl = $DockerTemplates->getUserTemplate(unscript(urldecode($value)));
if ($echo && !$tmpl) {
echo "<script>addLog('<p>"._('Configuration not found').". "._('Was this container created using this plugin')."?</p>');</script>";
@flush();
continue;
}
$xml = file_get_contents($tmpl);
[$cmd, $Name, $Repository] = xmlToCommand($tmpl);
$Registry = getXmlVal($xml, "Registry");
$oldImageID = $DockerClient->getImageID($Repository);
// pull image
if ($echo && !pullImage($Name, $Repository)) continue;
$oldContainerInfo = $DockerClient->getContainerDetails($Name);
// determine if the container is still running
$startContainer = false;
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('/docker create ', '/docker run -d ', $cmd);
$startContainer = true;
// attempt graceful stop of container first
stopContainer($Name, $echo);
}
// force kill container if still running after 10 seconds
if (empty($_GET['communityApplications'])) removeContainer($Name, $echo);
execCommand($cmd, $echo);
if ($startContainer) addRoute($Name); // add route for remote WireGuard access
$DockerClient->flushCaches();
$newImageID = $DockerClient->getImageID($Repository);
// remove old orphan image since it's no longer used by this container
if ($oldImageID && $oldImageID != $newImageID) removeImage($oldImageID, $echo);
}
echo '<div style="text-align:center"><button type="button" onclick="window.parent.jQuery(\'#iframe-popup\').dialog(\'close\')">'._('Done').'</button></div><br>';
goto END;
}
#########################
## REMOVE TEMPLATE ##
#########################
if (isset($_POST['rmTemplate'])) {
if (file_exists($_POST['rmTemplate']) && dirname($_POST['rmTemplate'])==$dockerManPaths['templates-user']) unlink($_POST['rmTemplate']);
}
#########################
## LOAD TEMPLATE ##
#########################
if (isset($_GET['xmlTemplate'])) {
[$xmlType, $xmlTemplate] = my_explode(':', unscript(urldecode($_GET['xmlTemplate'])));
if (is_file($xmlTemplate)) {
$xml = xmlToVar($xmlTemplate);
$templateName = $xml['Name'];
if ($xmlType == 'default') {
if (!empty($dockercfg['DOCKER_APP_CONFIG_PATH']) && file_exists($dockercfg['DOCKER_APP_CONFIG_PATH'])) {
// override /config
foreach ($xml['Config'] as &$arrConfig) {
if ($arrConfig['Type'] == 'Path' && strtolower($arrConfig['Target']) == '/config') {
$arrConfig['Default'] = $arrConfig['Value'] = realpath($dockercfg['DOCKER_APP_CONFIG_PATH']).'/'.$xml['Name'];
if (empty($arrConfig['Display']) || preg_match("/^Host Path\s\d/", $arrConfig['Name'])) {
$arrConfig['Display'] = 'advanced-hide';
}
if (empty($arrConfig['Name']) || preg_match("/^Host Path\s\d/", $arrConfig['Name'])) {
$arrConfig['Name'] = 'AppData Config Path';
}
}
$arrConfig['Name'] = strip_tags($arrConfig['Name']);
$arrConfig['Description'] = strip_tags($arrConfig['Description']);
$arrConfig['Requires'] = strip_tags($arrConfig['Requires']);
}
}
if (!empty($dockercfg['DOCKER_APP_UNRAID_PATH']) && file_exists($dockercfg['DOCKER_APP_UNRAID_PATH'])) {
// override /unraid
$boolFound = false;
foreach ($xml['Config'] as &$arrConfig) {
if ($arrConfig['Type'] == 'Path' && strtolower($arrConfig['Target']) == '/unraid') {
$arrConfig['Default'] = $arrConfig['Value'] = realpath($dockercfg['DOCKER_APP_UNRAID_PATH']);
$arrConfig['Display'] = 'hidden';
$arrConfig['Name'] = 'Unraid Share Path';
$boolFound = true;
}
}
if (!$boolFound) {
$xml['Config'][] = [
'Name' => 'Unraid Share Path',
'Target' => '/unraid',
'Default' => realpath($dockercfg['DOCKER_APP_UNRAID_PATH']),
'Value' => realpath($dockercfg['DOCKER_APP_UNRAID_PATH']),
'Mode' => 'rw',
'Description' => '',
'Type' => 'Path',
'Display' => 'hidden',
'Required' => 'false',
'Mask' => 'false'
];
}
}
}
$xml['Overview'] = str_replace(['[', ']'], ['<', '>'], $xml['Overview']);
$xml['Description'] = $xml['Overview'] = strip_tags(str_replace("<br>","\n", $xml['Overview']));
echo "<script>var Settings=".json_encode($xml).";</script>";
}
}
echo "<script>var Allocations=".json_encode(getAllocations()).";</script>";
$authoringMode = $dockercfg['DOCKER_AUTHORING_MODE'] == "yes" ? true : false;
$authoring = $authoringMode ? 'advanced' : 'noshow';
$disableEdit = $authoringMode ? 'false' : 'true';
$showAdditionalInfo = '';
$bgcolor = strstr('white,azure',$display['theme']) ? '#f2f2f2' : '#1c1c1c';
?>
<link type="text/css" rel="stylesheet" href="<?autov("/webGui/styles/jquery.ui.css")?>">
<link type="text/css" rel="stylesheet" href="<?autov("/webGui/styles/jquery.switchbutton.css")?>">
<link type="text/css" rel="stylesheet" href="<?autov("/webGui/styles/jquery.filetree.css")?>">
<link type="text/css" rel="stylesheet" href="<?autov("/plugins/dynamix.docker.manager/styles/style-{$display['theme']}.css")?>">
<style>
.noshow,.advanced{display:none}
.fileTree{width:240px;max-height:200px;overflow-y:scroll;overflow-x:hidden;position:absolute;display:none;background:<?=$bgcolor?>}
.required:after{content:" *";color:#E80000}
span.boxed{display:inline-block;line-height:normal;white-space:normal;width:56%}
span.cpu,label.checkbox{display:inline-block;width:32px}
span.ct{display:inline-block;width:230px}
span.net{display:inline-block;width:120px}
span.ip{display:inline-block;width:160px}
dl,dt,dd{line-height:normal!important}
</style>
<script src="<?autov('/webGui/javascript/jquery.switchbutton.js')?>"></script>
<script src="<?autov('/webGui/javascript/jquery.filetree.js')?>" charset="utf-8"></script>
<script src="<?autov('/plugins/dynamix.vm.manager/javascript/dynamix.vm.manager.js')?>"></script>
<script src="<?autov('/plugins/dynamix.docker.manager/javascript/markdown.js')?>"></script>
<script>
var confNum = 0;
var drivers = {};
<?foreach ($driver as $d => $v) echo "drivers['$d']='$v';\n";?>
if (!Array.prototype.forEach) {
Array.prototype.forEach = function(fn, scope) {
for (var i = 0, len = this.length; i < len; ++i) fn.call(scope, this[i], i, this);
};
}
if (!String.prototype.format) {
String.prototype.format = function() {
var args = arguments;
return this.replace(/{(\d+)}/g, function(match, number) {
return typeof args[number] != 'undefined' ? args[number] : match;
});
};
}
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;
var newConfig = $("#templateDisplayConfig").html();
newConfig = newConfig.format(
stripTags(opts.Name),
opts.Target,
opts.Default,
opts.Mode,
opts.Description,
opts.Type,
opts.Display,
opts.Required,
opts.Mask,
escapeQuote(opts.Value),
opts.Buttons,
opts.Required=='true' ? 'required' : '',
sprintf('Container %s',opts.Type)
);
newConfig = "<div id='ConfigNum"+opts.Number+"' class='config_"+opts.Display+"'' >"+newConfig+"</div>";
newConfig = $($.parseHTML(newConfig));
value = newConfig.find("input[name='confValue[]']");
if (opts.Type == "Path") {
value.attr("onclick", "openFileBrowser(this,$(this).val(),'',true,false);");
} else if (opts.Type == "Device") {
value.attr("onclick", "openFileBrowser(this,$(this).val()||'/dev','',false,true);")
} else if (opts.Type == "Variable" && opts.Default.split("|").length > 1) {
var valueOpts = opts.Default.split("|");
var newValue = "<select name='confValue[]' class='selectVariable' default='"+valueOpts[0]+"'>";
for (var i = 0; i < valueOpts.length; i++) {
newValue += "<option value='"+valueOpts[i]+"' "+(opts.Value == valueOpts[i] ? "selected" : "")+">"+valueOpts[i]+"</option>";
}
newValue += "</select>";
value.replaceWith(newValue);
} else if (opts.Type == "Port") {
value.addClass("numbersOnly");
}
if (opts.Mask == "true") {
value.prop("autocomplete","new-password");
value.prop("type", "password");
}
return newConfig.prop('outerHTML');
}
function stripTags(string) {
return string.replace(/(<([^>]+)>)/ig,"");
}
function escapeQuote(string) {
return string.replace(new RegExp('"','g'),"&quot;");
}
function makeAllocations(container,current) {
var html = [];
for (var i=0,ct; ct=container[i]; i++) {
var highlight = ct.Name.toLowerCase()==current.toLowerCase() ? "font-weight:bold" : "";
html.push($("#templateAllocations").html().format(highlight,ct.Name,ct.Port));
}
return html.join('');
}
function getVal(el, name) {
var el = $(el).find("*[name="+name+"]");
if (el.length) {
return ($(el).attr('type') == 'checkbox') ? ($(el).is(':checked') ? "on" : "off") : $(el).val();
} else {
return "";
}
}
function addConfigPopup() {
var title = "_(Add Configuration)_";
var popup = $("#dialogAddConfig");
// Load popup the popup with the template info
popup.html($("#templatePopupConfig").html());
// Add switchButton to checkboxes
popup.find(".switch").switchButton({labels_placement:"right",on_label:"_(Yes)_",off_label:"_(No)_"});
popup.find(".switch-button-background").css("margin-top", "6px");
// Load Mode field if needed and enable field
toggleMode(popup.find("*[name=Type]:first"),false);
// Start Dialog section
popup.dialog({
title: title,
resizable: false,
width: 900,
modal: true,
show : {effect: 'fade' , duration: 250},
hide : {effect: 'fade' , duration: 250},
buttons: {
"_(Add)_": function() {
$(this).dialog("close");
confNum += 1;
var Opts = Object;
var Element = this;
["Name","Target","Default","Mode","Description","Type","Display","Required","Mask","Value"].forEach(function(e){
Opts[e] = getVal(Element, e);
});
if (!Opts.Name){
Opts.Name = makeName(Opts.Type);
}
if (Opts.Required == "true") {
Opts.Buttons = "<span class='advanced'><button type='button' onclick='editConfigPopup("+confNum+",false)'>_(Edit)_</button>";
Opts.Buttons += "<button type='button' onclick='removeConfig("+confNum+")'>_(Remove)_</button></span>";
} else {
Opts.Buttons = "<button type='button' onclick='editConfigPopup("+confNum+",false)'>_(Edit)_</button>";
Opts.Buttons += "<button type='button' onclick='removeConfig("+confNum+")'>_(Remove)_</button>";
}
Opts.Number = confNum;
newConf = makeConfig(Opts);
$("#configLocation").append(newConf);
reloadTriggers();
$('input[name="contName"]').trigger('change'); // signal change
},
"_(Cancel)_": function() {
$(this).dialog("close");
}
}
});
$(".ui-dialog .ui-dialog-titlebar").addClass('menu');
$('.ui-dialog .ui-dialog-titlebar-close').css({'display':'none'});
$(".ui-dialog .ui-dialog-title").css({'text-align':'center','width':'100%'});
$(".ui-dialog .ui-dialog-content").css({'padding-top':'15px','vertical-align':'bottom'});
$(".ui-button-text").css({'padding':'0px 5px'});
}
function editConfigPopup(num,disabled) {
var title = "_(Edit Configuration)_";
var popup = $("#dialogAddConfig");
// Load popup the popup with the template info
popup.html($("#templatePopupConfig").html());
// Load existing config info
var config = $("#ConfigNum"+num);
config.find("input").each(function(){
var name = $(this).attr("name").replace("conf", "").replace("[]", "");
popup.find("*[name='"+name+"']").val($(this).val());
});
// Hide passwords if needed
if (popup.find("*[name='Mask']").val() == "true") {
popup.find("*[name='Value']").prop("type", "password");
}
// Load Mode field if needed
var mode = config.find("input[name='confMode[]']").val();
toggleMode(popup.find("*[name=Type]:first"),disabled);
popup.find("*[name=Mode]:first").val(mode);
// Add switchButton to checkboxes
popup.find(".switch").switchButton({labels_placement:"right",on_label:"_(Yes)_",off_label:"_(No)_"});
// Start Dialog section
popup.find(".switch-button-background").css("margin-top", "6px");
popup.dialog({
title: title,
resizable: false,
width: 900,
modal: true,
show: {effect:'fade', duration: 250},
hide: {effect:'fade', duration: 250},
buttons: {
"_(Save)_": function() {
$(this).dialog("close");
var Opts = Object;
var Element = this;
["Name","Target","Default","Mode","Description","Type","Display","Required","Mask","Value"].forEach(function(e){
Opts[e] = getVal(Element, e);
});
if (Opts.Display == "always-hide" || Opts.Display == "advanced-hide") {
Opts.Buttons = "<span class='advanced'><button type='button' onclick='editConfigPopup("+num+",<?=$disableEdit?>)'>_(Edit)_</button>";
Opts.Buttons += "<button type='button' onclick='removeConfig("+num+")'>_(Remove)_</button></span>";
} else {
Opts.Buttons = "<button type='button' onclick='editConfigPopup("+num+",<?=$disableEdit?>)'>_(Edit)_</button>";
Opts.Buttons += "<button type='button' onclick='removeConfig("+num+")'>_(Remove)_</button>";
}
if (!Opts.Name){
Opts.Name = makeName(Opts.Type);
}
Opts.Number = num;
newConf = makeConfig(Opts);
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();
$('input[name="contName"]').trigger('change'); // signal change
},
"_(Cancel)_": function() {
$(this).dialog("close");
}
}
});
$(".ui-dialog .ui-dialog-titlebar").addClass('menu');
$('.ui-dialog .ui-dialog-titlebar-close').css({'display':'none'});
$(".ui-dialog .ui-dialog-title").css({'text-align':'center','width':'100%'});
$(".ui-dialog .ui-dialog-content").css({'padding-top':'15px','vertical-align':'bottom'});
$(".ui-button-text").css({'padding':'0px 5px'});
$('.desc_readmore').readmore({maxHeight:10});
}
function removeConfig(num) {
$('#ConfigNum'+num).fadeOut("fast", function() {$(this).remove();});
$('input[name="contName"]').trigger('change'); // signal change
}
function prepareConfig(form) {
var types = [], values = [], targets = [], vcpu = [];
if ($('select[name="contNetwork"]').val()=='host') {
$(form).find('input[name="confType[]"]').each(function(){types.push($(this).val());});
$(form).find('input[name="confValue[]"]').each(function(){values.push($(this));});
$(form).find('input[name="confTarget[]"]').each(function(){targets.push($(this));});
for (var i=0; i < types.length; i++) if (types[i]=='Port') $(targets[i]).val($(values[i]).val());
}
$(form).find('input[id^="box"]').each(function(){if ($(this).prop('checked')) vcpu.push($('#'+$(this).prop('id').replace('box','cpu')).text());});
form.contCPUset.value = vcpu.join(',');
}
function makeName(type) {
var i = $("#configLocation input[name^='confType'][value='"+type+"']").length+1;
return "Host "+type.replace('Variable','Key')+" "+i;
}
function toggleMode(el,disabled) {
var div = $(el).closest('div');
var targetDiv = div.find('#Target');
var valueDiv = div.find('#Value');
var defaultDiv = div.find('#Default');
var mode = div.find('#Mode');
var value = valueDiv.find('input[name=Value]');
var target = targetDiv.find('input[name=Target]');
var driver = drivers[$('select[name="contNetwork"]')[0].value];
value.unbind();
target.unbind();
valueDiv.css('display', '');
defaultDiv.css('display', '');
targetDiv.css('display', '');
mode.html('');
$(el).prop('disabled',disabled);
switch ($(el)[0].selectedIndex) {
case 0: // Path
mode.html("<dl><dt>_(Access Mode)_:</dt><dd><select name='Mode'><option value='rw'>_(Read/Write)_</option><option value='rw,slave'>_(Read/Write - Slave)_</option><option value='rw,shared'>_(Read/Write - Shared)_</option><option value='ro'>_(Read Only)_</option><option value='ro,slave'>_(Read Only - Slave)_</option><option value='ro,shared'>_(Read Only - Shared)_</option></select></dd></dl>");
value.bind("click", function(){openFileBrowser(this,$(this).val(), 'sh', true, false);});
targetDiv.find('#dt1').text("_(Container Path)_");
valueDiv.find('#dt2').text("_(Host Path)_");
break;
case 1: // Port
mode.html("<dl><dt>_(Connection Type)_:</dt><dd><select name='Mode'><option value='tcp'>_(TCP)_</option><option value='udp'>_(UDP)_</option></select></dd></dl>");
value.addClass("numbersOnly");
if (driver=='bridge') {
if (target.val()) target.prop('disabled',<?=$disableEdit?>); else target.addClass("numbersOnly");
targetDiv.find('#dt1').text("_(Container Port)_");
targetDiv.show();
} else {
targetDiv.hide();
}
if (driver!='null') {
valueDiv.find('#dt2').text("_(Host Port)_");
valueDiv.show();
} else {
valueDiv.hide();
mode.html('');
}
break;
case 2: // Variable
targetDiv.find('#dt1').text("_(Key)_");
valueDiv.find('#dt2').text("_(Value)_");
break;
case 3: // Label
targetDiv.find('#dt1').text("_(Key)_");
valueDiv.find('#dt2').text("_(Value)_");
break;
case 4: // Device
targetDiv.hide();
defaultDiv.hide();
valueDiv.find('#dt2').text("_(Value)_");
value.bind("click", function(){openFileBrowser(this,$(this).val()||'/dev', '', true, true);});
break;
}
reloadTriggers();
}
function loadTemplate(el) {
var template = $(el).val();
if (template.length) {
$('#formTemplate').find("input[name='xmlTemplate']").val(template);
$('#formTemplate').submit();
}
}
function rmTemplate(tmpl) {
var name = tmpl.split(/[\/]+/).pop();
swal({title:"_(Are you sure)_?",text:"_(Remove template)_: "+name,type:"warning",html:true,showCancelButton:true,confirmButtonText:"_(Proceed)_",cancelButtonText:"_(Cancel)_"},function(){$("#rmTemplate").val(tmpl);$("#formTemplate1").submit();});
}
function openFileBrowser(el, root, filter, on_folders, on_files, close_on_select) {
if (on_folders === undefined) on_folders = true;
if (on_files === undefined) on_files = true;
if (!filter && !on_files) filter = 'HIDE_FILES_FILTER';
if (!root.trim()) root = "/mnt/user/";
p = $(el);
// Skip is fileTree is already open
if (p.next().hasClass('fileTree')) return null;
// create a random id
var r = Math.floor((Math.random()*1000)+1);
// Add a new span and load fileTree
p.after("<span id='fileTree"+r+"' class='textarea fileTree'></span>");
var ft = $('#fileTree'+r);
ft.fileTree({
root: root,
filter: filter,
allowBrowsing: true
},
function(file){if(on_files){p.val(file);p.trigger('change');if(close_on_select){ft.slideUp('fast',function(){ft.remove();});}}},
function(folder){if(on_folders){p.val(folder.replace(/\/\/+/g,'/'));p.trigger('change');if(close_on_select){$(ft).slideUp('fast',function(){$(ft).remove();});}}});
// Format fileTree according to parent position, height and width
ft.css({'left':p.position().left,'top':(p.position().top+p.outerHeight()),'width':(p.width())});
// close if click elsewhere
$(document).mouseup(function(e){if(!ft.is(e.target) && ft.has(e.target).length === 0){ft.slideUp('fast',function(){$(ft).remove();});}});
// close if parent changed
p.bind("keydown", function(){ft.slideUp('fast', function(){$(ft).remove();});});
// Open fileTree
ft.slideDown('fast');
}
function resetField(el) {
var target = $(el).prev();
reset = target.attr("default");
if (reset.length) target.val(reset);
}
function prepareCategory() {
var values = $.map($('#catSelect option'),function(option) {
if ($(option).is(":selected")) return option.value;
});
$("input[name='contCategory']").val(values.join(" "));
}
$(function() {
var ctrl = "<span class='status <?=$tabbed?'':'vhshift'?>'><input type='checkbox' class='advancedview'></span>";
<?if ($tabbed):?>
$('.tabs').append(ctrl);
<?else:?>
$('div[id=title]').append(ctrl);
<?endif;?>
$('.advancedview').switchButton({labels_placement:'left', on_label: "_(Advanced View)_", off_label: "_(Basic View)_"});
$('.advancedview').change(function() {
var status = $(this).is(':checked');
toggleRows('advanced', status, 'basic');
load_contOverview();
$("#catSelect").dropdownchecklist("destroy");
$("#catSelect").dropdownchecklist({emptyText:"_(Select categories)_...", maxDropHeight:200, width:300, explicitClose:"..._(close)_"});
});
});
</script>
<div id="canvas">
<form markdown="1" method="POST" autocomplete="off" onsubmit="prepareConfig(this)">
<input type="hidden" name="csrf_token" value="<?=$var['csrf_token']?>">
<input type="hidden" name="contCPUset" value="">
<?if ($xmlType=='edit'):?>
<?if ($DockerClient->doesContainerExist($templateName)):?>
<input type="hidden" name="existingContainer" value="<?=$templateName?>">
<?endif;?>
<?else:?>
<div markdown="1" class="TemplateDropDown">
_(Template)_:
: <select id="TemplateSelect" onchange="loadTemplate(this);">
<?echo mk_option(0,"",_('Select a template'));
$rmadd = '';
$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){
if ( $value['name'] == "my-ca_profile" || $value['name'] == "ca_profile" ) continue;
$name = str_replace('my-', '', $value['name']);
$selected = (isset($xmlTemplate) && $value['path']==$xmlTemplate) ? ' selected ' : '';
if ($selected && $section=='default') $showAdditionalInfo = '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));
}
if (!$template) echo("<option class='list' disabled>&lt;"._('None')."&gt;</option>");
printf("</optgroup>");
}
?></select><?if ($rmadd):?><i class="fa fa-window-close button" title="<?=htmlspecialchars($rmadd)?>" onclick="rmTemplate('<?=addslashes(htmlspecialchars($rmadd))?>')"></i><?endif;?>
:docker_client_general_help:
</div>
<?endif;?>
<div markdown="1" class="<?=$showAdditionalInfo?>">
_(Name)_:
: <input type="text" name="contName" pattern="[a-zA-Z0-9][a-zA-Z0-9_.-]+" required>
:docker_client_name_help:
</div>
<div markdown="1" class="basic">
_(Overview)_:
: <span id="contDescription" class="boxed blue-text"></span>
</div>
<div markdown="1" class="advanced">
_(Overview)_:
: <textarea name="contOverview" spellcheck="false" cols="80" rows="15" style="width:56%"></textarea>
:docker_client_overview_help:
</div>
<div markdown="1" class="basic">
_(Additional Requirements)_:
: <span id="contRequires" class="boxed blue-text"></span>
</div>
<div markdown="1" class="advanced">
_(Additional Requirements)_:
: <textarea name="contRequires" spellcheck="false" cols="80" Rows="3" style="width:56%"></textarea>
:docker_client_additional_requirements_help:
</div>
<div markdown="1" class="<?=$showAdditionalInfo?>">
_(Repository)_:
: <input type="text" name="contRepository" required>
:docker_client_repository_help:
</div>
<div markdown="1" class="<?=$authoring?>">
_(Categories)_:
: <input type="hidden" name="contCategory">
<select id="catSelect" size="1" multiple="multiple" style="display:none" onchange="prepareCategory();">
<optgroup label="_(Categories)_">
<option value="Backup:">_(Backup)_</option>
<option value="Cloud:">_(Cloud)_</option>
<option value="Crypto:">_(Crypto Currency)_</option>
<option value="Downloaders:">_(Downloaders)_</option>
<option value="Drivers:">_(Drivers)_</option>
<option value="GameServers:">_(Game Servers)_</option>
<option value="HomeAutomation:">_(Home Automation)_</option>
<option value="Productivity:">_(Productivity)_</option>
<option value="Security:">_(Security)_</option>
<option value="Tools:">_(Tools)_</option>
<option value="Other:">_(Other)_</option>
</optgroup>
<optgroup label="_(MediaApp)_">
<option value="MediaApp:Video">_(MediaApp)_:_(Video)_</option>
<option value="MediaApp:Music">_(MediaApp)_:_(Music)_</option>
<option value="MediaApp:Books">_(MediaApp)_:_(Books)_</option>
<option value="MediaApp:Photos">_(MediaApp)_:_(Photos)_</option>
<option value="MediaApp:Other">_(MediaApp)_:_(Other)_</option>
</optgroup>
<optgroup label="_(MediaServer)_">
<option value="MediaServer:Video">_(MediaServer)_:_(Video)_</option>
<option value="MediaServer:Music">_(MediaServer)_:_(Music)_</option>
<option value="MediaServer:Books">_(MediaServer)_:_(Books)_</option>
<option value="MediaServer:Photos">_(MediaServer)_:_(Photos)_</option>
<option value="MediaServer:Other">_(MediaServer)_:_(Other)_</option>
</optgroup>
<optgroup label="_(Network)_">
<option value="Network:Web">_(Network)_:_(Web)_</option>
<option value="Network:DNS">_(Network)_:_(DNS)_</option>
<option value="Network:FTP">_(Network)_:_(FTP)_</option>
<option value="Network:Proxy">_(Network)_:_(Proxy)_</option>
<option value="Network:Voip">_(Network)_:_(Voip)_</option>
<option value="Network:Management">_(Network)_:_(Management)_</option>
<option value="Network:Messenger">_(Network)_:_(Messenger)_</option>
<option value="Network:VPN">_(Network)_:_(VPN)_</option>
<option value="Network:Privacy">_(Network)_:_(Privacy)_</option>
<option value="Network:Other">_(Network)_:_(Other)_</option>
</optgroup>
<optgroup label="_(Development Status)_">
<option value="Status:Stable">_(Status)_:_(Stable)_</option>
<option value="Status:Beta">_(Status)_:_(Beta)_</option>
</optgroup>
</select>
_(Support Thread)_:
: <input type="text" name="contSupport">
:docker_client_support_thread_help:
_(Project Page)_:
: <input type="text" name="contProject">
:docker_client_project_page_help:
_(Read Me First)_:
: <input type="text" name="contReadMe">
:docker_client_readme_help:
</div>
<div markdown="1" class="advanced">
_(Docker Hub URL)_:
: <input type="text" name="contRegistry"></td>
:docker_client_hub_url_help:
</div>
<div markdown="1" class="noshow"> <!-- Deprecated for author to enter or change, but needs to be present -->
Donation Text:
: <input type="text" name="contDonateText">
Donation Link:
: <input type="text" name="contDonateLink">
Template URL:
: <input type="text" name="contTemplateURL">
</div>
<div markdown="1" class="advanced">
_(Icon URL)_:
: <input type="text" name="contIcon">
:docker_client_icon_url_help:
_(WebUI)_:
: <input type="text" name="contWebUI">
:docker_client_webui_help:
_(Extra Parameters)_:
: <input type="text" name="contExtraParams">
:docker_extra_parameters_help:
_(Post Arguments)_:
: <input type="text" name="contPostArgs">
:docker_post_arguments_help:
_(CPU Pinning)_:
: <span style="display:inline-block"><?cpu_pinning()?></span>
:docker_cpu_pinning_help:
</div>
_(Network Type)_:
: <select name="contNetwork" onchange="showSubnet(this.value)">
<?=mk_option(1,'bridge',_('Bridge'))?>
<?=mk_option(1,'host',_('Host'))?>
<?=mk_option(1,'none',_('None'))?>
<?foreach ($custom as $network):?>
<?=mk_option(1,$network,_('Custom')." : $network")?>
<?endforeach;?></select>
<div markdown="1" class="myIP noshow">
_(Fixed IP address)_ (_(optional)_):
: <input type="text" name="contMyIP"><span id="myIP"></span>
:docker_fixed_ip_help:
</div>
_(Console shell command)_:
: <select name="contShell">
<?=mk_option(1,'sh',_('Shell'))?>
<?=mk_option(1,'bash',_('Bash'))?>
</select>
_(Privileged)_:
: <input type="checkbox" class="switch-on-off" name="contPrivileged">
:docker_privileged_help:
<div id="configLocation"></div>
&nbsp;
: <span id="readmore_toggle" class="readmore_collapsed"><a onclick="toggleReadmore()" style="cursor:pointer"><i class="fa fa-fw fa-chevron-down"></i> _(Show more settings)_ ...</a></span><div id="configLocationAdvanced" style="display:none"></div>
&nbsp;
: <span id="allocations_toggle" class="readmore_collapsed"><a onclick="toggleAllocations()" style="cursor:pointer"><i class="fa fa-fw fa-chevron-down"></i> _(Show docker allocations)_ ...</a></span><div id="dockerAllocations" style="display:none"></div>
&nbsp;
: <a href="javascript:addConfigPopup()"><i class="fa fa-fw fa-plus"></i> _(Add another Path, Port, Variable, Label or Device)_</a>
&nbsp;
: <input type="submit" value="<?=$xmlType=='edit' ? "_(Apply)_" : " _(Apply)_ "?>"><input type="button" value="_(Done)_" onclick="done()">
<?if ($authoringMode):?><button type="submit" name="dryRun" value="true" onclick="$('*[required]').prop('required', null);">_(Save)_</button><?endif;?>
</form>
</div>
<form method="GET" id="formTemplate">
<input type="hidden" id="xmlTemplate" name="xmlTemplate" value="">
</form>
<form method="POST" id="formTemplate1">
<input type="hidden" name="csrf_token" value="<?=$var['csrf_token']?>">
<input type="hidden" id="rmTemplate" name="rmTemplate" value="">
</form>
<div id="dialogAddConfig" style="display:none"></div>
<?
# ██╗███████╗ ████████╗███████╗███╗ ███╗██████╗ ██╗ █████╗ ████████╗███████╗███████╗
# ██║██╔════╝ ╚══██╔══╝██╔════╝████╗ ████║██╔══██╗██║ ██╔══██╗╚══██╔══╝██╔════╝██╔════╝
# ██║███████╗ ██║ █████╗ ██╔████╔██║██████╔╝██║ ███████║ ██║ █████╗ ███████╗
# ██ ██║╚════██║ ██║ ██╔══╝ ██║╚██╔╝██║██╔═══╝ ██║ ██╔══██║ ██║ ██╔══╝ ╚════██║
# ╚█████╔╝███████║ ██║ ███████╗██║ ╚═╝ ██║██║ ███████╗██║ ██║ ██║ ███████╗███████║
# ╚════╝ ╚══════╝ ╚═╝ ╚══════╝╚═╝ ╚═╝╚═╝ ╚══════╝╚═╝ ╚═╝ ╚═╝ ╚══════╝╚══════╝
?>
<div markdown="1" id="templatePopupConfig" style="display:none">
<html <?=$display['rtl']?>lang="<?=strtok($locale,'_')?:'en'?>">
<head>
<meta http-equiv="Content-Type" content="text/html; charset=utf-8">
<meta http-equiv="X-UA-Compatible" content="IE=edge">
<meta name="format-detection" content="telephone=no">
<meta name="viewport" content="width=1600">
<meta name="robots" content="noindex, nofollow">
<meta name="referrer" content="same-origin">
</head>
<body>
_(Config Type)_:
: <select name="Type" onchange="toggleMode(this,false)">
<option value="Path">_(Path)_</option>
<option value="Port">_(Port)_</option>
<option value="Variable">_(Variable)_</option>
<option value="Label">_(Label)_</option>
<option value="Device">_(Device)_</option>
</select>
_(Name)_:
: <input type="text" name="Name" autocomplete="off" spellcheck="false">
<div markdown="1" id="Target">
<span id="dt1">_(Target)_</span>:
: <input type="text" name="Target" autocomplete="off" spellcheck="false">
</div>
<div markdown="1" id="Value">
<span id="dt2">_(Value)_</span>:
: <input type="text" name="Value" autocomplete="off" spellcheck="false">
</div>
<div markdown="1" id="Default">
_(Default Value)_:
: <input type="text" name="Default" autocomplete="off" spellcheck="false">
</div>
<div id="Mode"></div>
_(Description)_:
: <textarea name="Description" spellcheck="false" cols="80" rows="3" style="width:304px;"></textarea>
<div markdown="1" class="advanced">
_(Display)_:
: <select name="Display">
<option value="always" selected>_(Always)_</option>
<option value="always-hide">_(Always)_ - _(Hide Buttons)_</option>
<option value="advanced">_(Advanced)_</option>
<option value="advanced-hide">_(Advanced)_ - _(Hide Buttons)_</option>
</select>
_(Required)_:
: <select name="Required">
<option value="false" selected>_(No)_</option>
<option value="true">_(Yes)_</option>
</select>
_(Password Mask)_:
: <select name="Mask">
<option value="false" selected>_(No)_</option>
<option value="true">_(Yes)_</option>
</select>
</div>
</body>
</html>
</div>
<div markdown="1" id="templateDisplayConfig" style="display:none">
<input type="hidden" name="confName[]" value="{0}">
<input type="hidden" name="confTarget[]" value="{1}">
<input type="hidden" name="confDefault[]" value="{2}">
<input type="hidden" name="confMode[]" value="{3}">
<input type="hidden" name="confDescription[]" value="{4}">
<input type="hidden" name="confType[]" value="{5}">
<input type="hidden" name="confDisplay[]" value="{6}">
<input type="hidden" name="confRequired[]" value="{7}">
<input type="hidden" name="confMask[]" value="{8}">
<span class="{11}">{0}:</span>
: <span class="boxed"><input type="text" name="confValue[]" default="{2}" value="{9}" autocomplete="off" spellcheck="false" {11}>{10}<br><span class='orange-text'>{12}: {1}</span><br><span class="orange-text">{4}</span><br>
</div>
<div markdown="1" id="templateAllocations" style="display:none">
&nbsp;
: <span class="boxed"><span class="ct">{1}</span>{2}</span>
</div>
<script>
var subnet = {};
<?foreach ($subnet as $network => $value):?>
subnet['<?=$network?>'] = '<?=$value?>';
<?endforeach;?>
function showSubnet(bridge) {
if (bridge.match(/^(bridge|host|none)$/i) !== null) {
$('.myIP').hide();
$('input[name="contMyIP"]').val('');
} else {
$('.myIP').show();
$('#myIP').html('Subnet: '+subnet[bridge]);
}
}
function reloadTriggers() {
$(".basic").toggle(!$(".advancedview").is(":checked"));
$(".advanced").toggle($(".advancedview").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('<i class="fa fa-fw fa-chevron-up"></i> _(Hide more settings)_ ...');
} else {
$('#configLocationAdvanced').slideUp('fast');
readm.removeClass('readmore_expanded').addClass('readmore_collapsed');
readm.find('a').html('<i class="fa fa-fw fa-chevron-down"></i> _(Show more settings)_ ...');
}
}
function toggleAllocations() {
var readm = $('#allocations_toggle');
if (readm.hasClass('readmore_collapsed')) {
readm.removeClass('readmore_collapsed').addClass('readmore_expanded');
$('#dockerAllocations').slideDown('fast');
readm.find('a').html('<i class="fa fa-fw fa-chevron-up"></i> _(Hide docker allocations)_ ...');
} else {
$('#dockerAllocations').slideUp('fast');
readm.removeClass('readmore_expanded').addClass('readmore_collapsed');
readm.find('a').html('<i class="fa fa-fw fa-chevron-down"></i> _(Show docker allocations)_ ...');
}
}
function load_contOverview() {
var new_overview = $("textarea[name='contOverview']").val();
new_overview = new_overview.replaceAll("[","<").replaceAll("]",">");
// Handle code block being created by authors indenting (manually editing the xml and spacing)
new_overview = new_overview.replaceAll(" ","&nbsp;&nbsp;&nbsp;&nbsp;");
new_overview = marked(new_overview);
new_overview = new_overview.replaceAll("\n","<br>"); // has to be after marked
$("#contDescription").html(new_overview);
var new_requires = $("textarea[name='contRequires']").val();
new_requires = new_requires.replaceAll("[","<").replaceAll("]",">");
// Handle code block being created by authors indenting (manually editing the xml and spacing)
new_requires = new_requires.replaceAll(" ","&nbsp;&nbsp;&nbsp;&nbsp;");
new_requires = marked(new_requires);
new_requires = new_requires.replaceAll("\n","<br>"); // has to be after marked
new_requires = new_requires ? new_requires : "<em>_(None Listed)_</em>";
$("#contRequires").html(new_requires);
}
$(function() {
// Load container info on page load
if (typeof Settings != 'undefined') {
for (var key in Settings) {
if (Settings.hasOwnProperty(key)) {
var target = $('#canvas').find('*[name=cont'+key+']:first');
if (target.length) {
var value = Settings[key];
if (target.attr("type") == 'checkbox') {
target.prop('checked', (value == 'true'));
} else if ($(target).prop('nodeName') == 'DIV') {
target.html(value);
} else {
target.val(value);
}
}
}
}
load_contOverview();
// Load the confCategory input into the s1 select
categories=$("input[name='contCategory']").val().split(" ");
for (var i = 0; i < categories.length; i++) {
$("#catSelect option[value='"+categories[i]+"']").prop("selected", true);
}
// Remove empty description
if (!Settings.Description.length) {
$('#canvas').find('#Overview:first').hide();
}
// Load config info
var network = $('select[name="contNetwork"]')[0].selectedIndex;
for (var i = 0; i < Settings.Config.length; i++) {
confNum += 1;
Opts = Settings.Config[i];
if (Opts.Display == "always-hide" || Opts.Display == "advanced-hide") {
Opts.Buttons = "<span class='advanced'><button type='button' onclick='editConfigPopup("+confNum+",<?=$disableEdit?>)'>_(Edit)_</button>";
Opts.Buttons += "<button type='button' onclick='removeConfig("+confNum+")'>_(Remove)_</button></span>";
} else {
Opts.Buttons = "<button type='button' onclick='editConfigPopup("+confNum+",<?=$disableEdit?>)'>_(Edit)_</button>";
Opts.Buttons += "<button type='button' onclick='removeConfig("+confNum+")'>_(Remove)_</button>";
}
Opts.Number = confNum;
newConf = makeConfig(Opts);
if (Opts.Display == 'advanced' || Opts.Display == 'advanced-hide') {
$("#configLocationAdvanced").append(newConf);
} else {
$("#configLocation").append(newConf);
}
}
} else {
$('#canvas').find('#Overview:first').hide();
}
// Show associated subnet with fixed IP (if existing)
showSubnet($('select[name="contNetwork"]').val());
// Add list of docker allocations
$("#dockerAllocations").html(makeAllocations(Allocations,$('input[name="contName"]').val()));
// Add switchButton
$('.switch-on-off').switchButton({labels_placement:'right',on_label:"_(On)_",off_label:"_(Off)_"});
// Add dropdownchecklist to Select Categories
$("#catSelect").dropdownchecklist({emptyText:"_(Select categories)_...", maxDropHeight:200, width:300, explicitClose:"..._(close)_"});
<?if ($authoringMode){
echo "$('.advancedview').prop('checked','true'); $('.advancedview').change();";
echo "$('.advancedview').siblings('.switch-button-background').click();";
}?>
});
if (window.location.href.indexOf("/Apps/") > 0 && <? if (is_file($xmlTemplate)) echo "true"; else echo "false"; ?> ) {
$(".TemplateDropDown").hide();
}
</script>
<?END:?>