Merge branch 'release/1.15.4'

pull/1487/head 1.15.4
Anthony Lapenna 2017-12-10 10:09:56 +01:00
commit 39236ae84e
145 changed files with 9321 additions and 3290 deletions

View File

@ -26,6 +26,14 @@ You can try out the public demo instance: http://demo.portainer.io/ (login with
Please note that the public demo cluster is **reset every 15min**.
Alternatively, you can deploy a copy of the demo stack inside a [play-with-docker (PWD)](https://labs.play-with-docker.com) playground:
- Browse [PWD/?stack=portainer-demo/play-with-docker/docker-stack.yml](http://play-with-docker.com/?stack=https://raw.githubusercontent.com/portainer/portainer-demo/master/play-with-docker/docker-stack.yml)
- Sign in with your [Docker ID](https://docs.docker.com/docker-id)
- Follow [these](https://github.com/portainer/portainer-demo/blob/master/play-with-docker/docker-stack.yml#L5-L8) steps.
Unlike the public demo, the playground sessions are deleted after 4 hours. Apart from that, all the settings are same, including default credentials.
## Getting started
* [Deploy Portainer](https://portainer.readthedocs.io/en/latest/deployment.html)

View File

@ -0,0 +1,16 @@
package bolt
func (m *Migrator) updateSettingsToVersion7() error {
legacySettings, err := m.SettingsService.Settings()
if err != nil {
return err
}
legacySettings.DisplayDonationHeader = true
err = m.SettingsService.StoreSettings(legacySettings)
if err != nil {
return err
}
return nil
}

View File

@ -81,6 +81,14 @@ func (m *Migrator) Migrate() error {
}
}
// https://github.com/portainer/portainer/issues/1449
if m.CurrentDBVersion < 7 {
err := m.updateSettingsToVersion7()
if err != nil {
return err
}
}
err := m.VersionService.StoreDBVersion(portainer.DBVersion)
if err != nil {
return err

View File

@ -48,7 +48,7 @@ func (h *Handler) ServeHTTP(w http.ResponseWriter, r *http.Request) {
case strings.HasPrefix(r.URL.Path, "/api/dockerhub"):
http.StripPrefix("/api", h.DockerHubHandler).ServeHTTP(w, r)
case strings.HasPrefix(r.URL.Path, "/api/endpoints"):
if strings.Contains(r.URL.Path, "/docker") {
if strings.Contains(r.URL.Path, "/docker/") {
http.StripPrefix("/api/endpoints", h.DockerHandler).ServeHTTP(w, r)
} else if strings.Contains(r.URL.Path, "/stacks") {
http.StripPrefix("/api/endpoints", h.StackHandler).ServeHTTP(w, r)

View File

@ -390,9 +390,9 @@ type (
const (
// APIVersion is the version number of the Portainer API.
APIVersion = "1.15.3"
APIVersion = "1.15.4"
// DBVersion is the version number of the Portainer database.
DBVersion = 6
DBVersion = 7
// DefaultTemplatesURL represents the default URL for the templates definitions.
DefaultTemplatesURL = "https://raw.githubusercontent.com/portainer/templates/master/templates.json"
)

View File

@ -56,7 +56,7 @@ info:
**NOTE**: You can find more information on how to query the Docker API in the [Docker official documentation](https://docs.docker.com/engine/api/v1.30/) as well as in [this Portainer example](https://gist.github.com/deviantony/77026d402366b4b43fa5918d41bc42f8).
version: "1.15.3"
version: "1.15.4"
title: "Portainer API"
contact:
email: "info@portainer.io"
@ -1869,7 +1869,7 @@ definitions:
description: "Is analytics enabled"
Version:
type: "string"
example: "1.15.3"
example: "1.15.4"
description: "Portainer API version"
PublicSettingsInspectResponse:
type: "object"

View File

@ -9,6 +9,7 @@ angular.module('portainer', [
'LocalStorageModule',
'angular-jwt',
'angular-google-analytics',
'ui',
'angular-loading-bar',
'portainer.templates',
'portainer.filters',

View File

@ -8,73 +8,13 @@
</rd-header>
<div class="row">
<div class="col-lg-12 col-md-12 col-xs-12">
<rd-widget>
<rd-widget-header icon="fa-file-code-o" title="Configs">
</rd-widget-header>
<rd-widget-taskbar classes="col-lg-12 col-md-12 col-xs-12">
<div class="pull-left">
<button type="button" class="btn btn-danger" ng-click="removeAction()" ng-disabled="!state.selectedItemCount"><i class="fa fa-trash space-right" aria-hidden="true"></i>Remove</button>
<a class="btn btn-primary" type="button" ui-sref="actions.create.config"><i class="fa fa-plus space-right" aria-hidden="true"></i>Add config</a>
</div>
<div class="pull-right">
<input type="text" id="filter" ng-model="state.filter" placeholder="Filter..." class="form-control input-sm" />
</div>
</rd-widget-taskbar>
<rd-widget-body classes="no-padding">
<div class="table-responsive">
<table class="table table-hover">
<thead>
<th>
<input type="checkbox" ng-model="allSelected" ng-change="selectItems(allSelected)" />
</th>
<th>
<a ng-click="order('Name')">
Name
<span ng-show="sortType == 'Name' && !sortReverse" class="glyphicon glyphicon-chevron-down"></span>
<span ng-show="sortType == 'Name' && sortReverse" class="glyphicon glyphicon-chevron-up"></span>
</a>
</th>
<th>
<a ng-click="order('CreatedAt')">
Created at
<span ng-show="sortType == 'CreatedAt' && !sortReverse" class="glyphicon glyphicon-chevron-down"></span>
<span ng-show="sortType == 'CreatedAt' && sortReverse" class="glyphicon glyphicon-chevron-up"></span>
</a>
</th>
<th ng-if="applicationState.application.authentication">
<a ng-click="order('ResourceControl.Ownership')">
Ownership
<span ng-show="sortType == 'ResourceControl.Ownership' && !sortReverse" class="glyphicon glyphicon-chevron-down"></span>
<span ng-show="sortType == 'ResourceControl.Ownership' && sortReverse" class="glyphicon glyphicon-chevron-up"></span>
</a>
</th>
</thead>
<tbody>
<tr dir-paginate="config in (state.filteredConfigs = ( configs | filter:state.filter | orderBy:sortType:sortReverse | itemsPerPage: pagination_count))" ng-class="{active: config.Checked}">
<td><input type="checkbox" ng-model="config.Checked" ng-change="selectItem(config)"/></td>
<td><a ui-sref="config({id: config.Id})">{{ config.Name }}</a></td>
<td>{{ config.CreatedAt | getisodate }}</td>
<td ng-if="applicationState.application.authentication">
<span>
<i ng-class="config.ResourceControl.Ownership | ownershipicon" aria-hidden="true"></i>
{{ config.ResourceControl.Ownership ? config.ResourceControl.Ownership : config.ResourceControl.Ownership = 'public' }}
</span>
</td>
</tr>
<tr ng-if="!configs">
<td colspan="4" class="text-center text-muted">Loading...</td>
</tr>
<tr ng-if="configs.length == 0">
<td colspan="4" class="text-center text-muted">No configs available.</td>
</tr>
</tbody>
</table>
<div ng-if="configs" class="pull-left pagination-controls">
<dir-pagination-controls></dir-pagination-controls>
</div>
</div>
</rd-widget-body>
<rd-widget>
<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>

View File

@ -1,47 +1,25 @@
angular.module('configs', [])
.controller('ConfigsController', ['$scope', '$stateParams', '$state', 'ConfigService', 'Notifications', 'Pagination',
function ($scope, $stateParams, $state, ConfigService, Notifications, Pagination) {
$scope.state = {};
$scope.state.selectedItemCount = 0;
$scope.state.pagination_count = Pagination.getPaginationCount('configs');
$scope.sortType = 'Name';
$scope.sortReverse = false;
$scope.order = function (sortType) {
$scope.sortReverse = ($scope.sortType === sortType) ? !$scope.sortReverse : false;
$scope.sortType = sortType;
};
$scope.selectItems = function (allSelected) {
angular.forEach($scope.state.filteredConfigs, function (config) {
if (config.Checked !== allSelected) {
config.Checked = allSelected;
$scope.selectItem(config);
}
});
};
$scope.selectItem = function (item) {
if (item.Checked) {
$scope.state.selectedItemCount++;
} else {
$scope.state.selectedItemCount--;
}
};
$scope.removeAction = function () {
angular.forEach($scope.configs, function (config) {
if (config.Checked) {
ConfigService.remove(config.Id)
.then(function success() {
Notifications.success('Config deleted', config.Id);
var index = $scope.configs.indexOf(config);
$scope.configs.splice(index, 1);
})
.catch(function error(err) {
Notifications.error('Failure', err, 'Unable to remove config');
});
}
.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();
}
});
});
};

View File

@ -12,17 +12,20 @@
<rd-widget-header icon="fa-cogs" title="Actions"></rd-widget-header>
<rd-widget-body classes="padding">
<div class="btn-group" role="group" aria-label="...">
<button class="btn btn-success btn-responsive" ng-click="start()" ng-disabled="container.State.Running"><i class="fa fa-play space-right" aria-hidden="true"></i>Start</button>
<button class="btn btn-danger btn-responsive" ng-click="stop()" ng-disabled="!container.State.Running"><i class="fa fa-stop space-right" aria-hidden="true"></i>Stop</button>
<button class="btn btn-danger btn-responsive" ng-click="kill()" ng-disabled="!container.State.Running"><i class="fa fa-bomb space-right" aria-hidden="true"></i>Kill</button>
<button class="btn btn-primary btn-responsive" ng-click="restart()" ng-disabled="!container.State.Running"><i class="fa fa-refresh space-right" aria-hidden="true"></i>Restart</button>
<button class="btn btn-primary btn-responsive" ng-click="pause()" ng-disabled="!container.State.Running || container.State.Paused"><i class="fa fa-pause space-right" aria-hidden="true"></i>Pause</button>
<button class="btn btn-primary btn-responsive" ng-click="unpause()" ng-disabled="!container.State.Paused"><i class="fa fa-play space-right" aria-hidden="true"></i>Resume</button>
<button class="btn btn-danger btn-responsive" ng-click="confirmRemove()"><i class="fa fa-trash space-right" aria-hidden="true"></i>Remove</button>
<button class="btn btn-success btn-sm" ng-click="start()" ng-disabled="container.State.Running"><i class="fa fa-play space-right" aria-hidden="true"></i>Start</button>
<button class="btn btn-danger btn-sm" ng-click="stop()" ng-disabled="!container.State.Running"><i class="fa fa-stop space-right" aria-hidden="true"></i>Stop</button>
<button class="btn btn-danger btn-sm" ng-click="kill()" ng-disabled="!container.State.Running"><i class="fa fa-bomb space-right" aria-hidden="true"></i>Kill</button>
<button class="btn btn-primary btn-sm" ng-click="restart()" ng-disabled="!container.State.Running"><i class="fa fa-refresh space-right" aria-hidden="true"></i>Restart</button>
<button class="btn btn-primary btn-sm" ng-click="pause()" ng-disabled="!container.State.Running || container.State.Paused"><i class="fa fa-pause space-right" aria-hidden="true"></i>Pause</button>
<button class="btn btn-primary btn-sm" ng-click="unpause()" ng-disabled="!container.State.Paused"><i class="fa fa-play space-right" aria-hidden="true"></i>Resume</button>
<button class="btn btn-danger btn-sm" ng-click="confirmRemove()"><i class="fa fa-trash space-right" aria-hidden="true"></i>Remove</button>
</div>
<div class="btn-group" role="group" aria-label="...">
<button class="btn btn-danger btn-responsive" ng-click="recreate()" ng-if="!container.Config.Labels['com.docker.swarm.service.id']"><i class="fa fa-refresh space-right" aria-hidden="true"></i>Recreate</button>
<button class="btn btn-primary btn-responsive" ng-click="duplicate()" ng-if="!container.Config.Labels['com.docker.swarm.service.id']"><i class="fa fa-files-o space-right" aria-hidden="true"></i>Duplicate/Edit</button>
<button type="button" class="btn btn-danger btn-sm" ng-disabled="state.recreateContainerInProgress" ng-click="recreate()" button-spinner="state.recreateContainerInProgress" ng-if="!container.Config.Labels['com.docker.swarm.service.id']">
<span ng-hide="state.recreateContainerInProgress"><i class="fa fa-refresh space-right" aria-hidden="true"></i>Recreate</span>
<span ng-show="state.recreateContainerInProgress">Recreation in progress...</span>
</button>
<button class="btn btn-primary btn-sm" ng-click="duplicate()" ng-if="!container.Config.Labels['com.docker.swarm.service.id']"><i class="fa fa-files-o space-right" aria-hidden="true"></i>Duplicate/Edit</button>
</div>
</rd-widget-body>
</rd-widget>
@ -81,10 +84,10 @@
<tr>
<td colspan="2">
<div class="btn-group" role="group" aria-label="...">
<a class="btn btn-outline-secondary" type="button" ui-sref="stats({id: container.Id})"><i class="fa fa-area-chart space-right" aria-hidden="true"></i>Stats</a>
<a class="btn btn-outline-secondary" type="button" ui-sref="containerlogs({id: container.Id})"><i class="fa fa-exclamation-circle space-right" aria-hidden="true"></i>Logs</a>
<a class="btn btn-outline-secondary" type="button" ui-sref="console({id: container.Id})"><i class="fa fa-terminal space-right" aria-hidden="true"></i>Console</a>
<a class="btn btn-outline-secondary" type="button" ui-sref="inspect({id: container.Id})"><i class="fa fa-info-circle space-right" aria-hidden="true"></i>Inspect</a>
<a class="btn" type="button" ui-sref="stats({id: container.Id})"><i class="fa fa-area-chart space-right" aria-hidden="true"></i>Stats</a>
<a class="btn" type="button" ui-sref="containerlogs({id: container.Id})"><i class="fa fa-exclamation-circle space-right" aria-hidden="true"></i>Logs</a>
<a class="btn" type="button" ui-sref="console({id: container.Id})"><i class="fa fa-terminal space-right" aria-hidden="true"></i>Console</a>
<a class="btn" type="button" ui-sref="inspect({id: container.Id})"><i class="fa fa-info-circle space-right" aria-hidden="true"></i>Inspect</a>
</div>
</td>
</tr>
@ -267,70 +270,16 @@
</div>
<div class="row">
<div class="col-lg-12 col-md-12 col-xs-12">
<rd-widget>
<rd-widget-header icon="fa-sitemap" title="Connected networks">
<div class="pull-right">
Items per page:
<select ng-model="state.pagination_count" ng-change="changePaginationCount()">
<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>
</div>
</rd-widget-header>
<rd-widget-body classes="no-padding">
<table class="table">
<thead>
<th>Network Name</th>
<th>IP Address</th>
<th>Gateway</th>
<th>MacAddress</th>
<th>Actions</th>
</thead>
<tbody>
<tr dir-paginate="(key, value) in container.NetworkSettings.Networks | itemsPerPage: state.pagination_count">
<td><a ui-sref="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="state.leaveNetworkInProgress" button-spinner="state.leaveNetworkInProgress" ng-click="containerLeaveNetwork(container, value.NetworkID)">
<span ng-hide="state.leaveNetworkInProgress"><i class="fa fa-trash space-right" aria-hidden="true"></i> Leave network</span>
<span ng-show="state.leaveNetworkInProgress">Leaving network...</span>
</button>
</td>
</tr>
<tr ng-if="(container.NetworkSettings.Networks | emptyobject)">
<td colspan="5" class="text-center text-muted">No networks connected.</td>
</tr>
</tbody>
</table>
<div class="pagination-controls">
<dir-pagination-controls></dir-pagination-controls>
</div>
<hr />
<form class="form-horizontal">
<!-- network-input -->
<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="selectedNetwork" id="container_network">
<option selected disabled hidden value="">Select a network</option>
<option ng-repeat="net in 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="state.joinNetworkInProgress || !selectedNetwork" ng-click="containerJoinNetwork(container, selectedNetwork)" button-spinner="state.joinNetworkInProgress">
<span ng-hide="state.joinNetworkInProgress">Join network</span>
<span ng-show="state.joinNetworkInProgress">Joining network...</span>
</button>
</div>
</div>
</form>
</rd-widget-body>
</rd-widget>
<div class="col-sm-12">
<container-networks-datatable
title="Connected networks" title-icon="fa-sitemap"
dataset="container.NetworkSettings.Networks" table-key="container-networks"
container="container"
available-networks="availableNetworks"
join-network-action="containerJoinNetwork"
join-network-action-in-progress="state.joinNetworkInProgress"
leave-network-action="containerLeaveNetwork"
leave-network-action-in-progress="state.leaveNetworkInProgress"
></container-networks-datatable>
</div>
</div>

View File

@ -1,20 +1,18 @@
angular.module('container', [])
.controller('ContainerController', ['$q', '$scope', '$state','$transition$', '$filter', 'Container', 'ContainerCommit', 'ContainerHelper', 'ContainerService', 'ImageHelper', 'Network', 'NetworkService', 'Notifications', 'Pagination', 'ModalService', 'ResourceControlService', 'RegistryService', 'ImageService',
function ($q, $scope, $state, $transition$, $filter, Container, ContainerCommit, ContainerHelper, ContainerService, ImageHelper, Network, NetworkService, Notifications, Pagination, ModalService, ResourceControlService, RegistryService, ImageService) {
.controller('ContainerController', ['$q', '$scope', '$state','$transition$', '$filter', 'Container', 'ContainerCommit', 'ContainerHelper', 'ContainerService', 'ImageHelper', 'Network', 'NetworkService', 'Notifications', 'ModalService', 'ResourceControlService', 'RegistryService', 'ImageService',
function ($q, $scope, $state, $transition$, $filter, Container, ContainerCommit, ContainerHelper, ContainerService, ImageHelper, Network, NetworkService, Notifications, ModalService, ResourceControlService, RegistryService, ImageService) {
$scope.activityTime = 0;
$scope.portBindings = [];
$scope.config = {
Image: '',
Registry: ''
};
$scope.state = {
joinNetworkInProgress: false,
leaveNetworkInProgress: false,
pagination_count: Pagination.getPaginationCount('container_networks')
};
$scope.changePaginationCount = function() {
Pagination.setPaginationCount('container_networks', $scope.state.pagination_count);
$scope.state = {
recreateContainerInProgress: false,
joinNetworkInProgress: false,
leaveNetworkInProgress: false
};
var update = function () {
@ -207,6 +205,7 @@ function ($q, $scope, $state, $transition$, $filter, Container, ContainerCommit,
function recreateContainer(pullImage) {
var container = $scope.container;
var config = ContainerHelper.configFromContainer(container.Model);
$scope.state.recreateContainerInProgress = true;
ContainerService.remove(container, true)
.then(function success() {
return RegistryService.retrieveRegistryFromRepository(container.Config.Image);
@ -239,6 +238,7 @@ function ($q, $scope, $state, $transition$, $filter, Container, ContainerCommit,
})
.catch(function error(err) {
Notifications.error('Failure', err, 'Unable to re-create container');
$scope.state.recreateContainerInProgress = false;
});
}

View File

@ -51,7 +51,6 @@
<div class="row">
<div ng-class="{true: 'col-md-6 col-sm-12', false: 'col-lg-4 col-md-6 col-sm-12'}[state.networkStatsUnavailable]">
<!-- <div class="col-lg-4 col-md-6 col-sm-12"> -->
<rd-widget>
<rd-widget-header icon="fa-area-chart" title="Memory usage"></rd-widget-header>
<rd-widget-body>
@ -81,49 +80,13 @@
</rd-widget-body>
</rd-widget>
</div>
<div class="col-sm-12" ng-if="applicationState.endpoint.mode.provider !== 'VMWARE_VIC'">
<rd-widget>
<rd-widget-header icon="fa-tasks" title="Processes">
<div class="pull-right">
Items per page:
<select ng-model="state.pagination_count" ng-change="changePaginationCount()">
<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>
</div>
</rd-widget-header>
<rd-widget-body classes="no-padding">
<table class="table table-striped">
<thead>
<tr>
<th ng-repeat="title in processInfo.Titles">
<a ng-click="order(title)">
{{ title }}
<span ng-show="sortType == title && !sortReverse" class="glyphicon glyphicon-chevron-down"></span>
<span ng-show="sortType == title && sortReverse" class="glyphicon glyphicon-chevron-up"></span>
</a>
</th>
</tr>
</thead>
<tbody>
<tr dir-paginate="processDetails in state.filteredProcesses = (processInfo.Processes | orderBy:sortType:sortReverse | itemsPerPage: state.pagination_count)">
<td ng-repeat="procInfo in processDetails track by $index">{{ procInfo }}</td>
</tr>
<tr ng-if="!processInfo.Processes">
<td colspan="processInfo.Titles.length" class="text-center text-muted">Loading...</td>
</tr>
<tr ng-if="state.filteredProcesses.length === 0">
<td colspan="processInfo.Titles.length" class="text-center text-muted">No processes available.</td>
</tr>
</tbody>
</table>
<div ng-if="processInfo.Processes" class="pagination-controls">
<dir-pagination-controls></dir-pagination-controls>
</div>
</rd-widget-body>
</rd-widget>
<container-processes-datatable
title="Processes" title-icon="fa-tasks"
dataset="processInfo.Processes" headerset="processInfo.Titles"
table-key="container-processes"
show-text-filter="true"
></container-processes>
</div>
</div>

View File

@ -1,25 +1,12 @@
angular.module('containerStats', [])
.controller('ContainerStatsController', ['$q', '$scope', '$transition$', '$document', '$interval', 'ContainerService', 'ChartService', 'Notifications', 'Pagination',
function ($q, $scope, $transition$, $document, $interval, ContainerService, ChartService, Notifications, Pagination) {
.controller('ContainerStatsController', ['$q', '$scope', '$transition$', '$document', '$interval', 'ContainerService', 'ChartService', 'Notifications',
function ($q, $scope, $transition$, $document, $interval, ContainerService, ChartService, Notifications) {
$scope.state = {
refreshRate: '5',
networkStatsUnavailable: false
};
$scope.state.pagination_count = Pagination.getPaginationCount('stats_processes');
$scope.sortType = 'CMD';
$scope.sortReverse = false;
$scope.order = function (sortType) {
$scope.sortReverse = ($scope.sortType === sortType) ? !$scope.sortReverse : false;
$scope.sortType = sortType;
};
$scope.changePaginationCount = function() {
Pagination.setPaginationCount('stats_processes', $scope.state.pagination_count);
};
$scope.$on('$destroy', function() {
stopRepeater();
});

View File

@ -7,145 +7,23 @@
<rd-header-content>Containers</rd-header-content>
</rd-header>
<div class="col-lg-12">
<rd-widget>
<rd-widget-header icon="fa-server" title="Containers">
<div class="pull-right">
Items per page:
<select ng-model="state.pagination_count" ng-change="changePaginationCount()">
<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>
</div>
</rd-widget-header>
<rd-widget-taskbar classes="col-lg-12">
<div class="pull-left">
<div class="btn-group" role="group" aria-label="...">
<button type="button" class="btn btn-success btn-responsive" ng-click="startAction()" ng-disabled="!state.selectedItemCount || state.noStoppedItemsSelected"><i class="fa fa-play space-right" aria-hidden="true"></i>Start</button>
<button type="button" class="btn btn-danger btn-responsive" ng-click="stopAction()" ng-disabled="!state.selectedItemCount || state.noRunningItemsSelected"><i class="fa fa-stop space-right" aria-hidden="true"></i>Stop</button>
<button type="button" class="btn btn-danger btn-responsive" ng-click="killAction()" ng-disabled="!state.selectedItemCount"><i class="fa fa-bomb space-right" aria-hidden="true"></i>Kill</button>
<button type="button" class="btn btn-primary btn-responsive" ng-click="restartAction()" ng-disabled="!state.selectedItemCount"><i class="fa fa-refresh space-right" aria-hidden="true"></i>Restart</button>
<button type="button" class="btn btn-primary btn-responsive" ng-click="pauseAction()" ng-disabled="!state.selectedItemCount || state.noRunningItemsSelected"><i class="fa fa-pause space-right" aria-hidden="true"></i>Pause</button>
<button type="button" class="btn btn-primary btn-responsive" ng-click="unpauseAction()" ng-disabled="!state.selectedItemCount || state.noPausedItemsSelected"><i class="fa fa-play space-right" aria-hidden="true"></i>Resume</button>
<button type="button" class="btn btn-danger btn-responsive" ng-click="confirmRemoveAction()" ng-disabled="!state.selectedItemCount"><i class="fa fa-trash space-right" aria-hidden="true"></i>Remove</button>
</div>
<a class="btn btn-primary btn-responsive" type="button" ui-sref="actions.create.container"><i class="fa fa-plus space-right" aria-hidden="true"></i>Add container</a>
</div>
<div class="pull-right">
<input type="checkbox" ng-model="state.displayAll" id="displayAll" ng-change="toggleGetAll()" style="margin-top: -2px; margin-right: 5px;"/><label for="displayAll">Show all containers</label>
<input type="text" id="filter" ng-model="state.filter" placeholder="Filter..." class="form-control input-sm" />
</div>
</rd-widget-taskbar>
<rd-widget-body classes="no-padding">
<div class="table-responsive">
<table class="table table-hover">
<thead>
<tr>
<th>
<input type="checkbox" ng-model="allSelected" ng-change="selectItems(allSelected)" />
</th>
<th>
<a ng-click="order('Status')">
State
<span ng-show="sortType == 'Status' && !sortReverse" class="glyphicon glyphicon-chevron-down"></span>
<span ng-show="sortType == 'Status' && sortReverse" class="glyphicon glyphicon-chevron-up"></span>
</a>
</th>
<th>
<a ng-click="order('Names')">
Name
<span ng-show="sortType == 'Names' && !sortReverse" class="glyphicon glyphicon-chevron-down"></span>
<span ng-show="sortType == 'Names' && sortReverse" class="glyphicon glyphicon-chevron-up"></span>
</a>
<a data-toggle="tooltip" title="More" ng-click="truncateMore();" ng-show="showMore">
<i class="fa fa-plus-square" aria-hidden="true"></i>
</a>
</th>
<th>
<a ng-click="order('StackName')">
Stack
<span ng-show="sortType == 'StackName' && !sortReverse" class="glyphicon glyphicon-chevron-down"></span>
<span ng-show="sortType == 'StackName' && sortReverse" class="glyphicon glyphicon-chevron-up"></span>
</a>
</th>
<th>
<a ng-click="order('Image')">
Image
<span ng-show="sortType == 'Image' && !sortReverse" class="glyphicon glyphicon-chevron-down"></span>
<span ng-show="sortType == 'Image' && sortReverse" class="glyphicon glyphicon-chevron-up"></span>
</a>
</th>
<th ng-if="state.displayIP">
<a ng-click="order('IP')">
IP Address
<span ng-show="sortType == 'IP' && !sortReverse" class="glyphicon glyphicon-chevron-down"></span>
<span ng-show="sortType == 'IP' && sortReverse" class="glyphicon glyphicon-chevron-up"></span>
</a>
</th>
<th ng-if="applicationState.endpoint.mode.provider === 'DOCKER_SWARM'">
<a ng-click="order('Host')">
Host IP
<span ng-show="sortType == 'Host' && !sortReverse" class="glyphicon glyphicon-chevron-down"></span>
<span ng-show="sortType == 'Host' && sortReverse" class="glyphicon glyphicon-chevron-up"></span>
</a>
</th>
<th>
<a ng-click="order('Ports')">
Published Ports
<span ng-show="sortType == 'Ports' && !sortReverse" class="glyphicon glyphicon-chevron-down"></span>
<span ng-show="sortType == 'Ports' && sortReverse" class="glyphicon glyphicon-chevron-up"></span>
</a>
</th>
<th ng-if="applicationState.application.authentication">
<a ng-click="order('ResourceControl.Ownership')">
Ownership
<span ng-show="sortType == 'ResourceControl.Ownership' && !sortReverse" class="glyphicon glyphicon-chevron-down"></span>
<span ng-show="sortType == 'ResourceControl.Ownership' && sortReverse" class="glyphicon glyphicon-chevron-up"></span>
</a>
</th>
</tr>
</thead>
<tbody>
<tr dir-paginate="container in (state.filteredContainers = ( containers | filter:state.filter | orderBy:sortType:sortReverse | itemsPerPage: state.pagination_count))" ng-class="{active: container.Checked}">
<td><input type="checkbox" ng-model="container.Checked" ng-change="selectItem(container)"/></td>
<td>
<span ng-if="['starting','healthy','unhealthy'].indexOf(container.Status) !== -1" class="label label-{{ container.Status|containerstatusbadge }} interactive" uib-tooltip="This container has a health check">{{ container.Status }}</span>
<span ng-if="['starting','healthy','unhealthy'].indexOf(container.Status) === -1" class="label label-{{ container.Status|containerstatusbadge }}">{{ container.Status }}</span>
</td>
<td ng-if="applicationState.endpoint.mode.provider === 'DOCKER_SWARM'"><a ui-sref="container({id: container.Id})">{{ container|swarmcontainername|truncate: truncate_size}}</a></td>
<td ng-if="applicationState.endpoint.mode.provider !== 'DOCKER_SWARM'"><a ui-sref="container({id: container.Id})">{{ container|containername|truncate: truncate_size}}</a></td>
<td>{{ container.StackName ? container.StackName : '-' }}</td>
<td><a ui-sref="image({id: container.Image})">{{ container.Image | hideshasum }}</a></td>
<td ng-if="state.displayIP">{{ container.IP ? container.IP : '-' }}</td>
<td ng-if="applicationState.endpoint.mode.provider === 'DOCKER_SWARM'">{{ container.hostIP }}</td>
<td>
<a ng-if="container.Ports.length > 0" ng-repeat="p in container.Ports" class="image-tag" ng-href="http://{{ 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="container.Ports.length == 0" >-</span>
</td>
<td ng-if="applicationState.application.authentication">
<span>
<i ng-class="container.ResourceControl.Ownership | ownershipicon" aria-hidden="true"></i>
{{ container.ResourceControl.Ownership ? container.ResourceControl.Ownership : container.ResourceControl.Ownership = 'public' }}
</span>
</td>
</tr>
<tr ng-if="!containers">
<td colspan="9" class="text-center text-muted">Loading...</td>
</tr>
<tr ng-if="containers.length == 0">
<td colspan="9" class="text-center text-muted">No containers available.</td>
</tr>
</tbody>
</table>
<div ng-if="containers" class="pull-left pagination-controls">
<dir-pagination-controls></dir-pagination-controls>
</div>
</div>
</rd-widget-body>
</rd-widget>
<div class="row">
<div class="col-sm-12" ng-if="containers">
<containers-datatable
title="Containers" title-icon="fa-server"
dataset="containers" table-key="containers"
order-by="Status" show-text-filter="true"
show-ownership-column="applicationState.application.authentication"
swarm-containers="applicationState.endpoint.mode.provider === 'DOCKER_SWARM'"
public-url="state.publicURL"
container-name-truncate-size="truncate_size"
start-action="startAction"
stop-action="stopAction"
restart-action="restartAction"
kill-action="killAction"
pause-action="pauseAction"
resume-action="resumeAction"
remove-action="confirmRemoveAction"
></containers-datatable>
</div>
</div>

View File

@ -1,219 +1,108 @@
angular.module('containers', [])
.controller('ContainersController', ['$q', '$scope', '$state', '$filter', 'Container', 'ContainerService', 'ContainerHelper', 'SystemService', 'Notifications', 'Pagination', 'EntityListService', 'ModalService', 'ResourceControlService', 'EndpointProvider', 'LocalStorage',
function ($q, $scope, $state, $filter, Container, ContainerService, ContainerHelper, SystemService, Notifications, Pagination, EntityListService, ModalService, ResourceControlService, EndpointProvider, LocalStorage) {
$scope.state = {};
$scope.state.pagination_count = Pagination.getPaginationCount('containers');
$scope.state.displayAll = LocalStorage.getFilterContainerShowAll();
$scope.state.displayIP = false;
$scope.sortType = 'State';
$scope.sortReverse = false;
$scope.state.selectedItemCount = 0;
$scope.truncate_size = 40;
$scope.showMore = true;
$scope.order = function (sortType) {
$scope.sortReverse = ($scope.sortType === sortType) ? !$scope.sortReverse : false;
$scope.sortType = sortType;
};
$scope.PublicURL = EndpointProvider.endpointPublicURL();
$scope.changePaginationCount = function() {
Pagination.setPaginationCount('containers', $scope.state.pagination_count);
.controller('ContainersController', ['$q', '$scope', '$state', '$filter', '$transition$', 'ContainerService', 'SystemService', 'Notifications', 'ModalService', 'EndpointProvider',
function ($q, $scope, $state, $filter, $transition$, ContainerService, SystemService, Notifications, ModalService, EndpointProvider) {
$scope.state = {
publicURL: EndpointProvider.endpointPublicURL()
};
$scope.cleanAssociatedVolumes = false;
var update = function (data) {
$scope.state.selectedItemCount = 0;
Container.query(data, function (d) {
var containers = d;
$scope.containers = containers.map(function (container) {
var model = new ContainerViewModel(container);
model.Status = $filter('containerstatus')(model.Status);
EntityListService.rememberPreviousSelection($scope.containers, model, function onSelect(model){
$scope.selectItem(model);
});
if (model.IP) {
$scope.state.displayIP = true;
}
if ($scope.applicationState.endpoint.mode.provider === 'DOCKER_SWARM') {
model.hostIP = $scope.swarm_hosts[_.split(container.Names[0], '/')[1]];
}
return model;
});
updateSelectionFlags();
}, function (e) {
Notifications.error('Failure', e, 'Unable to retrieve containers');
$scope.containers = [];
});
$scope.startAction = function(selectedItems) {
var successMessage = 'Container successfully started';
var errorMessage = 'Unable to start container';
executeActionOnContainerList(selectedItems, ContainerService.startContainer, successMessage, errorMessage);
};
var batch = function (items, action, msg) {
var counter = 0;
var complete = function () {
counter = counter - 1;
if (counter === 0) {
update({all: $scope.state.displayAll ? 1 : 0});
}
};
angular.forEach(items, function (c) {
if (c.Checked) {
counter = counter + 1;
if (action === Container.start) {
action({id: c.Id}, {}, function (d) {
Notifications.success('Container ' + msg, c.Id);
complete();
}, function (e) {
Notifications.error('Failure', e, 'Unable to start container');
complete();
});
}
else if (action === Container.remove) {
ContainerService.remove(c, $scope.cleanAssociatedVolumes)
.then(function success() {
var index = items.indexOf(c);
items.splice(index, 1);
Notifications.success('Container successfully removed');
complete();
})
.catch(function error(err) {
Notifications.error('Failure', err, 'Unable to remove container');
complete();
});
}
else if (action === Container.pause) {
action({id: c.Id}, function (d) {
if (d.message) {
Notifications.success('Container is already paused', c.Id);
} else {
Notifications.success('Container ' + msg, c.Id);
}
complete();
}, function (e) {
Notifications.error('Failure', e, 'Unable to pause container');
complete();
});
}
else {
action({id: c.Id}, function (d) {
Notifications.success('Container ' + msg, c.Id);
complete();
}, function (e) {
Notifications.error('Failure', e, 'An error occured');
complete();
});
}
}
});
$scope.stopAction = function(selectedItems) {
var successMessage = 'Container successfully stopped';
var errorMessage = 'Unable to stop container';
executeActionOnContainerList(selectedItems, ContainerService.stopContainer, successMessage, errorMessage);
};
$scope.selectItems = function (allSelected) {
angular.forEach($scope.state.filteredContainers, function (container) {
if (container.Checked !== allSelected) {
container.Checked = allSelected;
toggleItemSelection(container);
}
});
updateSelectionFlags();
$scope.restartAction = function(selectedItems) {
var successMessage = 'Container successfully restarted';
var errorMessage = 'Unable to restart container';
executeActionOnContainerList(selectedItems, ContainerService.restartContainer, successMessage, errorMessage);
};
$scope.selectItem = function (item) {
toggleItemSelection(item);
updateSelectionFlags();
$scope.killAction = function(selectedItems) {
var successMessage = 'Container successfully killed';
var errorMessage = 'Unable to kill container';
executeActionOnContainerList(selectedItems, ContainerService.killContainer, successMessage, errorMessage);
};
$scope.toggleGetAll = function () {
LocalStorage.storeFilterContainerShowAll($scope.state.displayAll);
update({all: $scope.state.displayAll ? 1 : 0});
$scope.pauseAction = function(selectedItems) {
var successMessage = 'Container successfully paused';
var errorMessage = 'Unable to pause container';
executeActionOnContainerList(selectedItems, ContainerService.pauseContainer, successMessage, errorMessage);
};
$scope.startAction = function () {
batch($scope.containers, Container.start, 'Started');
$scope.resumeAction = function(selectedItems) {
var successMessage = 'Container successfully resumed';
var errorMessage = 'Unable to resume container';
executeActionOnContainerList(selectedItems, ContainerService.resumeContainer, successMessage, errorMessage);
};
$scope.stopAction = function () {
batch($scope.containers, Container.stop, 'Stopped');
};
$scope.restartAction = function () {
batch($scope.containers, Container.restart, 'Restarted');
};
$scope.killAction = function () {
batch($scope.containers, Container.kill, 'Killed');
};
$scope.pauseAction = function () {
batch($scope.containers, Container.pause, 'Paused');
};
$scope.unpauseAction = function () {
batch($scope.containers, Container.unpause, 'Unpaused');
};
$scope.removeAction = function () {
batch($scope.containers, Container.remove, 'Removed');
};
$scope.truncateMore = function(size) {
$scope.truncate_size = 80;
$scope.showMore = false;
};
$scope.confirmRemoveAction = function () {
$scope.confirmRemoveAction = function(selectedItems) {
var isOneContainerRunning = false;
angular.forEach($scope.containers, function (c) {
if (c.Checked && c.State === 'running') {
for (var i = 0; i < selectedItems.length; i++) {
var container = selectedItems[i];
if (container.State === 'running') {
isOneContainerRunning = true;
return;
break;
}
});
}
var title = 'You are about to remove one or more container.';
if (isOneContainerRunning) {
title = 'You are about to remove one or more running containers.';
title = 'You are about to remove one or more running container.';
}
ModalService.confirmContainerDeletion(
title,
function (result) {
ModalService.confirmContainerDeletion(title, function (result) {
if(!result) { return; }
$scope.cleanAssociatedVolumes = false;
var cleanVolumes = false;
if (result[0]) {
$scope.cleanAssociatedVolumes = true;
cleanVolumes = true;
}
$scope.removeAction();
removeAction(selectedItems, cleanVolumes);
}
);
};
function toggleItemSelection(item) {
if (item.Checked) {
$scope.state.selectedItemCount++;
} else {
$scope.state.selectedItemCount--;
}
function executeActionOnContainerList(containers, action, successMessage, errorMessage) {
var actionCount = containers.length;
angular.forEach(containers, function (container) {
action(container.Id)
.then(function success() {
Notifications.success(successMessage, container.Names[0]);
})
.catch(function error(err) {
Notifications.error('Failure', err, errorMessage);
})
.finally(function final() {
--actionCount;
if (actionCount === 0) {
$state.transitionTo($state.current, { selectedContainers: containers }, { reload: true });
}
});
});
}
function updateSelectionFlags() {
$scope.state.noStoppedItemsSelected = true;
$scope.state.noRunningItemsSelected = true;
$scope.state.noPausedItemsSelected = true;
$scope.containers.forEach(function(container) {
if(!container.Checked) {
return;
}
if(container.Status === 'paused') {
$scope.state.noPausedItemsSelected = false;
} else if(container.Status === 'stopped' ||
container.Status === 'created') {
$scope.state.noStoppedItemsSelected = false;
} else if(container.Status === 'running') {
$scope.state.noRunningItemsSelected = false;
}
} );
function removeAction(containers, cleanVolumes) {
var actionCount = containers.length;
angular.forEach(containers, function (container) {
ContainerService.remove(container, cleanVolumes)
.then(function success() {
Notifications.success('Container successfully removed', container.Names[0]);
})
.catch(function error(err) {
Notifications.error('Failure', err, 'Unable to remove container');
})
.finally(function final() {
--actionCount;
if (actionCount === 0) {
$state.reload();
}
});
});
}
function retrieveSwarmHostsInfo(data) {
@ -231,17 +120,42 @@ angular.module('containers', [])
return swarm_hosts;
}
function assignContainers(containers, provider) {
var previouslySelectedContainers = $transition$.params().selectedContainers || [];
$scope.containers = containers.map(function (container) {
container.Status = $filter('containerstatus')(container.Status);
if (provider === 'DOCKER_SWARM') {
container.hostIP = $scope.swarm_hosts[_.split(container.Names[0], '/')[1]];
}
var previousContainer = _.find(previouslySelectedContainers, function(item) {
return item.Id === container.Id;
});
if (previousContainer && previousContainer.Checked) {
container.Checked = true;
}
return container;
});
}
function initView() {
var provider = $scope.applicationState.endpoint.mode.provider;
$q.when(provider !== 'DOCKER_SWARM' || SystemService.info())
$q.all({
swarm: provider !== 'DOCKER_SWARM' || SystemService.info(),
containers: ContainerService.containers(1)
})
.then(function success(data) {
if (provider === 'DOCKER_SWARM') {
$scope.swarm_hosts = retrieveSwarmHostsInfo(data);
$scope.swarm_hosts = retrieveSwarmHostsInfo(data.swarm);
}
update({all: $scope.state.displayAll ? 1 : 0});
assignContainers(data.containers, provider);
})
.catch(function error(err) {
Notifications.error('Failure', err, 'Unable to retrieve cluster information');
Notifications.error('Failure', err, 'Unable to retrieve containers');
$scope.containers = [];
});
}

View File

@ -523,7 +523,7 @@
Memory reservation
</label>
<div class="col-sm-3">
<por-slider model="formValues.MemoryReservation" floor="0" ceil="state.sliderMaxMemory" step="256" ng-if="state.sliderMaxMemory"></por-slider>
<slider model="formValues.MemoryReservation" floor="0" ceil="state.sliderMaxMemory" step="256" ng-if="state.sliderMaxMemory"></slider>
</div>
<div class="col-sm-2">
<input type="number" min="0" class="form-control" ng-model="formValues.MemoryReservation" id="memory-reservation">
@ -541,7 +541,7 @@
Memory limit
</label>
<div class="col-sm-3">
<por-slider model="formValues.MemoryLimit" floor="0" ceil="state.sliderMaxMemory" step="256" ng-if="state.sliderMaxMemory"></por-slider>
<slider model="formValues.MemoryLimit" floor="0" ceil="state.sliderMaxMemory" step="256" ng-if="state.sliderMaxMemory"></slider>
</div>
<div class="col-sm-2">
<input type="number" min="0" class="form-control" ng-model="formValues.MemoryLimit" id="memory-limit">
@ -559,7 +559,7 @@
CPU limit
</label>
<div class="col-sm-5">
<por-slider model="formValues.CpuLimit" floor="0" ceil="state.sliderMaxCpu" step="0.25" precision="2" ng-if="state.sliderMaxCpu"></por-slider>
<slider model="formValues.CpuLimit" floor="0" ceil="state.sliderMaxCpu" step="0.25" precision="2" ng-if="state.sliderMaxCpu"></slider>
</div>
<div class="col-sm-4" style="margin-top: 20px;">
<p class="small text-muted">

View File

@ -24,7 +24,7 @@ function ($q, $scope, $state, $timeout, Service, ServiceHelper, ConfigService, C
Parallelism: 1,
PlacementConstraints: [],
PlacementPreferences: [],
UpdateDelay: 0,
UpdateDelay: '0s',
UpdateOrder: 'stop-first',
FailureAction: 'pause',
Secrets: [],
@ -35,7 +35,11 @@ function ($q, $scope, $state, $timeout, Service, ServiceHelper, ConfigService, C
MemoryLimit: 0,
MemoryReservation: 0,
MemoryLimitUnit: 'MB',
MemoryReservationUnit: 'MB'
MemoryReservationUnit: 'MB',
RestartCondition: 'any',
RestartDelay: '5s',
RestartMaxAttempts: 0,
RestartWindow: '0s'
};
$scope.state = {
@ -243,12 +247,21 @@ function ($q, $scope, $state, $timeout, Service, ServiceHelper, ConfigService, C
function prepareUpdateConfig(config, input) {
config.UpdateConfig = {
Parallelism: input.Parallelism || 0,
Delay: input.UpdateDelay * 1000000000 || 0,
Delay: ServiceHelper.translateHumanDurationToNanos(input.UpdateDelay) || 0,
FailureAction: input.FailureAction,
Order: input.UpdateOrder
};
}
function prepareRestartPolicy(config, input) {
config.TaskTemplate.RestartPolicy = {
Condition: input.RestartCondition || 'any',
Delay: ServiceHelper.translateHumanDurationToNanos(input.RestartDelay) || 5000000000,
MaxAttempts: input.RestartMaxAttempts || 0,
Window: ServiceHelper.translateHumanDurationToNanos(input.RestartWindow) || 0
};
}
function preparePlacementConfig(config, input) {
config.TaskTemplate.Placement.Constraints = ServiceHelper.translateKeyValueToPlacementConstraints(input.PlacementConstraints);
config.TaskTemplate.Placement.Preferences = ServiceHelper.translateKeyValueToPlacementPreferences(input.PlacementPreferences);
@ -348,6 +361,7 @@ function ($q, $scope, $state, $timeout, Service, ServiceHelper, ConfigService, C
preparePlacementConfig(config, input);
prepareResourcesCpuConfig(config, input);
prepareResourcesMemoryConfig(config, input);
prepareRestartPolicy(config, input);
return config;
}

View File

@ -130,7 +130,7 @@
<li class="interactive"><a data-target="#volumes" data-toggle="tab">Volumes</a></li>
<li class="interactive"><a data-target="#network" data-toggle="tab">Network</a></li>
<li class="interactive"><a data-target="#labels" data-toggle="tab">Labels</a></li>
<li class="interactive"><a data-target="#update-config" data-toggle="tab">Update config</a></li>
<li class="interactive"><a data-target="#update-config" data-toggle="tab">Update config & Restart</a></li>
<li class="interactive" ng-if="applicationState.endpoint.apiVersion >= 1.25"><a data-target="#secrets" data-toggle="tab">Secrets</a></li>
<li class="interactive"><a data-target="#configs" data-toggle="tab" ng-if="applicationState.endpoint.apiVersion >= 1.30">Configs</a></li>
<li class="interactive"><a data-target="#resources-placement" data-toggle="tab" ng-click="refreshSlider()">Resources & Placement</a></li>
@ -372,71 +372,7 @@
</div>
<!-- !tab-labels -->
<!-- tab-update-config -->
<div class="tab-pane" id="update-config">
<form class="form-horizontal" style="margin-top: 15px;">
<!-- parallelism-input -->
<div class="form-group">
<label for="parallelism" class="col-sm-3 col-lg-1 control-label text-left">Parallelism</label>
<div class="col-sm-4 col-lg-3">
<input type="number" class="form-control" ng-model="formValues.Parallelism" id="parallelism" placeholder="e.g. 1">
</div>
<div class="col-sm-5">
<p class="small text-muted">
Maximum number of tasks to be updated simultaneously (0 to update all at once).
</p>
</div>
</div>
<!-- !parallelism-input -->
<!-- delay-input -->
<div class="form-group">
<label for="update-delay" class="col-sm-3 col-lg-1 control-label text-left">Delay</label>
<div class="col-sm-4 col-lg-3">
<input type="number" class="form-control" ng-model="formValues.UpdateDelay" id="update-delay" placeholder="e.g. 10">
</div>
<div class="col-sm-5">
<p class="small text-muted">
Amount of time between updates. Time in seconds.
</p>
</div>
</div>
<!-- !delay-input -->
<!-- failureAction-input -->
<div class="form-group">
<label for="failure-action" class="col-sm-3 col-lg-1 control-label text-left">Failure action</label>
<div class="col-sm-4 col-lg-3">
<div class="btn-group btn-group-sm">
<label class="btn btn-primary" ng-model="formValues.FailureAction" uib-btn-radio="'continue'">Continue</label>
<label class="btn btn-primary" ng-model="formValues.FailureAction" uib-btn-radio="'pause'">Pause</label>
</div>
</div>
<div class="col-sm-5">
<p class="small text-muted">
Action taken on failure to start after update.
</p>
</div>
</div>
<!-- !failureAction-input -->
<!-- order-input -->
<div class="form-group" ng-if="applicationState.endpoint.apiVersion >= 1.29">
<label for="update-order" class="col-sm-3 col-lg-1 control-label text-left">Order</label>
<div class="col-sm-4 col-lg-3">
<div class="btn-group btn-group-sm">
<label class="btn btn-primary" ng-model="formValues.UpdateOrder" uib-btn-radio="'start-first'">start-first</label>
<label class="btn btn-primary" ng-model="formValues.UpdateOrder" uib-btn-radio="'stop-first'">stop-first</label>
</div>
</div>
<div class="col-sm-5">
<p class="small text-muted">
Operation order on failure.
</p>
</div>
</div>
<!-- !order-input -->
</form>
</div>
<div class="tab-pane" id="update-config" ng-include="'app/components/createService/includes/update-restart.html'"></div>
<!-- !tab-update-config -->
<!-- tab-secrets -->
<div class="tab-pane" id="secrets" ng-if="applicationState.endpoint.apiVersion >= 1.25" ng-include="'app/components/createService/includes/secret.html'"></div>

View File

@ -8,7 +8,7 @@
Memory reservation
</label>
<div class="col-sm-3">
<por-slider model="formValues.MemoryReservation" floor="0" ceil="state.sliderMaxMemory" step="256" ng-if="state.sliderMaxMemory"></por-slider>
<slider model="formValues.MemoryReservation" floor="0" ceil="state.sliderMaxMemory" step="256" ng-if="state.sliderMaxMemory"></slider>
</div>
<div class="col-sm-2">
<input type="number" min="0" class="form-control" ng-model="formValues.MemoryReservation">
@ -26,7 +26,7 @@
Memory limit
</label>
<div class="col-sm-3">
<por-slider model="formValues.MemoryLimit" floor="0" ceil="state.sliderMaxMemory" step="256" ng-if="state.sliderMaxMemory"></por-slider>
<slider model="formValues.MemoryLimit" floor="0" ceil="state.sliderMaxMemory" step="256" ng-if="state.sliderMaxMemory"></slider>
</div>
<div class="col-sm-2">
<input type="number" min="0" class="form-control" ng-model="formValues.MemoryLimit">
@ -44,7 +44,7 @@
CPU reservation
</label>
<div class="col-sm-5">
<por-slider model="formValues.CpuReservation" floor="0" ceil="state.sliderMaxCpu" step="0.25" precision="2" ng-if="state.sliderMaxCpu"></por-slider>
<slider model="formValues.CpuReservation" floor="0" ceil="state.sliderMaxCpu" step="0.25" precision="2" ng-if="state.sliderMaxCpu"></slider>
</div>
<div class="col-sm-4" style="margin-top: 20px;">
<p class="small text-muted">
@ -59,7 +59,7 @@
CPU limit
</label>
<div class="col-sm-5">
<por-slider model="formValues.CpuLimit" floor="0" ceil="state.sliderMaxCpu" step="0.25" precision="2" ng-if="state.sliderMaxCpu"></por-slider>
<slider model="formValues.CpuLimit" floor="0" ceil="state.sliderMaxCpu" step="0.25" precision="2" ng-if="state.sliderMaxCpu"></slider>
</div>
<div class="col-sm-4" style="margin-top: 20px;">
<p class="small text-muted">

View File

@ -0,0 +1,132 @@
<form class="form-horizontal" style="margin-top: 15px;">
<div class="col-sm-12 form-section-title">
Update config
</div>
<!-- parallelism-input -->
<div class="form-group">
<label for="parallelism" class="col-sm-3 col-lg-2 control-label text-left">Update parallelism</label>
<div class="col-sm-4 col-lg-3">
<input type="number" class="form-control" ng-model="formValues.Parallelism" id="parallelism" placeholder="e.g. 1">
</div>
<div class="col-sm-5">
<p class="small text-muted">
Maximum number of tasks to be updated simultaneously (0 to update all at once).
</p>
</div>
</div>
<!-- !parallelism-input -->
<!-- delay-input -->
<div class="form-group">
<label for="update-delay" class="col-sm-3 col-lg-2 control-label text-left">
Update delay
<portainer-tooltip position="bottom" message="Supported format examples: 1h, 5m, 10s, 1000ms, 15us, 60ns."></portainer-tooltip>
</label>
<div class="col-sm-4 col-lg-3">
<input type="text" class="form-control" ng-model="formValues.UpdateDelay" id="update-delay" placeholder="e.g. 1m" ng-pattern="/^([0-9]+)(h|m|s|ms|us|ns)$/i">
</div>
<div class="col-sm-5">
<p class="small text-muted">
Amount of time between updates expressed by a number followed by unit (ns|us|ms|s|m|h). Default value is 0s, 0 seconds.
</p>
</div>
</div>
<!-- !delay-input -->
<!-- failureAction-input -->
<div class="form-group">
<label for="failure-action" class="col-sm-3 col-lg-2 control-label text-left">Update failure action</label>
<div class="col-sm-4 col-lg-3">
<div class="btn-group btn-group-sm">
<label class="btn btn-primary" ng-model="formValues.FailureAction" uib-btn-radio="'continue'">Continue</label>
<label class="btn btn-primary" ng-model="formValues.FailureAction" uib-btn-radio="'pause'">Pause</label>
</div>
</div>
<div class="col-sm-5">
<p class="small text-muted">
Action taken on failure to start after update.
</p>
</div>
</div>
<!-- !failureAction-input -->
<!-- order-input -->
<div class="form-group" ng-if="applicationState.endpoint.apiVersion >= 1.29">
<label for="update-order" class="col-sm-3 col-lg-2 control-label text-left">Update order</label>
<div class="col-sm-4 col-lg-3">
<div class="btn-group btn-group-sm">
<label class="btn btn-primary" ng-model="formValues.UpdateOrder" uib-btn-radio="'start-first'">start-first</label>
<label class="btn btn-primary" ng-model="formValues.UpdateOrder" uib-btn-radio="'stop-first'">stop-first</label>
</div>
</div>
<div class="col-sm-5">
<p class="small text-muted">
Operation order on failure.
</p>
</div>
</div>
<!-- !order-input -->
<div class="col-sm-12 form-section-title">
Restart policy
</div>
<!-- restartCondition-input -->
<div class="form-group">
<label for="restart-condition" class="col-sm-3 col-lg-2 control-label text-left">Restart condition</label>
<div class="col-sm-4 col-lg-3">
<div class="btn-group btn-group-sm">
<label class="btn btn-primary" ng-model="formValues.RestartCondition" uib-btn-radio="'none'">None</label>
<label class="btn btn-primary" ng-model="formValues.RestartCondition" uib-btn-radio="'on-failure'">On-failure</label>
<label class="btn btn-primary" ng-model="formValues.RestartCondition" uib-btn-radio="'any'">Any</label>
</div>
</div>
<div class="col-sm-5">
<p class="small text-muted">
Restart when condition is met (default condition "any").
</p>
</div>
</div>
<!-- !restartCondition-input -->
<!-- restartDelay-input -->
<div class="form-group">
<label for="restart-delay" class="col-sm-3 col-lg-2 control-label text-left">
Restart delay
<portainer-tooltip position="bottom" message="Supported format examples: 1h, 5m, 10s, 1000ms, 15us, 60ns."></portainer-tooltip>
</label>
<div class="col-sm-4 col-lg-3">
<input type="text" class="form-control" ng-model="formValues.RestartDelay" id="restart-delay" placeholder="e.g. 1m" ng-pattern="/^([0-9]+)(h|m|s|ms|us|ns)$/i">
</div>
<div class="col-sm-5">
<p class="small text-muted">
Delay between restart attempts expressed by a number followed by unit (ns|us|ms|s|m|h). Default value is 5s, 5 seconds.
</p>
</div>
</div>
<!-- !restartDelay-input -->
<!-- restartMaxAttempts-input -->
<div class="form-group">
<label for="restart-max-attempts" class="col-sm-3 col-lg-2 control-label text-left">Restart max attempts</label>
<div class="col-sm-4 col-lg-3">
<input type="number" class="form-control" ng-model="formValues.RestartMaxAttempts" id="restart-max-attempts" placeholder="e.g. 0">
</div>
<div class="col-sm-5">
<p class="small text-muted">
Maximum attempts to restart a given task before giving up (default value is 0, which means unlimited).
</p>
</div>
</div>
<!-- !restartMaxAttempts-input -->
<!-- restartWindow-input -->
<div class="form-group">
<label for="restart-window" class="col-sm-3 col-lg-2 control-label text-left">
Restart window
<portainer-tooltip position="bottom" message="Supported format examples: 1h, 5m, 10s, 1000ms, 15us, 60ns."></portainer-tooltip>
</label>
<div class="col-sm-4 col-lg-3">
<input type="text" class="form-control" ng-model="formValues.RestartWindow" id="restart-window" placeholder="e.g. 1m" ng-pattern="/^([0-9]+)(h|m|s|ms|us|ns)$/i">
</div>
<div class="col-sm-5">
<p class="small text-muted">
Time window to evaluate restart attempts expressed by a number followed by unit (ns|us|ms|s|m|h). Default value is 0 seconds, which is unbounded.
</p>
</div>
</div>
<!-- !restartWindow-input -->
</form>

View File

@ -75,6 +75,13 @@
<td>Nodes in the cluster</td>
<td>{{ infoData.Swarm.Nodes }}</td>
</tr>
<tr>
<td colspan="2">
<div class="btn-group" role="group" aria-label="...">
<a ui-sref="swarm.visualizer"><i class="fa fa-object-group space-right" aria-hidden="true"></i>Go to cluster visualizer</a>
</div>
</td>
</tr>
</tbody>
</table>
</rd-widget-body>

View File

@ -67,93 +67,26 @@
<div class="col-sm-12">
<button type="submit" class="btn btn-primary btn-sm" ng-disabled="state.actionInProgress || !formValues.Name || !formValues.URL || (formValues.TLS && ((formValues.TLSVerify && !formValues.TLSCACert) || (formValues.TLSClientCert && (!formValues.TLSCert || !formValues.TLSKey))))" ng-click="addEndpoint()" button-spinner="state.actionInProgress">
<span ng-hide="state.actionInProgress"><i class="fa fa-plus" aria-hidden="true"></i> Add endpoint</span>
<span ng-show="state.actionInProgress">Creating endpoint...</span>
<span ng-show="state.actionInProgress">Creating endpoint...</span>
</button>
</div>
</div>
</div>
<!-- !actions -->
</form>
</rd-widget-body>
</rd-widget>
</div>
</div>
<div class="row">
<div class="col-lg-12 col-md-12 col-xs-12">
<rd-widget>
<rd-widget-header icon="fa-plug" title="Endpoints">
<div class="pull-right">
Items per page:
<select ng-model="state.pagination_count" ng-change="changePaginationCount()">
<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>
</div>
</rd-widget-header>
<rd-widget-taskbar classes="col-lg-12">
<div class="pull-left" ng-if="applicationState.application.endpointManagement">
<button type="button" class="btn btn-danger" ng-click="removeAction()" ng-disabled="!state.selectedItemCount"><i class="fa fa-trash space-right" aria-hidden="true"></i>Remove</button>
</div>
<div class="pull-right">
<input type="text" id="filter" ng-model="state.filter" placeholder="Filter..." class="form-control input-sm" />
</div>
</rd-widget-taskbar>
<rd-widget-body classes="no-padding">
<div class="table-responsive">
<table class="table table-hover">
<thead>
<tr>
<th ng-if="applicationState.application.endpointManagement">
<input type="checkbox" ng-model="allSelected" ng-change="selectItems(allSelected)" />
</th>
<th>
<a ui-sref="endpoints" ng-click="order('Name')">
Name
<span ng-show="sortType == 'Name' && !sortReverse" class="glyphicon glyphicon-chevron-down"></span>
<span ng-show="sortType == 'Name' && sortReverse" class="glyphicon glyphicon-chevron-up"></span>
</a>
</th>
<th>
<a ui-sref="endpoints" ng-click="order('URL')">
URL
<span ng-show="sortType == 'URL' && !sortReverse" class="glyphicon glyphicon-chevron-down"></span>
<span ng-show="sortType == 'URL' && sortReverse" class="glyphicon glyphicon-chevron-up"></span>
</a>
</th>
<th></th>
</tr>
</thead>
<tbody>
<tr dir-paginate="endpoint in (state.filteredEndpoints = (endpoints | filter:state.filter | orderBy:sortType:sortReverse | itemsPerPage: state.pagination_count))" ng-class="{active: endpoint.Checked}">
<td ng-if="applicationState.application.endpointManagement"><input type="checkbox" ng-model="endpoint.Checked" ng-change="selectItem(endpoint)" /></td>
<td><i class="fa fa-star" aria-hidden="true" ng-if="endpoint.Id === activeEndpoint.Id"></i> {{ endpoint.Name }}</td>
<td>{{ endpoint.URL | stripprotocol }}</td>
<td>
<span ng-if="applicationState.application.endpointManagement">
<a ui-sref="endpoint({id: endpoint.Id})"><i class="fa fa-pencil-square-o" aria-hidden="true"></i> Edit</a>
</span>
<span ng-if="applicationState.application.authentication">
<a ui-sref="endpoint.access({id: endpoint.Id})"><i class="fa fa-users" aria-hidden="true" style="margin-left: 7px;"></i> Manage access</a>
</span>
</td>
</tr>
<tr ng-if="!endpoints">
<td colspan="5" class="text-center text-muted">Loading...</td>
</tr>
<tr ng-if="endpoints.length == 0">
<td colspan="5" class="text-center text-muted">No endpoints available.</td>
</tr>
</tbody>
</table>
<div ng-if="endpoints" class="pull-left pagination-controls">
<dir-pagination-controls></dir-pagination-controls>
</div>
</div>
<!-- !actions -->
</form>
</rd-widget-body>
</rd-widget>
</div>
</div>
<div class="row">
<div class="col-sm-12">
<endpoints-datatable
title="Endpoints" title-icon="fa-plug"
dataset="endpoints" table-key="endpoints"
order-by="Name" show-text-filter="true"
endpoint-management="applicationState.application.endpointManagement"
access-management="applicationState.application.authentication"
remove-action="removeAction"
></endpoints-datatable>
</div>
</div>

View File

@ -1,14 +1,10 @@
angular.module('endpoints', [])
.controller('EndpointsController', ['$scope', '$state', '$filter', 'EndpointService', 'EndpointProvider', 'Notifications', 'Pagination',
function ($scope, $state, $filter, EndpointService, EndpointProvider, Notifications, Pagination) {
.controller('EndpointsController', ['$scope', '$state', '$filter', 'EndpointService', 'Notifications',
function ($scope, $state, $filter, EndpointService, Notifications) {
$scope.state = {
uploadInProgress: false,
selectedItemCount: 0,
pagination_count: Pagination.getPaginationCount('endpoints'),
actionInProgress: false
};
$scope.sortType = 'Name';
$scope.sortReverse = true;
$scope.formValues = {
Name: '',
@ -17,32 +13,6 @@ function ($scope, $state, $filter, EndpointService, EndpointProvider, Notificati
SecurityFormData: new EndpointSecurityFormData()
};
$scope.order = function(sortType) {
$scope.sortReverse = ($scope.sortType === sortType) ? !$scope.sortReverse : false;
$scope.sortType = sortType;
};
$scope.changePaginationCount = function() {
Pagination.setPaginationCount('endpoints', $scope.state.pagination_count);
};
$scope.selectItems = function (allSelected) {
angular.forEach($scope.state.filteredEndpoints, function (endpoint) {
if (endpoint.Checked !== allSelected) {
endpoint.Checked = allSelected;
$scope.selectItem(endpoint);
}
});
};
$scope.selectItem = function (item) {
if (item.Checked) {
$scope.state.selectedItemCount++;
} else {
$scope.state.selectedItemCount--;
}
};
$scope.addEndpoint = function() {
var name = $scope.formValues.Name;
var URL = $filter('stripprotocol')($scope.formValues.URL);
@ -75,17 +45,24 @@ function ($scope, $state, $filter, EndpointService, EndpointProvider, Notificati
});
};
$scope.removeAction = function () {
angular.forEach($scope.endpoints, function (endpoint) {
if (endpoint.Checked) {
EndpointService.deleteEndpoint(endpoint.Id).then(function success(data) {
Notifications.success('Endpoint deleted', endpoint.Name);
var index = $scope.endpoints.indexOf(endpoint);
$scope.endpoints.splice(index, 1);
}, function error(err) {
Notifications.error('Failure', err, 'Unable to remove endpoint');
});
}
$scope.removeAction = function (selectedItems) {
var actionCount = selectedItems.length;
angular.forEach(selectedItems, function (endpoint) {
EndpointService.deleteEndpoint(endpoint.Id)
.then(function success() {
Notifications.success('Endpoint successfully removed', endpoint.Name);
var index = $scope.endpoints.indexOf(endpoint);
$scope.endpoints.splice(index, 1);
})
.catch(function error(err) {
Notifications.error('Failure', err, 'Unable to remove endpoint');
})
.finally(function final() {
--actionCount;
if (actionCount === 0) {
$state.reload();
}
});
});
};

View File

@ -8,66 +8,12 @@
</rd-header>
<div class="row">
<div class="col-lg-12 col-md-12 col-xs-12">
<rd-widget>
<rd-widget-header icon="fa-history" title="Events">
<div class="pull-right">
Items per page:
<select ng-model="state.pagination_count" ng-change="changePaginationCount()">
<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>
</div>
</rd-widget-header>
<rd-widget-taskbar classes="col-lg-12">
<div class="pull-right">
<input type="text" id="filter" ng-model="state.filter" placeholder="Filter..." class="form-control input-sm" />
</div>
</rd-widget-taskbar>
<rd-widget-body classes="no-padding">
<div class="table-responsive">
<table class="table table-hover">
<thead>
<tr>
<th>
<a ui-sref="events" ng-click="order('Time')">
Date
<span ng-show="sortType == 'Time' && !sortReverse" class="glyphicon glyphicon-chevron-down"></span>
<span ng-show="sortType == 'Time' && sortReverse" class="glyphicon glyphicon-chevron-up"></span>
</a>
</th>
<th>
<a ui-sref="events" ng-click="order('Type')">
Category
<span ng-show="sortType == 'Type' && !sortReverse" class="glyphicon glyphicon-chevron-down"></span>
<span ng-show="sortType == 'Type' && sortReverse" class="glyphicon glyphicon-chevron-up"></span>
</a>
</th>
<th>
<a ui-sref="events" ng-click="order('Details')">
Details
<span ng-show="sortType == 'Details' && !sortReverse" class="glyphicon glyphicon-chevron-down"></span>
<span ng-show="sortType == 'Details' && sortReverse" class="glyphicon glyphicon-chevron-up"></span>
</a>
</th>
</tr>
</thead>
<tbody>
<tr dir-paginate="event in (events | filter:state.filter | orderBy:sortType:sortReverse | itemsPerPage: state.pagination_count)">
<td>{{ event.Time|getisodatefromtimestamp }}</td>
<td>{{ event.Type }}</td>
<td>{{ event.Details }}</td>
</tr>
</tbody>
</table>
<div ng-if="events" class="pull-left pagination-controls">
<dir-pagination-controls></dir-pagination-controls>
</div>
</div>
</rd-widget-body>
</rd-widget>
<div class="col-sm-12">
<events-datatable
title="Events" title-icon="fa-history"
dataset="events" table-key="events"
order-by="Time" reverse-order="true"
show-text-filter="true"
></events-datatable>
</div>
</div>

View File

@ -1,19 +1,6 @@
angular.module('events', [])
.controller('EventsController', ['$scope', 'Notifications', 'SystemService', 'Pagination',
function ($scope, Notifications, SystemService, Pagination) {
$scope.state = {};
$scope.state.pagination_count = Pagination.getPaginationCount('events');
$scope.sortType = 'Time';
$scope.sortReverse = true;
$scope.order = function(sortType) {
$scope.sortReverse = ($scope.sortType === sortType) ? !$scope.sortReverse : false;
$scope.sortType = sortType;
};
$scope.changePaginationCount = function() {
Pagination.setPaginationCount('events', $scope.state.pagination_count);
};
.controller('EventsController', ['$scope', 'Notifications', 'SystemService',
function ($scope, Notifications, SystemService) {
function initView() {
var from = moment().subtract(24, 'hour').unix();

View File

@ -41,112 +41,13 @@
</div>
<div class="row">
<div class="col-lg-12 col-md-12 col-xs-12">
<rd-widget>
<rd-widget-header icon="fa-clone" title="Images">
<div class="pull-right">
Items per page:
<select ng-model="state.pagination_count" ng-change="changePaginationCount()">
<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>
</div>
</rd-widget-header>
<rd-widget-taskbar classes="col-lg-12">
<div class="pull-left">
<div class="btn-group">
<button type="button" class="btn btn-danger" ng-click="removeAction()" ng-disabled="!state.selectedItemCount"><i class="fa fa-trash space-right" aria-hidden="true"></i>Remove</button>
<button type="button" class="btn btn-danger dropdown-toggle" data-toggle="dropdown" aria-haspopup="true" aria-expanded="false" ng-disabled="!state.selectedItemCount" >
<span class="caret"></span>
<span class="sr-only">Toggle Dropdown</span>
</button>
<ul class="dropdown-menu">
<li><a ng-click="confirmRemovalAction(true)" ng-disabled="!state.selectedItemCount" >Force Remove</a></li>
</ul>
</div>
</div>
<div class="pull-right">
<input type="text" id="filter" ng-model="state.filter" placeholder="Filter..." class="form-control input-sm" />
</div>
<span class="btn-group btn-group-sm pull-right" style="margin-right: 20px;">
<label class="btn btn-primary" ng-model="state.containersCountFilter" uib-btn-radio="undefined">
All
</label>
<label class="btn btn-primary" ng-model="state.containersCountFilter" uib-btn-radio="'!' + 0">
Used
</label>
<label class="btn btn-primary" ng-model="state.containersCountFilter" uib-btn-radio="0">
Unused
</label>
</span>
</rd-widget-taskbar>
<rd-widget-body classes="no-padding">
<div class="table-responsive">
<table class="table table-hover">
<thead>
<tr>
<th>
<input type="checkbox" ng-model="allSelected" ng-change="selectItems(allSelected)" />
</th>
<th>
<a ui-sref="images" ng-click="order('Id')">
Id
<span ng-show="sortType == 'Id' && !sortReverse" class="glyphicon glyphicon-chevron-down"></span>
<span ng-show="sortType == 'Id' && sortReverse" class="glyphicon glyphicon-chevron-up"></span>
</a>
</th>
<th>
<a ui-sref="images" ng-click="order('RepoTags')">
Tags
<span ng-show="sortType == 'RepoTags' && !sortReverse" class="glyphicon glyphicon-chevron-down"></span>
<span ng-show="sortType == 'RepoTags' && sortReverse" class="glyphicon glyphicon-chevron-up"></span>
</a>
</th>
<th>
<a ui-sref="images" ng-click="order('VirtualSize')">
Size
<span ng-show="sortType == 'VirtualSize' && !sortReverse" class="glyphicon glyphicon-chevron-down"></span>
<span ng-show="sortType == 'VirtualSize' && sortReverse" class="glyphicon glyphicon-chevron-up"></span>
</a>
</th>
<th>
<a ui-sref="images" ng-click="order('Created')">
Created
<span ng-show="sortType == 'Created' && !sortReverse" class="glyphicon glyphicon-chevron-down"></span>
<span ng-show="sortType == 'Created' && sortReverse" class="glyphicon glyphicon-chevron-up"></span>
</a>
</th>
</tr>
</thead>
<tbody>
<tr dir-paginate="image in (state.filteredImages = (images | filter:{ ContainerCount: state.containersCountFilter } | filter:state.filter | orderBy:sortType:sortReverse | itemsPerPage: state.pagination_count))" ng-class="{active: image.Checked}">
<td><input type="checkbox" ng-model="image.Checked" ng-change="selectItem(image)" /></td>
<td>
<a class="monospaced" ui-sref="image({id: image.Id})">{{ image.Id|truncate:20}}</a>
<span style="margin-left: 10px;" class="label label-warning image-tag" ng-if="::image.ContainerCount === 0">Unused</span>
</td>
<td>
<span class="label label-primary image-tag" ng-repeat="tag in (image|repotags)">{{ tag }}</span>
</td>
<td>{{ image.VirtualSize|humansize }}</td>
<td>{{ image.Created|getisodatefromtimestamp }}</td>
</tr>
<tr ng-if="!images">
<td colspan="5" class="text-center text-muted">Loading...</td>
</tr>
<tr ng-if="state.filteredImages.length === 0">
<td colspan="5" class="text-center text-muted">No images available.</td>
</tr>
</tbody>
</table>
<div ng-if="images" class="pull-left pagination-controls">
<dir-pagination-controls></dir-pagination-controls>
</div>
</div>
</rd-widget-body>
</rd-widget>
<div class="col-sm-12">
<images-datatable
title="Images" title-icon="fa-clone"
dataset="images" table-key="images"
order-by="RepoTags" show-text-filter="true"
remove-action="removeAction"
force-remove-action="confirmRemovalAction"
></images-datatable>
</div>
</div>

View File

@ -1,46 +1,15 @@
angular.module('images', [])
.controller('ImagesController', ['$scope', '$state', 'ImageService', 'Notifications', 'Pagination', 'ModalService',
function ($scope, $state, ImageService, Notifications, Pagination, ModalService) {
.controller('ImagesController', ['$scope', '$state', 'ImageService', 'Notifications', 'ModalService',
function ($scope, $state, ImageService, Notifications, ModalService) {
$scope.state = {
pagination_count: Pagination.getPaginationCount('images'),
actionInProgress: false,
selectedItemCount: 0
actionInProgress: false
};
$scope.sortType = 'RepoTags';
$scope.sortReverse = true;
$scope.formValues = {
Image: '',
Registry: ''
};
$scope.order = function(sortType) {
$scope.sortReverse = ($scope.sortType === sortType) ? !$scope.sortReverse : false;
$scope.sortType = sortType;
};
$scope.changePaginationCount = function() {
Pagination.setPaginationCount('images', $scope.state.pagination_count);
};
$scope.selectItems = function (allSelected) {
angular.forEach($scope.state.filteredImages, function (image) {
if (image.Checked !== allSelected) {
image.Checked = allSelected;
$scope.selectItem(image);
}
});
};
$scope.selectItem = function (item) {
if (item.Checked) {
$scope.state.selectedItemCount++;
} else {
$scope.state.selectedItemCount--;
}
};
$scope.pullImage = function() {
var image = $scope.formValues.Image;
var registry = $scope.formValues.Registry;
@ -59,33 +28,38 @@ function ($scope, $state, ImageService, Notifications, Pagination, ModalService)
});
};
$scope.confirmRemovalAction = function (force) {
$scope.confirmRemovalAction = function (selectedItems, force) {
ModalService.confirmImageForceRemoval(function (confirmed) {
if(!confirmed) { return; }
$scope.removeAction(force);
$scope.removeAction(selectedItems, force);
});
};
$scope.removeAction = function (force) {
force = !!force;
angular.forEach($scope.images, function (i) {
if (i.Checked) {
ImageService.deleteImage(i.Id, force)
.then(function success(data) {
Notifications.success('Image deleted', i.Id);
var index = $scope.images.indexOf(i);
$scope.images.splice(index, 1);
})
.catch(function error(err) {
Notifications.error('Failure', err, 'Unable to remove image');
});
}
$scope.removeAction = function (selectedItems, force) {
var actionCount = selectedItems.length;
angular.forEach(selectedItems, function (image) {
ImageService.deleteImage(image.Id, force)
.then(function success() {
Notifications.success('Image successfully removed', image.Id);
var index = $scope.images.indexOf(image);
$scope.images.splice(index, 1);
})
.catch(function error(err) {
Notifications.error('Failure', err, 'Unable to remove image');
})
.finally(function final() {
--actionCount;
if (actionCount === 0) {
$state.reload();
}
});
});
};
function fetchImages() {
function initView() {
var endpointProvider = $scope.applicationState.endpoint.mode.provider;
var apiVersion = $scope.applicationState.endpoint.apiVersion;
ImageService.images(true)
.then(function success(data) {
$scope.images = data;
@ -96,5 +70,5 @@ function ($scope, $state, ImageService, Notifications, Pagination, ModalService)
});
}
fetchImages();
initView();
}]);

View File

@ -8,6 +8,18 @@
</rd-header>
<div class="row">
<div class="col-sm-12">
<networks-datatable
title="Networks" title-icon="fa-sitemap"
dataset="networks" table-key="networks"
order-by="Name" show-text-filter="true"
remove-action="removeAction"
show-ownership-column="applicationState.application.authentication"
></networks-datatable>
</div>
</div>
<!-- <div class="row">
<div class="col-lg-12 col-md-12 col-xs-12">
<rd-widget>
<rd-widget-header icon="fa-sitemap" title="Networks">
@ -129,4 +141,4 @@
</rd-widget-body>
</rd-widget>
</div>
</div>
</div> -->

View File

@ -1,54 +1,25 @@
angular.module('networks', [])
.controller('NetworksController', ['$scope', '$state', 'Network', 'NetworkService', 'Notifications', 'Pagination',
function ($scope, $state, Network, NetworkService, Notifications, Pagination) {
$scope.state = {};
$scope.state.pagination_count = Pagination.getPaginationCount('networks');
$scope.state.selectedItemCount = 0;
$scope.state.advancedSettings = false;
$scope.sortType = 'Name';
$scope.sortReverse = false;
.controller('NetworksController', ['$scope', '$state', 'NetworkService', 'Notifications',
function ($scope, $state, NetworkService, Notifications) {
$scope.changePaginationCount = function() {
Pagination.setPaginationCount('networks', $scope.state.pagination_count);
};
$scope.order = function(sortType) {
$scope.sortReverse = ($scope.sortType === sortType) ? !$scope.sortReverse : false;
$scope.sortType = sortType;
};
$scope.selectItems = function(allSelected) {
angular.forEach($scope.state.filteredNetworks, function (network) {
if (network.Checked !== allSelected) {
network.Checked = allSelected;
$scope.selectItem(network);
}
});
};
$scope.selectItem = function (item) {
if (item.Checked) {
$scope.state.selectedItemCount++;
} else {
$scope.state.selectedItemCount--;
}
};
$scope.removeAction = function () {
angular.forEach($scope.networks, function (network) {
if (network.Checked) {
Network.remove({id: network.Id}, function (d) {
if (d.message) {
Notifications.error('Error', d, 'Unable to remove network');
} else {
Notifications.success('Network removed', network.Id);
var index = $scope.networks.indexOf(network);
$scope.networks.splice(index, 1);
}
}, function (e) {
Notifications.error('Failure', e, 'Unable to remove network');
});
}
$scope.removeAction = function (selectedItems) {
var actionCount = selectedItems.length;
angular.forEach(selectedItems, function (network) {
NetworkService.remove(network.Id)
.then(function success() {
Notifications.success('Network successfully removed', network.Name);
var index = $scope.networks.indexOf(network);
$scope.networks.splice(index, 1);
})
.catch(function error(err) {
Notifications.error('Failure', err, 'Unable to remove network');
})
.finally(function final() {
--actionCount;
if (actionCount === 0) {
$state.reload();
}
});
});
};

View File

@ -143,6 +143,33 @@
</div>
</div>
<div class="row" ng-if="node">
<div class="col-lg-12 col-md-12 col-sm-12 col-xs-12">
<rd-widget>
<rd-widget-header icon="fa-object-group" title="Engine labels"></rd-widget-header>
<rd-widget-body ng-if="!node.EngineLabels || node.EngineLabels.length === 0">
<p>There are no engine labels for this node.</p>
</rd-widget-body>
<rd-widget-body classes="no-padding" ng-if="node.EngineLabels && node.EngineLabels.length > 0">
<table class="table">
<thead>
<tr>
<th>Label</th>
<th>Value</th>
</tr>
</thead>
<tbody>
<tr ng-repeat="engineLabel in node.EngineLabels">
<td>{{ engineLabel.key }}</td>
<td>{{ engineLabel.value }}</td>
</tr>
</tbody>
</table>
</rd-widget-body>
</rd-widget>
</div>
</div>
<div class="row" ng-if="node">
<div class="col-lg-12 col-md-12 col-sm-12 col-xs-12">
<rd-widget>
@ -205,69 +232,12 @@
</div>
<div class="row" ng-if="node && tasks.length > 0">
<div class="col-lg-12 col-md-12 col-xs-12">
<rd-widget>
<rd-widget-header icon="fa-tasks" title="Associated tasks">
<div class="pull-right">
Items per page:
<select ng-model="state.pagination_count" ng-change="changePaginationCount()">
<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>
</div>
</rd-widget-header>
<rd-widget-body classes="no-padding">
<table class="table">
<thead>
<tr>
<th>Id</th>
<th>
<a ui-sref="node" ng-click="order('Status')">
Status
<span ng-show="sortType == 'Status' && !sortReverse" class="glyphicon glyphicon-chevron-down"></span>
<span ng-show="sortType == 'Status' && sortReverse" class="glyphicon glyphicon-chevron-up"></span>
</a>
</th>
<th>
<a ui-sref="node" ng-click="order('Slot')">
Slot
<span ng-show="sortType == 'Slot' && !sortReverse" class="glyphicon glyphicon-chevron-down"></span>
<span ng-show="sortType == 'Slot' && sortReverse" class="glyphicon glyphicon-chevron-up"></span>
</a>
</th>
<th>
<a ui-sref="node" ng-click="order('Spec.ContainerSpec.Image')">
Image
<span ng-show="sortType == 'Spec.ContainerSpec.Image' && !sortReverse" class="glyphicon glyphicon-chevron-down"></span>
<span ng-show="sortType == 'Spec.ContainerSpec.Image' && sortReverse" class="glyphicon glyphicon-chevron-up"></span>
</a>
</th>
<th>
<a ui-sref="node" ng-click="order('Updated')">
Last update
<span ng-show="sortType == 'Updated' && !sortReverse" class="glyphicon glyphicon-chevron-down"></span>
<span ng-show="sortType == 'Updated' && sortReverse" class="glyphicon glyphicon-chevron-up"></span>
</a>
</th>
</tr>
</thead>
<tbody>
<tr dir-paginate="task in (filteredTasks = ( tasks | orderBy:sortType:sortReverse | itemsPerPage: state.pagination_count))">
<td><a ui-sref="task({ id: task.Id })">{{ task.Id }}</a></td>
<td><span class="label label-{{ task.Status.State|taskstatusbadge }}">{{ task.Status.State }}</span></td>
<td>{{ task.Slot ? task.Slot : '-' }}</td>
<td>{{ task.Spec.ContainerSpec.Image | hideshasum }}</td>
<td>{{ task.Updated | getisodate }}</td>
</tr>
</tbody>
</table>
<div ng-if="tasks" class="pagination-controls">
<dir-pagination-controls></dir-pagination-controls>
</div>
</rd-widget-body>
</rd-widget>
<div class="col-sm-12">
<node-tasks-datatable
title="Tasks" title-icon="fa-tasks"
dataset="tasks" table-key="node-tasks"
order-by="Updated" reverse-order="true"
show-text-filter="true"
></node-tasks-datatable>
</div>
</div>

View File

@ -1,28 +1,15 @@
// @@OLD_SERVICE_CONTROLLER: this service should be rewritten to use services.
// See app/components/templates/templatesController.js as a reference.
angular.module('node', [])
.controller('NodeController', ['$scope', '$state', '$transition$', 'LabelHelper', 'Node', 'NodeHelper', 'Task', 'Pagination', 'Notifications',
function ($scope, $state, $transition$, LabelHelper, Node, NodeHelper, Task, Pagination, Notifications) {
.controller('NodeController', ['$scope', '$state', '$transition$', 'LabelHelper', 'Node', 'NodeHelper', 'Task', 'Notifications',
function ($scope, $state, $transition$, LabelHelper, Node, NodeHelper, Task, Notifications) {
$scope.state = {};
$scope.state.pagination_count = Pagination.getPaginationCount('node_tasks');
$scope.loading = true;
$scope.tasks = [];
$scope.sortType = 'Status';
$scope.sortReverse = false;
var originalNode = {};
var editedKeys = [];
$scope.order = function(sortType) {
$scope.sortReverse = ($scope.sortType === sortType) ? !$scope.sortReverse : false;
$scope.sortType = sortType;
};
$scope.changePaginationCount = function() {
Pagination.setPaginationCount('node_tasks', $scope.state.pagination_count);
};
$scope.updateNodeAttribute = function updateNodeAttribute(node, key) {
editedKeys.push(key);
};

View File

@ -70,82 +70,12 @@
<div class="row">
<div class="col-sm-12">
<rd-widget>
<rd-widget-header icon="fa-database" title="Available registries">
<div class="pull-right">
Items per page:
<select ng-model="state.pagination_count" ng-change="changePaginationCount()">
<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>
</div>
</rd-widget-header>
<rd-widget-taskbar classes="col-lg-12">
<div class="pull-left">
<button type="button" class="btn btn-danger" ng-click="removeAction()" ng-disabled="!state.selectedItemCount"><i class="fa fa-trash space-right" aria-hidden="true"></i>Remove</button>
<a class="btn btn-primary" type="button" ui-sref="actions.create.registry"><i class="fa fa-plus space-right" aria-hidden="true"></i>Add registry</a>
</div>
<div class="pull-right">
<input type="text" id="filter" ng-model="state.filter" placeholder="Filter..." class="form-control input-sm" />
</div>
</rd-widget-taskbar>
<rd-widget-body classes="no-padding">
<div class="table-responsive">
<table class="table table-hover">
<thead>
<tr>
<th>
<input type="checkbox" ng-model="allSelected" ng-change="selectItems(allSelected)" />
</th>
<th>
<a ui-sref="registries" ng-click="order('Name')">
Name
<span ng-show="sortType == 'Name' && !sortReverse" class="glyphicon glyphicon-chevron-down"></span>
<span ng-show="sortType == 'Name' && sortReverse" class="glyphicon glyphicon-chevron-up"></span>
</a>
</th>
<th>
<a ui-sref="registries" ng-click="order('URL')">
URL
<span ng-show="sortType == 'URL' && !sortReverse" class="glyphicon glyphicon-chevron-down"></span>
<span ng-show="sortType == 'URL' && sortReverse" class="glyphicon glyphicon-chevron-up"></span>
</a>
</th>
<th></th>
</tr>
</thead>
<tbody>
<tr dir-paginate="registry in (state.filteredRegistries = (registries | filter:state.filter | orderBy:sortType:sortReverse | itemsPerPage: state.pagination_count))" ng-class="{active: registry.Checked}">
<td><input type="checkbox" ng-model="registry.Checked" ng-change="selectItem(registry)" /></td>
<td>
<a ui-sref="registry({id: registry.Id})">{{ registry.Name }}</a>
<span ng-if="registry.Authentication" style="margin-left: 5px;">
<i class="fa fa-shield" aria-hidden="true" tooltip-placement="bottom" tooltip-class="portainer-tooltip" uib-tooltip="Authentication is enabled for this registry."></i>
</span>
</td>
<td>{{ registry.URL }}</td>
<td>
<span ng-if="applicationState.application.authentication">
<a ui-sref="registry.access({id: registry.Id})"><i class="fa fa-users" aria-hidden="true" style="margin-left: 7px;"></i> Manage access</a>
</span>
</td>
</tr>
<tr ng-if="!registries">
<td colspan="4" class="text-center text-muted">Loading...</td>
</tr>
<tr ng-if="registries.length == 0">
<td colspan="4" class="text-center text-muted">No registries available.</td>
</tr>
</tbody>
</table>
<div ng-if="registries" class="pull-left pagination-controls">
<dir-pagination-controls></dir-pagination-controls>
</div>
</div>
</rd-widget-body>
</rd-widget>
<registries-datatable
title="Registries" title-icon="fa-database"
dataset="registries" table-key="registries"
order-by="Name" show-text-filter="true"
access-management="applicationState.application.authentication"
remove-action="removeAction"
></registries-datatable>
</div>
</div>

View File

@ -1,14 +1,10 @@
angular.module('registries', [])
.controller('RegistriesController', ['$q', '$scope', '$state', 'RegistryService', 'DockerHubService', 'ModalService', 'Notifications', 'Pagination',
function ($q, $scope, $state, RegistryService, DockerHubService, ModalService, Notifications, Pagination) {
.controller('RegistriesController', ['$q', '$scope', '$state', 'RegistryService', 'DockerHubService', 'ModalService', 'Notifications', 'PaginationService',
function ($q, $scope, $state, RegistryService, DockerHubService, ModalService, Notifications, PaginationService) {
$scope.state = {
selectedItemCount: 0,
pagination_count: Pagination.getPaginationCount('registries'),
actionInProgress: false
};
$scope.sortType = 'Name';
$scope.sortReverse = true;
$scope.updateDockerHub = function() {
var dockerhub = $scope.dockerhub;
@ -25,56 +21,34 @@ function ($q, $scope, $state, RegistryService, DockerHubService, ModalService, N
});
};
$scope.order = function(sortType) {
$scope.sortReverse = ($scope.sortType === sortType) ? !$scope.sortReverse : false;
$scope.sortType = sortType;
};
$scope.changePaginationCount = function() {
Pagination.setPaginationCount('endpoints', $scope.state.pagination_count);
};
$scope.selectItems = function (allSelected) {
angular.forEach($scope.state.filteredRegistries, function (registry) {
if (registry.Checked !== allSelected) {
registry.Checked = allSelected;
$scope.selectItem(registry);
}
});
};
$scope.selectItem = function (item) {
if (item.Checked) {
$scope.state.selectedItemCount++;
} else {
$scope.state.selectedItemCount--;
}
};
$scope.removeAction = function() {
$scope.removeAction = function(selectedItems) {
ModalService.confirmDeletion(
'Do you want to remove the selected registries?',
function onConfirm(confirmed) {
if(!confirmed) { return; }
removeRegistries();
deleteSelectedRegistries(selectedItems);
}
);
};
function removeRegistries() {
var registries = $scope.registries;
angular.forEach(registries, function (registry) {
if (registry.Checked) {
RegistryService.deleteRegistry(registry.Id)
.then(function success(data) {
var index = registries.indexOf(registry);
registries.splice(index, 1);
Notifications.success('Registry deleted', registry.Name);
})
.catch(function error(err) {
Notifications.error('Failure', err, 'Unable to remove registry');
});
}
function deleteSelectedRegistries(selectedItems) {
var actionCount = selectedItems.length;
angular.forEach(selectedItems, function (registry) {
RegistryService.deleteRegistry(registry.Id)
.then(function success() {
Notifications.success('Registry successfully removed', registry.Name);
var index = $scope.registries.indexOf(registry);
$scope.registries.splice(index, 1);
})
.catch(function error(err) {
Notifications.error('Failure', err, 'Unable to remove registry');
})
.finally(function final() {
--actionCount;
if (actionCount === 0) {
$state.reload();
}
});
});
}

View File

@ -8,73 +8,13 @@
</rd-header>
<div class="row">
<div class="col-lg-12 col-md-12 col-xs-12">
<rd-widget>
<rd-widget-header icon="fa-user-secret" title="Secrets">
</rd-widget-header>
<rd-widget-taskbar classes="col-lg-12 col-md-12 col-xs-12">
<div class="pull-left">
<button type="button" class="btn btn-danger" ng-click="removeAction()" ng-disabled="!state.selectedItemCount"><i class="fa fa-trash space-right" aria-hidden="true"></i>Remove</button>
<a class="btn btn-primary" type="button" ui-sref="actions.create.secret"><i class="fa fa-plus space-right" aria-hidden="true"></i>Add secret</a>
</div>
<div class="pull-right">
<input type="text" id="filter" ng-model="state.filter" placeholder="Filter..." class="form-control input-sm" />
</div>
</rd-widget-taskbar>
<rd-widget-body classes="no-padding">
<div class="table-responsive">
<table class="table table-hover">
<thead>
<th>
<input type="checkbox" ng-model="allSelected" ng-change="selectItems(allSelected)" />
</th>
<th>
<a ng-click="order('Name')">
Name
<span ng-show="sortType == 'Name' && !sortReverse" class="glyphicon glyphicon-chevron-down"></span>
<span ng-show="sortType == 'Name' && sortReverse" class="glyphicon glyphicon-chevron-up"></span>
</a>
</th>
<th>
<a ng-click="order('CreatedAt')">
Created at
<span ng-show="sortType == 'CreatedAt' && !sortReverse" class="glyphicon glyphicon-chevron-down"></span>
<span ng-show="sortType == 'CreatedAt' && sortReverse" class="glyphicon glyphicon-chevron-up"></span>
</a>
</th>
<th ng-if="applicationState.application.authentication">
<a ng-click="order('ResourceControl.Ownership')">
Ownership
<span ng-show="sortType == 'ResourceControl.Ownership' && !sortReverse" class="glyphicon glyphicon-chevron-down"></span>
<span ng-show="sortType == 'ResourceControl.Ownership' && sortReverse" class="glyphicon glyphicon-chevron-up"></span>
</a>
</th>
</thead>
<tbody>
<tr dir-paginate="secret in (state.filteredSecrets = ( secrets | filter:state.filter | orderBy:sortType:sortReverse | itemsPerPage: pagination_count))">
<td><input type="checkbox" ng-model="secret.Checked" ng-change="selectItem(secret)"/></td>
<td><a ui-sref="secret({id: secret.Id})">{{ secret.Name }}</a></td>
<td>{{ secret.CreatedAt | getisodate }}</td>
<td ng-if="applicationState.application.authentication">
<span>
<i ng-class="secret.ResourceControl.Ownership | ownershipicon" aria-hidden="true"></i>
{{ secret.ResourceControl.Ownership ? secret.ResourceControl.Ownership : secret.ResourceControl.Ownership = 'public' }}
</span>
</td>
</tr>
<tr ng-if="!secrets">
<td colspan="4" class="text-center text-muted">Loading...</td>
</tr>
<tr ng-if="secrets.length == 0">
<td colspan="4" class="text-center text-muted">No secrets available.</td>
</tr>
</tbody>
</table>
<div ng-if="secrets" class="pull-left pagination-controls">
<dir-pagination-controls></dir-pagination-controls>
</div>
</div>
</rd-widget-body>
</rd-widget>
<div class="col-sm-12">
<secrets-datatable
title="Secrets" title-icon="fa-user-secret"
dataset="secrets" table-key="secrets"
order-by="Name" show-text-filter="true"
show-ownership-column="applicationState.application.authentication"
remove-action="removeAction"
></secrets-datatable>
</div>
</div>

View File

@ -1,47 +1,25 @@
angular.module('secrets', [])
.controller('SecretsController', ['$scope', '$state', 'SecretService', 'Notifications', 'Pagination',
function ($scope, $state, SecretService, Notifications, Pagination) {
$scope.state = {};
$scope.state.selectedItemCount = 0;
$scope.state.pagination_count = Pagination.getPaginationCount('secrets');
$scope.sortType = 'Name';
$scope.sortReverse = false;
.controller('SecretsController', ['$scope', '$state', 'SecretService', 'Notifications',
function ($scope, $state, SecretService, Notifications) {
$scope.order = function (sortType) {
$scope.sortReverse = ($scope.sortType === sortType) ? !$scope.sortReverse : false;
$scope.sortType = sortType;
};
$scope.selectItems = function (allSelected) {
angular.forEach($scope.state.filteredSecrets, function (secret) {
if (secret.Checked !== allSelected) {
secret.Checked = allSelected;
$scope.selectItem(secret);
}
});
};
$scope.selectItem = function (item) {
if (item.Checked) {
$scope.state.selectedItemCount++;
} else {
$scope.state.selectedItemCount--;
}
};
$scope.removeAction = function () {
angular.forEach($scope.secrets, function (secret) {
if (secret.Checked) {
SecretService.remove(secret.Id)
.then(function success() {
Notifications.success('Secret deleted', secret.Id);
var index = $scope.secrets.indexOf(secret);
$scope.secrets.splice(index, 1);
})
.catch(function error(err) {
Notifications.error('Failure', err, 'Unable to remove secret');
});
}
$scope.removeAction = function (selectedItems) {
var actionCount = selectedItems.length;
angular.forEach(selectedItems, function (secret) {
SecretService.remove(secret.Id)
.then(function success() {
Notifications.success('Secret successfully removed', secret.Name);
var index = $scope.secrets.indexOf(secret);
$scope.secrets.splice(index, 1);
})
.catch(function error(err) {
Notifications.error('Failure', err, 'Unable to remove secret');
})
.finally(function final() {
--actionCount;
if (actionCount === 0) {
$state.reload();
}
});
});
};

View File

@ -38,7 +38,7 @@
</div>
</td>
<td>
<por-slider model="service.ReservationNanoCPUs" floor="0" ceil="state.sliderMaxCpu" step="0.25" precision="2" ng-if="service && state.sliderMaxCpu" on-change="updateServiceAttribute(service, 'ReservationNanoCPUs')"></por-slider>
<slider model="service.ReservationNanoCPUs" floor="0" ceil="state.sliderMaxCpu" step="0.25" precision="2" ng-if="service && state.sliderMaxCpu" on-change="updateServiceAttribute(service, 'ReservationNanoCPUs')"></slider>
</td>
<td style="vertical-align : middle;">
<p class="small text-muted">
@ -53,7 +53,7 @@
</div>
</td>
<td>
<por-slider model="service.LimitNanoCPUs" floor="0" ceil="state.sliderMaxCpu" step="0.25" precision="2" ng-if="service && state.sliderMaxCpu" on-change="updateServiceAttribute(service, 'LimitNanoCPUs')"></por-slider>
<slider model="service.LimitNanoCPUs" floor="0" ceil="state.sliderMaxCpu" step="0.25" precision="2" ng-if="service && state.sliderMaxCpu" on-change="updateServiceAttribute(service, 'LimitNanoCPUs')"></slider>
</td>
<td style="vertical-align : middle;">
<p class="small text-muted">

View File

@ -25,11 +25,11 @@
<tr>
<td>Restart delay</td>
<td>
<input class="input-sm" type="number" ng-model="service.RestartDelay" ng-change="updateServiceAttribute(service, 'RestartDelay')" ng-disabled="isUpdating"/>
<input class="input-sm" type="text" ng-model="service.RestartDelay" ng-change="updateServiceAttribute(service, 'RestartDelay')" ng-pattern="/^([0-9]+)(h|m|s|ms|us|ns)$/i" ng-disabled="isUpdating"/>
</td>
<td>
<p class="small text-muted" style="margin-top: 10px;">
Delay between restart attempts. Time in seconds.
Delay between restart attempts expressed by a number followed by unit (ns|us|ms|s|m|h). Default value is 5s, 5 seconds.
</p>
</td>
</tr>
@ -40,18 +40,18 @@
</td>
<td>
<p class="small text-muted" style="margin-top: 10px;">
Maximum attempts to restart a given container before giving up (default value is 0, which is ignored).
Maximum attempts to restart a given task before giving up (default value is 0, which means unlimited).
</p>
</td>
</tr>
<tr>
<td>Restart window</td>
<td>
<input class="input-sm" type="number" ng-model="service.RestartWindow" ng-change="updateServiceAttribute(service, 'RestartWindow')" ng-disabled="isUpdating"/>
<input class="input-sm" type="text" ng-model="service.RestartWindow" ng-change="updateServiceAttribute(service, 'RestartWindow')" ng-pattern="/^([0-9]+)(h|m|s|ms|us|ns)$/i" ng-disabled="isUpdating"/>
</td>
<td>
<p class="small text-muted" style="margin-top: 10px;">
The time window used to evaluate the restart policy (default value is 0, which is unbounded). Time in seconds.
Time window to evaluate restart attempts expressed by a number followed by unit (ns|us|ms|s|m|h). Default value is 0 seconds, which is unbounded.
</p>
</td>
</tr>

View File

@ -1,71 +1,10 @@
<div ng-if="tasks.length > 0 && nodes" id="service-tasks">
<rd-widget>
<rd-widget-header icon="fa-tasks" title="Associated tasks">
<div class="pull-right">
Items per page:
<select ng-model="state.pagination_count" ng-change="changePaginationCount()">
<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>
</div>
</rd-widget-header>
<rd-widget-taskbar classes="col-lg-12">
<div class="pull-right">
<input type="text" id="filter" ng-model="state.filter" placeholder="Filter..." class="form-control input-sm" />
</div>
</rd-widget-taskbar>
<rd-widget-body classes="no-padding">
<table class="table">
<thead>
<tr>
<th>Id</th>
<th>
<a ng-click="order('Status.State')">
Status
<span ng-show="sortType == 'Status.State' && !sortReverse" class="glyphicon glyphicon-chevron-down"></span>
<span ng-show="sortType == 'Status.State' && sortReverse" class="glyphicon glyphicon-chevron-up"></span>
</a>
</th>
<th ng-if="service.Mode !== 'global'">
<a ng-click="order('Slot')">
Slot
<span ng-show="sortType == 'Slot' && !sortReverse" class="glyphicon glyphicon-chevron-down"></span>
<span ng-show="sortType == 'Slot' && sortReverse" class="glyphicon glyphicon-chevron-up"></span>
</a>
</th>
<th>
<a ng-click="order('NodeId')">
Node
<span ng-show="sortType == 'NodeId' && !sortReverse" class="glyphicon glyphicon-chevron-down"></span>
<span ng-show="sortType == 'NodeId' && sortReverse" class="glyphicon glyphicon-chevron-up"></span>
</a>
</th>
<th>
<a ng-click="order('Updated')">
Last update
<span ng-show="sortType == 'Updated' && !sortReverse" class="glyphicon glyphicon-chevron-down"></span>
<span ng-show="sortType == 'Updated' && sortReverse" class="glyphicon glyphicon-chevron-up"></span>
</a>
</th>
</tr>
</thead>
<tbody>
<tr dir-paginate="task in (filteredTasks = ( tasks | filter:state.filter | orderBy:sortType:sortReverse | itemsPerPage: state.pagination_count))">
<td><a ui-sref="task({ id: task.Id })" class="monospaced">{{ task.Id }}</a></td>
<td><span class="label label-{{ task.Status.State|taskstatusbadge }}">{{ task.Status.State }}</span></td>
<td ng-if="service.Mode !== 'global'">{{ task.Slot }}</td>
<td>{{ task.NodeId | tasknodename: nodes }}</td>
<td>{{ task.Updated | getisodate }}</td>
</tr>
</tbody>
</table>
<div ng-if="tasks" class="pagination-controls" >
<dir-pagination-controls></dir-pagination-controls>
</div>
</rd-widget-body>
</rd-widget>
<tasks-datatable
title="Tasks" title-icon="fa-tasks"
dataset="tasks" table-key="service-tasks"
order-by="Updated" reverse-order="true"
nodes="nodes"
show-text-filter="true"
show-slot-column="service.Mode !== 'global'"
></tasks-datatable>
</div>

View File

@ -19,11 +19,11 @@
<tr>
<td>Update Delay</td>
<td>
<input class="input-sm" type="number" ng-model="service.UpdateDelay" ng-change="updateServiceAttribute(service, 'UpdateDelay')" ng-disabled="isUpdating"/>
<input class="input-sm" type="text" ng-model="service.UpdateDelay" ng-change="updateServiceAttribute(service, 'UpdateDelay')" ng-pattern="/^([0-9]+)(h|m|s|ms|us|ns)$/i" ng-disabled="isUpdating"/>
</td>
<td>
<p class="small text-muted" style="margin-top: 10px;">
Amount of time between updates. Time in seconds.
Amount of time between updates expressed by a number followed by unit (ns|us|ms|s|m|h). Example: 1m.
</p>
</td>
</tr>

View File

@ -75,7 +75,7 @@
<tr ng-if="applicationState.endpoint.apiVersion >= 1.30">
<td colspan="2">
<div class="btn-group" role="group" aria-label="...">
<a class="btn btn-outline-secondary" type="button" ui-sref="servicelogs({id: service.Id})"><i class="fa fa-exclamation-circle space-right" aria-hidden="true"></i>Logs</a>
<a class="btn" type="button" ui-sref="servicelogs({id: service.Id})"><i class="fa fa-exclamation-circle space-right" aria-hidden="true"></i>Logs</a>
</div>
</td>
</tr>

View File

@ -1,12 +1,9 @@
angular.module('service', [])
.controller('ServiceController', ['$q', '$scope', '$transition$', '$state', '$location', '$timeout', '$anchorScroll', 'ServiceService', 'ConfigService', 'ConfigHelper', 'SecretService', 'ImageService', 'SecretHelper', 'Service', 'ServiceHelper', 'LabelHelper', 'TaskService', 'NodeService', 'Notifications', 'Pagination', 'ModalService',
function ($q, $scope, $transition$, $state, $location, $timeout, $anchorScroll, ServiceService, ConfigService, ConfigHelper, SecretService, ImageService, SecretHelper, Service, ServiceHelper, LabelHelper, TaskService, NodeService, Notifications, Pagination, ModalService) {
.controller('ServiceController', ['$q', '$scope', '$transition$', '$state', '$location', '$timeout', '$anchorScroll', 'ServiceService', 'ConfigService', 'ConfigHelper', 'SecretService', 'ImageService', 'SecretHelper', 'Service', 'ServiceHelper', 'LabelHelper', 'TaskService', 'NodeService', 'Notifications', 'ModalService',
function ($q, $scope, $transition$, $state, $location, $timeout, $anchorScroll, ServiceService, ConfigService, ConfigHelper, SecretService, ImageService, SecretHelper, Service, ServiceHelper, LabelHelper, TaskService, NodeService, Notifications, ModalService) {
$scope.state = {};
$scope.state.pagination_count = Pagination.getPaginationCount('service_tasks');
$scope.tasks = [];
$scope.sortType = 'Updated';
$scope.sortReverse = true;
$scope.availableImages = [];
$scope.lastVersion = 0;
@ -14,15 +11,6 @@ function ($q, $scope, $transition$, $state, $location, $timeout, $anchorScroll,
var originalService = {};
var previousServiceValues = [];
$scope.order = function (sortType) {
$scope.sortReverse = ($scope.sortType === sortType) ? !$scope.sortReverse : false;
$scope.sortType = sortType;
};
$scope.changePaginationCount = function() {
Pagination.setPaginationCount('service_tasks', $scope.state.pagination_count);
};
$scope.renameService = function renameService(service) {
updateServiceAttribute(service, 'Name', service.newServiceName || service.name);
service.EditName = false;
@ -244,16 +232,16 @@ function ($q, $scope, $transition$, $state, $location, $timeout, $anchorScroll,
config.UpdateConfig = {
Parallelism: service.UpdateParallelism,
Delay: service.UpdateDelay * 1000000000,
Delay: ServiceHelper.translateHumanDurationToNanos(service.UpdateDelay) || 0,
FailureAction: service.UpdateFailureAction,
Order: service.UpdateOrder
};
config.TaskTemplate.RestartPolicy = {
Condition: service.RestartCondition,
Delay: service.RestartDelay * 1000000000,
Delay: ServiceHelper.translateHumanDurationToNanos(service.RestartDelay) || 5000000000,
MaxAttempts: service.RestartMaxAttempts,
Window: service.RestartWindow * 1000000000
Window: ServiceHelper.translateHumanDurationToNanos(service.RestartWindow) || 0
};
if (service.Ports) {
@ -320,11 +308,11 @@ function ($q, $scope, $transition$, $state, $location, $timeout, $anchorScroll,
service.LimitMemoryBytes = service.LimitMemoryBytes / 1024 / 1024 || 0;
service.ReservationMemoryBytes = service.ReservationMemoryBytes / 1024 / 1024 || 0;
}
function transformDurations(service) {
service.RestartDelay = service.RestartDelay / 1000000000 || 5;
service.RestartWindow = service.RestartWindow / 1000000000 || 0;
service.UpdateDelay = service.UpdateDelay / 1000000000 || 0;
service.RestartDelay = ServiceHelper.translateNanosToHumanDuration(service.RestartDelay) || '5s';
service.RestartWindow = ServiceHelper.translateNanosToHumanDuration(service.RestartWindow) || '0s';
service.UpdateDelay = ServiceHelper.translateNanosToHumanDuration(service.UpdateDelay) || '0s';
}
function initView() {

View File

@ -8,133 +8,15 @@
</rd-header>
<div class="row">
<div class="col-lg-12 col-md-12 col-xs-12">
<rd-widget>
<rd-widget-header icon="fa-list-alt" title="Services">
<div class="pull-right">
Items per page:
<select ng-model="state.pagination_count" ng-change="changePaginationCount()">
<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>
</div>
</rd-widget-header>
<rd-widget-taskbar classes="col-lg-12 col-md-12 col-xs-12">
<div class="pull-left">
<button type="button" class="btn btn-danger" ng-click="removeAction()" ng-disabled="!state.selectedItemCount"><i class="fa fa-trash space-right" aria-hidden="true"></i>Remove</button>
<a class="btn btn-primary" type="button" ui-sref="actions.create.service"><i class="fa fa-plus space-right" aria-hidden="true"></i>Add service</a>
</div>
<div class="pull-right">
<input type="text" id="filter" ng-model="state.filter" placeholder="Filter..." class="form-control input-sm" />
</div>
</rd-widget-taskbar>
<rd-widget-body classes="no-padding">
<div class="table-responsive">
<table class="table table-hover">
<thead>
<th></th>
<th>
<a ng-click="order('Name')">
Name
<span ng-show="sortType == 'Name' && !sortReverse" class="glyphicon glyphicon-chevron-down"></span>
<span ng-show="sortType == 'Name' && sortReverse" class="glyphicon glyphicon-chevron-up"></span>
</a>
</th>
<th>
<a ng-click="order('StackName')">
Stack
<span ng-show="sortType == 'StackName' && !sortReverse" class="glyphicon glyphicon-chevron-down"></span>
<span ng-show="sortType == 'StackName' && sortReverse" class="glyphicon glyphicon-chevron-up"></span>
</a>
</th>
<th>
<a ng-click="order('Image')">
Image
<span ng-show="sortType == 'Image' && !sortReverse" class="glyphicon glyphicon-chevron-down"></span>
<span ng-show="sortType == 'Image' && sortReverse" class="glyphicon glyphicon-chevron-up"></span>
</a>
</th>
<th>
<a ng-click="order('Mode')">
Scheduling mode
<span ng-show="sortType == 'Mode' && !sortReverse" class="glyphicon glyphicon-chevron-down"></span>
<span ng-show="sortType == 'Mode' && sortReverse" class="glyphicon glyphicon-chevron-up"></span>
</a>
</th>
<th>
<a ng-click="order('Ports')">
Published Ports
<span ng-show="sortType == 'Ports' && !sortReverse" class="glyphicon glyphicon-chevron-down"></span>
<span ng-show="sortType == 'Ports' && sortReverse" class="glyphicon glyphicon-chevron-up"></span>
</a>
</th>
<th>
<a ng-click="order('UpdatedAt')">
Updated at
<span ng-show="sortType == 'UpdatedAt' && !sortReverse" class="glyphicon glyphicon-chevron-down"></span>
<span ng-show="sortType == 'UpdatedAt' && sortReverse" class="glyphicon glyphicon-chevron-up"></span>
</a>
</th>
<th ng-if="applicationState.application.authentication">
<a ng-click="order('ResourceControl.Ownership')">
Ownership
<span ng-show="sortType == 'ResourceControl.Ownership' && !sortReverse" class="glyphicon glyphicon-chevron-down"></span>
<span ng-show="sortType == 'ResourceControl.Ownership' && sortReverse" class="glyphicon glyphicon-chevron-up"></span>
</a>
</th>
</thead>
<tbody>
<tr dir-paginate="service in (state.filteredServices = ( services | filter:state.filter | orderBy:sortType:sortReverse | itemsPerPage: state.pagination_count))" ng-class="{active: service.Checked}">
<td><input type="checkbox" ng-model="service.Checked" ng-change="selectItem(service)"/></td>
<td><a ui-sref="service({id: service.Id})">{{ service.Name }}</a></td>
<td>{{ service.StackName ? service.StackName : '-' }}</td>
<td>{{ service.Image | hideshasum }}</td>
<td>
{{ service.Mode }}
<code data-toggle="tooltip" title="Replicas">{{ service.Running }}</code>
/
<code data-toggle="tooltip" title="Replicas">{{ service.Replicas }}</code>
<span ng-if="service.Mode === 'replicated' && !service.Scale">
<a class="interactive" ng-click="service.Scale = true; service.ReplicaCount = service.Replicas;"><i class="fa fa-arrows-v" aria-hidden="true"></i> Scale</a>
</span>
<span ng-if="service.Mode === 'replicated' && service.Scale">
<input class="input-sm" type="number" ng-model="service.Replicas" />
<a class="interactive" ng-click="service.Scale = false;"><i class="fa fa-times"></i></a>
<a class="interactive" ng-click="scaleService(service)"><i class="fa fa-check-square-o"></i></a>
</span>
</td>
<td>
<a ng-if="service.Ports && service.Ports.length > 0 && swarmManagerIP && p.PublishedPort" ng-repeat="p in service.Ports" class="image-tag" ng-href="http://{{swarmManagerIP}}:{{p.PublishedPort}}" target="_blank">
<i class="fa fa-external-link" aria-hidden="true"></i> {{ p.PublishedPort }}:{{ p.TargetPort }}
</a>
<span ng-if="!service.Ports || service.Ports.length === 0 || !swarmManagerIP" >-</span>
</td>
<td>
{{ service.UpdatedAt|getisodate }}
</td>
<td ng-if="applicationState.application.authentication">
<span>
<i ng-class="service.ResourceControl.Ownership | ownershipicon" aria-hidden="true"></i>
{{ service.ResourceControl.Ownership ? service.ResourceControl.Ownership : service.ResourceControl.Ownership = 'public' }}
</span>
</td>
</tr>
<tr ng-if="!services">
<td colspan="7" class="text-center text-muted">Loading...</td>
</tr>
<tr ng-if="services.length == 0">
<td colspan="7" class="text-center text-muted">No services available.</td>
</tr>
</tbody>
</table>
<div ng-if="services" class="pull-left pagination-controls">
<dir-pagination-controls></dir-pagination-controls>
</div>
</div>
</rd-widget-body>
</rd-widget>
<div class="col-sm-12">
<services-datatable
title="Services" title-icon="fa-list-alt"
dataset="services" table-key="services"
order-by="Name" show-text-filter="true"
show-ownership-column="applicationState.application.authentication"
remove-action="removeAction"
scale-action="scaleAction"
swarm-manager-ip="swarmManagerIP"
></services-datatable>
</div>
</div>

View File

@ -1,80 +1,50 @@
angular.module('services', [])
.controller('ServicesController', ['$q', '$scope', '$transition$', '$state', 'Service', 'ServiceService', 'ServiceHelper', 'Notifications', 'Pagination', 'Task', 'Node', 'NodeHelper', 'ModalService', 'ResourceControlService',
function ($q, $scope, $transition$, $state, Service, ServiceService, ServiceHelper, Notifications, Pagination, Task, Node, NodeHelper, ModalService, ResourceControlService) {
$scope.state = {};
$scope.state.selectedItemCount = 0;
$scope.state.pagination_count = Pagination.getPaginationCount('services');
$scope.sortType = 'Name';
$scope.sortReverse = false;
.controller('ServicesController', ['$q', '$scope', '$state', 'Service', 'ServiceService', 'ServiceHelper', 'Notifications', 'Task', 'Node', 'NodeHelper', 'ModalService',
function ($q, $scope, $state, Service, ServiceService, ServiceHelper, Notifications, Task, Node, NodeHelper, ModalService) {
$scope.changePaginationCount = function() {
Pagination.setPaginationCount('services', $scope.state.pagination_count);
};
$scope.order = function (sortType) {
$scope.sortReverse = ($scope.sortType === sortType) ? !$scope.sortReverse : false;
$scope.sortType = sortType;
};
$scope.selectItem = function (item) {
if (item.Checked) {
$scope.state.selectedItemCount++;
} else {
$scope.state.selectedItemCount--;
}
};
$scope.scaleService = function scaleService(service) {
$scope.scaleAction = function scaleService(service) {
var config = ServiceHelper.serviceToConfig(service.Model);
config.Mode.Replicated.Replicas = service.Replicas;
Service.update({ id: service.Id, version: service.Version }, config, function (data) {
ServiceService.update(service, config)
.then(function success(data) {
Notifications.success('Service successfully scaled', 'New replica count: ' + service.Replicas);
$state.reload();
}, function (e) {
})
.catch(function error(err) {
Notifications.error('Failure', err, 'Unable to scale service');
service.Scale = false;
service.Replicas = service.ReplicaCount;
Notifications.error('Failure', e, 'Unable to scale service');
});
};
$scope.removeAction = function() {
$scope.removeAction = function(selectedItems) {
ModalService.confirmDeletion(
'Do you want to remove the selected service(s)? All the containers associated to the selected service(s) will be removed too.',
function onConfirm(confirmed) {
if(!confirmed) { return; }
removeServices();
removeServices(selectedItems);
}
);
};
function removeServices() {
angular.forEach($scope.services, function (service) {
if (service.Checked) {
ServiceService.remove(service)
.then(function success(data) {
Notifications.success('Service successfully deleted');
var index = $scope.services.indexOf(service);
$scope.services.splice(index, 1);
})
.catch(function error(err) {
Notifications.error('Failure', err, 'Unable to remove service');
});
}
});
}
function mapUsersToServices(users) {
angular.forEach($scope.services, function (service) {
if (service.Metadata) {
var serviceRC = service.Metadata.ResourceControl;
if (serviceRC && serviceRC.OwnerId !== $scope.user.ID) {
angular.forEach(users, function (user) {
if (serviceRC.OwnerId === user.Id) {
service.Owner = user.Username;
}
});
function removeServices(services) {
var actionCount = services.length;
angular.forEach(services, function (service) {
ServiceService.remove(service)
.then(function success() {
Notifications.success('Service successfully removed', service.Name);
var index = $scope.services.indexOf(service);
$scope.services.splice(index, 1);
})
.catch(function error(err) {
Notifications.error('Failure', err, 'Unable to remove service');
})
.finally(function final() {
--actionCount;
if (actionCount === 0) {
$state.reload();
}
}
});
});
}

View File

@ -22,8 +22,8 @@ function ($q, $scope, Notifications, SettingsService, FileUploadService) {
$scope.LDAPSettings.SearchSettings.splice(index, 1);
};
$scope.LDAPConnectivityCheck = function() {
var settings = $scope.settings;
$scope.LDAPConnectivityCheck = function() {
var settings = $scope.settings;
var TLSCAFile = $scope.formValues.TLSCACert !== settings.LDAPSettings.TLSConfig.TLSCACert ? $scope.formValues.TLSCACert : null;
var uploadRequired = ($scope.LDAPSettings.TLSConfig.TLS || $scope.LDAPSettings.StartTLS) && !$scope.LDAPSettings.TLSConfig.TLSSkipVerify;
@ -32,6 +32,7 @@ function ($q, $scope, Notifications, SettingsService, FileUploadService) {
$scope.state.connectivityCheckInProgress = true;
$q.when(!uploadRequired || FileUploadService.uploadLDAPTLSFiles(TLSCAFile, null, null))
.then(function success(data) {
addLDAPDefaultPort(settings, $scope.LDAPSettings.TLSConfig.TLS);
return SettingsService.checkLDAPConnectivity(settings);
})
.then(function success(data) {
@ -60,6 +61,7 @@ function ($q, $scope, Notifications, SettingsService, FileUploadService) {
$scope.state.actionInProgress = true;
$q.when(!uploadRequired || FileUploadService.uploadLDAPTLSFiles(TLSCAFile, null, null))
.then(function success(data) {
addLDAPDefaultPort(settings, $scope.LDAPSettings.TLSConfig.TLS);
return SettingsService.update(settings);
})
.then(function success(data) {
@ -74,6 +76,13 @@ function ($q, $scope, Notifications, SettingsService, FileUploadService) {
});
};
// Add default port if :port is not defined in URL
function addLDAPDefaultPort(settings, tlsEnabled) {
if (settings.LDAPSettings.URL.indexOf(':') === -1) {
settings.LDAPSettings.URL += tlsEnabled ? ':636' : ':389';
}
}
function initView() {
SettingsService.settings()
.then(function success(data) {

View File

@ -48,7 +48,7 @@
<li class="sidebar-list" ng-if="applicationState.endpoint.apiVersion >= 1.25 && applicationState.endpoint.mode.provider === 'DOCKER_SWARM_MODE' && applicationState.endpoint.mode.role === 'MANAGER'">
<a ui-sref="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="(applicationState.endpoint.mode.provider === 'DOCKER_STANDALONE' || applicationState.endpoint.mode.provider === 'VMWARE_VIC') && isAdmin">
<li class="sidebar-list" ng-if="(applicationState.endpoint.mode.provider === 'DOCKER_STANDALONE' || applicationState.endpoint.mode.provider === 'VMWARE_VIC') && (!applicationState.application.authentication || isAdmin)">
<a ui-sref="events" ui-sref-active="active">Events <span class="menu-icon fa fa-history fa-fw"></span></a>
</li>
<li class="sidebar-list" ng-if="applicationState.endpoint.mode.provider === 'DOCKER_SWARM' || (applicationState.endpoint.mode.provider === 'DOCKER_SWARM_MODE' && applicationState.endpoint.mode.role === 'MANAGER')">

View File

@ -18,8 +18,31 @@
</por-access-control-panel>
<!-- !access-control-panel -->
<por-service-list services="services" nodes="nodes"></por-service-list>
<por-task-list tasks="tasks" nodes="nodes"></por-task-list>
<div class="row">
<div class="col-sm-12">
<stack-services-datatable
title="Services" title-icon="fa-list-alt"
dataset="services" table-key="stack-services"
order-by="Name"
nodes="nodes"
public-url="state.publicURL"
show-text-filter="true"
></stack-services-datatable>
</div>
</div>
<div class="row">
<div class="col-sm-12">
<tasks-datatable
title="Tasks" title-icon="fa-tasks"
dataset="tasks" table-key="stack-tasks"
order-by="Updated" reverse-order="true"
nodes="nodes"
show-text-filter="true"
show-slot-column="true"
></tasks-datatable>
</div>
</div>
<div class="row" ng-if="stackFileContent">
<div class="col-sm-12">

View File

@ -1,9 +1,10 @@
angular.module('stack', [])
.controller('StackController', ['$q', '$scope', '$state', '$stateParams', '$document', 'StackService', 'NodeService', 'ServiceService', 'TaskService', 'ServiceHelper', 'CodeMirrorService', 'Notifications', 'FormHelper',
function ($q, $scope, $state, $stateParams, $document, StackService, NodeService, ServiceService, TaskService, ServiceHelper, CodeMirrorService, Notifications, FormHelper) {
.controller('StackController', ['$q', '$scope', '$state', '$stateParams', '$document', 'StackService', 'NodeService', 'ServiceService', 'TaskService', 'ServiceHelper', 'CodeMirrorService', 'Notifications', 'FormHelper', 'EndpointProvider',
function ($q, $scope, $state, $stateParams, $document, StackService, NodeService, ServiceService, TaskService, ServiceHelper, CodeMirrorService, Notifications, FormHelper, EndpointProvider) {
$scope.state = {
actionInProgress: false
actionInProgress: false,
publicURL: EndpointProvider.endpointPublicURL()
};
$scope.deployStack = function () {

View File

@ -7,7 +7,7 @@
<rd-header-content>Stacks</rd-header-content>
</rd-header>
<div class="row" ng-if="state.DisplayInformationPanel">
<div class="row" ng-if="state.displayInformationPanel">
<div class="col-sm-12">
<rd-widget>
<rd-widget-header icon="fa-info-circle" title="Information"></rd-widget-header>
@ -27,7 +27,7 @@
Display external stacks
</label>
<label class="switch" style="margin-left: 20px;">
<input type="checkbox" ng-model="state.DisplayExternalStacks"><i></i>
<input type="checkbox" ng-model="state.displayExternalStacks"><i></i>
</label>
</div>
</div>
@ -39,81 +39,13 @@
<div class="row">
<div class="col-sm-12">
<rd-widget>
<rd-widget-header icon="fa-th-list" title="Stacks">
<div class="pull-right">
Items per page:
<select ng-model="state.pagination_count" ng-change="changePaginationCount()">
<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>
</div>
</rd-widget-header>
<rd-widget-taskbar classes="col-lg-12 col-md-12 col-xs-12">
<div class="pull-left">
<button type="button" class="btn btn-danger" ng-click="removeAction()" ng-disabled="!state.selectedItemCount"><i class="fa fa-trash space-right" aria-hidden="true"></i>Remove</button>
<a class="btn btn-primary" type="button" ui-sref="actions.create.stack"><i class="fa fa-plus space-right" aria-hidden="true"></i>Add stack</a>
</div>
<div class="pull-right">
<input type="text" id="filter" ng-model="state.filter" placeholder="Filter..." class="form-control input-sm" />
</div>
</rd-widget-taskbar>
<rd-widget-body classes="no-padding">
<div class="table-responsive">
<table class="table table-hover">
<thead>
<th>
<input type="checkbox" ng-model="allSelected" ng-change="selectItems(allSelected)" />
</th>
<th>
<a ng-click="order('Name')">
Name
<span ng-show="sortType == 'Name' && !sortReverse" class="glyphicon glyphicon-chevron-down"></span>
<span ng-show="sortType == 'Name' && sortReverse" class="glyphicon glyphicon-chevron-up"></span>
</a>
</th>
<th ng-if="applicationState.application.authentication">
<a ng-click="order('ResourceControl.Ownership')">
Ownership
<span ng-show="sortType == 'ResourceControl.Ownership' && !sortReverse" class="glyphicon glyphicon-chevron-down"></span>
<span ng-show="sortType == 'ResourceControl.Ownership' && sortReverse" class="glyphicon glyphicon-chevron-up"></span>
</a>
</th>
</thead>
<tbody>
<tr dir-paginate="stack in (state.filteredStacks = ( stacks | filter:state.filter | orderBy:sortType:sortReverse | itemsPerPage: state.pagination_count))" ng-if="state.DisplayExternalStacks || (!state.DisplayExternalStacks && !stack.External)" ng-class="{active: stacks.Checked}">
<td><input type="checkbox" ng-model="stack.Checked" ng-change="selectItem(stack)" ng-disabled="!stack.Id"/></td>
<td>
<span ng-if="stack.Id">
<a ui-sref="stack({ id: stack.Id })">{{ stack.Name }}</a>
</span>
<span ng-if="!stack.Id">
{{ stack.Name }} <i class="fa fa-exclamation-circle orange-icon" aria-hidden="true"></i>
</span>
</td>
<td ng-if="applicationState.application.authentication">
<span>
<i ng-class="stack.ResourceControl.Ownership | ownershipicon" aria-hidden="true"></i>
{{ stack.ResourceControl.Ownership ? stack.ResourceControl.Ownership : stack.ResourceControl.Ownership = 'public' }}
</span>
</td>
</tr>
<tr ng-if="!stacks">
<td colspan="3" class="text-center text-muted">Loading...</td>
</tr>
<tr ng-if="stacks.length === 0">
<td colspan="3" class="text-center text-muted">No stacks available.</td>
</tr>
</tbody>
</table>
<div ng-if="stacks" class="pull-left pagination-controls">
<dir-pagination-controls></dir-pagination-controls>
</div>
</div>
</rd-widget-body>
<rd-widget>
<stacks-datatable
title="Stacks" title-icon="fa-th-list"
dataset="stacks" table-key="stacks"
order-by="Name" show-text-filter="true"
remove-action="removeAction"
show-ownership-column="applicationState.application.authentication"
display-external-stacks="state.displayExternalStacks"
></stacks-datatable>
</div>
</div>

View File

@ -1,63 +1,39 @@
angular.module('stacks', [])
.controller('StacksController', ['$scope', 'Notifications', 'Pagination', 'StackService', 'ModalService',
function ($scope, Notifications, Pagination, StackService, ModalService) {
$scope.state = {};
$scope.state.selectedItemCount = 0;
$scope.state.pagination_count = Pagination.getPaginationCount('stacks');
$scope.sortType = 'Name';
$scope.sortReverse = false;
$scope.state.DisplayInformationPanel = false;
$scope.state.DisplayExternalStacks = true;
$scope.changePaginationCount = function() {
Pagination.setPaginationCount('stacks', $scope.state.pagination_count);
.controller('StacksController', ['$scope', '$state', 'Notifications', 'StackService', 'ModalService',
function ($scope, $state, Notifications, StackService, ModalService) {
$scope.state = {
displayInformationPanel: false,
displayExternalStacks: true
};
$scope.order = function (sortType) {
$scope.sortReverse = ($scope.sortType === sortType) ? !$scope.sortReverse : false;
$scope.sortType = sortType;
};
$scope.selectItems = function (allSelected) {
angular.forEach($scope.state.filteredStacks, function (stack) {
if (stack.Id && stack.Checked !== allSelected) {
stack.Checked = allSelected;
$scope.selectItem(stack);
}
});
};
$scope.selectItem = function (item) {
if (item.Checked) {
$scope.state.selectedItemCount++;
} else {
$scope.state.selectedItemCount--;
}
};
$scope.removeAction = function () {
$scope.removeAction = function(selectedItems) {
ModalService.confirmDeletion(
'Do you want to remove the selected stack(s)? Associated services will be removed as well.',
function onConfirm(confirmed) {
if(!confirmed) { return; }
deleteSelectedStacks();
deleteSelectedStacks(selectedItems);
}
);
};
function deleteSelectedStacks() {
angular.forEach($scope.stacks, function (stack) {
if (stack.Checked) {
StackService.remove(stack)
.then(function success() {
Notifications.success('Stack deleted', stack.Name);
var index = $scope.stacks.indexOf(stack);
$scope.stacks.splice(index, 1);
})
.catch(function error(err) {
Notifications.error('Failure', err, 'Unable to remove stack ' + stack.Name);
});
}
function deleteSelectedStacks(stacks) {
var actionCount = stacks.length;
angular.forEach(stacks, function (stack) {
StackService.remove(stack)
.then(function success() {
Notifications.success('Stack successfully removed', stack.Name);
var index = $scope.stacks.indexOf(stack);
$scope.stacks.splice(index, 1);
})
.catch(function error(err) {
Notifications.error('Failure', err, 'Unable to remove stack ' + stack.Name);
})
.finally(function final() {
--actionCount;
if (actionCount === 0) {
$state.reload();
}
});
});
}
@ -68,7 +44,7 @@ function ($scope, Notifications, Pagination, StackService, ModalService) {
for (var i = 0; i < stacks.length; i++) {
var stack = stacks[i];
if (stack.External) {
$scope.state.DisplayInformationPanel = true;
$scope.state.displayInformationPanel = true;
break;
}
}

View File

@ -8,7 +8,7 @@
</rd-header>
<div class="row">
<div class="col-lg-12 col-md-12 col-sm-12 col-xs-12">
<div class="col-sm-12">
<rd-widget>
<rd-widget-header icon="fa-object-group" title="Cluster status"></rd-widget-header>
<rd-widget-body classes="no-padding">
@ -60,7 +60,7 @@
<tr ng-if="applicationState.endpoint.mode.provider === 'DOCKER_SWARM_MODE'">
<td colspan="2">
<div class="btn-group" role="group" aria-label="...">
<a class="btn btn-outline-secondary" type="button" ui-sref="swarm.visualizer"><i class="fa fa-object-group space-right" aria-hidden="true"></i>Go to cluster visualizer</a>
<a ui-sref="swarm.visualizer"><i class="fa fa-object-group space-right" aria-hidden="true"></i>Go to cluster visualizer</a>
</div>
</td>
</tr>
@ -72,173 +72,20 @@
</div>
<div class="row">
<div class="col-lg-12 col-md-12 col-sm-12 col-xs-12" ng-if="applicationState.endpoint.mode.provider === 'DOCKER_SWARM'">
<rd-widget>
<rd-widget-header icon="fa-hdd-o" title="Node status">
<div class="pull-right">
Items per page:
<select ng-model="state.pagination_count" ng-change="changePaginationCount()">
<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>
</div>
</rd-widget-header>
<rd-widget-body classes="no-padding">
<table class="table table-striped">
<thead>
<tr>
<th>
<a ui-sref="swarm" ng-click="order('name')">
Name
<span ng-show="sortType == 'name' && !sortReverse" class="glyphicon glyphicon-chevron-down"></span>
<span ng-show="sortType == 'name' && sortReverse" class="glyphicon glyphicon-chevron-up"></span>
</a>
</th>
<th>
<a ui-sref="swarm" ng-click="order('cpu')">
CPU
<span ng-show="sortType == 'cpu' && !sortReverse" class="glyphicon glyphicon-chevron-down"></span>
<span ng-show="sortType == 'cpu' && sortReverse" class="glyphicon glyphicon-chevron-up"></span>
</a>
</th>
<th>
<a ui-sref="swarm" ng-click="order('memory')">
Memory
<span ng-show="sortType == 'memory' && !sortReverse" class="glyphicon glyphicon-chevron-down"></span>
<span ng-show="sortType == 'memory' && sortReverse" class="glyphicon glyphicon-chevron-up"></span>
</a>
</th>
<th>
<a ui-sref="swarm" ng-click="order('ip')">
IP
<span ng-show="sortType == 'ip' && !sortReverse" class="glyphicon glyphicon-chevron-down"></span>
<span ng-show="sortType == 'ip' && sortReverse" class="glyphicon glyphicon-chevron-up"></span>
</a>
</th>
<th>
<a ui-sref="swarm" ng-click="order('version')">
Engine
<span ng-show="sortType == 'version' && !sortReverse" class="glyphicon glyphicon-chevron-down"></span>
<span ng-show="sortType == 'version' && sortReverse" class="glyphicon glyphicon-chevron-up"></span>
</a>
</th>
<th>
<a ui-sref="swarm" ng-click="order('status')">
Status
<span ng-show="sortType == 'status' && !sortReverse" class="glyphicon glyphicon-chevron-down"></span>
<span ng-show="sortType == 'status' && sortReverse" class="glyphicon glyphicon-chevron-up"></span>
</a>
</th>
</tr>
</thead>
<tbody>
<tr dir-paginate="node in (state.filteredNodes = (swarm.Status | filter:state.filter | orderBy:sortType:sortReverse | itemsPerPage: state.pagination_count))">
<td>{{ node.name }}</td>
<td>{{ node.cpu }}</td>
<td>{{ node.memory }}</td>
<td>{{ node.ip }}</td>
<td>{{ node.version }}</td>
<td><span class="label label-{{ node.status|nodestatusbadge }}">{{ node.status }}</span></td>
</tr>
</tbody>
</table>
<div class="pagination-controls">
<dir-pagination-controls></dir-pagination-controls>
</div>
</rd-widget-body>
</rd-widget>
<div class="col-sm-12" ng-if="applicationState.endpoint.mode.provider === 'DOCKER_SWARM'">
<nodes-ss-datatable
title="Nodes" title-icon="fa-hdd-o"
dataset="swarm.Status" table-key="nodes"
order-by="name" show-text-filter="true"
></nodes-ss-datatable>
</div>
<div class="col-lg-12 col-md-12 col-sm-12 col-xs-12" ng-if="applicationState.endpoint.mode.provider === 'DOCKER_SWARM_MODE'">
<rd-widget>
<rd-widget-header icon="fa-hdd-o" title="Node status">
<div class="pull-right">
Items per page:
<select ng-model="state.pagination_count" ng-change="changePaginationCount()">
<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>
</div>
</rd-widget-header>
<rd-widget-body classes="no-padding">
<table class="table table-striped">
<thead>
<tr>
<th>
<a ui-sref="swarm" ng-click="order('Hostname')">
Name
<span ng-show="sortType == 'Hostname' && !sortReverse" class="glyphicon glyphicon-chevron-down"></span>
<span ng-show="sortType == 'Hostname' && sortReverse" class="glyphicon glyphicon-chevron-up"></span>
</a>
</th>
<th>
<a ui-sref="swarm" ng-click="order('Role')">
Role
<span ng-show="sortType == 'Role' && !sortReverse" class="glyphicon glyphicon-chevron-down"></span>
<span ng-show="sortType == 'Role' && sortReverse" class="glyphicon glyphicon-chevron-up"></span>
</a>
</th>
<th>
<a ui-sref="swarm" ng-click="order('CPUs')">
CPU
<span ng-show="sortType == 'CPUs' && !sortReverse" class="glyphicon glyphicon-chevron-down"></span>
<span ng-show="sortType == 'CPUs' && sortReverse" class="glyphicon glyphicon-chevron-up"></span>
</a>
</th>
<th>
<a ui-sref="swarm" ng-click="order('Memory')">
Memory
<span ng-show="sortType == 'Memory' && !sortReverse" class="glyphicon glyphicon-chevron-down"></span>
<span ng-show="sortType == 'Memory' && sortReverse" class="glyphicon glyphicon-chevron-up"></span>
</a>
</th>
<th>
<a ui-sref="swarm" ng-click="order('EngineVersion')">
Engine
<span ng-show="sortType == 'EngineVersion' && !sortReverse" class="glyphicon glyphicon-chevron-down"></span>
<span ng-show="sortType == 'EngineVersion' && sortReverse" class="glyphicon glyphicon-chevron-up"></span>
</a>
</th>
<th ng-if="applicationState.endpoint.apiVersion >= 1.25">
<a ui-sref="swarm" ng-click="order('Addr')">
IP Address
<span ng-show="sortType == 'Addr' && !sortReverse" class="glyphicon glyphicon-chevron-down"></span>
<span ng-show="sortType == 'Addr' && sortReverse" class="glyphicon glyphicon-chevron-up"></span>
</a>
</th>
<th>
<a ui-sref="swarm" ng-click="order('Status')">
Status
<span ng-show="sortType == 'Status' && !sortReverse" class="glyphicon glyphicon-chevron-down"></span>
<span ng-show="sortType == 'Status' && sortReverse" class="glyphicon glyphicon-chevron-up"></span>
</a>
</th>
</tr>
</thead>
<tbody>
<tr dir-paginate="node in (state.filteredNodes = (nodes | filter:state.filter | orderBy:sortType:sortReverse | itemsPerPage: state.pagination_count))">
<td>
<a ui-sref="node({id: node.Id})" ng-if="!applicationState.application.authentication || isAdmin">{{ node.Hostname }}</a>
<span ng-if="applicationState.application.authentication && !isAdmin">{{ node.Hostname }}</span>
</td>
<td>{{ node.Role }}</td>
<td>{{ node.CPUs / 1000000000 }}</td>
<td>{{ node.Memory|humansize }}</td>
<td>{{ node.EngineVersion }}</td>
<td ng-if="applicationState.endpoint.apiVersion >= 1.25">{{ node.Addr }}</td>
<td><span class="label label-{{ node.Status|nodestatusbadge }}">{{ node.Status }}</span></td>
</tr>
</tbody>
</table>
<div class="pagination-controls">
<dir-pagination-controls></dir-pagination-controls>
</div>
</rd-widget-body>
</rd-widget>
<div class="col-sm-12" ng-if="applicationState.endpoint.mode.provider === 'DOCKER_SWARM_MODE'">
<nodes-datatable
title="Nodes" title-icon="fa-hdd-o"
dataset="nodes" table-key="nodes"
order-by="Hostname" show-text-filter="true"
show-ip-address-column="applicationState.endpoint.apiVersion >= 1.25"
access-to-node-details="!applicationState.application.authentication || isAdmin"
></nodes-datatable>
</div>
</div>

View File

@ -1,25 +1,12 @@
angular.module('swarm', [])
.controller('SwarmController', ['$q', '$scope', 'SystemService', 'NodeService', 'Pagination', 'Notifications', 'StateManager', 'Authentication',
function ($q, $scope, SystemService, NodeService, Pagination, Notifications, StateManager, Authentication) {
$scope.state = {};
$scope.state.pagination_count = Pagination.getPaginationCount('swarm_nodes');
$scope.sortType = 'Spec.Role';
$scope.sortReverse = false;
.controller('SwarmController', ['$q', '$scope', 'SystemService', 'NodeService', 'Notifications', 'StateManager', 'Authentication',
function ($q, $scope, SystemService, NodeService, Notifications, StateManager, Authentication) {
$scope.info = {};
$scope.docker = {};
$scope.swarm = {};
$scope.totalCPU = 0;
$scope.totalMemory = 0;
$scope.order = function(sortType) {
$scope.sortReverse = ($scope.sortType === sortType) ? !$scope.sortReverse : false;
$scope.sortType = sortType;
};
$scope.changePaginationCount = function() {
Pagination.setPaginationCount('swarm_nodes', $scope.state.pagination_count);
};
function extractSwarmInfo(info) {
// Swarm info is available in SystemStatus object
var systemStatus = info.SystemStatus;

View File

@ -73,6 +73,8 @@
</div>
</div>
<div>{{ node.Role }}</div>
<div>CPU: {{ node.CPUs / 1000000000 }}</div>
<div>Memory: {{ node.Memory|humansize: 2 }}</div>
</div>
<div class="tasks">
<div class="task task_{{ task.Status.State | visualizerTask }}" ng-repeat="task in node.Tasks | filter: (state.DisplayOnlyRunningTasks || '') && { Status: { State: 'running' } }">
@ -80,6 +82,8 @@
<div>Image: {{ task.Spec.ContainerSpec.Image | hideshasum }}</div>
<div>Status: {{ task.Status.State }}</div>
<div>Update: {{ task.Updated | getisodate }}</div>
<div ng-if="task.Spec.Resources.Limits.MemoryBytes">Memory limit: {{ task.Spec.Resources.Limits.MemoryBytes | humansize: 2:2 }}</div>
<div ng-if="task.Spec.Resources.Limits.NanoCPUs">CPU limit: {{ task.Spec.Resources.Limits.NanoCPUs / 1000000000 }}</div>
</div>
</div>
</div>

View File

@ -76,7 +76,7 @@
<td>
{{ user.Username }}
<span style="margin-left: 5px;">
<a class="btn-outline-secondary" ng-click="addUser(user)"><i class="fa fa-plus-circle space-right" aria-hidden="true"></i>Add</a>
<a ng-click="addUser(user)"><i class="fa fa-plus-circle space-right" aria-hidden="true"></i>Add</a>
</span>
</td>
</tr>
@ -143,7 +143,7 @@
<td>
{{ user.Username }}
<span style="margin-left: 5px;" ng-if="isAdmin || user.TeamRole === 'Member'">
<a class="btn-outline-secondary" ng-click="removeUser(user)"><i class="fa fa-minus-circle space-right" aria-hidden="true"></i>Remove</a>
<a ng-click="removeUser(user)"><i class="fa fa-minus-circle space-right" aria-hidden="true"></i>Remove</a>
</span>
</td>
<td>
@ -151,8 +151,8 @@
<i ng-if="user.TeamRole === 'Member'" class="fa fa-user" aria-hidden="true" style="margin-right: 2px;"></i>
{{ user.TeamRole }}
<span style="margin-left: 5px;" ng-if="isAdmin">
<a class="btn-outline-secondary" style="margin-left: 5px;" ng-click="promoteToLeader(user)" ng-if="user.TeamRole === 'Member'"><i class="fa fa-user-plus space-right" aria-hidden="true"></i>Leader</a>
<a class="btn-outline-secondary" style="margin-left: 5px;" ng-click="demoteToMember(user)" ng-if="user.TeamRole === 'Leader'"><i class="fa fa-user-times space-right" aria-hidden="true"></i>Member</a>
<a style="margin-left: 5px;" ng-click="promoteToLeader(user)" ng-if="user.TeamRole === 'Member'"><i class="fa fa-user-plus space-right" aria-hidden="true"></i>Leader</a>
<a style="margin-left: 5px;" ng-click="demoteToMember(user)" ng-if="user.TeamRole === 'Leader'"><i class="fa fa-user-times space-right" aria-hidden="true"></i>Member</a>
</span>
</td>
</tr>

View File

@ -1,10 +1,10 @@
angular.module('team', [])
.controller('TeamController', ['$q', '$scope', '$state', '$transition$', 'TeamService', 'UserService', 'TeamMembershipService', 'ModalService', 'Notifications', 'Pagination', 'Authentication',
function ($q, $scope, $state, $transition$, TeamService, UserService, TeamMembershipService, ModalService, Notifications, Pagination, Authentication) {
.controller('TeamController', ['$q', '$scope', '$state', '$transition$', 'TeamService', 'UserService', 'TeamMembershipService', 'ModalService', 'Notifications', 'PaginationService', 'Authentication',
function ($q, $scope, $state, $transition$, TeamService, UserService, TeamMembershipService, ModalService, Notifications, PaginationService, Authentication) {
$scope.state = {
pagination_count_users: Pagination.getPaginationCount('team_available_users'),
pagination_count_members: Pagination.getPaginationCount('team_members')
pagination_count_users: PaginationService.getPaginationLimit('team_available_users'),
pagination_count_members: PaginationService.getPaginationLimit('team_members')
};
$scope.sortTypeUsers = 'Username';
$scope.sortReverseUsers = true;
@ -18,7 +18,7 @@ function ($q, $scope, $state, $transition$, TeamService, UserService, TeamMember
};
$scope.changePaginationCountUsers = function() {
Pagination.setPaginationCount('team_available_users', $scope.state.pagination_count_users);
PaginationService.setPaginationLimit('team_available_users', $scope.state.pagination_count_users);
};
$scope.sortTypeGroupMembers = 'TeamRole';
@ -30,7 +30,7 @@ function ($q, $scope, $state, $transition$, TeamService, UserService, TeamMember
};
$scope.changePaginationCountGroupMembers = function() {
Pagination.setPaginationCount('team_members', $scope.state.pagination_count_members);
PaginationService.setPaginationLimit('team_members', $scope.state.pagination_count_members);
};
$scope.deleteTeam = function() {

View File

@ -65,67 +65,12 @@
</div>
<div class="row">
<div class="col-lg-12 col-md-12 col-xs-12">
<rd-widget>
<rd-widget-header icon="fa-users" title="Teams">
<div class="pull-right">
Items per page:
<select ng-model="state.pagination_count" ng-change="changePaginationCount()">
<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>
</div>
</rd-widget-header>
<rd-widget-taskbar classes="col-lg-12">
<div class="pull-left" ng-if="isAdmin">
<button type="button" class="btn btn-danger" ng-click="removeAction()" ng-disabled="!state.selectedItemCount"><i class="fa fa-trash space-right" aria-hidden="true"></i>Remove</button>
</div>
<div class="pull-right">
<input type="text" id="filter" ng-model="state.filter" placeholder="Filter..." class="form-control input-sm" />
</div>
</rd-widget-taskbar>
<rd-widget-body classes="no-padding">
<div class="table-responsive">
<table class="table table-hover">
<thead>
<tr>
<th ng-if="isAdmin">
<input type="checkbox" ng-model="allSelected" ng-change="selectItems(allSelected)" />
</th>
<th>
<a ng-click="order('Name')">
Name
<span ng-show="sortType == 'Name' && !sortReverse" class="glyphicon glyphicon-chevron-down"></span>
<span ng-show="sortType == 'Name' && sortReverse" class="glyphicon glyphicon-chevron-up"></span>
</a>
</th>
<th></th>
</tr>
</thead>
<tbody>
<tr dir-paginate="team in (state.filteredTeams = (teams | filter:state.filter | orderBy:sortType:sortReverse | itemsPerPage: state.pagination_count))" ng-class="{active: team.Checked}">
<td ng-if="isAdmin"><input type="checkbox" ng-model="team.Checked" ng-change="selectItem(team)" /></td>
<td>{{ team.Name }}</td>
<td>
<a ui-sref="team({id: team.Id})"><i class="fa fa-pencil-square-o" aria-hidden="true"></i> Edit</a>
</td>
</tr>
<tr ng-if="!teams">
<td colspan="3" class="text-center text-muted">Loading...</td>
</tr>
<tr ng-if="teams.length == 0">
<td colspan="3" class="text-center text-muted">No teams available.</td>
</tr>
</tbody>
</table>
<div ng-if="teams" class="pull-left pagination-controls">
<dir-pagination-controls></dir-pagination-controls>
</div>
</div>
</rd-widget-body>
</rd-widget>
<div class="col-sm-12">
<teams-datatable
title="Teams" title-icon="fa-users"
dataset="teams" table-key="teams"
order-by="Name" show-text-filter="true"
remove-action="removeAction"
></teams-datatable>
</div>
</div>

View File

@ -1,47 +1,17 @@
angular.module('teams', [])
.controller('TeamsController', ['$q', '$scope', '$state', 'TeamService', 'UserService', 'TeamMembershipService', 'ModalService', 'Notifications', 'Pagination', 'Authentication',
function ($q, $scope, $state, TeamService, UserService, TeamMembershipService, ModalService, Notifications, Pagination, Authentication) {
.controller('TeamsController', ['$q', '$scope', '$state', 'TeamService', 'UserService', 'ModalService', 'Notifications', 'Authentication',
function ($q, $scope, $state, TeamService, UserService, ModalService, Notifications, Authentication) {
$scope.state = {
userGroupGroupCreationError: '',
selectedItemCount: 0,
validName: false,
pagination_count: Pagination.getPaginationCount('teams'),
actionInProgress: false
};
$scope.sortType = 'Name';
$scope.sortReverse = false;
$scope.formValues = {
Name: '',
Leaders: []
};
$scope.order = function(sortType) {
$scope.sortReverse = ($scope.sortType === sortType) ? !$scope.sortReverse : false;
$scope.sortType = sortType;
};
$scope.changePaginationCount = function() {
Pagination.setPaginationCount('teams', $scope.state.pagination_count);
};
$scope.selectItems = function (allSelected) {
angular.forEach($scope.state.filteredTeams, function (team) {
if (team.Checked !== allSelected) {
team.Checked = allSelected;
$scope.selectItem(team);
}
});
};
$scope.selectItem = function (item) {
if (item.Checked) {
$scope.state.selectedItemCount++;
} else {
$scope.state.selectedItemCount--;
}
};
$scope.checkNameValidity = function() {
var valid = true;
for (var i = 0; i < $scope.teams.length; i++) {
@ -76,32 +46,37 @@ function ($q, $scope, $state, TeamService, UserService, TeamMembershipService, M
});
};
function deleteSelectedTeams() {
angular.forEach($scope.teams, function (team) {
if (team.Checked) {
TeamService.deleteTeam(team.Id)
.then(function success(data) {
var index = $scope.teams.indexOf(team);
$scope.teams.splice(index, 1);
Notifications.success('Team successfully deleted', team.Name);
})
.catch(function error(err) {
Notifications.error('Failure', err, 'Unable to remove team');
});
}
});
}
$scope.removeAction = function () {
$scope.removeAction = function (selectedItems) {
ModalService.confirmDeletion(
'Do you want to delete the selected team(s)? Users in the team(s) will not be deleted.',
function onConfirm(confirmed) {
if(!confirmed) { return; }
deleteSelectedTeams();
deleteSelectedTeams(selectedItems);
}
);
};
function deleteSelectedTeams(selectedItems) {
var actionCount = selectedItems.length;
angular.forEach(selectedItems, function (team) {
TeamService.deleteTeam(team.Id)
.then(function success() {
Notifications.success('Team successfully removed', team.Name);
var index = $scope.teams.indexOf(team);
$scope.teams.splice(index, 1);
})
.catch(function error(err) {
Notifications.error('Failure', err, 'Unable to remove team');
})
.finally(function final() {
--actionCount;
if (actionCount === 0) {
$state.reload();
}
});
});
}
function initView() {
var userDetails = Authentication.getUserDetails();
var isAdmin = userDetails.role === 1 ? true: false;

View File

@ -1,6 +1,6 @@
angular.module('templates', [])
.controller('TemplatesController', ['$scope', '$q', '$state', '$transition$', '$anchorScroll', '$filter', 'ContainerService', 'ContainerHelper', 'ImageService', 'NetworkService', 'TemplateService', 'TemplateHelper', 'VolumeService', 'Notifications', 'Pagination', 'ResourceControlService', 'Authentication', 'FormValidator', 'SettingsService', 'StackService',
function ($scope, $q, $state, $transition$, $anchorScroll, $filter, ContainerService, ContainerHelper, ImageService, NetworkService, TemplateService, TemplateHelper, VolumeService, Notifications, Pagination, ResourceControlService, Authentication, FormValidator, SettingsService, StackService) {
.controller('TemplatesController', ['$scope', '$q', '$state', '$transition$', '$anchorScroll', '$filter', 'ContainerService', 'ContainerHelper', 'ImageService', 'NetworkService', 'TemplateService', 'TemplateHelper', 'VolumeService', 'Notifications', 'PaginationService', 'ResourceControlService', 'Authentication', 'FormValidator', 'SettingsService', 'StackService',
function ($scope, $q, $state, $transition$, $anchorScroll, $filter, ContainerService, ContainerHelper, ImageService, NetworkService, TemplateService, TemplateHelper, VolumeService, Notifications, PaginationService, ResourceControlService, Authentication, FormValidator, SettingsService, StackService) {
$scope.state = {
selectedTemplate: null,
showAdvancedOptions: false,

View File

@ -113,91 +113,12 @@
</div>
<div class="row">
<div class="col-lg-12 col-md-12 col-xs-12">
<rd-widget>
<rd-widget-header icon="fa-user" title="Users">
<div class="pull-right">
Items per page:
<select ng-model="state.pagination_count" ng-change="changePaginationCount()">
<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>
</div>
</rd-widget-header>
<rd-widget-taskbar classes="col-lg-12">
<div class="pull-left" ng-if="isAdmin">
<button type="button" class="btn btn-danger" ng-click="removeAction()" ng-disabled="!state.selectedItemCount"><i class="fa fa-trash space-right" aria-hidden="true"></i>Remove</button>
</div>
<div class="pull-right">
<input type="text" id="filter" ng-model="state.filter" placeholder="Filter..." class="form-control input-sm" />
</div>
</rd-widget-taskbar>
<rd-widget-body classes="no-padding">
<div class="table-responsive">
<table class="table table-hover">
<thead>
<tr>
<th ng-if="isAdmin">
<input type="checkbox" ng-model="allSelected" ng-change="selectItems(allSelected)" />
</th>
<th>
<a ng-click="order('Username')">
Name
<span ng-show="sortType == 'Username' && !sortReverse" class="glyphicon glyphicon-chevron-down"></span>
<span ng-show="sortType == 'Username' && sortReverse" class="glyphicon glyphicon-chevron-up"></span>
</a>
</th>
<th>
<a ng-click="order('RoleName')">
Role
<span ng-show="sortType == 'RoleName' && !sortReverse" class="glyphicon glyphicon-chevron-down"></span>
<span ng-show="sortType == 'RoleName' && sortReverse" class="glyphicon glyphicon-chevron-up"></span>
</a>
</th>
<th>
<a ng-click="order('AuthenticationMethod')">
Authentication
<span ng-show="sortType == 'AuthenticationMethod' && !sortReverse" class="glyphicon glyphicon-chevron-down"></span>
<span ng-show="sortType == 'AuthenticationMethod' && sortReverse" class="glyphicon glyphicon-chevron-up"></span>
</a>
</th>
<th ng-if="isAdmin"></th>
</tr>
</thead>
<tbody>
<tr dir-paginate="user in (state.filteredUsers = (users | filter:state.filter | orderBy:sortType:sortReverse | itemsPerPage: state.pagination_count))" ng-class="{active: user.Checked}">
<td ng-if="isAdmin"><input type="checkbox" ng-model="user.Checked" ng-change="selectItem(user)" /></td>
<td>{{ user.Username }}</td>
<td>
<i ng-if="user.Role === 1" class="fa fa-user-circle-o" aria-hidden="true" style="margin-right: 2px;"></i>
<i ng-if="user.Role !== 1 && !user.isTeamLeader" class="fa fa-user" aria-hidden="true" style="margin-right: 2px;"></i>
<i ng-if="user.isTeamLeader" class="fa fa-user-plus" aria-hidden="true" style="margin-right: 2px;"></i>
{{ user.RoleName }}
</td>
<td>
<span ng-if="AuthenticationMethod === 1 || user.Id === 1">Internal</span>
<span ng-if="AuthenticationMethod === 2 && user.Id !== 1">LDAP</span>
</td>
<td ng-if="isAdmin">
<a ui-sref="user({id: user.Id})"><i class="fa fa-pencil-square-o" aria-hidden="true"></i> Edit</a>
</td>
</tr>
<tr ng-if="!users">
<td colspan="4" class="text-center text-muted">Loading...</td>
</tr>
<tr ng-if="users.length == 0">
<td colspan="4" class="text-center text-muted">No users available.</td>
</tr>
</tbody>
</table>
<div ng-if="users" class="pull-left pagination-controls">
<dir-pagination-controls></dir-pagination-controls>
</div>
</div>
</rd-widget-body>
</rd-widget>
<div class="col-sm-12">
<users-datatable
title="Users" title-icon="fa-user"
dataset="users" table-key="users"
order-by="Username" show-text-filter="true"
remove-action="removeAction"
></users-datatable>
</div>
</div>

View File

@ -1,15 +1,11 @@
angular.module('users', [])
.controller('UsersController', ['$q', '$scope', '$state', '$sanitize', 'UserService', 'TeamService', 'TeamMembershipService', 'ModalService', 'Notifications', 'Pagination', 'Authentication', 'SettingsService',
function ($q, $scope, $state, $sanitize, UserService, TeamService, TeamMembershipService, ModalService, Notifications, Pagination, Authentication, SettingsService) {
.controller('UsersController', ['$q', '$scope', '$state', '$sanitize', 'UserService', 'TeamService', 'TeamMembershipService', 'ModalService', 'Notifications', 'Authentication', 'SettingsService',
function ($q, $scope, $state, $sanitize, UserService, TeamService, TeamMembershipService, ModalService, Notifications, Authentication, SettingsService) {
$scope.state = {
userCreationError: '',
selectedItemCount: 0,
validUsername: false,
pagination_count: Pagination.getPaginationCount('users'),
actionInProgress: false
};
$scope.sortType = 'RoleName';
$scope.sortReverse = false;
$scope.formValues = {
Username: '',
@ -19,32 +15,6 @@ function ($q, $scope, $state, $sanitize, UserService, TeamService, TeamMembershi
Teams: []
};
$scope.order = function(sortType) {
$scope.sortReverse = ($scope.sortType === sortType) ? !$scope.sortReverse : false;
$scope.sortType = sortType;
};
$scope.changePaginationCount = function() {
Pagination.setPaginationCount('endpoints', $scope.state.pagination_count);
};
$scope.selectItems = function (allSelected) {
angular.forEach($scope.state.filteredUsers, function (user) {
if (user.Checked !== allSelected) {
user.Checked = allSelected;
$scope.selectItem(user);
}
});
};
$scope.selectItem = function (item) {
if (item.Checked) {
$scope.state.selectedItemCount++;
} else {
$scope.state.selectedItemCount--;
}
};
$scope.checkUsernameValidity = function() {
var valid = true;
for (var i = 0; i < $scope.users.length; i++) {
@ -80,28 +50,33 @@ function ($q, $scope, $state, $sanitize, UserService, TeamService, TeamMembershi
});
};
function deleteSelectedUsers() {
angular.forEach($scope.users, function (user) {
if (user.Checked) {
UserService.deleteUser(user.Id)
.then(function success(data) {
var index = $scope.users.indexOf(user);
$scope.users.splice(index, 1);
Notifications.success('User successfully deleted', user.Username);
})
.catch(function error(err) {
Notifications.error('Failure', err, 'Unable to remove user');
});
}
function deleteSelectedUsers(selectedItems) {
var actionCount = selectedItems.length;
angular.forEach(selectedItems, function (user) {
UserService.deleteUser(user.Id)
.then(function success() {
Notifications.success('User successfully removed', user.Username);
var index = $scope.users.indexOf(user);
$scope.users.splice(index, 1);
})
.catch(function error(err) {
Notifications.error('Failure', err, 'Unable to remove user');
})
.finally(function final() {
--actionCount;
if (actionCount === 0) {
$state.reload();
}
});
});
}
$scope.removeAction = function () {
$scope.removeAction = function (selectedItems) {
ModalService.confirmDeletion(
'Do you want to remove the selected users? They will not be able to login into Portainer anymore.',
function onConfirm(confirmed) {
if(!confirmed) { return; }
deleteSelectedUsers();
deleteSelectedUsers(selectedItems);
}
);
};

View File

@ -7,114 +7,14 @@
<rd-header-content>Volumes</rd-header-content>
</rd-header>
<div class="col-lg-12">
<rd-widget>
<rd-widget-header icon="fa-cubes" title="Volumes">
<div class="pull-right">
Items per page:
<select ng-model="state.pagination_count" ng-change="changePaginationCount()">
<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>
</div>
</rd-widget-header>
<rd-widget-taskbar classes="col-lg-12">
<div class="pull-left">
<button type="button" class="btn btn-danger" ng-click="removeAction()" ng-disabled="!state.selectedItemCount"><i class="fa fa-trash space-right" aria-hidden="true"></i>Remove</button>
<a class="btn btn-primary" type="button" ui-sref="actions.create.volume"><i class="fa fa-plus space-right" aria-hidden="true"></i>Add volume</a>
</div>
<div class="pull-right">
<input type="text" id="filter" ng-model="state.filter" placeholder="Filter..." class="form-control input-sm" />
</div>
<span class="btn-group btn-group-sm pull-right" style="margin-right: 20px;">
<label class="btn btn-primary" ng-model="state.danglingVolumesOnly" uib-btn-radio="undefined">
All
</label>
<label class="btn btn-primary" ng-model="state.danglingVolumesOnly" uib-btn-radio="false">
Attached
</label>
<label class="btn btn-primary" ng-model="state.danglingVolumesOnly" uib-btn-radio="true">
Unused
</label>
</span>
</rd-widget-taskbar>
<rd-widget-body classes="no-padding">
<div class="table-responsive">
<table class="table table-hover">
<thead>
<tr>
<th>
<input type="checkbox" ng-model="allSelected" ng-change="selectItems(allSelected)" />
</th>
<th>
<a ui-sref="volumes" ng-click="order('Id')">
Name
<span ng-show="sortType == 'Id' && !sortReverse" class="glyphicon glyphicon-chevron-down"></span>
<span ng-show="sortType == 'Id' && sortReverse" class="glyphicon glyphicon-chevron-up"></span>
</a>
</th>
<th>
<a ui-sref="volumes" ng-click="order('StackName')">
Stack
<span ng-show="sortType == 'StackName' && !sortReverse" class="glyphicon glyphicon-chevron-down"></span>
<span ng-show="sortType == 'StackName' && sortReverse" class="glyphicon glyphicon-chevron-up"></span>
</a>
</th>
<th>
<a ui-sref="volumes" ng-click="order('Driver')">
Driver
<span ng-show="sortType == 'Driver' && !sortReverse" class="glyphicon glyphicon-chevron-down"></span>
<span ng-show="sortType == 'Driver' && sortReverse" class="glyphicon glyphicon-chevron-up"></span>
</a>
</th>
<th>
<a ui-sref="volumes" ng-click="order('Mountpoint')">
Mount point
<span ng-show="sortType == 'Mountpoint' && !sortReverse" class="glyphicon glyphicon-chevron-down"></span>
<span ng-show="sortType == 'Mountpoint' && sortReverse" class="glyphicon glyphicon-chevron-up"></span>
</a>
</th>
<th ng-if="applicationState.application.authentication">
<a ui-sref="volumes" ng-click="order('ResourceControl.Ownership')">
Ownership
<span ng-show="sortType == 'ResourceControl.Ownership' && !sortReverse" class="glyphicon glyphicon-chevron-down"></span>
<span ng-show="sortType == 'ResourceControl.Ownership' && sortReverse" class="glyphicon glyphicon-chevron-up"></span>
</a>
</th>
</tr>
</thead>
<tbody>
<tr dir-paginate="volume in (state.filteredVolumes = (volumes | filter:{dangling: state.danglingVolumesOnly} | filter:state.filter | orderBy:sortType:sortReverse | itemsPerPage: state.pagination_count))" ng-class="{active: volume.Checked}">
<td><input type="checkbox" ng-model="volume.Checked" ng-change="selectItem(volume)"/></td>
<td>
<a ui-sref="volume({id: volume.Id})" class="monospaced">{{ volume.Id|truncate:25 }}</a>
<span style="margin-left: 10px;" class="label label-warning image-tag" ng-if="volume.dangling">Unused</span></td>
</td>
<td>{{ volume.StackName ? volume.StackName : '-' }}</td>
<td>{{ volume.Driver }}</td>
<td>{{ volume.Mountpoint | truncatelr }}</td>
<td ng-if="applicationState.application.authentication">
<span>
<i ng-class="volume.ResourceControl.Ownership | ownershipicon" aria-hidden="true"></i>
{{ volume.ResourceControl.Ownership ? volume.ResourceControl.Ownership : volume.ResourceControl.Ownership = 'public' }}
</span>
</td>
</tr>
<tr ng-if="!volumes">
<td colspan="5" class="text-center text-muted">Loading...</td>
</tr>
<tr ng-if="volumes.length === 0 || state.filteredVolumes.length === 0">
<td colspan="5" class="text-center text-muted">No volumes available.</td>
</tr>
</tbody>
</table>
<div ng-if="volumes" class="pull-left pagination-controls">
<dir-pagination-controls></dir-pagination-controls>
</div>
</div>
</rd-widget-body>
</rd-widget>
<div class="row">
<div class="col-sm-12">
<volumes-datatable
title="Volumes" title-icon="fa-cubes"
dataset="volumes" table-key="volumes"
order-by="Id" show-text-filter="true"
remove-action="removeAction"
show-ownership-column="applicationState.application.authentication"
></volumes-datatable>
</div>
</div>

View File

@ -1,67 +1,32 @@
angular.module('volumes', [])
.controller('VolumesController', ['$q', '$scope', 'VolumeService', 'Notifications', 'Pagination',
function ($q, $scope, VolumeService, Notifications, Pagination) {
$scope.state = {};
$scope.state.pagination_count = Pagination.getPaginationCount('volumes');
$scope.state.selectedItemCount = 0;
$scope.sortType = 'Id';
$scope.sortReverse = false;
.controller('VolumesController', ['$q', '$scope', '$state', 'VolumeService', 'Notifications',
function ($q, $scope, $state, VolumeService, Notifications) {
$scope.changePaginationCount = function() {
Pagination.setPaginationCount('volumes', $scope.state.pagination_count);
};
$scope.order = function(sortType) {
$scope.sortReverse = ($scope.sortType === sortType) ? !$scope.sortReverse : false;
$scope.sortType = sortType;
};
$scope.selectItems = function (allSelected) {
angular.forEach($scope.state.filteredVolumes, function (volume) {
if (volume.Checked !== allSelected) {
volume.Checked = allSelected;
$scope.selectItem(volume);
}
});
};
$scope.selectItem = function (item) {
if (item.Checked) {
$scope.state.selectedItemCount++;
} else {
$scope.state.selectedItemCount--;
}
};
$scope.removeAction = function () {
angular.forEach($scope.volumes, function (volume) {
if (volume.Checked) {
VolumeService.remove(volume)
.then(function success() {
Notifications.success('Volume deleted', volume.Id);
var index = $scope.volumes.indexOf(volume);
$scope.volumes.splice(index, 1);
})
.catch(function error(err) {
Notifications.error('Failure', err, 'Unable to remove volume');
});
}
$scope.removeAction = function (selectedItems) {
var actionCount = selectedItems.length;
angular.forEach(selectedItems, function (volume) {
VolumeService.remove(volume)
.then(function success() {
Notifications.success('Volume successfully removed', volume.Id);
var index = $scope.volumes.indexOf(volume);
$scope.volumes.splice(index, 1);
})
.catch(function error(err) {
Notifications.error('Failure', err, 'Unable to remove volume');
})
.finally(function final() {
--actionCount;
if (actionCount === 0) {
$state.reload();
}
});
});
};
function initView() {
$q.all({
attached: VolumeService.volumes({
filters: {
'dangling': ['false']
}
}),
dangling: VolumeService.volumes({
filters: {
'dangling': ['true']
}
})
attached: VolumeService.volumes({ filters: { 'dangling': ['false'] } }),
dangling: VolumeService.volumes({ filters: { 'dangling': ['true'] } })
})
.then(function success(data) {
$scope.volumes = data.attached.map(function(volume) {

View File

@ -67,7 +67,7 @@
&& !$ctrl.state.editOwnership
&& ($ctrl.isAdmin || $ctrl.state.canEditOwnership)">
<td colspan="2">
<a class="btn-outline-secondary" ng-click="$ctrl.state.editOwnership = true"><i class="fa fa-edit space-right" aria-hidden="true"></i>Change ownership</a>
<a ng-click="$ctrl.state.editOwnership = true"><i class="fa fa-edit space-right" aria-hidden="true"></i>Change ownership</a>
</td>
</tr>
<!-- !edit-ownership -->

View File

@ -1,11 +1,11 @@
angular.module('portainer')
.controller('porAccessManagementController', ['AccessService', 'Pagination', 'Notifications',
function (AccessService, Pagination, Notifications) {
.controller('porAccessManagementController', ['AccessService', 'PaginationService', 'Notifications',
function (AccessService, PaginationService, Notifications) {
var ctrl = this;
ctrl.state = {
pagination_count_accesses: Pagination.getPaginationCount('access_management_accesses'),
pagination_count_authorizedAccesses: Pagination.getPaginationCount('access_management_AuthorizedAccesses'),
pagination_count_accesses: PaginationService.getPaginationLimit('access_management_accesses'),
pagination_count_authorizedAccesses: PaginationService.getPaginationLimit('access_management_AuthorizedAccesses'),
sortAccessesBy: 'Type',
sortAccessesReverse: false,
sortAuthorizedAccessesBy: 'Type',
@ -23,11 +23,11 @@ function (AccessService, Pagination, Notifications) {
};
ctrl.changePaginationCountAuthorizedAccesses = function() {
Pagination.setPaginationCount('access_management_AuthorizedAccesses', ctrl.state.pagination_count_authorizedAccesses);
PaginationService.setPaginationLimit('access_management_AuthorizedAccesses', ctrl.state.pagination_count_authorizedAccesses);
};
ctrl.changePaginationCountAccesses = function() {
Pagination.setPaginationCount('access_management_accesses', ctrl.state.pagination_count_accesses);
PaginationService.setPaginationLimit('access_management_accesses', ctrl.state.pagination_count_accesses);
};
function dispatchUserAndTeamIDs(accesses, users, teams) {

View File

@ -1,8 +0,0 @@
angular.module('portainer').component('porServiceList', {
templateUrl: 'app/directives/serviceList/porServiceList.html',
controller: 'porServiceListController',
bindings: {
'services': '<',
'nodes': '<'
}
});

View File

@ -1,98 +0,0 @@
<div class="row">
<div class="col-sm-12">
<rd-widget>
<rd-widget-header icon="fa-list-alt" title="Associated services">
<div class="pull-right">
Items per page:
<select ng-model="$ctrl.state.pagination_count" ng-change="$ctrl.changePaginationCount()">
<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>
</div>
</rd-widget-header>
<rd-widget-taskbar classes="col-sm-12">
<div class="pull-right">
<input type="text" id="filter" ng-model="$ctrl.state.filter" placeholder="Filter..." class="form-control input-sm" />
</div>
</rd-widget-taskbar>
<rd-widget-body classes="no-padding">
<div class="table-responsive">
<table class="table table-hover">
<thead>
<tr>
<th>
<a ng-click="$ctrl.order('Name')">
Name
<span ng-show="$ctrl.sortType === 'Name' && !$ctrl.sortReverse" class="glyphicon glyphicon-chevron-down"></span>
<span ng-show="$ctrl.sortType === 'Name' && $ctrl.sortReverse" class="glyphicon glyphicon-chevron-up"></span>
</a>
</th>
<th>
<a ng-click="$ctrl.order('Image')">
Image
<span ng-show="$ctrl.sortType === 'Image' && !$ctrl.sortReverse" class="glyphicon glyphicon-chevron-down"></span>
<span ng-show="$ctrl.sortType === 'Image' && $ctrl.sortReverse" class="glyphicon glyphicon-chevron-up"></span>
</a>
</th>
<th>
<a ng-click="$ctrl.order('Mode')">
Scheduling mode
<span ng-show="$ctrl.sortType === 'Mode' && !$ctrl.sortReverse" class="glyphicon glyphicon-chevron-down"></span>
<span ng-show="$ctrl.sortType === 'Mode' && $ctrl.sortReverse" class="glyphicon glyphicon-chevron-up"></span>
</a>
</th>
<th>
<a ng-click="$ctrl.order('Ports')">
Published Ports
<span ng-show="$ctrl.sortType === 'Ports' && !$ctrl.sortReverse" class="glyphicon glyphicon-chevron-down"></span>
<span ng-show="$ctrl.sortType === 'Ports' && $ctrl.sortReverse" class="glyphicon glyphicon-chevron-up"></span>
</a>
</th>
<th>
<a ng-click="$ctrl.order('UpdatedAt')">
Updated at
<span ng-show="$ctrl.sortType === 'UpdatedAt' && !$ctrl.sortReverse" class="glyphicon glyphicon-chevron-down"></span>
<span ng-show="$ctrl.sortType === 'UpdatedAt' && $ctrl.sortReverse" class="glyphicon glyphicon-chevron-up"></span>
</a>
</th>
</tr>
</thead>
<tbody>
<tr dir-paginate="service in $ctrl.services | filter:$ctrl.state.filter | orderBy:$ctrl.sortType:$ctrl.sortReverse | itemsPerPage:$ctrl.state.pagination_count" pagination-id="services_list">
<td><a ui-sref="service({id: service.Id})">{{ service.Name }}</a></td>
<td>{{ service.Image | hideshasum }}</td>
<td>
{{ service.Mode }}
<code data-toggle="tooltip" title="Replicas">{{ service.Tasks | runningtaskscount }}</code>
/
<code data-toggle="tooltip" title="Replicas">{{ service.Mode === 'replicated' ? service.Replicas : ($ctrl.nodes | availablenodecount) }}</code>
</td>
<td>
<a ng-if="service.Ports && service.Ports.length > 0" ng-repeat="p in service.Ports" class="image-tag" ng-href="http://{{$ctrl.state.publicURL}}:{{p.PublishedPort}}" target="_blank">
<i class="fa fa-external-link" aria-hidden="true"></i> {{ p.PublishedPort }}:{{ p.TargetPort }}
</a>
<span ng-if="!service.Ports || service.Ports.length === 0" >-</span>
</td>
<td>
{{ service.UpdatedAt|getisodate }}
</td>
</tr>
<tr ng-if="!$ctrl.services">
<td colspan="5" class="text-center text-muted">Loading...</td>
</tr>
<tr ng-if="($ctrl.services | filter:$ctrl.state.filter | orderBy:$ctrl.sortType:$ctrl.sortReverse | itemsPerPage: $ctrl.state.pagination_count).length === 0">
<td colspan="5" class="text-center text-muted">No services available.</td>
</tr>
</tbody>
</table>
<div ng-if="$ctrl.services" class="pull-left pagination-controls">
<dir-pagination-controls pagination-id="services_list"></dir-pagination-controls >
</div>
</div>
</rd-widget-body>
<rd-widget>
</div>
</div>

View File

@ -1,21 +0,0 @@
angular.module('portainer')
.controller('porServiceListController', ['EndpointProvider', 'Pagination',
function (EndpointProvider, Pagination) {
var ctrl = this;
ctrl.state = {
pagination_count: Pagination.getPaginationCount('services_list'),
publicURL: EndpointProvider.endpointPublicURL()
};
ctrl.sortType = 'Name';
ctrl.sortReverse = false;
ctrl.order = function(sortType) {
ctrl.sortReverse = (ctrl.sortType === sortType) ? !ctrl.sortReverse : false;
ctrl.sortType = sortType;
};
ctrl.changePaginationCount = function() {
Pagination.setPaginationCount('services_list', ctrl.state.pagination_count);
};
}]);

View File

@ -1,12 +0,0 @@
angular.module('portainer').component('porSlider', {
templateUrl: 'app/directives/slider/porSlider.html',
controller: 'porSliderController',
bindings: {
model: '=',
onChange: '&',
floor: '<',
ceil: '<',
step: '<',
precision: '<'
}
});

View File

@ -1,8 +0,0 @@
angular.module('portainer').component('porTaskList', {
templateUrl: 'app/directives/taskList/porTaskList.html',
controller: 'porTaskListController',
bindings: {
'tasks': '<',
'nodes': '<'
}
});

View File

@ -1,78 +0,0 @@
<div class="row">
<div class="col-sm-12">
<rd-widget>
<rd-widget-header icon="fa-tasks" title="Associated tasks">
<div class="pull-right">
Items per page:
<select ng-model="$ctrl.state.pagination_count" ng-change="$ctrl.changePaginationCount()">
<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>
</div>
</rd-widget-header>
<rd-widget-taskbar classes="col-sm-12">
<div class="pull-right">
<input type="text" id="filter" ng-model="$ctrl.state.filter" placeholder="Filter..." class="form-control input-sm" />
</div>
</rd-widget-taskbar>
<rd-widget-body classes="no-padding">
<table class="table">
<thead>
<tr>
<th>Id</th>
<th>
<a ng-click="$ctrl.order('Status.State')">
Status
<span ng-show="$ctrl.sortType === 'Status.State' && !$ctrl.sortReverse" class="glyphicon glyphicon-chevron-down"></span>
<span ng-show="$ctrl.sortType === 'Status.State' && $ctrl.sortReverse" class="glyphicon glyphicon-chevron-up"></span>
</a>
</th>
<th ng-if="service.Mode !== 'global'">
<a ng-click="$ctrl.order('Slot')">
Slot
<span ng-show="$ctrl.sortType === 'Slot' && !$ctrl.sortReverse" class="glyphicon glyphicon-chevron-down"></span>
<span ng-show="$ctrl.sortType === 'Slot' && $ctrl.sortReverse" class="glyphicon glyphicon-chevron-up"></span>
</a>
</th>
<th>
<a ng-click="$ctrl.order('NodeId')">
Node
<span ng-show="$ctrl.sortType === 'NodeId' && !$ctrl.sortReverse" class="glyphicon glyphicon-chevron-down"></span>
<span ng-show="$ctrl.sortType === 'NodeId' && $ctrl.sortReverse" class="glyphicon glyphicon-chevron-up"></span>
</a>
</th>
<th>
<a ng-click="$ctrl.order('Updated')">
Last update
<span ng-show="$ctrl.sortType === 'Updated' && !$ctrl.sortReverse" class="glyphicon glyphicon-chevron-down"></span>
<span ng-show="$ctrl.sortType === 'Updated' && $ctrl.sortReverse" class="glyphicon glyphicon-chevron-up"></span>
</a>
</th>
</tr>
</thead>
<tbody>
<tr dir-paginate="task in $ctrl.tasks | filter:$ctrl.state.filter | orderBy:$ctrl.sortType:$ctrl.sortReverse | itemsPerPage:$ctrl.state.pagination_count">
<td><a ui-sref="task({ id: task.Id })">{{ task.Id }}</a></td>
<td><span class="label label-{{ task.Status.State | taskstatusbadge }}">{{ task.Status.State }}</span></td>
<td>{{ task.Slot ? task.Slot : '-' }}</td>
<td>{{ task.NodeId | tasknodename: $ctrl.nodes }}</td>
<td>{{ task.Updated | getisodate }}</td>
</tr>
<tr ng-if="!$ctrl.tasks">
<td colspan="5" class="text-center text-muted">Loading...</td>
</tr>
<tr ng-if="($ctrl.tasks | filter:$ctrl.state.filter | orderBy:$ctrl.sortType:$ctrl.sortReverse | itemsPerPage: $ctrl.state.pagination_count).length === 0">
<td colspan="5" class="text-center text-muted">No tasks available.</td>
</tr>
</tbody>
</table>
<div class="pagination-controls">
<dir-pagination-controls></dir-pagination-controls>
</div>
</rd-widget-body>
</rd-widget>
</div>
</div>

View File

@ -1,19 +0,0 @@
angular.module('portainer')
.controller('porTaskListController', ['Pagination',
function (Pagination) {
var ctrl = this;
ctrl.state = {
pagination_count: Pagination.getPaginationCount('tasks_list')
};
ctrl.sortType = 'Updated';
ctrl.sortReverse = true;
ctrl.order = function(sortType) {
ctrl.sortReverse = (ctrl.sortType === sortType) ? !ctrl.sortReverse : false;
ctrl.sortType = sortType;
};
ctrl.changePaginationCount = function() {
Pagination.setPaginationCount('tasks_list', ctrl.state.pagination_count);
};
}]);

View File

@ -0,0 +1 @@
angular.module('ui', []);

View File

@ -0,0 +1,65 @@
angular.module('ui')
.controller('GenericDatatableController', ['PaginationService', 'DatatableService',
function (PaginationService, DatatableService) {
this.state = {
selectAll: false,
orderBy: this.orderBy,
paginatedItemLimit: PaginationService.getPaginationLimit(this.tableKey),
displayTextFilter: false,
selectedItemCount: 0,
selectedItems: []
};
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.$onInit = function() {
setDefaults(this);
var storedOrder = DatatableService.getDataTableOrder(this.tableKey);
if (storedOrder !== null) {
this.state.reverseOrder = storedOrder.reverse;
this.state.orderBy = storedOrder.orderBy;
}
};
function setDefaults(ctrl) {
ctrl.showTextFilter = ctrl.showTextFilter ? ctrl.showTextFilter : false;
ctrl.state.reverseOrder = ctrl.reverseOrder ? ctrl.reverseOrder : false;
}
}]);

View File

@ -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="actions.create.config">
<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="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>

View File

@ -0,0 +1,15 @@
angular.module('ui').component('configsDatatable', {
templateUrl: 'app/directives/ui/datatables/configs-datatable/configsDatatable.html',
controller: 'GenericDatatableController',
bindings: {
title: '@',
titleIcon: '@',
dataset: '<',
tableKey: '@',
orderBy: '@',
reverseOrder: '<',
showTextFilter: '<',
showOwnershipColumn: '<',
removeAction: '<'
}
});

View File

@ -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="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>

View File

@ -0,0 +1,16 @@
angular.module('ui').component('containerNetworksDatatable', {
templateUrl: 'app/directives/ui/datatables/container-networks-datatable/containerNetworksDatatable.html',
controller: 'GenericDatatableController',
bindings: {
title: '@',
titleIcon: '@',
dataset: '<',
tableKey: '@',
container: '<',
availableNetworks: '<',
joinNetworkAction: '<',
joinNetworkActionInProgress: '<',
leaveNetworkActionInProgress: '<',
leaveNetworkAction: '<'
}
});

View File

@ -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>

View File

@ -0,0 +1,14 @@
angular.module('ui').component('containerProcessesDatatable', {
templateUrl: 'app/directives/ui/datatables/container-processes-datatable/containerProcessesDatatable.html',
controller: 'GenericDatatableController',
bindings: {
title: '@',
titleIcon: '@',
dataset: '=',
headerset: '<',
tableKey: '@',
orderBy: '@',
reverseOrder: '<',
showTextFilter: '<'
}
});

View File

@ -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="actions.create.container">
<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="container({ id: item.Id })" ng-if="!$ctrl.swarmContainers">{{ item | containername | truncate: $ctrl.settings.containerNameTruncateSize }}</a>
<a ui-sref="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="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="containerlogs({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="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="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="image({ id: item.Image })">{{ item.Image | hideshasum }}</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>

View File

@ -0,0 +1,24 @@
angular.module('ui').component('containersDatatable', {
templateUrl: 'app/directives/ui/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: '<'
}
});

View File

@ -0,0 +1,171 @@
angular.module('ui')
.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 && item.Status === 'paused') {
this.state.noPausedItemsSelected = false;
} else if (item.Checked && (item.Status === 'stopped' || item.Status === 'created')) {
this.state.noStoppedItemsSelected = false;
} else if (item.Checked && item.Status === 'running') {
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.$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.filters = storedFilters;
}
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;
}
}]);

View File

@ -0,0 +1,244 @@
.datatable .toolBar {
background-color: #f6f6f6;
color: #767676;
overflow: auto;
padding: 10px;
}
.datatable .actionBar {
color: #767676;
padding: 10px;
}
.datatable .toolBar .toolBarTitle {
float: left;
margin: 5px 0 0 10px;
}
.datatable .toolBar .settings {
float: right;
margin: 5px 10px 0 0;
}
.datatable .toolBar .setting {
cursor: pointer;
margin-right: 5px;
}
.datatable .toolBar .setting-active {
color: #337ab7;
}
.datatable .searchBar {
border-top: 1px solid #f6f6f6;
padding: 10px;
}
.datatable .searchInput {
background: none;
border: none;
width: 95%;
}
.datatable .searchIcon {
color: #767676;
margin-right: 5px;
}
.datatable .searchInput:active, .datatable .searchInput:focus {
outline: none;
}
.datatable .pagination-controls {
margin-right: 15px;
}
.datatable .table {
margin-bottom: 0;
}
.datatable .footer {
background-color: #f6f6f6;
color: #767676;
overflow: auto;
}
.datatable .footer .infoBar {
float: left;
font-size: 12px;
margin: 15px 0 0 10px;
}
.datatable .footer .paginationControls {
float: right;
margin: 10px 15px 5px 0;
}
.datatable .footer .paginationControls .limitSelector {
font-size: 12px;
margin-right: 15px;
}
.datatable .footer .paginationControls .pagination {
margin: 0;
}
.datatable .pagination > li > a, .pagination > li > span {
float: none;
}
.tableMenu {
color: #767676;
padding: 10px;
}
.tableMenu .menuHeader {
font-size: 16px;
}
.tableMenu .menuContent {
border-bottom: 1px solid #777;
font-size: 12px;
margin: 10px 0;
max-height: 140px;
overflow-y: auto;
}
.tableMenu .menuContent .md-radio:first-child {
margin: 0;
}
.datatable .table-filters thead tr > th {
height: 32px;
vertical-align: middle;
}
.widget .widget-body table thead th .table-filter {
color: #767676;
cursor: pointer;
font-size: 12px !important;
}
.widget .widget-body table thead th .filter-active {
color: #f0ad4e;
font-size: 12px !important;
}
.datatable .filterbar > th {
border: none;
font-size: 12px !important;
padding: 0;
}
.md-checkbox {
margin: 1px 0;
position: relative;
}
.md-checkbox label {
cursor: pointer;
}
.md-checkbox label:before, .md-checkbox label:after {
content: "";
left: 0;
position: absolute;
top: 0;
}
.md-checkbox label:before {
background: #fff;
border: 2px solid black;
border: 2px solid rgba(0, 0, 0, 0.54);
border-radius: 2px;
cursor: pointer;
height: 16px;
transition: background .3s;
width: 16px;
}
.md-checkbox input[type="checkbox"] {
margin-right: 5px;
opacity: 0;
outline: 0;
}
.md-checkbox input[type="checkbox"]:checked + label:before {
background: #337ab7;
border: none;
}
.md-checkbox input[type="checkbox"]:disabled + label:before {
background: #ececec;
border: 2px solid #ddd;
cursor: auto;
}
.md-checkbox input[type="checkbox"]:checked + label:after {
border: 2px solid #fff;
border-right-style: none;
border-top-style: none;
height: 4px;
left: 4px;
top: 5px;
transform: rotate(-45deg);
width: 9px;
}
.md-radio {
margin: 6px 0;
}
.md-radio .md-radio-inline {
display: inline-block;
}
.md-radio input[type="radio"] {
display: none;
}
.md-radio input[type="radio"]:checked + label:before {
animation: ripple 0.2s linear forwards;
border-color: #337ab7;
}
.md-radio input[type="radio"]:checked + label:after {
transform: scale(1);
}
.md-radio label {
cursor: pointer;
display: inline-block;
height: 16px;
margin-bottom: 0;
padding: 0 22px;
position: relative;
vertical-align: bottom;
}
.md-radio label:before, .md-radio label:after {
border-radius: 50%;
content: '';
position: absolute;
transition: all .3s ease;
transition-property: transform, border-color;
}
.md-radio label:before {
border: 2px solid black;
border: 2px solid rgba(0, 0, 0, 0.54);
height: 16px;
left: 0;
top: 0;
width: 16px;
}
.md-radio label:after {
background: #337ab7;
height: 8px;
left: 4px;
top: 4px;
transform: scale(0);
width: 8px;
}

View File

@ -0,0 +1,37 @@
angular.module('ui')
.factory('DatatableService', ['LocalStorage',
function DatatableServiceFactory(LocalStorage) {
'use strict';
var service = {};
service.setDataTableSettings = function(key, settings) {
LocalStorage.storeDataTableSettings(key, settings);
};
service.getDataTableSettings = function(key) {
return LocalStorage.getDataTableSettings(key);
};
service.setDataTableFilters = function(key, filters) {
LocalStorage.storeDataTableFilters(key, filters);
};
service.getDataTableFilters = function(key) {
return LocalStorage.getDataTableFilters(key);
};
service.getDataTableOrder = function(key) {
return LocalStorage.getDataTableOrder(key);
};
service.setDataTableOrder = function(key, orderBy, reverse) {
var filter = {
orderBy: orderBy,
reverse: reverse
};
LocalStorage.storeDataTableOrder(key, filter);
};
return service;
}]);

View File

@ -0,0 +1,99 @@
<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" ng-if="$ctrl.endpointManagement">
<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>
<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" ng-if="$ctrl.endpointManagement">
<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('URL')">
URL
<i class="fa fa-sort-alpha-asc" aria-hidden="true" ng-if="$ctrl.state.orderBy === 'URL' && !$ctrl.state.reverseOrder"></i>
<i class="fa fa-sort-alpha-desc" aria-hidden="true" ng-if="$ctrl.state.orderBy === 'URL' && $ctrl.state.reverseOrder"></i>
</a>
</th>
<th>Actions</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" ng-if="$ctrl.endpointManagement">
<input id="select_{{ $index }}" type="checkbox" ng-model="item.Checked" ng-change="$ctrl.selectItem(item)"/>
<label for="select_{{ $index }}"></label>
</span>
<a ui-sref="endpoint({id: item.Id})" ng-if="$ctrl.endpointManagement">{{ item.Name }}</a>
<span ng-if="!$ctrl.endpointManagement">{{ item.Name }}</span>
</td>
<td>{{ item.URL | stripprotocol }}</td>
<td>
<a ui-sref="endpoint.access({id: item.Id})" ng-if="$ctrl.accessManagement">
<i class="fa fa-users" aria-hidden="true"></i> Manage access
</a>
</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 endpoint 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>

View File

@ -0,0 +1,16 @@
angular.module('ui').component('endpointsDatatable', {
templateUrl: 'app/directives/ui/datatables/endpoints-datatable/endpointsDatatable.html',
controller: 'GenericDatatableController',
bindings: {
title: '@',
titleIcon: '@',
dataset: '<',
tableKey: '@',
orderBy: '@',
reverseOrder: '<',
showTextFilter: '<',
endpointManagement: '<',
accessManagement: '<',
removeAction: '<'
}
});

View File

@ -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>

View File

@ -0,0 +1,13 @@
angular.module('ui').component('eventsDatatable', {
templateUrl: 'app/directives/ui/datatables/events-datatable/eventsDatatable.html',
controller: 'GenericDatatableController',
bindings: {
title: '@',
titleIcon: '@',
dataset: '<',
tableKey: '@',
orderBy: '@',
reverseOrder: '<',
showTextFilter: '<'
}
});

View File

@ -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="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>

View File

@ -0,0 +1,15 @@
angular.module('ui').component('imagesDatatable', {
templateUrl: 'app/directives/ui/datatables/images-datatable/imagesDatatable.html',
controller: 'ImagesDatatableController',
bindings: {
title: '@',
titleIcon: '@',
dataset: '<',
tableKey: '@',
orderBy: '@',
reverseOrder: '<',
showTextFilter: '<',
removeAction: '<',
forceRemoveAction: '<'
}
});

View File

@ -0,0 +1,102 @@
angular.module('ui')
.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;
}
}]);

View File

@ -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="actions.create.network">
<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">
<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="network({id: item.Id})">{{ 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>

View File

@ -0,0 +1,15 @@
angular.module('ui').component('networksDatatable', {
templateUrl: 'app/directives/ui/datatables/networks-datatable/networksDatatable.html',
controller: 'GenericDatatableController',
bindings: {
title: '@',
titleIcon: '@',
dataset: '<',
tableKey: '@',
orderBy: '@',
reverseOrder: '<',
showTextFilter: '<',
showOwnershipColumn: '<',
removeAction: '<'
}
});

View File

@ -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="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>

View File

@ -0,0 +1,13 @@
angular.module('ui').component('nodeTasksDatatable', {
templateUrl: 'app/directives/ui/datatables/node-tasks-datatable/nodeTasksDatatable.html',
controller: 'GenericDatatableController',
bindings: {
title: '@',
titleIcon: '@',
dataset: '<',
tableKey: '@',
orderBy: '@',
reverseOrder: '<',
showTextFilter: '<'
}
});

View File

@ -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="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>

View File

@ -0,0 +1,15 @@
angular.module('ui').component('nodesDatatable', {
templateUrl: 'app/directives/ui/datatables/nodes-datatable/nodesDatatable.html',
controller: 'GenericDatatableController',
bindings: {
title: '@',
titleIcon: '@',
dataset: '<',
tableKey: '@',
orderBy: '@',
reverseOrder: '<',
showTextFilter: '<',
showIpAddressColumn: '<',
accessToNodeDetails: '<'
}
});

View File

@ -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>

View File

@ -0,0 +1,13 @@
angular.module('ui').component('nodesSsDatatable', {
templateUrl: 'app/directives/ui/datatables/nodes-ss-datatable/nodesSSDatatable.html',
controller: 'GenericDatatableController',
bindings: {
title: '@',
titleIcon: '@',
dataset: '<',
tableKey: '@',
orderBy: '@',
reverseOrder: '<',
showTextFilter: '<'
}
});

View File

@ -0,0 +1,104 @@
<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="actions.create.registry">
<i class="fa fa-plus space-right" aria-hidden="true"></i>Add registry
</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('URL')">
URL
<i class="fa fa-sort-alpha-asc" aria-hidden="true" ng-if="$ctrl.state.orderBy === 'URL' && !$ctrl.state.reverseOrder"></i>
<i class="fa fa-sort-alpha-desc" aria-hidden="true" ng-if="$ctrl.state.orderBy === 'URL' && $ctrl.state.reverseOrder"></i>
</a>
</th>
<th>Actions</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="registry({id: item.Id})">{{ item.Name }}</a>
<span ng-if="item.Authentication" style="margin-left: 5px;" class="label label-info image-tag">authentication-enabled</span>
</td>
<td>
{{ item.URL }}
</td>
<td>
<a ui-sref="registry.access({id: item.Id})" ng-if="$ctrl.accessManagement">
<i class="fa fa-users" aria-hidden="true"></i> Manage access
</a>
</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 registry 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>

View File

@ -0,0 +1,15 @@
angular.module('ui').component('registriesDatatable', {
templateUrl: 'app/directives/ui/datatables/registries-datatable/registriesDatatable.html',
controller: 'GenericDatatableController',
bindings: {
title: '@',
titleIcon: '@',
dataset: '<',
tableKey: '@',
orderBy: '@',
reverseOrder: '<',
showTextFilter: '<',
accessManagement: '<',
removeAction: '<'
}
});

Some files were not shown because too many files have changed in this diff Show More