1
0
Fork 0
mirror of https://github.com/portainer/portainer.git synced 2025-08-08 07:15:23 +02:00

chore(project): add prettier for code format (#3645)

* chore(project): install prettier and lint-staged

* chore(project): apply prettier to html too

* chore(project): git ignore eslintcache

* chore(project): add a comment about format script

* chore(prettier): update printWidth

* chore(prettier): remove useTabs option

* chore(prettier): add HTML validation

* refactor(prettier): fix closing tags

* feat(prettier): define angular parser for html templates

* style(prettier): run prettier on codebase

Co-authored-by: Anthony Lapenna <lapenna.anthony@gmail.com>
This commit is contained in:
Chaim Lev-Ari 2020-04-11 00:54:53 +03:00 committed by GitHub
parent 6663073be1
commit cf5056d9c0
No known key found for this signature in database
GPG key ID: 4AEE18F83AFDEB23
714 changed files with 31228 additions and 28305 deletions

View file

@ -1,7 +1,7 @@
<rd-header>
<rd-header-title title-text="Container console"></rd-header-title>
<rd-header-content>
<a ui-sref="docker.containers">Containers</a> &gt; <a ui-sref="docker.containers.container({id: container.Id})">{{ container.Name|trimcontainername }}</a> &gt; Console
<a ui-sref="docker.containers">Containers</a> &gt; <a ui-sref="docker.containers.container({id: container.Id})">{{ container.Name | trimcontainername }}</a> &gt; Console
</rd-header-content>
</rd-header>
@ -10,7 +10,6 @@
<rd-widget>
<rd-widget-header icon="fa-terminal" title-text="Attach"></rd-widget-header>
<rd-widget-body>
<div class="small text-warning" ng-if="!container.Config.OpenStdin">
<p>
<i class="fa fa-exclamation-triangle" aria-hidden="true"></i>
@ -32,12 +31,16 @@
</p>
</div>
<button type="button" class="btn btn-primary" ng-disabled="state === states.connecting || !container.State.Running" ng-click="state == states.disconnected ? connectAttach() : disconnect()">
<button
type="button"
class="btn btn-primary"
ng-disabled="state === states.connecting || !container.State.Running"
ng-click="state == states.disconnected ? connectAttach() : disconnect()"
>
<span ng-show="state === states.disconnected">Attach to Container</span>
<span ng-show="state === states.connected">Detach</span>
<span ng-show="state === states.connecting">Attaching...</span>
</button>
</rd-widget-body>
</rd-widget>
</div>

View file

@ -1,41 +1,63 @@
import {Terminal} from 'xterm';
import { Terminal } from 'xterm';
angular.module('portainer.docker')
.controller('ContainerConsoleController', ['$scope', '$transition$', 'ContainerService', 'ImageService', 'EndpointProvider', 'Notifications', 'ContainerHelper', 'ExecService', 'HttpRequestHelper', 'LocalStorage', 'CONSOLE_COMMANDS_LABEL_PREFIX',
function ($scope, $transition$, ContainerService, ImageService, EndpointProvider, Notifications, ContainerHelper, ExecService, HttpRequestHelper, LocalStorage, CONSOLE_COMMANDS_LABEL_PREFIX) {
var socket, term;
angular.module('portainer.docker').controller('ContainerConsoleController', [
'$scope',
'$transition$',
'ContainerService',
'ImageService',
'EndpointProvider',
'Notifications',
'ContainerHelper',
'ExecService',
'HttpRequestHelper',
'LocalStorage',
'CONSOLE_COMMANDS_LABEL_PREFIX',
function (
$scope,
$transition$,
ContainerService,
ImageService,
EndpointProvider,
Notifications,
ContainerHelper,
ExecService,
HttpRequestHelper,
LocalStorage,
CONSOLE_COMMANDS_LABEL_PREFIX
) {
var socket, term;
let states = Object.freeze({
disconnected: 0,
connecting: 1,
connected: 2,
});
let states = Object.freeze({
disconnected: 0,
connecting: 1,
connected: 2,
});
$scope.loaded = false;
$scope.states = states;
$scope.state = states.disconnected;
$scope.loaded = false;
$scope.states = states;
$scope.state = states.disconnected;
$scope.formValues = {};
$scope.containerCommands = [];
$scope.formValues = {};
$scope.containerCommands = [];
// Ensure the socket is closed before leaving the view
$scope.$on('$stateChangeStart', function () {
$scope.disconnect();
});
// Ensure the socket is closed before leaving the view
$scope.$on('$stateChangeStart', function () {
$scope.disconnect();
});
$scope.connectAttach = function() {
if ($scope.state > states.disconnected) {
return;
}
$scope.connectAttach = function () {
if ($scope.state > states.disconnected) {
return;
}
$scope.state = states.connecting;
$scope.state = states.connecting;
let attachId = $transition$.params().id;
ContainerService.container(attachId).then((details) => {
let attachId = $transition$.params().id;
ContainerService.container(attachId)
.then((details) => {
if (!details.State.Running) {
Notifications.error("Failure", details, "Container " + attachId + " is not running!");
Notifications.error('Failure', details, 'Container ' + attachId + ' is not running!');
$scope.disconnect();
return;
}
@ -43,173 +65,177 @@ angular.module('portainer.docker')
const params = {
token: LocalStorage.getJWT(),
endpointId: EndpointProvider.endpointID(),
id: attachId
id: attachId,
};
var url = window.location.href.split('#')[0] + 'api/websocket/attach?' + (Object.keys(params).map((k) => k + "=" + params[k]).join("&"));
var url =
window.location.href.split('#')[0] +
'api/websocket/attach?' +
Object.keys(params)
.map((k) => k + '=' + params[k])
.join('&');
initTerm(url, ContainerService.resizeTTY.bind(this, attachId));
})
.catch(function error(err) {
Notifications.error('Error', err, 'Unable to retrieve container details');
$scope.disconnect();
});
};
$scope.connectExec = function () {
if ($scope.state > states.disconnected) {
return;
}
$scope.state = states.connecting;
var command = $scope.formValues.isCustomCommand ?
$scope.formValues.customCommand : $scope.formValues.command;
var execConfig = {
id: $transition$.params().id,
AttachStdin: true,
AttachStdout: true,
AttachStderr: true,
Tty: true,
User: $scope.formValues.user,
Cmd: ContainerHelper.commandStringToArray(command)
};
ContainerService.createExec(execConfig)
.then(function success(data) {
const params = {
token: LocalStorage.getJWT(),
endpointId: EndpointProvider.endpointID(),
id: data.Id
};
var url = window.location.href.split('#')[0] + 'api/websocket/exec?' + (Object.keys(params).map((k) => k + "=" + params[k]).join("&"));
initTerm(url, ExecService.resizeTTY.bind(this, params.id));
})
.catch(function error(err) {
Notifications.error('Failure', err, 'Unable to exec into container');
$scope.disconnect();
});
};
$scope.disconnect = function () {
if (socket) {
socket.close();
}
if ($scope.state > states.disconnected) {
$scope.state = states.disconnected;
if (term) {
term.write("\n\r(connection closed)");
term.dispose();
}
}
};
$scope.autoconnectAttachView = function () {
return $scope.initView().then(function success() {
if ($scope.container.State.Running) {
$scope.connectAttach();
}
.catch(function error(err) {
Notifications.error('Error', err, 'Unable to retrieve container details');
$scope.disconnect();
});
};
};
function resize(restcall, add) {
add = add || 0;
term.fit();
var termWidth = term.cols;
var termHeight = 30;
term.resize(termWidth, termHeight);
restcall(termWidth + add, termHeight + add, 1);
$scope.connectExec = function () {
if ($scope.state > states.disconnected) {
return;
}
function initTerm(url, resizeRestCall) {
$scope.state = states.connecting;
var command = $scope.formValues.isCustomCommand ? $scope.formValues.customCommand : $scope.formValues.command;
var execConfig = {
id: $transition$.params().id,
AttachStdin: true,
AttachStdout: true,
AttachStderr: true,
Tty: true,
User: $scope.formValues.user,
Cmd: ContainerHelper.commandStringToArray(command),
};
let resizefun = resize.bind(this, resizeRestCall);
ContainerService.createExec(execConfig)
.then(function success(data) {
const params = {
token: LocalStorage.getJWT(),
endpointId: EndpointProvider.endpointID(),
id: data.Id,
};
if ($transition$.params().nodeName) {
url += '&nodeName=' + $transition$.params().nodeName;
var url =
window.location.href.split('#')[0] +
'api/websocket/exec?' +
Object.keys(params)
.map((k) => k + '=' + params[k])
.join('&');
initTerm(url, ExecService.resizeTTY.bind(this, params.id));
})
.catch(function error(err) {
Notifications.error('Failure', err, 'Unable to exec into container');
$scope.disconnect();
});
};
$scope.disconnect = function () {
if (socket) {
socket.close();
}
if ($scope.state > states.disconnected) {
$scope.state = states.disconnected;
if (term) {
term.write('\n\r(connection closed)');
term.dispose();
}
if (url.indexOf('https') > -1) {
url = url.replace('https://', 'wss://');
} else {
url = url.replace('http://', 'ws://');
}
};
$scope.autoconnectAttachView = function () {
return $scope.initView().then(function success() {
if ($scope.container.State.Running) {
$scope.connectAttach();
}
});
};
socket = new WebSocket(url);
function resize(restcall, add) {
add = add || 0;
term.fit();
var termWidth = term.cols;
var termHeight = 30;
term.resize(termWidth, termHeight);
socket.onopen = function () {
$scope.state = states.connected;
term = new Terminal();
restcall(termWidth + add, termHeight + add, 1);
}
function initTerm(url, resizeRestCall) {
let resizefun = resize.bind(this, resizeRestCall);
term.on('data', function (data) {
socket.send(data);
});
var terminal_container = document.getElementById('terminal-container');
term.open(terminal_container);
term.focus();
term.setOption('cursorBlink', true);
if ($transition$.params().nodeName) {
url += '&nodeName=' + $transition$.params().nodeName;
}
if (url.indexOf('https') > -1) {
url = url.replace('https://', 'wss://');
} else {
url = url.replace('http://', 'ws://');
}
window.onresize = function () {
resizefun();
$scope.$apply();
};
socket = new WebSocket(url);
$scope.$watch('toggle', function () {
setTimeout(resizefun, 400);
});
socket.onopen = function () {
$scope.state = states.connected;
term = new Terminal();
socket.onmessage = function (e) {
term.write(e.data);
};
socket.onerror = function (err) {
$scope.disconnect();
$scope.$apply();
Notifications.error("Failure", err, "Connection error");
};
socket.onclose = function () {
$scope.disconnect();
$scope.$apply();
};
term.on('data', function (data) {
socket.send(data);
});
var terminal_container = document.getElementById('terminal-container');
term.open(terminal_container);
term.focus();
term.setOption('cursorBlink', true);
resizefun(1);
window.onresize = function () {
resizefun();
$scope.$apply();
};
}
$scope.initView = function () {
HttpRequestHelper.setPortainerAgentTargetHeader($transition$.params().nodeName);
return ContainerService.container($transition$.params().id)
.then(function success(data) {
var container = data;
$scope.container = container;
return ImageService.image(container.Image);
})
.then(function success(data) {
var image = data;
var containerLabels = $scope.container.Config.Labels;
$scope.imageOS = image.Os;
$scope.formValues.command = image.Os === 'windows' ? 'powershell' : 'bash';
$scope.containerCommands = Object.keys(containerLabels)
.filter(function (label) {
return label.indexOf(CONSOLE_COMMANDS_LABEL_PREFIX) === 0;
})
.map(function (label) {
return {
title: label.replace(CONSOLE_COMMANDS_LABEL_PREFIX, ''),
command: containerLabels[label]
};
});
$scope.loaded = true;
})
.catch(function error(err) {
Notifications.error('Error', err, 'Unable to retrieve container details');
});
}
}]);
$scope.$watch('toggle', function () {
setTimeout(resizefun, 400);
});
socket.onmessage = function (e) {
term.write(e.data);
};
socket.onerror = function (err) {
$scope.disconnect();
$scope.$apply();
Notifications.error('Failure', err, 'Connection error');
};
socket.onclose = function () {
$scope.disconnect();
$scope.$apply();
};
resizefun(1);
$scope.$apply();
};
}
$scope.initView = function () {
HttpRequestHelper.setPortainerAgentTargetHeader($transition$.params().nodeName);
return ContainerService.container($transition$.params().id)
.then(function success(data) {
var container = data;
$scope.container = container;
return ImageService.image(container.Image);
})
.then(function success(data) {
var image = data;
var containerLabels = $scope.container.Config.Labels;
$scope.imageOS = image.Os;
$scope.formValues.command = image.Os === 'windows' ? 'powershell' : 'bash';
$scope.containerCommands = Object.keys(containerLabels)
.filter(function (label) {
return label.indexOf(CONSOLE_COMMANDS_LABEL_PREFIX) === 0;
})
.map(function (label) {
return {
title: label.replace(CONSOLE_COMMANDS_LABEL_PREFIX, ''),
command: containerLabels[label],
};
});
$scope.loaded = true;
})
.catch(function error(err) {
Notifications.error('Error', err, 'Unable to retrieve container details');
});
};
},
]);

View file

@ -1,7 +1,7 @@
<rd-header>
<rd-header-title title-text="Container console"></rd-header-title>
<rd-header-content>
<a ui-sref="docker.containers">Containers</a> &gt; <a ui-sref="docker.containers.container({id: container.Id})">{{ container.Name|trimcontainername }}</a> &gt; Console
<a ui-sref="docker.containers">Containers</a> &gt; <a ui-sref="docker.containers.container({id: container.Id})">{{ container.Name | trimcontainername }}</a> &gt; Console
</rd-header-content>
</rd-header>
@ -13,32 +13,30 @@
<form class="form-horizontal">
<div ng-if="state === states.disconnected">
<!-- command-list -->
<div class="form-group">
<label for="command" class="col-lg-1 text-left col-sm-2 control-label">Command</label>
<div class="col-lg-11 col-sm-10">
<div class="input-group" ng-if="!formValues.isCustomCommand">
<span class="input-group-addon">
<i class="fab fa-linux" aria-hidden="true" ng-if="imageOS == 'linux'"></i>
<i class="fab fa-windows" aria-hidden="true" ng-if="imageOS == 'windows'"></i>
</span>
<select class="form-control" ng-model="formValues.command" id="command">
<option value="ash" ng-if="imageOS == 'linux'">/bin/ash</option>
<option value="bash" ng-if="imageOS == 'linux'">/bin/bash</option>
<option value="sh" ng-if="imageOS == 'linux'">/bin/sh</option>
<option value="powershell" ng-if="imageOS == 'windows'">powershell</option>
<option value="cmd.exe" ng-if="imageOS == 'windows'">cmd.exe</option>
<option ng-repeat="command in containerCommands" value="{{command.command}}">{{command.title}}: {{command.command}}</option>
</select>
</div>
<input class="form-control" ng-if="formValues.isCustomCommand" type="text" name="custom-command" ng-model="formValues.customCommand" placeholder="e.g. ps aux">
<div class="form-group">
<label for="command" class="col-lg-1 text-left col-sm-2 control-label">Command</label>
<div class="col-lg-11 col-sm-10">
<div class="input-group" ng-if="!formValues.isCustomCommand">
<span class="input-group-addon">
<i class="fab fa-linux" aria-hidden="true" ng-if="imageOS == 'linux'"></i>
<i class="fab fa-windows" aria-hidden="true" ng-if="imageOS == 'windows'"></i>
</span>
<select class="form-control" ng-model="formValues.command" id="command">
<option value="ash" ng-if="imageOS == 'linux'">/bin/ash</option>
<option value="bash" ng-if="imageOS == 'linux'">/bin/bash</option>
<option value="sh" ng-if="imageOS == 'linux'">/bin/sh</option>
<option value="powershell" ng-if="imageOS == 'windows'">powershell</option>
<option value="cmd.exe" ng-if="imageOS == 'windows'">cmd.exe</option>
<option ng-repeat="command in containerCommands" value="{{ command.command }}">{{ command.title }}: {{ command.command }}</option>
</select>
</div>
<input class="form-control" ng-if="formValues.isCustomCommand" type="text" name="custom-command" ng-model="formValues.customCommand" placeholder="e.g. ps aux" />
</div>
</div>
<!-- !command-list -->
<div class="form-group col-lg-12">
<label for="command" class="text-left control-label">Use custom command</label>
<label class="switch" style="margin-left: 20px;">
<input type="checkbox" ng-model="formValues.isCustomCommand"><i></i>
</label>
<label class="switch" style="margin-left: 20px;"> <input type="checkbox" ng-model="formValues.isCustomCommand" /><i></i> </label>
</div>
<div class="form-group">
<label for="username" class="col-lg-1 text-left col-sm-2 control-label">
@ -46,7 +44,7 @@
<portainer-tooltip position="bottom" message="Format is one of: user, user:group, uid or uid:gid"></portainer-tooltip>
</label>
<div class="col-lg-11 col-sm-10">
<input class="form-control" type="text" name="username" ng-model="formValues.user" placeholder="root">
<input class="form-control" type="text" name="username" ng-model="formValues.user" placeholder="root" />
</div>
</div>
<div class="form-group">
@ -62,7 +60,10 @@
</div>
</div>
<div ng-if="state !== states.disconnected">
<label>Exec into container as <code>{{ ::formValues.user || 'default user' }}</code> using command <code>{{ formValues.isCustomCommand ? formValues.customCommand : formValues.command }}</code></label>
<label
>Exec into container as <code>{{ ::formValues.user || 'default user' }}</code> using command
<code>{{ formValues.isCustomCommand ? formValues.customCommand : formValues.command }}</code></label
>
<button type="button" class="btn btn-primary" ng-click="disconnect()">
<span ng-show="state === states.connected">Disconnect</span>
<span ng-show="state === states.connecting">Connecting...</span>

View file

@ -10,8 +10,10 @@
<div class="row">
<div class="col-sm-12" ng-if="containers">
<containers-datatable
title-text="Containers" title-icon="fa-server"
dataset="containers" table-key="containers"
title-text="Containers"
title-icon="fa-server"
dataset="containers"
table-key="containers"
order-by="Status"
show-ownership-column="applicationState.application.authentication"
show-host-column="applicationState.endpoint.mode.agentProxy && applicationState.endpoint.mode.provider === 'DOCKER_SWARM_MODE'"

View file

@ -1,26 +1,29 @@
angular.module('portainer.docker')
.controller('ContainersController', ['$scope', 'ContainerService', 'Notifications', 'EndpointProvider',
function ($scope, ContainerService, Notifications, EndpointProvider) {
angular.module('portainer.docker').controller('ContainersController', [
'$scope',
'ContainerService',
'Notifications',
'EndpointProvider',
function ($scope, ContainerService, Notifications, EndpointProvider) {
$scope.offlineMode = false;
$scope.offlineMode = false;
$scope.getContainers = getContainers;
$scope.getContainers = getContainers;
function getContainers() {
ContainerService.containers(1)
.then(function success(data) {
$scope.containers = data;
$scope.offlineMode = EndpointProvider.offlineMode();
})
.catch(function error(err) {
Notifications.error('Failure', err, 'Unable to retrieve containers');
$scope.containers = [];
});
}
function getContainers() {
ContainerService.containers(1)
.then(function success(data) {
$scope.containers = data;
$scope.offlineMode = EndpointProvider.offlineMode();
})
.catch(function error(err) {
Notifications.error('Failure', err, 'Unable to retrieve containers');
$scope.containers = [];
});
}
function initView() {
getContainers();
}
function initView() {
getContainers();
}
initView();
}]);
initView();
},
]);

File diff suppressed because it is too large Load diff

View file

@ -1,8 +1,6 @@
<rd-header>
<rd-header-title title-text="Create container"></rd-header-title>
<rd-header-content>
<a ui-sref="docker.containers">Containers</a> &gt; Add container
</rd-header-content>
<rd-header-content> <a ui-sref="docker.containers">Containers</a> &gt; Add container </rd-header-content>
</rd-header>
<div class="row">
@ -14,7 +12,7 @@
<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">
<input type="text" class="form-control" ng-model="config.name" id="container_name" placeholder="e.g. myContainer" />
</div>
</div>
<!-- !name-input -->
@ -23,7 +21,10 @@
</div>
<div ng-if="!formValues.RegistryModel.Registry && fromContainer">
<i class="fa fa-exclamation-triangle orange-icon" aria-hidden="true"></i>
<span class="small text-danger" style="margin-left: 5px;">The Docker registry for the <code>{{ config.Image }}</code> image is not registered inside Portainer, you will not be able to create a container. Please register that registry first.</span>
<span class="small text-danger" style="margin-left: 5px;"
>The Docker registry for the <code>{{ config.Image }}</code> image is not registered inside Portainer, you will not be able to create a container. Please register
that registry first.</span
>
</div>
<div ng-if="formValues.RegistryModel.Registry || !fromContainer">
<!-- image-and-registry -->
@ -32,7 +33,8 @@
pull-warning="formValues.alwaysPull"
ng-if="formValues.RegistryModel.Registry"
auto-complete="true"
label-class="col-sm-1" input-class="col-sm-11"
label-class="col-sm-1"
input-class="col-sm-11"
></por-image-registry>
<!-- !image-and-registry -->
<!-- always-pull -->
@ -40,11 +42,12 @@
<div class="col-sm-12">
<label for="ownership" class="control-label text-left">
Always pull the image
<portainer-tooltip position="bottom" message="When enabled, Portainer will automatically try to pull the specified image before creating the container."></portainer-tooltip>
</label>
<label class="switch" style="margin-left: 20px;">
<input type="checkbox" ng-model="formValues.alwaysPull"><i></i>
<portainer-tooltip
position="bottom"
message="When enabled, Portainer will automatically try to pull the specified image before creating the container."
></portainer-tooltip>
</label>
<label class="switch" style="margin-left: 20px;"> <input type="checkbox" ng-model="formValues.alwaysPull" /><i></i> </label>
</div>
</div>
<!-- !always-pull -->
@ -57,11 +60,12 @@
<div class="col-sm-12">
<label class="control-label text-left">
Publish all exposed network ports to random host ports
<portainer-tooltip position="bottom" message="When enabled, Portainer will let Docker automatically map a random port on the host to each one defined in the image Dockerfile."></portainer-tooltip>
</label>
<label class="switch" style="margin-left: 20px;">
<input type="checkbox" ng-model="config.HostConfig.PublishAllPorts"><i></i>
<portainer-tooltip
position="bottom"
message="When enabled, Portainer will let Docker automatically map a random port on the host to each one defined in the image Dockerfile."
></portainer-tooltip>
</label>
<label class="switch" style="margin-left: 20px;"> <input type="checkbox" ng-model="config.HostConfig.PublishAllPorts" /><i></i> </label>
</div>
</div>
<!-- !publish-exposed-ports -->
@ -70,7 +74,10 @@
<div class="col-sm-12">
<label class="control-label text-left">
Manual network port publishing
<portainer-tooltip position="bottom" message="When a range of ports on the host and a single port on the container is specified, Docker will randomly choose a single available port in the defined range and forward that to the container port."></portainer-tooltip>
<portainer-tooltip
position="bottom"
message="When a range of ports on the host and a single port on the container is specified, Docker will randomly choose a single available port in the defined range and forward that to the container port."
></portainer-tooltip>
</label>
<span class="label label-default interactive" style="margin-left: 10px;" ng-click="addPortBinding()">
<i class="fa fa-plus-circle" aria-hidden="true"></i> publish a new network port
@ -82,7 +89,7 @@
<!-- host-port -->
<div class="input-group col-sm-4 input-group-sm">
<span class="input-group-addon">host</span>
<input type="text" class="form-control" ng-model="portBinding.hostPort" placeholder="e.g. 80, 80-88, ip:80 or ip:80-88 (optional)">
<input type="text" class="form-control" ng-model="portBinding.hostPort" placeholder="e.g. 80, 80-88, ip:80 or ip:80-88 (optional)" />
</div>
<!-- !host-port -->
<span style="margin: 0 10px 0 10px;">
@ -91,7 +98,7 @@
<!-- container-port -->
<div class="input-group col-sm-4 input-group-sm">
<span class="input-group-addon">container</span>
<input type="text" class="form-control" ng-model="portBinding.containerPort" placeholder="e.g. 80 or 80-88">
<input type="text" class="form-control" ng-model="portBinding.containerPort" placeholder="e.g. 80 or 80-88" />
</div>
<!-- !container-port -->
<!-- protocol-actions -->
@ -115,13 +122,15 @@
Deployment
</div>
<!-- node-selection -->
<node-selector
model="formValues.NodeName">
</node-selector>
<node-selector model="formValues.NodeName"> </node-selector>
<!-- !node-selection -->
</div>
<!-- access-control -->
<por-access-control-form form-data="formValues.AccessControlData" resource-control="fromContainer.ResourceControl" ng-if="applicationState.application.authentication && fromContainer"></por-access-control-form>
<por-access-control-form
form-data="formValues.AccessControlData"
resource-control="fromContainer.ResourceControl"
ng-if="applicationState.application.authentication && fromContainer"
></por-access-control-form>
<!-- !access-control -->
<!-- actions -->
<div class="col-sm-12 form-section-title">
@ -132,19 +141,24 @@
<div class="col-sm-12">
<label for="ownership" class="control-label text-left">
Auto remove
<portainer-tooltip position="bottom" message="When enabled, Portainer will automatically remove the container when it exits. This is useful when you want to use the container only once."></portainer-tooltip>
</label>
<label class="switch" style="margin-left: 20px;">
<input type="checkbox" ng-model="config.HostConfig.AutoRemove"><i></i>
<portainer-tooltip
position="bottom"
message="When enabled, Portainer will automatically remove the container when it exits. This is useful when you want to use the container only once."
></portainer-tooltip>
</label>
<label class="switch" style="margin-left: 20px;"> <input type="checkbox" ng-model="config.HostConfig.AutoRemove" /><i></i> </label>
</div>
</div>
<!-- !autoremove -->
<div class="form-group">
<div class="col-sm-12">
<button type="button" class="btn btn-primary btn-sm"
<button
type="button"
class="btn btn-primary btn-sm"
ng-disabled="state.actionInProgress || !formValues.RegistryModel.Image || (!formValues.RegistryModel.Registry && fromContainer)"
ng-click="create()" button-spinner="state.actionInProgress">
ng-click="create()"
button-spinner="state.actionInProgress"
>
<span ng-hide="state.actionInProgress">Deploy the container</span>
<span ng-show="state.actionInProgress">Deployment in progress...</span>
</button>
@ -182,7 +196,7 @@
<div class="form-group">
<label for="container_command" class="col-sm-2 col-lg-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">
<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 -->
@ -190,7 +204,7 @@
<div class="form-group">
<label for="container_entrypoint" class="col-sm-2 col-lg-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">
<input type="text" class="form-control" ng-model="config.Entrypoint" id="container_entrypoint" placeholder="e.g. /bin/sh -c" />
</div>
</div>
<!-- !entrypoint-input -->
@ -198,11 +212,11 @@
<div class="form-group">
<label for="container_workingdir" class="col-sm-2 col-lg-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">
<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">
<input type="text" class="form-control" ng-model="config.User" id="container_user" placeholder="e.g. nginx" />
</div>
</div>
<!-- !workdir-user-input -->
@ -212,13 +226,13 @@
<div class="col-sm-10 col-lg-11">
<div class="col-sm-4">
<label class="radio-inline">
<input type="radio" name="container_console" ng-model="formValues.Console" value="both">
<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">
<input type="radio" name="container_console" ng-model="formValues.Console" value="interactive" />
Interactive <span class="small text-muted">(-i)</span>
</label>
</div>
@ -226,13 +240,13 @@
<div class="col-sm-offset-2 col-sm-10 col-lg-offset-1 col-lg-11">
<div class="col-sm-4">
<label class="radio-inline">
<input type="radio" name="container_console" ng-model="formValues.Console" value="tty">
<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">
<input type="radio" name="container_console" ng-model="formValues.Console" value="none" />
None
</label>
</div>
@ -255,7 +269,8 @@
</div>
<div class="col-sm-5">
<p class="small text-muted">
Logging driver that will override the default docker daemon driver. Select Default logging driver if you don't want to override it. Supported logging drivers can be found <a href="https://docs.docker.com/engine/admin/logging/overview/#supported-logging-drivers" target="_blank">in the Docker documentation</a>.
Logging driver that will override the default docker daemon driver. Select Default logging driver if you don't want to override it. Supported logging drivers
can be found <a href="https://docs.docker.com/engine/admin/logging/overview/#supported-logging-drivers" target="_blank">in the Docker documentation</a>.
</p>
</div>
</div>
@ -265,9 +280,16 @@
<div class="col-sm-12" style="margin-top: 5px;">
<label class="control-label text-left">
Options
<portainer-tooltip position="top" message="Add button is disabled unless a driver other than none or default is selected. Options are specific to the selected driver, refer to the driver documentation."></portainer-tooltip>
<portainer-tooltip
position="top"
message="Add button is disabled unless a driver other than none or default is selected. Options are specific to the selected driver, refer to the driver documentation."
></portainer-tooltip>
</label>
<span class="label label-default interactive" style="margin-left: 10px;" ng-click="!formValues.LogDriverName || formValues.LogDriverName === 'none' || addLogDriverOpt(formValues.LogDriverName)">
<span
class="label label-default interactive"
style="margin-left: 10px;"
ng-click="!formValues.LogDriverName || formValues.LogDriverName === 'none' || addLogDriverOpt(formValues.LogDriverName)"
>
<i class="fa fa-plus-circle" aria-hidden="true"></i> add logging driver option
</span>
</div>
@ -276,11 +298,11 @@
<div ng-repeat="opt in formValues.LogDriverOpts" style="margin-top: 2px;">
<div class="input-group col-sm-5 input-group-sm">
<span class="input-group-addon">option</span>
<input type="text" class="form-control" ng-model="opt.name" placeholder="e.g. FOO">
<input type="text" class="form-control" ng-model="opt.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="opt.value" placeholder="e.g. bar">
<input type="text" class="form-control" ng-model="opt.value" placeholder="e.g. bar" />
</div>
<button class="btn btn-sm btn-danger" type="button" ng-click="removeLogDriverOpt($index)">
<i class="fa fa-trash" aria-hidden="true"></i>
@ -290,8 +312,6 @@
<!-- logging-opts-input-list -->
</div>
<!-- !logging-opts -->
</form>
</div>
<!-- !tab-command -->
@ -314,7 +334,7 @@
<!-- container-path -->
<div class="input-group input-group-sm col-sm-6">
<span class="input-group-addon">container</span>
<input type="text" class="form-control" ng-model="volume.containerPath" placeholder="e.g. /path/in/container">
<input type="text" class="form-control" ng-model="volume.containerPath" placeholder="e.g. /path/in/container" />
</div>
<!-- !container-path -->
<!-- volume-type -->
@ -338,14 +358,14 @@
<span class="input-group-addon">volume</span>
<select class="form-control" ng-model="volume.name">
<option selected disabled hidden value="">Select a volume</option>
<option ng-repeat="vol in availableVolumes" ng-value="vol.Name">{{ vol.Name|truncate:30}} - {{ vol.Driver|truncate:30}}</option>
<option ng-repeat="vol in availableVolumes" ng-value="vol.Name">{{ vol.Name | truncate: 30 }} - {{ vol.Driver | truncate: 30 }}</option>
</select>
</div>
<!-- !volume -->
<!-- bind -->
<div class="input-group input-group-sm col-sm-6" ng-if="volume.type === 'bind'">
<span class="input-group-addon">host</span>
<input type="text" class="form-control" ng-model="volume.name" placeholder="e.g. /path/on/host">
<input type="text" class="form-control" ng-model="volume.name" placeholder="e.g. /path/on/host" />
</div>
<!-- !bind -->
<!-- read-only -->
@ -399,7 +419,7 @@
<div class="form-group">
<label for="container_hostname" class="col-sm-2 col-lg-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">
<input type="text" class="form-control" ng-model="config.Hostname" id="container_hostname" placeholder="e.g. web01" />
</div>
</div>
<!-- !hostname-input -->
@ -407,7 +427,7 @@
<div class="form-group">
<label for="container_domainname" class="col-sm-2 col-lg-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">
<input type="text" class="form-control" ng-model="config.Domainname" id="container_domainname" placeholder="e.g. example.com" />
</div>
</div>
<!-- !domainname -->
@ -415,7 +435,7 @@
<div class="form-group">
<label for="container_macaddress" class="col-sm-2 col-lg-1 control-label text-left">Mac Address</label>
<div class="col-sm-9">
<input type="text" class="form-control" ng-model="formValues.MacAddress" id="container_macaddress" placeholder="e.g. 12-34-56-78-9a-bc">
<input type="text" class="form-control" ng-model="formValues.MacAddress" id="container_macaddress" placeholder="e.g. 12-34-56-78-9a-bc" />
</div>
</div>
<!-- !mac-address-input -->
@ -423,7 +443,7 @@
<div class="form-group">
<label for="container_ipv4" class="col-sm-2 col-lg-1 control-label text-left">IPv4 Address</label>
<div class="col-sm-9">
<input type="text" class="form-control" ng-model="formValues.IPv4" id="container_ipv4" placeholder="e.g. 172.20.0.7">
<input type="text" class="form-control" ng-model="formValues.IPv4" id="container_ipv4" placeholder="e.g. 172.20.0.7" />
</div>
</div>
<!-- !ipv4-input -->
@ -431,7 +451,7 @@
<div class="form-group">
<label for="container_ipv6" class="col-sm-2 col-lg-1 control-label text-left">IPv6 Address</label>
<div class="col-sm-9">
<input type="text" class="form-control" ng-model="formValues.IPv6" id="container_ipv6" placeholder="e.g. a:b:c:d::1234">
<input type="text" class="form-control" ng-model="formValues.IPv6" id="container_ipv6" placeholder="e.g. a:b:c:d::1234" />
</div>
</div>
<!-- !ipv6-input -->
@ -439,7 +459,7 @@
<div class="form-group">
<label for="container_dns_primary" class="col-sm-2 col-lg-1 control-label text-left">Primary DNS Server</label>
<div class="col-sm-9">
<input type="text" class="form-control" ng-model="formValues.DnsPrimary" id="container_dns_primary" placeholder="e.g. 1.1.1.1">
<input type="text" class="form-control" ng-model="formValues.DnsPrimary" id="container_dns_primary" placeholder="e.g. 1.1.1.1" />
</div>
</div>
<!-- !dns-primary-input -->
@ -447,7 +467,7 @@
<div class="form-group">
<label for="container_dns_secondary" class="col-sm-2 col-lg-1 control-label text-left">Secondary DNS Server</label>
<div class="col-sm-9">
<input type="text" class="form-control" ng-model="formValues.DnsSecondary" id="container_dns_secondary" placeholder="e.g. 1.0.0.1">
<input type="text" class="form-control" ng-model="formValues.DnsSecondary" id="container_dns_secondary" placeholder="e.g. 1.0.0.1" />
</div>
</div>
<!-- !dns-secondary-input -->
@ -464,7 +484,7 @@
<div ng-repeat="variable in formValues.ExtraHosts" style="margin-top: 2px;">
<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. host:IP">
<input type="text" class="form-control" ng-model="variable.value" placeholder="e.g. host:IP" />
</div>
<button class="btn btn-sm btn-danger" type="button" ng-click="removeExtraHost($index)">
<i class="fa fa-trash" aria-hidden="true"></i>
@ -493,11 +513,11 @@
<div ng-repeat="label in formValues.Labels" 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="label.name" placeholder="e.g. com.example.foo">
<input type="text" class="form-control" ng-model="label.name" placeholder="e.g. com.example.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="label.value" placeholder="e.g. bar">
<input type="text" class="form-control" ng-model="label.value" placeholder="e.g. bar" />
</div>
<button class="btn btn-sm btn-danger" type="button" ng-click="removeLabel($index)">
<i class="fa fa-trash" aria-hidden="true"></i>
@ -526,11 +546,11 @@
<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">
<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">
<input type="text" class="form-control" ng-model="variable.value" placeholder="e.g. bar" />
</div>
<button class="btn btn-sm btn-danger" type="button" ng-click="removeEnvironmentVariable($index)">
<i class="fa fa-trash" aria-hidden="true"></i>
@ -582,9 +602,7 @@
<label for="privileged_mode" class="control-label text-left">
Privileged mode
</label>
<label class="switch" style="margin-left: 20px;">
<input type="checkbox" name="privileged_mode" ng-model="config.HostConfig.Privileged"><i></i>
</label>
<label class="switch" style="margin-left: 20px;"> <input type="checkbox" name="privileged_mode" ng-model="config.HostConfig.Privileged" /><i></i> </label>
</div>
</div>
<!-- !privileged-mode -->
@ -592,8 +610,7 @@
<div class="form-group">
<label for="container_runtime" class="col-sm-1 control-label text-left">Runtime</label>
<div class="col-sm-11">
<select class="form-control" ng-model="config.HostConfig.Runtime"
id="container_runtime" ng-options="runtime for runtime in availableRuntimes">
<select class="form-control" ng-model="config.HostConfig.Runtime" id="container_runtime" ng-options="runtime for runtime in availableRuntimes">
<option selected value="">Default</option>
</select>
</div>
@ -609,16 +626,16 @@
<i class="fa fa-plus-circle" aria-hidden="true"></i> add device
</span>
</div>
<!-- devices-input-list -->
<!-- devices-input-list -->
<div class="col-sm-12 form-inline" style="margin-top: 10px;">
<div ng-repeat="device in config.HostConfig.Devices" 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="device.pathOnHost" placeholder="e.g. /dev/tty0">
<input type="text" class="form-control" ng-model="device.pathOnHost" placeholder="e.g. /dev/tty0" />
</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="device.pathInContainer" placeholder="e.g. /dev/tty0">
<input type="text" class="form-control" ng-model="device.pathInContainer" placeholder="e.g. /dev/tty0" />
</div>
<button class="btn btn-sm btn-danger" type="button" ng-click="removeDevice($index)">
<i class="fa fa-trash" aria-hidden="true"></i>
@ -640,7 +657,7 @@
<slider model="formValues.MemoryReservation" floor="0" ceil="state.sliderMaxMemory" step="256" ng-if="state.sliderMaxMemory"></slider>
</div>
<div class="col-sm-2">
<input type="number" min="0" class="form-control" ng-model="formValues.MemoryReservation" id="memory-reservation">
<input type="number" min="0" class="form-control" ng-model="formValues.MemoryReservation" id="memory-reservation" />
</div>
<div class="col-sm-4">
<p class="small text-muted" style="margin-top: 7px;">
@ -658,7 +675,7 @@
<slider model="formValues.MemoryLimit" floor="0" ceil="state.sliderMaxMemory" step="256" ng-if="state.sliderMaxMemory"></slider>
</div>
<div class="col-sm-2">
<input type="number" min="0" class="form-control" ng-model="formValues.MemoryLimit" id="memory-limit">
<input type="number" min="0" class="form-control" ng-model="formValues.MemoryLimit" id="memory-limit" />
</div>
<div class="col-sm-4">
<p class="small text-muted" style="margin-top: 7px;">
@ -687,7 +704,7 @@
<!-- !tab-runtime-resources -->
<!-- tab-container-capabilities -->
<div class="tab-pane" id="container-capabilities">
<container-capabilities capabilities="formValues.capabilities" ></container-capabilities>
<container-capabilities capabilities="formValues.capabilities"></container-capabilities>
</div>
<!-- !tab-container-capabilities -->
</div>

View file

@ -1,277 +1,326 @@
<rd-header>
<rd-header-title title-text="Container details">
</rd-header-title>
<rd-header-title title-text="Container details"> </rd-header-title>
<rd-header-content>
<a ui-sref="docker.containers">Containers</a> &gt; <a ui-sref="docker.containers.container({id: container.Id})">{{ container.Name|trimcontainername }}</a>
<a ui-sref="docker.containers">Containers</a> &gt; <a ui-sref="docker.containers.container({id: container.Id})">{{ container.Name | trimcontainername }}</a>
</rd-header-content>
</rd-header>
<div class="row" authorization="DockerContainerStart, DockerContainerStop, DockerContainerKill, DockerContainerRestart, DockerContainerPause, DockerContainerUnpause, DockerContainerDelete, DockerContainerCreate">
<div class="col-lg-12 col-md-12 col-xs-12">
<rd-widget>
<rd-widget-header icon="fa-cogs" title-text="Actions"></rd-widget-header>
<rd-widget-body classes="padding">
<div class="btn-group" role="group" aria-label="...">
<button authorization="DockerContainerStart" class="btn btn-success btn-sm" ng-click="start()" ng-disabled="container.State.Running"><i class="fa fa-play space-right" aria-hidden="true"></i>Start</button>
<button authorization="DockerContainerStop" class="btn btn-danger btn-sm" ng-click="stop()" ng-disabled="!container.State.Running"><i class="fa fa-stop space-right" aria-hidden="true"></i>Stop</button>
<button authorization="DockerContainerKill" class="btn btn-danger btn-sm" ng-click="kill()" ng-disabled="!container.State.Running"><i class="fa fa-bomb space-right" aria-hidden="true"></i>Kill</button>
<button authorization="DockerContainerRestart" class="btn btn-primary btn-sm" ng-click="restart()" ng-disabled="!container.State.Running"><i class="fa fa-sync space-right" aria-hidden="true"></i>Restart</button>
<button authorization="DockerContainerPause" class="btn btn-primary btn-sm" ng-click="pause()" ng-disabled="!container.State.Running || container.State.Paused"><i class="fa fa-pause space-right" aria-hidden="true"></i>Pause</button>
<button authorization="DockerContainerUnpause" class="btn btn-primary btn-sm" ng-click="unpause()" ng-disabled="!container.State.Paused"><i class="fa fa-play space-right" aria-hidden="true"></i>Resume</button>
<button authorization="DockerContainerDelete" class="btn btn-danger btn-sm" ng-click="confirmRemove()"><i class="fa fa-trash-alt space-right" aria-hidden="true"></i>Remove</button>
<div
class="row"
authorization="DockerContainerStart, DockerContainerStop, DockerContainerKill, DockerContainerRestart, DockerContainerPause, DockerContainerUnpause, DockerContainerDelete, DockerContainerCreate"
>
<div class="col-lg-12 col-md-12 col-xs-12">
<rd-widget>
<rd-widget-header icon="fa-cogs" title-text="Actions"></rd-widget-header>
<rd-widget-body classes="padding">
<div class="btn-group" role="group" aria-label="...">
<button authorization="DockerContainerStart" class="btn btn-success btn-sm" ng-click="start()" ng-disabled="container.State.Running"
><i class="fa fa-play space-right" aria-hidden="true"></i>Start</button
>
<button authorization="DockerContainerStop" class="btn btn-danger btn-sm" ng-click="stop()" ng-disabled="!container.State.Running"
><i class="fa fa-stop space-right" aria-hidden="true"></i>Stop</button
>
<button authorization="DockerContainerKill" class="btn btn-danger btn-sm" ng-click="kill()" ng-disabled="!container.State.Running"
><i class="fa fa-bomb space-right" aria-hidden="true"></i>Kill</button
>
<button authorization="DockerContainerRestart" class="btn btn-primary btn-sm" ng-click="restart()" ng-disabled="!container.State.Running"
><i class="fa fa-sync space-right" aria-hidden="true"></i>Restart</button
>
<button authorization="DockerContainerPause" class="btn btn-primary btn-sm" ng-click="pause()" ng-disabled="!container.State.Running || container.State.Paused"
><i class="fa fa-pause space-right" aria-hidden="true"></i>Pause</button
>
<button authorization="DockerContainerUnpause" class="btn btn-primary btn-sm" ng-click="unpause()" ng-disabled="!container.State.Paused"
><i class="fa fa-play space-right" aria-hidden="true"></i>Resume</button
>
<button authorization="DockerContainerDelete" class="btn btn-danger btn-sm" ng-click="confirmRemove()"
><i class="fa fa-trash-alt space-right" aria-hidden="true"></i>Remove</button
>
</div>
<div class="btn-group" role="group" aria-label="..." ng-if="displayRecreateButton" authorization="DockerContainerCreate">
<button
type="button"
class="btn btn-danger btn-sm"
ng-disabled="state.recreateContainerInProgress"
ng-click="recreate()"
button-spinner="state.recreateContainerInProgress"
>
<span ng-hide="state.recreateContainerInProgress"><i class="fa fa-sync space-right" aria-hidden="true"></i>Recreate</span>
<span ng-show="state.recreateContainerInProgress">Recreation in progress...</span>
</button>
<a class="btn btn-primary btn-sm" type="button" ui-sref="docker.containers.new({ from: container.Id, nodeName: nodeName })"
><i class="fa fa-copy space-right" aria-hidden="true"></i>Duplicate/Edit</a
>
</div>
</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-header icon="fa-server" title-text="Container status"></rd-widget-header>
<rd-widget-body classes="no-padding">
<table class="table">
<tbody>
<tr>
<td>ID</td>
<td>{{ container.Id }}</td>
</tr>
<tr>
<td>Name</td>
<td ng-if="!container.edit">
{{ container.Name | trimcontainername }}
<a authorization="DockerContainerRename" href="" data-toggle="tooltip" title="Edit container name" ng-click="container.edit = true;"><i class="fa fa-edit"></i></a>
</td>
<td ng-if="container.edit">
<form ng-submit="renameContainer()">
<input type="text" class="containerNameInput" ng-model="container.newContainerName" />
<a href="" ng-click="container.edit = false;"><i class="fa fa-times"></i></a>
<a href="" ng-click="renameContainer()"><i class="fa fa-check-square"></i></a>
</form>
</td>
</tr>
<tr ng-if="container.NetworkSettings.IPAddress">
<td>IP address</td>
<td>{{ container.NetworkSettings.IPAddress }}</td>
</tr>
<tr>
<td>Status</td>
<td>
<i class="fa fa-heartbeat space-right green-icon" ng-if="container.State.Running"></i>
<i class="fa fa-heartbeat space-right red-icon" ng-if="!container.State.Running && container.State.Status !== 'created'"></i>
{{ container.State | getstatetext }} for {{ activityTime
}}<span ng-if="!container.State.Running && container.State.Status !== 'created'"> with exit code {{ container.State.ExitCode }}</span>
</td>
</tr>
<tr>
<td>Created</td>
<td>{{ container.Created | getisodate }}</td>
</tr>
<tr ng-if="container.State.Running">
<td>Start time</td>
<td>{{ container.State.StartedAt | getisodate }}</td>
</tr>
<tr ng-if="!container.State.Running && container.State.Status !== 'created'">
<td>Finished</td>
<td>{{ container.State.FinishedAt | getisodate }}</td>
</tr>
<tr authorization="DockerContainerLogs, DockerContainerInspect, DockerContainerStats, DockerExecStart">
<td colspan="2">
<div class="btn-group" role="group" aria-label="...">
<a authorization="DockerContainerLogs" class="btn" type="button" ui-sref="docker.containers.container.logs({ id: container.Id })"
><i class="fa fa-file-alt space-right" aria-hidden="true"></i>Logs</a
>
<a authorization="DockerContainerInspect" class="btn" type="button" ui-sref="docker.containers.container.inspect({ id: container.Id })"
><i class="fa fa-info-circle space-right" aria-hidden="true"></i>Inspect</a
>
<a authorization="DockerContainerStats" class="btn" type="button" ui-sref="docker.containers.container.stats({ id: container.Id })"
><i class="fa fa-chart-area space-right" aria-hidden="true"></i>Stats</a
>
<a authorization="DockerExecStart" class="btn" type="button" ui-sref="docker.containers.container.exec({ id: container.Id })"
><i class="fa fa-terminal space-right" aria-hidden="true"></i>Console</a
>
<a authorization="DockerContainerAttach" class="btn" type="button" ui-sref="docker.containers.container.attach({ id: container.Id })"
><i class="fa fa-plug space-right" aria-hidden="true"></i>Attach</a
>
</div>
</td>
</tr>
</tbody>
</table>
</rd-widget-body>
</rd-widget>
</div>
</div>
<!-- access-control-panel -->
<por-access-control-panel
ng-if="container && applicationState.application.authentication"
resource-id="container.Id"
resource-control="container.ResourceControl"
resource-type="'container'"
>
</por-access-control-panel>
<!-- !access-control-panel -->
<div ng-if="container.State.Health" class="row">
<div class="col-lg-12 col-md-12 col-xs-12">
<rd-widget>
<rd-widget-header icon="fa-server" title-text="Container health"></rd-widget-header>
<rd-widget-body classes="no-padding">
<table class="table">
<tbody>
<tr>
<td>Status</td>
<td>
<i
ng-class="
{ healthy: 'fa fa-heartbeat space-right green-icon', unhealthy: 'fa fa-heartbeat space-right red-icon', starting: 'fa fa-heartbeat space-right orange-icon' }[
container.State.Health.Status
]
"
></i>
{{ container.State.Health.Status }}
</td>
</tr>
<tr>
<td>Failure count</td>
<td>{{ container.State.Health.FailingStreak }}</td>
</tr>
<tr>
<td>Last output</td>
<td>{{ container.State.Health.Log[container.State.Health.Log.length - 1].Output }}</td>
</tr>
</tbody>
</table>
</rd-widget-body>
</rd-widget>
</div>
</div>
<div class="row" authorization="DockerImageCreate">
<div class="col-lg-12 col-md-12 col-xs-12">
<rd-widget>
<rd-widget-header icon="fa-clone" title-text="Create image"></rd-widget-header>
<rd-widget-body>
<form class="form-horizontal">
<!-- tag-description -->
<div class="form-group">
<div class="col-sm-12">
<span class="small text-muted">
You can create an image from this container, this allows you to backup important data or save helpful configurations. You'll be able to spin up another container
based on this image afterward.
</span>
</div>
</div>
<div class="btn-group" role="group" aria-label="..." ng-if="displayRecreateButton" authorization="DockerContainerCreate">
<button type="button" class="btn btn-danger btn-sm" ng-disabled="state.recreateContainerInProgress" ng-click="recreate()" button-spinner="state.recreateContainerInProgress">
<span ng-hide="state.recreateContainerInProgress"><i class="fa fa-sync space-right" aria-hidden="true"></i>Recreate</span>
<span ng-show="state.recreateContainerInProgress">Recreation in progress...</span>
</button>
<a class="btn btn-primary btn-sm" type="button" ui-sref="docker.containers.new({ from: container.Id, nodeName: nodeName })"><i class="fa fa-copy space-right" aria-hidden="true"></i>Duplicate/Edit</a>
<!-- !tag-description -->
<!-- image-and-registry -->
<por-image-registry model="config.RegistryModel" auto-complete="true" label-class="col-sm-1" input-class="col-sm-11"></por-image-registry>
<!-- !image-and-registry -->
<!-- tag-note -->
<div class="form-group">
<div class="col-sm-12">
<span class="small text-muted">Note: if you don't specify the tag in the image name, <span class="label label-default">latest</span> will be used.</span>
</div>
</div>
</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-header icon="fa-server" title-text="Container status"></rd-widget-header>
<rd-widget-body classes="no-padding">
<table class="table">
<tbody>
<tr>
<td>ID</td>
<td>{{ container.Id }}</td>
</tr>
<tr>
<td>Name</td>
<td ng-if="!container.edit">
{{ container.Name|trimcontainername }}
<a authorization="DockerContainerRename" href="" data-toggle="tooltip" title="Edit container name" ng-click="container.edit = true;"><i class="fa fa-edit"></i></a>
</td>
<td ng-if="container.edit">
<form ng-submit="renameContainer()">
<input type="text" class="containerNameInput" ng-model="container.newContainerName">
<a href="" ng-click="container.edit = false;"><i class="fa fa-times"></i></a>
<a href="" ng-click="renameContainer()"><i class="fa fa-check-square"></i></a>
</form>
</td>
</tr>
<tr ng-if="container.NetworkSettings.IPAddress">
<td>IP address</td>
<td>{{ container.NetworkSettings.IPAddress }}</td>
</tr>
<tr>
<td>Status</td>
<td>
<i class="fa fa-heartbeat space-right green-icon" ng-if="container.State.Running"></i>
<i class="fa fa-heartbeat space-right red-icon" ng-if="!container.State.Running && container.State.Status !== 'created'"></i>
{{ container.State|getstatetext }} for {{ activityTime }}<span ng-if="!container.State.Running && container.State.Status !== 'created'"> with exit code {{ container.State.ExitCode }}</span>
</td>
</tr>
<tr>
<td>Created</td>
<td>{{ container.Created|getisodate }}</td>
</tr>
<tr ng-if="container.State.Running">
<td>Start time</td>
<td>{{ container.State.StartedAt|getisodate }}</td>
</tr>
<tr ng-if="!container.State.Running && container.State.Status !== 'created'">
<td>Finished</td>
<td>{{ container.State.FinishedAt|getisodate }}</td>
</tr>
<tr authorization="DockerContainerLogs, DockerContainerInspect, DockerContainerStats, DockerExecStart">
<td colspan="2">
<div class="btn-group" role="group" aria-label="...">
<a authorization="DockerContainerLogs" class="btn" type="button" ui-sref="docker.containers.container.logs({ id: container.Id })"><i class="fa fa-file-alt space-right" aria-hidden="true"></i>Logs</a>
<a authorization="DockerContainerInspect" class="btn" type="button" ui-sref="docker.containers.container.inspect({ id: container.Id })"><i class="fa fa-info-circle space-right" aria-hidden="true"></i>Inspect</a>
<a authorization="DockerContainerStats" class="btn" type="button" ui-sref="docker.containers.container.stats({ id: container.Id })"><i class="fa fa-chart-area space-right" aria-hidden="true"></i>Stats</a>
<a authorization="DockerExecStart" class="btn" type="button" ui-sref="docker.containers.container.exec({ id: container.Id })"><i class="fa fa-terminal space-right" aria-hidden="true"></i>Console</a>
<a authorization="DockerContainerAttach" class="btn" type="button" ui-sref="docker.containers.container.attach({ id: container.Id })"><i class="fa fa-plug space-right" aria-hidden="true"></i>Attach</a>
</div>
</td>
</tr>
</tbody>
</table>
</rd-widget-body>
</rd-widget>
</div>
</div>
<!-- access-control-panel -->
<por-access-control-panel
ng-if="container && applicationState.application.authentication"
resource-id="container.Id"
resource-control="container.ResourceControl"
resource-type="'container'">
</por-access-control-panel>
<!-- !access-control-panel -->
<div ng-if="container.State.Health" class="row">
<div class="col-lg-12 col-md-12 col-xs-12">
<rd-widget>
<rd-widget-header icon="fa-server" title-text="Container health"></rd-widget-header>
<rd-widget-body classes="no-padding">
<table class="table">
<tbody>
<tr>
<td>Status</td>
<td>
<i ng-class="{'healthy': 'fa fa-heartbeat space-right green-icon', 'unhealthy': 'fa fa-heartbeat space-right red-icon', 'starting': 'fa fa-heartbeat space-right orange-icon'}[container.State.Health.Status]"></i>
{{ container.State.Health.Status }}
</td>
</tr>
<tr>
<td>Failure count</td>
<td>{{ container.State.Health.FailingStreak }}</td>
</tr>
<tr>
<td>Last output</td>
<td>{{ container.State.Health.Log[container.State.Health.Log.length - 1].Output }}</td>
</tr>
</tbody>
</table>
</rd-widget-body>
</rd-widget>
</div>
</div>
<div class="row" authorization="DockerImageCreate">
<div class="col-lg-12 col-md-12 col-xs-12">
<rd-widget>
<rd-widget-header icon="fa-clone" title-text="Create image"></rd-widget-header>
<rd-widget-body>
<form class="form-horizontal">
<!-- tag-description -->
<div class="form-group">
<div class="col-sm-12">
<span class="small text-muted">
You can create an image from this container, this allows you to backup important data or save
helpful configurations. You'll be able to spin up another container based on this image afterward.
</span>
</div>
<!-- !tag-note -->
<div class="form-group">
<div class="col-sm-12">
<button type="button" class="btn btn-primary btn-sm" ng-disabled="!config.RegistryModel.Image || config.commitInProgress" ng-click="commit()">Create</button>
</div>
<!-- !tag-description -->
<!-- image-and-registry -->
<por-image-registry
model="config.RegistryModel"
auto-complete="true"
label-class="col-sm-1" input-class="col-sm-11"
></por-image-registry>
<!-- !image-and-registry -->
<!-- tag-note -->
<div class="form-group">
<div class="col-sm-12">
<span class="small text-muted">Note: if you don't specify the tag in the image name, <span class="label label-default">latest</span> will be used.</span>
</div>
</div>
<!-- !tag-note -->
<div class="form-group">
<div class="col-sm-12">
<button type="button" class="btn btn-primary btn-sm" ng-disabled="!config.RegistryModel.Image || config.commitInProgress" ng-click="commit()">Create</button>
</div>
</div>
</form>
</rd-widget-body>
</rd-widget>
</div>
</div>
</form>
</rd-widget-body>
</rd-widget>
</div>
<div class="row">
<div class="col-lg-12 col-md-12 col-xs-12">
<rd-widget>
<rd-widget-header icon="fa-server" title-text="Container details"></rd-widget-header>
<rd-widget-body classes="no-padding">
<table class="table">
<tbody>
<tr>
<td>Image</td>
<td><a ui-sref="docker.images.image({ id: container.Image, nodeName: nodeName })">{{ container.Config.Image}}@{{container.Image}}</a></td>
</tr>
<tr ng-if="portBindings.length > 0">
<td>Port configuration</td>
<td>
<div ng-repeat="portMapping in portBindings">
{{ portMapping.host }} <i class="fa fa-long-arrow-alt-right"></i> {{ portMapping.container }}
</div>
</td>
</tr>
<tr>
<td>CMD</td>
<td><code>{{ container.Config.Cmd|command }}</code></td>
</tr>
<tr>
<td>ENTRYPOINT</td>
<td><code>{{ container.Config.Entrypoint ? (container.Config.Entrypoint|command) : "null" }}</code></td>
</tr>
<tr>
<td>ENV</td>
<td>
<table class="table table-bordered table-condensed">
<tr ng-repeat="var in container.Config.Env track by $index">
<td>{{ var|key: '=' }}</td>
<td>{{ var|value: '=' }}</td>
</tr>
</table>
</td>
</tr>
<tr ng-if="!(container.Config.Labels | emptyobject)">
<td>Labels</td>
<td>
<table class="table table-bordered table-condensed">
<tr ng-repeat="(k, v) in container.Config.Labels">
<td>{{ k }}</td>
<td>{{ v }}</td>
</tr>
</table>
</td>
</tr>
<tr>
<td>Restart policies</td>
<td>
<container-restart-policy ng-if="container"
name="container.HostConfig.RestartPolicy.Name"
maximum-retry-count="container.HostConfig.RestartPolicy.MaximumRetryCount"
update-restart-policy="updateRestartPolicy(name, maximumRetryCount)">
</td>
</tr>
</tbody>
</table>
</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-header icon="fa-server" title-text="Container details"></rd-widget-header>
<rd-widget-body classes="no-padding">
<table class="table">
<tbody>
<tr>
<td>Image</td>
<td
><a ui-sref="docker.images.image({ id: container.Image, nodeName: nodeName })">{{ container.Config.Image }}@{{ container.Image }}</a></td
>
</tr>
<tr ng-if="portBindings.length > 0">
<td>Port configuration</td>
<td>
<div ng-repeat="portMapping in portBindings"> {{ portMapping.host }} <i class="fa fa-long-arrow-alt-right"></i> {{ portMapping.container }} </div>
</td>
</tr>
<tr>
<td>CMD</td>
<td
><code>{{ container.Config.Cmd | command }}</code></td
>
</tr>
<tr>
<td>ENTRYPOINT</td>
<td
><code>{{ container.Config.Entrypoint ? (container.Config.Entrypoint | command) : 'null' }}</code></td
>
</tr>
<tr>
<td>ENV</td>
<td>
<table class="table table-bordered table-condensed">
<tr ng-repeat="var in container.Config.Env track by $index">
<td>{{ var|key: '=' }}</td>
<td>{{ var|value: '=' }}</td>
</tr>
</table>
</td>
</tr>
<tr ng-if="!(container.Config.Labels | emptyobject)">
<td>Labels</td>
<td>
<table class="table table-bordered table-condensed">
<tr ng-repeat="(k, v) in container.Config.Labels">
<td>{{ k }}</td>
<td>{{ v }}</td>
</tr>
</table>
</td>
</tr>
<tr>
<td>Restart policies</td>
<td>
<container-restart-policy
ng-if="container"
name="container.HostConfig.RestartPolicy.Name"
maximum-retry-count="container.HostConfig.RestartPolicy.MaximumRetryCount"
update-restart-policy="updateRestartPolicy(name, maximumRetryCount)"
>
</container-restart-policy>
</td>
</tr>
</tbody>
</table>
</rd-widget-body>
</rd-widget>
</div>
</div>
<div class="row" ng-if="container.Mounts.length > 0">
<div class="col-lg-12 col-md-12 col-xs-12">
<rd-widget>
<rd-widget-header icon="fa-cubes" title-text="Volumes"></rd-widget-header>
<rd-widget-body classes="no-padding">
<table class="table">
<thead>
<tr>
<th>Host/volume</th>
<th>Path in container</th>
</tr>
</thead>
<tbody>
<tr ng-repeat="vol in container.Mounts">
<td ng-if="vol.Type === 'bind'">{{ vol.Source }}</td>
<td ng-if="vol.Type === 'volume'"><a ui-sref="docker.volumes.volume({ id: vol.Name, nodeName: nodeName })">{{ vol.Name }}</a></td>
<td>{{ vol.Destination }}</td>
</tr>
</tbody>
</table>
</rd-widget-body>
</rd-widget>
</div>
<div class="row" ng-if="container.Mounts.length > 0">
<div class="col-lg-12 col-md-12 col-xs-12">
<rd-widget>
<rd-widget-header icon="fa-cubes" title-text="Volumes"></rd-widget-header>
<rd-widget-body classes="no-padding">
<table class="table">
<thead>
<tr>
<th>Host/volume</th>
<th>Path in container</th>
</tr>
</thead>
<tbody>
<tr ng-repeat="vol in container.Mounts">
<td ng-if="vol.Type === 'bind'">{{ vol.Source }}</td>
<td ng-if="vol.Type === 'volume'"
><a ui-sref="docker.volumes.volume({ id: vol.Name, nodeName: nodeName })">{{ vol.Name }}</a></td
>
<td>{{ vol.Destination }}</td>
</tr>
</tbody>
</table>
</rd-widget-body>
</rd-widget>
</div>
</div>
<div class="row">
<div class="col-sm-12">
<container-networks-datatable
title-text="Connected networks" title-icon="fa-sitemap"
dataset="container.NetworkSettings.Networks" table-key="container-networks"
<div class="row">
<div class="col-sm-12">
<container-networks-datatable
title-text="Connected networks"
title-icon="fa-sitemap"
dataset="container.NetworkSettings.Networks"
table-key="container-networks"
container="container"
available-networks="availableNetworks"
join-network-action="containerJoinNetwork"
@ -279,6 +328,6 @@
leave-network-action="containerLeaveNetwork"
leave-network-action-in-progress="state.leaveNetworkInProgress"
node-name="nodeName"
></container-networks-datatable>
</div>
></container-networks-datatable>
</div>
</div>

View file

@ -1,356 +1,383 @@
import moment from 'moment';
import { PorImageRegistryModel } from 'Docker/models/porImageRegistry';
angular.module('portainer.docker')
.controller('ContainerController', ['$q', '$scope', '$state','$transition$', '$filter', '$async', 'ExtensionService', 'Commit', 'ContainerHelper', 'ContainerService', 'ImageHelper', 'NetworkService', 'Notifications', 'ModalService', 'ResourceControlService', 'RegistryService', 'ImageService', 'HttpRequestHelper', 'Authentication',
function ($q, $scope, $state, $transition$, $filter, $async, ExtensionService, Commit, ContainerHelper, ContainerService, ImageHelper, NetworkService, Notifications, ModalService, ResourceControlService, RegistryService, ImageService, HttpRequestHelper, Authentication) {
$scope.activityTime = 0;
$scope.portBindings = [];
$scope.displayRecreateButton = false;
angular.module('portainer.docker').controller('ContainerController', [
'$q',
'$scope',
'$state',
'$transition$',
'$filter',
'$async',
'ExtensionService',
'Commit',
'ContainerHelper',
'ContainerService',
'ImageHelper',
'NetworkService',
'Notifications',
'ModalService',
'ResourceControlService',
'RegistryService',
'ImageService',
'HttpRequestHelper',
'Authentication',
function (
$q,
$scope,
$state,
$transition$,
$filter,
$async,
ExtensionService,
Commit,
ContainerHelper,
ContainerService,
ImageHelper,
NetworkService,
Notifications,
ModalService,
ResourceControlService,
RegistryService,
ImageService,
HttpRequestHelper,
Authentication
) {
$scope.activityTime = 0;
$scope.portBindings = [];
$scope.displayRecreateButton = false;
$scope.config = {
RegistryModel: new PorImageRegistryModel(),
commitInProgress: false
};
$scope.config = {
RegistryModel: new PorImageRegistryModel(),
commitInProgress: false,
};
$scope.state = {
recreateContainerInProgress: false,
joinNetworkInProgress: false,
leaveNetworkInProgress: false
};
$scope.state = {
recreateContainerInProgress: false,
joinNetworkInProgress: false,
leaveNetworkInProgress: false,
};
$scope.updateRestartPolicy = updateRestartPolicy;
$scope.updateRestartPolicy = updateRestartPolicy;
var update = function () {
var nodeName = $transition$.params().nodeName;
HttpRequestHelper.setPortainerAgentTargetHeader(nodeName);
$scope.nodeName = nodeName;
var update = function () {
var nodeName = $transition$.params().nodeName;
HttpRequestHelper.setPortainerAgentTargetHeader(nodeName);
$scope.nodeName = nodeName;
ContainerService.container($transition$.params().id)
.then(function success(data) {
var container = data;
$scope.container = container;
$scope.container.edit = false;
$scope.container.newContainerName = $filter('trimcontainername')(container.Name);
ContainerService.container($transition$.params().id)
.then(function success(data) {
var container = data;
$scope.container = container;
$scope.container.edit = false;
$scope.container.newContainerName = $filter('trimcontainername')(container.Name);
if (container.State.Running) {
$scope.activityTime = moment.duration(moment(container.State.StartedAt).utc().diff(moment().utc())).humanize();
} else if (container.State.Status === 'created') {
$scope.activityTime = moment.duration(moment(container.Created).utc().diff(moment().utc())).humanize();
} else {
$scope.activityTime = moment.duration(moment().utc().diff(moment(container.State.FinishedAt).utc())).humanize();
}
$scope.portBindings = [];
if (container.NetworkSettings.Ports) {
angular.forEach(Object.keys(container.NetworkSettings.Ports), function(portMapping) {
if (container.NetworkSettings.Ports[portMapping]) {
var mapping = {};
mapping.container = portMapping;
mapping.host = container.NetworkSettings.Ports[portMapping][0].HostIp + ':' + container.NetworkSettings.Ports[portMapping][0].HostPort;
$scope.portBindings.push(mapping);
if (container.State.Running) {
$scope.activityTime = moment.duration(moment(container.State.StartedAt).utc().diff(moment().utc())).humanize();
} else if (container.State.Status === 'created') {
$scope.activityTime = moment.duration(moment(container.Created).utc().diff(moment().utc())).humanize();
} else {
$scope.activityTime = moment.duration(moment().utc().diff(moment(container.State.FinishedAt).utc())).humanize();
}
$scope.portBindings = [];
if (container.NetworkSettings.Ports) {
angular.forEach(Object.keys(container.NetworkSettings.Ports), function (portMapping) {
if (container.NetworkSettings.Ports[portMapping]) {
var mapping = {};
mapping.container = portMapping;
mapping.host = container.NetworkSettings.Ports[portMapping][0].HostIp + ':' + container.NetworkSettings.Ports[portMapping][0].HostPort;
$scope.portBindings.push(mapping);
}
});
}
const inSwarm = $scope.container.Config.Labels['com.docker.swarm.service.id'];
const autoRemove = $scope.container.HostConfig.AutoRemove;
const admin = Authentication.isAdmin();
ExtensionService.extensionEnabled(ExtensionService.EXTENSIONS.RBAC).then((rbacEnabled) => {
$scope.displayRecreateButton = !inSwarm && !autoRemove && (rbacEnabled ? admin : true);
});
})
.catch(function error(err) {
Notifications.error('Failure', err, 'Unable to retrieve container info');
});
};
function executeContainerAction(id, action, successMessage, errorMessage) {
action(id)
.then(function success() {
Notifications.success(successMessage, id);
update();
})
.catch(function error(err) {
Notifications.error('Failure', err, errorMessage);
});
}
$scope.start = function () {
var successMessage = 'Container successfully started';
var errorMessage = 'Unable to start container';
executeContainerAction($transition$.params().id, ContainerService.startContainer, successMessage, errorMessage);
};
$scope.stop = function () {
var successMessage = 'Container successfully stopped';
var errorMessage = 'Unable to stop container';
executeContainerAction($transition$.params().id, ContainerService.stopContainer, successMessage, errorMessage);
};
$scope.kill = function () {
var successMessage = 'Container successfully killed';
var errorMessage = 'Unable to kill container';
executeContainerAction($transition$.params().id, ContainerService.killContainer, successMessage, errorMessage);
};
$scope.pause = function () {
var successMessage = 'Container successfully paused';
var errorMessage = 'Unable to pause container';
executeContainerAction($transition$.params().id, ContainerService.pauseContainer, successMessage, errorMessage);
};
$scope.unpause = function () {
var successMessage = 'Container successfully resumed';
var errorMessage = 'Unable to resume container';
executeContainerAction($transition$.params().id, ContainerService.resumeContainer, successMessage, errorMessage);
};
$scope.restart = function () {
var successMessage = 'Container successfully restarted';
var errorMessage = 'Unable to restart container';
executeContainerAction($transition$.params().id, ContainerService.restartContainer, successMessage, errorMessage);
};
$scope.renameContainer = function () {
var container = $scope.container;
ContainerService.renameContainer($transition$.params().id, container.newContainerName)
.then(function success() {
container.Name = container.newContainerName;
Notifications.success('Container successfully renamed', container.Name);
})
.catch(function error(err) {
container.newContainerName = container.Name;
Notifications.error('Failure', err, 'Unable to rename container');
})
.finally(function final() {
$scope.container.edit = false;
});
};
$scope.containerLeaveNetwork = function containerLeaveNetwork(container, networkId) {
$scope.state.leaveNetworkInProgress = true;
NetworkService.disconnectContainer(networkId, container.Id, false)
.then(function success() {
Notifications.success('Container left network', container.Id);
$state.reload();
})
.catch(function error(err) {
Notifications.error('Failure', err, 'Unable to disconnect container from network');
})
.finally(function final() {
$scope.state.leaveNetworkInProgress = false;
});
};
$scope.containerJoinNetwork = function containerJoinNetwork(container, networkId) {
$scope.state.joinNetworkInProgress = true;
NetworkService.connectContainer(networkId, container.Id)
.then(function success() {
Notifications.success('Container joined network', container.Id);
$state.reload();
})
.catch(function error(err) {
Notifications.error('Failure', err, 'Unable to connect container to network');
})
.finally(function final() {
$scope.state.joinNetworkInProgress = false;
});
};
async function commitContainerAsync() {
$scope.config.commitInProgress = true;
const registryModel = $scope.config.RegistryModel;
const imageConfig = ImageHelper.createImageConfigForContainer(registryModel);
try {
await Commit.commitContainer({ id: $transition$.params().id, repo: imageConfig.fromImage }).$promise;
Notifications.success('Image created', $transition$.params().id);
$state.reload();
} catch (err) {
Notifications.error('Failure', err, 'Unable to create image');
$scope.config.commitInProgress = false;
}
const inSwarm = $scope.container.Config.Labels['com.docker.swarm.service.id'];
const autoRemove = $scope.container.HostConfig.AutoRemove;
const admin = Authentication.isAdmin();
ExtensionService.extensionEnabled(ExtensionService.EXTENSIONS.RBAC).then((rbacEnabled) => {
$scope.displayRecreateButton = !inSwarm && !autoRemove && (rbacEnabled ? admin : true)
});
})
.catch(function error(err) {
Notifications.error('Failure', err, 'Unable to retrieve container info');
});
};
function executeContainerAction(id, action, successMessage, errorMessage) {
action(id)
.then(function success() {
Notifications.success(successMessage, id);
update();
})
.catch(function error(err) {
Notifications.error('Failure', err, errorMessage);
});
}
$scope.start = function () {
var successMessage = 'Container successfully started';
var errorMessage = 'Unable to start container';
executeContainerAction($transition$.params().id, ContainerService.startContainer, successMessage, errorMessage);
};
$scope.stop = function () {
var successMessage = 'Container successfully stopped';
var errorMessage = 'Unable to stop container';
executeContainerAction($transition$.params().id, ContainerService.stopContainer, successMessage, errorMessage);
};
$scope.kill = function () {
var successMessage = 'Container successfully killed';
var errorMessage = 'Unable to kill container';
executeContainerAction($transition$.params().id, ContainerService.killContainer, successMessage, errorMessage);
};
$scope.pause = function() {
var successMessage = 'Container successfully paused';
var errorMessage = 'Unable to pause container';
executeContainerAction($transition$.params().id, ContainerService.pauseContainer, successMessage, errorMessage);
};
$scope.unpause = function() {
var successMessage = 'Container successfully resumed';
var errorMessage = 'Unable to resume container';
executeContainerAction($transition$.params().id, ContainerService.resumeContainer, successMessage, errorMessage);
};
$scope.restart = function () {
var successMessage = 'Container successfully restarted';
var errorMessage = 'Unable to restart container';
executeContainerAction($transition$.params().id, ContainerService.restartContainer, successMessage, errorMessage);
};
$scope.renameContainer = function () {
var container = $scope.container;
ContainerService.renameContainer($transition$.params().id, container.newContainerName)
.then(function success() {
container.Name = container.newContainerName;
Notifications.success('Container successfully renamed', container.Name);
})
.catch(function error(err) {
container.newContainerName = container.Name;
Notifications.error('Failure', err, 'Unable to rename container');
})
.finally(function final() {
$scope.container.edit = false;
});
};
$scope.containerLeaveNetwork = function containerLeaveNetwork(container, networkId) {
$scope.state.leaveNetworkInProgress = true;
NetworkService.disconnectContainer(networkId, container.Id, false)
.then(function success() {
Notifications.success('Container left network', container.Id);
$state.reload();
})
.catch(function error(err) {
Notifications.error('Failure', err, 'Unable to disconnect container from network');
})
.finally(function final() {
$scope.state.leaveNetworkInProgress = false;
});
};
$scope.containerJoinNetwork = function containerJoinNetwork(container, networkId) {
$scope.state.joinNetworkInProgress = true;
NetworkService.connectContainer(networkId, container.Id)
.then(function success() {
Notifications.success('Container joined network', container.Id);
$state.reload();
})
.catch(function error(err) {
Notifications.error('Failure', err, 'Unable to connect container to network');
})
.finally(function final() {
$scope.state.joinNetworkInProgress = false;
});
};
async function commitContainerAsync() {
$scope.config.commitInProgress = true;
const registryModel = $scope.config.RegistryModel;
const imageConfig = ImageHelper.createImageConfigForContainer(registryModel);
try {
await Commit.commitContainer({id: $transition$.params().id, repo: imageConfig.fromImage}).$promise;
Notifications.success('Image created', $transition$.params().id);
$state.reload();
} catch (err) {
Notifications.error('Failure', err, 'Unable to create image');
$scope.config.commitInProgress = false;
}
}
$scope.commit = function () {
return $async(commitContainerAsync);
};
$scope.commit = function () {
return $async(commitContainerAsync);
};
$scope.confirmRemove = function () {
var title = 'You are about to remove a container.';
if ($scope.container.State.Running) {
title = 'You are about to remove a running container.';
}
ModalService.confirmContainerDeletion(
title,
function (result) {
if(!result) { return; }
$scope.confirmRemove = function () {
var title = 'You are about to remove a container.';
if ($scope.container.State.Running) {
title = 'You are about to remove a running container.';
}
ModalService.confirmContainerDeletion(title, function (result) {
if (!result) {
return;
}
var cleanAssociatedVolumes = false;
if (result[0]) {
cleanAssociatedVolumes = true;
}
removeContainer(cleanAssociatedVolumes);
}
);
};
function removeContainer(cleanAssociatedVolumes) {
ContainerService.remove($scope.container, cleanAssociatedVolumes)
.then(function success() {
Notifications.success('Container successfully removed');
$state.go('docker.containers', {}, {reload: true});
})
.catch(function error(err) {
Notifications.error('Failure', err, 'Unable to remove container');
});
}
function recreateContainer(pullImage) {
var container = $scope.container;
var config = ContainerHelper.configFromContainer(container.Model);
$scope.state.recreateContainerInProgress = true;
var isRunning = container.State.Running;
return pullImageIfNeeded()
.then(stopContainerIfNeeded)
.then(renameContainer)
.then(setMainNetworkAndCreateContainer)
.then(connectContainerToOtherNetworks)
.then(startContainerIfNeeded)
.then(createResourceControl)
.then(deleteOldContainer)
.then(notifyAndChangeView)
.catch(notifyOnError);
function stopContainerIfNeeded() {
if (!isRunning) {
return $q.when();
}
return ContainerService.stopContainer(container.Id);
}
function renameContainer() {
return ContainerService.renameContainer(container.Id, container.Name + '-old');
}
function pullImageIfNeeded() {
if (!pullImage) {
return $q.when();
}
return RegistryService.retrievePorRegistryModelFromRepository(container.Config.Image)
.then(function pullImage(registryModel) {
return ImageService.pullImage(registryModel, true);
});
}
};
function setMainNetworkAndCreateContainer() {
var networks = config.NetworkingConfig.EndpointsConfig;
var networksNames = Object.keys(networks);
if (networksNames.length > 1) {
config.NetworkingConfig.EndpointsConfig = {};
config.NetworkingConfig.EndpointsConfig[networksNames[0]] = networks[0];
}
return $q.all([ContainerService.createContainer(config), networks]);
}
function connectContainerToOtherNetworks(createContainerData) {
var newContainer = createContainerData[0];
var networks = createContainerData[1];
var networksNames = Object.keys(networks);
var connectionPromises = networksNames.map(function connectToNetwork(name) {
NetworkService.connectContainer(name, newContainer.Id);
});
return $q.all(connectionPromises)
.then(function onConnectToNetworkSuccess() {
return newContainer;
function removeContainer(cleanAssociatedVolumes) {
ContainerService.remove($scope.container, cleanAssociatedVolumes)
.then(function success() {
Notifications.success('Container successfully removed');
$state.go('docker.containers', {}, { reload: true });
})
.catch(function error(err) {
Notifications.error('Failure', err, 'Unable to remove container');
});
}
function deleteOldContainer(newContainer) {
return ContainerService.remove(container, true).then(
function onRemoveSuccess() {
return newContainer;
function recreateContainer(pullImage) {
var container = $scope.container;
var config = ContainerHelper.configFromContainer(container.Model);
$scope.state.recreateContainerInProgress = true;
var isRunning = container.State.Running;
return pullImageIfNeeded()
.then(stopContainerIfNeeded)
.then(renameContainer)
.then(setMainNetworkAndCreateContainer)
.then(connectContainerToOtherNetworks)
.then(startContainerIfNeeded)
.then(createResourceControl)
.then(deleteOldContainer)
.then(notifyAndChangeView)
.catch(notifyOnError);
function stopContainerIfNeeded() {
if (!isRunning) {
return $q.when();
}
);
}
function startContainerIfNeeded(newContainer) {
if (!isRunning) {
return $q.when(newContainer);
return ContainerService.stopContainer(container.Id);
}
return ContainerService.startContainer(newContainer.Id).then(
function onStartSuccess() {
return newContainer;
function renameContainer() {
return ContainerService.renameContainer(container.Id, container.Name + '-old');
}
function pullImageIfNeeded() {
if (!pullImage) {
return $q.when();
}
);
}
function createResourceControl(newContainer) {
const userId = Authentication.getUserDetails().ID;
const oldResourceControl = container.ResourceControl;
const newResourceControl = newContainer.Portainer.ResourceControl;
return ResourceControlService.duplicateResourceControl(userId, oldResourceControl, newResourceControl);
}
function notifyAndChangeView() {
Notifications.success('Container successfully re-created');
$state.go('docker.containers', {}, { reload: true });
}
function notifyOnError(err) {
Notifications.error('Failure', err, 'Unable to re-create container');
$scope.state.recreateContainerInProgress = false;
}
}
$scope.recreate = function() {
ModalService.confirmContainerRecreation(function (result) {
if(!result) { return; }
var pullImage = false;
if (result[0]) {
pullImage = true;
return RegistryService.retrievePorRegistryModelFromRepository(container.Config.Image).then(function pullImage(registryModel) {
return ImageService.pullImage(registryModel, true);
});
}
recreateContainer(pullImage);
});
};
function updateRestartPolicy(restartPolicy, maximumRetryCount) {
maximumRetryCount = restartPolicy === 'on-failure' ? maximumRetryCount : undefined;
function setMainNetworkAndCreateContainer() {
var networks = config.NetworkingConfig.EndpointsConfig;
var networksNames = Object.keys(networks);
if (networksNames.length > 1) {
config.NetworkingConfig.EndpointsConfig = {};
config.NetworkingConfig.EndpointsConfig[networksNames[0]] = networks[0];
}
return $q.all([ContainerService.createContainer(config), networks]);
}
return ContainerService
.updateRestartPolicy($scope.container.Id, restartPolicy, maximumRetryCount)
.then(onUpdateSuccess)
.catch(notifyOnError);
function connectContainerToOtherNetworks(createContainerData) {
var newContainer = createContainerData[0];
var networks = createContainerData[1];
var networksNames = Object.keys(networks);
var connectionPromises = networksNames.map(function connectToNetwork(name) {
NetworkService.connectContainer(name, newContainer.Id);
});
return $q.all(connectionPromises).then(function onConnectToNetworkSuccess() {
return newContainer;
});
}
function onUpdateSuccess() {
$scope.container.HostConfig.RestartPolicy = {
Name: restartPolicy,
MaximumRetryCount: maximumRetryCount
};
Notifications.success('Restart policy updated');
function deleteOldContainer(newContainer) {
return ContainerService.remove(container, true).then(function onRemoveSuccess() {
return newContainer;
});
}
function startContainerIfNeeded(newContainer) {
if (!isRunning) {
return $q.when(newContainer);
}
return ContainerService.startContainer(newContainer.Id).then(function onStartSuccess() {
return newContainer;
});
}
function createResourceControl(newContainer) {
const userId = Authentication.getUserDetails().ID;
const oldResourceControl = container.ResourceControl;
const newResourceControl = newContainer.Portainer.ResourceControl;
return ResourceControlService.duplicateResourceControl(userId, oldResourceControl, newResourceControl);
}
function notifyAndChangeView() {
Notifications.success('Container successfully re-created');
$state.go('docker.containers', {}, { reload: true });
}
function notifyOnError(err) {
Notifications.error('Failure', err, 'Unable to re-create container');
$scope.state.recreateContainerInProgress = false;
}
}
function notifyOnError(err) {
Notifications.error('Failure', err, 'Unable to update restart policy');
return $q.reject(err);
$scope.recreate = function () {
ModalService.confirmContainerRecreation(function (result) {
if (!result) {
return;
}
var pullImage = false;
if (result[0]) {
pullImage = true;
}
recreateContainer(pullImage);
});
};
function updateRestartPolicy(restartPolicy, maximumRetryCount) {
maximumRetryCount = restartPolicy === 'on-failure' ? maximumRetryCount : undefined;
return ContainerService.updateRestartPolicy($scope.container.Id, restartPolicy, maximumRetryCount).then(onUpdateSuccess).catch(notifyOnError);
function onUpdateSuccess() {
$scope.container.HostConfig.RestartPolicy = {
Name: restartPolicy,
MaximumRetryCount: maximumRetryCount,
};
Notifications.success('Restart policy updated');
}
function notifyOnError(err) {
Notifications.error('Failure', err, 'Unable to update restart policy');
return $q.reject(err);
}
}
}
var provider = $scope.applicationState.endpoint.mode.provider;
var apiVersion = $scope.applicationState.endpoint.apiVersion;
NetworkService.networks(
provider === 'DOCKER_STANDALONE' || provider === 'DOCKER_SWARM_MODE',
false,
provider === 'DOCKER_SWARM_MODE' && apiVersion >= 1.25
)
.then(function success(data) {
var networks = data;
$scope.availableNetworks = networks;
})
.catch(function error(err) {
Notifications.error('Failure', err, 'Unable to retrieve networks');
});
var provider = $scope.applicationState.endpoint.mode.provider;
var apiVersion = $scope.applicationState.endpoint.apiVersion;
NetworkService.networks(provider === 'DOCKER_STANDALONE' || provider === 'DOCKER_SWARM_MODE', false, provider === 'DOCKER_SWARM_MODE' && apiVersion >= 1.25)
.then(function success(data) {
var networks = data;
$scope.availableNetworks = networks;
})
.catch(function error(err) {
Notifications.error('Failure', err, 'Unable to retrieve networks');
});
update();
}]);
update();
},
]);

View file

@ -1,22 +1,26 @@
angular.module('portainer.docker')
.controller('ContainerInspectController', ['$scope', '$transition$', 'Notifications', 'ContainerService', 'HttpRequestHelper',
function ($scope, $transition$, Notifications, ContainerService, HttpRequestHelper) {
angular.module('portainer.docker').controller('ContainerInspectController', [
'$scope',
'$transition$',
'Notifications',
'ContainerService',
'HttpRequestHelper',
function ($scope, $transition$, Notifications, ContainerService, HttpRequestHelper) {
$scope.state = {
DisplayTextView: false,
};
$scope.containerInfo = {};
$scope.state = {
DisplayTextView: false
};
$scope.containerInfo = {};
function initView() {
HttpRequestHelper.setPortainerAgentTargetHeader($transition$.params().nodeName);
ContainerService.inspect($transition$.params().id)
.then(function success(d) {
$scope.containerInfo = d;
})
.catch(function error(e) {
Notifications.error('Failure', e, 'Unable to inspect container');
});
}
function initView() {
HttpRequestHelper.setPortainerAgentTargetHeader($transition$.params().nodeName);
ContainerService.inspect($transition$.params().id)
.then(function success(d) {
$scope.containerInfo = d;
})
.catch(function error(e) {
Notifications.error('Failure', e, 'Unable to inspect container');
});
}
initView();
}]);
initView();
},
]);

View file

@ -1,8 +1,8 @@
<rd-header>
<rd-header-title title-text="Container inspect">
</rd-header-title>
<rd-header-title title-text="Container inspect"> </rd-header-title>
<rd-header-content>
<a ui-sref="docker.containers">Containers</a> &gt; <a ui-sref="docker.containers.container({id: containerInfo.Id})">{{ containerInfo.Name|trimcontainername }}</a> &gt; Inspect
<a ui-sref="docker.containers">Containers</a> &gt; <a ui-sref="docker.containers.container({id: containerInfo.Id})">{{ containerInfo.Name | trimcontainername }}</a> &gt;
Inspect
</rd-header-content>
</rd-header>
@ -16,7 +16,7 @@
</span>
</rd-widget-header>
<rd-widget-body>
<pre ng-show="state.DisplayTextView">{{ containerInfo|json:4 }}</pre>
<pre ng-show="state.DisplayTextView">{{ containerInfo | json: 4 }}</pre>
<json-tree ng-hide="state.DisplayTextView" object="containerInfo" root-name="containerInfo.Id" start-expanded="true"></json-tree>
</rd-widget-body>
</rd-widget>

View file

@ -1,73 +1,87 @@
import moment from 'moment';
angular.module('portainer.docker')
.controller('ContainerLogsController', ['$scope', '$transition$', '$interval', 'ContainerService', 'Notifications', 'HttpRequestHelper',
function ($scope, $transition$, $interval, ContainerService, Notifications, HttpRequestHelper) {
$scope.state = {
refreshRate: 3,
lineCount: 100,
sinceTimestamp: '',
displayTimestamps: false
};
angular.module('portainer.docker').controller('ContainerLogsController', [
'$scope',
'$transition$',
'$interval',
'ContainerService',
'Notifications',
'HttpRequestHelper',
function ($scope, $transition$, $interval, ContainerService, Notifications, HttpRequestHelper) {
$scope.state = {
refreshRate: 3,
lineCount: 100,
sinceTimestamp: '',
displayTimestamps: false,
};
$scope.changeLogCollection = function(logCollectionStatus) {
if (!logCollectionStatus) {
stopRepeater();
} else {
setUpdateRepeater(!$scope.container.Config.Tty);
}
};
$scope.$on('$destroy', function() {
stopRepeater();
});
function stopRepeater() {
var repeater = $scope.repeater;
if (angular.isDefined(repeater)) {
$interval.cancel(repeater);
repeater = null;
}
}
function setUpdateRepeater(skipHeaders) {
var refreshRate = $scope.state.refreshRate;
$scope.repeater = $interval(function() {
ContainerService.logs($transition$.params().id, 1, 1, $scope.state.displayTimestamps ? 1 : 0, moment($scope.state.sinceTimestamp).unix(), $scope.state.lineCount, skipHeaders)
.then(function success(data) {
$scope.logs = data;
})
.catch(function error(err) {
$scope.changeLogCollection = function (logCollectionStatus) {
if (!logCollectionStatus) {
stopRepeater();
Notifications.error('Failure', err, 'Unable to retrieve container logs');
});
}, refreshRate * 1000);
}
} else {
setUpdateRepeater(!$scope.container.Config.Tty);
}
};
function startLogPolling(skipHeaders) {
ContainerService.logs($transition$.params().id, 1, 1, $scope.state.displayTimestamps ? 1 : 0, moment($scope.state.sinceTimestamp).unix(), $scope.state.lineCount, skipHeaders)
.then(function success(data) {
$scope.logs = data;
setUpdateRepeater(skipHeaders);
})
.catch(function error(err) {
$scope.$on('$destroy', function () {
stopRepeater();
Notifications.error('Failure', err, 'Unable to retrieve container logs');
});
}
function initView() {
HttpRequestHelper.setPortainerAgentTargetHeader($transition$.params().nodeName);
ContainerService.container($transition$.params().id)
.then(function success(data) {
var container = data;
$scope.container = container;
startLogPolling(!container.Config.Tty);
})
.catch(function error(err) {
Notifications.error('Failure', err, 'Unable to retrieve container information');
});
}
function stopRepeater() {
var repeater = $scope.repeater;
if (angular.isDefined(repeater)) {
$interval.cancel(repeater);
repeater = null;
}
}
initView();
}]);
function setUpdateRepeater(skipHeaders) {
var refreshRate = $scope.state.refreshRate;
$scope.repeater = $interval(function () {
ContainerService.logs(
$transition$.params().id,
1,
1,
$scope.state.displayTimestamps ? 1 : 0,
moment($scope.state.sinceTimestamp).unix(),
$scope.state.lineCount,
skipHeaders
)
.then(function success(data) {
$scope.logs = data;
})
.catch(function error(err) {
stopRepeater();
Notifications.error('Failure', err, 'Unable to retrieve container logs');
});
}, refreshRate * 1000);
}
function startLogPolling(skipHeaders) {
ContainerService.logs($transition$.params().id, 1, 1, $scope.state.displayTimestamps ? 1 : 0, moment($scope.state.sinceTimestamp).unix(), $scope.state.lineCount, skipHeaders)
.then(function success(data) {
$scope.logs = data;
setUpdateRepeater(skipHeaders);
})
.catch(function error(err) {
stopRepeater();
Notifications.error('Failure', err, 'Unable to retrieve container logs');
});
}
function initView() {
HttpRequestHelper.setPortainerAgentTargetHeader($transition$.params().nodeName);
ContainerService.container($transition$.params().id)
.then(function success(data) {
var container = data;
$scope.container = container;
startLogPolling(!container.Config.Tty);
})
.catch(function error(err) {
Notifications.error('Failure', err, 'Unable to retrieve container information');
});
}
initView();
},
]);

View file

@ -1,10 +1,15 @@
<rd-header>
<rd-header-title title-text="Container logs"></rd-header-title>
<rd-header-content>
<a ui-sref="docker.containers">Containers</a> &gt; <a ui-sref="docker.containers.container({id: container.Id})">{{ container.Name|trimcontainername }}</a> &gt; Logs
<a ui-sref="docker.containers">Containers</a> &gt; <a ui-sref="docker.containers.container({id: container.Id})">{{ container.Name | trimcontainername }}</a> &gt; Logs
</rd-header-content>
</rd-header>
<log-viewer
data="logs" ng-if="logs" log-collection-change="changeLogCollection" display-timestamps="state.displayTimestamps" line-count="state.lineCount" since-timestamp="state.sinceTimestamp"
data="logs"
ng-if="logs"
log-collection-change="changeLogCollection"
display-timestamps="state.displayTimestamps"
line-count="state.lineCount"
since-timestamp="state.sinceTimestamp"
></log-viewer>

View file

@ -1,156 +1,163 @@
import moment from 'moment';
angular.module('portainer.docker')
.controller('ContainerStatsController', ['$q', '$scope', '$transition$', '$document', '$interval', 'ContainerService', 'ChartService', 'Notifications', 'HttpRequestHelper',
function ($q, $scope, $transition$, $document, $interval, ContainerService, ChartService, Notifications, HttpRequestHelper) {
angular.module('portainer.docker').controller('ContainerStatsController', [
'$q',
'$scope',
'$transition$',
'$document',
'$interval',
'ContainerService',
'ChartService',
'Notifications',
'HttpRequestHelper',
function ($q, $scope, $transition$, $document, $interval, ContainerService, ChartService, Notifications, HttpRequestHelper) {
$scope.state = {
refreshRate: '5',
networkStatsUnavailable: false,
};
$scope.state = {
refreshRate: '5',
networkStatsUnavailable: false
};
$scope.$on('$destroy', function () {
stopRepeater();
});
$scope.$on('$destroy', function() {
stopRepeater();
});
function stopRepeater() {
var repeater = $scope.repeater;
if (angular.isDefined(repeater)) {
$interval.cancel(repeater);
repeater = null;
function stopRepeater() {
var repeater = $scope.repeater;
if (angular.isDefined(repeater)) {
$interval.cancel(repeater);
repeater = null;
}
}
}
function updateNetworkChart(stats, chart) {
if (stats.Networks.length > 0) {
var rx = stats.Networks[0].rx_bytes;
var tx = stats.Networks[0].tx_bytes;
function updateNetworkChart(stats, chart) {
if (stats.Networks.length > 0) {
var rx = stats.Networks[0].rx_bytes;
var tx = stats.Networks[0].tx_bytes;
var label = moment(stats.read).format('HH:mm:ss');
ChartService.UpdateNetworkChart(label, rx, tx, chart);
}
}
function updateMemoryChart(stats, chart) {
var label = moment(stats.read).format('HH:mm:ss');
ChartService.UpdateNetworkChart(label, rx, tx, chart);
}
}
function updateMemoryChart(stats, chart) {
var label = moment(stats.read).format('HH:mm:ss');
ChartService.UpdateMemoryChart(label, stats.MemoryUsage, stats.MemoryCache, chart);
}
function updateCPUChart(stats, chart) {
var label = moment(stats.read).format('HH:mm:ss');
var value = stats.isWindows ? calculateCPUPercentWindows(stats) : calculateCPUPercentUnix(stats);
ChartService.UpdateCPUChart(label, value, chart);
}
function calculateCPUPercentUnix(stats) {
var cpuPercent = 0.0;
var cpuDelta = stats.CurrentCPUTotalUsage - stats.PreviousCPUTotalUsage;
var systemDelta = stats.CurrentCPUSystemUsage - stats.PreviousCPUSystemUsage;
if (systemDelta > 0.0 && cpuDelta > 0.0) {
cpuPercent = (cpuDelta / systemDelta) * stats.CPUCores * 100.0;
ChartService.UpdateMemoryChart(label, stats.MemoryUsage, stats.MemoryCache, chart);
}
return cpuPercent;
}
function updateCPUChart(stats, chart) {
var label = moment(stats.read).format('HH:mm:ss');
var value = stats.isWindows ? calculateCPUPercentWindows(stats) : calculateCPUPercentUnix(stats);
function calculateCPUPercentWindows(stats) {
var possIntervals = stats.NumProcs * parseFloat(
moment(stats.read, 'YYYY-MM-DDTHH:mm:ss.SSSSSSSSSZ').valueOf() - moment(stats.preread, 'YYYY-MM-DDTHH:mm:ss.SSSSSSSSSZ').valueOf());
var windowsCpuUsage = 0.0;
if(possIntervals > 0) {
windowsCpuUsage = parseFloat(stats.CurrentCPUTotalUsage - stats.PreviousCPUTotalUsage) / parseFloat(possIntervals * 100);
ChartService.UpdateCPUChart(label, value, chart);
}
return windowsCpuUsage;
}
function calculateCPUPercentUnix(stats) {
var cpuPercent = 0.0;
var cpuDelta = stats.CurrentCPUTotalUsage - stats.PreviousCPUTotalUsage;
var systemDelta = stats.CurrentCPUSystemUsage - stats.PreviousCPUSystemUsage;
$scope.changeUpdateRepeater = function() {
var networkChart = $scope.networkChart;
var cpuChart = $scope.cpuChart;
var memoryChart = $scope.memoryChart;
stopRepeater();
setUpdateRepeater(networkChart, cpuChart, memoryChart);
$('#refreshRateChange').show();
$('#refreshRateChange').fadeOut(1500);
};
function startChartUpdate(networkChart, cpuChart, memoryChart) {
$q.all({
stats: ContainerService.containerStats($transition$.params().id),
top: ContainerService.containerTop($transition$.params().id)
})
.then(function success(data) {
var stats = data.stats;
$scope.processInfo = data.top;
if (stats.Networks.length === 0) {
$scope.state.networkStatsUnavailable = true;
if (systemDelta > 0.0 && cpuDelta > 0.0) {
cpuPercent = (cpuDelta / systemDelta) * stats.CPUCores * 100.0;
}
updateNetworkChart(stats, networkChart);
updateMemoryChart(stats, memoryChart);
updateCPUChart(stats, cpuChart);
setUpdateRepeater(networkChart, cpuChart, memoryChart);
})
.catch(function error(err) {
stopRepeater();
Notifications.error('Failure', err, 'Unable to retrieve container statistics');
});
}
function setUpdateRepeater(networkChart, cpuChart, memoryChart) {
var refreshRate = $scope.state.refreshRate;
$scope.repeater = $interval(function() {
return cpuPercent;
}
function calculateCPUPercentWindows(stats) {
var possIntervals =
stats.NumProcs * parseFloat(moment(stats.read, 'YYYY-MM-DDTHH:mm:ss.SSSSSSSSSZ').valueOf() - moment(stats.preread, 'YYYY-MM-DDTHH:mm:ss.SSSSSSSSSZ').valueOf());
var windowsCpuUsage = 0.0;
if (possIntervals > 0) {
windowsCpuUsage = parseFloat(stats.CurrentCPUTotalUsage - stats.PreviousCPUTotalUsage) / parseFloat(possIntervals * 100);
}
return windowsCpuUsage;
}
$scope.changeUpdateRepeater = function () {
var networkChart = $scope.networkChart;
var cpuChart = $scope.cpuChart;
var memoryChart = $scope.memoryChart;
stopRepeater();
setUpdateRepeater(networkChart, cpuChart, memoryChart);
$('#refreshRateChange').show();
$('#refreshRateChange').fadeOut(1500);
};
function startChartUpdate(networkChart, cpuChart, memoryChart) {
$q.all({
stats: ContainerService.containerStats($transition$.params().id),
top: ContainerService.containerTop($transition$.params().id)
top: ContainerService.containerTop($transition$.params().id),
})
.then(function success(data) {
var stats = data.stats;
$scope.processInfo = data.top;
updateNetworkChart(stats, networkChart);
updateMemoryChart(stats, memoryChart);
updateCPUChart(stats, cpuChart);
})
.catch(function error(err) {
stopRepeater();
Notifications.error('Failure', err, 'Unable to retrieve container statistics');
.then(function success(data) {
var stats = data.stats;
$scope.processInfo = data.top;
if (stats.Networks.length === 0) {
$scope.state.networkStatsUnavailable = true;
}
updateNetworkChart(stats, networkChart);
updateMemoryChart(stats, memoryChart);
updateCPUChart(stats, cpuChart);
setUpdateRepeater(networkChart, cpuChart, memoryChart);
})
.catch(function error(err) {
stopRepeater();
Notifications.error('Failure', err, 'Unable to retrieve container statistics');
});
}
function setUpdateRepeater(networkChart, cpuChart, memoryChart) {
var refreshRate = $scope.state.refreshRate;
$scope.repeater = $interval(function () {
$q.all({
stats: ContainerService.containerStats($transition$.params().id),
top: ContainerService.containerTop($transition$.params().id),
})
.then(function success(data) {
var stats = data.stats;
$scope.processInfo = data.top;
updateNetworkChart(stats, networkChart);
updateMemoryChart(stats, memoryChart);
updateCPUChart(stats, cpuChart);
})
.catch(function error(err) {
stopRepeater();
Notifications.error('Failure', err, 'Unable to retrieve container statistics');
});
}, refreshRate * 1000);
}
function initCharts() {
var networkChartCtx = $('#networkChart');
var networkChart = ChartService.CreateNetworkChart(networkChartCtx);
$scope.networkChart = networkChart;
var cpuChartCtx = $('#cpuChart');
var cpuChart = ChartService.CreateCPUChart(cpuChartCtx);
$scope.cpuChart = cpuChart;
var memoryChartCtx = $('#memoryChart');
var memoryChart = ChartService.CreateMemoryChart(memoryChartCtx);
$scope.memoryChart = memoryChart;
startChartUpdate(networkChart, cpuChart, memoryChart);
}
function initView() {
HttpRequestHelper.setPortainerAgentTargetHeader($transition$.params().nodeName);
ContainerService.container($transition$.params().id)
.then(function success(data) {
$scope.container = data;
})
.catch(function error(err) {
Notifications.error('Failure', err, 'Unable to retrieve container information');
});
$document.ready(function () {
initCharts();
});
}, refreshRate * 1000);
}
}
function initCharts() {
var networkChartCtx = $('#networkChart');
var networkChart = ChartService.CreateNetworkChart(networkChartCtx);
$scope.networkChart = networkChart;
var cpuChartCtx = $('#cpuChart');
var cpuChart = ChartService.CreateCPUChart(cpuChartCtx);
$scope.cpuChart = cpuChart;
var memoryChartCtx = $('#memoryChart');
var memoryChart = ChartService.CreateMemoryChart(memoryChartCtx);
$scope.memoryChart = memoryChart;
startChartUpdate(networkChart, cpuChart, memoryChart);
}
function initView() {
HttpRequestHelper.setPortainerAgentTargetHeader($transition$.params().nodeName);
ContainerService.container($transition$.params().id)
.then(function success(data) {
$scope.container = data;
})
.catch(function error(err) {
Notifications.error('Failure', err, 'Unable to retrieve container information');
});
$document.ready(function() {
initCharts();
});
}
initView();
}]);
initView();
},
]);

View file

@ -1,22 +1,21 @@
<rd-header>
<rd-header-title title-text="Container statistics"></rd-header-title>
<rd-header-content>
<a ui-sref="docker.containers">Containers</a> &gt; <a ui-sref="docker.containers.container({id: container.Id})">{{ container.Name|trimcontainername }}</a> &gt; Stats
<a ui-sref="docker.containers">Containers</a> &gt; <a ui-sref="docker.containers.container({id: container.Id})">{{ container.Name | trimcontainername }}</a> &gt; Stats
</rd-header-content>
</rd-header>
<div class="row">
<div class="col-md-12">
<rd-widget>
<rd-widget-header icon="fa-info-circle" title-text="About statistics">
</rd-widget-header>
<rd-widget-header icon="fa-info-circle" title-text="About statistics"> </rd-widget-header>
<rd-widget-body>
<form class="form-horizontal">
<div class="form-group">
<div class="col-sm-12">
<span class="small text-muted">
This view displays real-time statistics about the container <b>{{ container.Name|trimcontainername }}</b> as well as a list of the running processes
inside this container.
This view displays real-time statistics about the container <b>{{ container.Name | trimcontainername }}</b> as well as a list of the running processes inside this
container.
</span>
</div>
</div>
@ -40,9 +39,7 @@
</div>
<div class="form-group" ng-if="state.networkStatsUnavailable">
<div class="col-sm-12">
<span class="small text-muted">
<i class="fa fa-exclamation-triangle orange-icon" aria-hidden="true"></i> Network stats are unavailable for this container.
</span>
<span class="small text-muted"> <i class="fa fa-exclamation-triangle orange-icon" aria-hidden="true"></i> Network stats are unavailable for this container. </span>
</div>
</div>
</form>
@ -52,7 +49,7 @@
</div>
<div class="row">
<div ng-class="{true: 'col-md-6 col-sm-12', false: 'col-lg-4 col-md-6 col-sm-12'}[state.networkStatsUnavailable]">
<div ng-class="{ true: 'col-md-6 col-sm-12', false: 'col-lg-4 col-md-6 col-sm-12' }[state.networkStatsUnavailable]">
<rd-widget>
<rd-widget-header icon="fa-chart-area" title-text="Memory usage"></rd-widget-header>
<rd-widget-body>
@ -62,7 +59,7 @@
</rd-widget-body>
</rd-widget>
</div>
<div ng-class="{true: 'col-md-6 col-sm-12', false: 'col-lg-4 col-md-6 col-sm-12'}[state.networkStatsUnavailable]">
<div ng-class="{ true: 'col-md-6 col-sm-12', false: 'col-lg-4 col-md-6 col-sm-12' }[state.networkStatsUnavailable]">
<rd-widget>
<rd-widget-header icon="fa-chart-area" title-text="CPU usage"></rd-widget-header>
<rd-widget-body>
@ -85,9 +82,11 @@
<div class="col-sm-12" ng-if="applicationState.endpoint.mode.provider !== 'VMWARE_VIC'">
<container-processes-datatable
title-text="Processes" title-icon="fa-tasks"
dataset="processInfo.Processes" headerset="processInfo.Titles"
table-key="container-processes"
title-text="Processes"
title-icon="fa-tasks"
dataset="processInfo.Processes"
headerset="processInfo.Titles"
table-key="container-processes"
></container-processes-datatable>
</div>
</div>