github.com/billybanfield/evergreen@v0.0.0-20170525200750-eeee692790f7/service/templates/distros.html (about)

     1  {{define "scripts"}}
     2  <script type="text/javascript" src="{{Static "js" "distros.js"}}?hash={{ StaticsMD5 }}"></script>
     3  <script type="text/javascript">
     4    window.distros = {{ .Distros }};
     5    window.keys = {{ .Keys }};
     6  </script>
     7  {{end}}
     8  {{define "title"}}
     9  Evergreen - Distros
    10  {{end}}
    11  {{define "content"}}
    12  <div class="container" ng-controller="DistrosCtrl" style="margin-bottom: 15px;">
    13    <notify-box ng-init="destination='errorHeader'"></notify-box>
    14    {{template "flash" . }}
    15    <div ng-show="distros.length == 0">
    16      <h2>No Distros</h2>
    17      <div class="row">
    18        <button type="button" ng-hide="readOnly" class="btn btn-primary" style="margin-left: 15px" ng-click="newDistro()" ng-disabled="activeDistro.new"><i class="fa fa-plus"></i>New Distro</button> 
    19      </div>
    20    </div>
    21    <div ng-form="form" class="row">
    22      <div id="nav-container" class="col-md-2" ng-show="distros.length != 0">
    23        <div>
    24          <div>
    25            <h2 style="text-align: center;">Distros<span ng-show="distros.length != 0">([[distros.length]])</span></h2>
    26          </div>
    27          <div class="row" style="text-align: center;">
    28            <button type="button" class="btn btn-primary col-lg-8" ng-hide="readOnly" style="margin-bottom: 10px; margin-left: 35px" ng-click="newDistro()" ng-disabled="activeDistro.new"><i class="fa fa-plus"></i>New Distro</button>
    29          </div>
    30          <div id="distros-list-container">
    31            <ul id="distros-list">
    32              <li ng-repeat="distro in distros" ng-click="form.$setPristine();setActiveDistro(distro)"
    33                ng-class="{'active-distro': distro._id == activeDistro._id}">
    34                [[distro._id]]
    35              </li>
    36            </ul>
    37          </div>
    38        </div>
    39      </div>
    40      <div class="col-lg-2"></div>
    41      <div class="col-lg-6 col-lg-offset-1" ng-show="distros.length != 0">
    42        <div ng-init="initOptions()">
    43          <div ng-show="activeDistro">
    44            <h2 style="display:inline-block; padding-right:15px">Configure
    45            </h2>
    46              <a class="pointer" ng-click="copyDistro()" ng-hide="hasNew||readOnly"> make a copy </a> / 
    47              <a ng-href="/event_log/distro/[[activeDistro._id]]"> view event log </a>
    48          </div>
    49          <div style="padding-top: -25px;" class="panel-body panel-default">
    50            <div>
    51              <label class="distro-label">Identifier:</label>
    52              <input required ng-readonly="!activeDistro.new" id="identifier" name="id" type="text" class="form-control" ng-model="activeDistro._id" placeholder="Unique identifier for this distro">
    53              <label class="icon fa fa-warning distro-error" ng-show="form.id.$error.required">Distro identifier is required<br></label>
    54              <label class="icon fa fa-warning distro-error" ng-show="form.id.$dirty && form.id.$error.unsique || (activeDistro.new && form.id.$error.unique)">Distro identifier already exists</label>
    55            </div>
    56            <br>
    57  
    58            <div class="panel-body panel panel-default">
    59              <div class="dropdown">
    60                <span class="distro-menu-title">Agent Architecture:</span>
    61                <button class="btn btn-default dropdown-toggle" type="button" data-toggle="dropdown" aria-expanded="true" ng-disabled="readOnly">
    62                <strong class="distro-menu-item">[[activeDistro.arch | archDisplay:this]]<span class="fa fa-caret-down"></span></strong>
    63                </button>
    64                <ul class="dropdown-menu" style="margin-left: 125px; align: left;" role="menu">
    65                  <li ng-click="form.$setDirty();setKeyValue('arch', arch.id)" required name="agentArch" ng-repeat="arch in architectures | orderBy:'display'" role="presentation"><a role="menuitem" tabindex="-1">[[arch.display]]</a></li>
    66                </ul>
    67                <div class="icon fa fa-warning distro-error" ng-show="form.agentArch.$dirty && form.agentArch.$error.required || form.agentArch.$invalid">Agent architecture is required</div>
    68              </div>
    69              <div>
    70                <label class="distro-label">Working Directory:</label>
    71                <input required name="workDir" type="text" class="form-control" ng-model="activeDistro.work_dir" placeholder="Absolute path in which agent runs tasks on host machine" ng-readonly="readOnly">
    72                <div class="icon fa fa-warning distro-error" ng-show="form.workDir.$dirty && form.workDir.$error.required || form.workDir.$invalid">Working Directory is required</div>
    73              </div>
    74            </div>
    75            <br>
    76            <div class="panel-body panel panel-default">
    77              <div class="dropdown">
    78                <span class="distro-menu-title">Provider:</span>
    79                <button class="btn btn-default dropdown-toggle" ng-disabled="readOnly" type="button" data-toggle="dropdown">
    80                <strong class="distro-menu-item">[[activeDistro.provider | providerDisplay:this]]<span class="fa fa-caret-down"></span></strong>
    81                </button>
    82                <ul class="dropdown-menu" role="menu" style="margin-left: 60px; align: left;">
    83                  <li ng-click="form.$setDirty();setKeyValue('provider', provider.id)" required name="providerForm" ng-repeat="provider in providers" role="presentation"><a role="menuitem" tabindex="-1">[[provider.display]]</a></li>
    84                </ul>
    85                <div class="icon fa fa-warning distro-error" ng-show="form.providerForm.$dirty && form.providerForm.$error.required">Distro provider is required</div>
    86              </div>
    87              <div ng-show="activeDistro.provider == 'docker'">
    88                <div>
    89                  <label class="distro-label">Host:</label>
    90                  <input type="text" ng-required="activeDistro.provider == 'docker'" name="hostIP" class="form-control" ng-model="activeDistro.settings.host_ip" placeholder="Machine DNS name" ng-readonly="readOnly">
    91                  <div class="icon fa fa-warning distro-error" ng-show="form.hostIP.$dirty && form.hostIP.$error.required || form.hostIP.$invalid">Host DNS is required</div>
    92                </div>
    93                <div>
    94                  <label class="distro-label">Image ID:</label>
    95                  <input type="text" ng-readonly="readOnly" ng-required="activeDistro.provider == 'docker'" name="imageName" class="form-control" ng-model="activeDistro.settings.image_name">
    96                  <div class="icon fa fa-warning distro-error" ng-show="form.imageName.$dirty && form.imageName.$error.required || form.imageName.$invalid">Image ID is required</div>
    97                </div>
    98                <div>
    99                  <label class="distro-label">Bind Address:</label>
   100                  <input type="text" ng-required="activeDistro.provider == 'docker'" name="bindIP" class="form-control" ng-model="activeDistro.settings.bind_ip" placeholder="e.g. localhost" ng-readonly="readOnly">
   101                  <div class="icon fa fa-warning distro-error" ng-show="form.bindIP.$dirty && form.bindIP.$error.required || form.bindIP.$invalid">An address for the container to bind a port to is required</div>
   102                </div>
   103  
   104                <div>
   105                  <label class="distro-label">Docker Client Port:</label>
   106                  <input ng-readonly="readOnly" ng-required="activeDistro.provider == 'docker'" name="clientPort" class="form-control" type="number" ng-model="activeDistro.settings.client_port" placeholder="Port for connecting to docker client, e.g. 2376">
   107                  <div class="icon fa fa-warning distro-error" ng-show="form.clientPort.$dirty && form.clientPort.$error.required || form.clientPort.$invalid || form.clientPort.$modelValue < 0">Non-negative numeric Client Port is required</div>
   108                </div>
   109                <div id="port-table" class="distro-table-scroll">
   110                  <label class="distro-label">Container Port Range:</label>
   111                  <table style="margin-left: -8px;" ng-form name="portRange" class="table distro-table" ng-init="form.devName=''; form.devSize=''">
   112                    <tr>
   113                      <td style="padding-left: 10px;"><input ng-readonly="readOnly" ng-required="activeDistro.settings.port_range.max_port" name="minPort" type="number" ng-model="activeDistro.settings.port_range.min_port" class="form-control" placeholder="Min Port"></td>
   114                      <td><input ng-readonly="readOnly" ng-required="activeDistro.settings.port_range.min_port" name="maxPort" type="number" ng-model="activeDistro.settings.port_range.max_port" class="form-control" placeholder="Max Port"></td>
   115                    </tr>
   116                  </table>
   117                  <div class="icon fa fa-warning distro-error" ng-show="!checkPortRange(form.portRange.minPort.$modelValue, form.portRange.maxPort.$modelValue)">A non-negative, increasing port range is required</div>
   118                </div>
   119                <div>
   120                  <label class="distro-label">Cert.pem:</label>
   121                  <textarea ng-required="activeDistro.provider == 'docker'" name="cert" type="text" wrap="off" class="form-control" rows="5" ng-model="activeDistro.settings.auth.cert" style="margin-left: 0px;" placeholder="Paste your (PEM formatted) certificate here" ng-readonly="readOnly"></textarea>
   122                  <div class="icon fa fa-warning distro-error" ng-show="form.cert.$dirty && form.cert.$error.required || form.cert.$invalid">Valid certificate is required</div>
   123                </div>
   124                <div>
   125                  <label class="distro-label">Key.pem:</label>
   126                  <textarea ng-required="activeDistro.provider == 'docker'" name="key" type="text" wrap="off" class="form-control" rows="5" ng-model="activeDistro.settings.auth.key" style="margin-left: 0px;" placeholder="Paste your (PEM formatted) private key here" ng-readonly="readOnly"></textarea>
   127                  <div class="icon fa fa-warning distro-error" ng-show="form.key.$dirty && form.key.$error.required || form.key.$invalid">Valid key is required</div>
   128                </div>
   129                <div>
   130                  <label class="distro-label">Ca.pem:</label>
   131                  <textarea ng-required="activeDistro.provider == 'docker'" name="ca" type="text" wrap="off" class="form-control" rows="5" ng-model="activeDistro.settings.auth.ca" style="margin-left: 0px;" placeholder="Paste your (PEM formatted) certificate authority here" ng-readonly="readOnly"></textarea>
   132                  <div class="icon fa fa-warning distro-error" ng-show="form.ca.$dirty && form.ca.$error.required || form.ca.$invalid">Valid certificate authority is required</div>
   133                </div>
   134              </div>
   135              <div ng-show="activeDistro.provider == 'digitalocean'">
   136                <div>
   137                  <label class="distro-label">Image ID:</label>
   138                  <input ng-readonly="readOnly" ng-required="activeDistro.provider == 'digitalocean'" name="imageID" type="number" class="form-control" ng-model="activeDistro.settings.image_id">
   139                  <div class="icon fa fa-warning distro-error" ng-show="form.imageID.$dirty && form.imageID.$error.required || form.imageID.$invalid">Numeric image ID is required</div>
   140                </div>
   141                <div>
   142                  <label class="distro-label">Size ID:</label>
   143                  <input ng-readonly="readOnly" ng-required="activeDistro.provider == 'digitalocean'" name="sizeID" type="number" class="form-control" ng-model="activeDistro.settings.size_id">
   144                  <div class="icon fa fa-warning distro-error" ng-show="form.sizeID.$dirty && form.sizeID.$error.required || form.sizeID.$invalid">Numeric size ID is required</div>
   145                </div>
   146                <div>
   147                  <label class="distro-label">Region ID:</label>
   148                  <input ng-readonly="readOnly" ng-required="activeDistro.provider == 'digitalocean'" name="regionID" type="number" class="form-control" ng-model="activeDistro.settings.region_id">
   149                  <div class="icon fa fa-warning distro-error" ng-show="form.regionID.$dirty && form.regionID.$error.required || form.regionID.$invalid">Numeric region ID is required</div>
   150                </div>
   151                <div>
   152                  <label class="distro-label">SSH Key ID:</label>
   153                  <input ng-readonly="readOnly" ng-required="activeDistro.provider == 'digitalocean'" name="sshKeyID" type="number" class="form-control" ng-model="activeDistro.settings.ssh_key_id">
   154                  <div class="icon fa fa-warning distro-error" ng-show="form.sshKeyID.$dirty && form.sshKeyID.$error.required || form.sshKeyID.$invalid">Numeric SSH Key ID is required</div>
   155                </div>
   156              </div>
   157              <div ng-show="activeDistro.provider == 'ec2' || activeDistro.provider == 'ec2-spot'">
   158                <div>
   159                  <label class="distro-label">AMI ID:</label>
   160                  <input ng-readonly="readOnly" type="text" ng-required="activeDistro.provider == 'ec2' || activeDistro.provider == 'ec2-spot'" name="ami" class="form-control" ng-model="activeDistro.settings.ami" placeholder="EC2 image identifier e.g. ami-1ecae776" ng-readonly="readOnly">
   161                  <div class="icon fa fa-warning distro-error" ng-show="form.ami.$dirty && form.ami.$error.required || form.ami.$invalid">AMI ID is required</div>
   162                </div>
   163                <div>
   164                  <label class="distro-label">Instance Type:</label>
   165                  <input ng-readonly="readOnly" type="text" ng-required="activeDistro.provider == 'ec2' || activeDistro.provider == 'ec2-spot'" name="instanceType" class="form-control" ng-model="activeDistro.settings.instance_type" placeholder="EC2 instance type for the AMI e.g t1.micro (must be available)" ng-readonly="readOnly">
   166                  <div class="icon fa fa-warning distro-error" ng-show="form.instanceType.$dirty && form.instanceType.$error.required || form.instanceType.$invalid">Instance type is required</div>
   167                </div>
   168                <div ng-show="activeDistro.provider == 'ec2-spot'">
   169                  <label class="distro-label">Bid Price:</label>
   170                  <input ng-readonly="readOnly" ng-required="activeDistro.provider == 'ec2-spot'" name="bidPrice" type="number" class="form-control" ng-model="activeDistro.settings.bid_price" placeholder="Maximum amount you're willing to pay per hour (dollars)">
   171                  <div class="icon fa fa-warning distro-error" ng-show="form.bidPrice.$dirty && form.bidPrice.$error.required || form.bidPrice.$invalid">Numeric bid price is required</div>
   172                </div>
   173                <div>
   174                  <label class="distro-label">Key Name:</label>
   175                  <input type="text" ng-readonly="readOnly" ng-required="activeDistro.provider == 'ec2' || activeDistro.provider == 'ec2-spot'" name="keyName" class="form-control" ng-model="activeDistro.settings.key_name" placeholder="SSH Key (public part in EC2) to add on host machine" ng-readonly="readOnly">
   176                  <div class="icon fa fa-warning distro-error" ng-show="form.keyName.$dirty && form.keyName.$error.required || form.keyName.$invalid">EC2 key name is required</div>
   177                </div>
   178                <div>
   179                  <label class="distro-label"><input style="margin-right:10px;" ng-disabled="readOnly" type="checkbox" name="is_vpc" ng-model="activeDistro.settings.is_vpc">Use security group in an EC2 VPC </label> <br>
   180                  <label class="distro-label">Security Group:</label>
   181                  <input type="text" ng-readonly="readOnly" ng-required="activeDistro.provider == 'ec2' || activeDistro.provider == 'ec2-spot'" name="securityGroup" ng-model="activeDistro.settings.security_group" placeholder="EC2 security group (must already exist)" class="form-control">
   182                  <div class="icon fa fa-warning distro-error" ng-show="form.securityGroup.$dirty && form.securityGroup.$error.required || form.securityGroup.$invalid">Security group is required</div>
   183                  <div class="icon fa fa-warning distro-error" ng-show="!validSecurityGroup()">Security group for EC2 VPC must be the id (starts with 'sg-')</div>
   184                </div>
   185                <div ng-show="activeDistro.settings.is_vpc">
   186                  <label class="distro-label"> Subnet Id</label>
   187                  <input type="text" name="subnet_id" ng-readonly="readOnly" class="form-control" ng-model="activeDistro.settings.subnet_id" placeholder="EC2 subnet id (must already exist) e.g subnet-xxxx" ng-required="activeDistro.settings.is_vpc">
   188                  <div class="icon fa fa-warning distro-error" ng-show="form.securityGroup.$dirty && form.subnet_id.$error.required || form.subnet_id.$invalid || !validSubnetId()"> Subnet Id is required for EC2 VPC (must start with 'subnet-')</div>
   189                </div>
   190                <div ng-show="activeDistro.provider == 'ec2' || activeDistro.provider == 'ec2-spot'">
   191                  <div id="mounts-table" class="distro-table-scroll">
   192                    <label class="distro-label">Mount Points:</label>
   193                    <table ng-form name="mountPoints" class="table distro-table" ng-show="activeDistro.settings.mount_points" ng-init="form.devName=''; form.devSize=''">
   194                      <thead class="muted">
   195                        <tr>
   196                          <th>Device Name</th>
   197                          <th>Virtual Name</th>
   198                          <th>Size</th>
   199                        </tr>
   200                      </thead>
   201                      <tbody ng-repeat="mount_point in activeDistro.settings.mount_points">
   202                        <tr>
   203                          <td><input ng-readonly="readOnly" required name="devName" type="text" ng-model="mount_point.device_name" class="form-control"></td>
   204                          <td><input ng-readonly="readOnly" ng-required="!mount_point.size" name="virtName" type="text" ng-model="mount_point.virtual_name" class="form-control"></td>
   205                          <td><input ng-readonly="readOnly" type="number" ng-required="!mount_point.virtual_name" name="devSize" type="text" ng-model="mount_point.size" class="form-control"></td>
   206                          <td ng-hide="readOnly"><a ng-click="form.$setDirty();removeMount(mount_point)"><i style="margin-top:9px" class="fa fa-trash distro-trash-icon"></i></a></td>
   207                        </tr>
   208                      </tbody>
   209                    </table>
   210                  </div>
   211                </div>
   212                <div>
   213                  <div class="icon fa fa-warning distro-error" ng-show="mountPoints.devName.$dirty && mountPoints.devName.$error.required">Device name is required<br /></div>
   214                  <div class="icon fa fa-warning distro-error" ng-show="mountPoints.devName.$dirty && mountPoints.virtName.$error.required && mountPoints.devSize.$error.required">Must specify either virtual device name or device size<br /></div>
   215                  <button ng-hide="readOnly" type="button" ng-disabled="mountPoints.devName.$dirty && mountPoints.$invalid || mountPoints.devName.$error.required" class="btn btn-primary" ng-click="form.$setDirty();addMount()"><i class="fa fa-plus"></i>Add Mount Point</button>
   216                </div>
   217              </div>
   218              <div ng-show="activeDistro.provider != 'static'">
   219                <label class="distro-label">Maximum number of hosts allowed:</label>
   220                <input ng-readonly="readOnly" type="number" ng-required="activeDistro.provider != 'static'" name="poolSize" class="form-control" ng-model="activeDistro.pool_size" placeholder="Max pool size e.g. 10">
   221                <div class="icon fa fa-warning distro-error" ng-show="form.poolSize.$dirty && form.poolSize.$error.required || form.poolSize.$invalid">Numeric pool size is required</div>
   222              </div>
   223              <div ng-form name="hostProviderForm" ng-show="activeDistro.provider == 'static'">
   224                <label class="distro-label">Hosts<span ng-show="activeDistro.settings.hosts && activeDistro.settings.hosts.length != 0">([[activeDistro.settings.hosts.length]])</span>:</label>
   225                <div id="hosts-table" class="distro-table-scroll">
   226                  <table style="margin-left: -8px;" class="table distro-table" ng-show="activeDistro.settings.hosts">
   227                    <tbody id="hosts-table" >
   228                      <tr ng-repeat="host in activeDistro.settings.hosts">
   229                        <td><input ng-readonly="readOnly" required name="hostName" type="text" ng-model="host.name" class="col-md-10" placeholder="Machine DNS name"></td>
   230                        <td ng-hide="readOnly"><a ng-click="form.$setDirty();removeHost(host)"><i class="fa fa-trash distro-trash-icon"></i></a></td>
   231                      </tr>
   232                    </tbody>
   233                  </table>
   234                </div>
   235                <div>
   236                  <div class="icon fa fa-warning distro-error" ng-show="hostProviderForm.hostName.$dirty && hostProviderForm.hostName.$error.required">Static host can not be blank<br /></div>
   237                  <br />
   238                  <button type="button" ng-hide="readOnly" ng-disabled="hostProviderForm.hostName.$dirty && hostProviderForm.$invalid || hostProviderForm.hostName.$error.required" class="btn btn-primary" ng-click="form.$setDirty();addHost()"><i class="fa fa-plus"></i>Add Host</button>
   239                </div>
   240              </div>
   241            </div>
   242            <div>
   243              <label class="distro-label">User:</label>
   244              <input required ng-readonly="readOnly" name="userName" type="text" class="form-control" ng-model="activeDistro.user" placeholder="Username with which to SSH into host machine">
   245              <div class="icon fa fa-warning distro-error" ng-show="form.userName.$dirty && form.userName.$error.required || form.userName.$invalid">SSH user is required</div><br>
   246            </div>
   247            <div class="dropdown">
   248              <span class="distro-menu-title">SSH Key:</span>
   249              <button class="btn btn-default dropdown-toggle" ng-disabled="readOnly" type="button" data-toggle="dropdown">
   250              <strong class="distro-menu-item">[[activeDistro.ssh_key]]<span class="fa fa-caret-down"></span></strong>
   251              </button>
   252              <ul class="dropdown-menu" role="menu" style="margin-left: 63px; align: left;">
   253                <li required name="sshKeyForm" ng-click="form.$setDirty();setKeyValue('ssh_key', key.name)" ng-repeat="key in keys" role="presentation"><a role="menuitem" tabindex="-1">[[key.name]] - [[key.location]]</a></li>
   254              </ul>
   255              <br>
   256              <div class="icon fa fa-warning distro-error" ng-show="!activeDistro.ssh_key">SSH keys must be configured</div>
   257            </div>
   258            <div ng-form name="sshForm">
   259              <label class="distro-label">SSH Options:</label>
   260              <div id="ssh-options-table" class="distro-table-scroll">
   261                <table style="margin-left: -8px;" class="table distro-table">
   262                  <tbody ng-repeat="opt in activeDistro.ssh_options track by $index">
   263                    <tr>
   264                      <td style="padding-left: 10px;"><input required ng-readonly="readOnly" name="opt" type="text" ng-model="activeDistro.ssh_options[$index]"
   265                        class="form-control" placeholder="e.g. BatchMode=yes">
   266                      </td>
   267                      <td ng-hide="readOnly"> 
   268                        <a ng-click="form.$setDirty();removeSSHOption(opt)">
   269                          <i class="fa fa-trash distro-trash-icon"></i>
   270                        </a>
   271                      </td>
   272                    </tr>
   273                  </tbody>
   274                </table>
   275              </div>
   276              <div class="icon fa fa-warning distro-error" ng-show="sshForm.opt.$dirty && sshForm.opt.$error.required">SSH option can not be blank<br /></div>
   277              <button type="button" class="btn btn-primary" ng-hide="readOnly" ng-disabled="sshForm.opt.$dirty && sshForm.$invalid || sshForm.opt.$error.required" ng-click="form.$setDirty();addSSHOption()"><i class="fa fa-plus"></i>Add SSH Option</button>
   278            </div>
   279            <div>
   280              <div>
   281                <span style="float: right; margin-top: 20px;" class="distro-checkbox checkbox"><input ng-disabled="readOnly" type="checkbox" ng-model="activeDistro.setup_as_sudo">Run scripts as sudo</span>
   282                <label class="distro-label">Setup Script:</label>
   283              </div>
   284              <textarea ng-readonly="readOnly" name="script" type="text" wrap="off" class="form-control" rows="7" ng-model="activeDistro.setup" style="margin-left: 0px; font-family: monospace"></textarea>
   285              <div ng-hide="activeDistro.provider=='static'">
   286                <label class="distro-label">Teardown Script:</label>
   287                <textarea ng-readonly="readOnly" name="script" type="text" wrap="off" class="form-control" rows="2" ng-model="activeDistro.teardown" style="margin-left: 0px; font-family: monospace"></textarea>
   288                <div ng-show="activeDistro.teardown.length"><i label class="icon fa fa-warning warning-text"></i>
   289                  There is no guarantee this script will be run if the host is terminated by mechanisms outside of Evergreen.
   290                </div>
   291              </div>
   292            </div>
   293            <div>
   294              <div ng-form name="expansions">
   295                <label class="distro-label">Expansions:</label>
   296                <div id="expansions-table" class="distro-table-scroll">
   297                  <table style="margin-left: -8px;" class="table distro-table" ng-show="activeDistro.expansions">
   298                    <thead class="muted">
   299                      <tr>
   300                        <th>Key</th>
   301                        <th>Value</th>
   302                      </tr>
   303                    </thead>
   304                    <tbody ng-repeat="expansion in activeDistro.expansions">
   305                      <tr>
   306                        <td><input ng-readonly="readOnly" type="text" required name="expKey" ng-model="expansion.key" class="form-control"></td>
   307                        <td><input ng-readonly="readOnly" type="text" ng-model="expansion.value" class="form-control"></td>
   308                        <td ng-hide="readOnly"><a ng-click="form.$setDirty();removeExpansion(expansion)"><i class="fa fa-trash distro-trash-icon"></i></a></td>
   309                      </tr>
   310                    </tbody>
   311                  </table>
   312                </div>
   313                <div>
   314                  <div class="icon fa fa-warning distro-error" ng-show="expansions.expKey.$dirty && expansions.expKey.$error.required">Expansion key can not be blank<br /></div>
   315                  <button type="button" ng-hide="readOnly" ng-disabled="(expansions.expKey.$dirty && expansions.$invalid) || expansions.expKey.$error.required" class="btn btn-primary" ng-click="form.$setDirty();addExpansion()"><i class="fa fa-plus"></i>Add Expansion</button>
   316                </div>
   317              </div>
   318              <div>
   319                <p class="distro-checkbox checkbox">
   320                  <input ng-disabled="readOnly" type="checkbox" ng-model="activeDistro.spawn_allowed">
   321                  Allow users to spawn these hosts for personal use
   322                </p>
   323              </div>
   324            </div>
   325          </div>
   326          <div ng-hide="readOnly">
   327            <br><br>
   328            <p class="distro-checkbox checkbox" style="margin-left: 5px">
   329              <input ng-disabled="readOnly" type="checkbox" name="shouldDeco" ng-model="shouldDeco">
   330              Decommission hosts of this distro for this update
   331            </p>
   332            <button type="button" class="btn btn-primary" style="float: left; margin-left: 5px;" ng-disabled="form.$pristine || (form.$dirty && form.$invalid) || !validForm()" ng-click="saveConfiguration()">Save Configuration</button>
   333            <button type="button" class="btn btn-danger" style="float: right; margin-right: 5px;" ng-click="openConfirmationModal('removeDistro')" ng-disabled="activeDistro.new">Remove Configuration</button>
   334            <admin-modal>
   335              <remove-distro ng-show="confirmationOption == 'removeDistro'"></remove-distro>
   336            </admin-modal>
   337          </div>
   338        </div>
   339      </div>
   340    </div>
   341  </div>
   342  {{end}}