mirror of
https://github.com/portainer/portainer.git
synced 2025-07-24 15:59:41 +02:00
feat(ui): new container creation view (#29)
This commit is contained in:
parent
87e835e873
commit
227e5883e9
10 changed files with 570 additions and 636 deletions
213
app/components/createContainer/createContainerController.js
Normal file
213
app/components/createContainer/createContainerController.js
Normal file
|
@ -0,0 +1,213 @@
|
|||
angular.module('createContainer', [])
|
||||
.controller('CreateContainerController', ['$scope', '$state', 'Config', 'Container', 'Image', 'Volume', 'Network', 'Messages', 'ViewSpinner', 'errorMsgFilter',
|
||||
function ($scope, $state, Config, Container, Image, Volume, Network, Messages, ViewSpinner, errorMsgFilter) {
|
||||
|
||||
$scope.state = {
|
||||
alwaysPull: true
|
||||
};
|
||||
|
||||
$scope.formValues = {
|
||||
Console: 'none',
|
||||
Volumes: [],
|
||||
};
|
||||
|
||||
$scope.config = {
|
||||
Env: [],
|
||||
HostConfig: {
|
||||
RestartPolicy: {
|
||||
Name: 'no'
|
||||
},
|
||||
PortBindings: [],
|
||||
Binds: [],
|
||||
NetworkMode: 'bridge',
|
||||
Privileged: false
|
||||
}
|
||||
};
|
||||
|
||||
$scope.resetVolumePath = function(index) {
|
||||
$scope.formValues.Volumes[index].name = '';
|
||||
};
|
||||
|
||||
$scope.addVolume = function() {
|
||||
$scope.formValues.Volumes.push({ name: '', containerPath: '', readOnly: false, isPath: false });
|
||||
};
|
||||
|
||||
$scope.removeVolume = function(index) {
|
||||
$scope.formValues.Volumes.splice(index, 1);
|
||||
};
|
||||
|
||||
$scope.addEnvironmentVariable = function() {
|
||||
$scope.config.Env.push({ name: '', value: ''});
|
||||
};
|
||||
|
||||
$scope.removeEnvironmentVariable = function(index) {
|
||||
$scope.config.Env.splice(index, 1);
|
||||
};
|
||||
|
||||
$scope.addPortBinding = function() {
|
||||
$scope.config.HostConfig.PortBindings.push({ hostPort: '', containerPort: '', protocol: 'tcp' });
|
||||
};
|
||||
|
||||
$scope.removePortBinding = function(index) {
|
||||
$scope.config.HostConfig.PortBindings.splice(index, 1);
|
||||
};
|
||||
|
||||
Config.$promise.then(function (c) {
|
||||
var swarm = c.swarm;
|
||||
|
||||
Volume.query({}, function (d) {
|
||||
$scope.availableVolumes = d.Volumes;
|
||||
}, function (e) {
|
||||
Messages.error("Failure", e.data);
|
||||
});
|
||||
|
||||
Network.query({}, function (d) {
|
||||
var networks = d;
|
||||
if (swarm) {
|
||||
networks = d.filter(function (network) {
|
||||
if (network.Scope === 'global') {
|
||||
return network;
|
||||
}
|
||||
});
|
||||
networks.push({Name: "bridge"});
|
||||
networks.push({Name: "host"});
|
||||
networks.push({Name: "none"});
|
||||
}
|
||||
$scope.availableNetworks = networks;
|
||||
}, function (e) {
|
||||
Messages.error("Failure", e.data);
|
||||
});
|
||||
});
|
||||
|
||||
function createContainer(config) {
|
||||
ViewSpinner.spin();
|
||||
Container.create(config, function (d) {
|
||||
if (d.Id) {
|
||||
var reqBody = config.HostConfig || {};
|
||||
reqBody.id = d.Id;
|
||||
Container.start(reqBody, function (cd) {
|
||||
ViewSpinner.stop();
|
||||
Messages.send('Container Started', d.Id);
|
||||
$state.go('containers', {}, {reload: true});
|
||||
}, function (e) {
|
||||
ViewSpinner.stop();
|
||||
Messages.error('Error', errorMsgFilter(e));
|
||||
});
|
||||
} else {
|
||||
ViewSpinner.stop();
|
||||
Messages.error('Error', errorMsgFilter(d));
|
||||
}
|
||||
}, function (e) {
|
||||
ViewSpinner.stop();
|
||||
Messages.error('Error', errorMsgFilter(e));
|
||||
});
|
||||
}
|
||||
|
||||
function createImageConfig(imageName) {
|
||||
var imageNameAndTag = imageName.split(':');
|
||||
var imageConfig = {
|
||||
fromImage: imageNameAndTag[0],
|
||||
tag: imageNameAndTag[1] ? imageNameAndTag[1] : 'latest'
|
||||
};
|
||||
return imageConfig;
|
||||
}
|
||||
|
||||
function pullImageAndCreateContainer(config) {
|
||||
ViewSpinner.spin();
|
||||
|
||||
var image = _.toLower(config.Image);
|
||||
var imageConfig = createImageConfig(image);
|
||||
|
||||
Image.create(imageConfig, function (data) {
|
||||
var err = data.length > 0 && data[data.length - 1].hasOwnProperty('error');
|
||||
if (err) {
|
||||
var detail = data[data.length - 1];
|
||||
ViewSpinner.stop();
|
||||
Messages.error('Error', detail.error);
|
||||
} else {
|
||||
createContainer(config);
|
||||
}
|
||||
}, function (e) {
|
||||
ViewSpinner.stop();
|
||||
Messages.error('Error', 'Unable to pull image ' + image);
|
||||
});
|
||||
}
|
||||
|
||||
function preparePortBindings(config) {
|
||||
var bindings = {};
|
||||
config.HostConfig.PortBindings.forEach(function (portBinding) {
|
||||
if (portBinding.hostPort && portBinding.containerPort) {
|
||||
var key = portBinding.containerPort + "/" + portBinding.protocol;
|
||||
bindings[key] = [{ HostPort: portBinding.hostPort }];
|
||||
}
|
||||
});
|
||||
config.HostConfig.PortBindings = bindings;
|
||||
}
|
||||
|
||||
function prepareConsole(config) {
|
||||
var value = $scope.formValues.Console;
|
||||
var openStdin = true;
|
||||
var tty = true;
|
||||
if (value === 'tty') {
|
||||
openStdin = false;
|
||||
} else if (value === 'interactive') {
|
||||
tty = false;
|
||||
} else if (value === 'none') {
|
||||
openStdin = false;
|
||||
tty = false;
|
||||
}
|
||||
config.OpenStdin = openStdin;
|
||||
config.Tty = tty;
|
||||
}
|
||||
|
||||
function prepareEnvironmentVariables(config) {
|
||||
var env = [];
|
||||
config.Env.forEach(function (v) {
|
||||
if (v.name && v.value) {
|
||||
env.push(v.name + "=" + v.value);
|
||||
}
|
||||
});
|
||||
config.Env = env;
|
||||
}
|
||||
|
||||
function prepareVolumes(config) {
|
||||
var binds = [];
|
||||
var volumes = {};
|
||||
|
||||
$scope.formValues.Volumes.forEach(function (volume) {
|
||||
var name = volume.name;
|
||||
var containerPath = volume.containerPath;
|
||||
if (name && containerPath) {
|
||||
var bind = name + ':' + containerPath;
|
||||
volumes[containerPath] = {};
|
||||
if (volume.readOnly) {
|
||||
bind += ':ro';
|
||||
}
|
||||
binds.push(bind);
|
||||
}
|
||||
});
|
||||
config.HostConfig.Binds = binds;
|
||||
config.Volumes = volumes;
|
||||
}
|
||||
|
||||
function prepareConfiguration() {
|
||||
var config = angular.copy($scope.config);
|
||||
preparePortBindings(config);
|
||||
prepareConsole(config);
|
||||
prepareEnvironmentVariables(config);
|
||||
prepareVolumes(config);
|
||||
return config;
|
||||
}
|
||||
|
||||
$scope.create = function () {
|
||||
var config = prepareConfiguration();
|
||||
console.log(JSON.stringify(config, null, 4));
|
||||
|
||||
if ($scope.state.alwaysPull) {
|
||||
pullImageAndCreateContainer(config);
|
||||
} else {
|
||||
createContainer(config);
|
||||
}
|
||||
};
|
||||
|
||||
}]);
|
312
app/components/createContainer/createcontainer.html
Normal file
312
app/components/createContainer/createcontainer.html
Normal file
|
@ -0,0 +1,312 @@
|
|||
<rd-header>
|
||||
<rd-header-title title="Create container"></rd-header-title>
|
||||
<rd-header-content>
|
||||
Containers > Add container
|
||||
</rd-header-content>
|
||||
</rd-header>
|
||||
|
||||
<div class="row">
|
||||
<div class="col-lg-12 col-md-12 col-xs-12">
|
||||
<rd-widget>
|
||||
<rd-widget-body>
|
||||
<form class="form-horizontal">
|
||||
<!-- name-input -->
|
||||
<div class="form-group">
|
||||
<label for="container_name" class="col-sm-1 control-label text-left">Name</label>
|
||||
<div class="col-sm-11">
|
||||
<input type="text" class="form-control" ng-model="config.name" id="container_name" placeholder="e.g. myContainer">
|
||||
</div>
|
||||
</div>
|
||||
<!-- !name-input -->
|
||||
<!-- image input -->
|
||||
<div class="form-group">
|
||||
<label for="container_image" class="col-sm-1 control-label text-left">Image</label>
|
||||
<div class="col-sm-11">
|
||||
<input type="text" class="form-control" ng-model="config.Image" id="container_image" placeholder="ubuntu:trusty">
|
||||
</div>
|
||||
<div class="col-sm-offset-1 col-sm-11">
|
||||
<div class="checkbox">
|
||||
<label>
|
||||
<input type="checkbox" ng-model="state.alwaysPull"> Always pull image before creating
|
||||
</label>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
<!-- !image-input -->
|
||||
<!-- restart-policy -->
|
||||
<div class="form-group">
|
||||
<label class="col-sm-1 control-label text-left">Restart policy</label>
|
||||
<div class="col-sm-11">
|
||||
<label class="radio-inline">
|
||||
<input type="radio" name="container_restart_policy" ng-model="config.HostConfig.RestartPolicy.Name" value="no">
|
||||
Never
|
||||
</label>
|
||||
<label class="radio-inline">
|
||||
<input type="radio" name="container_restart_policy" ng-model="config.HostConfig.RestartPolicy.Name" value="always">
|
||||
Always
|
||||
</label>
|
||||
<label class="radio-inline">
|
||||
<input type="radio" name="container_restart_policy" ng-model="config.HostConfig.RestartPolicy.Name" value="on-failure">
|
||||
<span class="radio-value">On failure</span>
|
||||
</label>
|
||||
</div>
|
||||
</div>
|
||||
<!-- !restart-policy -->
|
||||
<!-- port-mapping -->
|
||||
<div class="form-group">
|
||||
<label for="container_ports" class="col-sm-1 control-label text-left">Port mapping</label>
|
||||
<div class="col-sm-11">
|
||||
<span class="label label-default clickable" ng-click="addPortBinding()">
|
||||
<i class="fa fa-plus-circle" aria-hidden="true"></i> map port
|
||||
</span>
|
||||
</div>
|
||||
<!-- port-mapping-input-list -->
|
||||
<div class="col-sm-offset-1 col-sm-11 form-inline" style="margin-top: 10px;">
|
||||
<div ng-repeat="portBinding in config.HostConfig.PortBindings" style="margin-top: 2px;">
|
||||
<div class="input-group col-sm-5 input-group-sm">
|
||||
<span class="input-group-addon">host</span>
|
||||
<input type="text" class="form-control" ng-model="portBinding.hostPort" placeholder="e.g. 80 or 1.2.3.4:80">
|
||||
</div>
|
||||
<div class="input-group col-sm-5 input-group-sm">
|
||||
<span class="input-group-addon">container</span>
|
||||
<input type="text" class="form-control" ng-model="portBinding.containerPort" placeholder="e.g. 80">
|
||||
</div>
|
||||
<div class="input-group col-sm-1 input-group-sm">
|
||||
<select class="selectpicker form-control" ng-model="portBinding.protocol">
|
||||
<option value="tcp">tcp</option>
|
||||
<option value="udp">udp</option>
|
||||
</select>
|
||||
<span class="input-group-btn">
|
||||
<button class="btn btn-default" type="button" ng-click="removePortBinding($index)">
|
||||
<i class="fa fa-minus" aria-hidden="true"></i>
|
||||
</button>
|
||||
</span>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
<!-- !port-mapping-input-list -->
|
||||
</div>
|
||||
<!-- !port-mapping -->
|
||||
</form>
|
||||
</rd-widget-body>
|
||||
</rd-widget>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div class="row">
|
||||
<div class="col-lg-12 col-md-12 col-xs-12">
|
||||
<rd-widget>
|
||||
<rd-widget-body>
|
||||
<ul class="nav nav-tabs">
|
||||
<li class="active clickable"><a data-target="#command" data-toggle="tab">Command</a></li>
|
||||
<li class="clickable"><a data-target="#volumes" data-toggle="tab">Volumes</a></li>
|
||||
<li class="clickable"><a data-target="#network" data-toggle="tab">Network</a></li>
|
||||
<li class="clickable"><a data-target="#security" data-toggle="tab">Security/Host</a></li>
|
||||
</ul>
|
||||
|
||||
<!-- tab-content -->
|
||||
<div class="tab-content">
|
||||
<!-- tab-command -->
|
||||
<div class="tab-pane active" id="command">
|
||||
<form class="form-horizontal" style="margin-top: 15px;">
|
||||
<!-- command-input -->
|
||||
<div class="form-group">
|
||||
<label for="container_command" class="col-sm-1 control-label text-left">Command</label>
|
||||
<div class="col-sm-9">
|
||||
<input type="text" class="form-control" ng-model="config.Cmd" id="container_command" placeholder="e.g. /usr/bin/nginx -t -c /mynginx.conf">
|
||||
</div>
|
||||
</div>
|
||||
<!-- !command-input -->
|
||||
<!-- entrypoint-input -->
|
||||
<div class="form-group">
|
||||
<label for="container_entrypoint" class="col-sm-1 control-label text-left">Entry Point</label>
|
||||
<div class="col-sm-9">
|
||||
<input type="text" class="form-control" ng-model="config.Entrypoint" id="container_entrypoint" placeholder="e.g. /bin/sh -c">
|
||||
</div>
|
||||
</div>
|
||||
<!-- !entrypoint-input -->
|
||||
<!-- workdir-user-input -->
|
||||
<div class="form-group">
|
||||
<label for="container_workingdir" class="col-sm-1 control-label text-left">Working Dir</label>
|
||||
<div class="col-sm-4">
|
||||
<input type="text" class="form-control" ng-model="config.WorkingDir" id="container_workingdir" placeholder="e.g. /myapp">
|
||||
</div>
|
||||
<label for="container_user" class="col-sm-1 control-label text-left">User</label>
|
||||
<div class="col-sm-4">
|
||||
<input type="text" class="form-control" ng-model="config.User" id="container_user" placeholder="e.g. nginx">
|
||||
</div>
|
||||
</div>
|
||||
<!-- !workdir-user-input -->
|
||||
<!-- console -->
|
||||
<div class="form-group">
|
||||
<label for="container_console" class="col-sm-1 control-label text-left">Console</label>
|
||||
<div class="col-sm-11">
|
||||
<div class="col-sm-4">
|
||||
<label class="radio-inline">
|
||||
<input type="radio" name="container_console" ng-model="formValues.Console" value="both">
|
||||
Interactive & TTY <span class="small text-muted">(-i -t)</span>
|
||||
</label>
|
||||
</div>
|
||||
<div class="col-sm-4">
|
||||
<label class="radio-inline">
|
||||
<input type="radio" name="container_console" ng-model="formValues.Console" value="interactive">
|
||||
Interactive <span class="small text-muted">(-i)</span>
|
||||
</label>
|
||||
</div>
|
||||
</div>
|
||||
<div class="col-sm-offset-1 col-sm-11">
|
||||
<div class="col-sm-4">
|
||||
<label class="radio-inline">
|
||||
<input type="radio" name="container_console" ng-model="formValues.Console" value="tty">
|
||||
TTY <span class="small text-muted">(-t)</span>
|
||||
</label>
|
||||
</div>
|
||||
<div class="col-sm-4">
|
||||
<label class="radio-inline">
|
||||
<input type="radio" name="container_console" ng-model="formValues.Console" value="none">
|
||||
None
|
||||
</label>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
<!-- !console -->
|
||||
<!-- environment-variables -->
|
||||
<div class="form-group">
|
||||
<label for="container_env" class="col-sm-1 control-label text-left">Environment variables</label>
|
||||
<div class="col-sm-11">
|
||||
<span class="label label-default clickable" ng-click="addEnvironmentVariable()">
|
||||
<i class="fa fa-plus-circle" aria-hidden="true"></i> environment variable
|
||||
</span>
|
||||
</div>
|
||||
<!-- environment-variable-input-list -->
|
||||
<div class="col-sm-offset-1 col-sm-11 form-inline" style="margin-top: 10px;">
|
||||
<div ng-repeat="variable in config.Env" style="margin-top: 2px;">
|
||||
<div class="input-group col-sm-5 input-group-sm">
|
||||
<span class="input-group-addon">name</span>
|
||||
<input type="text" class="form-control" ng-model="variable.name" placeholder="e.g. FOO">
|
||||
</div>
|
||||
<div class="input-group col-sm-5 input-group-sm">
|
||||
<span class="input-group-addon">value</span>
|
||||
<input type="text" class="form-control" ng-model="variable.value" placeholder="e.g. bar">
|
||||
<span class="input-group-btn">
|
||||
<button class="btn btn-default" type="button" ng-click="removeEnvironmentVariable($index)">
|
||||
<i class="fa fa-minus" aria-hidden="true"></i>
|
||||
</button>
|
||||
</span>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
<!-- !environment-variable-input-list -->
|
||||
</div>
|
||||
<!-- !environment-variables -->
|
||||
</form>
|
||||
</div>
|
||||
<!-- !tab-command -->
|
||||
<!-- tab-volume -->
|
||||
<div class="tab-pane" id="volumes">
|
||||
<form class="form-horizontal" style="margin-top: 15px;">
|
||||
<!-- volumes -->
|
||||
<div class="form-group">
|
||||
<label for="container_volumes" class="col-sm-1 control-label text-left">Volumes</label>
|
||||
<div class="col-sm-11">
|
||||
<span class="label label-default clickable" ng-click="addVolume()">
|
||||
<i class="fa fa-plus-circle" aria-hidden="true"></i> volume
|
||||
</span>
|
||||
</div>
|
||||
<!-- volumes-input-list -->
|
||||
<div class="col-sm-offset-1 col-sm-11 form-inline" style="margin-top: 10px;">
|
||||
<div ng-repeat="volume in formValues.Volumes" style="margin-top: 2px;">
|
||||
<div class="input-group col-sm-1 input-group-sm">
|
||||
<div class="checkbox">
|
||||
<label>
|
||||
<input type="checkbox" ng-model="volume.readOnly"> Read-only
|
||||
</label>
|
||||
</div>
|
||||
</div>
|
||||
<div class="input-group col-sm-5 input-group-sm">
|
||||
<span class="input-group-addon"><input type="checkbox" ng-model="volume.isPath" ng-click="resetVolumePath($index)">Path</span>
|
||||
<select class="selectpicker form-control" ng-model="volume.name" ng-if="!volume.isPath">
|
||||
<option selected disabled hidden value="">Select a volume</option>
|
||||
<option ng-repeat="vol in availableVolumes" ng-value="vol.Name">{{ vol.Name|truncate:30}}</option>
|
||||
</select>
|
||||
<input ng-if="volume.isPath" type="text" class="form-control" ng-model="volume.name" placeholder="e.g. /path/on/host">
|
||||
</div>
|
||||
<div class="input-group col-sm-5 input-group-sm">
|
||||
<span class="input-group-addon">container</span>
|
||||
<input type="text" class="form-control" ng-model="volume.containerPath" placeholder="e.g. /path/in/container">
|
||||
<span class="input-group-btn">
|
||||
<button class="btn btn-default" type="button" ng-click="removeVolume($index)">
|
||||
<i class="fa fa-minus" aria-hidden="true"></i>
|
||||
</button>
|
||||
</span>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
<!-- !volumes-input-list -->
|
||||
</div>
|
||||
</form>
|
||||
<!-- !volumes -->
|
||||
</div>
|
||||
<!-- !tab-volume -->
|
||||
<!-- tab-network -->
|
||||
<div class="tab-pane" id="network">
|
||||
<form class="form-horizontal" style="margin-top: 15px;">
|
||||
<!-- network-input -->
|
||||
<div class="form-group">
|
||||
<label for="container_network" class="col-sm-1 control-label text-left">Network</label>
|
||||
<div class="col-sm-9">
|
||||
<select class="selectpicker form-control" ng-model="config.HostConfig.NetworkMode">
|
||||
<option selected disabled hidden value="">Select a network</option>
|
||||
<option ng-repeat="net in availableNetworks" ng-value="net.Name">{{ net.Name }}</option>
|
||||
</select>
|
||||
</div>
|
||||
</div>
|
||||
<!-- !network-input -->
|
||||
<!-- hostname-input -->
|
||||
<div class="form-group">
|
||||
<label for="container_hostname" class="col-sm-1 control-label text-left">Hostname</label>
|
||||
<div class="col-sm-9">
|
||||
<input type="text" class="form-control" ng-model="config.Hostname" id="container_hostname" placeholder="e.g. web01">
|
||||
</div>
|
||||
</div>
|
||||
<!-- !hostname-input -->
|
||||
<!-- domainname-input -->
|
||||
<div class="form-group">
|
||||
<label for="container_domainname" class="col-sm-1 control-label text-left">Domain Name</label>
|
||||
<div class="col-sm-9">
|
||||
<input type="text" class="form-control" ng-model="config.Domainname" id="container_domainname" placeholder="e.g. example.com">
|
||||
</div>
|
||||
</div>
|
||||
<!-- !domainname -->
|
||||
</form>
|
||||
</div>
|
||||
<!-- !tab-network -->
|
||||
<!-- tab-security -->
|
||||
<div class="tab-pane" id="security">
|
||||
<form class="form-horizontal" style="margin-top: 15px;">
|
||||
<!-- privileged-mode -->
|
||||
<div class="form-group">
|
||||
<div class="col-sm-12">
|
||||
<div class="checkbox">
|
||||
<label>
|
||||
<input type="checkbox" ng-model="config.HostConfig.Privileged"> Privileged mode
|
||||
</label>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
<!-- !privileged-mode -->
|
||||
</form>
|
||||
</div>
|
||||
<!-- !tab-security -->
|
||||
</div>
|
||||
</rd-widget-body>
|
||||
</rd-widget>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div class="row">
|
||||
<div class="col-lg-12 col-md-12 col-xs-12" style="text-align: center;">
|
||||
<button type="button" class="btn btn-default btn-lg" ng-click="create()">Create</button>
|
||||
<a type="button" class="btn btn-default btn-lg" ui-sref="containers">Cancel</a>
|
||||
</div>
|
||||
</div>
|
Loading…
Add table
Add a link
Reference in a new issue