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

refactor(app): introduce new project structure for the frontend (#1623)

This commit is contained in:
Anthony Lapenna 2018-02-01 13:27:52 +01:00 committed by GitHub
parent e6422a6d75
commit 27dceadba1
No known key found for this signature in database
GPG key ID: 4AEE18F83AFDEB23
354 changed files with 1518 additions and 1755 deletions

View file

@ -0,0 +1,285 @@
<rd-header>
<rd-header-title title="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>
</rd-header-content>
</rd-header>
<div class="row">
<div class="col-lg-12 col-md-12 col-xs-12">
<rd-widget>
<rd-widget-header icon="fa-cogs" title="Actions"></rd-widget-header>
<rd-widget-body classes="padding">
<div class="btn-group" role="group" aria-label="...">
<button 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 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 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 class="btn btn-primary btn-sm" ng-click="restart()" ng-disabled="!container.State.Running"><i class="fa fa-refresh space-right" aria-hidden="true"></i>Restart</button>
<button 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 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 class="btn btn-danger btn-sm" ng-click="confirmRemove()"><i class="fa fa-trash space-right" aria-hidden="true"></i>Remove</button>
</div>
<div class="btn-group" role="group" aria-label="...">
<button type="button" class="btn btn-danger btn-sm" ng-disabled="state.recreateContainerInProgress" ng-click="recreate()" button-spinner="state.recreateContainerInProgress" ng-if="!container.Config.Labels['com.docker.swarm.service.id']">
<span ng-hide="state.recreateContainerInProgress"><i class="fa fa-refresh space-right" aria-hidden="true"></i>Recreate</span>
<span ng-show="state.recreateContainerInProgress">Recreation in progress...</span>
</button>
<button class="btn btn-primary btn-sm" ng-click="duplicate()" ng-if="!container.Config.Labels['com.docker.swarm.service.id']"><i class="fa fa-files-o space-right" aria-hidden="true"></i>Duplicate/Edit</button>
</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="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 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-o"></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>
<td colspan="2">
<div class="btn-group" role="group" aria-label="...">
<a class="btn" type="button" ui-sref="docker.containers.container.stats({id: container.Id})"><i class="fa fa-area-chart space-right" aria-hidden="true"></i>Stats</a>
<a class="btn" type="button" ui-sref="docker.containers.container.logs({id: container.Id})"><i class="fa fa-exclamation-circle space-right" aria-hidden="true"></i>Logs</a>
<a class="btn" type="button" ui-sref="docker.containers.container.console({id: container.Id})"><i class="fa fa-terminal space-right" aria-hidden="true"></i>Console</a>
<a 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>
</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="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">
<div class="col-lg-12 col-md-12 col-xs-12">
<rd-widget>
<rd-widget-header icon="fa-clone" title="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>
<!-- !tag-description -->
<!-- image-and-registry -->
<div class="form-group">
<por-image-registry image="config.Image" registry="config.Registry"></por-image-registry>
</div>
<!-- !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.Image" ng-click="commit()">Create</button>
</div>
</div>
</form>
</rd-widget-body>
</rd-widget>
</div>
</div>
<div class="row">
<div class="col-lg-12 col-md-12 col-xs-12">
<rd-widget>
<rd-widget-header icon="fa-server" title="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})">{{ container.Image }}</a></td>
</tr>
<tr ng-if="portBindings.length > 0">
<td>Port configuration</td>
<td>
<div ng-repeat="portMapping in portBindings">
{{ portMapping.container }} <i class="fa fa-long-arrow-right"></i> {{ portMapping.host }}
</div>
</td>
</tr>
<tr>
<td>CMD</td>
<td><code>{{ container.Config.Cmd|command }}</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 ng-if="container.HostConfig.RestartPolicy.Name !== 'no'">
<td>Restart policies</td>
<td>
<table class="table table-bordered table-condensed">
<tr>
<td class="col-md-3">Name</td>
<td>{{ container.HostConfig.RestartPolicy.Name }}</td>
</tr>
<tr>
<td class="col-md-3">MaximumRetryCount</td>
<td>
{{ container.HostConfig.RestartPolicy.MaximumRetryCount }}
</td>
</tr>
</table>
</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="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})">{{ 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="Connected networks" title-icon="fa-sitemap"
dataset="container.NetworkSettings.Networks" table-key="container-networks"
container="container"
available-networks="availableNetworks"
join-network-action="containerJoinNetwork"
join-network-action-in-progress="state.joinNetworkInProgress"
leave-network-action="containerLeaveNetwork"
leave-network-action-in-progress="state.leaveNetworkInProgress"
></container-networks-datatable>
</div>
</div>

View file

@ -0,0 +1,289 @@
angular.module('portainer.docker')
.controller('ContainerController', ['$q', '$scope', '$state','$transition$', '$filter', 'Container', 'ContainerCommit', 'ContainerHelper', 'ContainerService', 'ImageHelper', 'Network', 'NetworkService', 'Notifications', 'ModalService', 'ResourceControlService', 'RegistryService', 'ImageService',
function ($q, $scope, $state, $transition$, $filter, Container, ContainerCommit, ContainerHelper, ContainerService, ImageHelper, Network, NetworkService, Notifications, ModalService, ResourceControlService, RegistryService, ImageService) {
$scope.activityTime = 0;
$scope.portBindings = [];
$scope.config = {
Image: '',
Registry: ''
};
$scope.state = {
recreateContainerInProgress: false,
joinNetworkInProgress: false,
leaveNetworkInProgress: false
};
var update = function () {
Container.get({id: $transition$.params().id}, function (d) {
var container = new ContainerDetailsViewModel(d);
$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);
}
});
}
}, function (e) {
Notifications.error('Failure', e, 'Unable to retrieve container info');
});
};
$scope.start = function () {
Container.start({id: $scope.container.Id}, {}, function (d) {
update();
Notifications.success('Container started', $transition$.params().id);
}, function (e) {
update();
Notifications.error('Failure', e, 'Unable to start container');
});
};
$scope.stop = function () {
Container.stop({id: $transition$.params().id}, function (d) {
update();
Notifications.success('Container stopped', $transition$.params().id);
}, function (e) {
update();
Notifications.error('Failure', e, 'Unable to stop container');
});
};
$scope.kill = function () {
Container.kill({id: $transition$.params().id}, function (d) {
update();
Notifications.success('Container killed', $transition$.params().id);
}, function (e) {
update();
Notifications.error('Failure', e, 'Unable to kill container');
});
};
$scope.commit = function () {
var image = $scope.config.Image;
var registry = $scope.config.Registry;
var imageConfig = ImageHelper.createImageConfigForCommit(image, registry.URL);
ContainerCommit.commit({id: $transition$.params().id, tag: imageConfig.tag, repo: imageConfig.repo}, function (d) {
update();
Notifications.success('Container commited', $transition$.params().id);
}, function (e) {
update();
Notifications.error('Failure', e, 'Unable to commit container');
});
};
$scope.pause = function () {
Container.pause({id: $transition$.params().id}, function (d) {
update();
Notifications.success('Container paused', $transition$.params().id);
}, function (e) {
update();
Notifications.error('Failure', e, 'Unable to pause container');
});
};
$scope.unpause = function () {
Container.unpause({id: $transition$.params().id}, function (d) {
update();
Notifications.success('Container unpaused', $transition$.params().id);
}, function (e) {
update();
Notifications.error('Failure', e, 'Unable to unpause container');
});
};
$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;
}
$scope.remove(cleanAssociatedVolumes);
}
);
};
$scope.remove = function(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');
});
};
$scope.restart = function () {
Container.restart({id: $transition$.params().id}, function (d) {
update();
Notifications.success('Container restarted', $transition$.params().id);
}, function (e) {
update();
Notifications.error('Failure', e, 'Unable to restart container');
});
};
$scope.renameContainer = function () {
var container = $scope.container;
Container.rename({id: $transition$.params().id, 'name': container.newContainerName}, function (d) {
if (d.message) {
container.newContainerName = container.Name;
Notifications.error('Unable to rename container', {}, d.message);
} else {
container.Name = container.newContainerName;
Notifications.success('Container successfully renamed', container.Name);
}
}, function (e) {
Notifications.error('Failure', e, 'Unable to rename container');
});
$scope.container.edit = false;
};
$scope.containerLeaveNetwork = function containerLeaveNetwork(container, networkId) {
$scope.state.leaveNetworkInProgress = true;
Network.disconnect({id: networkId}, { Container: $transition$.params().id, Force: false }, function (d) {
if (container.message) {
Notifications.error('Error', d, 'Unable to disconnect container from network');
} else {
Notifications.success('Container left network', $transition$.params().id);
$state.go('docker.containers.container', {id: $transition$.params().id}, {reload: true});
}
$scope.state.leaveNetworkInProgress = false;
}, function (e) {
Notifications.error('Failure', e, 'Unable to disconnect container from network');
$scope.state.leaveNetworkInProgress = false;
});
};
$scope.duplicate = function() {
$state.go('docker.containers.new', {from: $transition$.params().id}, {reload: true});
};
$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;
}
$scope.remove(cleanAssociatedVolumes);
}
);
};
function recreateContainer(pullImage) {
var container = $scope.container;
var config = ContainerHelper.configFromContainer(container.Model);
$scope.state.recreateContainerInProgress = true;
ContainerService.remove(container, true)
.then(function success() {
return RegistryService.retrieveRegistryFromRepository(container.Config.Image);
})
.then(function success(data) {
return $q.when(!pullImage || ImageService.pullImage(container.Config.Image, data, true));
})
.then(function success() {
return ContainerService.createAndStartContainer(config);
})
.then(function success(data) {
if (!container.ResourceControl) {
return true;
} else {
var containerIdentifier = data.Id;
var resourceControl = container.ResourceControl;
var users = resourceControl.UserAccesses.map(function(u) {
return u.UserId;
});
var teams = resourceControl.TeamAccesses.map(function(t) {
return t.TeamId;
});
return ResourceControlService.createResourceControl(resourceControl.AdministratorsOnly,
users, teams, containerIdentifier, 'container', []);
}
})
.then(function success(data) {
Notifications.success('Container successfully re-created');
$state.go('docker.containers', {}, {reload: true});
})
.catch(function error(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;
}
recreateContainer(pullImage);
});
};
$scope.containerJoinNetwork = function containerJoinNetwork(container, networkId) {
$scope.state.joinNetworkInProgress = true;
Network.connect({id: networkId}, { Container: $transition$.params().id }, function (d) {
if (container.message) {
Notifications.error('Error', d, 'Unable to connect container to network');
} else {
Notifications.success('Container joined network', $transition$.params().id);
$state.go('docker.containers.container', {id: $transition$.params().id}, {reload: true});
}
$scope.state.joinNetworkInProgress = false;
}, function (e) {
Notifications.error('Failure', e, 'Unable to connect container to network');
$scope.state.joinNetworkInProgress = false;
});
};
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,
provider === 'DOCKER_SWARM'
)
.then(function success(data) {
var networks = data;
$scope.availableNetworks = networks;
})
.catch(function error(err) {
Notifications.error('Failure', err, 'Unable to retrieve networks');
});
update();
}]);