mirror of
https://github.com/portainer/portainer.git
synced 2025-08-05 05:45:22 +02:00
refactor(app): introduce new project structure for the frontend (#1623)
This commit is contained in:
parent
e6422a6d75
commit
27dceadba1
354 changed files with 1518 additions and 1755 deletions
484
app/docker/__module.js
Normal file
484
app/docker/__module.js
Normal file
|
@ -0,0 +1,484 @@
|
|||
angular.module('portainer.docker', ['portainer.app'])
|
||||
.config(['$stateRegistryProvider', function ($stateRegistryProvider) {
|
||||
'use strict';
|
||||
|
||||
var docker = {
|
||||
name: 'docker',
|
||||
parent: 'root',
|
||||
abstract: true
|
||||
};
|
||||
|
||||
var configs = {
|
||||
name: 'docker.configs',
|
||||
url: '/configs',
|
||||
views: {
|
||||
'content@': {
|
||||
templateUrl: 'app/docker/views/configs/configs.html',
|
||||
controller: 'ConfigsController'
|
||||
}
|
||||
}
|
||||
};
|
||||
|
||||
var config = {
|
||||
name: 'docker.configs.config',
|
||||
url: '/:id',
|
||||
views: {
|
||||
'content@': {
|
||||
templateUrl: 'app/docker/views/configs/edit/config.html',
|
||||
controller: 'ConfigController'
|
||||
}
|
||||
}
|
||||
};
|
||||
|
||||
var configCreation = {
|
||||
name: 'docker.configs.new',
|
||||
url: '/new',
|
||||
views: {
|
||||
'content@': {
|
||||
templateUrl: 'app/docker/views/configs/create/createconfig.html',
|
||||
controller: 'CreateConfigController'
|
||||
}
|
||||
}
|
||||
};
|
||||
|
||||
var containers = {
|
||||
name: 'docker.containers',
|
||||
url: '/containers',
|
||||
views: {
|
||||
'content@': {
|
||||
templateUrl: 'app/docker/views/containers/containers.html',
|
||||
controller: 'ContainersController'
|
||||
}
|
||||
},
|
||||
params: {
|
||||
selectedContainers: []
|
||||
}
|
||||
};
|
||||
|
||||
var container = {
|
||||
name: 'docker.containers.container',
|
||||
url: '/:id',
|
||||
views: {
|
||||
'content@': {
|
||||
templateUrl: 'app/docker/views/containers/edit/container.html',
|
||||
controller: 'ContainerController'
|
||||
}
|
||||
}
|
||||
};
|
||||
|
||||
var containerConsole = {
|
||||
name: 'docker.containers.container.console',
|
||||
url: '/console',
|
||||
views: {
|
||||
'content@': {
|
||||
templateUrl: 'app/docker/views/containers/console/containerconsole.html',
|
||||
controller: 'ContainerConsoleController'
|
||||
}
|
||||
}
|
||||
};
|
||||
|
||||
var containerCreation = {
|
||||
name: 'docker.containers.new',
|
||||
url: '/new',
|
||||
views: {
|
||||
'content@': {
|
||||
templateUrl: 'app/docker/views/containers/create/createcontainer.html',
|
||||
controller: 'CreateContainerController'
|
||||
}
|
||||
},
|
||||
params: {
|
||||
from: ''
|
||||
}
|
||||
};
|
||||
|
||||
var containerInspect = {
|
||||
name: 'docker.containers.container.inspect',
|
||||
url: '/inspect',
|
||||
views: {
|
||||
'content@': {
|
||||
templateUrl: 'app/docker/views/containers/inspect/containerinspect.html',
|
||||
controller: 'ContainerInspectController'
|
||||
}
|
||||
}
|
||||
};
|
||||
|
||||
var containerLogs = {
|
||||
name: 'docker.containers.container.logs',
|
||||
url: '/logs',
|
||||
views: {
|
||||
'content@': {
|
||||
templateUrl: 'app/docker/views/containers/logs/containerlogs.html',
|
||||
controller: 'ContainerLogsController'
|
||||
}
|
||||
}
|
||||
};
|
||||
|
||||
var containerStats = {
|
||||
name: 'docker.containers.container.stats',
|
||||
url: '/stats',
|
||||
views: {
|
||||
'content@': {
|
||||
templateUrl: 'app/docker/views/containers/stats/containerstats.html',
|
||||
controller: 'ContainerStatsController'
|
||||
}
|
||||
}
|
||||
};
|
||||
|
||||
var dashboard = {
|
||||
name: 'docker.dashboard',
|
||||
url: '/dashboard',
|
||||
views: {
|
||||
'content@': {
|
||||
templateUrl: 'app/docker/views/dashboard/dashboard.html',
|
||||
controller: 'DashboardController'
|
||||
}
|
||||
}
|
||||
};
|
||||
|
||||
var engine = {
|
||||
name: 'docker.engine',
|
||||
url: '/engine',
|
||||
views: {
|
||||
'content@': {
|
||||
templateUrl: 'app/docker/views/engine/engine.html',
|
||||
controller: 'EngineController'
|
||||
}
|
||||
}
|
||||
};
|
||||
|
||||
var events = {
|
||||
name: 'docker.events',
|
||||
url: '/events',
|
||||
views: {
|
||||
'content@': {
|
||||
templateUrl: 'app/docker/views/events/events.html',
|
||||
controller: 'EventsController'
|
||||
}
|
||||
}
|
||||
};
|
||||
|
||||
var images = {
|
||||
name: 'docker.images',
|
||||
url: '/images',
|
||||
views: {
|
||||
'content@': {
|
||||
templateUrl: 'app/docker/views/images/images.html',
|
||||
controller: 'ImagesController'
|
||||
}
|
||||
}
|
||||
};
|
||||
|
||||
var image = {
|
||||
name: 'docker.images.image',
|
||||
url: '/:id',
|
||||
views: {
|
||||
'content@': {
|
||||
templateUrl: 'app/docker/views/images/edit/image.html',
|
||||
controller: 'ImageController'
|
||||
}
|
||||
}
|
||||
};
|
||||
|
||||
var networks = {
|
||||
name: 'docker.networks',
|
||||
url: '/networks',
|
||||
views: {
|
||||
'content@': {
|
||||
templateUrl: 'app/docker/views/networks/networks.html',
|
||||
controller: 'NetworksController'
|
||||
}
|
||||
}
|
||||
};
|
||||
|
||||
var network = {
|
||||
name: 'docker.networks.network',
|
||||
url: '/:id',
|
||||
views: {
|
||||
'content@': {
|
||||
templateUrl: 'app/docker/views/networks/edit/network.html',
|
||||
controller: 'NetworkController'
|
||||
}
|
||||
}
|
||||
};
|
||||
|
||||
var networkCreation = {
|
||||
name: 'docker.networks.new',
|
||||
url: '/new',
|
||||
views: {
|
||||
'content@': {
|
||||
templateUrl: 'app/docker/views/networks/create/createnetwork.html',
|
||||
controller: 'CreateNetworkController'
|
||||
}
|
||||
}
|
||||
};
|
||||
|
||||
var nodes = {
|
||||
name: 'docker.nodes',
|
||||
url: '/nodes',
|
||||
abstract: true
|
||||
};
|
||||
|
||||
var node = {
|
||||
name: 'docker.nodes.node',
|
||||
url: '/:id',
|
||||
views: {
|
||||
'content@': {
|
||||
templateUrl: 'app/docker/views/nodes/edit/node.html',
|
||||
controller: 'NodeController'
|
||||
}
|
||||
}
|
||||
};
|
||||
|
||||
var secrets = {
|
||||
name: 'docker.secrets',
|
||||
url: '/secrets',
|
||||
views: {
|
||||
'content@': {
|
||||
templateUrl: 'app/docker/views/secrets/secrets.html',
|
||||
controller: 'SecretsController'
|
||||
}
|
||||
}
|
||||
};
|
||||
|
||||
var secret = {
|
||||
name: 'docker.secrets.secret',
|
||||
url: '/:id',
|
||||
views: {
|
||||
'content@': {
|
||||
templateUrl: 'app/docker/views/secrets/edit/secret.html',
|
||||
controller: 'SecretController'
|
||||
}
|
||||
}
|
||||
};
|
||||
|
||||
var secretCreation = {
|
||||
name: 'docker.secrets.new',
|
||||
url: '/new',
|
||||
views: {
|
||||
'content@': {
|
||||
templateUrl: 'app/docker/views/secrets/create/createsecret.html',
|
||||
controller: 'CreateSecretController'
|
||||
}
|
||||
}
|
||||
};
|
||||
|
||||
var services = {
|
||||
name: 'docker.services',
|
||||
url: '/services',
|
||||
views: {
|
||||
'content@': {
|
||||
templateUrl: 'app/docker/views/services/services.html',
|
||||
controller: 'ServicesController'
|
||||
}
|
||||
}
|
||||
};
|
||||
|
||||
var service = {
|
||||
name: 'docker.services.service',
|
||||
url: '/:id',
|
||||
views: {
|
||||
'content@': {
|
||||
templateUrl: 'app/docker/views/services/edit/service.html',
|
||||
controller: 'ServiceController'
|
||||
}
|
||||
}
|
||||
};
|
||||
|
||||
var serviceCreation = {
|
||||
name: 'docker.services.new',
|
||||
url: '/new',
|
||||
views: {
|
||||
'content@': {
|
||||
templateUrl: 'app/docker/views/services/create/createservice.html',
|
||||
controller: 'CreateServiceController'
|
||||
}
|
||||
}
|
||||
};
|
||||
|
||||
var serviceLogs = {
|
||||
name: 'docker.services.service.logs',
|
||||
url: '/logs',
|
||||
views: {
|
||||
'content@': {
|
||||
templateUrl: 'app/docker/views/services/logs/servicelogs.html',
|
||||
controller: 'ServiceLogsController'
|
||||
}
|
||||
}
|
||||
};
|
||||
|
||||
var stacks = {
|
||||
name: 'docker.stacks',
|
||||
url: '/stacks',
|
||||
views: {
|
||||
'content@': {
|
||||
templateUrl: 'app/docker/views/stacks/stacks.html',
|
||||
controller: 'StacksController'
|
||||
}
|
||||
}
|
||||
};
|
||||
|
||||
var stack = {
|
||||
name: 'docker.stacks.stack',
|
||||
url: '/:id',
|
||||
views: {
|
||||
'content@': {
|
||||
templateUrl: 'app/docker/views/stacks/edit/stack.html',
|
||||
controller: 'StackController'
|
||||
}
|
||||
}
|
||||
};
|
||||
|
||||
var stackCreation = {
|
||||
name: 'docker.stacks.new',
|
||||
url: '/new',
|
||||
views: {
|
||||
'content@': {
|
||||
templateUrl: 'app/docker/views/stacks/create/createstack.html',
|
||||
controller: 'CreateStackController'
|
||||
}
|
||||
}
|
||||
};
|
||||
|
||||
var swarm = {
|
||||
name: 'docker.swarm',
|
||||
url: '/swarm',
|
||||
views: {
|
||||
'content@': {
|
||||
templateUrl: 'app/docker/views/swarm/swarm.html',
|
||||
controller: 'SwarmController'
|
||||
}
|
||||
}
|
||||
};
|
||||
|
||||
var swarmVisualizer = {
|
||||
name: 'docker.swarm.visualizer',
|
||||
url: '/visualizer',
|
||||
views: {
|
||||
'content@': {
|
||||
templateUrl: 'app/docker/views/swarm/visualizer/swarmvisualizer.html',
|
||||
controller: 'SwarmVisualizerController'
|
||||
}
|
||||
}
|
||||
};
|
||||
|
||||
var tasks = {
|
||||
name: 'docker.tasks',
|
||||
url: '/tasks',
|
||||
abstract: true
|
||||
};
|
||||
|
||||
var task = {
|
||||
name: 'docker.tasks.task',
|
||||
url: '/:id',
|
||||
views: {
|
||||
'content@': {
|
||||
templateUrl: 'app/docker/views/tasks/edit/task.html',
|
||||
controller: 'TaskController'
|
||||
}
|
||||
}
|
||||
};
|
||||
|
||||
var templates = {
|
||||
name: 'docker.templates',
|
||||
url: '/templates',
|
||||
views: {
|
||||
'content@': {
|
||||
templateUrl: 'app/docker/views/templates/templates.html',
|
||||
controller: 'TemplatesController'
|
||||
}
|
||||
},
|
||||
params: {
|
||||
key: 'containers',
|
||||
hide_descriptions: false
|
||||
}
|
||||
};
|
||||
|
||||
var templatesLinuxServer = {
|
||||
name: 'docker.templates.linuxserver',
|
||||
url: '/linuxserver',
|
||||
views: {
|
||||
'content@': {
|
||||
templateUrl: 'app/docker/views/templates/templates.html',
|
||||
controller: 'TemplatesController'
|
||||
}
|
||||
},
|
||||
params: {
|
||||
key: 'linuxserver.io',
|
||||
hide_descriptions: true
|
||||
}
|
||||
};
|
||||
|
||||
var volumes = {
|
||||
name: 'docker.volumes',
|
||||
url: '/volumes',
|
||||
views: {
|
||||
'content@': {
|
||||
templateUrl: 'app/docker/views/volumes/volumes.html',
|
||||
controller: 'VolumesController'
|
||||
}
|
||||
}
|
||||
};
|
||||
|
||||
var volume = {
|
||||
name: 'docker.volumes.volume',
|
||||
url: '/:id',
|
||||
views: {
|
||||
'content@': {
|
||||
templateUrl: 'app/docker/views/volumes/edit/volume.html',
|
||||
controller: 'VolumeController'
|
||||
}
|
||||
}
|
||||
};
|
||||
|
||||
var volumeCreation = {
|
||||
name: 'docker.volumes.new',
|
||||
url: '/new',
|
||||
views: {
|
||||
'content@': {
|
||||
templateUrl: 'app/docker/views/volumes/create/createvolume.html',
|
||||
controller: 'CreateVolumeController'
|
||||
}
|
||||
}
|
||||
};
|
||||
|
||||
$stateRegistryProvider.register(configs);
|
||||
$stateRegistryProvider.register(config);
|
||||
$stateRegistryProvider.register(configCreation);
|
||||
$stateRegistryProvider.register(containers);
|
||||
$stateRegistryProvider.register(container);
|
||||
$stateRegistryProvider.register(containerConsole);
|
||||
$stateRegistryProvider.register(containerCreation);
|
||||
$stateRegistryProvider.register(containerInspect);
|
||||
$stateRegistryProvider.register(containerLogs);
|
||||
$stateRegistryProvider.register(containerStats);
|
||||
$stateRegistryProvider.register(docker);
|
||||
$stateRegistryProvider.register(dashboard);
|
||||
$stateRegistryProvider.register(engine);
|
||||
$stateRegistryProvider.register(events);
|
||||
$stateRegistryProvider.register(images);
|
||||
$stateRegistryProvider.register(image);
|
||||
$stateRegistryProvider.register(networks);
|
||||
$stateRegistryProvider.register(network);
|
||||
$stateRegistryProvider.register(networkCreation);
|
||||
$stateRegistryProvider.register(nodes);
|
||||
$stateRegistryProvider.register(node);
|
||||
$stateRegistryProvider.register(secrets);
|
||||
$stateRegistryProvider.register(secret);
|
||||
$stateRegistryProvider.register(secretCreation);
|
||||
$stateRegistryProvider.register(services);
|
||||
$stateRegistryProvider.register(service);
|
||||
$stateRegistryProvider.register(serviceCreation);
|
||||
$stateRegistryProvider.register(serviceLogs);
|
||||
$stateRegistryProvider.register(stacks);
|
||||
$stateRegistryProvider.register(stack);
|
||||
$stateRegistryProvider.register(stackCreation);
|
||||
$stateRegistryProvider.register(swarm);
|
||||
$stateRegistryProvider.register(swarmVisualizer);
|
||||
$stateRegistryProvider.register(tasks);
|
||||
$stateRegistryProvider.register(task);
|
||||
$stateRegistryProvider.register(templates);
|
||||
$stateRegistryProvider.register(templatesLinuxServer);
|
||||
$stateRegistryProvider.register(volumes);
|
||||
$stateRegistryProvider.register(volume);
|
||||
$stateRegistryProvider.register(volumeCreation);
|
||||
}]);
|
|
@ -0,0 +1,108 @@
|
|||
<div class="datatable">
|
||||
<rd-widget>
|
||||
<rd-widget-body classes="no-padding">
|
||||
<div class="toolBar">
|
||||
<div class="toolBarTitle">
|
||||
<i class="fa" ng-class="$ctrl.titleIcon" aria-hidden="true" style="margin-right: 2px;"></i> {{ $ctrl.title }}
|
||||
</div>
|
||||
<div class="settings">
|
||||
<span class="setting" ng-class="{ 'setting-active': $ctrl.state.displayTextFilter }" ng-click="$ctrl.updateDisplayTextFilter()" ng-if="$ctrl.showTextFilter">
|
||||
<i class="fa fa-search" aria-hidden="true"></i> Search
|
||||
</span>
|
||||
</div>
|
||||
</div>
|
||||
<div class="actionBar">
|
||||
<button type="button" class="btn btn-sm btn-danger"
|
||||
ng-disabled="$ctrl.state.selectedItemCount === 0" ng-click="$ctrl.removeAction($ctrl.state.selectedItems)">
|
||||
<i class="fa fa-trash space-right" aria-hidden="true"></i>Remove
|
||||
</button>
|
||||
<button type="button" class="btn btn-sm btn-primary" ui-sref="docker.configs.new">
|
||||
<i class="fa fa-plus space-right" aria-hidden="true"></i>Add config
|
||||
</button>
|
||||
</div>
|
||||
<div class="searchBar" ng-if="$ctrl.state.displayTextFilter">
|
||||
<i class="fa fa-search searchIcon" aria-hidden="true"></i>
|
||||
<input type="text" class="searchInput" ng-model="$ctrl.state.textFilter" placeholder="Search...">
|
||||
</div>
|
||||
<div class="table-responsive">
|
||||
<table class="table table-hover">
|
||||
<thead>
|
||||
<tr>
|
||||
<th>
|
||||
<span class="md-checkbox">
|
||||
<input id="select_all" type="checkbox" ng-model="$ctrl.state.selectAll" ng-change="$ctrl.selectAll()" />
|
||||
<label for="select_all"></label>
|
||||
</span>
|
||||
<a ng-click="$ctrl.changeOrderBy('Name')">
|
||||
Name
|
||||
<i class="fa fa-sort-alpha-asc" aria-hidden="true" ng-if="$ctrl.state.orderBy === 'Name' && !$ctrl.state.reverseOrder"></i>
|
||||
<i class="fa fa-sort-alpha-desc" aria-hidden="true" ng-if="$ctrl.state.orderBy === 'Name' && $ctrl.state.reverseOrder"></i>
|
||||
</a>
|
||||
</th>
|
||||
<th>
|
||||
<a ng-click="$ctrl.changeOrderBy('CreatedAt')">
|
||||
Creation Date
|
||||
<i class="fa fa-sort-alpha-asc" aria-hidden="true" ng-if="$ctrl.state.orderBy === 'CreatedAt' && !$ctrl.state.reverseOrder"></i>
|
||||
<i class="fa fa-sort-alpha-desc" aria-hidden="true" ng-if="$ctrl.state.orderBy === 'CreatedAt' && $ctrl.state.reverseOrder"></i>
|
||||
</a>
|
||||
</th>
|
||||
<th ng-if="$ctrl.showOwnershipColumn">
|
||||
<a ng-click="$ctrl.changeOrderBy('ResourceControl.Ownership')">
|
||||
Ownership
|
||||
<i class="fa fa-sort-alpha-asc" aria-hidden="true" ng-if="$ctrl.state.orderBy === 'ResourceControl.Ownership' && !$ctrl.state.reverseOrder"></i>
|
||||
<i class="fa fa-sort-alpha-desc" aria-hidden="true" ng-if="$ctrl.state.orderBy === 'ResourceControl.Ownership' && $ctrl.state.reverseOrder"></i>
|
||||
</a>
|
||||
</th>
|
||||
</tr>
|
||||
</thead>
|
||||
<tbody>
|
||||
<tr dir-paginate="item in ($ctrl.state.filteredDataSet = ($ctrl.dataset | filter:$ctrl.state.textFilter | orderBy:$ctrl.state.orderBy:$ctrl.state.reverseOrder | itemsPerPage: $ctrl.state.paginatedItemLimit))" ng-class="{active: item.Checked}">
|
||||
<td>
|
||||
<span class="md-checkbox">
|
||||
<input id="select_{{ $index }}" type="checkbox" ng-model="item.Checked" ng-change="$ctrl.selectItem(item)"/>
|
||||
<label for="select_{{ $index }}"></label>
|
||||
</span>
|
||||
<a ui-sref="docker.configs.config({id: item.Id})">{{ item.Name }}</a>
|
||||
</td>
|
||||
<td>{{ item.CreatedAt | getisodate }}</td>
|
||||
<td ng-if="$ctrl.showOwnershipColumn">
|
||||
<span>
|
||||
<i ng-class="item.ResourceControl.Ownership | ownershipicon" aria-hidden="true"></i>
|
||||
{{ item.ResourceControl.Ownership ? item.ResourceControl.Ownership : item.ResourceControl.Ownership = 'public' }}
|
||||
</span>
|
||||
</td>
|
||||
</tr>
|
||||
<tr ng-if="!$ctrl.dataset">
|
||||
<td colspan="3" class="text-center text-muted">Loading...</td>
|
||||
</tr>
|
||||
<tr ng-if="$ctrl.state.filteredDataSet.length === 0">
|
||||
<td colspan="3" class="text-center text-muted">No config available.</td>
|
||||
</tr>
|
||||
</tbody>
|
||||
</table>
|
||||
</div>
|
||||
<div class="footer" ng-if="$ctrl.dataset">
|
||||
<div class="infoBar" ng-if="$ctrl.state.selectedItemCount !== 0">
|
||||
{{ $ctrl.state.selectedItemCount }} item(s) selected
|
||||
</div>
|
||||
<div class="paginationControls">
|
||||
<form class="form-inline">
|
||||
<span class="limitSelector">
|
||||
<span style="margin-right: 5px;">
|
||||
Items per page
|
||||
</span>
|
||||
<select class="form-control" ng-model="$ctrl.state.paginatedItemLimit" ng-change="$ctrl.changePaginationLimit()">
|
||||
<option value="0">All</option>
|
||||
<option value="10">10</option>
|
||||
<option value="25">25</option>
|
||||
<option value="50">50</option>
|
||||
<option value="100">100</option>
|
||||
</select>
|
||||
</span>
|
||||
<dir-pagination-controls max-size="5"></dir-pagination-controls>
|
||||
</form>
|
||||
</div>
|
||||
</div>
|
||||
</rd-widget-body>
|
||||
</rd-widget>
|
||||
</div>
|
|
@ -0,0 +1,15 @@
|
|||
angular.module('portainer.docker').component('configsDatatable', {
|
||||
templateUrl: 'app/docker/components/datatables/configs-datatable/configsDatatable.html',
|
||||
controller: 'GenericDatatableController',
|
||||
bindings: {
|
||||
title: '@',
|
||||
titleIcon: '@',
|
||||
dataset: '<',
|
||||
tableKey: '@',
|
||||
orderBy: '@',
|
||||
reverseOrder: '<',
|
||||
showTextFilter: '<',
|
||||
showOwnershipColumn: '<',
|
||||
removeAction: '<'
|
||||
}
|
||||
});
|
|
@ -0,0 +1,82 @@
|
|||
<div class="datatable">
|
||||
<rd-widget>
|
||||
<rd-widget-body classes="no-padding">
|
||||
<div class="toolBar">
|
||||
<div class="toolBarTitle">
|
||||
<i class="fa" ng-class="$ctrl.titleIcon" aria-hidden="true" style="margin-right: 2px;"></i> {{ $ctrl.title }}
|
||||
</div>
|
||||
</div>
|
||||
<div class="actionBar">
|
||||
<form class="form-horizontal">
|
||||
<div class="row">
|
||||
<label for="container_network" class="col-sm-3 col-lg-2 control-label text-left">Join a network</label>
|
||||
<div class="col-sm-5 col-lg-4">
|
||||
<select class="form-control" ng-model="$ctrl.selectedNetwork" id="container_network">
|
||||
<option selected disabled hidden value="">Select a network</option>
|
||||
<option ng-repeat="net in $ctrl.availableNetworks" ng-value="net.Id">{{ net.Name }}</option>
|
||||
</select>
|
||||
</div>
|
||||
<div class="col-sm-1">
|
||||
<button type="button" class="btn btn-primary btn-sm" ng-disabled="$ctrl.joinNetworkActionInProgress || !$ctrl.selectedNetwork" ng-click="$ctrl.joinNetworkAction($ctrl.container, $ctrl.selectedNetwork)" button-spinner="$ctrl.joinNetworkActionInProgress">
|
||||
<span ng-hide="$ctrl.joinNetworkActionInProgress">Join network</span>
|
||||
<span ng-show="$ctrl.joinNetworkActionInProgress">Joining network...</span>
|
||||
</button>
|
||||
</div>
|
||||
</div>
|
||||
</form>
|
||||
</div>
|
||||
<div class="table-responsive">
|
||||
<table class="table table-hover">
|
||||
<thead>
|
||||
<tr>
|
||||
<th>Network</th>
|
||||
<th>IP Address</th>
|
||||
<th>Gateway</th>
|
||||
<th>MAC Address</th>
|
||||
<th>Actions</th>
|
||||
</tr>
|
||||
</thead>
|
||||
<tbody>
|
||||
<tr dir-paginate="(key, value) in $ctrl.dataset | itemsPerPage: $ctrl.state.paginatedItemLimit" ng-class="{active: item.Checked}">
|
||||
<td><a ui-sref="docker.networks.network({id: value.NetworkID})">{{ key }}</a></td>
|
||||
<td>{{ value.IPAddress || '-' }}</td>
|
||||
<td>{{ value.Gateway || '-' }}</td>
|
||||
<td>{{ value.MacAddress || '-' }}</td>
|
||||
<td>
|
||||
<button type="button" class="btn btn-xs btn-danger" ng-disabled="$ctrl.leaveNetworkActionInProgress" button-spinner="$ctrl.leaveNetworkActionInProgress" ng-click="$ctrl.leaveNetworkAction($ctrl.container, value.NetworkID)">
|
||||
<span ng-hide="$ctrl.leaveNetworkActionInProgress"><i class="fa fa-trash space-right" aria-hidden="true"></i> Leave network</span>
|
||||
<span ng-show="$ctrl.leaveNetworkActionInProgress">Leaving network...</span>
|
||||
</button>
|
||||
</td>
|
||||
</tr>
|
||||
<tr ng-if="!$ctrl.dataset">
|
||||
<td colspan="5" class="text-center text-muted">Loading...</td>
|
||||
</tr>
|
||||
<tr ng-if="$ctrl.dataset.length === 0">
|
||||
<td colspan="5" class="text-center text-muted">No network available.</td>
|
||||
</tr>
|
||||
</tbody>
|
||||
</table>
|
||||
</div>
|
||||
<div class="footer" ng-if="$ctrl.dataset">
|
||||
<div class="paginationControls">
|
||||
<form class="form-inline">
|
||||
<span class="limitSelector">
|
||||
<span style="margin-right: 5px;">
|
||||
Items per page
|
||||
</span>
|
||||
<select class="form-control" ng-model="$ctrl.state.paginatedItemLimit" ng-change="$ctrl.changePaginationLimit()">
|
||||
<option value="0">All</option>
|
||||
<option value="10">10</option>
|
||||
<option value="25">25</option>
|
||||
<option value="50">50</option>
|
||||
<option value="100">100</option>
|
||||
</select>
|
||||
</span>
|
||||
<dir-pagination-controls max-size="5"></dir-pagination-controls>
|
||||
</form>
|
||||
</div>
|
||||
</div>
|
||||
</rd-widget-body>
|
||||
</rd-widget>
|
||||
</div>
|
|
@ -0,0 +1,16 @@
|
|||
angular.module('portainer.docker').component('containerNetworksDatatable', {
|
||||
templateUrl: 'app/docker/components/datatables/container-networks-datatable/containerNetworksDatatable.html',
|
||||
controller: 'GenericDatatableController',
|
||||
bindings: {
|
||||
title: '@',
|
||||
titleIcon: '@',
|
||||
dataset: '<',
|
||||
tableKey: '@',
|
||||
container: '<',
|
||||
availableNetworks: '<',
|
||||
joinNetworkAction: '<',
|
||||
joinNetworkActionInProgress: '<',
|
||||
leaveNetworkActionInProgress: '<',
|
||||
leaveNetworkAction: '<'
|
||||
}
|
||||
});
|
|
@ -0,0 +1,61 @@
|
|||
<div class="datatable">
|
||||
<rd-widget>
|
||||
<rd-widget-body classes="no-padding">
|
||||
<div class="toolBar">
|
||||
<div class="toolBarTitle">
|
||||
<i class="fa" ng-class="$ctrl.titleIcon" aria-hidden="true" style="margin-right: 2px;"></i> {{ $ctrl.title }}
|
||||
</div>
|
||||
<div class="settings">
|
||||
<span class="setting" ng-class="{ 'setting-active': $ctrl.state.displayTextFilter }" ng-click="$ctrl.updateDisplayTextFilter()" ng-if="$ctrl.showTextFilter">
|
||||
<i class="fa fa-search" aria-hidden="true"></i> Search
|
||||
</span>
|
||||
</div>
|
||||
</div>
|
||||
<div class="searchBar" ng-if="$ctrl.state.displayTextFilter">
|
||||
<i class="fa fa-search searchIcon" aria-hidden="true"></i>
|
||||
<input type="text" class="searchInput" ng-model="$ctrl.state.textFilter" placeholder="Search...">
|
||||
</div>
|
||||
<div class="table-responsive">
|
||||
<table class="table table-hover">
|
||||
<thead>
|
||||
<tr>
|
||||
<th ng-repeat="header in $ctrl.headerset">
|
||||
{{ header }}
|
||||
</th>
|
||||
</tr>
|
||||
</thead>
|
||||
<tbody>
|
||||
<tr dir-paginate="item in ($ctrl.state.filteredProcesses = ($ctrl.dataset | filter:$ctrl.state.textFilter | itemsPerPage: $ctrl.state.paginatedItemLimit))">
|
||||
<td ng-repeat="info in item track by $index">{{ info }}</td>
|
||||
</tr>
|
||||
<tr ng-if="!$ctrl.dataset">
|
||||
<td colspan="{{ $ctrl.headerset.length }}" class="text-center text-muted">Loading...</td>
|
||||
</tr>
|
||||
<tr ng-if="$ctrl.state.filteredProcesses.length === 0">
|
||||
<td colspan="{{ $ctrl.headerset.length }}" class="text-center text-muted">No process available.</td>
|
||||
</tr>
|
||||
</tbody>
|
||||
</table>
|
||||
</div>
|
||||
<div class="footer" ng-if="$ctrl.dataset">
|
||||
<div class="paginationControls">
|
||||
<form class="form-inline">
|
||||
<span class="limitSelector">
|
||||
<span style="margin-right: 5px;">
|
||||
Items per page
|
||||
</span>
|
||||
<select class="form-control" ng-model="$ctrl.state.paginatedItemLimit" ng-change="$ctrl.changePaginationLimit()">
|
||||
<option value="0">All</option>
|
||||
<option value="10">10</option>
|
||||
<option value="25">25</option>
|
||||
<option value="50">50</option>
|
||||
<option value="100">100</option>
|
||||
</select>
|
||||
</span>
|
||||
<dir-pagination-controls max-size="5"></dir-pagination-controls>
|
||||
</form>
|
||||
</div>
|
||||
</div>
|
||||
</rd-widget-body>
|
||||
</rd-widget>
|
||||
</div>
|
|
@ -0,0 +1,14 @@
|
|||
angular.module('portainer.docker').component('containerProcessesDatatable', {
|
||||
templateUrl: 'app/docker/components/datatables/container-processes-datatable/containerProcessesDatatable.html',
|
||||
controller: 'GenericDatatableController',
|
||||
bindings: {
|
||||
title: '@',
|
||||
titleIcon: '@',
|
||||
dataset: '=',
|
||||
headerset: '<',
|
||||
tableKey: '@',
|
||||
orderBy: '@',
|
||||
reverseOrder: '<',
|
||||
showTextFilter: '<'
|
||||
}
|
||||
});
|
|
@ -0,0 +1,254 @@
|
|||
<div class="datatable">
|
||||
<rd-widget>
|
||||
<rd-widget-body classes="no-padding">
|
||||
<div class="toolBar">
|
||||
<div class="toolBarTitle">
|
||||
<i class="fa" ng-class="$ctrl.titleIcon" aria-hidden="true" style="margin-right: 2px;"></i> {{ $ctrl.title }}
|
||||
</div>
|
||||
<div class="settings">
|
||||
<span class="setting" ng-class="{ 'setting-active': $ctrl.state.displayTextFilter }" ng-click="$ctrl.updateDisplayTextFilter()" ng-if="$ctrl.showTextFilter">
|
||||
<i class="fa fa-search" aria-hidden="true"></i> Search
|
||||
</span>
|
||||
<span class="setting" ng-class="{ 'setting-active': $ctrl.settings.open }" uib-dropdown dropdown-append-to-body auto-close="disabled" is-open="$ctrl.settings.open">
|
||||
<span uib-dropdown-toggle><i class="fa fa-cog" aria-hidden="true"></i> Settings</span>
|
||||
<div class="dropdown-menu dropdown-menu-right" uib-dropdown-menu>
|
||||
<div class="tableMenu">
|
||||
<div class="menuHeader">
|
||||
Table settings
|
||||
</div>
|
||||
<div class="menuContent">
|
||||
<div class="md-checkbox">
|
||||
<input id="setting_container_trunc" type="checkbox" ng-model="$ctrl.settings.truncateContainerName" ng-change="$ctrl.onSettingsContainerNameTruncateChange()"/>
|
||||
<label for="setting_container_trunc">Truncate container name</label>
|
||||
</div>
|
||||
</div>
|
||||
<div class="menuHeader">
|
||||
Quick actions
|
||||
</div>
|
||||
<div class="menuContent">
|
||||
<div class="md-checkbox">
|
||||
<input id="setting_show_stats" type="checkbox" ng-model="$ctrl.settings.showQuickActionStats" ng-change="$ctrl.onSettingsQuickActionChange()"/>
|
||||
<label for="setting_show_stats">Stats</label>
|
||||
</div>
|
||||
<div class="md-checkbox">
|
||||
<input id="setting_show_logs" type="checkbox" ng-model="$ctrl.settings.showQuickActionLogs" ng-change="$ctrl.onSettingsQuickActionChange()"/>
|
||||
<label for="setting_show_logs">Logs</label>
|
||||
</div>
|
||||
<div class="md-checkbox">
|
||||
<input id="setting_show_console" type="checkbox" ng-model="$ctrl.settings.showQuickActionConsole" ng-change="$ctrl.onSettingsQuickActionChange()"/>
|
||||
<label for="setting_show_console">Console</label>
|
||||
</div>
|
||||
<div class="md-checkbox">
|
||||
<input id="setting_show_inspect" type="checkbox" ng-model="$ctrl.settings.showQuickActionInspect" ng-change="$ctrl.onSettingsQuickActionChange()"/>
|
||||
<label for="setting_show_inspect">Inspect</label>
|
||||
</div>
|
||||
</div>
|
||||
<div>
|
||||
<a type="button" class="btn btn-default btn-sm" ng-click="$ctrl.settings.open = false;">Close</a>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</span>
|
||||
</div>
|
||||
</div>
|
||||
<div class="actionBar">
|
||||
<div class="btn-group" role="group" aria-label="...">
|
||||
<button type="button" class="btn btn-sm btn-success" ng-click="$ctrl.startAction($ctrl.state.selectedItems)"
|
||||
ng-disabled="$ctrl.state.selectedItemCount === 0 || $ctrl.state.noStoppedItemsSelected">
|
||||
<i class="fa fa-play space-right" aria-hidden="true"></i>Start
|
||||
</button>
|
||||
<button type="button" class="btn btn-sm btn-danger" ng-click="$ctrl.stopAction($ctrl.state.selectedItems)"
|
||||
ng-disabled="$ctrl.state.selectedItemCount === 0 || $ctrl.state.noRunningItemsSelected">
|
||||
<i class="fa fa-stop space-right" aria-hidden="true"></i>Stop
|
||||
</button>
|
||||
<button type="button" class="btn btn-sm btn-danger" ng-click="$ctrl.killAction($ctrl.state.selectedItems)"
|
||||
ng-disabled="$ctrl.state.selectedItemCount === 0">
|
||||
<i class="fa fa-bomb space-right" aria-hidden="true"></i>Kill
|
||||
</button>
|
||||
<button type="button" class="btn btn-sm btn-primary" ng-click="$ctrl.restartAction($ctrl.state.selectedItems)"
|
||||
ng-disabled="$ctrl.state.selectedItemCount === 0">
|
||||
<i class="fa fa-refresh space-right" aria-hidden="true"></i>Restart
|
||||
</button>
|
||||
<button type="button" class="btn btn-sm btn-primary" ng-click="$ctrl.pauseAction($ctrl.state.selectedItems)"
|
||||
ng-disabled="$ctrl.state.selectedItemCount === 0 || $ctrl.state.noRunningItemsSelected">
|
||||
<i class="fa fa-pause space-right" aria-hidden="true"></i>Pause
|
||||
</button>
|
||||
<button type="button" class="btn btn-sm btn-primary" ng-click="$ctrl.resumeAction($ctrl.state.selectedItems)"
|
||||
ng-disabled="$ctrl.state.selectedItemCount === 0 || $ctrl.state.noPausedItemsSelected">
|
||||
<i class="fa fa-play space-right" aria-hidden="true"></i>Resume
|
||||
</button>
|
||||
<button type="button" class="btn btn-sm btn-danger"
|
||||
ng-disabled="$ctrl.state.selectedItemCount === 0" ng-click="$ctrl.removeAction($ctrl.state.selectedItems)">
|
||||
<i class="fa fa-trash space-right" aria-hidden="true"></i>Remove
|
||||
</button>
|
||||
</div>
|
||||
<button type="button" class="btn btn-sm btn-primary" ui-sref="docker.containers.new">
|
||||
<i class="fa fa-plus space-right" aria-hidden="true"></i>Add container
|
||||
</button>
|
||||
</div>
|
||||
<div class="searchBar" ng-if="$ctrl.state.displayTextFilter">
|
||||
<i class="fa fa-search searchIcon" aria-hidden="true"></i>
|
||||
<input type="text" class="searchInput" ng-model="$ctrl.state.textFilter" placeholder="Search...">
|
||||
</div>
|
||||
<div class="table-responsive">
|
||||
<table class="table table-hover table-filters">
|
||||
<thead>
|
||||
<tr>
|
||||
<th>
|
||||
<span class="md-checkbox">
|
||||
<input id="select_all" type="checkbox" ng-model="$ctrl.state.selectAll" ng-change="$ctrl.selectAll()" />
|
||||
<label for="select_all"></label>
|
||||
</span>
|
||||
<a ng-click="$ctrl.changeOrderBy('Names')">
|
||||
Name
|
||||
<i class="fa fa-sort-alpha-asc" aria-hidden="true" ng-if="$ctrl.state.orderBy === 'Names' && !$ctrl.state.reverseOrder"></i>
|
||||
<i class="fa fa-sort-alpha-desc" aria-hidden="true" ng-if="$ctrl.state.orderBy === 'Names' && $ctrl.state.reverseOrder"></i>
|
||||
</a>
|
||||
</th>
|
||||
<th uib-dropdown dropdown-append-to-body auto-close="disabled" is-open="$ctrl.filters.state.open">
|
||||
<a ng-click="$ctrl.changeOrderBy('Status')">
|
||||
State
|
||||
<i class="fa fa-sort-alpha-asc" aria-hidden="true" ng-if="$ctrl.state.orderBy === 'Status' && !$ctrl.state.reverseOrder"></i>
|
||||
<i class="fa fa-sort-alpha-desc" aria-hidden="true" ng-if="$ctrl.state.orderBy === 'Status' && $ctrl.state.reverseOrder"></i>
|
||||
</a>
|
||||
<div>
|
||||
<span uib-dropdown-toggle class="table-filter" ng-if="!$ctrl.filters.state.enabled">Filter <i class="fa fa-filter" aria-hidden="true"></i></span>
|
||||
<span uib-dropdown-toggle class="table-filter filter-active" ng-if="$ctrl.filters.state.enabled">Filter <i class="fa fa-check" aria-hidden="true"></i></span>
|
||||
</div>
|
||||
<div class="dropdown-menu" uib-dropdown-menu>
|
||||
<div class="tableMenu">
|
||||
<div class="menuHeader">
|
||||
Filter by state
|
||||
</div>
|
||||
<div class="menuContent">
|
||||
<div class="md-checkbox" ng-repeat="filter in $ctrl.filters.state.values track by $index">
|
||||
<input id="filter_state_{{ $index }}" type="checkbox" ng-model="filter.display" ng-change="$ctrl.onStateFilterChange()"/>
|
||||
<label for="filter_state_{{ $index }}">{{ filter.label }}</label>
|
||||
</div>
|
||||
</div>
|
||||
<div>
|
||||
<a type="button" class="btn btn-default btn-sm" ng-click="$ctrl.filters.state.open = false;">Close</a>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</th>
|
||||
<th ng-if="$ctrl.settings.showQuickActionStats || $ctrl.settings.showQuickActionLogs || $ctrl.settings.showQuickActionConsole || $ctrl.settings.showQuickActionInspect">
|
||||
Quick actions
|
||||
</th>
|
||||
<th>
|
||||
<a ng-click="$ctrl.changeOrderBy('StackName')">
|
||||
Stack
|
||||
<i class="fa fa-sort-alpha-asc" aria-hidden="true" ng-if="$ctrl.state.orderBy === 'StackName' && !$ctrl.state.reverseOrder"></i>
|
||||
<i class="fa fa-sort-alpha-desc" aria-hidden="true" ng-if="$ctrl.state.orderBy === 'StackName' && $ctrl.state.reverseOrder"></i>
|
||||
</a>
|
||||
</th>
|
||||
<th>
|
||||
<a ng-click="$ctrl.changeOrderBy('Image')">
|
||||
Image
|
||||
<i class="fa fa-sort-alpha-asc" aria-hidden="true" ng-if="$ctrl.state.orderBy === 'Image' && !$ctrl.state.reverseOrder"></i>
|
||||
<i class="fa fa-sort-alpha-desc" aria-hidden="true" ng-if="$ctrl.state.orderBy === 'Image' && $ctrl.state.reverseOrder"></i>
|
||||
</a>
|
||||
</th>
|
||||
<th>
|
||||
<a ng-click="$ctrl.changeOrderBy('IP')">
|
||||
IP Address
|
||||
<i class="fa fa-sort-alpha-asc" aria-hidden="true" ng-if="$ctrl.state.orderBy === 'IP' && !$ctrl.state.reverseOrder"></i>
|
||||
<i class="fa fa-sort-alpha-desc" aria-hidden="true" ng-if="$ctrl.state.orderBy === 'IP' && $ctrl.state.reverseOrder"></i>
|
||||
</a>
|
||||
</th>
|
||||
<th ng-if="$ctrl.swarmContainers">
|
||||
<a ng-click="$ctrl.changeOrderBy('Host')">
|
||||
Host IP
|
||||
<i class="fa fa-sort-alpha-asc" aria-hidden="true" ng-if="$ctrl.state.orderBy === 'Host' && !$ctrl.state.reverseOrder"></i>
|
||||
<i class="fa fa-sort-alpha-desc" aria-hidden="true" ng-if="$ctrl.state.orderBy === 'Host' && $ctrl.state.reverseOrder"></i>
|
||||
</a>
|
||||
</th>
|
||||
<th>
|
||||
<a ng-click="$ctrl.changeOrderBy('Ports')">
|
||||
Published Ports
|
||||
<i class="fa fa-sort-alpha-asc" aria-hidden="true" ng-if="$ctrl.state.orderBy === 'Ports' && !$ctrl.state.reverseOrder"></i>
|
||||
<i class="fa fa-sort-alpha-desc" aria-hidden="true" ng-if="$ctrl.state.orderBy === 'Ports' && $ctrl.state.reverseOrder"></i>
|
||||
</a>
|
||||
</th>
|
||||
<th ng-if="$ctrl.showOwnershipColumn">
|
||||
<a ng-click="$ctrl.changeOrderBy('ResourceControl.Ownership')">
|
||||
Ownership
|
||||
<i class="fa fa-sort-alpha-asc" aria-hidden="true" ng-if="$ctrl.state.orderBy === 'ResourceControl.Ownership' && !$ctrl.state.reverseOrder"></i>
|
||||
<i class="fa fa-sort-alpha-desc" aria-hidden="true" ng-if="$ctrl.state.orderBy === 'ResourceControl.Ownership' && $ctrl.state.reverseOrder"></i>
|
||||
</a>
|
||||
</th>
|
||||
</tr>
|
||||
</thead>
|
||||
<tbody>
|
||||
<tr dir-paginate="item in ($ctrl.state.filteredDataSet = ($ctrl.dataset | filter: $ctrl.applyFilters | filter:$ctrl.state.textFilter | orderBy:$ctrl.state.orderBy:$ctrl.state.reverseOrder | itemsPerPage: $ctrl.state.paginatedItemLimit))" ng-class="{active: item.Checked}">
|
||||
<td>
|
||||
<span class="md-checkbox">
|
||||
<input id="select_{{ $index }}" type="checkbox" ng-model="item.Checked" ng-change="$ctrl.selectItem(item)"/>
|
||||
<label for="select_{{ $index }}"></label>
|
||||
</span>
|
||||
<a ui-sref="docker.containers.container({ id: item.Id })" ng-if="!$ctrl.swarmContainers">{{ item | containername | truncate: $ctrl.settings.containerNameTruncateSize }}</a>
|
||||
<a ui-sref="docker.containers.container({ id: item.Id })" ng-if="$ctrl.swarmContainers">{{ item | swarmcontainername | truncate: $ctrl.settings.containerNameTruncateSize }}</a>
|
||||
</td>
|
||||
<td>
|
||||
<span ng-if="['starting','healthy','unhealthy'].indexOf(item.Status) !== -1" class="label label-{{ item.Status|containerstatusbadge }} interactive" uib-tooltip="This container has a health check">{{ item.Status }}</span>
|
||||
<span ng-if="['starting','healthy','unhealthy'].indexOf(item.Status) === -1" class="label label-{{ item.Status|containerstatusbadge }}">{{ item.Status }}</span>
|
||||
</td>
|
||||
<td ng-if="$ctrl.settings.showQuickActionStats || $ctrl.settings.showQuickActionLogs || $ctrl.settings.showQuickActionConsole || $ctrl.settings.showQuickActionInspect">
|
||||
<div class="btn-group btn-group-xs" role="group" aria-label="..." style="display:inline-flex;">
|
||||
<a ng-if="$ctrl.settings.showQuickActionStats" style="margin: 0 2.5px;" ui-sref="docker.containers.container.stats({id: item.Id})"><i class="fa fa-area-chart space-right" aria-hidden="true"></i></a>
|
||||
<a ng-if="$ctrl.settings.showQuickActionLogs" style="margin: 0 2.5px;" ui-sref="docker.containers.container.logs({id: item.Id})"><i class="fa fa-exclamation-circle space-right" aria-hidden="true"></i></a>
|
||||
<a ng-if="$ctrl.settings.showQuickActionConsole" style="margin: 0 2.5px;" ui-sref="docker.containers.container.console({id: item.Id})"><i class="fa fa-terminal space-right" aria-hidden="true"></i></a>
|
||||
<a ng-if="$ctrl.settings.showQuickActionInspect" style="margin: 0 2.5px;" ui-sref="docker.containers.container.inspect({id: item.Id})"><i class="fa fa-info-circle space-right" aria-hidden="true"></i></a>
|
||||
</div>
|
||||
</td>
|
||||
<td>{{ item.StackName ? item.StackName : '-' }}</td>
|
||||
<td><a ui-sref="docker.images.image({ id: item.Image })">{{ item.Image | trimshasum }}</a></td>
|
||||
<td>{{ item.IP ? item.IP : '-' }}</td>
|
||||
<td ng-if="$ctrl.swarmContainers">{{ item.hostIP }}</td>
|
||||
<td>
|
||||
<a ng-if="item.Ports.length > 0" ng-repeat="p in item.Ports" class="image-tag" ng-href="http://{{ $ctrl.publicUrl || p.host }}:{{p.public}}" target="_blank">
|
||||
<i class="fa fa-external-link" aria-hidden="true"></i> {{ p.public }}:{{ p.private }}
|
||||
</a>
|
||||
<span ng-if="item.Ports.length == 0" >-</span>
|
||||
</td>
|
||||
<td ng-if="$ctrl.showOwnershipColumn">
|
||||
<span>
|
||||
<i ng-class="item.ResourceControl.Ownership | ownershipicon" aria-hidden="true"></i>
|
||||
{{ item.ResourceControl.Ownership ? item.ResourceControl.Ownership : item.ResourceControl.Ownership = 'public' }}
|
||||
</span>
|
||||
</td>
|
||||
</tr>
|
||||
<tr ng-if="!$ctrl.dataset">
|
||||
<td colspan="8" class="text-center text-muted">Loading...</td>
|
||||
</tr>
|
||||
<tr ng-if="$ctrl.state.filteredDataSet.length === 0">
|
||||
<td colspan="8" class="text-center text-muted">No container available.</td>
|
||||
</tr>
|
||||
</tbody>
|
||||
</table>
|
||||
</div>
|
||||
<div class="footer" ng-if="$ctrl.dataset">
|
||||
<div class="infoBar" ng-if="$ctrl.state.selectedItemCount !== 0">
|
||||
{{ $ctrl.state.selectedItemCount }} item(s) selected
|
||||
</div>
|
||||
<div class="paginationControls">
|
||||
<form class="form-inline">
|
||||
<span class="limitSelector">
|
||||
<span style="margin-right: 5px;">
|
||||
Items per page
|
||||
</span>
|
||||
<select class="form-control" ng-model="$ctrl.state.paginatedItemLimit" ng-change="$ctrl.changePaginationLimit()">
|
||||
<option value="0">All</option>
|
||||
<option value="10">10</option>
|
||||
<option value="25">25</option>
|
||||
<option value="50">50</option>
|
||||
<option value="100">100</option>
|
||||
</select>
|
||||
</span>
|
||||
<dir-pagination-controls max-size="5"></dir-pagination-controls>
|
||||
</form>
|
||||
</div>
|
||||
</div>
|
||||
</rd-widget-body>
|
||||
</rd-widget>
|
||||
</div>
|
|
@ -0,0 +1,24 @@
|
|||
angular.module('portainer.docker').component('containersDatatable', {
|
||||
templateUrl: 'app/docker/components/datatables/containers-datatable/containersDatatable.html',
|
||||
controller: 'ContainersDatatableController',
|
||||
bindings: {
|
||||
title: '@',
|
||||
titleIcon: '@',
|
||||
dataset: '<',
|
||||
tableKey: '@',
|
||||
orderBy: '@',
|
||||
reverseOrder: '<',
|
||||
showTextFilter: '<',
|
||||
showOwnershipColumn: '<',
|
||||
swarmContainers: '<',
|
||||
publicUrl: '<',
|
||||
containerNameTruncateSize: '<',
|
||||
startAction: '<',
|
||||
stopAction: '<',
|
||||
killAction: '<',
|
||||
restartAction: '<',
|
||||
pauseAction: '<',
|
||||
resumeAction: '<',
|
||||
removeAction: '<'
|
||||
}
|
||||
});
|
|
@ -0,0 +1,190 @@
|
|||
angular.module('portainer.docker')
|
||||
.controller('ContainersDatatableController', ['PaginationService', 'DatatableService',
|
||||
function (PaginationService, DatatableService) {
|
||||
|
||||
var ctrl = this;
|
||||
|
||||
this.state = {
|
||||
selectAll: false,
|
||||
orderBy: this.orderBy,
|
||||
paginatedItemLimit: PaginationService.getPaginationLimit(this.tableKey),
|
||||
displayTextFilter: false,
|
||||
selectedItemCount: 0,
|
||||
selectedItems: []
|
||||
};
|
||||
|
||||
this.settings = {
|
||||
open: false,
|
||||
truncateContainerName: true,
|
||||
containerNameTruncateSize: 32,
|
||||
showQuickActionStats: true,
|
||||
showQuickActionLogs: true,
|
||||
showQuickActionConsole: true,
|
||||
showQuickActionInspect: true
|
||||
};
|
||||
|
||||
this.filters = {
|
||||
state: {
|
||||
open: false,
|
||||
enabled: false,
|
||||
values: []
|
||||
}
|
||||
};
|
||||
|
||||
this.changeOrderBy = function(orderField) {
|
||||
this.state.reverseOrder = this.state.orderBy === orderField ? !this.state.reverseOrder : false;
|
||||
this.state.orderBy = orderField;
|
||||
DatatableService.setDataTableOrder(this.tableKey, orderField, this.state.reverseOrder);
|
||||
};
|
||||
|
||||
this.toggleItemSelection = function(item) {
|
||||
if (item.Checked) {
|
||||
this.state.selectedItemCount++;
|
||||
this.state.selectedItems.push(item);
|
||||
} else {
|
||||
this.state.selectedItems.splice(this.state.selectedItems.indexOf(item), 1);
|
||||
this.state.selectedItemCount--;
|
||||
}
|
||||
};
|
||||
|
||||
this.selectItem = function(item) {
|
||||
this.toggleItemSelection(item);
|
||||
this.updateSelectionState();
|
||||
};
|
||||
|
||||
this.selectAll = function() {
|
||||
for (var i = 0; i < this.state.filteredDataSet.length; i++) {
|
||||
var item = this.state.filteredDataSet[i];
|
||||
if (item.Checked !== this.state.selectAll) {
|
||||
item.Checked = this.state.selectAll;
|
||||
this.toggleItemSelection(item);
|
||||
}
|
||||
}
|
||||
this.updateSelectionState();
|
||||
};
|
||||
|
||||
this.updateSelectionState = function() {
|
||||
this.state.noStoppedItemsSelected = true;
|
||||
this.state.noRunningItemsSelected = true;
|
||||
this.state.noPausedItemsSelected = true;
|
||||
|
||||
for (var i = 0; i < this.dataset.length; i++) {
|
||||
var item = this.dataset[i];
|
||||
if (item.Checked) {
|
||||
this.updateSelectionStateBasedOnItemStatus(item);
|
||||
}
|
||||
}
|
||||
};
|
||||
|
||||
this.updateSelectionStateBasedOnItemStatus = function(item) {
|
||||
if (item.Status === 'paused') {
|
||||
this.state.noPausedItemsSelected = false;
|
||||
} else if (['stopped', 'created'].indexOf(item.Status) !== -1) {
|
||||
this.state.noStoppedItemsSelected = false;
|
||||
} else if (['running', 'healthy', 'unhealthy', 'starting'].indexOf(item.Status) !== -1) {
|
||||
this.state.noRunningItemsSelected = false;
|
||||
}
|
||||
};
|
||||
|
||||
this.changePaginationLimit = function() {
|
||||
PaginationService.setPaginationLimit(this.tableKey, this.state.paginatedItemLimit);
|
||||
};
|
||||
|
||||
this.updateDisplayTextFilter = function() {
|
||||
this.state.displayTextFilter = !this.state.displayTextFilter;
|
||||
if (!this.state.displayTextFilter) {
|
||||
delete this.state.textFilter;
|
||||
}
|
||||
};
|
||||
|
||||
this.applyFilters = function(value, index, array) {
|
||||
var container = value;
|
||||
var filters = ctrl.filters;
|
||||
for (var i = 0; i < filters.state.values.length; i++) {
|
||||
var filter = filters.state.values[i];
|
||||
if (container.Status === filter.label && filter.display) {
|
||||
return true;
|
||||
}
|
||||
}
|
||||
return false;
|
||||
};
|
||||
|
||||
this.onStateFilterChange = function() {
|
||||
var filters = this.filters.state.values;
|
||||
var filtered = false;
|
||||
for (var i = 0; i < filters.length; i++) {
|
||||
var filter = filters[i];
|
||||
if (!filter.display) {
|
||||
filtered = true;
|
||||
}
|
||||
}
|
||||
this.filters.state.enabled = filtered;
|
||||
DatatableService.setDataTableFilters(this.tableKey, this.filters);
|
||||
};
|
||||
|
||||
this.onSettingsContainerNameTruncateChange = function() {
|
||||
if (this.settings.truncateContainerName) {
|
||||
this.settings.containerNameTruncateSize = 32;
|
||||
} else {
|
||||
this.settings.containerNameTruncateSize = 256;
|
||||
}
|
||||
DatatableService.setDataTableSettings(this.tableKey, this.settings);
|
||||
};
|
||||
|
||||
this.onSettingsQuickActionChange = function() {
|
||||
DatatableService.setDataTableSettings(this.tableKey, this.settings);
|
||||
};
|
||||
|
||||
this.prepareTableFromDataset = function() {
|
||||
var availableStateFilters = [];
|
||||
for (var i = 0; i < this.dataset.length; i++) {
|
||||
var item = this.dataset[i];
|
||||
if (item.Checked) {
|
||||
this.selectItem(item);
|
||||
}
|
||||
availableStateFilters.push({ label: item.Status, display: true });
|
||||
}
|
||||
this.filters.state.values = _.uniqBy(availableStateFilters, 'label');
|
||||
};
|
||||
|
||||
this.updateStoredFilters = function(storedFilters) {
|
||||
var datasetFilters = this.filters.state.values;
|
||||
|
||||
for (var i = 0; i < datasetFilters.length; i++) {
|
||||
var filter = datasetFilters[i];
|
||||
existingFilter = _.find(storedFilters, ['label', filter.label]);
|
||||
if (existingFilter && !existingFilter.display) {
|
||||
filter.display = existingFilter.display;
|
||||
this.filters.state.enabled = true;
|
||||
}
|
||||
}
|
||||
};
|
||||
|
||||
this.$onInit = function() {
|
||||
setDefaults(this);
|
||||
this.prepareTableFromDataset();
|
||||
|
||||
var storedOrder = DatatableService.getDataTableOrder(this.tableKey);
|
||||
if (storedOrder !== null) {
|
||||
this.state.reverseOrder = storedOrder.reverse;
|
||||
this.state.orderBy = storedOrder.orderBy;
|
||||
}
|
||||
|
||||
var storedFilters = DatatableService.getDataTableFilters(this.tableKey);
|
||||
if (storedFilters !== null) {
|
||||
this.updateStoredFilters(storedFilters.state.values);
|
||||
}
|
||||
this.filters.state.open = false;
|
||||
|
||||
var storedSettings = DatatableService.getDataTableSettings(this.tableKey);
|
||||
if (storedSettings !== null) {
|
||||
this.settings = storedSettings;
|
||||
}
|
||||
this.settings.open = false;
|
||||
};
|
||||
|
||||
function setDefaults(ctrl) {
|
||||
ctrl.showTextFilter = ctrl.showTextFilter ? ctrl.showTextFilter : false;
|
||||
ctrl.state.reverseOrder = ctrl.reverseOrder ? ctrl.reverseOrder : false;
|
||||
}
|
||||
}]);
|
|
@ -0,0 +1,81 @@
|
|||
<div class="datatable">
|
||||
<rd-widget>
|
||||
<rd-widget-body classes="no-padding">
|
||||
<div class="toolBar">
|
||||
<div class="toolBarTitle">
|
||||
<i class="fa" ng-class="$ctrl.titleIcon" aria-hidden="true" style="margin-right: 2px;"></i> {{ $ctrl.title }}
|
||||
</div>
|
||||
<div class="settings">
|
||||
<span class="setting" ng-class="{ 'setting-active': $ctrl.state.displayTextFilter }" ng-click="$ctrl.updateDisplayTextFilter()" ng-if="$ctrl.showTextFilter">
|
||||
<i class="fa fa-search" aria-hidden="true"></i> Search
|
||||
</span>
|
||||
</div>
|
||||
</div>
|
||||
<div class="searchBar" ng-if="$ctrl.state.displayTextFilter">
|
||||
<i class="fa fa-search searchIcon" aria-hidden="true"></i>
|
||||
<input type="text" class="searchInput" ng-model="$ctrl.state.textFilter" placeholder="Search...">
|
||||
</div>
|
||||
<div class="table-responsive">
|
||||
<table class="table table-hover">
|
||||
<thead>
|
||||
<tr>
|
||||
<th>
|
||||
<a ng-click="$ctrl.changeOrderBy('Time')">
|
||||
Date
|
||||
<i class="fa fa-sort-alpha-asc" aria-hidden="true" ng-if="$ctrl.state.orderBy === 'Time' && !$ctrl.state.reverseOrder"></i>
|
||||
<i class="fa fa-sort-alpha-desc" aria-hidden="true" ng-if="$ctrl.state.orderBy === 'Time' && $ctrl.state.reverseOrder"></i>
|
||||
</a>
|
||||
</th>
|
||||
<th>
|
||||
<a ng-click="$ctrl.changeOrderBy('Type')">
|
||||
Category
|
||||
<i class="fa fa-sort-alpha-asc" aria-hidden="true" ng-if="$ctrl.state.orderBy === 'Type' && !$ctrl.state.reverseOrder"></i>
|
||||
<i class="fa fa-sort-alpha-desc" aria-hidden="true" ng-if="$ctrl.state.orderBy === 'Type' && $ctrl.state.reverseOrder"></i>
|
||||
</a>
|
||||
</th>
|
||||
<th>
|
||||
<a ng-click="$ctrl.changeOrderBy('Details')">
|
||||
Details
|
||||
<i class="fa fa-sort-alpha-asc" aria-hidden="true" ng-if="$ctrl.state.orderBy === 'Details' && !$ctrl.state.reverseOrder"></i>
|
||||
<i class="fa fa-sort-alpha-desc" aria-hidden="true" ng-if="$ctrl.state.orderBy === 'Details' && $ctrl.state.reverseOrder"></i>
|
||||
</a>
|
||||
</th>
|
||||
</tr>
|
||||
</thead>
|
||||
<tbody>
|
||||
<tr dir-paginate="item in ($ctrl.state.filteredDataSet = ($ctrl.dataset | filter:$ctrl.state.textFilter | orderBy:$ctrl.state.orderBy:$ctrl.state.reverseOrder | itemsPerPage: $ctrl.state.paginatedItemLimit))" ng-class="{active: item.Checked}">
|
||||
<td>{{ item.Time | getisodatefromtimestamp }}</td>
|
||||
<td>{{ item.Type }}</td>
|
||||
<td>{{ item.Details }}</td>
|
||||
</tr>
|
||||
<tr ng-if="!$ctrl.dataset">
|
||||
<td colspan="3" class="text-center text-muted">Loading...</td>
|
||||
</tr>
|
||||
<tr ng-if="$ctrl.state.filteredDataSet.length === 0">
|
||||
<td colspan="3" class="text-center text-muted">No event available.</td>
|
||||
</tr>
|
||||
</tbody>
|
||||
</table>
|
||||
</div>
|
||||
<div class="footer" ng-if="$ctrl.dataset">
|
||||
<div class="paginationControls">
|
||||
<form class="form-inline">
|
||||
<span class="limitSelector">
|
||||
<span style="margin-right: 5px;">
|
||||
Items per page
|
||||
</span>
|
||||
<select class="form-control" ng-model="$ctrl.state.paginatedItemLimit" ng-change="$ctrl.changePaginationLimit()">
|
||||
<option value="0">All</option>
|
||||
<option value="10">10</option>
|
||||
<option value="25">25</option>
|
||||
<option value="50">50</option>
|
||||
<option value="100">100</option>
|
||||
</select>
|
||||
</span>
|
||||
<dir-pagination-controls max-size="5"></dir-pagination-controls>
|
||||
</form>
|
||||
</div>
|
||||
</div>
|
||||
</rd-widget-body>
|
||||
</rd-widget>
|
||||
</div>
|
|
@ -0,0 +1,13 @@
|
|||
angular.module('portainer.docker').component('eventsDatatable', {
|
||||
templateUrl: 'app/docker/components/datatables/events-datatable/eventsDatatable.html',
|
||||
controller: 'GenericDatatableController',
|
||||
bindings: {
|
||||
title: '@',
|
||||
titleIcon: '@',
|
||||
dataset: '<',
|
||||
tableKey: '@',
|
||||
orderBy: '@',
|
||||
reverseOrder: '<',
|
||||
showTextFilter: '<'
|
||||
}
|
||||
});
|
|
@ -0,0 +1,144 @@
|
|||
<div class="datatable">
|
||||
<rd-widget>
|
||||
<rd-widget-body classes="no-padding">
|
||||
<div class="toolBar">
|
||||
<div class="toolBarTitle">
|
||||
<i class="fa" ng-class="$ctrl.titleIcon" aria-hidden="true" style="margin-right: 2px;"></i> {{ $ctrl.title }}
|
||||
</div>
|
||||
<div class="settings">
|
||||
<span class="setting" ng-class="{ 'setting-active': $ctrl.state.displayTextFilter }" ng-click="$ctrl.updateDisplayTextFilter()" ng-if="$ctrl.showTextFilter">
|
||||
<i class="fa fa-search" aria-hidden="true"></i> Search
|
||||
</span>
|
||||
</div>
|
||||
</div>
|
||||
<div class="actionBar">
|
||||
<div class="btn-group">
|
||||
<button type="button" class="btn btn-sm btn-danger"
|
||||
ng-disabled="$ctrl.state.selectedItemCount === 0" ng-click="$ctrl.removeAction($ctrl.state.selectedItems, false)">
|
||||
<i class="fa fa-trash space-right" aria-hidden="true"></i>Remove
|
||||
</button>
|
||||
<button type="button" class="btn btn-sm btn-danger dropdown-toggle" data-toggle="dropdown" aria-haspopup="true" aria-expanded="false" ng-disabled="$ctrl.state.selectedItemCount === 0">
|
||||
<span class="caret"></span>
|
||||
<span class="sr-only">Toggle Dropdown</span>
|
||||
</button>
|
||||
<ul class="dropdown-menu">
|
||||
<li><a ng-click="$ctrl.forceRemoveAction($ctrl.state.selectedItems, true)" ng-disabled="$ctrl.state.selectedItemCount === 0">Force Remove</a></li>
|
||||
</ul>
|
||||
</div>
|
||||
</div>
|
||||
<div class="searchBar" ng-if="$ctrl.state.displayTextFilter">
|
||||
<i class="fa fa-search searchIcon" aria-hidden="true"></i>
|
||||
<input type="text" class="searchInput" ng-model="$ctrl.state.textFilter" placeholder="Search...">
|
||||
</div>
|
||||
<div class="table-responsive">
|
||||
<table class="table table-hover table-filters">
|
||||
<thead>
|
||||
<tr>
|
||||
<th uib-dropdown dropdown-append-to-body auto-close="disabled" popover-placement="bottom-left" is-open="$ctrl.filters.usage.open">
|
||||
<span class="md-checkbox">
|
||||
<input id="select_all" type="checkbox" ng-model="$ctrl.state.selectAll" ng-change="$ctrl.selectAll()" />
|
||||
<label for="select_all"></label>
|
||||
</span>
|
||||
<a ng-click="$ctrl.changeOrderBy('Id')">
|
||||
Id
|
||||
<i class="fa fa-sort-alpha-asc" aria-hidden="true" ng-if="$ctrl.state.orderBy === 'Id' && !$ctrl.state.reverseOrder"></i>
|
||||
<i class="fa fa-sort-alpha-desc" aria-hidden="true" ng-if="$ctrl.state.orderBy === 'Id' && $ctrl.state.reverseOrder"></i>
|
||||
</a>
|
||||
<div>
|
||||
<span uib-dropdown-toggle class="table-filter" ng-if="!$ctrl.filters.usage.enabled">Filter <i class="fa fa-filter" aria-hidden="true"></i></span>
|
||||
<span uib-dropdown-toggle class="table-filter filter-active" ng-if="$ctrl.filters.usage.enabled">Filter <i class="fa fa-check" aria-hidden="true"></i></span>
|
||||
</div>
|
||||
<div class="dropdown-menu" uib-dropdown-menu>
|
||||
<div class="tableMenu">
|
||||
<div class="menuHeader">
|
||||
Filter by usage
|
||||
</div>
|
||||
<div class="menuContent">
|
||||
<div class="md-checkbox">
|
||||
<input id="filter_usage_usedImages" type="checkbox" ng-model="$ctrl.filters.usage.showUsedImages" ng-change="$ctrl.onUsageFilterChange()"/>
|
||||
<label for="filter_usage_usedImages">Used images</label>
|
||||
</div>
|
||||
<div class="md-checkbox">
|
||||
<input id="filter_usage_unusedImages" type="checkbox" ng-model="$ctrl.filters.usage.showUnusedImages" ng-change="$ctrl.onUsageFilterChange()"/>
|
||||
<label for="filter_usage_unusedImages">Unused images</label>
|
||||
</div>
|
||||
</div>
|
||||
<div>
|
||||
<a type="button" class="btn btn-default btn-sm" ng-click="$ctrl.filters.usage.open = false;">Close</a>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</th>
|
||||
<th>
|
||||
<a ng-click="$ctrl.changeOrderBy('RepoTags')">
|
||||
Tags
|
||||
<i class="fa fa-sort-alpha-asc" aria-hidden="true" ng-if="$ctrl.state.orderBy === 'RepoTags' && !$ctrl.state.reverseOrder"></i>
|
||||
<i class="fa fa-sort-alpha-desc" aria-hidden="true" ng-if="$ctrl.state.orderBy === 'RepoTags' && $ctrl.state.reverseOrder"></i>
|
||||
</a>
|
||||
</th>
|
||||
<th>
|
||||
<a ng-click="$ctrl.changeOrderBy('VirtualSize')">
|
||||
Size
|
||||
<i class="fa fa-sort-alpha-asc" aria-hidden="true" ng-if="$ctrl.state.orderBy === 'VirtualSize' && !$ctrl.state.reverseOrder"></i>
|
||||
<i class="fa fa-sort-alpha-desc" aria-hidden="true" ng-if="$ctrl.state.orderBy === 'VirtualSize' && $ctrl.state.reverseOrder"></i>
|
||||
</a>
|
||||
</th>
|
||||
<th>
|
||||
<a ng-click="$ctrl.changeOrderBy('Created')">
|
||||
Created
|
||||
<i class="fa fa-sort-alpha-asc" aria-hidden="true" ng-if="$ctrl.state.orderBy === 'Created' && !$ctrl.state.reverseOrder"></i>
|
||||
<i class="fa fa-sort-alpha-desc" aria-hidden="true" ng-if="$ctrl.state.orderBy === 'Created' && $ctrl.state.reverseOrder"></i>
|
||||
</a>
|
||||
</th>
|
||||
</tr>
|
||||
</thead>
|
||||
<tbody>
|
||||
<tr dir-paginate="item in ($ctrl.state.filteredDataSet = ($ctrl.dataset | filter: $ctrl.applyFilters | filter:$ctrl.state.textFilter | orderBy:$ctrl.state.orderBy:$ctrl.state.reverseOrder | itemsPerPage: $ctrl.state.paginatedItemLimit))" ng-class="{active: item.Checked}">
|
||||
<td>
|
||||
<span class="md-checkbox">
|
||||
<input id="select_{{ $index }}" type="checkbox" ng-model="item.Checked" ng-change="$ctrl.selectItem(item)"/>
|
||||
<label for="select_{{ $index }}"></label>
|
||||
</span>
|
||||
<a ui-sref="docker.images.image({id: item.Id})" class="monospaced">{{ item.Id | truncate:20 }}</a>
|
||||
<span style="margin-left: 10px;" class="label label-warning image-tag" ng-if="::item.ContainerCount === 0">Unused</span>
|
||||
</td>
|
||||
<td>
|
||||
<span class="label label-primary image-tag" ng-repeat="tag in (item | repotags)">{{ tag }}</span>
|
||||
</td>
|
||||
<td>{{ item.VirtualSize | humansize }}</td>
|
||||
<td>{{ item.Created | getisodatefromtimestamp }}</td>
|
||||
</tr>
|
||||
<tr ng-if="!$ctrl.dataset">
|
||||
<td colspan="4" class="text-center text-muted">Loading...</td>
|
||||
</tr>
|
||||
<tr ng-if="$ctrl.state.filteredDataSet.length === 0">
|
||||
<td colspan="4" class="text-center text-muted">No image available.</td>
|
||||
</tr>
|
||||
</tbody>
|
||||
</table>
|
||||
</div>
|
||||
<div class="footer" ng-if="$ctrl.dataset">
|
||||
<div class="infoBar" ng-if="$ctrl.state.selectedItemCount !== 0">
|
||||
{{ $ctrl.state.selectedItemCount }} item(s) selected
|
||||
</div>
|
||||
<div class="paginationControls">
|
||||
<form class="form-inline">
|
||||
<span class="limitSelector">
|
||||
<span style="margin-right: 5px;">
|
||||
Items per page
|
||||
</span>
|
||||
<select class="form-control" ng-model="$ctrl.state.paginatedItemLimit" ng-change="$ctrl.changePaginationLimit()">
|
||||
<option value="0">All</option>
|
||||
<option value="10">10</option>
|
||||
<option value="25">25</option>
|
||||
<option value="50">50</option>
|
||||
<option value="100">100</option>
|
||||
</select>
|
||||
</span>
|
||||
<dir-pagination-controls max-size="5"></dir-pagination-controls>
|
||||
</form>
|
||||
</div>
|
||||
</div>
|
||||
</rd-widget-body>
|
||||
</rd-widget>
|
||||
</div>
|
|
@ -0,0 +1,15 @@
|
|||
angular.module('portainer.docker').component('imagesDatatable', {
|
||||
templateUrl: 'app/docker/components/datatables/images-datatable/imagesDatatable.html',
|
||||
controller: 'ImagesDatatableController',
|
||||
bindings: {
|
||||
title: '@',
|
||||
titleIcon: '@',
|
||||
dataset: '<',
|
||||
tableKey: '@',
|
||||
orderBy: '@',
|
||||
reverseOrder: '<',
|
||||
showTextFilter: '<',
|
||||
removeAction: '<',
|
||||
forceRemoveAction: '<'
|
||||
}
|
||||
});
|
|
@ -0,0 +1,102 @@
|
|||
angular.module('portainer.docker')
|
||||
.controller('ImagesDatatableController', ['PaginationService', 'DatatableService',
|
||||
function (PaginationService, DatatableService) {
|
||||
|
||||
var ctrl = this;
|
||||
|
||||
this.state = {
|
||||
selectAll: false,
|
||||
orderBy: this.orderBy,
|
||||
paginatedItemLimit: PaginationService.getPaginationLimit(this.tableKey),
|
||||
displayTextFilter: false,
|
||||
selectedItemCount: 0,
|
||||
selectedItems: []
|
||||
};
|
||||
|
||||
this.filters = {
|
||||
usage: {
|
||||
open: false,
|
||||
enabled: false,
|
||||
showUsedImages: true,
|
||||
showUnusedImages: true
|
||||
}
|
||||
};
|
||||
|
||||
this.changeOrderBy = function(orderField) {
|
||||
this.state.reverseOrder = this.state.orderBy === orderField ? !this.state.reverseOrder : false;
|
||||
this.state.orderBy = orderField;
|
||||
DatatableService.setDataTableOrder(this.tableKey, orderField, this.state.reverseOrder);
|
||||
};
|
||||
|
||||
this.selectItem = function(item) {
|
||||
if (item.Checked) {
|
||||
this.state.selectedItemCount++;
|
||||
this.state.selectedItems.push(item);
|
||||
} else {
|
||||
this.state.selectedItems.splice(this.state.selectedItems.indexOf(item), 1);
|
||||
this.state.selectedItemCount--;
|
||||
}
|
||||
};
|
||||
|
||||
this.selectAll = function() {
|
||||
for (var i = 0; i < this.state.filteredDataSet.length; i++) {
|
||||
var item = this.state.filteredDataSet[i];
|
||||
if (item.Checked !== this.state.selectAll) {
|
||||
item.Checked = this.state.selectAll;
|
||||
this.selectItem(item);
|
||||
}
|
||||
}
|
||||
};
|
||||
|
||||
this.changePaginationLimit = function() {
|
||||
PaginationService.setPaginationLimit(this.tableKey, this.state.paginatedItemLimit);
|
||||
};
|
||||
|
||||
this.updateDisplayTextFilter = function() {
|
||||
this.state.displayTextFilter = !this.state.displayTextFilter;
|
||||
if (!this.state.displayTextFilter) {
|
||||
delete this.state.textFilter;
|
||||
}
|
||||
};
|
||||
|
||||
this.applyFilters = function(value, index, array) {
|
||||
var image = value;
|
||||
var filters = ctrl.filters;
|
||||
if ((image.ContainerCount === 0 && filters.usage.showUnusedImages)
|
||||
|| (image.ContainerCount !== 0 && filters.usage.showUsedImages)) {
|
||||
return true;
|
||||
}
|
||||
return false;
|
||||
};
|
||||
|
||||
this.onUsageFilterChange = function() {
|
||||
var filters = this.filters.usage;
|
||||
var filtered = false;
|
||||
if (!filters.showUsedImages || !filters.showUnusedImages) {
|
||||
filtered = true;
|
||||
}
|
||||
this.filters.usage.enabled = filtered;
|
||||
DatatableService.setDataTableFilters(this.tableKey, this.filters);
|
||||
};
|
||||
|
||||
this.$onInit = function() {
|
||||
setDefaults(this);
|
||||
|
||||
var storedOrder = DatatableService.getDataTableOrder(this.tableKey);
|
||||
if (storedOrder !== null) {
|
||||
this.state.reverseOrder = storedOrder.reverse;
|
||||
this.state.orderBy = storedOrder.orderBy;
|
||||
}
|
||||
|
||||
var storedFilters = DatatableService.getDataTableFilters(this.tableKey);
|
||||
if (storedFilters !== null) {
|
||||
this.filters = storedFilters;
|
||||
}
|
||||
this.filters.usage.open = false;
|
||||
};
|
||||
|
||||
function setDefaults(ctrl) {
|
||||
ctrl.showTextFilter = ctrl.showTextFilter ? ctrl.showTextFilter : false;
|
||||
ctrl.state.reverseOrder = ctrl.reverseOrder ? ctrl.reverseOrder : false;
|
||||
}
|
||||
}]);
|
|
@ -0,0 +1,148 @@
|
|||
<div class="datatable">
|
||||
<rd-widget>
|
||||
<rd-widget-body classes="no-padding">
|
||||
<div class="toolBar">
|
||||
<div class="toolBarTitle">
|
||||
<i class="fa" ng-class="$ctrl.titleIcon" aria-hidden="true" style="margin-right: 2px;"></i> {{ $ctrl.title }}
|
||||
</div>
|
||||
<div class="settings">
|
||||
<span class="setting" ng-class="{ 'setting-active': $ctrl.state.displayTextFilter }" ng-click="$ctrl.updateDisplayTextFilter()" ng-if="$ctrl.showTextFilter">
|
||||
<i class="fa fa-search" aria-hidden="true"></i> Search
|
||||
</span>
|
||||
</div>
|
||||
</div>
|
||||
<div class="actionBar">
|
||||
<button type="button" class="btn btn-sm btn-danger"
|
||||
ng-disabled="$ctrl.state.selectedItemCount === 0" ng-click="$ctrl.removeAction($ctrl.state.selectedItems)">
|
||||
<i class="fa fa-trash space-right" aria-hidden="true"></i>Remove
|
||||
</button>
|
||||
<button type="button" class="btn btn-sm btn-primary" ui-sref="docker.networks.new">
|
||||
<i class="fa fa-plus space-right" aria-hidden="true"></i>Add network
|
||||
</button>
|
||||
</div>
|
||||
<div class="searchBar" ng-if="$ctrl.state.displayTextFilter">
|
||||
<i class="fa fa-search searchIcon" aria-hidden="true"></i>
|
||||
<input type="text" class="searchInput" ng-model="$ctrl.state.textFilter" placeholder="Search...">
|
||||
</div>
|
||||
<div class="table-responsive">
|
||||
<table class="table table-hover">
|
||||
<thead>
|
||||
<tr>
|
||||
<th>
|
||||
<span class="md-checkbox">
|
||||
<input id="select_all" type="checkbox" ng-model="$ctrl.state.selectAll" ng-change="$ctrl.selectAll()" />
|
||||
<label for="select_all"></label>
|
||||
</span>
|
||||
<a ng-click="$ctrl.changeOrderBy('Name')">
|
||||
Name
|
||||
<i class="fa fa-sort-alpha-asc" aria-hidden="true" ng-if="$ctrl.state.orderBy === 'Name' && !$ctrl.state.reverseOrder"></i>
|
||||
<i class="fa fa-sort-alpha-desc" aria-hidden="true" ng-if="$ctrl.state.orderBy === 'Name' && $ctrl.state.reverseOrder"></i>
|
||||
</a>
|
||||
</th>
|
||||
<th>
|
||||
<a ng-click="$ctrl.changeOrderBy('StackName')">
|
||||
Stack
|
||||
<i class="fa fa-sort-alpha-asc" aria-hidden="true" ng-if="$ctrl.state.orderBy === 'StackName' && !$ctrl.state.reverseOrder"></i>
|
||||
<i class="fa fa-sort-alpha-desc" aria-hidden="true" ng-if="$ctrl.state.orderBy === 'StackName' && $ctrl.state.reverseOrder"></i>
|
||||
</a>
|
||||
</th>
|
||||
<th>
|
||||
<a ng-click="$ctrl.changeOrderBy('Scope')">
|
||||
Scope
|
||||
<i class="fa fa-sort-alpha-asc" aria-hidden="true" ng-if="$ctrl.state.orderBy === 'Scope' && !$ctrl.state.reverseOrder"></i>
|
||||
<i class="fa fa-sort-alpha-desc" aria-hidden="true" ng-if="$ctrl.state.orderBy === 'Scope' && $ctrl.state.reverseOrder"></i>
|
||||
</a>
|
||||
</th>
|
||||
<th>
|
||||
<a ng-click="$ctrl.changeOrderBy('Driver')">
|
||||
Driver
|
||||
<i class="fa fa-sort-alpha-asc" aria-hidden="true" ng-if="$ctrl.state.orderBy === 'Driver' && !$ctrl.state.reverseOrder"></i>
|
||||
<i class="fa fa-sort-alpha-desc" aria-hidden="true" ng-if="$ctrl.state.orderBy === 'Driver' && $ctrl.state.reverseOrder"></i>
|
||||
</a>
|
||||
</th>
|
||||
<th>
|
||||
<a ng-click="$ctrl.changeOrderBy('IPAM.Driver')">
|
||||
IPAM Driver
|
||||
<i class="fa fa-sort-alpha-asc" aria-hidden="true" ng-if="$ctrl.state.orderBy === 'IPAM.Driver' && !$ctrl.state.reverseOrder"></i>
|
||||
<i class="fa fa-sort-alpha-desc" aria-hidden="true" ng-if="$ctrl.state.orderBy === 'IPAM.Driver' && $ctrl.state.reverseOrder"></i>
|
||||
</a>
|
||||
</th>
|
||||
<th>
|
||||
<a ng-click="$ctrl.changeOrderBy('IPAM.Config[0].Subnet')">
|
||||
IPAM Subnet
|
||||
<i class="fa fa-sort-alpha-asc" aria-hidden="true" ng-if="$ctrl.state.orderBy === 'IPAM.Config[0].Subnet' && !$ctrl.state.reverseOrder"></i>
|
||||
<i class="fa fa-sort-alpha-desc" aria-hidden="true" ng-if="$ctrl.state.orderBy === 'IPAM.Config[0].Subnet' && $ctrl.state.reverseOrder"></i>
|
||||
</a>
|
||||
</th>
|
||||
<th>
|
||||
<a ng-click="$ctrl.changeOrderBy('IPAM.Config[0].Gateway')">
|
||||
IPAM Gateway
|
||||
<i class="fa fa-sort-alpha-asc" aria-hidden="true" ng-if="$ctrl.state.orderBy === 'IPAM.Config[0].Gateway' && !$ctrl.state.reverseOrder"></i>
|
||||
<i class="fa fa-sort-alpha-desc" aria-hidden="true" ng-if="$ctrl.state.orderBy === 'IPAM.Config[0].Gateway' && $ctrl.state.reverseOrder"></i>
|
||||
</a>
|
||||
</th>
|
||||
<th ng-if="$ctrl.showOwnershipColumn">
|
||||
<a ng-click="$ctrl.changeOrderBy('ResourceControl.Ownership')">
|
||||
Ownership
|
||||
<i class="fa fa-sort-alpha-asc" aria-hidden="true" ng-if="$ctrl.state.orderBy === 'ResourceControl.Ownership' && !$ctrl.state.reverseOrder"></i>
|
||||
<i class="fa fa-sort-alpha-desc" aria-hidden="true" ng-if="$ctrl.state.orderBy === 'ResourceControl.Ownership' && $ctrl.state.reverseOrder"></i>
|
||||
</a>
|
||||
</th>
|
||||
</tr>
|
||||
</thead>
|
||||
<tbody>
|
||||
<tr dir-paginate="item in ($ctrl.state.filteredDataSet = ($ctrl.dataset | filter:$ctrl.state.textFilter | orderBy:$ctrl.state.orderBy:$ctrl.state.reverseOrder | itemsPerPage: $ctrl.state.paginatedItemLimit))" ng-class="{active: item.Checked}">
|
||||
<td>
|
||||
<span class="md-checkbox">
|
||||
<input id="select_{{ $index }}" type="checkbox" ng-model="item.Checked" ng-change="$ctrl.selectItem(item)"/>
|
||||
<label for="select_{{ $index }}"></label>
|
||||
</span>
|
||||
<a ui-sref="docker.networks.network({id: item.Id})" title="{{ item.Name }}">{{ item.Name | truncate:40 }}</a>
|
||||
</td>
|
||||
<td>{{ item.StackName ? item.StackName : '-' }}</td>
|
||||
<td>{{ item.Scope }}</td>
|
||||
<td>{{ item.Driver }}</td>
|
||||
<td>{{ item.IPAM.Driver }}</td>
|
||||
<td>{{ item.IPAM.Config[0].Subnet ? item.IPAM.Config[0].Subnet : '-' }}</td>
|
||||
<td>{{ item.IPAM.Config[0].Gateway ? item.IPAM.Config[0].Gateway : '-' }}</td>
|
||||
<td ng-if="$ctrl.showOwnershipColumn">
|
||||
<span>
|
||||
<i ng-class="item.ResourceControl.Ownership | ownershipicon" aria-hidden="true"></i>
|
||||
{{ item.ResourceControl.Ownership ? item.ResourceControl.Ownership : item.ResourceControl.Ownership = 'public' }}
|
||||
</span>
|
||||
</td>
|
||||
</tr>
|
||||
<tr ng-if="!$ctrl.dataset">
|
||||
<td colspan="8" class="text-center text-muted">Loading...</td>
|
||||
</tr>
|
||||
<tr ng-if="$ctrl.state.filteredDataSet.length === 0">
|
||||
<td colspan="8" class="text-center text-muted">No network available.</td>
|
||||
</tr>
|
||||
</tbody>
|
||||
</table>
|
||||
</div>
|
||||
<div class="footer" ng-if="$ctrl.dataset">
|
||||
<div class="infoBar" ng-if="$ctrl.state.selectedItemCount !== 0">
|
||||
{{ $ctrl.state.selectedItemCount }} item(s) selected
|
||||
</div>
|
||||
<div class="paginationControls">
|
||||
<form class="form-inline">
|
||||
<span class="limitSelector">
|
||||
<span style="margin-right: 5px;">
|
||||
Items per page
|
||||
</span>
|
||||
<select class="form-control" ng-model="$ctrl.state.paginatedItemLimit" ng-change="$ctrl.changePaginationLimit()">
|
||||
<option value="0">All</option>
|
||||
<option value="10">10</option>
|
||||
<option value="25">25</option>
|
||||
<option value="50">50</option>
|
||||
<option value="100">100</option>
|
||||
</select>
|
||||
</span>
|
||||
<dir-pagination-controls max-size="5"></dir-pagination-controls>
|
||||
</form>
|
||||
</div>
|
||||
</div>
|
||||
</rd-widget-body>
|
||||
</rd-widget>
|
||||
</div>
|
|
@ -0,0 +1,15 @@
|
|||
angular.module('portainer.docker').component('networksDatatable', {
|
||||
templateUrl: 'app/docker/components/datatables/networks-datatable/networksDatatable.html',
|
||||
controller: 'GenericDatatableController',
|
||||
bindings: {
|
||||
title: '@',
|
||||
titleIcon: '@',
|
||||
dataset: '<',
|
||||
tableKey: '@',
|
||||
orderBy: '@',
|
||||
reverseOrder: '<',
|
||||
showTextFilter: '<',
|
||||
showOwnershipColumn: '<',
|
||||
removeAction: '<'
|
||||
}
|
||||
});
|
|
@ -0,0 +1,97 @@
|
|||
<div class="datatable">
|
||||
<rd-widget>
|
||||
<rd-widget-body classes="no-padding">
|
||||
<div class="toolBar">
|
||||
<div class="toolBarTitle">
|
||||
<i class="fa" ng-class="$ctrl.titleIcon" aria-hidden="true" style="margin-right: 2px;"></i> {{ $ctrl.title }}
|
||||
</div>
|
||||
<div class="settings">
|
||||
<span class="setting" ng-class="{ 'setting-active': $ctrl.state.displayTextFilter }" ng-click="$ctrl.updateDisplayTextFilter()" ng-if="$ctrl.showTextFilter">
|
||||
<i class="fa fa-search" aria-hidden="true"></i> Search
|
||||
</span>
|
||||
</div>
|
||||
</div>
|
||||
<div class="searchBar" ng-if="$ctrl.state.displayTextFilter">
|
||||
<i class="fa fa-search searchIcon" aria-hidden="true"></i>
|
||||
<input type="text" class="searchInput" ng-model="$ctrl.state.textFilter" placeholder="Search...">
|
||||
</div>
|
||||
<div class="table-responsive">
|
||||
<table class="table table-hover">
|
||||
<thead>
|
||||
<tr>
|
||||
<th>
|
||||
<a ng-click="$ctrl.changeOrderBy('Id')">
|
||||
Id
|
||||
<i class="fa fa-sort-alpha-asc" aria-hidden="true" ng-if="$ctrl.state.orderBy === 'Id' && !$ctrl.state.reverseOrder"></i>
|
||||
<i class="fa fa-sort-alpha-desc" aria-hidden="true" ng-if="$ctrl.state.orderBy === 'Id' && $ctrl.state.reverseOrder"></i>
|
||||
</a>
|
||||
</th>
|
||||
<th>
|
||||
<a ng-click="$ctrl.changeOrderBy('Status')">
|
||||
Status
|
||||
<i class="fa fa-sort-alpha-asc" aria-hidden="true" ng-if="$ctrl.state.orderBy === 'Status' && !$ctrl.state.reverseOrder"></i>
|
||||
<i class="fa fa-sort-alpha-desc" aria-hidden="true" ng-if="$ctrl.state.orderBy === 'Status' && $ctrl.state.reverseOrder"></i>
|
||||
</a>
|
||||
</th>
|
||||
<th>
|
||||
<a ng-click="$ctrl.changeOrderBy('Slot')">
|
||||
Slot
|
||||
<i class="fa fa-sort-alpha-asc" aria-hidden="true" ng-if="$ctrl.state.orderBy === 'Slot' && !$ctrl.state.reverseOrder"></i>
|
||||
<i class="fa fa-sort-alpha-desc" aria-hidden="true" ng-if="$ctrl.state.orderBy === 'Slot' && $ctrl.state.reverseOrder"></i>
|
||||
</a>
|
||||
</th>
|
||||
<th>
|
||||
<a ng-click="$ctrl.changeOrderBy('Spec.ContainerSpec.Image')">
|
||||
Image
|
||||
<i class="fa fa-sort-alpha-asc" aria-hidden="true" ng-if="$ctrl.state.orderBy === 'Spec.ContainerSpec.Image' && !$ctrl.state.reverseOrder"></i>
|
||||
<i class="fa fa-sort-alpha-desc" aria-hidden="true" ng-if="$ctrl.state.orderBy === 'Spec.ContainerSpec.Image' && $ctrl.state.reverseOrder"></i>
|
||||
</a>
|
||||
</th>
|
||||
<th>
|
||||
<a ng-click="$ctrl.changeOrderBy('Updated')">
|
||||
Last Update
|
||||
<i class="fa fa-sort-alpha-asc" aria-hidden="true" ng-if="$ctrl.state.orderBy === 'Updated' && !$ctrl.state.reverseOrder"></i>
|
||||
<i class="fa fa-sort-alpha-desc" aria-hidden="true" ng-if="$ctrl.state.orderBy === 'Updated' && $ctrl.state.reverseOrder"></i>
|
||||
</a>
|
||||
</th>
|
||||
</tr>
|
||||
</thead>
|
||||
<tbody>
|
||||
<tr dir-paginate="item in ($ctrl.state.filteredDataSet = ($ctrl.dataset | filter:$ctrl.state.textFilter | orderBy:$ctrl.state.orderBy:$ctrl.state.reverseOrder | itemsPerPage: $ctrl.state.paginatedItemLimit))" ng-class="{active: item.Checked}">
|
||||
<td><a ui-sref="docker.tasks.task({id: item.Id})" class="monospaced">{{ item.Id }}</a></td>
|
||||
<td><span class="label label-{{ item.Status.State | taskstatusbadge }}">{{ item.Status.State }}</span></td>
|
||||
<td>{{ item.Slot ? item.Slot : '-' }}</td>
|
||||
<td>{{ item.Spec.ContainerSpec.Image | hideshasum }}</td>
|
||||
<td>{{ item.Updated | getisodate }}</td>
|
||||
</tr>
|
||||
<tr ng-if="!$ctrl.dataset">
|
||||
<td colspan="5" class="text-center text-muted">Loading...</td>
|
||||
</tr>
|
||||
<tr ng-if="$ctrl.state.filteredDataSet.length === 0">
|
||||
<td colspan="5" class="text-center text-muted">No task available.</td>
|
||||
</tr>
|
||||
</tbody>
|
||||
</table>
|
||||
</div>
|
||||
<div class="footer" ng-if="$ctrl.dataset">
|
||||
<div class="paginationControls">
|
||||
<form class="form-inline">
|
||||
<span class="limitSelector">
|
||||
<span style="margin-right: 5px;">
|
||||
Items per page
|
||||
</span>
|
||||
<select class="form-control" ng-model="$ctrl.state.paginatedItemLimit" ng-change="$ctrl.changePaginationLimit()">
|
||||
<option value="0">All</option>
|
||||
<option value="10">10</option>
|
||||
<option value="25">25</option>
|
||||
<option value="50">50</option>
|
||||
<option value="100">100</option>
|
||||
</select>
|
||||
</span>
|
||||
<dir-pagination-controls max-size="5"></dir-pagination-controls>
|
||||
</form>
|
||||
</div>
|
||||
</div>
|
||||
</rd-widget-body>
|
||||
</rd-widget>
|
||||
</div>
|
|
@ -0,0 +1,13 @@
|
|||
angular.module('portainer.docker').component('nodeTasksDatatable', {
|
||||
templateUrl: 'app/docker/components/datatables/node-tasks-datatable/nodeTasksDatatable.html',
|
||||
controller: 'GenericDatatableController',
|
||||
bindings: {
|
||||
title: '@',
|
||||
titleIcon: '@',
|
||||
dataset: '<',
|
||||
tableKey: '@',
|
||||
orderBy: '@',
|
||||
reverseOrder: '<',
|
||||
showTextFilter: '<'
|
||||
}
|
||||
});
|
|
@ -0,0 +1,116 @@
|
|||
<div class="datatable">
|
||||
<rd-widget>
|
||||
<rd-widget-body classes="no-padding">
|
||||
<div class="toolBar">
|
||||
<div class="toolBarTitle">
|
||||
<i class="fa" ng-class="$ctrl.titleIcon" aria-hidden="true" style="margin-right: 2px;"></i> {{ $ctrl.title }}
|
||||
</div>
|
||||
<div class="settings">
|
||||
<span class="setting" ng-class="{ 'setting-active': $ctrl.state.displayTextFilter }" ng-click="$ctrl.updateDisplayTextFilter()" ng-if="$ctrl.showTextFilter">
|
||||
<i class="fa fa-search" aria-hidden="true"></i> Search
|
||||
</span>
|
||||
</div>
|
||||
</div>
|
||||
<div class="searchBar" ng-if="$ctrl.state.displayTextFilter">
|
||||
<i class="fa fa-search searchIcon" aria-hidden="true"></i>
|
||||
<input type="text" class="searchInput" ng-model="$ctrl.state.textFilter" placeholder="Search...">
|
||||
</div>
|
||||
<div class="table-responsive">
|
||||
<table class="table table-hover">
|
||||
<thead>
|
||||
<tr>
|
||||
<th>
|
||||
<a ng-click="$ctrl.changeOrderBy('Hostname')">
|
||||
Name
|
||||
<i class="fa fa-sort-alpha-asc" aria-hidden="true" ng-if="$ctrl.state.orderBy === 'Hostname' && !$ctrl.state.reverseOrder"></i>
|
||||
<i class="fa fa-sort-alpha-desc" aria-hidden="true" ng-if="$ctrl.state.orderBy === 'Hostname' && $ctrl.state.reverseOrder"></i>
|
||||
</a>
|
||||
</th>
|
||||
<th>
|
||||
<a ng-click="$ctrl.changeOrderBy('Role')">
|
||||
Role
|
||||
<i class="fa fa-sort-alpha-asc" aria-hidden="true" ng-if="$ctrl.state.orderBy === 'Role' && !$ctrl.state.reverseOrder"></i>
|
||||
<i class="fa fa-sort-alpha-desc" aria-hidden="true" ng-if="$ctrl.state.orderBy === 'Role' && $ctrl.state.reverseOrder"></i>
|
||||
</a>
|
||||
</th>
|
||||
<th>
|
||||
<a ng-click="$ctrl.changeOrderBy('CPUs')">
|
||||
CPU
|
||||
<i class="fa fa-sort-alpha-asc" aria-hidden="true" ng-if="$ctrl.state.orderBy === 'CPUs' && !$ctrl.state.reverseOrder"></i>
|
||||
<i class="fa fa-sort-alpha-desc" aria-hidden="true" ng-if="$ctrl.state.orderBy === 'CPUs' && $ctrl.state.reverseOrder"></i>
|
||||
</a>
|
||||
</th>
|
||||
<th>
|
||||
<a ng-click="$ctrl.changeOrderBy('Memory')">
|
||||
Memory
|
||||
<i class="fa fa-sort-alpha-asc" aria-hidden="true" ng-if="$ctrl.state.orderBy === 'Memory' && !$ctrl.state.reverseOrder"></i>
|
||||
<i class="fa fa-sort-alpha-desc" aria-hidden="true" ng-if="$ctrl.state.orderBy === 'Memory' && $ctrl.state.reverseOrder"></i>
|
||||
</a>
|
||||
</th>
|
||||
<th>
|
||||
<a ng-click="$ctrl.changeOrderBy('EngineVersion')">
|
||||
Engine
|
||||
<i class="fa fa-sort-alpha-asc" aria-hidden="true" ng-if="$ctrl.state.orderBy === 'EngineVersion' && !$ctrl.state.reverseOrder"></i>
|
||||
<i class="fa fa-sort-alpha-desc" aria-hidden="true" ng-if="$ctrl.state.orderBy === 'EngineVersion' && $ctrl.state.reverseOrder"></i>
|
||||
</a>
|
||||
</th>
|
||||
<th ng-if="$ctrl.showIpAddressColumn">
|
||||
<a ng-click="$ctrl.changeOrderBy('Addr')">
|
||||
IP Address
|
||||
<i class="fa fa-sort-alpha-asc" aria-hidden="true" ng-if="$ctrl.state.orderBy === 'Addr' && !$ctrl.state.reverseOrder"></i>
|
||||
<i class="fa fa-sort-alpha-desc" aria-hidden="true" ng-if="$ctrl.state.orderBy === 'Addr' && $ctrl.state.reverseOrder"></i>
|
||||
</a>
|
||||
</th>
|
||||
<th>
|
||||
<a ng-click="$ctrl.changeOrderBy('Status')">
|
||||
Status
|
||||
<i class="fa fa-sort-alpha-asc" aria-hidden="true" ng-if="$ctrl.state.orderBy === 'Status' && !$ctrl.state.reverseOrder"></i>
|
||||
<i class="fa fa-sort-alpha-desc" aria-hidden="true" ng-if="$ctrl.state.orderBy === 'Status' && $ctrl.state.reverseOrder"></i>
|
||||
</a>
|
||||
</th>
|
||||
</tr>
|
||||
</thead>
|
||||
<tbody>
|
||||
<tr dir-paginate="item in ($ctrl.state.filteredDataSet = ($ctrl.dataset | filter:$ctrl.state.textFilter | orderBy:$ctrl.state.orderBy:$ctrl.state.reverseOrder | itemsPerPage: $ctrl.state.paginatedItemLimit))" ng-class="{active: item.Checked}">
|
||||
<td>
|
||||
<a ui-sref="docker.nodes.node({id: item.Id})" ng-if="$ctrl.accessToNodeDetails">{{ item.Hostname }}</a>
|
||||
<span ng-if="!$ctrl.accessToNodeDetails">{{ item.Hostname }}</span>
|
||||
</td>
|
||||
<td>{{ item.Role }}</td>
|
||||
<td>{{ item.CPUs / 1000000000 }}</td>
|
||||
<td>{{ item.Memory | humansize }}</td>
|
||||
<td>{{ item.EngineVersion }}</td>
|
||||
<td ng-if="$ctrl.showIpAddressColumn">{{ item.Addr }}</td>
|
||||
<td><span class="label label-{{ item.Status | nodestatusbadge }}">{{ item.Status }}</span></td>
|
||||
</tr>
|
||||
<tr ng-if="!$ctrl.dataset">
|
||||
<td colspan="7" class="text-center text-muted">Loading...</td>
|
||||
</tr>
|
||||
<tr ng-if="$ctrl.state.filteredDataSet.length === 0">
|
||||
<td colspan="7" class="text-center text-muted">No node available.</td>
|
||||
</tr>
|
||||
</tbody>
|
||||
</table>
|
||||
</div>
|
||||
<div class="footer" ng-if="$ctrl.dataset">
|
||||
<div class="paginationControls">
|
||||
<form class="form-inline">
|
||||
<span class="limitSelector">
|
||||
<span style="margin-right: 5px;">
|
||||
Items per page
|
||||
</span>
|
||||
<select class="form-control" ng-model="$ctrl.state.paginatedItemLimit" ng-change="$ctrl.changePaginationLimit()">
|
||||
<option value="0">All</option>
|
||||
<option value="10">10</option>
|
||||
<option value="25">25</option>
|
||||
<option value="50">50</option>
|
||||
<option value="100">100</option>
|
||||
</select>
|
||||
</span>
|
||||
<dir-pagination-controls max-size="5"></dir-pagination-controls>
|
||||
</form>
|
||||
</div>
|
||||
</div>
|
||||
</rd-widget-body>
|
||||
</rd-widget>
|
||||
</div>
|
|
@ -0,0 +1,15 @@
|
|||
angular.module('portainer.docker').component('nodesDatatable', {
|
||||
templateUrl: 'app/docker/components/datatables/nodes-datatable/nodesDatatable.html',
|
||||
controller: 'GenericDatatableController',
|
||||
bindings: {
|
||||
title: '@',
|
||||
titleIcon: '@',
|
||||
dataset: '<',
|
||||
tableKey: '@',
|
||||
orderBy: '@',
|
||||
reverseOrder: '<',
|
||||
showTextFilter: '<',
|
||||
showIpAddressColumn: '<',
|
||||
accessToNodeDetails: '<'
|
||||
}
|
||||
});
|
|
@ -0,0 +1,105 @@
|
|||
<div class="datatable">
|
||||
<rd-widget>
|
||||
<rd-widget-body classes="no-padding">
|
||||
<div class="toolBar">
|
||||
<div class="toolBarTitle">
|
||||
<i class="fa" ng-class="$ctrl.titleIcon" aria-hidden="true" style="margin-right: 2px;"></i> {{ $ctrl.title }}
|
||||
</div>
|
||||
<div class="settings">
|
||||
<span class="setting" ng-class="{ 'setting-active': $ctrl.state.displayTextFilter }" ng-click="$ctrl.updateDisplayTextFilter()" ng-if="$ctrl.showTextFilter">
|
||||
<i class="fa fa-search" aria-hidden="true"></i> Search
|
||||
</span>
|
||||
</div>
|
||||
</div>
|
||||
<div class="searchBar" ng-if="$ctrl.state.displayTextFilter">
|
||||
<i class="fa fa-search searchIcon" aria-hidden="true"></i>
|
||||
<input type="text" class="searchInput" ng-model="$ctrl.state.textFilter" placeholder="Search...">
|
||||
</div>
|
||||
<div class="table-responsive">
|
||||
<table class="table table-hover">
|
||||
<thead>
|
||||
<tr>
|
||||
<th>
|
||||
<a ng-click="$ctrl.changeOrderBy('name')">
|
||||
Name
|
||||
<i class="fa fa-sort-alpha-asc" aria-hidden="true" ng-if="$ctrl.state.orderBy === 'name' && !$ctrl.state.reverseOrder"></i>
|
||||
<i class="fa fa-sort-alpha-desc" aria-hidden="true" ng-if="$ctrl.state.orderBy === 'name' && $ctrl.state.reverseOrder"></i>
|
||||
</a>
|
||||
</th>
|
||||
<th>
|
||||
<a ng-click="$ctrl.changeOrderBy('cpu')">
|
||||
CPU
|
||||
<i class="fa fa-sort-alpha-asc" aria-hidden="true" ng-if="$ctrl.state.orderBy === 'cpu' && !$ctrl.state.reverseOrder"></i>
|
||||
<i class="fa fa-sort-alpha-desc" aria-hidden="true" ng-if="$ctrl.state.orderBy === 'cpu' && $ctrl.state.reverseOrder"></i>
|
||||
</a>
|
||||
</th>
|
||||
<th>
|
||||
<a ng-click="$ctrl.changeOrderBy('memory')">
|
||||
Memory
|
||||
<i class="fa fa-sort-alpha-asc" aria-hidden="true" ng-if="$ctrl.state.orderBy === 'memory' && !$ctrl.state.reverseOrder"></i>
|
||||
<i class="fa fa-sort-alpha-desc" aria-hidden="true" ng-if="$ctrl.state.orderBy === 'memory' && $ctrl.state.reverseOrder"></i>
|
||||
</a>
|
||||
</th>
|
||||
<th>
|
||||
<a ng-click="$ctrl.changeOrderBy('ip')">
|
||||
IP Address
|
||||
<i class="fa fa-sort-alpha-asc" aria-hidden="true" ng-if="$ctrl.state.orderBy === 'ip' && !$ctrl.state.reverseOrder"></i>
|
||||
<i class="fa fa-sort-alpha-desc" aria-hidden="true" ng-if="$ctrl.state.orderBy === 'ip' && $ctrl.state.reverseOrder"></i>
|
||||
</a>
|
||||
</th>
|
||||
<th>
|
||||
<a ng-click="$ctrl.changeOrderBy('version')">
|
||||
Engine
|
||||
<i class="fa fa-sort-alpha-asc" aria-hidden="true" ng-if="$ctrl.state.orderBy === 'version' && !$ctrl.state.reverseOrder"></i>
|
||||
<i class="fa fa-sort-alpha-desc" aria-hidden="true" ng-if="$ctrl.state.orderBy === 'version' && $ctrl.state.reverseOrder"></i>
|
||||
</a>
|
||||
</th>
|
||||
<th>
|
||||
<a ng-click="$ctrl.changeOrderBy('status')">
|
||||
Status
|
||||
<i class="fa fa-sort-alpha-asc" aria-hidden="true" ng-if="$ctrl.state.orderBy === 'status' && !$ctrl.state.reverseOrder"></i>
|
||||
<i class="fa fa-sort-alpha-desc" aria-hidden="true" ng-if="$ctrl.state.orderBy === 'status' && $ctrl.state.reverseOrder"></i>
|
||||
</a>
|
||||
</th>
|
||||
</tr>
|
||||
</thead>
|
||||
<tbody>
|
||||
<tr dir-paginate="item in ($ctrl.state.filteredDataSet = ($ctrl.dataset | filter:$ctrl.state.textFilter | orderBy:$ctrl.state.orderBy:$ctrl.state.reverseOrder | itemsPerPage: $ctrl.state.paginatedItemLimit))" ng-class="{active: item.Checked}">
|
||||
<td>{{ item.name }}</td>
|
||||
<td>{{ item.cpu }}</td>
|
||||
<td>{{ item.memory }}</td>
|
||||
<td>{{ item.ip }}</td>
|
||||
<td>{{ item.version }}</td>
|
||||
<td><span class="label label-{{ item.status | nodestatusbadge }}">{{ item.status }}</span></td>
|
||||
</tr>
|
||||
<tr ng-if="!$ctrl.dataset">
|
||||
<td colspan="6" class="text-center text-muted">Loading...</td>
|
||||
</tr>
|
||||
<tr ng-if="$ctrl.state.filteredDataSet.length === 0">
|
||||
<td colspan="6" class="text-center text-muted">No node available.</td>
|
||||
</tr>
|
||||
</tbody>
|
||||
</table>
|
||||
</div>
|
||||
<div class="footer" ng-if="$ctrl.dataset">
|
||||
<div class="paginationControls">
|
||||
<form class="form-inline">
|
||||
<span class="limitSelector">
|
||||
<span style="margin-right: 5px;">
|
||||
Items per page
|
||||
</span>
|
||||
<select class="form-control" ng-model="$ctrl.state.paginatedItemLimit" ng-change="$ctrl.changePaginationLimit()">
|
||||
<option value="0">All</option>
|
||||
<option value="10">10</option>
|
||||
<option value="25">25</option>
|
||||
<option value="50">50</option>
|
||||
<option value="100">100</option>
|
||||
</select>
|
||||
</span>
|
||||
<dir-pagination-controls max-size="5"></dir-pagination-controls>
|
||||
</form>
|
||||
</div>
|
||||
</div>
|
||||
</rd-widget-body>
|
||||
</rd-widget>
|
||||
</div>
|
|
@ -0,0 +1,13 @@
|
|||
angular.module('portainer.docker').component('nodesSsDatatable', {
|
||||
templateUrl: 'app/docker/components/datatables/nodes-ss-datatable/nodesSSDatatable.html',
|
||||
controller: 'GenericDatatableController',
|
||||
bindings: {
|
||||
title: '@',
|
||||
titleIcon: '@',
|
||||
dataset: '<',
|
||||
tableKey: '@',
|
||||
orderBy: '@',
|
||||
reverseOrder: '<',
|
||||
showTextFilter: '<'
|
||||
}
|
||||
});
|
|
@ -0,0 +1,108 @@
|
|||
<div class="datatable">
|
||||
<rd-widget>
|
||||
<rd-widget-body classes="no-padding">
|
||||
<div class="toolBar">
|
||||
<div class="toolBarTitle">
|
||||
<i class="fa" ng-class="$ctrl.titleIcon" aria-hidden="true" style="margin-right: 2px;"></i> {{ $ctrl.title }}
|
||||
</div>
|
||||
<div class="settings">
|
||||
<span class="setting" ng-class="{ 'setting-active': $ctrl.state.displayTextFilter }" ng-click="$ctrl.updateDisplayTextFilter()" ng-if="$ctrl.showTextFilter">
|
||||
<i class="fa fa-search" aria-hidden="true"></i> Search
|
||||
</span>
|
||||
</div>
|
||||
</div>
|
||||
<div class="actionBar">
|
||||
<button type="button" class="btn btn-sm btn-danger"
|
||||
ng-disabled="$ctrl.state.selectedItemCount === 0" ng-click="$ctrl.removeAction($ctrl.state.selectedItems)">
|
||||
<i class="fa fa-trash space-right" aria-hidden="true"></i>Remove
|
||||
</button>
|
||||
<button type="button" class="btn btn-sm btn-primary" ui-sref="docker.secrets.new">
|
||||
<i class="fa fa-plus space-right" aria-hidden="true"></i>Add secret
|
||||
</button>
|
||||
</div>
|
||||
<div class="searchBar" ng-if="$ctrl.state.displayTextFilter">
|
||||
<i class="fa fa-search searchIcon" aria-hidden="true"></i>
|
||||
<input type="text" class="searchInput" ng-model="$ctrl.state.textFilter" placeholder="Search...">
|
||||
</div>
|
||||
<div class="table-responsive">
|
||||
<table class="table table-hover">
|
||||
<thead>
|
||||
<tr>
|
||||
<th>
|
||||
<span class="md-checkbox">
|
||||
<input id="select_all" type="checkbox" ng-model="$ctrl.state.selectAll" ng-change="$ctrl.selectAll()" />
|
||||
<label for="select_all"></label>
|
||||
</span>
|
||||
<a ng-click="$ctrl.changeOrderBy('Name')">
|
||||
Name
|
||||
<i class="fa fa-sort-alpha-asc" aria-hidden="true" ng-if="$ctrl.state.orderBy === 'Name' && !$ctrl.state.reverseOrder"></i>
|
||||
<i class="fa fa-sort-alpha-desc" aria-hidden="true" ng-if="$ctrl.state.orderBy === 'Name' && $ctrl.state.reverseOrder"></i>
|
||||
</a>
|
||||
</th>
|
||||
<th>
|
||||
<a ng-click="$ctrl.changeOrderBy('CreatedAt')">
|
||||
Creation Date
|
||||
<i class="fa fa-sort-alpha-asc" aria-hidden="true" ng-if="$ctrl.state.orderBy === 'CreatedAt' && !$ctrl.state.reverseOrder"></i>
|
||||
<i class="fa fa-sort-alpha-desc" aria-hidden="true" ng-if="$ctrl.state.orderBy === 'CreatedAt' && $ctrl.state.reverseOrder"></i>
|
||||
</a>
|
||||
</th>
|
||||
<th ng-if="$ctrl.showOwnershipColumn">
|
||||
<a ng-click="$ctrl.changeOrderBy('ResourceControl.Ownership')">
|
||||
Ownership
|
||||
<i class="fa fa-sort-alpha-asc" aria-hidden="true" ng-if="$ctrl.state.orderBy === 'ResourceControl.Ownership' && !$ctrl.state.reverseOrder"></i>
|
||||
<i class="fa fa-sort-alpha-desc" aria-hidden="true" ng-if="$ctrl.state.orderBy === 'ResourceControl.Ownership' && $ctrl.state.reverseOrder"></i>
|
||||
</a>
|
||||
</th>
|
||||
</tr>
|
||||
</thead>
|
||||
<tbody>
|
||||
<tr dir-paginate="item in ($ctrl.state.filteredDataSet = ($ctrl.dataset | filter:$ctrl.state.textFilter | orderBy:$ctrl.state.orderBy:$ctrl.state.reverseOrder | itemsPerPage: $ctrl.state.paginatedItemLimit))" ng-class="{active: item.Checked}">
|
||||
<td>
|
||||
<span class="md-checkbox">
|
||||
<input id="select_{{ $index }}" type="checkbox" ng-model="item.Checked" ng-change="$ctrl.selectItem(item)"/>
|
||||
<label for="select_{{ $index }}"></label>
|
||||
</span>
|
||||
<a ui-sref="docker.secrets.secret({id: item.Id})">{{ item.Name }}</a>
|
||||
</td>
|
||||
<td>{{ item.CreatedAt | getisodate }}</td>
|
||||
<td ng-if="$ctrl.showOwnershipColumn">
|
||||
<span>
|
||||
<i ng-class="item.ResourceControl.Ownership | ownershipicon" aria-hidden="true"></i>
|
||||
{{ item.ResourceControl.Ownership ? item.ResourceControl.Ownership : item.ResourceControl.Ownership = 'public' }}
|
||||
</span>
|
||||
</td>
|
||||
</tr>
|
||||
<tr ng-if="!$ctrl.dataset">
|
||||
<td colspan="3" class="text-center text-muted">Loading...</td>
|
||||
</tr>
|
||||
<tr ng-if="$ctrl.state.filteredDataSet.length === 0">
|
||||
<td colspan="3" class="text-center text-muted">No secret available.</td>
|
||||
</tr>
|
||||
</tbody>
|
||||
</table>
|
||||
</div>
|
||||
<div class="footer" ng-if="$ctrl.dataset">
|
||||
<div class="infoBar" ng-if="$ctrl.state.selectedItemCount !== 0">
|
||||
{{ $ctrl.state.selectedItemCount }} item(s) selected
|
||||
</div>
|
||||
<div class="paginationControls">
|
||||
<form class="form-inline">
|
||||
<span class="limitSelector">
|
||||
<span style="margin-right: 5px;">
|
||||
Items per page
|
||||
</span>
|
||||
<select class="form-control" ng-model="$ctrl.state.paginatedItemLimit" ng-change="$ctrl.changePaginationLimit()">
|
||||
<option value="0">All</option>
|
||||
<option value="10">10</option>
|
||||
<option value="25">25</option>
|
||||
<option value="50">50</option>
|
||||
<option value="100">100</option>
|
||||
</select>
|
||||
</span>
|
||||
<dir-pagination-controls max-size="5"></dir-pagination-controls>
|
||||
</form>
|
||||
</div>
|
||||
</div>
|
||||
</rd-widget-body>
|
||||
</rd-widget>
|
||||
</div>
|
|
@ -0,0 +1,15 @@
|
|||
angular.module('portainer.docker').component('secretsDatatable', {
|
||||
templateUrl: 'app/docker/components/datatables/secrets-datatable/secretsDatatable.html',
|
||||
controller: 'GenericDatatableController',
|
||||
bindings: {
|
||||
title: '@',
|
||||
titleIcon: '@',
|
||||
dataset: '<',
|
||||
tableKey: '@',
|
||||
orderBy: '@',
|
||||
reverseOrder: '<',
|
||||
showTextFilter: '<',
|
||||
showOwnershipColumn: '<',
|
||||
removeAction: '<'
|
||||
}
|
||||
});
|
|
@ -0,0 +1,163 @@
|
|||
<div class="datatable">
|
||||
<rd-widget>
|
||||
<rd-widget-body classes="no-padding">
|
||||
<div class="toolBar">
|
||||
<div class="toolBarTitle">
|
||||
<i class="fa" ng-class="$ctrl.titleIcon" aria-hidden="true" style="margin-right: 2px;"></i> {{ $ctrl.title }}
|
||||
</div>
|
||||
<div class="settings">
|
||||
<span class="setting" ng-class="{ 'setting-active': $ctrl.state.displayTextFilter }" ng-click="$ctrl.updateDisplayTextFilter()" ng-if="$ctrl.showTextFilter">
|
||||
<i class="fa fa-search" aria-hidden="true"></i> Search
|
||||
</span>
|
||||
</div>
|
||||
</div>
|
||||
<div class="actionBar">
|
||||
<div class="btn-group" role="group" aria-label="...">
|
||||
<button ng-if="$ctrl.showForceUpdateButton" type="button" class="btn btn-sm btn-primary"
|
||||
ng-disabled="$ctrl.state.selectedItemCount === 0" ng-click="$ctrl.forceUpdateAction($ctrl.state.selectedItems)">
|
||||
<i class="fa fa-refresh space-right" aria-hidden="true"></i>Update
|
||||
</button>
|
||||
<button type="button" class="btn btn-sm btn-danger"
|
||||
ng-disabled="$ctrl.state.selectedItemCount === 0" ng-click="$ctrl.removeAction($ctrl.state.selectedItems)">
|
||||
<i class="fa fa-trash space-right" aria-hidden="true"></i>Remove
|
||||
</button>
|
||||
</div>
|
||||
<button type="button" class="btn btn-sm btn-primary" ui-sref="docker.services.new">
|
||||
<i class="fa fa-plus space-right" aria-hidden="true"></i>Add service
|
||||
</button>
|
||||
</div>
|
||||
<div class="searchBar" ng-if="$ctrl.state.displayTextFilter">
|
||||
<i class="fa fa-search searchIcon" aria-hidden="true"></i>
|
||||
<input type="text" class="searchInput" ng-model="$ctrl.state.textFilter" placeholder="Search...">
|
||||
</div>
|
||||
<div class="table-responsive">
|
||||
<table class="table table-hover">
|
||||
<thead>
|
||||
<tr>
|
||||
<th>
|
||||
<span class="md-checkbox">
|
||||
<input id="select_all" type="checkbox" ng-model="$ctrl.state.selectAll" ng-change="$ctrl.selectAll()" />
|
||||
<label for="select_all"></label>
|
||||
</span>
|
||||
<a ng-click="$ctrl.changeOrderBy('Name')">
|
||||
Name
|
||||
<i class="fa fa-sort-alpha-asc" aria-hidden="true" ng-if="$ctrl.state.orderBy === 'Name' && !$ctrl.state.reverseOrder"></i>
|
||||
<i class="fa fa-sort-alpha-desc" aria-hidden="true" ng-if="$ctrl.state.orderBy === 'Name' && $ctrl.state.reverseOrder"></i>
|
||||
</a>
|
||||
</th>
|
||||
<th>
|
||||
<a ng-click="$ctrl.changeOrderBy('StackName')">
|
||||
Stack
|
||||
<i class="fa fa-sort-alpha-asc" aria-hidden="true" ng-if="$ctrl.state.orderBy === 'StackName' && !$ctrl.state.reverseOrder"></i>
|
||||
<i class="fa fa-sort-alpha-desc" aria-hidden="true" ng-if="$ctrl.state.orderBy === 'StackName' && $ctrl.state.reverseOrder"></i>
|
||||
</a>
|
||||
</th>
|
||||
<th>
|
||||
<a ng-click="$ctrl.changeOrderBy('Image')">
|
||||
Image
|
||||
<i class="fa fa-sort-alpha-asc" aria-hidden="true" ng-if="$ctrl.state.orderBy === 'Image' && !$ctrl.state.reverseOrder"></i>
|
||||
<i class="fa fa-sort-alpha-desc" aria-hidden="true" ng-if="$ctrl.state.orderBy === 'Image' && $ctrl.state.reverseOrder"></i>
|
||||
</a>
|
||||
</th>
|
||||
<th>
|
||||
<a ng-click="$ctrl.changeOrderBy('Mode')">
|
||||
Scheduling Mode
|
||||
<i class="fa fa-sort-alpha-asc" aria-hidden="true" ng-if="$ctrl.state.orderBy === 'Mode' && !$ctrl.state.reverseOrder"></i>
|
||||
<i class="fa fa-sort-alpha-desc" aria-hidden="true" ng-if="$ctrl.state.orderBy === 'Mode' && $ctrl.state.reverseOrder"></i>
|
||||
</a>
|
||||
</th>
|
||||
<th>
|
||||
<a ng-click="$ctrl.changeOrderBy('Ports')">
|
||||
Published Ports
|
||||
<i class="fa fa-sort-alpha-asc" aria-hidden="true" ng-if="$ctrl.state.orderBy === 'Ports' && !$ctrl.state.reverseOrder"></i>
|
||||
<i class="fa fa-sort-alpha-desc" aria-hidden="true" ng-if="$ctrl.state.orderBy === 'Ports' && $ctrl.state.reverseOrder"></i>
|
||||
</a>
|
||||
</th>
|
||||
<th>
|
||||
<a ng-click="$ctrl.changeOrderBy('UpdatedAt')">
|
||||
Last Update
|
||||
<i class="fa fa-sort-alpha-asc" aria-hidden="true" ng-if="$ctrl.state.orderBy === 'UpdatedAt' && !$ctrl.state.reverseOrder"></i>
|
||||
<i class="fa fa-sort-alpha-desc" aria-hidden="true" ng-if="$ctrl.state.orderBy === 'UpdatedAt' && $ctrl.state.reverseOrder"></i>
|
||||
</a>
|
||||
</th>
|
||||
<th ng-if="$ctrl.showOwnershipColumn">
|
||||
<a ng-click="$ctrl.changeOrderBy('ResourceControl.Ownership')">
|
||||
Ownership
|
||||
<i class="fa fa-sort-alpha-asc" aria-hidden="true" ng-if="$ctrl.state.orderBy === 'ResourceControl.Ownership' && !$ctrl.state.reverseOrder"></i>
|
||||
<i class="fa fa-sort-alpha-desc" aria-hidden="true" ng-if="$ctrl.state.orderBy === 'ResourceControl.Ownership' && $ctrl.state.reverseOrder"></i>
|
||||
</a>
|
||||
</th>
|
||||
</tr>
|
||||
</thead>
|
||||
<tbody>
|
||||
<tr dir-paginate="item in ($ctrl.state.filteredDataSet = ($ctrl.dataset | filter:$ctrl.state.textFilter | orderBy:$ctrl.state.orderBy:$ctrl.state.reverseOrder | itemsPerPage: $ctrl.state.paginatedItemLimit))" ng-class="{active: item.Checked}">
|
||||
<td>
|
||||
<span class="md-checkbox">
|
||||
<input id="select_{{ $index }}" type="checkbox" ng-model="item.Checked" ng-change="$ctrl.selectItem(item)"/>
|
||||
<label for="select_{{ $index }}"></label>
|
||||
</span>
|
||||
<a ui-sref="docker.services.service({id: item.Id})">{{ item.Name }}</a>
|
||||
</td>
|
||||
<td>{{ item.StackName ? item.StackName : '-' }}</td>
|
||||
<td>{{ item.Image | hideshasum }}</td>
|
||||
<td>
|
||||
{{ item.Mode }} <code>{{ item.Running }}</code> / <code>{{ item.Replicas }}</code>
|
||||
<span ng-if="item.Mode === 'replicated' && !item.Scale">
|
||||
<a class="interactive" ng-click="item.Scale = true; item.ReplicaCount = item.Replicas;">
|
||||
<i class="fa fa-arrows-v" aria-hidden="true"></i> Scale
|
||||
</a>
|
||||
</span>
|
||||
<span ng-if="item.Mode === 'replicated' && item.Scale">
|
||||
<input class="input-sm" type="number" ng-model="item.Replicas" on-enter-key="$ctrl.scaleAction(item)" />
|
||||
<a class="interactive" ng-click="item.Scale = false;"><i class="fa fa-times"></i></a>
|
||||
<a class="interactive" ng-click="$ctrl.scaleAction(item)"><i class="fa fa-check-square-o"></i></a>
|
||||
</span>
|
||||
</td>
|
||||
<td>
|
||||
<a ng-if="item.Ports && item.Ports.length > 0 && $ctrl.swarmManagerIp && p.PublishedPort" ng-repeat="p in item.Ports" class="image-tag" ng-href="http://{{ $ctrl.swarmManagerIp }}:{{ p.PublishedPort }}" target="_blank">
|
||||
<i class="fa fa-external-link" aria-hidden="true"></i> {{ p.PublishedPort }}:{{ p.TargetPort }}
|
||||
</a>
|
||||
<span ng-if="!item.Ports || item.Ports.length === 0 || !$ctrl.swarmManagerIp" >-</span>
|
||||
</td>
|
||||
<td>{{ item.UpdatedAt | getisodate }}</td>
|
||||
<td ng-if="$ctrl.showOwnershipColumn">
|
||||
<span>
|
||||
<i ng-class="item.ResourceControl.Ownership | ownershipicon" aria-hidden="true"></i>
|
||||
{{ item.ResourceControl.Ownership ? item.ResourceControl.Ownership : item.ResourceControl.Ownership = 'public' }}
|
||||
</span>
|
||||
</td>
|
||||
</tr>
|
||||
<tr ng-if="!$ctrl.dataset">
|
||||
<td colspan="7" class="text-center text-muted">Loading...</td>
|
||||
</tr>
|
||||
<tr ng-if="$ctrl.state.filteredDataSet.length === 0">
|
||||
<td colspan="7" class="text-center text-muted">No service available.</td>
|
||||
</tr>
|
||||
</tbody>
|
||||
</table>
|
||||
</div>
|
||||
<div class="footer" ng-if="$ctrl.dataset">
|
||||
<div class="infoBar" ng-if="$ctrl.state.selectedItemCount !== 0">
|
||||
{{ $ctrl.state.selectedItemCount }} item(s) selected
|
||||
</div>
|
||||
<div class="paginationControls">
|
||||
<form class="form-inline">
|
||||
<span class="limitSelector">
|
||||
<span style="margin-right: 5px;">
|
||||
Items per page
|
||||
</span>
|
||||
<select class="form-control" ng-model="$ctrl.state.paginatedItemLimit" ng-change="$ctrl.changePaginationLimit()">
|
||||
<option value="0">All</option>
|
||||
<option value="10">10</option>
|
||||
<option value="25">25</option>
|
||||
<option value="50">50</option>
|
||||
<option value="100">100</option>
|
||||
</select>
|
||||
</span>
|
||||
<dir-pagination-controls max-size="5"></dir-pagination-controls>
|
||||
</form>
|
||||
</div>
|
||||
</div>
|
||||
</rd-widget-body>
|
||||
</rd-widget>
|
||||
</div>
|
|
@ -0,0 +1,19 @@
|
|||
angular.module('portainer.docker').component('servicesDatatable', {
|
||||
templateUrl: 'app/docker/components/datatables/services-datatable/servicesDatatable.html',
|
||||
controller: 'GenericDatatableController',
|
||||
bindings: {
|
||||
title: '@',
|
||||
titleIcon: '@',
|
||||
dataset: '<',
|
||||
tableKey: '@',
|
||||
orderBy: '@',
|
||||
reverseOrder: '<',
|
||||
showTextFilter: '<',
|
||||
showOwnershipColumn: '<',
|
||||
removeAction: '<',
|
||||
scaleAction: '<',
|
||||
swarmManagerIp: '<',
|
||||
forceUpdateAction: '<',
|
||||
showForceUpdateButton: '<'
|
||||
}
|
||||
});
|
|
@ -0,0 +1,97 @@
|
|||
<div class="datatable">
|
||||
<rd-widget>
|
||||
<rd-widget-body classes="no-padding">
|
||||
<div class="toolBar">
|
||||
<div class="toolBarTitle">
|
||||
<i class="fa" ng-class="$ctrl.titleIcon" aria-hidden="true" style="margin-right: 2px;"></i> {{ $ctrl.title }}
|
||||
</div>
|
||||
<div class="settings">
|
||||
<span class="setting" ng-class="{ 'setting-active': $ctrl.state.displayTextFilter }" ng-click="$ctrl.updateDisplayTextFilter()" ng-if="$ctrl.showTextFilter">
|
||||
<i class="fa fa-search" aria-hidden="true"></i> Search
|
||||
</span>
|
||||
</div>
|
||||
</div>
|
||||
<div class="searchBar" ng-if="$ctrl.state.displayTextFilter">
|
||||
<i class="fa fa-search searchIcon" aria-hidden="true"></i>
|
||||
<input type="text" class="searchInput" ng-model="$ctrl.state.textFilter" placeholder="Search...">
|
||||
</div>
|
||||
<div class="table-responsive">
|
||||
<table class="table table-hover">
|
||||
<thead>
|
||||
<tr>
|
||||
<th>
|
||||
<a ng-click="$ctrl.changeOrderBy('Id')">
|
||||
Id
|
||||
<i class="fa fa-sort-alpha-asc" aria-hidden="true" ng-if="$ctrl.state.orderBy === 'Id' && !$ctrl.state.reverseOrder"></i>
|
||||
<i class="fa fa-sort-alpha-desc" aria-hidden="true" ng-if="$ctrl.state.orderBy === 'Id' && $ctrl.state.reverseOrder"></i>
|
||||
</a>
|
||||
</th>
|
||||
<th>
|
||||
<a ng-click="$ctrl.changeOrderBy('Status')">
|
||||
Status
|
||||
<i class="fa fa-sort-alpha-asc" aria-hidden="true" ng-if="$ctrl.state.orderBy === 'Status' && !$ctrl.state.reverseOrder"></i>
|
||||
<i class="fa fa-sort-alpha-desc" aria-hidden="true" ng-if="$ctrl.state.orderBy === 'Status' && $ctrl.state.reverseOrder"></i>
|
||||
</a>
|
||||
</th>
|
||||
<th ng-if="$ctrl.showSlotColumn">
|
||||
<a ng-click="$ctrl.changeOrderBy('Slot')">
|
||||
Slot
|
||||
<i class="fa fa-sort-alpha-asc" aria-hidden="true" ng-if="$ctrl.state.orderBy === 'Slot' && !$ctrl.state.reverseOrder"></i>
|
||||
<i class="fa fa-sort-alpha-desc" aria-hidden="true" ng-if="$ctrl.state.orderBy === 'Slot' && $ctrl.state.reverseOrder"></i>
|
||||
</a>
|
||||
</th>
|
||||
<th>
|
||||
<a ng-click="$ctrl.changeOrderBy('NodeId')">
|
||||
Node
|
||||
<i class="fa fa-sort-alpha-asc" aria-hidden="true" ng-if="$ctrl.state.orderBy === 'NodeId' && !$ctrl.state.reverseOrder"></i>
|
||||
<i class="fa fa-sort-alpha-desc" aria-hidden="true" ng-if="$ctrl.state.orderBy === 'NodeId' && $ctrl.state.reverseOrder"></i>
|
||||
</a>
|
||||
</th>
|
||||
<th>
|
||||
<a ng-click="$ctrl.changeOrderBy('Updated')">
|
||||
Last Update
|
||||
<i class="fa fa-sort-alpha-asc" aria-hidden="true" ng-if="$ctrl.state.orderBy === 'Updated' && !$ctrl.state.reverseOrder"></i>
|
||||
<i class="fa fa-sort-alpha-desc" aria-hidden="true" ng-if="$ctrl.state.orderBy === 'Updated' && $ctrl.state.reverseOrder"></i>
|
||||
</a>
|
||||
</th>
|
||||
</tr>
|
||||
</thead>
|
||||
<tbody>
|
||||
<tr dir-paginate="item in ($ctrl.state.filteredDataSet = ($ctrl.dataset | filter:$ctrl.state.textFilter | orderBy:$ctrl.state.orderBy:$ctrl.state.reverseOrder | itemsPerPage: $ctrl.state.paginatedItemLimit))" ng-class="{active: item.Checked}">
|
||||
<td><a ui-sref="docker.tasks.task({id: item.Id})" class="monospaced">{{ item.Id }}</a></td>
|
||||
<td><span class="label label-{{ item.Status.State | taskstatusbadge }}">{{ item.Status.State }}</span></td>
|
||||
<td ng-if="$ctrl.showSlotColumn">{{ item.Slot ? item.Slot : '-' }}</td>
|
||||
<td>{{ item.NodeId | tasknodename: $ctrl.nodes }}</td>
|
||||
<td>{{ item.Updated | getisodate }}</td>
|
||||
</tr>
|
||||
<tr ng-if="!$ctrl.dataset">
|
||||
<td colspan="5" class="text-center text-muted">Loading...</td>
|
||||
</tr>
|
||||
<tr ng-if="$ctrl.state.filteredDataSet.length === 0">
|
||||
<td colspan="5" class="text-center text-muted">No task available.</td>
|
||||
</tr>
|
||||
</tbody>
|
||||
</table>
|
||||
</div>
|
||||
<div class="footer" ng-if="$ctrl.dataset">
|
||||
<div class="paginationControls">
|
||||
<form class="form-inline">
|
||||
<span class="limitSelector">
|
||||
<span style="margin-right: 5px;">
|
||||
Items per page
|
||||
</span>
|
||||
<select class="form-control" ng-model="$ctrl.state.paginatedItemLimit" ng-change="$ctrl.changePaginationLimit()">
|
||||
<option value="0">All</option>
|
||||
<option value="10">10</option>
|
||||
<option value="25">25</option>
|
||||
<option value="50">50</option>
|
||||
<option value="100">100</option>
|
||||
</select>
|
||||
</span>
|
||||
<dir-pagination-controls max-size="5"></dir-pagination-controls>
|
||||
</form>
|
||||
</div>
|
||||
</div>
|
||||
</rd-widget-body>
|
||||
</rd-widget>
|
||||
</div>
|
|
@ -0,0 +1,15 @@
|
|||
angular.module('portainer.docker').component('tasksDatatable', {
|
||||
templateUrl: 'app/docker/components/datatables/tasks-datatable/tasksDatatable.html',
|
||||
controller: 'GenericDatatableController',
|
||||
bindings: {
|
||||
title: '@',
|
||||
titleIcon: '@',
|
||||
dataset: '<',
|
||||
tableKey: '@',
|
||||
orderBy: '@',
|
||||
reverseOrder: '<',
|
||||
nodes: '<',
|
||||
showTextFilter: '<',
|
||||
showSlotColumn: '<'
|
||||
}
|
||||
});
|
|
@ -0,0 +1,149 @@
|
|||
<div class="datatable">
|
||||
<rd-widget>
|
||||
<rd-widget-body classes="no-padding">
|
||||
<div class="toolBar">
|
||||
<div class="toolBarTitle">
|
||||
<i class="fa" ng-class="$ctrl.titleIcon" aria-hidden="true" style="margin-right: 2px;"></i> {{ $ctrl.title }}
|
||||
</div>
|
||||
<div class="settings">
|
||||
<span class="setting" ng-class="{ 'setting-active': $ctrl.state.displayTextFilter }" ng-click="$ctrl.updateDisplayTextFilter()" ng-if="$ctrl.showTextFilter">
|
||||
<i class="fa fa-search" aria-hidden="true"></i> Search
|
||||
</span>
|
||||
</div>
|
||||
</div>
|
||||
<div class="actionBar">
|
||||
<button type="button" class="btn btn-sm btn-danger"
|
||||
ng-disabled="$ctrl.state.selectedItemCount === 0" ng-click="$ctrl.removeAction($ctrl.state.selectedItems)">
|
||||
<i class="fa fa-trash space-right" aria-hidden="true"></i>Remove
|
||||
</button>
|
||||
<button type="button" class="btn btn-sm btn-primary" ui-sref="docker.volumes.new">
|
||||
<i class="fa fa-plus space-right" aria-hidden="true"></i>Add volume
|
||||
</button>
|
||||
</div>
|
||||
<div class="searchBar" ng-if="$ctrl.state.displayTextFilter">
|
||||
<i class="fa fa-search searchIcon" aria-hidden="true"></i>
|
||||
<input type="text" class="searchInput" ng-model="$ctrl.state.textFilter" placeholder="Search...">
|
||||
</div>
|
||||
<div class="table-responsive">
|
||||
<table class="table table-hover table-filters">
|
||||
<thead>
|
||||
<tr>
|
||||
<th uib-dropdown dropdown-append-to-body auto-close="disabled" is-open="$ctrl.filters.usage.open">
|
||||
<span class="md-checkbox">
|
||||
<input id="select_all" type="checkbox" ng-model="$ctrl.state.selectAll" ng-change="$ctrl.selectAll()" />
|
||||
<label for="select_all"></label>
|
||||
</span>
|
||||
<a ng-click="$ctrl.changeOrderBy('Id')">
|
||||
Name
|
||||
<i class="fa fa-sort-alpha-asc" aria-hidden="true" ng-if="$ctrl.state.orderBy === 'Id' && !$ctrl.state.reverseOrder"></i>
|
||||
<i class="fa fa-sort-alpha-desc" aria-hidden="true" ng-if="$ctrl.state.orderBy === 'Id' && $ctrl.state.reverseOrder"></i>
|
||||
</a>
|
||||
<div>
|
||||
<span uib-dropdown-toggle class="table-filter" ng-if="!$ctrl.filters.usage.enabled">Filter <i class="fa fa-filter" aria-hidden="true"></i></span>
|
||||
<span uib-dropdown-toggle class="table-filter filter-active" ng-if="$ctrl.filters.usage.enabled">Filter <i class="fa fa-check" aria-hidden="true"></i></span>
|
||||
</div>
|
||||
<div class="dropdown-menu" uib-dropdown-menu>
|
||||
<div class="tableMenu">
|
||||
<div class="menuHeader">
|
||||
Filter by usage
|
||||
</div>
|
||||
<div class="menuContent">
|
||||
<div class="md-checkbox">
|
||||
<input id="filter_usage_usedImages" type="checkbox" ng-model="$ctrl.filters.usage.showUsedVolumes" ng-change="$ctrl.onUsageFilterChange()"/>
|
||||
<label for="filter_usage_usedImages">Used volumes</label>
|
||||
</div>
|
||||
<div class="md-checkbox">
|
||||
<input id="filter_usage_unusedImages" type="checkbox" ng-model="$ctrl.filters.usage.showUnusedVolumes" ng-change="$ctrl.onUsageFilterChange()"/>
|
||||
<label for="filter_usage_unusedImages">Unused volumes</label>
|
||||
</div>
|
||||
</div>
|
||||
<div>
|
||||
<a type="button" class="btn btn-default btn-sm" ng-click="$ctrl.filters.usage.open = false;">Close</a>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</th>
|
||||
<th>
|
||||
<a ng-click="$ctrl.changeOrderBy('StackName')">
|
||||
Stack
|
||||
<i class="fa fa-sort-alpha-asc" aria-hidden="true" ng-if="$ctrl.state.orderBy === 'StackName' && !$ctrl.state.reverseOrder"></i>
|
||||
<i class="fa fa-sort-alpha-desc" aria-hidden="true" ng-if="$ctrl.state.orderBy === 'StackName' && $ctrl.state.reverseOrder"></i>
|
||||
</a>
|
||||
</th>
|
||||
<th>
|
||||
<a ng-click="$ctrl.changeOrderBy('Driver')">
|
||||
Driver
|
||||
<i class="fa fa-sort-alpha-asc" aria-hidden="true" ng-if="$ctrl.state.orderBy === 'Driver' && !$ctrl.state.reverseOrder"></i>
|
||||
<i class="fa fa-sort-alpha-desc" aria-hidden="true" ng-if="$ctrl.state.orderBy === 'Driver' && $ctrl.state.reverseOrder"></i>
|
||||
</a>
|
||||
</th>
|
||||
<th>
|
||||
<a ng-click="$ctrl.changeOrderBy('Mountpoint')">
|
||||
Mount point
|
||||
<i class="fa fa-sort-alpha-asc" aria-hidden="true" ng-if="$ctrl.state.orderBy === 'Mountpoint' && !$ctrl.state.reverseOrder"></i>
|
||||
<i class="fa fa-sort-alpha-desc" aria-hidden="true" ng-if="$ctrl.state.orderBy === 'Mountpoint' && $ctrl.state.reverseOrder"></i>
|
||||
</a>
|
||||
</th>
|
||||
<th ng-if="$ctrl.showOwnershipColumn">
|
||||
<a ng-click="$ctrl.changeOrderBy('ResourceControl.Ownership')">
|
||||
Ownership
|
||||
<i class="fa fa-sort-alpha-asc" aria-hidden="true" ng-if="$ctrl.state.orderBy === 'ResourceControl.Ownership' && !$ctrl.state.reverseOrder"></i>
|
||||
<i class="fa fa-sort-alpha-desc" aria-hidden="true" ng-if="$ctrl.state.orderBy === 'ResourceControl.Ownership' && $ctrl.state.reverseOrder"></i>
|
||||
</a>
|
||||
</th>
|
||||
</tr>
|
||||
</thead>
|
||||
<tbody>
|
||||
<tr dir-paginate="item in ($ctrl.state.filteredDataSet = ($ctrl.dataset | filter: $ctrl.applyFilters | filter:$ctrl.state.textFilter | orderBy:$ctrl.state.orderBy:$ctrl.state.reverseOrder | itemsPerPage: $ctrl.state.paginatedItemLimit))" ng-class="{active: item.Checked}">
|
||||
<td>
|
||||
<span class="md-checkbox">
|
||||
<input id="select_{{ $index }}" type="checkbox" ng-model="item.Checked" ng-change="$ctrl.selectItem(item)"/>
|
||||
<label for="select_{{ $index }}"></label>
|
||||
</span>
|
||||
<a ui-sref="docker.volumes.volume({id: item.Id})" class="monospaced">{{ item.Id | truncate:25 }}</a>
|
||||
<span style="margin-left: 10px;" class="label label-warning image-tag" ng-if="item.dangling">Unused</span>
|
||||
</td>
|
||||
<td>{{ item.StackName ? item.StackName : '-' }}</td>
|
||||
<td>{{ item.Driver }}</td>
|
||||
<td>{{ item.Mountpoint | truncatelr }}</td>
|
||||
<td ng-if="$ctrl.showOwnershipColumn">
|
||||
<span>
|
||||
<i ng-class="item.ResourceControl.Ownership | ownershipicon" aria-hidden="true"></i>
|
||||
{{ item.ResourceControl.Ownership ? item.ResourceControl.Ownership : item.ResourceControl.Ownership = 'public' }}
|
||||
</span>
|
||||
</td>
|
||||
</tr>
|
||||
<tr ng-if="!$ctrl.dataset">
|
||||
<td colspan="5" class="text-center text-muted">Loading...</td>
|
||||
</tr>
|
||||
<tr ng-if="$ctrl.state.filteredDataSet.length === 0">
|
||||
<td colspan="5" class="text-center text-muted">No volume available.</td>
|
||||
</tr>
|
||||
</tbody>
|
||||
</table>
|
||||
</div>
|
||||
<div class="footer" ng-if="$ctrl.dataset">
|
||||
<div class="infoBar" ng-if="$ctrl.state.selectedItemCount !== 0">
|
||||
{{ $ctrl.state.selectedItemCount }} item(s) selected
|
||||
</div>
|
||||
<div class="paginationControls">
|
||||
<form class="form-inline">
|
||||
<span class="limitSelector">
|
||||
<span style="margin-right: 5px;">
|
||||
Items per page
|
||||
</span>
|
||||
<select class="form-control" ng-model="$ctrl.state.paginatedItemLimit" ng-change="$ctrl.changePaginationLimit()">
|
||||
<option value="0">All</option>
|
||||
<option value="10">10</option>
|
||||
<option value="25">25</option>
|
||||
<option value="50">50</option>
|
||||
<option value="100">100</option>
|
||||
</select>
|
||||
</span>
|
||||
<dir-pagination-controls max-size="5"></dir-pagination-controls>
|
||||
</form>
|
||||
</div>
|
||||
</div>
|
||||
</rd-widget-body>
|
||||
</rd-widget>
|
||||
</div>
|
|
@ -0,0 +1,15 @@
|
|||
angular.module('portainer.docker').component('volumesDatatable', {
|
||||
templateUrl: 'app/docker/components/datatables/volumes-datatable/volumesDatatable.html',
|
||||
controller: 'VolumesDatatableController',
|
||||
bindings: {
|
||||
title: '@',
|
||||
titleIcon: '@',
|
||||
dataset: '<',
|
||||
tableKey: '@',
|
||||
orderBy: '@',
|
||||
reverseOrder: '<',
|
||||
showTextFilter: '<',
|
||||
showOwnershipColumn: '<',
|
||||
removeAction: '<'
|
||||
}
|
||||
});
|
|
@ -0,0 +1,102 @@
|
|||
angular.module('portainer.docker')
|
||||
.controller('VolumesDatatableController', ['PaginationService', 'DatatableService',
|
||||
function (PaginationService, DatatableService) {
|
||||
|
||||
var ctrl = this;
|
||||
|
||||
this.state = {
|
||||
selectAll: false,
|
||||
orderBy: this.orderBy,
|
||||
paginatedItemLimit: PaginationService.getPaginationLimit(this.tableKey),
|
||||
displayTextFilter: false,
|
||||
selectedItemCount: 0,
|
||||
selectedItems: []
|
||||
};
|
||||
|
||||
this.filters = {
|
||||
usage: {
|
||||
open: false,
|
||||
enabled: false,
|
||||
showUsedVolumes: true,
|
||||
showUnusedVolumes: true
|
||||
}
|
||||
};
|
||||
|
||||
this.changeOrderBy = function(orderField) {
|
||||
this.state.reverseOrder = this.state.orderBy === orderField ? !this.state.reverseOrder : false;
|
||||
this.state.orderBy = orderField;
|
||||
DatatableService.setDataTableOrder(this.tableKey, orderField, this.state.reverseOrder);
|
||||
};
|
||||
|
||||
this.selectItem = function(item) {
|
||||
if (item.Checked) {
|
||||
this.state.selectedItemCount++;
|
||||
this.state.selectedItems.push(item);
|
||||
} else {
|
||||
this.state.selectedItems.splice(this.state.selectedItems.indexOf(item), 1);
|
||||
this.state.selectedItemCount--;
|
||||
}
|
||||
};
|
||||
|
||||
this.selectAll = function() {
|
||||
for (var i = 0; i < this.state.filteredDataSet.length; i++) {
|
||||
var item = this.state.filteredDataSet[i];
|
||||
if (item.Checked !== this.state.selectAll) {
|
||||
item.Checked = this.state.selectAll;
|
||||
this.selectItem(item);
|
||||
}
|
||||
}
|
||||
};
|
||||
|
||||
this.changePaginationLimit = function() {
|
||||
PaginationService.setPaginationLimit(this.tableKey, this.state.paginatedItemLimit);
|
||||
};
|
||||
|
||||
this.updateDisplayTextFilter = function() {
|
||||
this.state.displayTextFilter = !this.state.displayTextFilter;
|
||||
if (!this.state.displayTextFilter) {
|
||||
delete this.state.textFilter;
|
||||
}
|
||||
};
|
||||
|
||||
this.applyFilters = function(value, index, array) {
|
||||
var volume = value;
|
||||
var filters = ctrl.filters;
|
||||
if ((volume.dangling && filters.usage.showUnusedVolumes)
|
||||
|| (!volume.dangling && filters.usage.showUsedVolumes)) {
|
||||
return true;
|
||||
}
|
||||
return false;
|
||||
};
|
||||
|
||||
this.onUsageFilterChange = function() {
|
||||
var filters = this.filters.usage;
|
||||
var filtered = false;
|
||||
if (!filters.showUsedVolumes || !filters.showUnusedVolumes) {
|
||||
filtered = true;
|
||||
}
|
||||
this.filters.usage.enabled = filtered;
|
||||
DatatableService.setDataTableFilters(this.tableKey, this.filters);
|
||||
};
|
||||
|
||||
this.$onInit = function() {
|
||||
setDefaults(this);
|
||||
|
||||
var storedOrder = DatatableService.getDataTableOrder(this.tableKey);
|
||||
if (storedOrder !== null) {
|
||||
this.state.reverseOrder = storedOrder.reverse;
|
||||
this.state.orderBy = storedOrder.orderBy;
|
||||
}
|
||||
|
||||
var storedFilters = DatatableService.getDataTableFilters(this.tableKey);
|
||||
if (storedFilters !== null) {
|
||||
this.filters = storedFilters;
|
||||
}
|
||||
this.filters.usage.open = false;
|
||||
};
|
||||
|
||||
function setDefaults(ctrl) {
|
||||
ctrl.showTextFilter = ctrl.showTextFilter ? ctrl.showTextFilter : false;
|
||||
ctrl.state.reverseOrder = ctrl.reverseOrder ? ctrl.reverseOrder : false;
|
||||
}
|
||||
}]);
|
|
@ -0,0 +1,12 @@
|
|||
angular.module('portainer.docker').component('dockerSidebarContent', {
|
||||
templateUrl: 'app/docker/components/dockerSidebarContent/dockerSidebarContent.html',
|
||||
bindings: {
|
||||
'endpointApiVersion': '<',
|
||||
'swarmManagement': '<',
|
||||
'standaloneManagement': '<',
|
||||
'adminAccess': '<',
|
||||
'externalContributions': '<',
|
||||
'sidebarToggledOn': '<',
|
||||
'currentState': '<'
|
||||
}
|
||||
});
|
|
@ -0,0 +1,42 @@
|
|||
<li class="sidebar-list">
|
||||
<a ui-sref="docker.dashboard" ui-sref-active="active">Dashboard <span class="menu-icon fa fa-tachometer fa-fw"></span></a>
|
||||
</li>
|
||||
<li class="sidebar-list">
|
||||
<a ui-sref="docker.templates" ui-sref-active="active">App Templates <span class="menu-icon fa fa-rocket fa-fw"></span></a>
|
||||
<div class="sidebar-sublist" ng-if="$ctrl.sidebarToggledOn && $ctrl.externalContributions && ($ctrl.currentState === 'docker.templates' || $ctrl.currentState === 'docker.templates.linuxserver')">
|
||||
<a ui-sref="docker.templates.linuxserver" ui-sref-active="active">LinuxServer.io</a>
|
||||
</div>
|
||||
</li>
|
||||
<li class="sidebar-list" ng-if="$ctrl.endpointApiVersion >= 1.25 && $ctrl.swarmManagement">
|
||||
<a ui-sref="docker.stacks" ui-sref-active="active">Stacks <span class="menu-icon fa fa-th-list fa-fw"></span></a>
|
||||
</li>
|
||||
<li class="sidebar-list" ng-if="$ctrl.swarmManagement">
|
||||
<a ui-sref="docker.services" ui-sref-active="active">Services <span class="menu-icon fa fa-list-alt fa-fw"></span></a>
|
||||
</li>
|
||||
<li class="sidebar-list">
|
||||
<a ui-sref="docker.containers" ui-sref-active="active">Containers <span class="menu-icon fa fa-server fa-fw"></span></a>
|
||||
</li>
|
||||
<li class="sidebar-list">
|
||||
<a ui-sref="docker.images" ui-sref-active="active">Images <span class="menu-icon fa fa-clone fa-fw"></span></a>
|
||||
</li>
|
||||
<li class="sidebar-list">
|
||||
<a ui-sref="docker.networks" ui-sref-active="active">Networks <span class="menu-icon fa fa-sitemap fa-fw"></span></a>
|
||||
</li>
|
||||
<li class="sidebar-list">
|
||||
<a ui-sref="docker.volumes" ui-sref-active="active">Volumes <span class="menu-icon fa fa-cubes fa-fw"></span></a>
|
||||
</li>
|
||||
<li class="sidebar-list" ng-if="$ctrl.endpointApiVersion >= 1.30 && $ctrl.swarmManagement">
|
||||
<a ui-sref="docker.configs" ui-sref-active="active">Configs <span class="menu-icon fa fa-file-code-o fa-fw"></span></a>
|
||||
</li>
|
||||
<li class="sidebar-list" ng-if="$ctrl.endpointApiVersion >= 1.25 && $ctrl.swarmManagement">
|
||||
<a ui-sref="docker.secrets" ui-sref-active="active">Secrets <span class="menu-icon fa fa-user-secret fa-fw"></span></a>
|
||||
</li>
|
||||
<li class="sidebar-list" ng-if="$ctrl.standaloneManagement && $ctrl.adminAccess">
|
||||
<a ui-sref="docker.events" ui-sref-active="active">Events <span class="menu-icon fa fa-history fa-fw"></span></a>
|
||||
</li>
|
||||
<li class="sidebar-list" ng-if="$ctrl.swarmManagement">
|
||||
<a ui-sref="docker.swarm" ui-sref-active="active">Swarm <span class="menu-icon fa fa-object-group fa-fw"></span></a>
|
||||
</li>
|
||||
<li class="sidebar-list" ng-if="$ctrl.standaloneManagement">
|
||||
<a ui-sref="docker.engine" ui-sref-active="active">Engine <span class="menu-icon fa fa-th fa-fw"></span></a>
|
||||
</li>
|
|
@ -0,0 +1,9 @@
|
|||
angular.module('portainer.docker').component('porImageRegistry', {
|
||||
templateUrl: 'app/docker/components/imageRegistry/porImageRegistry.html',
|
||||
controller: 'porImageRegistryController',
|
||||
bindings: {
|
||||
'image': '=',
|
||||
'registry': '=',
|
||||
'autoComplete': '<'
|
||||
}
|
||||
});
|
14
app/docker/components/imageRegistry/porImageRegistry.html
Normal file
14
app/docker/components/imageRegistry/porImageRegistry.html
Normal file
|
@ -0,0 +1,14 @@
|
|||
<div>
|
||||
<label for="image_name" class="col-sm-1 control-label text-left">Name</label>
|
||||
<div class="col-sm-11 col-md-6">
|
||||
<input type="text" class="form-control" uib-typeahead="image for image in $ctrl.availableImages | filter:$viewValue | limitTo:5"
|
||||
ng-model="$ctrl.image" id="image_name" placeholder="e.g. myImage:myTag">
|
||||
</div>
|
||||
<label for="image_registry" class="col-sm-2 col-md-1 margin-sm-top control-label text-left">
|
||||
Registry
|
||||
</label>
|
||||
<div class="col-sm-10 col-md-4 margin-sm-top">
|
||||
<select ng-options="registry as registry.Name for registry in $ctrl.availableRegistries" ng-model="$ctrl.registry" id="image_registry"
|
||||
class="form-control"></select>
|
||||
</div>
|
||||
</div>
|
|
@ -0,0 +1,29 @@
|
|||
angular.module('portainer.docker')
|
||||
.controller('porImageRegistryController', ['$q', 'RegistryService', 'DockerHubService', 'ImageService', 'Notifications',
|
||||
function ($q, RegistryService, DockerHubService, ImageService, Notifications) {
|
||||
var ctrl = this;
|
||||
|
||||
function initComponent() {
|
||||
$q.all({
|
||||
registries: RegistryService.registries(),
|
||||
dockerhub: DockerHubService.dockerhub(),
|
||||
availableImages: ctrl.autoComplete ? ImageService.images() : []
|
||||
})
|
||||
.then(function success(data) {
|
||||
var dockerhub = data.dockerhub;
|
||||
var registries = data.registries;
|
||||
ctrl.availableImages = ImageService.getUniqueTagListFromImages(data.availableImages);
|
||||
ctrl.availableRegistries = [dockerhub].concat(registries);
|
||||
if (!ctrl.registry.Id) {
|
||||
ctrl.registry = dockerhub;
|
||||
} else {
|
||||
ctrl.registry = _.find(ctrl.availableRegistries, { 'Id': ctrl.registry.Id });
|
||||
}
|
||||
})
|
||||
.catch(function error(err) {
|
||||
Notifications.error('Failure', err, 'Unable to retrieve registries');
|
||||
});
|
||||
}
|
||||
|
||||
initComponent();
|
||||
}]);
|
239
app/docker/filters/filters.js
Normal file
239
app/docker/filters/filters.js
Normal file
|
@ -0,0 +1,239 @@
|
|||
function includeString(text, values) {
|
||||
return values.some(function(val){
|
||||
return text.indexOf(val) !== -1;
|
||||
});
|
||||
}
|
||||
|
||||
angular.module('portainer.docker')
|
||||
.filter('visualizerTask', function () {
|
||||
'use strict';
|
||||
return function (text) {
|
||||
var status = _.toLower(text);
|
||||
if (includeString(status, ['new', 'allocated', 'assigned', 'accepted', 'complete', 'preparing'])) {
|
||||
return 'info';
|
||||
} else if (includeString(status, ['pending'])) {
|
||||
return 'warning';
|
||||
} else if (includeString(status, ['shutdown', 'failed', 'rejected'])) {
|
||||
return 'stopped';
|
||||
}
|
||||
return 'running';
|
||||
};
|
||||
})
|
||||
.filter('taskstatusbadge', function () {
|
||||
'use strict';
|
||||
return function (text) {
|
||||
var status = _.toLower(text);
|
||||
var labelStyle = 'default';
|
||||
if (includeString(status, ['new', 'allocated', 'assigned', 'accepted', 'preparing', 'ready', 'starting', 'remove'])) {
|
||||
labelStyle = 'info';
|
||||
} else if (includeString(status, ['pending'])) {
|
||||
labelStyle = 'warning';
|
||||
} else if (includeString(status, ['shutdown', 'failed', 'rejected', 'orphaned'])) {
|
||||
labelStyle = 'danger';
|
||||
} else if (includeString(status, ['complete'])) {
|
||||
labelStyle = 'primary';
|
||||
} else if (includeString(status, ['running'])) {
|
||||
labelStyle = 'success';
|
||||
}
|
||||
return labelStyle;
|
||||
};
|
||||
})
|
||||
.filter('containerstatusbadge', function () {
|
||||
'use strict';
|
||||
return function (text) {
|
||||
var status = _.toLower(text);
|
||||
if (includeString(status, ['paused', 'starting'])) {
|
||||
return 'warning';
|
||||
} else if (includeString(status, ['created'])) {
|
||||
return 'info';
|
||||
} else if (includeString(status, ['stopped', 'unhealthy', 'dead', 'exited'])) {
|
||||
return 'danger';
|
||||
}
|
||||
return 'success';
|
||||
};
|
||||
})
|
||||
.filter('containerstatus', function () {
|
||||
'use strict';
|
||||
return function (text) {
|
||||
var status = _.toLower(text);
|
||||
if (includeString(status, ['paused'])) {
|
||||
return 'paused';
|
||||
} else if (includeString(status, ['dead'])) {
|
||||
return 'dead';
|
||||
} else if (includeString(status, ['created'])) {
|
||||
return 'created';
|
||||
} else if (includeString(status, ['exited'])) {
|
||||
return 'stopped';
|
||||
} else if (includeString(status, ['(healthy)'])) {
|
||||
return 'healthy';
|
||||
} else if (includeString(status, ['(unhealthy)'])) {
|
||||
return 'unhealthy';
|
||||
} else if (includeString(status, ['(health: starting)'])) {
|
||||
return 'starting';
|
||||
}
|
||||
return 'running';
|
||||
};
|
||||
})
|
||||
.filter('nodestatusbadge', function () {
|
||||
'use strict';
|
||||
return function (text) {
|
||||
if (text === 'down' || text === 'Unhealthy') {
|
||||
return 'danger';
|
||||
}
|
||||
return 'success';
|
||||
};
|
||||
})
|
||||
.filter('trimcontainername', function () {
|
||||
'use strict';
|
||||
return function (name) {
|
||||
if (name) {
|
||||
return (name.indexOf('/') === 0 ? name.replace('/','') : name);
|
||||
}
|
||||
return '';
|
||||
};
|
||||
})
|
||||
.filter('getstatetext', function () {
|
||||
'use strict';
|
||||
return function (state) {
|
||||
if (state === undefined) {
|
||||
return '';
|
||||
}
|
||||
if (state.Dead) {
|
||||
return 'Dead';
|
||||
}
|
||||
if (state.Ghost && state.Running) {
|
||||
return 'Ghost';
|
||||
}
|
||||
if (state.Running && state.Paused) {
|
||||
return 'Running (Paused)';
|
||||
}
|
||||
if (state.Running) {
|
||||
return 'Running';
|
||||
}
|
||||
if (state.Status === 'created') {
|
||||
return 'Created';
|
||||
}
|
||||
return 'Stopped';
|
||||
};
|
||||
})
|
||||
.filter('getstatelabel', function () {
|
||||
'use strict';
|
||||
return function (state) {
|
||||
if (state === undefined) {
|
||||
return 'label-default';
|
||||
}
|
||||
if (state.Ghost && state.Running) {
|
||||
return 'label-important';
|
||||
}
|
||||
if (state.Running) {
|
||||
return 'label-success';
|
||||
}
|
||||
return 'label-default';
|
||||
};
|
||||
})
|
||||
.filter('containername', function () {
|
||||
'use strict';
|
||||
return function (container) {
|
||||
var name = container.Names[0];
|
||||
return name.substring(1, name.length);
|
||||
};
|
||||
})
|
||||
.filter('swarmcontainername', function () {
|
||||
'use strict';
|
||||
return function (container) {
|
||||
return _.split(container.Names[0], '/')[2];
|
||||
};
|
||||
})
|
||||
.filter('swarmversion', function () {
|
||||
'use strict';
|
||||
return function (text) {
|
||||
return _.split(text, '/')[1];
|
||||
};
|
||||
})
|
||||
.filter('swarmhostname', function () {
|
||||
'use strict';
|
||||
return function (container) {
|
||||
return _.split(container.Names[0], '/')[1];
|
||||
};
|
||||
})
|
||||
.filter('repotags', function () {
|
||||
'use strict';
|
||||
return function (image) {
|
||||
if (image.RepoTags && image.RepoTags.length > 0) {
|
||||
var tag = image.RepoTags[0];
|
||||
if (tag === '<none>:<none>') {
|
||||
return [];
|
||||
}
|
||||
return image.RepoTags;
|
||||
}
|
||||
return [];
|
||||
};
|
||||
})
|
||||
.filter('command', function () {
|
||||
'use strict';
|
||||
return function (command) {
|
||||
if (command) {
|
||||
return command.join(' ');
|
||||
}
|
||||
};
|
||||
})
|
||||
.filter('hideshasum', function () {
|
||||
'use strict';
|
||||
return function (imageName) {
|
||||
if (imageName) {
|
||||
return imageName.split('@sha')[0];
|
||||
}
|
||||
return '';
|
||||
};
|
||||
})
|
||||
.filter('availablenodecount', function () {
|
||||
'use strict';
|
||||
return function (nodes) {
|
||||
var availableNodes = 0;
|
||||
for (var i = 0; i < nodes.length; i++) {
|
||||
var node = nodes[i];
|
||||
if (node.Availability === 'active' && node.Status === 'ready') {
|
||||
availableNodes++;
|
||||
}
|
||||
}
|
||||
return availableNodes;
|
||||
};
|
||||
})
|
||||
.filter('runningtaskscount', function () {
|
||||
'use strict';
|
||||
return function (tasks) {
|
||||
var runningTasks = 0;
|
||||
for (var i = 0; i < tasks.length; i++) {
|
||||
var task = tasks[i];
|
||||
if (task.Status.State === 'running') {
|
||||
runningTasks++;
|
||||
}
|
||||
}
|
||||
return runningTasks;
|
||||
};
|
||||
})
|
||||
.filter('tasknodename', function () {
|
||||
'use strict';
|
||||
return function (nodeId, nodes) {
|
||||
var node = _.find(nodes, { Id: nodeId });
|
||||
if (node) {
|
||||
return node.Hostname;
|
||||
}
|
||||
return '';
|
||||
};
|
||||
})
|
||||
.filter('imagelayercommand', function () {
|
||||
'use strict';
|
||||
return function (createdBy) {
|
||||
return createdBy.replace('/bin/sh -c #(nop) ', '').replace('/bin/sh -c ', 'RUN ');
|
||||
};
|
||||
})
|
||||
.filter('trimshasum', function () {
|
||||
'use strict';
|
||||
return function (imageName) {
|
||||
if (imageName.indexOf('sha256:') === 0) {
|
||||
return imageName.substring(7, 19);
|
||||
}
|
||||
return _.split(imageName, '@sha256')[0];
|
||||
};
|
||||
});
|
34
app/docker/helpers/configHelper.js
Normal file
34
app/docker/helpers/configHelper.js
Normal file
|
@ -0,0 +1,34 @@
|
|||
angular.module('portainer.docker')
|
||||
.factory('ConfigHelper', [function ConfigHelperFactory() {
|
||||
'use strict';
|
||||
return {
|
||||
flattenConfig: function(config) {
|
||||
if (config) {
|
||||
return {
|
||||
Id: config.ConfigID,
|
||||
Name: config.ConfigName,
|
||||
FileName: config.File.Name,
|
||||
Uid: config.File.UID,
|
||||
Gid: config.File.GID,
|
||||
Mode: config.File.Mode
|
||||
};
|
||||
}
|
||||
return {};
|
||||
},
|
||||
configConfig: function(config) {
|
||||
if (config) {
|
||||
return {
|
||||
ConfigID: config.Id,
|
||||
ConfigName: config.Name,
|
||||
File: {
|
||||
Name: config.FileName || config.Name,
|
||||
UID: config.Uid || '0',
|
||||
GID: config.Gid || '0',
|
||||
Mode: config.Mode || 292
|
||||
}
|
||||
};
|
||||
}
|
||||
return {};
|
||||
}
|
||||
};
|
||||
}]);
|
62
app/docker/helpers/containerHelper.js
Normal file
62
app/docker/helpers/containerHelper.js
Normal file
|
@ -0,0 +1,62 @@
|
|||
angular.module('portainer.docker')
|
||||
.factory('ContainerHelper', [function ContainerHelperFactory() {
|
||||
'use strict';
|
||||
var helper = {};
|
||||
|
||||
helper.commandStringToArray = function(command) {
|
||||
return splitargs(command);
|
||||
};
|
||||
|
||||
helper.commandArrayToString = function(array) {
|
||||
return array.map(function(elem) {
|
||||
return '\'' + elem + '\'';
|
||||
}).join(' ');
|
||||
};
|
||||
|
||||
helper.configFromContainer = function(container) {
|
||||
var config = container.Config;
|
||||
// HostConfig
|
||||
config.HostConfig = container.HostConfig;
|
||||
// Name
|
||||
config.name = container.Name.replace(/^\//g, '');
|
||||
// Network
|
||||
var mode = config.HostConfig.NetworkMode;
|
||||
config.NetworkingConfig = {
|
||||
'EndpointsConfig': {}
|
||||
};
|
||||
config.NetworkingConfig.EndpointsConfig = container.NetworkSettings.Networks;
|
||||
if (mode.indexOf('container:') !== -1) {
|
||||
delete config.Hostname;
|
||||
delete config.ExposedPorts;
|
||||
}
|
||||
// Set volumes
|
||||
var binds = [];
|
||||
var volumes = {};
|
||||
for (var v in container.Mounts) {
|
||||
if ({}.hasOwnProperty.call(container.Mounts, v)) {
|
||||
var mount = container.Mounts[v];
|
||||
var volume = {
|
||||
'type': mount.Type,
|
||||
'name': mount.Name || mount.Source,
|
||||
'containerPath': mount.Destination,
|
||||
'readOnly': mount.RW === false
|
||||
};
|
||||
var name = mount.Name || mount.Source;
|
||||
var containerPath = mount.Destination;
|
||||
if (name && containerPath) {
|
||||
var bind = name + ':' + containerPath;
|
||||
volumes[containerPath] = {};
|
||||
if (mount.RW === false) {
|
||||
bind += ':ro';
|
||||
}
|
||||
binds.push(bind);
|
||||
}
|
||||
}
|
||||
}
|
||||
config.HostConfig.Binds = binds;
|
||||
config.Volumes = volumes;
|
||||
return config;
|
||||
};
|
||||
|
||||
return helper;
|
||||
}]);
|
59
app/docker/helpers/imageHelper.js
Normal file
59
app/docker/helpers/imageHelper.js
Normal file
|
@ -0,0 +1,59 @@
|
|||
angular.module('portainer.docker')
|
||||
.factory('ImageHelper', [function ImageHelperFactory() {
|
||||
'use strict';
|
||||
|
||||
var helper = {};
|
||||
|
||||
helper.extractImageAndRegistryFromRepository = function(repository) {
|
||||
var slashCount = _.countBy(repository)['/'];
|
||||
var registry = null;
|
||||
var image = repository;
|
||||
if (slashCount >= 1) {
|
||||
// assume something/something[/...]
|
||||
registry = repository.substr(0, repository.indexOf('/'));
|
||||
// assume valid DNS name or IP (contains at least one '.')
|
||||
if (_.countBy(registry)['.'] > 0) {
|
||||
image = repository.substr(repository.indexOf('/') + 1);
|
||||
} else {
|
||||
registry = null;
|
||||
}
|
||||
}
|
||||
|
||||
return {
|
||||
registry: registry,
|
||||
image: image
|
||||
};
|
||||
};
|
||||
|
||||
function extractNameAndTag(imageName, registry) {
|
||||
var imageNameAndTag = imageName.split(':');
|
||||
var image = imageNameAndTag[0];
|
||||
var tag = imageNameAndTag[1] ? imageNameAndTag[1] : 'latest';
|
||||
if (registry) {
|
||||
image = registry + '/' + imageNameAndTag[0];
|
||||
}
|
||||
|
||||
return {
|
||||
image: image,
|
||||
tag: tag
|
||||
};
|
||||
}
|
||||
|
||||
helper.createImageConfigForCommit = function(imageName, registry) {
|
||||
var imageAndTag = extractNameAndTag(imageName, registry);
|
||||
return {
|
||||
repo: imageAndTag.image,
|
||||
tag: imageAndTag.tag
|
||||
};
|
||||
};
|
||||
|
||||
helper.createImageConfigForContainer = function (imageName, registry) {
|
||||
var imageAndTag = extractNameAndTag(imageName, registry);
|
||||
return {
|
||||
fromImage: imageAndTag.image,
|
||||
tag: imageAndTag.tag
|
||||
};
|
||||
};
|
||||
|
||||
return helper;
|
||||
}]);
|
36
app/docker/helpers/infoHelper.js
Normal file
36
app/docker/helpers/infoHelper.js
Normal file
|
@ -0,0 +1,36 @@
|
|||
angular.module('portainer.docker')
|
||||
.factory('InfoHelper', [function InfoHelperFactory() {
|
||||
'use strict';
|
||||
return {
|
||||
determineEndpointMode: function(info) {
|
||||
var mode = {
|
||||
provider: '',
|
||||
role: ''
|
||||
};
|
||||
if (_.startsWith(info.ServerVersion, 'swarm')) {
|
||||
mode.provider = 'DOCKER_SWARM';
|
||||
if (info.SystemStatus[0][1] === 'primary') {
|
||||
mode.role = 'PRIMARY';
|
||||
} else {
|
||||
mode.role = 'REPLICA';
|
||||
}
|
||||
} else {
|
||||
if (!info.Swarm || _.isEmpty(info.Swarm.NodeID)) {
|
||||
if (info.ID === 'vSphere Integrated Containers') {
|
||||
mode.provider = 'VMWARE_VIC';
|
||||
} else {
|
||||
mode.provider = 'DOCKER_STANDALONE';
|
||||
}
|
||||
} else {
|
||||
mode.provider = 'DOCKER_SWARM_MODE';
|
||||
if (info.Swarm.ControlAvailable) {
|
||||
mode.role = 'MANAGER';
|
||||
} else {
|
||||
mode.role = 'WORKER';
|
||||
}
|
||||
}
|
||||
}
|
||||
return mode;
|
||||
}
|
||||
};
|
||||
}]);
|
23
app/docker/helpers/labelHelper.js
Normal file
23
app/docker/helpers/labelHelper.js
Normal file
|
@ -0,0 +1,23 @@
|
|||
angular.module('portainer.docker')
|
||||
.factory('LabelHelper', [function LabelHelperFactory() {
|
||||
'use strict';
|
||||
return {
|
||||
fromLabelHashToKeyValue: function(labels) {
|
||||
if (labels) {
|
||||
return Object.keys(labels).map(function(key) {
|
||||
return {key: key, value: labels[key], originalKey: key, originalValue: labels[key], added: true};
|
||||
});
|
||||
}
|
||||
return [];
|
||||
},
|
||||
fromKeyValueToLabelHash: function(labelKV) {
|
||||
var labels = {};
|
||||
if (labelKV) {
|
||||
labelKV.forEach(function(label) {
|
||||
labels[label.key] = label.value;
|
||||
});
|
||||
}
|
||||
return labels;
|
||||
}
|
||||
};
|
||||
}]);
|
24
app/docker/helpers/nodeHelper.js
Normal file
24
app/docker/helpers/nodeHelper.js
Normal file
|
@ -0,0 +1,24 @@
|
|||
angular.module('portainer.docker')
|
||||
.factory('NodeHelper', [function NodeHelperFactory() {
|
||||
'use strict';
|
||||
return {
|
||||
nodeToConfig: function(node) {
|
||||
return {
|
||||
Name: node.Spec.Name,
|
||||
Role: node.Spec.Role,
|
||||
Labels: node.Spec.Labels,
|
||||
Availability: node.Spec.Availability
|
||||
};
|
||||
},
|
||||
getManagerIP: function(nodes) {
|
||||
var managerIp;
|
||||
for (var n in nodes) {
|
||||
if (undefined === nodes[n].ManagerStatus || nodes[n].ManagerStatus.Reachability !== 'reachable') {
|
||||
continue;
|
||||
}
|
||||
managerIp = nodes[n].ManagerStatus.Addr.split(':')[0];
|
||||
}
|
||||
return managerIp;
|
||||
}
|
||||
};
|
||||
}]);
|
34
app/docker/helpers/secretHelper.js
Normal file
34
app/docker/helpers/secretHelper.js
Normal file
|
@ -0,0 +1,34 @@
|
|||
angular.module('portainer.docker')
|
||||
.factory('SecretHelper', [function SecretHelperFactory() {
|
||||
'use strict';
|
||||
return {
|
||||
flattenSecret: function(secret) {
|
||||
if (secret) {
|
||||
return {
|
||||
Id: secret.SecretID,
|
||||
Name: secret.SecretName,
|
||||
FileName: secret.File.Name,
|
||||
Uid: secret.File.UID,
|
||||
Gid: secret.File.GID,
|
||||
Mode: secret.File.Mode
|
||||
};
|
||||
}
|
||||
return {};
|
||||
},
|
||||
secretConfig: function(secret) {
|
||||
if (secret) {
|
||||
return {
|
||||
SecretID: secret.Id,
|
||||
SecretName: secret.Name,
|
||||
File: {
|
||||
Name: secret.FileName,
|
||||
UID: secret.Uid || '0',
|
||||
GID: secret.Gid || '0',
|
||||
Mode: secret.Mode || 444
|
||||
}
|
||||
};
|
||||
}
|
||||
return {};
|
||||
}
|
||||
};
|
||||
}]);
|
247
app/docker/helpers/serviceHelper.js
Normal file
247
app/docker/helpers/serviceHelper.js
Normal file
|
@ -0,0 +1,247 @@
|
|||
angular.module('portainer.docker')
|
||||
.factory('ServiceHelper', [function ServiceHelperFactory() {
|
||||
'use strict';
|
||||
|
||||
var helper = {};
|
||||
|
||||
helper.associateTasksToService = function(service, tasks) {
|
||||
service.Tasks = [];
|
||||
var otherServicesTasks = [];
|
||||
for (var i = 0; i < tasks.length; i++) {
|
||||
var task = tasks[i];
|
||||
if (task.ServiceId === service.Id) {
|
||||
service.Tasks.push(task);
|
||||
} else {
|
||||
otherServicesTasks.push(task);
|
||||
}
|
||||
}
|
||||
tasks = otherServicesTasks;
|
||||
};
|
||||
|
||||
helper.serviceToConfig = function(service) {
|
||||
return {
|
||||
Name: service.Spec.Name,
|
||||
Labels: service.Spec.Labels,
|
||||
TaskTemplate: service.Spec.TaskTemplate,
|
||||
Mode: service.Spec.Mode,
|
||||
UpdateConfig: service.Spec.UpdateConfig,
|
||||
Networks: service.Spec.Networks,
|
||||
EndpointSpec: service.Spec.EndpointSpec
|
||||
};
|
||||
};
|
||||
|
||||
helper.translateKeyValueToPlacementPreferences = function(keyValuePreferences) {
|
||||
if (keyValuePreferences) {
|
||||
var preferences = [];
|
||||
keyValuePreferences.forEach(function(preference) {
|
||||
if (preference.strategy && preference.strategy !== '' && preference.value && preference.value !== '') {
|
||||
switch (preference.strategy.toLowerCase()) {
|
||||
case 'spread':
|
||||
preferences.push({
|
||||
'Spread': {
|
||||
'SpreadDescriptor': preference.value
|
||||
}
|
||||
});
|
||||
break;
|
||||
}
|
||||
}
|
||||
});
|
||||
return preferences;
|
||||
}
|
||||
return [];
|
||||
};
|
||||
|
||||
helper.translateKeyValueToPlacementConstraints = function(keyValueConstraints) {
|
||||
if (keyValueConstraints) {
|
||||
var constraints = [];
|
||||
keyValueConstraints.forEach(function(constraint) {
|
||||
if (constraint.key && constraint.key !== '' && constraint.value && constraint.value !== '') {
|
||||
constraints.push(constraint.key + constraint.operator + constraint.value);
|
||||
}
|
||||
});
|
||||
return constraints;
|
||||
}
|
||||
return [];
|
||||
};
|
||||
|
||||
helper.translateEnvironmentVariables = function(env) {
|
||||
if (env) {
|
||||
var variables = [];
|
||||
env.forEach(function(variable) {
|
||||
var idx = variable.indexOf('=');
|
||||
var keyValue = [variable.slice(0, idx), variable.slice(idx + 1)];
|
||||
var originalValue = (keyValue.length > 1) ? keyValue[1] : '';
|
||||
variables.push({
|
||||
key: keyValue[0],
|
||||
value: originalValue,
|
||||
originalKey: keyValue[0],
|
||||
originalValue: originalValue,
|
||||
added: true
|
||||
});
|
||||
});
|
||||
return variables;
|
||||
}
|
||||
return [];
|
||||
};
|
||||
|
||||
helper.translateEnvironmentVariablesToEnv = function(env) {
|
||||
if (env) {
|
||||
var variables = [];
|
||||
env.forEach(function(variable) {
|
||||
if (variable.key && variable.key !== '') {
|
||||
variables.push(variable.key + '=' + variable.value);
|
||||
}
|
||||
});
|
||||
return variables;
|
||||
}
|
||||
return [];
|
||||
};
|
||||
|
||||
helper.translatePreferencesToKeyValue = function(preferences) {
|
||||
if (preferences) {
|
||||
var keyValuePreferences = [];
|
||||
preferences.forEach(function(preference) {
|
||||
if (preference.Spread) {
|
||||
keyValuePreferences.push({
|
||||
strategy: 'Spread',
|
||||
value: preference.Spread.SpreadDescriptor
|
||||
});
|
||||
}
|
||||
});
|
||||
return keyValuePreferences;
|
||||
}
|
||||
return [];
|
||||
};
|
||||
|
||||
helper.translateConstraintsToKeyValue = function(constraints) {
|
||||
function getOperator(constraint) {
|
||||
var indexEquals = constraint.indexOf('==');
|
||||
if (indexEquals >= 0) {
|
||||
return [indexEquals, '=='];
|
||||
}
|
||||
return [constraint.indexOf('!='), '!='];
|
||||
}
|
||||
if (constraints) {
|
||||
var keyValueConstraints = [];
|
||||
constraints.forEach(function(constraint) {
|
||||
var operatorIndices = getOperator(constraint);
|
||||
|
||||
var key = constraint.slice(0, operatorIndices[0]);
|
||||
var operator = operatorIndices[1];
|
||||
var value = constraint.slice(operatorIndices[0] + 2);
|
||||
|
||||
keyValueConstraints.push({
|
||||
key: key,
|
||||
value: value,
|
||||
operator: operator,
|
||||
originalKey: key,
|
||||
originalValue: value
|
||||
});
|
||||
});
|
||||
return keyValueConstraints;
|
||||
}
|
||||
};
|
||||
|
||||
helper.translateHumanDurationToNanos = function(humanDuration) {
|
||||
var nanos;
|
||||
var regex = /^([0-9]+)(h|m|s|ms|us|ns)$/i;
|
||||
var matches = humanDuration.match(regex);
|
||||
|
||||
if (matches !== null && matches.length === 3) {
|
||||
var duration = parseInt(matches[1], 10);
|
||||
var unit = matches[2];
|
||||
// Moment.js cannot use micro or nanoseconds
|
||||
switch (unit) {
|
||||
case 'ns':
|
||||
nanos = duration;
|
||||
break;
|
||||
case 'us':
|
||||
nanos = duration * 1000;
|
||||
break;
|
||||
default:
|
||||
nanos = moment.duration(duration, unit).asMilliseconds() * 1000000;
|
||||
}
|
||||
}
|
||||
return nanos;
|
||||
};
|
||||
|
||||
// Convert nanoseconds to the higher unit possible
|
||||
// e.g 1840 nanoseconds = 1804ns
|
||||
// e.g 300000000000 nanoseconds = 5m
|
||||
// e.g 3510000000000 nanoseconds = 3510s
|
||||
// e.g 3540000000000 nanoseconds = 59m
|
||||
// e.g 3600000000000 nanoseconds = 1h
|
||||
|
||||
helper.translateNanosToHumanDuration = function(nanos) {
|
||||
var humanDuration = '0s';
|
||||
var conversionFromNano = {};
|
||||
conversionFromNano['ns'] = 1;
|
||||
conversionFromNano['us'] = conversionFromNano['ns'] * 1000;
|
||||
conversionFromNano['ms'] = conversionFromNano['us'] * 1000;
|
||||
conversionFromNano['s'] = conversionFromNano['ms'] * 1000;
|
||||
conversionFromNano['m'] = conversionFromNano['s'] * 60;
|
||||
conversionFromNano['h'] = conversionFromNano['m'] * 60;
|
||||
|
||||
Object.keys(conversionFromNano).forEach(function(unit) {
|
||||
if ( nanos % conversionFromNano[unit] === 0 && (nanos / conversionFromNano[unit]) > 0) {
|
||||
humanDuration = (nanos / conversionFromNano[unit]) + unit;
|
||||
}
|
||||
});
|
||||
return humanDuration;
|
||||
};
|
||||
|
||||
helper.translateLogDriverOptsToKeyValue = function(logOptions) {
|
||||
var options = [];
|
||||
if (logOptions) {
|
||||
Object.keys(logOptions).forEach(function(key) {
|
||||
options.push({
|
||||
key: key,
|
||||
value: logOptions[key],
|
||||
originalKey: key,
|
||||
originalValue: logOptions[key],
|
||||
added: true
|
||||
});
|
||||
});
|
||||
}
|
||||
return options;
|
||||
};
|
||||
|
||||
helper.translateKeyValueToLogDriverOpts = function(keyValueLogDriverOpts) {
|
||||
var options = {};
|
||||
if (keyValueLogDriverOpts) {
|
||||
keyValueLogDriverOpts.forEach(function(option) {
|
||||
if (option.key && option.key !== '' && option.value && option.value !== '') {
|
||||
options[option.key] = option.value;
|
||||
}
|
||||
});
|
||||
}
|
||||
return options;
|
||||
};
|
||||
|
||||
helper.translateHostsEntriesToHostnameIP = function(entries) {
|
||||
var ipHostEntries = [];
|
||||
if (entries) {
|
||||
entries.forEach(function(entry) {
|
||||
if (entry.indexOf(' ') && entry.split(' ').length === 2) {
|
||||
var keyValue = entry.split(' ');
|
||||
ipHostEntries.push({ hostname: keyValue[1], ip: keyValue[0]});
|
||||
}
|
||||
});
|
||||
}
|
||||
return ipHostEntries;
|
||||
};
|
||||
|
||||
helper.translateHostnameIPToHostsEntries = function(entries) {
|
||||
var ipHostEntries = [];
|
||||
if (entries) {
|
||||
entries.forEach(function(entry) {
|
||||
if (entry.ip && entry.hostname) {
|
||||
ipHostEntries.push(entry.ip + ' ' + entry.hostname);
|
||||
}
|
||||
});
|
||||
}
|
||||
return ipHostEntries;
|
||||
};
|
||||
|
||||
return helper;
|
||||
}]);
|
30
app/docker/helpers/volumeHelper.js
Normal file
30
app/docker/helpers/volumeHelper.js
Normal file
|
@ -0,0 +1,30 @@
|
|||
angular.module('portainer.docker')
|
||||
.factory('VolumeHelper', [function VolumeHelperFactory() {
|
||||
'use strict';
|
||||
var helper = {};
|
||||
|
||||
helper.createDriverOptions = function(optionArray) {
|
||||
var options = {};
|
||||
optionArray.forEach(function (option) {
|
||||
options[option.name] = option.value;
|
||||
});
|
||||
return options;
|
||||
};
|
||||
|
||||
helper.isVolumeUsedByAService = function(volume, services) {
|
||||
for (var i = 0; i < services.length; i++) {
|
||||
var service = services[i];
|
||||
var mounts = service.Mounts;
|
||||
for (var j = 0; j < mounts.length; j++) {
|
||||
var mount = mounts[j];
|
||||
if (mount.Source === volume.Id) {
|
||||
return true;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
return false;
|
||||
};
|
||||
|
||||
return helper;
|
||||
}]);
|
15
app/docker/models/config.js
Normal file
15
app/docker/models/config.js
Normal file
|
@ -0,0 +1,15 @@
|
|||
function ConfigViewModel(data) {
|
||||
this.Id = data.ID;
|
||||
this.CreatedAt = data.CreatedAt;
|
||||
this.UpdatedAt = data.UpdatedAt;
|
||||
this.Version = data.Version.Index;
|
||||
this.Name = data.Spec.Name;
|
||||
this.Labels = data.Spec.Labels;
|
||||
this.Data = atob(data.Spec.Data);
|
||||
|
||||
if (data.Portainer) {
|
||||
if (data.Portainer.ResourceControl) {
|
||||
this.ResourceControl = new ResourceControlViewModel(data.Portainer.ResourceControl);
|
||||
}
|
||||
}
|
||||
}
|
38
app/docker/models/container.js
Normal file
38
app/docker/models/container.js
Normal file
|
@ -0,0 +1,38 @@
|
|||
function ContainerViewModel(data) {
|
||||
this.Id = data.Id;
|
||||
this.Status = data.Status;
|
||||
this.State = data.State;
|
||||
this.Names = data.Names;
|
||||
// Unavailable in Docker < 1.10
|
||||
if (data.NetworkSettings && !_.isEmpty(data.NetworkSettings.Networks)) {
|
||||
this.IP = data.NetworkSettings.Networks[Object.keys(data.NetworkSettings.Networks)[0]].IPAddress;
|
||||
}
|
||||
this.NetworkSettings = data.NetworkSettings;
|
||||
this.Image = data.Image;
|
||||
this.ImageID = data.ImageID;
|
||||
this.Command = data.Command;
|
||||
this.Checked = false;
|
||||
this.Labels = data.Labels;
|
||||
if (this.Labels && this.Labels['com.docker.compose.project']) {
|
||||
this.StackName = this.Labels['com.docker.compose.project'];
|
||||
} else if (this.Labels && this.Labels['com.docker.stack.namespace']) {
|
||||
this.StackName = this.Labels['com.docker.stack.namespace'];
|
||||
}
|
||||
this.Mounts = data.Mounts;
|
||||
|
||||
this.Ports = [];
|
||||
if (data.Ports) {
|
||||
for (var i = 0; i < data.Ports.length; ++i) {
|
||||
var p = data.Ports[i];
|
||||
if (p.PublicPort) {
|
||||
this.Ports.push({ host: p.IP, private: p.PrivatePort, public: p.PublicPort });
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
if (data.Portainer) {
|
||||
if (data.Portainer.ResourceControl) {
|
||||
this.ResourceControl = new ResourceControlViewModel(data.Portainer.ResourceControl);
|
||||
}
|
||||
}
|
||||
}
|
18
app/docker/models/containerDetails.js
Normal file
18
app/docker/models/containerDetails.js
Normal file
|
@ -0,0 +1,18 @@
|
|||
function ContainerDetailsViewModel(data) {
|
||||
this.Model = data;
|
||||
this.Id = data.Id;
|
||||
this.State = data.State;
|
||||
this.Created = data.Created;
|
||||
this.Name = data.Name;
|
||||
this.NetworkSettings = data.NetworkSettings;
|
||||
this.Args = data.Args;
|
||||
this.Image = data.Image;
|
||||
this.Config = data.Config;
|
||||
this.HostConfig = data.HostConfig;
|
||||
this.Mounts = data.Mounts;
|
||||
if (data.Portainer) {
|
||||
if (data.Portainer.ResourceControl) {
|
||||
this.ResourceControl = new ResourceControlViewModel(data.Portainer.ResourceControl);
|
||||
}
|
||||
}
|
||||
}
|
12
app/docker/models/containerStats.js
Normal file
12
app/docker/models/containerStats.js
Normal file
|
@ -0,0 +1,12 @@
|
|||
function ContainerStatsViewModel(data) {
|
||||
this.Date = data.read;
|
||||
this.MemoryUsage = data.memory_stats.usage;
|
||||
this.PreviousCPUTotalUsage = data.precpu_stats.cpu_usage.total_usage;
|
||||
this.PreviousCPUSystemUsage = data.precpu_stats.system_cpu_usage;
|
||||
this.CurrentCPUTotalUsage = data.cpu_stats.cpu_usage.total_usage;
|
||||
this.CurrentCPUSystemUsage = data.cpu_stats.system_cpu_usage;
|
||||
if (data.cpu_stats.cpu_usage.percpu_usage) {
|
||||
this.CPUCores = data.cpu_stats.cpu_usage.percpu_usage.length;
|
||||
}
|
||||
this.Networks = _.values(data.networks);
|
||||
}
|
120
app/docker/models/event.js
Normal file
120
app/docker/models/event.js
Normal file
|
@ -0,0 +1,120 @@
|
|||
function createEventDetails(event) {
|
||||
var eventAttr = event.Actor.Attributes;
|
||||
var details = '';
|
||||
switch (event.Type) {
|
||||
case 'container':
|
||||
switch (event.Action) {
|
||||
case 'stop':
|
||||
details = 'Container ' + eventAttr.name + ' stopped';
|
||||
break;
|
||||
case 'destroy':
|
||||
details = 'Container ' + eventAttr.name + ' deleted';
|
||||
break;
|
||||
case 'create':
|
||||
details = 'Container ' + eventAttr.name + ' created';
|
||||
break;
|
||||
case 'start':
|
||||
details = 'Container ' + eventAttr.name + ' started';
|
||||
break;
|
||||
case 'kill':
|
||||
details = 'Container ' + eventAttr.name + ' killed';
|
||||
break;
|
||||
case 'die':
|
||||
details = 'Container ' + eventAttr.name + ' exited with status code ' + eventAttr.exitCode;
|
||||
break;
|
||||
case 'commit':
|
||||
details = 'Container ' + eventAttr.name + ' committed';
|
||||
break;
|
||||
case 'restart':
|
||||
details = 'Container ' + eventAttr.name + ' restarted';
|
||||
break;
|
||||
case 'pause':
|
||||
details = 'Container ' + eventAttr.name + ' paused';
|
||||
break;
|
||||
case 'unpause':
|
||||
details = 'Container ' + eventAttr.name + ' unpaused';
|
||||
break;
|
||||
case 'attach':
|
||||
details = 'Container ' + eventAttr.name + ' attached';
|
||||
break;
|
||||
default:
|
||||
if (event.Action.indexOf('exec_create') === 0) {
|
||||
details = 'Exec instance created';
|
||||
} else if (event.Action.indexOf('exec_start') === 0) {
|
||||
details = 'Exec instance started';
|
||||
} else {
|
||||
details = 'Unsupported event';
|
||||
}
|
||||
}
|
||||
break;
|
||||
case 'image':
|
||||
switch (event.Action) {
|
||||
case 'delete':
|
||||
details = 'Image deleted';
|
||||
break;
|
||||
case 'tag':
|
||||
details = 'New tag created for ' + eventAttr.name;
|
||||
break;
|
||||
case 'untag':
|
||||
details = 'Image untagged';
|
||||
break;
|
||||
case 'pull':
|
||||
details = 'Image ' + event.Actor.ID + ' pulled';
|
||||
break;
|
||||
default:
|
||||
details = 'Unsupported event';
|
||||
}
|
||||
break;
|
||||
case 'network':
|
||||
switch (event.Action) {
|
||||
case 'create':
|
||||
details = 'Network ' + eventAttr.name + ' created';
|
||||
break;
|
||||
case 'destroy':
|
||||
details = 'Network ' + eventAttr.name + ' deleted';
|
||||
break;
|
||||
case 'connect':
|
||||
details = 'Container connected to ' + eventAttr.name + ' network';
|
||||
break;
|
||||
case 'disconnect':
|
||||
details = 'Container disconnected from ' + eventAttr.name + ' network';
|
||||
break;
|
||||
default:
|
||||
details = 'Unsupported event';
|
||||
}
|
||||
break;
|
||||
case 'volume':
|
||||
switch (event.Action) {
|
||||
case 'create':
|
||||
details = 'Volume ' + event.Actor.ID + ' created';
|
||||
break;
|
||||
case 'destroy':
|
||||
details = 'Volume ' + event.Actor.ID + ' deleted';
|
||||
break;
|
||||
case 'mount':
|
||||
details = 'Volume ' + event.Actor.ID + ' mounted';
|
||||
break;
|
||||
case 'unmount':
|
||||
details = 'Volume ' + event.Actor.ID + ' unmounted';
|
||||
break;
|
||||
default:
|
||||
details = 'Unsupported event';
|
||||
}
|
||||
break;
|
||||
default:
|
||||
details = 'Unsupported event';
|
||||
}
|
||||
return details;
|
||||
}
|
||||
|
||||
function EventViewModel(data) {
|
||||
// Type, Action, Actor unavailable in Docker < 1.10
|
||||
this.Time = data.time;
|
||||
if (data.Type) {
|
||||
this.Type = data.Type;
|
||||
this.Details = createEventDetails(data);
|
||||
} else {
|
||||
this.Type = data.status;
|
||||
this.Details = data.from;
|
||||
}
|
||||
}
|
10
app/docker/models/image.js
Normal file
10
app/docker/models/image.js
Normal file
|
@ -0,0 +1,10 @@
|
|||
function ImageViewModel(data) {
|
||||
this.Id = data.Id;
|
||||
this.Tag = data.Tag;
|
||||
this.Repository = data.Repository;
|
||||
this.Created = data.Created;
|
||||
this.Checked = false;
|
||||
this.RepoTags = data.RepoTags;
|
||||
this.VirtualSize = data.VirtualSize;
|
||||
this.ContainerCount = data.ContainerCount;
|
||||
}
|
19
app/docker/models/imageDetails.js
Normal file
19
app/docker/models/imageDetails.js
Normal file
|
@ -0,0 +1,19 @@
|
|||
function ImageDetailsViewModel(data) {
|
||||
this.Id = data.Id;
|
||||
this.Tag = data.Tag;
|
||||
this.Parent = data.Parent;
|
||||
this.Repository = data.Repository;
|
||||
this.Created = data.Created;
|
||||
this.Checked = false;
|
||||
this.RepoTags = data.RepoTags;
|
||||
this.VirtualSize = data.VirtualSize;
|
||||
this.DockerVersion = data.DockerVersion;
|
||||
this.Os = data.Os;
|
||||
this.Architecture = data.Architecture;
|
||||
this.Author = data.Author;
|
||||
this.Command = data.Config.Cmd;
|
||||
this.Entrypoint = data.ContainerConfig.Entrypoint ? data.ContainerConfig.Entrypoint : '';
|
||||
this.ExposedPorts = data.ContainerConfig.ExposedPorts ? Object.keys(data.ContainerConfig.ExposedPorts) : [];
|
||||
this.Volumes = data.ContainerConfig.Volumes ? Object.keys(data.ContainerConfig.Volumes) : [];
|
||||
this.Env = data.ContainerConfig.Env ? data.ContainerConfig.Env : [];
|
||||
}
|
8
app/docker/models/imageLayer.js
Normal file
8
app/docker/models/imageLayer.js
Normal file
|
@ -0,0 +1,8 @@
|
|||
function ImageLayerViewModel(data) {
|
||||
this.Id = data.Id;
|
||||
this.Created = data.Created;
|
||||
this.CreatedBy = data.CreatedBy;
|
||||
this.Size = data.Size;
|
||||
this.Comment = data.Comment;
|
||||
this.Tags = data.Tags;
|
||||
}
|
23
app/docker/models/network.js
Normal file
23
app/docker/models/network.js
Normal file
|
@ -0,0 +1,23 @@
|
|||
function NetworkViewModel(data) {
|
||||
this.Id = data.Id;
|
||||
this.Name = data.Name;
|
||||
this.Scope = data.Scope;
|
||||
this.Driver = data.Driver;
|
||||
this.Attachable = data.Attachable;
|
||||
this.IPAM = data.IPAM;
|
||||
this.Containers = data.Containers;
|
||||
this.Options = data.Options;
|
||||
|
||||
this.Labels = data.Labels;
|
||||
if (this.Labels && this.Labels['com.docker.compose.project']) {
|
||||
this.StackName = this.Labels['com.docker.compose.project'];
|
||||
} else if (this.Labels && this.Labels['com.docker.stack.namespace']) {
|
||||
this.StackName = this.Labels['com.docker.stack.namespace'];
|
||||
}
|
||||
|
||||
if (data.Portainer) {
|
||||
if (data.Portainer.ResourceControl) {
|
||||
this.ResourceControl = new ResourceControlViewModel(data.Portainer.ResourceControl);
|
||||
}
|
||||
}
|
||||
}
|
47
app/docker/models/node.js
Normal file
47
app/docker/models/node.js
Normal file
|
@ -0,0 +1,47 @@
|
|||
function NodeViewModel(data) {
|
||||
this.Model = data;
|
||||
this.Id = data.ID;
|
||||
this.Version = data.Version.Index;
|
||||
this.Name = data.Spec.Name;
|
||||
this.Role = data.Spec.Role;
|
||||
this.CreatedAt = data.CreatedAt;
|
||||
this.UpdatedAt = data.UpdatedAt;
|
||||
this.Availability = data.Spec.Availability;
|
||||
|
||||
var labels = data.Spec.Labels;
|
||||
if (labels) {
|
||||
this.Labels = Object.keys(labels).map(function(key) {
|
||||
return { key: key, value: labels[key], originalKey: key, originalValue: labels[key], added: true };
|
||||
});
|
||||
} else {
|
||||
this.Labels = [];
|
||||
}
|
||||
|
||||
var engineLabels = data.Description.Engine.Labels;
|
||||
if (engineLabels) {
|
||||
this.EngineLabels = Object.keys(engineLabels).map(function(key) {
|
||||
return { key: key, value: engineLabels[key] };
|
||||
});
|
||||
} else {
|
||||
this.EngineLabels = [];
|
||||
}
|
||||
|
||||
this.Hostname = data.Description.Hostname;
|
||||
this.PlatformArchitecture = data.Description.Platform.Architecture;
|
||||
this.PlatformOS = data.Description.Platform.OS;
|
||||
this.CPUs = data.Description.Resources.NanoCPUs;
|
||||
this.Memory = data.Description.Resources.MemoryBytes;
|
||||
this.EngineVersion = data.Description.Engine.EngineVersion;
|
||||
this.Plugins = data.Description.Engine.Plugins;
|
||||
this.Status = data.Status.State;
|
||||
|
||||
if (data.Status.Addr) {
|
||||
this.Addr = data.Status.Addr;
|
||||
}
|
||||
|
||||
if (data.ManagerStatus) {
|
||||
this.Leader = data.ManagerStatus.Leader;
|
||||
this.Reachability = data.ManagerStatus.Reachability;
|
||||
this.ManagerAddr = data.ManagerStatus.Addr;
|
||||
}
|
||||
}
|
9
app/docker/models/plugin.js
Normal file
9
app/docker/models/plugin.js
Normal file
|
@ -0,0 +1,9 @@
|
|||
// This model is based on https://github.com/moby/moby/blob/0ac25dfc751fa4304ab45afd5cd8705c2235d101/api/types/plugin.go#L8-L31
|
||||
// instead of the official documentation.
|
||||
// See: https://github.com/moby/moby/issues/34241
|
||||
function PluginViewModel(data) {
|
||||
this.Id = data.Id;
|
||||
this.Name = data.Name;
|
||||
this.Enabled = data.Enabled;
|
||||
this.Config = data.Config;
|
||||
}
|
14
app/docker/models/secret.js
Normal file
14
app/docker/models/secret.js
Normal file
|
@ -0,0 +1,14 @@
|
|||
function SecretViewModel(data) {
|
||||
this.Id = data.ID;
|
||||
this.CreatedAt = data.CreatedAt;
|
||||
this.UpdatedAt = data.UpdatedAt;
|
||||
this.Version = data.Version.Index;
|
||||
this.Name = data.Spec.Name;
|
||||
this.Labels = data.Spec.Labels;
|
||||
|
||||
if (data.Portainer) {
|
||||
if (data.Portainer.ResourceControl) {
|
||||
this.ResourceControl = new ResourceControlViewModel(data.Portainer.ResourceControl);
|
||||
}
|
||||
}
|
||||
}
|
115
app/docker/models/service.js
Normal file
115
app/docker/models/service.js
Normal file
|
@ -0,0 +1,115 @@
|
|||
function ServiceViewModel(data, runningTasks, allTasks, nodes) {
|
||||
this.Model = data;
|
||||
this.Id = data.ID;
|
||||
this.Tasks = [];
|
||||
this.Name = data.Spec.Name;
|
||||
this.CreatedAt = data.CreatedAt;
|
||||
this.UpdatedAt = data.UpdatedAt;
|
||||
this.Image = data.Spec.TaskTemplate.ContainerSpec.Image;
|
||||
this.Version = data.Version.Index;
|
||||
if (data.Spec.Mode.Replicated) {
|
||||
this.Mode = 'replicated' ;
|
||||
this.Replicas = data.Spec.Mode.Replicated.Replicas;
|
||||
} else {
|
||||
this.Mode = 'global';
|
||||
if (allTasks) {
|
||||
this.Replicas = allTasks.length;
|
||||
}
|
||||
}
|
||||
if (runningTasks) {
|
||||
this.Running = runningTasks.length;
|
||||
}
|
||||
if (data.Spec.TaskTemplate.Resources) {
|
||||
if (data.Spec.TaskTemplate.Resources.Limits) {
|
||||
this.LimitNanoCPUs = data.Spec.TaskTemplate.Resources.Limits.NanoCPUs;
|
||||
this.LimitMemoryBytes = data.Spec.TaskTemplate.Resources.Limits.MemoryBytes;
|
||||
}
|
||||
if (data.Spec.TaskTemplate.Resources.Reservations) {
|
||||
this.ReservationNanoCPUs = data.Spec.TaskTemplate.Resources.Reservations.NanoCPUs;
|
||||
this.ReservationMemoryBytes = data.Spec.TaskTemplate.Resources.Reservations.MemoryBytes;
|
||||
}
|
||||
}
|
||||
|
||||
if (data.Spec.TaskTemplate.RestartPolicy) {
|
||||
this.RestartCondition = data.Spec.TaskTemplate.RestartPolicy.Condition || 'any';
|
||||
this.RestartDelay = data.Spec.TaskTemplate.RestartPolicy.Delay || 5000000000;
|
||||
this.RestartMaxAttempts = data.Spec.TaskTemplate.RestartPolicy.MaxAttempts || 0;
|
||||
this.RestartWindow = data.Spec.TaskTemplate.RestartPolicy.Window || 0;
|
||||
} else {
|
||||
this.RestartCondition = 'any';
|
||||
this.RestartDelay = 5000000000;
|
||||
this.RestartMaxAttempts = 0;
|
||||
this.RestartWindow = 0;
|
||||
}
|
||||
|
||||
if (data.Spec.TaskTemplate.LogDriver) {
|
||||
this.LogDriverName = data.Spec.TaskTemplate.LogDriver.Name || '';
|
||||
this.LogDriverOpts = data.Spec.TaskTemplate.LogDriver.Options || [];
|
||||
} else {
|
||||
this.LogDriverName = '';
|
||||
this.LogDriverOpts = [];
|
||||
}
|
||||
|
||||
this.Constraints = data.Spec.TaskTemplate.Placement ? data.Spec.TaskTemplate.Placement.Constraints || [] : [];
|
||||
this.Preferences = data.Spec.TaskTemplate.Placement ? data.Spec.TaskTemplate.Placement.Preferences || [] : [];
|
||||
this.Platforms = data.Spec.TaskTemplate.Placement ? data.Spec.TaskTemplate.Placement.Platforms || [] : [];
|
||||
this.Labels = data.Spec.Labels;
|
||||
if (this.Labels && this.Labels['com.docker.stack.namespace']) {
|
||||
this.StackName = this.Labels['com.docker.stack.namespace'];
|
||||
}
|
||||
|
||||
var containerSpec = data.Spec.TaskTemplate.ContainerSpec;
|
||||
if (containerSpec) {
|
||||
this.ContainerLabels = containerSpec.Labels;
|
||||
this.Command = containerSpec.Command;
|
||||
this.Arguments = containerSpec.Args;
|
||||
this.Hostname = containerSpec.Hostname;
|
||||
this.Env = containerSpec.Env;
|
||||
this.Dir = containerSpec.Dir;
|
||||
this.User = containerSpec.User;
|
||||
this.Groups = containerSpec.Groups;
|
||||
this.TTY = containerSpec.TTY;
|
||||
this.OpenStdin = containerSpec.OpenStdin;
|
||||
this.ReadOnly = containerSpec.ReadOnly;
|
||||
this.Mounts = containerSpec.Mounts || [];
|
||||
this.StopSignal = containerSpec.StopSignal;
|
||||
this.StopGracePeriod = containerSpec.StopGracePeriod;
|
||||
this.HealthCheck = containerSpec.HealthCheck || {};
|
||||
this.Hosts = containerSpec.Hosts;
|
||||
this.DNSConfig = containerSpec.DNSConfig;
|
||||
this.Secrets = containerSpec.Secrets;
|
||||
this.Configs = containerSpec.Configs;
|
||||
}
|
||||
if (data.Endpoint) {
|
||||
this.Ports = data.Endpoint.Ports;
|
||||
}
|
||||
|
||||
this.LogDriver = data.Spec.TaskTemplate.LogDriver;
|
||||
this.Runtime = data.Spec.TaskTemplate.Runtime;
|
||||
|
||||
this.VirtualIPs = data.Endpoint ? data.Endpoint.VirtualIPs : [];
|
||||
|
||||
if (data.Spec.UpdateConfig) {
|
||||
this.UpdateParallelism = (typeof data.Spec.UpdateConfig.Parallelism !== undefined) ? data.Spec.UpdateConfig.Parallelism || 0 : 1;
|
||||
this.UpdateDelay = data.Spec.UpdateConfig.Delay || 0;
|
||||
this.UpdateFailureAction = data.Spec.UpdateConfig.FailureAction || 'pause';
|
||||
this.UpdateOrder = data.Spec.UpdateConfig.Order || 'stop-first';
|
||||
} else {
|
||||
this.UpdateParallelism = 1;
|
||||
this.UpdateDelay = 0;
|
||||
this.UpdateFailureAction = 'pause';
|
||||
this.UpdateOrder = 'stop-first';
|
||||
}
|
||||
|
||||
this.RollbackConfig = data.Spec.RollbackConfig;
|
||||
|
||||
this.Checked = false;
|
||||
this.Scale = false;
|
||||
this.EditName = false;
|
||||
|
||||
if (data.Portainer) {
|
||||
if (data.Portainer.ResourceControl) {
|
||||
this.ResourceControl = new ResourceControlViewModel(data.Portainer.ResourceControl);
|
||||
}
|
||||
}
|
||||
}
|
10
app/docker/models/stack.js
Normal file
10
app/docker/models/stack.js
Normal file
|
@ -0,0 +1,10 @@
|
|||
function StackViewModel(data) {
|
||||
this.Id = data.Id;
|
||||
this.Name = data.Name;
|
||||
this.Checked = false;
|
||||
this.Env = data.Env ? data.Env : [];
|
||||
if (data.ResourceControl && data.ResourceControl.Id !== 0) {
|
||||
this.ResourceControl = new ResourceControlViewModel(data.ResourceControl);
|
||||
}
|
||||
this.External = data.External;
|
||||
}
|
11
app/docker/models/stackTemplate.js
Normal file
11
app/docker/models/stackTemplate.js
Normal file
|
@ -0,0 +1,11 @@
|
|||
function StackTemplateViewModel(data) {
|
||||
this.Type = data.type;
|
||||
this.Title = data.title;
|
||||
this.Description = data.description;
|
||||
this.Note = data.note;
|
||||
this.Categories = data.categories ? data.categories : [];
|
||||
this.Platform = data.platform ? data.platform : 'undefined';
|
||||
this.Logo = data.logo;
|
||||
this.Repository = data.repository;
|
||||
this.Env = data.env ? data.env : [];
|
||||
}
|
3
app/docker/models/swarm.js
Normal file
3
app/docker/models/swarm.js
Normal file
|
@ -0,0 +1,3 @@
|
|||
function SwarmViewModel(data) {
|
||||
this.Id = data.ID;
|
||||
}
|
10
app/docker/models/task.js
Normal file
10
app/docker/models/task.js
Normal file
|
@ -0,0 +1,10 @@
|
|||
function TaskViewModel(data) {
|
||||
this.Id = data.ID;
|
||||
this.Created = data.CreatedAt;
|
||||
this.Updated = data.UpdatedAt;
|
||||
this.Slot = data.Slot;
|
||||
this.Spec = data.Spec;
|
||||
this.Status = data.Status;
|
||||
this.ServiceId = data.ServiceID;
|
||||
this.NodeId = data.NodeID;
|
||||
}
|
49
app/docker/models/template.js
Normal file
49
app/docker/models/template.js
Normal file
|
@ -0,0 +1,49 @@
|
|||
function TemplateViewModel(data) {
|
||||
this.Type = data.type;
|
||||
this.Title = data.title;
|
||||
this.Description = data.description;
|
||||
this.Note = data.note;
|
||||
this.Categories = data.categories ? data.categories : [];
|
||||
this.Platform = data.platform ? data.platform : 'undefined';
|
||||
this.Logo = data.logo;
|
||||
this.Image = data.image;
|
||||
this.Registry = data.registry ? data.registry : '';
|
||||
this.Command = data.command ? data.command : '';
|
||||
this.Network = data.network ? data.network : '';
|
||||
this.Env = data.env ? data.env : [];
|
||||
this.Privileged = data.privileged ? data.privileged : false;
|
||||
this.Interactive = data.interactive ? data.interactive : false;
|
||||
this.RestartPolicy = data.restart_policy ? data.restart_policy : 'always';
|
||||
this.Labels = data.labels ? data.labels : [];
|
||||
this.Volumes = [];
|
||||
|
||||
if (data.volumes) {
|
||||
this.Volumes = data.volumes.map(function (v) {
|
||||
// @DEPRECATED: New volume definition introduced
|
||||
// via https://github.com/portainer/portainer/pull/1154
|
||||
var volume = {
|
||||
readOnly: v.readonly || false,
|
||||
containerPath: v.container || v,
|
||||
type: 'auto'
|
||||
};
|
||||
|
||||
if (v.bind) {
|
||||
volume.name = v.bind;
|
||||
volume.type = 'bind';
|
||||
}
|
||||
|
||||
return volume;
|
||||
});
|
||||
}
|
||||
this.Ports = [];
|
||||
if (data.ports) {
|
||||
this.Ports = data.ports.map(function (p) {
|
||||
var portAndProtocol = _.split(p, '/');
|
||||
return {
|
||||
containerPort: portAndProtocol[0],
|
||||
protocol: portAndProtocol[1]
|
||||
};
|
||||
});
|
||||
}
|
||||
this.Hosts = data.hosts ? data.hosts : [];
|
||||
}
|
36
app/docker/models/templateLinuxServer.js
Normal file
36
app/docker/models/templateLinuxServer.js
Normal file
|
@ -0,0 +1,36 @@
|
|||
function TemplateLSIOViewModel(data) {
|
||||
this.Type = data.type;
|
||||
this.Title = data.title;
|
||||
this.Note = data.description;
|
||||
this.Categories = data.category ? data.category : [];
|
||||
this.Platform = data.platform ? data.platform : 'linux';
|
||||
this.Logo = data.logo;
|
||||
this.Image = data.image;
|
||||
this.Registry = data.registry ? data.registry : '';
|
||||
this.Command = data.command ? data.command : '';
|
||||
this.Network = data.network ? data.network : '';
|
||||
this.Env = data.env ? data.env : [];
|
||||
this.Privileged = data.privileged ? data.privileged : false;
|
||||
this.Interactive = data.interactive ? data.interactive : false;
|
||||
this.RestartPolicy = data.restart_policy ? data.restart_policy : 'always';
|
||||
this.Volumes = [];
|
||||
if (data.volumes) {
|
||||
this.Volumes = data.volumes.map(function (v) {
|
||||
return {
|
||||
readOnly: false,
|
||||
containerPath: v,
|
||||
type: 'auto'
|
||||
};
|
||||
});
|
||||
}
|
||||
this.Ports = [];
|
||||
if (data.ports) {
|
||||
this.Ports = data.ports.map(function (p) {
|
||||
var portAndProtocol = _.split(p, '/');
|
||||
return {
|
||||
containerPort: portAndProtocol[0],
|
||||
protocol: portAndProtocol[1]
|
||||
};
|
||||
});
|
||||
}
|
||||
}
|
18
app/docker/models/volume.js
Normal file
18
app/docker/models/volume.js
Normal file
|
@ -0,0 +1,18 @@
|
|||
function VolumeViewModel(data) {
|
||||
this.Id = data.Name;
|
||||
this.Driver = data.Driver;
|
||||
this.Options = data.Options;
|
||||
this.Labels = data.Labels;
|
||||
if (this.Labels && this.Labels['com.docker.compose.project']) {
|
||||
this.StackName = this.Labels['com.docker.compose.project'];
|
||||
} else if (this.Labels && this.Labels['com.docker.stack.namespace']) {
|
||||
this.StackName = this.Labels['com.docker.stack.namespace'];
|
||||
}
|
||||
this.Mountpoint = data.Mountpoint;
|
||||
|
||||
if (data.Portainer) {
|
||||
if (data.Portainer.ResourceControl) {
|
||||
this.ResourceControl = new ResourceControlViewModel(data.Portainer.ResourceControl);
|
||||
}
|
||||
}
|
||||
}
|
12
app/docker/rest/config.js
Normal file
12
app/docker/rest/config.js
Normal file
|
@ -0,0 +1,12 @@
|
|||
angular.module('portainer.docker')
|
||||
.factory('Config', ['$resource', 'API_ENDPOINT_ENDPOINTS', 'EndpointProvider', function ConfigFactory($resource, API_ENDPOINT_ENDPOINTS, EndpointProvider) {
|
||||
'use strict';
|
||||
return $resource(API_ENDPOINT_ENDPOINTS + '/:endpointId/docker/configs/:id/:action', {
|
||||
endpointId: EndpointProvider.endpointID
|
||||
}, {
|
||||
get: { method: 'GET', params: { id: '@id' } },
|
||||
query: { method: 'GET', isArray: true },
|
||||
create: { method: 'POST', params: { action: 'create' }, ignoreLoadingBar: true },
|
||||
remove: { method: 'DELETE', params: { id: '@id' } }
|
||||
});
|
||||
}]);
|
49
app/docker/rest/container.js
Normal file
49
app/docker/rest/container.js
Normal file
|
@ -0,0 +1,49 @@
|
|||
angular.module('portainer.docker')
|
||||
.factory('Container', ['$resource', 'API_ENDPOINT_ENDPOINTS', 'EndpointProvider', function ContainerFactory($resource, API_ENDPOINT_ENDPOINTS, EndpointProvider) {
|
||||
'use strict';
|
||||
return $resource(API_ENDPOINT_ENDPOINTS + '/:endpointId/docker/containers/:id/:action', {
|
||||
name: '@name',
|
||||
endpointId: EndpointProvider.endpointID
|
||||
},
|
||||
{
|
||||
query: {method: 'GET', params: {all: 0, action: 'json', filters: '@filters' }, isArray: true},
|
||||
get: {method: 'GET', params: {action: 'json'}},
|
||||
stop: {method: 'POST', params: {id: '@id', t: 5, action: 'stop'}},
|
||||
restart: {method: 'POST', params: {id: '@id', t: 5, action: 'restart'}},
|
||||
kill: {method: 'POST', params: {id: '@id', action: 'kill'}},
|
||||
pause: {method: 'POST', params: {id: '@id', action: 'pause'}},
|
||||
unpause: {method: 'POST', params: {id: '@id', action: 'unpause'}},
|
||||
stats: {
|
||||
method: 'GET', params: { id: '@id', stream: false, action: 'stats' },
|
||||
timeout: 4500, ignoreLoadingBar: true
|
||||
},
|
||||
top: {
|
||||
method: 'GET', params: { id: '@id', action: 'top' },
|
||||
timeout: 4500, ignoreLoadingBar: true
|
||||
},
|
||||
start: {
|
||||
method: 'POST', params: {id: '@id', action: 'start'},
|
||||
transformResponse: genericHandler
|
||||
},
|
||||
create: {
|
||||
method: 'POST', params: {action: 'create'},
|
||||
transformResponse: genericHandler,
|
||||
ignoreLoadingBar: true
|
||||
},
|
||||
remove: {
|
||||
method: 'DELETE', params: {id: '@id', v: '@v', force: '@force'},
|
||||
transformResponse: genericHandler
|
||||
},
|
||||
rename: {
|
||||
method: 'POST', params: {id: '@id', action: 'rename', name: '@name'},
|
||||
transformResponse: genericHandler
|
||||
},
|
||||
exec: {
|
||||
method: 'POST', params: {id: '@id', action: 'exec'},
|
||||
transformResponse: genericHandler, ignoreLoadingBar: true
|
||||
},
|
||||
inspect: {
|
||||
method: 'GET', params: { id: '@id', action: 'json' }
|
||||
}
|
||||
});
|
||||
}]);
|
10
app/docker/rest/containerCommit.js
Normal file
10
app/docker/rest/containerCommit.js
Normal file
|
@ -0,0 +1,10 @@
|
|||
angular.module('portainer.docker')
|
||||
.factory('ContainerCommit', ['$resource', 'API_ENDPOINT_ENDPOINTS', 'EndpointProvider', function ContainerCommitFactory($resource, API_ENDPOINT_ENDPOINTS, EndpointProvider) {
|
||||
'use strict';
|
||||
return $resource(API_ENDPOINT_ENDPOINTS + '/:endpointId/docker/commit', {
|
||||
endpointId: EndpointProvider.endpointID
|
||||
},
|
||||
{
|
||||
commit: {method: 'POST', params: {container: '@id', repo: '@repo', tag: '@tag'}, ignoreLoadingBar: true}
|
||||
});
|
||||
}]);
|
21
app/docker/rest/containerLogs.js
Normal file
21
app/docker/rest/containerLogs.js
Normal file
|
@ -0,0 +1,21 @@
|
|||
angular.module('portainer.docker')
|
||||
.factory('ContainerLogs', ['$http', 'API_ENDPOINT_ENDPOINTS', 'EndpointProvider', function ContainerLogsFactory($http, API_ENDPOINT_ENDPOINTS, EndpointProvider) {
|
||||
'use strict';
|
||||
return {
|
||||
get: function (id, params, callback) {
|
||||
$http({
|
||||
method: 'GET',
|
||||
url: API_ENDPOINT_ENDPOINTS + '/' + EndpointProvider.endpointID() + '/docker/containers/' + id + '/logs',
|
||||
params: {
|
||||
'stdout': params.stdout || 0,
|
||||
'stderr': params.stderr || 0,
|
||||
'timestamps': params.timestamps || 0,
|
||||
'tail': params.tail || 'all'
|
||||
},
|
||||
ignoreLoadingBar: true
|
||||
}).success(callback).error(function (data, status, headers, config) {
|
||||
console.log(data);
|
||||
});
|
||||
}
|
||||
};
|
||||
}]);
|
13
app/docker/rest/exec.js
Normal file
13
app/docker/rest/exec.js
Normal file
|
@ -0,0 +1,13 @@
|
|||
angular.module('portainer.docker')
|
||||
.factory('Exec', ['$resource', 'API_ENDPOINT_ENDPOINTS', 'EndpointProvider', function ExecFactory($resource, API_ENDPOINT_ENDPOINTS, EndpointProvider) {
|
||||
'use strict';
|
||||
return $resource(API_ENDPOINT_ENDPOINTS + '/:endpointId/docker/exec/:id/:action', {
|
||||
endpointId: EndpointProvider.endpointID
|
||||
},
|
||||
{
|
||||
resize: {
|
||||
method: 'POST', params: {id: '@id', action: 'resize', h: '@height', w: '@width'},
|
||||
transformResponse: genericHandler, ignoreLoadingBar: true
|
||||
}
|
||||
});
|
||||
}]);
|
33
app/docker/rest/image.js
Normal file
33
app/docker/rest/image.js
Normal file
|
@ -0,0 +1,33 @@
|
|||
angular.module('portainer.docker')
|
||||
.factory('Image', ['$resource', 'API_ENDPOINT_ENDPOINTS', 'EndpointProvider', 'HttpRequestHelper', function ImageFactory($resource, API_ENDPOINT_ENDPOINTS, EndpointProvider, HttpRequestHelper) {
|
||||
'use strict';
|
||||
|
||||
return $resource(API_ENDPOINT_ENDPOINTS + '/:endpointId/docker/images/:id/:action', {
|
||||
endpointId: EndpointProvider.endpointID
|
||||
},
|
||||
{
|
||||
query: {method: 'GET', params: {all: 0, action: 'json'}, isArray: true},
|
||||
get: {method: 'GET', params: {action: 'json'}},
|
||||
search: {method: 'GET', params: {action: 'search'}},
|
||||
history: {method: 'GET', params: {action: 'history'}, isArray: true},
|
||||
insert: {method: 'POST', params: {id: '@id', action: 'insert'}},
|
||||
tag: {method: 'POST', params: {id: '@id', action: 'tag', force: 0, repo: '@repo', tag: '@tag'}, ignoreLoadingBar: true},
|
||||
inspect: {method: 'GET', params: {id: '@id', action: 'json'}},
|
||||
push: {
|
||||
method: 'POST', params: {action: 'push', id: '@tag'},
|
||||
isArray: true, transformResponse: jsonObjectsToArrayHandler,
|
||||
headers: { 'X-Registry-Auth': HttpRequestHelper.registryAuthenticationHeader },
|
||||
ignoreLoadingBar: true
|
||||
},
|
||||
create: {
|
||||
method: 'POST', params: {action: 'create', fromImage: '@fromImage', tag: '@tag'},
|
||||
isArray: true, transformResponse: jsonObjectsToArrayHandler,
|
||||
headers: { 'X-Registry-Auth': HttpRequestHelper.registryAuthenticationHeader },
|
||||
ignoreLoadingBar: true
|
||||
},
|
||||
remove: {
|
||||
method: 'DELETE', params: {id: '@id', force: '@force'},
|
||||
isArray: true, transformResponse: deleteImageHandler
|
||||
}
|
||||
});
|
||||
}]);
|
16
app/docker/rest/network.js
Normal file
16
app/docker/rest/network.js
Normal file
|
@ -0,0 +1,16 @@
|
|||
angular.module('portainer.docker')
|
||||
.factory('Network', ['$resource', 'API_ENDPOINT_ENDPOINTS', 'EndpointProvider', function NetworkFactory($resource, API_ENDPOINT_ENDPOINTS, EndpointProvider) {
|
||||
'use strict';
|
||||
return $resource(API_ENDPOINT_ENDPOINTS + '/:endpointId/docker/networks/:id/:action', {
|
||||
id: '@id',
|
||||
endpointId: EndpointProvider.endpointID
|
||||
},
|
||||
{
|
||||
query: {method: 'GET', isArray: true},
|
||||
get: {method: 'GET'},
|
||||
create: {method: 'POST', params: {action: 'create'}, transformResponse: genericHandler, ignoreLoadingBar: true},
|
||||
remove: { method: 'DELETE', transformResponse: genericHandler },
|
||||
connect: {method: 'POST', params: {action: 'connect'}},
|
||||
disconnect: {method: 'POST', params: {action: 'disconnect'}}
|
||||
});
|
||||
}]);
|
13
app/docker/rest/node.js
Normal file
13
app/docker/rest/node.js
Normal file
|
@ -0,0 +1,13 @@
|
|||
angular.module('portainer.docker')
|
||||
.factory('Node', ['$resource', 'API_ENDPOINT_ENDPOINTS', 'EndpointProvider', function NodeFactory($resource, API_ENDPOINT_ENDPOINTS, EndpointProvider) {
|
||||
'use strict';
|
||||
return $resource(API_ENDPOINT_ENDPOINTS + '/:endpointId/docker/nodes/:id/:action', {
|
||||
endpointId: EndpointProvider.endpointID
|
||||
},
|
||||
{
|
||||
query: {method: 'GET', isArray: true},
|
||||
get: {method: 'GET', params: {id: '@id'}},
|
||||
update: { method: 'POST', params: {id: '@id', action: 'update', version: '@version'} },
|
||||
remove: { method: 'DELETE', params: {id: '@id'} }
|
||||
});
|
||||
}]);
|
9
app/docker/rest/plugin.js
Normal file
9
app/docker/rest/plugin.js
Normal file
|
@ -0,0 +1,9 @@
|
|||
angular.module('portainer.docker')
|
||||
.factory('Plugin', ['$resource', 'API_ENDPOINT_ENDPOINTS', 'EndpointProvider', function PluginFactory($resource, API_ENDPOINT_ENDPOINTS, EndpointProvider) {
|
||||
'use strict';
|
||||
return $resource(API_ENDPOINT_ENDPOINTS + '/:endpointId/docker/plugins/:id/:action', {
|
||||
endpointId: EndpointProvider.endpointID
|
||||
}, {
|
||||
query: { method: 'GET', isArray: true }
|
||||
});
|
||||
}]);
|
68
app/docker/rest/response/handlers.js
Normal file
68
app/docker/rest/response/handlers.js
Normal file
|
@ -0,0 +1,68 @@
|
|||
function isJSONArray(jsonString) {
|
||||
return Object.prototype.toString.call(jsonString) === '[object Array]';
|
||||
}
|
||||
|
||||
function isJSON(jsonString) {
|
||||
try {
|
||||
var o = JSON.parse(jsonString);
|
||||
if (o && typeof o === 'object') {
|
||||
return o;
|
||||
}
|
||||
}
|
||||
catch (e) { }
|
||||
return false;
|
||||
}
|
||||
|
||||
// The Docker API often returns a list of JSON object.
|
||||
// This handler wrap the JSON objects in an array.
|
||||
// Used by the API in: Image push, Image create, Events query.
|
||||
function jsonObjectsToArrayHandler(data) {
|
||||
var str = '[' + data.replace(/\n/g, ' ').replace(/\}\s*\{/g, '}, {') + ']';
|
||||
return angular.fromJson(str);
|
||||
}
|
||||
|
||||
// The Docker API often returns an empty string or a valid JSON object on success (Docker 1.9 -> Docker 1.12).
|
||||
// On error, it returns either an error message as a string (Docker < 1.12) or a JSON object with the field message
|
||||
// container the error (Docker = 1.12)
|
||||
// This handler ensure a valid JSON object is returned in any case.
|
||||
// Used by the API in: container deletion, network deletion, network creation, volume creation,
|
||||
// container exec, exec resize.
|
||||
function genericHandler(data) {
|
||||
var response = {};
|
||||
// No data is returned when deletion is successful (Docker 1.9 -> 1.12)
|
||||
if (!data) {
|
||||
return response;
|
||||
}
|
||||
// A string is returned on failure (Docker < 1.12)
|
||||
else if (!isJSON(data)) {
|
||||
response.message = data;
|
||||
}
|
||||
// Docker 1.12 returns a valid JSON object when an error occurs
|
||||
else {
|
||||
response = angular.fromJson(data);
|
||||
}
|
||||
return response;
|
||||
}
|
||||
|
||||
// Image delete API returns an array on success (Docker 1.9 -> Docker 1.12).
|
||||
// On error, it returns either an error message as a string (Docker < 1.12) or a JSON object with the field message
|
||||
// container the error (Docker = 1.12).
|
||||
// This handler returns the original array on success or a newly created array containing
|
||||
// only one JSON object with the field message filled with the error message on failure.
|
||||
function deleteImageHandler(data) {
|
||||
// A string is returned on failure (Docker < 1.12)
|
||||
var response = [];
|
||||
if (!isJSON(data)) {
|
||||
response.push({message: data});
|
||||
}
|
||||
// A JSON object is returned on failure (Docker = 1.12)
|
||||
else if (!isJSONArray(data)) {
|
||||
var json = angular.fromJson(data);
|
||||
response.push(json);
|
||||
}
|
||||
// An array is returned on success (Docker 1.9 -> 1.12)
|
||||
else {
|
||||
response = angular.fromJson(data);
|
||||
}
|
||||
return response;
|
||||
}
|
12
app/docker/rest/secret.js
Normal file
12
app/docker/rest/secret.js
Normal file
|
@ -0,0 +1,12 @@
|
|||
angular.module('portainer.docker')
|
||||
.factory('Secret', ['$resource', 'API_ENDPOINT_ENDPOINTS', 'EndpointProvider', function SecretFactory($resource, API_ENDPOINT_ENDPOINTS, EndpointProvider) {
|
||||
'use strict';
|
||||
return $resource(API_ENDPOINT_ENDPOINTS + '/:endpointId/docker/secrets/:id/:action', {
|
||||
endpointId: EndpointProvider.endpointID
|
||||
}, {
|
||||
get: { method: 'GET', params: {id: '@id'} },
|
||||
query: { method: 'GET', isArray: true },
|
||||
create: { method: 'POST', params: {action: 'create'}, ignoreLoadingBar: true },
|
||||
remove: { method: 'DELETE', params: {id: '@id'} }
|
||||
});
|
||||
}]);
|
18
app/docker/rest/service.js
Normal file
18
app/docker/rest/service.js
Normal file
|
@ -0,0 +1,18 @@
|
|||
angular.module('portainer.docker')
|
||||
.factory('Service', ['$resource', 'API_ENDPOINT_ENDPOINTS', 'EndpointProvider', 'HttpRequestHelper' ,function ServiceFactory($resource, API_ENDPOINT_ENDPOINTS, EndpointProvider, HttpRequestHelper) {
|
||||
'use strict';
|
||||
return $resource(API_ENDPOINT_ENDPOINTS + '/:endpointId/docker/services/:id/:action', {
|
||||
endpointId: EndpointProvider.endpointID
|
||||
},
|
||||
{
|
||||
get: { method: 'GET', params: {id: '@id'} },
|
||||
query: { method: 'GET', isArray: true, params: {filters: '@filters'} },
|
||||
create: {
|
||||
method: 'POST', params: {action: 'create'},
|
||||
headers: { 'X-Registry-Auth': HttpRequestHelper.registryAuthenticationHeader },
|
||||
ignoreLoadingBar: true
|
||||
},
|
||||
update: { method: 'POST', params: {id: '@id', action: 'update', version: '@version'} },
|
||||
remove: { method: 'DELETE', params: {id: '@id'} }
|
||||
});
|
||||
}]);
|
20
app/docker/rest/serviceLogs.js
Normal file
20
app/docker/rest/serviceLogs.js
Normal file
|
@ -0,0 +1,20 @@
|
|||
angular.module('portainer.docker')
|
||||
.factory('ServiceLogs', ['$http', 'API_ENDPOINT_ENDPOINTS', 'EndpointProvider', function ServiceLogsFactory($http, API_ENDPOINT_ENDPOINTS, EndpointProvider) {
|
||||
'use strict';
|
||||
return {
|
||||
get: function (id, params, callback) {
|
||||
$http({
|
||||
method: 'GET',
|
||||
url: API_ENDPOINT_ENDPOINTS + '/' + EndpointProvider.endpointID() + '/docker/services/' + id + '/logs',
|
||||
params: {
|
||||
'stdout': params.stdout || 0,
|
||||
'stderr': params.stderr || 0,
|
||||
'timestamps': params.timestamps || 0,
|
||||
'tail': params.tail || 'all'
|
||||
}
|
||||
}).success(callback).error(function (data, status, headers, config) {
|
||||
console.log(data);
|
||||
});
|
||||
}
|
||||
};
|
||||
}]);
|
10
app/docker/rest/swarm.js
Normal file
10
app/docker/rest/swarm.js
Normal file
|
@ -0,0 +1,10 @@
|
|||
angular.module('portainer.docker')
|
||||
.factory('Swarm', ['$resource', 'API_ENDPOINT_ENDPOINTS', 'EndpointProvider', function SwarmFactory($resource, API_ENDPOINT_ENDPOINTS, EndpointProvider) {
|
||||
'use strict';
|
||||
return $resource(API_ENDPOINT_ENDPOINTS + '/:endpointId/docker/swarm', {
|
||||
endpointId: EndpointProvider.endpointID
|
||||
},
|
||||
{
|
||||
get: {method: 'GET'}
|
||||
});
|
||||
}]);
|
18
app/docker/rest/system.js
Normal file
18
app/docker/rest/system.js
Normal file
|
@ -0,0 +1,18 @@
|
|||
angular.module('portainer.docker')
|
||||
.factory('System', ['$resource', 'API_ENDPOINT_ENDPOINTS', 'EndpointProvider', function SystemFactory($resource, API_ENDPOINT_ENDPOINTS, EndpointProvider) {
|
||||
'use strict';
|
||||
return $resource(API_ENDPOINT_ENDPOINTS + '/:endpointId/docker/:action/:subAction', {
|
||||
name: '@name',
|
||||
endpointId: EndpointProvider.endpointID
|
||||
},
|
||||
{
|
||||
info: { method: 'GET', params: { action: 'info' }, ignoreLoadingBar: true },
|
||||
version: { method: 'GET', params: { action: 'version' }, ignoreLoadingBar: true },
|
||||
events: {
|
||||
method: 'GET', params: { action: 'events', since: '@since', until: '@until' },
|
||||
isArray: true, transformResponse: jsonObjectsToArrayHandler
|
||||
},
|
||||
auth: { method: 'POST', params: { action: 'auth' } },
|
||||
dataUsage: { method: 'GET', params: { action: 'system', subAction: 'df' } }
|
||||
});
|
||||
}]);
|
11
app/docker/rest/task.js
Normal file
11
app/docker/rest/task.js
Normal file
|
@ -0,0 +1,11 @@
|
|||
angular.module('portainer.docker')
|
||||
.factory('Task', ['$resource', 'API_ENDPOINT_ENDPOINTS', 'EndpointProvider', function TaskFactory($resource, API_ENDPOINT_ENDPOINTS, EndpointProvider) {
|
||||
'use strict';
|
||||
return $resource(API_ENDPOINT_ENDPOINTS + '/:endpointId/docker/tasks/:id', {
|
||||
endpointId: EndpointProvider.endpointID
|
||||
},
|
||||
{
|
||||
get: { method: 'GET', params: {id: '@id'} },
|
||||
query: { method: 'GET', isArray: true, params: {filters: '@filters'} }
|
||||
});
|
||||
}]);
|
16
app/docker/rest/volume.js
Normal file
16
app/docker/rest/volume.js
Normal file
|
@ -0,0 +1,16 @@
|
|||
angular.module('portainer.docker')
|
||||
.factory('Volume', ['$resource', 'API_ENDPOINT_ENDPOINTS', 'EndpointProvider', function VolumeFactory($resource, API_ENDPOINT_ENDPOINTS, EndpointProvider) {
|
||||
'use strict';
|
||||
return $resource(API_ENDPOINT_ENDPOINTS + '/:endpointId/docker/volumes/:id/:action',
|
||||
{
|
||||
endpointId: EndpointProvider.endpointID
|
||||
},
|
||||
{
|
||||
query: { method: 'GET' },
|
||||
get: { method: 'GET', params: {id: '@id'} },
|
||||
create: {method: 'POST', params: {action: 'create'}, transformResponse: genericHandler, ignoreLoadingBar: true},
|
||||
remove: {
|
||||
method: 'DELETE', transformResponse: genericHandler, params: {id: '@id'}
|
||||
}
|
||||
});
|
||||
}]);
|
61
app/docker/services/configService.js
Normal file
61
app/docker/services/configService.js
Normal file
|
@ -0,0 +1,61 @@
|
|||
angular.module('portainer.docker')
|
||||
.factory('ConfigService', ['$q', 'Config', function ConfigServiceFactory($q, Config) {
|
||||
'use strict';
|
||||
var service = {};
|
||||
|
||||
service.config = function(configId) {
|
||||
var deferred = $q.defer();
|
||||
|
||||
Config.get({id: configId}).$promise
|
||||
.then(function success(data) {
|
||||
var config = new ConfigViewModel(data);
|
||||
deferred.resolve(config);
|
||||
})
|
||||
.catch(function error(err) {
|
||||
deferred.reject({ msg: 'Unable to retrieve config details', err: err });
|
||||
});
|
||||
|
||||
return deferred.promise;
|
||||
};
|
||||
|
||||
service.configs = function() {
|
||||
var deferred = $q.defer();
|
||||
|
||||
Config.query({}).$promise
|
||||
.then(function success(data) {
|
||||
var configs = data.map(function (item) {
|
||||
return new ConfigViewModel(item);
|
||||
});
|
||||
deferred.resolve(configs);
|
||||
})
|
||||
.catch(function error(err) {
|
||||
deferred.reject({ msg: 'Unable to retrieve configs', err: err });
|
||||
});
|
||||
|
||||
return deferred.promise;
|
||||
};
|
||||
|
||||
service.remove = function(configId) {
|
||||
var deferred = $q.defer();
|
||||
|
||||
Config.remove({ id: configId }).$promise
|
||||
.then(function success(data) {
|
||||
if (data.message) {
|
||||
deferred.reject({ msg: data.message });
|
||||
} else {
|
||||
deferred.resolve();
|
||||
}
|
||||
})
|
||||
.catch(function error(err) {
|
||||
deferred.reject({ msg: 'Unable to remove config', err: err });
|
||||
});
|
||||
|
||||
return deferred.promise;
|
||||
};
|
||||
|
||||
service.create = function(config) {
|
||||
return Config.create(config).$promise;
|
||||
};
|
||||
|
||||
return service;
|
||||
}]);
|
158
app/docker/services/containerService.js
Normal file
158
app/docker/services/containerService.js
Normal file
|
@ -0,0 +1,158 @@
|
|||
angular.module('portainer.docker')
|
||||
.factory('ContainerService', ['$q', 'Container', 'ResourceControlService', function ContainerServiceFactory($q, Container, ResourceControlService) {
|
||||
'use strict';
|
||||
var service = {};
|
||||
|
||||
service.container = function(id) {
|
||||
var deferred = $q.defer();
|
||||
|
||||
Container.get({ id: id }).$promise
|
||||
.then(function success(data) {
|
||||
var container = new ContainerDetailsViewModel(data);
|
||||
deferred.resolve(container);
|
||||
})
|
||||
.catch(function error(err) {
|
||||
deferred.reject({ msg: 'Unable to retrieve container information', err: err });
|
||||
});
|
||||
|
||||
return deferred.promise;
|
||||
};
|
||||
|
||||
service.containers = function(all, filters) {
|
||||
var deferred = $q.defer();
|
||||
Container.query({ all : all, filters: filters }).$promise
|
||||
.then(function success(data) {
|
||||
var containers = data.map(function (item) {
|
||||
return new ContainerViewModel(item);
|
||||
});
|
||||
deferred.resolve(containers);
|
||||
})
|
||||
.catch(function error(err) {
|
||||
deferred.reject({ msg: 'Unable to retrieve containers', err: err });
|
||||
});
|
||||
|
||||
return deferred.promise;
|
||||
};
|
||||
|
||||
service.createContainer = function(configuration) {
|
||||
var deferred = $q.defer();
|
||||
Container.create(configuration).$promise
|
||||
.then(function success(data) {
|
||||
if (data.message) {
|
||||
deferred.reject({ msg: data.message });
|
||||
} else {
|
||||
deferred.resolve(data);
|
||||
}
|
||||
})
|
||||
.catch(function error(err) {
|
||||
deferred.reject({ msg: 'Unable to create container', err: err });
|
||||
});
|
||||
return deferred.promise;
|
||||
};
|
||||
|
||||
service.startContainer = function(containerID) {
|
||||
return Container.start({ id: containerID }, {}).$promise;
|
||||
};
|
||||
|
||||
service.stopContainer = function(containerID) {
|
||||
return Container.stop({ id: containerID }, {}).$promise;
|
||||
};
|
||||
|
||||
service.restartContainer = function(containerID) {
|
||||
return Container.restart({ id: containerID }, {}).$promise;
|
||||
};
|
||||
|
||||
service.killContainer = function(containerID) {
|
||||
return Container.kill({ id: containerID }, {}).$promise;
|
||||
};
|
||||
|
||||
service.pauseContainer = function(containerID) {
|
||||
return Container.pause({ id: containerID }, {}).$promise;
|
||||
};
|
||||
|
||||
service.resumeContainer = function(containerID) {
|
||||
return Container.unpause({ id: containerID }, {}).$promise;
|
||||
};
|
||||
|
||||
service.createAndStartContainer = function(configuration) {
|
||||
var deferred = $q.defer();
|
||||
var containerID;
|
||||
service.createContainer(configuration)
|
||||
.then(function success(data) {
|
||||
containerID = data.Id;
|
||||
return service.startContainer(containerID);
|
||||
})
|
||||
.then(function success() {
|
||||
deferred.resolve({ Id: containerID });
|
||||
})
|
||||
.catch(function error(err) {
|
||||
deferred.reject(err);
|
||||
});
|
||||
return deferred.promise;
|
||||
};
|
||||
|
||||
service.remove = function(container, removeVolumes) {
|
||||
var deferred = $q.defer();
|
||||
|
||||
Container.remove({id: container.Id, v: (removeVolumes) ? 1 : 0, force: true}).$promise
|
||||
.then(function success(data) {
|
||||
if (data.message) {
|
||||
deferred.reject({ msg: data.message, err: data.message });
|
||||
}
|
||||
if (container.ResourceControl && container.ResourceControl.Type === 1) {
|
||||
return ResourceControlService.deleteResourceControl(container.ResourceControl.Id);
|
||||
}
|
||||
})
|
||||
.then(function success() {
|
||||
deferred.resolve();
|
||||
})
|
||||
.catch(function error(err) {
|
||||
deferred.reject({ msg: 'Unable to remove container', err: err });
|
||||
});
|
||||
|
||||
return deferred.promise;
|
||||
};
|
||||
|
||||
service.createExec = function(execConfig) {
|
||||
var deferred = $q.defer();
|
||||
|
||||
Container.exec(execConfig).$promise
|
||||
.then(function success(data) {
|
||||
if (data.message) {
|
||||
deferred.reject({ msg: data.message, err: data.message });
|
||||
} else {
|
||||
deferred.resolve(data);
|
||||
}
|
||||
})
|
||||
.catch(function error(err) {
|
||||
deferred.reject(err);
|
||||
});
|
||||
|
||||
return deferred.promise;
|
||||
};
|
||||
|
||||
service.containerStats = function(id) {
|
||||
var deferred = $q.defer();
|
||||
|
||||
Container.stats({id: id}).$promise
|
||||
.then(function success(data) {
|
||||
var containerStats = new ContainerStatsViewModel(data);
|
||||
deferred.resolve(containerStats);
|
||||
})
|
||||
.catch(function error(err) {
|
||||
deferred.reject(err);
|
||||
});
|
||||
|
||||
return deferred.promise;
|
||||
};
|
||||
|
||||
service.containerTop = function(id) {
|
||||
return Container.top({id: id}).$promise;
|
||||
};
|
||||
|
||||
service.inspect = function(id) {
|
||||
return Container.inspect({id: id}).$promise;
|
||||
};
|
||||
|
||||
return service;
|
||||
}]);
|
27
app/docker/services/execService.js
Normal file
27
app/docker/services/execService.js
Normal file
|
@ -0,0 +1,27 @@
|
|||
angular.module('portainer.docker')
|
||||
.factory('ExecService', ['$q', '$timeout', 'Exec', function ExecServiceFactory($q, $timeout, Exec) {
|
||||
'use strict';
|
||||
var service = {};
|
||||
|
||||
service.resizeTTY = function(execId, height, width, timeout) {
|
||||
var deferred = $q.defer();
|
||||
|
||||
$timeout(function() {
|
||||
Exec.resize({id: execId, height: height, width: width}).$promise
|
||||
.then(function success(data) {
|
||||
if (data.message) {
|
||||
deferred.reject({ msg: 'Unable to exec into container', err: data.message });
|
||||
} else {
|
||||
deferred.resolve(data);
|
||||
}
|
||||
})
|
||||
.catch(function error(err) {
|
||||
deferred.reject({ msg: 'Unable to exec into container', err: err });
|
||||
});
|
||||
}, timeout);
|
||||
|
||||
return deferred.promise;
|
||||
};
|
||||
|
||||
return service;
|
||||
}]);
|
165
app/docker/services/imageService.js
Normal file
165
app/docker/services/imageService.js
Normal file
|
@ -0,0 +1,165 @@
|
|||
angular.module('portainer.docker')
|
||||
.factory('ImageService', ['$q', 'Image', 'ImageHelper', 'RegistryService', 'HttpRequestHelper', 'ContainerService', function ImageServiceFactory($q, Image, ImageHelper, RegistryService, HttpRequestHelper, ContainerService) {
|
||||
'use strict';
|
||||
var service = {};
|
||||
|
||||
service.image = function(imageId) {
|
||||
var deferred = $q.defer();
|
||||
Image.get({id: imageId}).$promise
|
||||
.then(function success(data) {
|
||||
if (data.message) {
|
||||
deferred.reject({ msg: data.message });
|
||||
} else {
|
||||
var image = new ImageDetailsViewModel(data);
|
||||
deferred.resolve(image);
|
||||
}
|
||||
})
|
||||
.catch(function error(err) {
|
||||
deferred.reject({ msg: 'Unable to retrieve image details', err: err });
|
||||
});
|
||||
return deferred.promise;
|
||||
};
|
||||
|
||||
service.images = function(withUsage) {
|
||||
var deferred = $q.defer();
|
||||
|
||||
$q.all({
|
||||
containers: withUsage ? ContainerService.containers(1) : [],
|
||||
images: Image.query({}).$promise
|
||||
})
|
||||
.then(function success(data) {
|
||||
var containers = data.containers;
|
||||
|
||||
var images = data.images.map(function(item) {
|
||||
item.ContainerCount = 0;
|
||||
for (var i = 0; i < containers.length; i++) {
|
||||
var container = containers[i];
|
||||
if (container.ImageID === item.Id) {
|
||||
item.ContainerCount++;
|
||||
}
|
||||
}
|
||||
return new ImageViewModel(item);
|
||||
});
|
||||
|
||||
deferred.resolve(images);
|
||||
})
|
||||
.catch(function error(err) {
|
||||
deferred.reject({ msg: 'Unable to retrieve images', err: err });
|
||||
});
|
||||
return deferred.promise;
|
||||
};
|
||||
|
||||
service.history = function(imageId) {
|
||||
var deferred = $q.defer();
|
||||
Image.history({id: imageId}).$promise
|
||||
.then(function success(data) {
|
||||
if (data.message) {
|
||||
deferred.reject({ msg: data.message });
|
||||
} else {
|
||||
var layers = [];
|
||||
angular.forEach(data, function(imageLayer) {
|
||||
layers.push(new ImageLayerViewModel(imageLayer));
|
||||
});
|
||||
deferred.resolve(layers);
|
||||
}
|
||||
})
|
||||
.catch(function error(err) {
|
||||
deferred.reject({ msg: 'Unable to retrieve image details', err: err });
|
||||
});
|
||||
return deferred.promise;
|
||||
};
|
||||
|
||||
service.pushImage = function(tag, registry) {
|
||||
var deferred = $q.defer();
|
||||
|
||||
var authenticationDetails = registry.Authentication ? RegistryService.encodedCredentials(registry) : '';
|
||||
HttpRequestHelper.setRegistryAuthenticationHeader(authenticationDetails);
|
||||
Image.push({tag: tag}).$promise
|
||||
.then(function success(data) {
|
||||
if (data[data.length - 1].error) {
|
||||
deferred.reject({ msg: data[data.length - 1].error });
|
||||
} else {
|
||||
deferred.resolve();
|
||||
}
|
||||
})
|
||||
.catch(function error(err) {
|
||||
deferred.reject({ msg: 'Unable to push image tag', err: err });
|
||||
});
|
||||
return deferred.promise;
|
||||
};
|
||||
|
||||
function pullImageAndIgnoreErrors(imageConfiguration) {
|
||||
var deferred = $q.defer();
|
||||
|
||||
Image.create({}, imageConfiguration).$promise
|
||||
.finally(function final() {
|
||||
deferred.resolve();
|
||||
});
|
||||
|
||||
return deferred.promise;
|
||||
}
|
||||
|
||||
function pullImageAndAcknowledgeErrors(imageConfiguration) {
|
||||
var deferred = $q.defer();
|
||||
|
||||
Image.create({}, imageConfiguration).$promise
|
||||
.then(function success(data) {
|
||||
var err = data.length > 0 && data[data.length - 1].hasOwnProperty('message');
|
||||
if (err) {
|
||||
var detail = data[data.length - 1];
|
||||
deferred.reject({ msg: detail.message });
|
||||
} else {
|
||||
deferred.resolve(data);
|
||||
}
|
||||
})
|
||||
.catch(function error(err) {
|
||||
deferred.reject({ msg: 'Unable to pull image', err: err });
|
||||
});
|
||||
|
||||
return deferred.promise;
|
||||
}
|
||||
|
||||
service.pullImage = function(image, registry, ignoreErrors) {
|
||||
var imageDetails = ImageHelper.extractImageAndRegistryFromRepository(image);
|
||||
var imageConfiguration = ImageHelper.createImageConfigForContainer(imageDetails.image, registry.URL);
|
||||
var authenticationDetails = registry.Authentication ? RegistryService.encodedCredentials(registry) : '';
|
||||
HttpRequestHelper.setRegistryAuthenticationHeader(authenticationDetails);
|
||||
|
||||
if (ignoreErrors) {
|
||||
return pullImageAndIgnoreErrors(imageConfiguration);
|
||||
}
|
||||
return pullImageAndAcknowledgeErrors(imageConfiguration);
|
||||
};
|
||||
|
||||
service.tagImage = function(id, image, registry) {
|
||||
var imageConfig = ImageHelper.createImageConfigForCommit(image, registry);
|
||||
return Image.tag({id: id, tag: imageConfig.tag, repo: imageConfig.repo}).$promise;
|
||||
};
|
||||
|
||||
service.deleteImage = function(id, forceRemoval) {
|
||||
var deferred = $q.defer();
|
||||
Image.remove({id: id, force: forceRemoval}).$promise
|
||||
.then(function success(data) {
|
||||
if (data[0].message) {
|
||||
deferred.reject({ msg: data[0].message });
|
||||
} else {
|
||||
deferred.resolve();
|
||||
}
|
||||
})
|
||||
.catch(function error(err) {
|
||||
deferred.reject({ msg: 'Unable to remove image', err: err });
|
||||
});
|
||||
return deferred.promise;
|
||||
};
|
||||
|
||||
service.getUniqueTagListFromImages = function (availableImages) {
|
||||
return _.flatten(_.map(availableImages, function (image) {
|
||||
_.remove(image.RepoTags, function (item) {
|
||||
return item.indexOf('<none>') !== -1;
|
||||
});
|
||||
return image.RepoTags ? _.uniqWith(image.RepoTags, _.isEqual) : [];
|
||||
}));
|
||||
};
|
||||
|
||||
return service;
|
||||
}]);
|
72
app/docker/services/networkService.js
Normal file
72
app/docker/services/networkService.js
Normal file
|
@ -0,0 +1,72 @@
|
|||
angular.module('portainer.docker')
|
||||
.factory('NetworkService', ['$q', 'Network', function NetworkServiceFactory($q, Network) {
|
||||
'use strict';
|
||||
var service = {};
|
||||
|
||||
service.create = function(networkConfiguration) {
|
||||
var deferred = $q.defer();
|
||||
|
||||
Network.create(networkConfiguration).$promise
|
||||
.then(function success(data) {
|
||||
deferred.resolve(data);
|
||||
})
|
||||
.catch(function error(err) {
|
||||
deferred.reject({ msg: 'Unable to create network', err: err });
|
||||
});
|
||||
return deferred.promise;
|
||||
};
|
||||
|
||||
service.network = function(id) {
|
||||
var deferred = $q.defer();
|
||||
|
||||
Network.get({id: id}).$promise
|
||||
.then(function success(data) {
|
||||
var network = new NetworkViewModel(data);
|
||||
deferred.resolve(network);
|
||||
})
|
||||
.catch(function error(err) {
|
||||
deferred.reject({msg: 'Unable to retrieve network details', err: err});
|
||||
});
|
||||
|
||||
return deferred.promise;
|
||||
};
|
||||
|
||||
service.networks = function(localNetworks, swarmNetworks, swarmAttachableNetworks, globalNetworks) {
|
||||
var deferred = $q.defer();
|
||||
|
||||
Network.query({}).$promise
|
||||
.then(function success(data) {
|
||||
var networks = data;
|
||||
|
||||
var filteredNetworks = networks.filter(function(network) {
|
||||
if (localNetworks && network.Scope === 'local') {
|
||||
return network;
|
||||
}
|
||||
if (swarmNetworks && network.Scope === 'swarm') {
|
||||
return network;
|
||||
}
|
||||
if (swarmAttachableNetworks && network.Scope === 'swarm' && network.Attachable === true) {
|
||||
return network;
|
||||
}
|
||||
if (globalNetworks && network.Scope === 'global') {
|
||||
return network;
|
||||
}
|
||||
}).map(function (item) {
|
||||
return new NetworkViewModel(item);
|
||||
});
|
||||
|
||||
deferred.resolve(filteredNetworks);
|
||||
})
|
||||
.catch(function error(err) {
|
||||
deferred.reject({msg: 'Unable to retrieve networks', err: err});
|
||||
});
|
||||
|
||||
return deferred.promise;
|
||||
};
|
||||
|
||||
service.remove = function(id) {
|
||||
return Network.remove({ id: id }).$promise;
|
||||
};
|
||||
|
||||
return service;
|
||||
}]);
|
24
app/docker/services/nodeService.js
Normal file
24
app/docker/services/nodeService.js
Normal file
|
@ -0,0 +1,24 @@
|
|||
angular.module('portainer.docker')
|
||||
.factory('NodeService', ['$q', 'Node', function NodeServiceFactory($q, Node) {
|
||||
'use strict';
|
||||
var service = {};
|
||||
|
||||
service.nodes = function() {
|
||||
var deferred = $q.defer();
|
||||
|
||||
Node.query({}).$promise
|
||||
.then(function success(data) {
|
||||
var nodes = data.map(function (item) {
|
||||
return new NodeViewModel(item);
|
||||
});
|
||||
deferred.resolve(nodes);
|
||||
})
|
||||
.catch(function error(err) {
|
||||
deferred.reject({ msg: 'Unable to retrieve nodes', err: err });
|
||||
});
|
||||
|
||||
return deferred.promise;
|
||||
};
|
||||
|
||||
return service;
|
||||
}]);
|
69
app/docker/services/pluginService.js
Normal file
69
app/docker/services/pluginService.js
Normal file
|
@ -0,0 +1,69 @@
|
|||
angular.module('portainer.docker')
|
||||
.factory('PluginService', ['$q', 'Plugin', 'SystemService', function PluginServiceFactory($q, Plugin, SystemService) {
|
||||
'use strict';
|
||||
var service = {};
|
||||
|
||||
service.plugins = function() {
|
||||
var deferred = $q.defer();
|
||||
var plugins = [];
|
||||
|
||||
Plugin.query({}).$promise
|
||||
.then(function success(data) {
|
||||
for (var i = 0; i < data.length; i++) {
|
||||
var plugin = new PluginViewModel(data[i]);
|
||||
plugins.push(plugin);
|
||||
}
|
||||
})
|
||||
.finally(function final() {
|
||||
deferred.resolve(plugins);
|
||||
});
|
||||
|
||||
return deferred.promise;
|
||||
};
|
||||
|
||||
function servicePlugins(systemOnly, pluginType, pluginVersion) {
|
||||
var deferred = $q.defer();
|
||||
|
||||
$q.all({
|
||||
system: SystemService.plugins(),
|
||||
plugins: systemOnly ? [] : service.plugins()
|
||||
})
|
||||
.then(function success(data) {
|
||||
var aggregatedPlugins = [];
|
||||
var systemPlugins = data.system;
|
||||
var plugins = data.plugins;
|
||||
|
||||
if (systemPlugins[pluginType]) {
|
||||
aggregatedPlugins = aggregatedPlugins.concat(systemPlugins[pluginType]);
|
||||
}
|
||||
|
||||
for (var i = 0; i < plugins.length; i++) {
|
||||
var plugin = plugins[i];
|
||||
if (plugin.Enabled && _.includes(plugin.Config.Interface.Types, pluginVersion)) {
|
||||
aggregatedPlugins.push(plugin.Name);
|
||||
}
|
||||
}
|
||||
|
||||
deferred.resolve(aggregatedPlugins);
|
||||
})
|
||||
.catch(function error(err) {
|
||||
deferred.reject({ msg: err.msg, err: err });
|
||||
});
|
||||
|
||||
return deferred.promise;
|
||||
}
|
||||
|
||||
service.volumePlugins = function(systemOnly) {
|
||||
return servicePlugins(systemOnly, 'Volume', 'docker.volumedriver/1.0');
|
||||
};
|
||||
|
||||
service.networkPlugins = function(systemOnly) {
|
||||
return servicePlugins(systemOnly, 'Network', 'docker.networkdriver/1.0');
|
||||
};
|
||||
|
||||
service.loggingPlugins = function(systemOnly) {
|
||||
return servicePlugins(systemOnly, 'Log', 'docker.logdriver/1.0');
|
||||
};
|
||||
|
||||
return service;
|
||||
}]);
|
61
app/docker/services/secretService.js
Normal file
61
app/docker/services/secretService.js
Normal file
|
@ -0,0 +1,61 @@
|
|||
angular.module('portainer.docker')
|
||||
.factory('SecretService', ['$q', 'Secret', function SecretServiceFactory($q, Secret) {
|
||||
'use strict';
|
||||
var service = {};
|
||||
|
||||
service.secret = function(secretId) {
|
||||
var deferred = $q.defer();
|
||||
|
||||
Secret.get({id: secretId}).$promise
|
||||
.then(function success(data) {
|
||||
var secret = new SecretViewModel(data);
|
||||
deferred.resolve(secret);
|
||||
})
|
||||
.catch(function error(err) {
|
||||
deferred.reject({ msg: 'Unable to retrieve secret details', err: err });
|
||||
});
|
||||
|
||||
return deferred.promise;
|
||||
};
|
||||
|
||||
service.secrets = function() {
|
||||
var deferred = $q.defer();
|
||||
|
||||
Secret.query({}).$promise
|
||||
.then(function success(data) {
|
||||
var secrets = data.map(function (item) {
|
||||
return new SecretViewModel(item);
|
||||
});
|
||||
deferred.resolve(secrets);
|
||||
})
|
||||
.catch(function error(err) {
|
||||
deferred.reject({ msg: 'Unable to retrieve secrets', err: err });
|
||||
});
|
||||
|
||||
return deferred.promise;
|
||||
};
|
||||
|
||||
service.remove = function(secretId) {
|
||||
var deferred = $q.defer();
|
||||
|
||||
Secret.remove({ id: secretId }).$promise
|
||||
.then(function success(data) {
|
||||
if (data.message) {
|
||||
deferred.reject({ msg: data.message });
|
||||
} else {
|
||||
deferred.resolve();
|
||||
}
|
||||
})
|
||||
.catch(function error(err) {
|
||||
deferred.reject({ msg: 'Unable to remove secret', err: err });
|
||||
});
|
||||
|
||||
return deferred.promise;
|
||||
};
|
||||
|
||||
service.create = function(secretConfig) {
|
||||
return Secret.create(secretConfig).$promise;
|
||||
};
|
||||
|
||||
return service;
|
||||
}]);
|
62
app/docker/services/serviceService.js
Normal file
62
app/docker/services/serviceService.js
Normal file
|
@ -0,0 +1,62 @@
|
|||
angular.module('portainer.docker')
|
||||
.factory('ServiceService', ['$q', 'Service', 'ServiceHelper', 'TaskService', 'ResourceControlService', function ServiceServiceFactory($q, Service, ServiceHelper, TaskService, ResourceControlService) {
|
||||
'use strict';
|
||||
var service = {};
|
||||
|
||||
service.services = function(filters) {
|
||||
var deferred = $q.defer();
|
||||
|
||||
Service.query({ filters: filters ? filters : {} }).$promise
|
||||
.then(function success(data) {
|
||||
var services = data.map(function (item) {
|
||||
return new ServiceViewModel(item);
|
||||
});
|
||||
deferred.resolve(services);
|
||||
})
|
||||
.catch(function error(err) {
|
||||
deferred.reject({ msg: 'Unable to retrieve services', err: err });
|
||||
});
|
||||
|
||||
return deferred.promise;
|
||||
};
|
||||
|
||||
service.service = function(id) {
|
||||
var deferred = $q.defer();
|
||||
|
||||
Service.get({ id: id }).$promise
|
||||
.then(function success(data) {
|
||||
var service = new ServiceViewModel(data);
|
||||
deferred.resolve(service);
|
||||
})
|
||||
.catch(function error(err) {
|
||||
deferred.reject({ msg: 'Unable to retrieve service details', err: err });
|
||||
});
|
||||
|
||||
return deferred.promise;
|
||||
};
|
||||
|
||||
service.remove = function(service) {
|
||||
var deferred = $q.defer();
|
||||
|
||||
Service.remove({id: service.Id}).$promise
|
||||
.then(function success() {
|
||||
if (service.ResourceControl && service.ResourceControl.Type === 2) {
|
||||
return ResourceControlService.deleteResourceControl(service.ResourceControl.Id);
|
||||
}
|
||||
})
|
||||
.then(function success() {
|
||||
deferred.resolve();
|
||||
})
|
||||
.catch(function error(err) {
|
||||
deferred.reject({ msg: 'Unable to remove service', err: err });
|
||||
});
|
||||
|
||||
return deferred.promise;
|
||||
};
|
||||
|
||||
service.update = function(service, config) {
|
||||
return Service.update({ id: service.Id, version: service.Version }, config).$promise;
|
||||
};
|
||||
|
||||
return service;
|
||||
}]);
|
175
app/docker/services/stackService.js
Normal file
175
app/docker/services/stackService.js
Normal file
|
@ -0,0 +1,175 @@
|
|||
angular.module('portainer.docker')
|
||||
.factory('StackService', ['$q', 'Stack', 'ResourceControlService', 'FileUploadService', 'StackHelper', 'ServiceService', 'SwarmService',
|
||||
function StackServiceFactory($q, Stack, ResourceControlService, FileUploadService, StackHelper, ServiceService, SwarmService) {
|
||||
'use strict';
|
||||
var service = {};
|
||||
|
||||
service.stack = function(id) {
|
||||
var deferred = $q.defer();
|
||||
|
||||
Stack.get({ id: id }).$promise
|
||||
.then(function success(data) {
|
||||
var stack = new StackViewModel(data);
|
||||
deferred.resolve(stack);
|
||||
})
|
||||
.catch(function error(err) {
|
||||
deferred.reject({ msg: 'Unable to retrieve stack details', err: err });
|
||||
});
|
||||
|
||||
return deferred.promise;
|
||||
};
|
||||
|
||||
service.getStackFile = function(id) {
|
||||
var deferred = $q.defer();
|
||||
|
||||
Stack.getStackFile({ id: id }).$promise
|
||||
.then(function success(data) {
|
||||
deferred.resolve(data.StackFileContent);
|
||||
})
|
||||
.catch(function error(err) {
|
||||
deferred.reject({ msg: 'Unable to retrieve stack content', err: err });
|
||||
});
|
||||
|
||||
return deferred.promise;
|
||||
};
|
||||
|
||||
service.externalStacks = function() {
|
||||
var deferred = $q.defer();
|
||||
|
||||
ServiceService.services()
|
||||
.then(function success(data) {
|
||||
var services = data;
|
||||
var stackNames = StackHelper.getExternalStackNamesFromServices(services);
|
||||
var stacks = stackNames.map(function (item) {
|
||||
return new StackViewModel({ Name: item, External: true });
|
||||
});
|
||||
deferred.resolve(stacks);
|
||||
})
|
||||
.catch(function error(err) {
|
||||
deferred.reject({ msg: 'Unable to retrieve external stacks', err: err });
|
||||
});
|
||||
|
||||
return deferred.promise;
|
||||
};
|
||||
|
||||
service.stacks = function(includeExternalStacks) {
|
||||
var deferred = $q.defer();
|
||||
|
||||
SwarmService.swarm()
|
||||
.then(function success(data) {
|
||||
var swarm = data;
|
||||
|
||||
return $q.all({
|
||||
stacks: Stack.query({ swarmId: swarm.Id }).$promise,
|
||||
externalStacks: includeExternalStacks ? service.externalStacks() : []
|
||||
});
|
||||
})
|
||||
.then(function success(data) {
|
||||
var stacks = data.stacks.map(function (item) {
|
||||
item.External = false;
|
||||
return new StackViewModel(item);
|
||||
});
|
||||
var externalStacks = data.externalStacks;
|
||||
|
||||
var result = _.unionWith(stacks, externalStacks, function(a, b) { return a.Name === b.Name; });
|
||||
deferred.resolve(result);
|
||||
})
|
||||
.catch(function error(err) {
|
||||
deferred.reject({ msg: 'Unable to retrieve stacks', err: err });
|
||||
});
|
||||
|
||||
return deferred.promise;
|
||||
};
|
||||
|
||||
service.remove = function(stack) {
|
||||
var deferred = $q.defer();
|
||||
|
||||
Stack.remove({ id: stack.Id }).$promise
|
||||
.then(function success(data) {
|
||||
if (stack.ResourceControl && stack.ResourceControl.Id) {
|
||||
return ResourceControlService.deleteResourceControl(stack.ResourceControl.Id);
|
||||
}
|
||||
})
|
||||
.then(function success() {
|
||||
deferred.resolve();
|
||||
})
|
||||
.catch(function error(err) {
|
||||
deferred.reject({ msg: 'Unable to remove the stack', err: err });
|
||||
});
|
||||
|
||||
return deferred.promise;
|
||||
};
|
||||
|
||||
service.createStackFromFileContent = function(name, stackFileContent, env) {
|
||||
var deferred = $q.defer();
|
||||
|
||||
SwarmService.swarm()
|
||||
.then(function success(data) {
|
||||
var swarm = data;
|
||||
var payload = {
|
||||
Name: name,
|
||||
SwarmID: swarm.Id,
|
||||
StackFileContent: stackFileContent,
|
||||
Env: env
|
||||
};
|
||||
return Stack.create({ method: 'string' }, payload).$promise;
|
||||
})
|
||||
.then(function success(data) {
|
||||
deferred.resolve(data);
|
||||
})
|
||||
.catch(function error(err) {
|
||||
deferred.reject({ msg: 'Unable to create the stack', err: err });
|
||||
});
|
||||
|
||||
return deferred.promise;
|
||||
};
|
||||
|
||||
service.createStackFromGitRepository = function(name, gitRepository, pathInRepository, env) {
|
||||
var deferred = $q.defer();
|
||||
|
||||
SwarmService.swarm()
|
||||
.then(function success(data) {
|
||||
var swarm = data;
|
||||
var payload = {
|
||||
Name: name,
|
||||
SwarmID: swarm.Id,
|
||||
GitRepository: gitRepository,
|
||||
PathInRepository: pathInRepository,
|
||||
Env: env
|
||||
};
|
||||
return Stack.create({ method: 'repository' }, payload).$promise;
|
||||
})
|
||||
.then(function success(data) {
|
||||
deferred.resolve(data);
|
||||
})
|
||||
.catch(function error(err) {
|
||||
deferred.reject({ msg: 'Unable to create the stack', err: err });
|
||||
});
|
||||
|
||||
return deferred.promise;
|
||||
};
|
||||
|
||||
service.createStackFromFileUpload = function(name, stackFile, env) {
|
||||
var deferred = $q.defer();
|
||||
|
||||
SwarmService.swarm()
|
||||
.then(function success(data) {
|
||||
var swarm = data;
|
||||
return FileUploadService.createStack(name, swarm.Id, stackFile, env);
|
||||
})
|
||||
.then(function success(data) {
|
||||
deferred.resolve(data.data);
|
||||
})
|
||||
.catch(function error(err) {
|
||||
deferred.reject({ msg: 'Unable to create the stack', err: err });
|
||||
});
|
||||
|
||||
return deferred.promise;
|
||||
};
|
||||
|
||||
service.updateStack = function(id, stackFile, env, prune) {
|
||||
return Stack.update({ id: id, StackFileContent: stackFile, Env: env, Prune: prune}).$promise;
|
||||
};
|
||||
|
||||
return service;
|
||||
}]);
|
22
app/docker/services/swarmService.js
Normal file
22
app/docker/services/swarmService.js
Normal file
|
@ -0,0 +1,22 @@
|
|||
angular.module('portainer.docker')
|
||||
.factory('SwarmService', ['$q', 'Swarm', function SwarmServiceFactory($q, Swarm) {
|
||||
'use strict';
|
||||
var service = {};
|
||||
|
||||
service.swarm = function() {
|
||||
var deferred = $q.defer();
|
||||
|
||||
Swarm.get().$promise
|
||||
.then(function success(data) {
|
||||
var swarm = new SwarmViewModel(data);
|
||||
deferred.resolve(swarm);
|
||||
})
|
||||
.catch(function error(err) {
|
||||
deferred.reject({ msg: 'Unable to retrieve Swarm details', err: err });
|
||||
});
|
||||
|
||||
return deferred.promise;
|
||||
};
|
||||
|
||||
return service;
|
||||
}]);
|
49
app/docker/services/systemService.js
Normal file
49
app/docker/services/systemService.js
Normal file
|
@ -0,0 +1,49 @@
|
|||
angular.module('portainer.docker')
|
||||
.factory('SystemService', ['$q', 'System', function SystemServiceFactory($q, System) {
|
||||
'use strict';
|
||||
var service = {};
|
||||
|
||||
service.plugins = function() {
|
||||
var deferred = $q.defer();
|
||||
System.info({}).$promise
|
||||
.then(function success(data) {
|
||||
var plugins = data.Plugins;
|
||||
deferred.resolve(plugins);
|
||||
})
|
||||
.catch(function error(err) {
|
||||
deferred.reject({msg: 'Unable to retrieve plugins information from system', err: err});
|
||||
});
|
||||
return deferred.promise;
|
||||
};
|
||||
|
||||
service.info = function() {
|
||||
return System.info({}).$promise;
|
||||
};
|
||||
|
||||
service.version = function() {
|
||||
return System.version({}).$promise;
|
||||
};
|
||||
|
||||
service.events = function(from, to) {
|
||||
var deferred = $q.defer();
|
||||
|
||||
System.events({since: from, until: to}).$promise
|
||||
.then(function success(data) {
|
||||
var events = data.map(function (item) {
|
||||
return new EventViewModel(item);
|
||||
});
|
||||
deferred.resolve(events);
|
||||
})
|
||||
.catch(function error(err) {
|
||||
deferred.reject({ msg: 'Unable to retrieve engine events', err: err });
|
||||
});
|
||||
|
||||
return deferred.promise;
|
||||
};
|
||||
|
||||
service.dataUsage = function () {
|
||||
return System.dataUsage().$promise;
|
||||
};
|
||||
|
||||
return service;
|
||||
}]);
|
39
app/docker/services/taskService.js
Normal file
39
app/docker/services/taskService.js
Normal file
|
@ -0,0 +1,39 @@
|
|||
angular.module('portainer.docker')
|
||||
.factory('TaskService', ['$q', 'Task', function TaskServiceFactory($q, Task) {
|
||||
'use strict';
|
||||
var service = {};
|
||||
|
||||
service.task = function(id) {
|
||||
var deferred = $q.defer();
|
||||
|
||||
Task.get({ id: id }).$promise
|
||||
.then(function success(data) {
|
||||
var task = new TaskViewModel(data);
|
||||
deferred.resolve(task);
|
||||
})
|
||||
.catch(function error(err) {
|
||||
deferred.reject({ msg: 'Unable to retrieve task details', err: err });
|
||||
});
|
||||
|
||||
return deferred.promise;
|
||||
};
|
||||
|
||||
service.tasks = function(filters) {
|
||||
var deferred = $q.defer();
|
||||
|
||||
Task.query({ filters: filters ? filters : {} }).$promise
|
||||
.then(function success(data) {
|
||||
var tasks = data.map(function (item) {
|
||||
return new TaskViewModel(item);
|
||||
});
|
||||
deferred.resolve(tasks);
|
||||
})
|
||||
.catch(function error(err) {
|
||||
deferred.reject({ msg: 'Unable to retrieve tasks', err: err });
|
||||
});
|
||||
|
||||
return deferred.promise;
|
||||
};
|
||||
|
||||
return service;
|
||||
}]);
|
103
app/docker/services/volumeService.js
Normal file
103
app/docker/services/volumeService.js
Normal file
|
@ -0,0 +1,103 @@
|
|||
angular.module('portainer.docker')
|
||||
.factory('VolumeService', ['$q', 'Volume', 'VolumeHelper', 'ResourceControlService', 'UserService', 'TeamService', function VolumeServiceFactory($q, Volume, VolumeHelper, ResourceControlService, UserService, TeamService) {
|
||||
'use strict';
|
||||
var service = {};
|
||||
|
||||
service.volumes = function(params) {
|
||||
var deferred = $q.defer();
|
||||
Volume.query(params).$promise
|
||||
.then(function success(data) {
|
||||
var volumes = data.Volumes || [];
|
||||
volumes = volumes.map(function (item) {
|
||||
return new VolumeViewModel(item);
|
||||
});
|
||||
deferred.resolve(volumes);
|
||||
})
|
||||
.catch(function error(err) {
|
||||
deferred.reject({msg: 'Unable to retrieve volumes', err: err});
|
||||
});
|
||||
return deferred.promise;
|
||||
};
|
||||
|
||||
service.volume = function(id) {
|
||||
var deferred = $q.defer();
|
||||
Volume.get({id: id}).$promise
|
||||
.then(function success(data) {
|
||||
var volume = new VolumeViewModel(data);
|
||||
deferred.resolve(volume);
|
||||
})
|
||||
.catch(function error(err) {
|
||||
deferred.reject({msg: 'Unable to retrieve volume details', err: err});
|
||||
});
|
||||
return deferred.promise;
|
||||
};
|
||||
|
||||
service.getVolumes = function() {
|
||||
return Volume.query({}).$promise;
|
||||
};
|
||||
|
||||
service.remove = function(volume) {
|
||||
var deferred = $q.defer();
|
||||
|
||||
Volume.remove({id: volume.Id}).$promise
|
||||
.then(function success(data) {
|
||||
if (data.message) {
|
||||
deferred.reject({ msg: data.message, err: data.message });
|
||||
}
|
||||
if (volume.ResourceControl && volume.ResourceControl.Type === 3) {
|
||||
return ResourceControlService.deleteResourceControl(volume.ResourceControl.Id);
|
||||
}
|
||||
})
|
||||
.then(function success() {
|
||||
deferred.resolve();
|
||||
})
|
||||
.catch(function error(err) {
|
||||
deferred.reject({ msg: 'Unable to remove volume', err: err });
|
||||
});
|
||||
|
||||
return deferred.promise;
|
||||
};
|
||||
|
||||
service.createVolumeConfiguration = function(name, driver, driverOptions) {
|
||||
var volumeConfiguration = {
|
||||
Name: name,
|
||||
Driver: driver,
|
||||
DriverOpts: VolumeHelper.createDriverOptions(driverOptions)
|
||||
};
|
||||
return volumeConfiguration;
|
||||
};
|
||||
|
||||
service.createVolume = function(volumeConfiguration) {
|
||||
var deferred = $q.defer();
|
||||
Volume.create(volumeConfiguration).$promise
|
||||
.then(function success(data) {
|
||||
if (data.message) {
|
||||
deferred.reject({ msg: data.message });
|
||||
} else {
|
||||
var volume = new VolumeViewModel(data);
|
||||
deferred.resolve(volume);
|
||||
}
|
||||
})
|
||||
.catch(function error(err) {
|
||||
deferred.reject({ msg: 'Unable to create volume', err: err });
|
||||
});
|
||||
return deferred.promise;
|
||||
};
|
||||
|
||||
service.createVolumes = function(volumeConfigurations) {
|
||||
var createVolumeQueries = volumeConfigurations.map(function(volumeConfiguration) {
|
||||
return service.createVolume(volumeConfiguration);
|
||||
});
|
||||
return $q.all(createVolumeQueries);
|
||||
};
|
||||
|
||||
service.createXAutoGeneratedLocalVolumes = function (x) {
|
||||
var createVolumeQueries = [];
|
||||
for (var i = 0; i < x; i++) {
|
||||
createVolumeQueries.push(service.createVolume({ Driver: 'local' }));
|
||||
}
|
||||
return $q.all(createVolumeQueries);
|
||||
};
|
||||
|
||||
return service;
|
||||
}]);
|
20
app/docker/views/configs/configs.html
Normal file
20
app/docker/views/configs/configs.html
Normal file
|
@ -0,0 +1,20 @@
|
|||
<rd-header>
|
||||
<rd-header-title title="Configs list">
|
||||
<a data-toggle="tooltip" title="Refresh" ui-sref="docker.configs" ui-sref-opts="{reload: true}">
|
||||
<i class="fa fa-refresh" aria-hidden="true"></i>
|
||||
</a>
|
||||
</rd-header-title>
|
||||
<rd-header-content>Configs</rd-header-content>
|
||||
</rd-header>
|
||||
|
||||
<div class="row">
|
||||
<div class="col-sm-12">
|
||||
<configs-datatable
|
||||
title="Configs" title-icon="fa-file-code-o"
|
||||
dataset="configs" table-key="configs"
|
||||
order-by="Name" show-text-filter="true"
|
||||
show-ownership-column="applicationState.application.authentication"
|
||||
remove-action="removeAction"
|
||||
></configs-datatable>
|
||||
</div>
|
||||
</div>
|
38
app/docker/views/configs/configsController.js
Normal file
38
app/docker/views/configs/configsController.js
Normal file
|
@ -0,0 +1,38 @@
|
|||
angular.module('portainer.docker')
|
||||
.controller('ConfigsController', ['$scope', '$state', 'ConfigService', 'Notifications',
|
||||
function ($scope, $state, ConfigService, Notifications) {
|
||||
|
||||
$scope.removeAction = function (selectedItems) {
|
||||
var actionCount = selectedItems.length;
|
||||
angular.forEach(selectedItems, function (config) {
|
||||
ConfigService.remove(config.Id)
|
||||
.then(function success() {
|
||||
Notifications.success('Config successfully removed', config.Name);
|
||||
var index = $scope.configs.indexOf(config);
|
||||
$scope.configs.splice(index, 1);
|
||||
})
|
||||
.catch(function error(err) {
|
||||
Notifications.error('Failure', err, 'Unable to remove config');
|
||||
})
|
||||
.finally(function final() {
|
||||
--actionCount;
|
||||
if (actionCount === 0) {
|
||||
$state.reload();
|
||||
}
|
||||
});
|
||||
});
|
||||
};
|
||||
|
||||
function initView() {
|
||||
ConfigService.configs()
|
||||
.then(function success(data) {
|
||||
$scope.configs = data;
|
||||
})
|
||||
.catch(function error(err) {
|
||||
$scope.configs = [];
|
||||
Notifications.error('Failure', err, 'Unable to retrieve configs');
|
||||
});
|
||||
}
|
||||
|
||||
initView();
|
||||
}]);
|
Some files were not shown because too many files have changed in this diff Show more
Loading…
Add table
Add a link
Reference in a new issue