mirror of
https://github.com/portainer/portainer.git
synced 2025-08-02 20:35:25 +02:00
feat(jobs): add the ability to run a job on a target endpoint #2374
* feat(jobs): adding the ability to run scripts on endpoints fix(job): click on containerId in JobsDatatable redirects to container's logs refactor(job): remove the jobs datatable settings + texts changes on JobCreation view fix(jobs): jobs payloads are now following API rules and case feat(jobs): adding the capability to run scripts on hosts * feat(jobs): adding the ability to purge jobs containers * refactor(job): apply review changes * feat(job-creation): store image name in local storage * feat(host): disable job exec link in non-agent Swarm setup * feat(host): only display execute job in agent setups or standalone * feat(job): job execution overhaul * docs(swagger): update EndpointJob documentation
This commit is contained in:
parent
6ab510e5cb
commit
354fda31f1
37 changed files with 739 additions and 100 deletions
|
@ -149,6 +149,16 @@ angular.module('portainer.docker', ['portainer.app'])
|
|||
}
|
||||
};
|
||||
|
||||
var hostJob = {
|
||||
name: 'docker.host.job',
|
||||
url: '/job',
|
||||
views: {
|
||||
'content@': {
|
||||
component: 'hostJobView'
|
||||
}
|
||||
}
|
||||
};
|
||||
|
||||
var events = {
|
||||
name: 'docker.events',
|
||||
url: '/events',
|
||||
|
@ -263,6 +273,16 @@ angular.module('portainer.docker', ['portainer.app'])
|
|||
}
|
||||
};
|
||||
|
||||
var nodeJob = {
|
||||
name: 'docker.nodes.node.job',
|
||||
url: '/job',
|
||||
views: {
|
||||
'content@': {
|
||||
component: 'nodeJobView'
|
||||
}
|
||||
}
|
||||
};
|
||||
|
||||
var secrets = {
|
||||
name: 'docker.secrets',
|
||||
url: '/secrets',
|
||||
|
@ -434,7 +454,7 @@ angular.module('portainer.docker', ['portainer.app'])
|
|||
}
|
||||
};
|
||||
|
||||
|
||||
|
||||
|
||||
$stateRegistryProvider.register(configs);
|
||||
$stateRegistryProvider.register(config);
|
||||
|
@ -450,6 +470,7 @@ angular.module('portainer.docker', ['portainer.app'])
|
|||
$stateRegistryProvider.register(dashboard);
|
||||
$stateRegistryProvider.register(host);
|
||||
$stateRegistryProvider.register(hostBrowser);
|
||||
$stateRegistryProvider.register(hostJob);
|
||||
$stateRegistryProvider.register(events);
|
||||
$stateRegistryProvider.register(images);
|
||||
$stateRegistryProvider.register(image);
|
||||
|
@ -461,6 +482,7 @@ angular.module('portainer.docker', ['portainer.app'])
|
|||
$stateRegistryProvider.register(nodes);
|
||||
$stateRegistryProvider.register(node);
|
||||
$stateRegistryProvider.register(nodeBrowser);
|
||||
$stateRegistryProvider.register(nodeJob);
|
||||
$stateRegistryProvider.register(secrets);
|
||||
$stateRegistryProvider.register(secret);
|
||||
$stateRegistryProvider.register(secretCreation);
|
||||
|
|
|
@ -0,0 +1,118 @@
|
|||
<div class="row">
|
||||
<div class="col-lg-12 col-md-12 col-sm-12 col-xs-12">
|
||||
<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.titleText }}
|
||||
</div>
|
||||
</div>
|
||||
<div class="actionBar">
|
||||
<button type="button" class="btn btn-sm btn-primary" ng-click="$ctrl.purgeAction()">
|
||||
<i class="fa fa-trash-alt space-right" aria-hidden="true"></i>Clear job history
|
||||
</button>
|
||||
</div>
|
||||
<div class="searchBar">
|
||||
<i class="fa fa-search searchIcon" aria-hidden="true"></i>
|
||||
<input type="text" class="searchInput" ng-model="$ctrl.state.textFilter" placeholder="Search..." auto-focus>
|
||||
</div>
|
||||
<div class="table-responsive">
|
||||
<table class="table table-hover table-filters nowrap-cells">
|
||||
<thead>
|
||||
<tr>
|
||||
<th>
|
||||
<a ng-click="$ctrl.changeOrderBy('Id')">
|
||||
Id
|
||||
<i class="fa fa-sort-alpha-down" aria-hidden="true" ng-if="$ctrl.state.orderBy === 'Id' && !$ctrl.state.reverseOrder"></i>
|
||||
<i class="fa fa-sort-alpha-up" aria-hidden="true" ng-if="$ctrl.state.orderBy === 'Id' && $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-down" aria-hidden="true" ng-if="$ctrl.state.orderBy === 'Status' && !$ctrl.state.reverseOrder"></i>
|
||||
<i class="fa fa-sort-alpha-up" 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>
|
||||
<a ng-click="$ctrl.changeOrderBy('Created')">
|
||||
<i class="fa fa-sort-alpha-down" aria-hidden="true" ng-if="$ctrl.state.orderBy === 'Created' && !$ctrl.state.reverseOrder"></i>
|
||||
<i class="fa fa-sort-alpha-up" aria-hidden="true" ng-if="$ctrl.state.orderBy === 'Created' && $ctrl.state.reverseOrder"></i>
|
||||
Created
|
||||
</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))">
|
||||
<td>
|
||||
<a ui-sref="docker.containers.container.logs({ id: item.Id, nodeName: item.NodeName })" title="{{ item.Id }}">
|
||||
{{ item.Id | truncate: 32}}</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>
|
||||
{{item.Created | getisodatefromtimestamp}}
|
||||
</td>
|
||||
</tr>
|
||||
<tr ng-if="!$ctrl.dataset">
|
||||
<td colspan="9" class="text-center text-muted">Loading...</td>
|
||||
</tr>
|
||||
<tr ng-if="$ctrl.state.filteredDataSet.length === 0">
|
||||
<td colspan="9" class="text-center text-muted">No jobs 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>
|
||||
</div>
|
||||
</div>
|
|
@ -0,0 +1,12 @@
|
|||
angular.module('portainer.docker').component('jobsDatatable', {
|
||||
templateUrl: 'app/docker/components/datatables/host-jobs-datatable/jobsDatatable.html',
|
||||
controller: 'JobsDatatableController',
|
||||
bindings: {
|
||||
titleText: '@',
|
||||
titleIcon: '@',
|
||||
dataset: '<',
|
||||
tableKey: '@',
|
||||
orderBy: '@',
|
||||
reverseOrder: '<'
|
||||
}
|
||||
});
|
|
@ -0,0 +1,140 @@
|
|||
angular.module('portainer.docker')
|
||||
.controller('JobsDatatableController', ['$q', '$state', 'PaginationService', 'DatatableService', 'ContainerService', 'ModalService', 'Notifications',
|
||||
function ($q, $state, PaginationService, DatatableService, ContainerService, ModalService, Notifications) {
|
||||
var ctrl = this;
|
||||
|
||||
this.state = {
|
||||
orderBy: this.orderBy,
|
||||
paginatedItemLimit: PaginationService.getPaginationLimit(this.tableKey),
|
||||
displayTextFilter: false
|
||||
};
|
||||
|
||||
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.changePaginationLimit = function () {
|
||||
PaginationService.setPaginationLimit(this.tableKey, this.state.paginatedItemLimit);
|
||||
};
|
||||
|
||||
this.applyFilters = function (value) {
|
||||
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.prepareTableFromDataset = function () {
|
||||
var availableStateFilters = [];
|
||||
for (var i = 0; i < this.dataset.length; i++) {
|
||||
var item = this.dataset[i];
|
||||
availableStateFilters.push({
|
||||
label: item.Status,
|
||||
display: true
|
||||
});
|
||||
}
|
||||
this.filters.state.values = _.uniqBy(availableStateFilters, 'label');
|
||||
};
|
||||
|
||||
this.updateStoredFilters = function (storedFilters) {
|
||||
var datasetFilters = this.filters.state.values;
|
||||
|
||||
for (var i = 0; i < datasetFilters.length; i++) {
|
||||
var filter = datasetFilters[i];
|
||||
existingFilter = _.find(storedFilters, ['label', filter.label]);
|
||||
if (existingFilter && !existingFilter.display) {
|
||||
filter.display = existingFilter.display;
|
||||
this.filters.state.enabled = true;
|
||||
}
|
||||
}
|
||||
};
|
||||
|
||||
function confirmPurgeJobs() {
|
||||
return showConfirmationModal();
|
||||
|
||||
function showConfirmationModal() {
|
||||
var deferred = $q.defer();
|
||||
|
||||
ModalService.confirm({
|
||||
title: 'Are you sure ?',
|
||||
message: 'Clearing job history will remove all stopped jobs containers.',
|
||||
buttons: {
|
||||
confirm: {
|
||||
label: 'Purge',
|
||||
className: 'btn-danger'
|
||||
}
|
||||
},
|
||||
callback: function onConfirm(confirmed) {
|
||||
deferred.resolve(confirmed);
|
||||
}
|
||||
});
|
||||
|
||||
return deferred.promise;
|
||||
}
|
||||
}
|
||||
|
||||
this.purgeAction = function () {
|
||||
confirmPurgeJobs().then(function success(confirmed) {
|
||||
if (!confirmed) {
|
||||
return $q.when();
|
||||
}
|
||||
ContainerService.prune({ label: ['io.portainer.job.endpoint'] }).then(function success() {
|
||||
Notifications.success('Success', 'Job hisotry cleared');
|
||||
$state.reload();
|
||||
}).catch(function error(err) {
|
||||
Notifications.error('Failure', err.message, 'Unable to clear job history');
|
||||
});
|
||||
});
|
||||
};
|
||||
|
||||
this.$onInit = function () {
|
||||
setDefaults(this);
|
||||
this.prepareTableFromDataset();
|
||||
|
||||
var storedOrder = DatatableService.getDataTableOrder(this.tableKey);
|
||||
if (storedOrder !== null) {
|
||||
this.state.reverseOrder = storedOrder.reverse;
|
||||
this.state.orderBy = storedOrder.orderBy;
|
||||
}
|
||||
|
||||
var storedFilters = DatatableService.getDataTableFilters(this.tableKey);
|
||||
if (storedFilters !== null) {
|
||||
this.updateStoredFilters(storedFilters.state.values);
|
||||
}
|
||||
this.filters.state.open = false;
|
||||
};
|
||||
|
||||
function setDefaults(ctrl) {
|
||||
ctrl.showTextFilter = ctrl.showTextFilter ? ctrl.showTextFilter : false;
|
||||
ctrl.state.reverseOrder = ctrl.reverseOrder ? ctrl.reverseOrder : false;
|
||||
}
|
||||
}
|
||||
]);
|
|
@ -8,14 +8,25 @@
|
|||
<rd-header-content>Docker</rd-header-content>
|
||||
</rd-header>
|
||||
|
||||
<host-details-panel
|
||||
host="$ctrl.hostDetails"
|
||||
<host-details-panel
|
||||
host="$ctrl.hostDetails"
|
||||
is-browse-enabled="$ctrl.isAgent && $ctrl.agentApiVersion > 1"
|
||||
browse-url="{{$ctrl.browseUrl}}"></host-details-panel>
|
||||
browse-url="{{$ctrl.browseUrl}}"
|
||||
is-job-enabled="$ctrl.isJobEnabled"
|
||||
job-url="{{$ctrl.jobUrl}}"
|
||||
></host-details-panel>
|
||||
|
||||
<engine-details-panel engine="$ctrl.engineDetails"></engine-details-panel>
|
||||
|
||||
<jobs-datatable
|
||||
ng-if="$ctrl.isJobEnabled && $ctrl.jobs"
|
||||
title-text="Jobs" title-icon="fa-tasks"
|
||||
dataset="$ctrl.jobs"
|
||||
table-key="jobs"
|
||||
order-by="Created" reverse-order="true"
|
||||
></jobs-datatable>
|
||||
|
||||
<devices-panel ng-if="$ctrl.isAgent && $ctrl.agentApiVersion > 1" devices="$ctrl.devices"></devices-panel>
|
||||
<disks-panel ng-if="$ctrl.isAgent && $ctrl.agentApiVersion > 1" disks="$ctrl.disks"></disks-panel>
|
||||
|
||||
<ng-transclude></ng-transclude>
|
||||
<ng-transclude></ng-transclude>
|
||||
|
|
|
@ -8,7 +8,10 @@ angular.module('portainer.docker').component('hostOverview', {
|
|||
isAgent: '<',
|
||||
agentApiVersion: '<',
|
||||
refreshUrl: '@',
|
||||
browseUrl: '@'
|
||||
browseUrl: '@',
|
||||
jobUrl: '@',
|
||||
isJobEnabled: '<',
|
||||
jobs: '<'
|
||||
},
|
||||
transclude: true
|
||||
});
|
||||
|
|
|
@ -26,20 +26,19 @@
|
|||
<td>Total memory</td>
|
||||
<td>{{ $ctrl.host.totalMemory | humansize }}</td>
|
||||
</tr>
|
||||
<tr ng-if="$ctrl.isBrowseEnabled">
|
||||
<tr ng-if="$ctrl.isBrowseEnabled || $ctrl.isJobEnabled">
|
||||
<td colspan="2">
|
||||
<button
|
||||
class="btn btn-primary btn-sm"
|
||||
title="Browse"
|
||||
ui-sref="{{$ctrl.browseUrl}}">
|
||||
<button class="btn btn-primary btn-sm" title="Browse" ng-if="$ctrl.isBrowseEnabled" ui-sref="{{$ctrl.browseUrl}}">
|
||||
Browse
|
||||
</button>
|
||||
<button class="btn btn-primary btn-sm" title="Execute job" ng-if="$ctrl.isJobEnabled" ui-sref="{{$ctrl.jobUrl}}">
|
||||
Execute job
|
||||
</button>
|
||||
</td>
|
||||
</tr>
|
||||
|
||||
</tbody>
|
||||
</table>
|
||||
</rd-widget-body>
|
||||
</rd-widget>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
|
|
@ -1,9 +1,10 @@
|
|||
angular.module('portainer.docker').component('hostDetailsPanel', {
|
||||
templateUrl:
|
||||
'app/docker/components/host-view-panels/host-details-panel/host-details-panel.html',
|
||||
templateUrl: 'app/docker/components/host-view-panels/host-details-panel/host-details-panel.html',
|
||||
bindings: {
|
||||
host: '<',
|
||||
isJobEnabled: '<',
|
||||
isBrowseEnabled: '<',
|
||||
browseUrl: '@'
|
||||
browseUrl: '@',
|
||||
jobUrl: '@'
|
||||
}
|
||||
});
|
||||
|
|
|
@ -68,6 +68,9 @@ function ContainerFactory($resource, API_ENDPOINT_ENDPOINTS, EndpointProvider) {
|
|||
},
|
||||
update: {
|
||||
method: 'POST', params: { id: '@id', action: 'update'}
|
||||
},
|
||||
prune: {
|
||||
method: 'POST', params: { action: 'prune', filters: '@filters' }
|
||||
}
|
||||
});
|
||||
}]);
|
||||
|
|
|
@ -186,5 +186,9 @@ function ContainerServiceFactory($q, Container, ResourceControlService, LogHelpe
|
|||
return Container.inspect({ id: id }).$promise;
|
||||
};
|
||||
|
||||
service.prune = function(filters) {
|
||||
return Container.prune({ filters: filters }).$promise;
|
||||
};
|
||||
|
||||
return service;
|
||||
}]);
|
||||
|
|
|
@ -1,21 +1,17 @@
|
|||
angular
|
||||
.module('portainer.docker')
|
||||
.controller('HostBrowserViewController', [
|
||||
'SystemService', 'HttpRequestHelper',
|
||||
function HostBrowserViewController(SystemService, HttpRequestHelper) {
|
||||
var ctrl = this;
|
||||
angular.module('portainer.docker').controller('HostBrowserViewController', [
|
||||
'SystemService', 'Notifications',
|
||||
function HostBrowserViewController(SystemService, Notifications) {
|
||||
var ctrl = this;
|
||||
ctrl.$onInit = $onInit;
|
||||
|
||||
ctrl.$onInit = $onInit;
|
||||
|
||||
function $onInit() {
|
||||
loadInfo();
|
||||
}
|
||||
|
||||
function loadInfo() {
|
||||
SystemService.info().then(function onInfoLoaded(host) {
|
||||
HttpRequestHelper.setPortainerAgentTargetHeader(host.Name);
|
||||
ctrl.host = host;
|
||||
});
|
||||
}
|
||||
function $onInit() {
|
||||
SystemService.info()
|
||||
.then(function onInfoLoaded(host) {
|
||||
ctrl.host = host;
|
||||
})
|
||||
.catch(function onError(err) {
|
||||
Notifications.error('Unable to retrieve host information', err);
|
||||
});
|
||||
}
|
||||
]);
|
||||
}
|
||||
]);
|
||||
|
|
17
app/docker/views/host/host-job/host-job-controller.js
Normal file
17
app/docker/views/host/host-job/host-job-controller.js
Normal file
|
@ -0,0 +1,17 @@
|
|||
angular.module('portainer.docker').controller('HostJobController', [
|
||||
'SystemService', 'Notifications',
|
||||
function HostJobController(SystemService, Notifications) {
|
||||
var ctrl = this;
|
||||
ctrl.$onInit = $onInit;
|
||||
|
||||
function $onInit() {
|
||||
SystemService.info()
|
||||
.then(function onInfoLoaded(host) {
|
||||
ctrl.host = host;
|
||||
})
|
||||
.catch(function onError(err) {
|
||||
Notifications.error('Unable to retrieve host information', err);
|
||||
});
|
||||
}
|
||||
}
|
||||
]);
|
16
app/docker/views/host/host-job/host-job.html
Normal file
16
app/docker/views/host/host-job/host-job.html
Normal file
|
@ -0,0 +1,16 @@
|
|||
<rd-header>
|
||||
<rd-header-title title-text="Host job execution"></rd-header-title>
|
||||
<rd-header-content>
|
||||
Host > <a ui-sref="docker.host">{{ $ctrl.host.Name }}</a> > execute job
|
||||
</rd-header-content>
|
||||
</rd-header>
|
||||
|
||||
<div class="row">
|
||||
<div class="col-sm-12">
|
||||
<rd-widget>
|
||||
<rd-widget-body>
|
||||
<execute-job-form></execute-job-form>
|
||||
</rd-widget-body>
|
||||
</rd-widget>
|
||||
</div>
|
||||
</div>
|
4
app/docker/views/host/host-job/host-job.js
Normal file
4
app/docker/views/host/host-job/host-job.js
Normal file
|
@ -0,0 +1,4 @@
|
|||
angular.module('portainer.docker').component('hostJobView', {
|
||||
templateUrl: 'app/docker/views/host/host-job/host-job.html',
|
||||
controller: 'HostJobController'
|
||||
});
|
|
@ -1,13 +1,15 @@
|
|||
angular.module('portainer.docker').controller('HostViewController', [
|
||||
'$q', 'SystemService', 'Notifications', 'StateManager', 'AgentService',
|
||||
function HostViewController($q, SystemService, Notifications, StateManager, AgentService) {
|
||||
'$q', 'SystemService', 'Notifications', 'StateManager', 'AgentService', 'ContainerService', 'Authentication',
|
||||
function HostViewController($q, SystemService, Notifications, StateManager, AgentService, ContainerService, Authentication) {
|
||||
var ctrl = this;
|
||||
|
||||
this.$onInit = initView;
|
||||
|
||||
ctrl.state = {
|
||||
isAgent: false
|
||||
isAgent: false,
|
||||
isAdmin : false
|
||||
};
|
||||
|
||||
|
||||
this.engineDetails = {};
|
||||
this.hostDetails = {};
|
||||
this.devices = null;
|
||||
|
@ -16,31 +18,34 @@ angular.module('portainer.docker').controller('HostViewController', [
|
|||
function initView() {
|
||||
var applicationState = StateManager.getState();
|
||||
ctrl.state.isAgent = applicationState.endpoint.mode.agentProxy;
|
||||
ctrl.state.isAdmin = Authentication.getUserDetails().role === 1;
|
||||
var agentApiVersion = applicationState.endpoint.agentApiVersion;
|
||||
ctrl.state.agentApiVersion = agentApiVersion;
|
||||
|
||||
$q.all({
|
||||
version: SystemService.version(),
|
||||
info: SystemService.info()
|
||||
info: SystemService.info(),
|
||||
jobs: ctrl.state.isAdmin ? ContainerService.containers(true, { label: ['io.portainer.job.endpoint'] }) : []
|
||||
})
|
||||
.then(function success(data) {
|
||||
ctrl.engineDetails = buildEngineDetails(data);
|
||||
ctrl.hostDetails = buildHostDetails(data.info);
|
||||
.then(function success(data) {
|
||||
ctrl.engineDetails = buildEngineDetails(data);
|
||||
ctrl.hostDetails = buildHostDetails(data.info);
|
||||
ctrl.jobs = data.jobs;
|
||||
|
||||
if (ctrl.state.isAgent && agentApiVersion > 1) {
|
||||
return AgentService.hostInfo(data.info.Hostname).then(function onHostInfoLoad(agentHostInfo) {
|
||||
ctrl.devices = agentHostInfo.PCIDevices;
|
||||
ctrl.disks = agentHostInfo.PhysicalDisks;
|
||||
});
|
||||
}
|
||||
})
|
||||
.catch(function error(err) {
|
||||
Notifications.error(
|
||||
'Failure',
|
||||
err,
|
||||
'Unable to retrieve engine details'
|
||||
);
|
||||
});
|
||||
if (ctrl.state.isAgent && agentApiVersion > 1) {
|
||||
return AgentService.hostInfo(data.info.Hostname).then(function onHostInfoLoad(agentHostInfo) {
|
||||
ctrl.devices = agentHostInfo.PCIDevices;
|
||||
ctrl.disks = agentHostInfo.PhysicalDisks;
|
||||
});
|
||||
}
|
||||
})
|
||||
.catch(function error(err) {
|
||||
Notifications.error(
|
||||
'Failure',
|
||||
err,
|
||||
'Unable to retrieve engine details'
|
||||
);
|
||||
});
|
||||
}
|
||||
|
||||
function buildEngineDetails(data) {
|
||||
|
|
|
@ -5,7 +5,9 @@
|
|||
agent-api-version="$ctrl.state.agentApiVersion"
|
||||
disks="$ctrl.disks"
|
||||
devices="$ctrl.devices"
|
||||
|
||||
refresh-url="docker.host"
|
||||
browse-url="docker.host.browser"
|
||||
></host-overview>
|
||||
is-job-enabled="$ctrl.state.isAdmin"
|
||||
job-url="docker.host.job"
|
||||
jobs="$ctrl.jobs"
|
||||
></host-overview>
|
||||
|
|
|
@ -1,19 +1,19 @@
|
|||
angular.module('portainer.docker').controller('NodeBrowserController', [
|
||||
'NodeService', 'HttpRequestHelper', '$stateParams',
|
||||
function NodeBrowserController(NodeService, HttpRequestHelper, $stateParams) {
|
||||
'$stateParams', 'NodeService', 'HttpRequestHelper', 'Notifications',
|
||||
function NodeBrowserController($stateParams, NodeService, HttpRequestHelper, Notifications) {
|
||||
var ctrl = this;
|
||||
|
||||
ctrl.$onInit = $onInit;
|
||||
|
||||
function $onInit() {
|
||||
ctrl.nodeId = $stateParams.id;
|
||||
loadNode();
|
||||
}
|
||||
|
||||
function loadNode() {
|
||||
NodeService.node(ctrl.nodeId).then(function onNodeLoaded(node) {
|
||||
NodeService.node(ctrl.nodeId)
|
||||
.then(function onNodeLoaded(node) {
|
||||
HttpRequestHelper.setPortainerAgentTargetHeader(node.Hostname);
|
||||
ctrl.node = node;
|
||||
})
|
||||
.catch(function onError(err) {
|
||||
Notifications.error('Unable to retrieve host information', err);
|
||||
});
|
||||
}
|
||||
}
|
||||
|
|
|
@ -1,35 +1,46 @@
|
|||
angular.module('portainer.docker').controller('NodeDetailsViewController', [
|
||||
'$stateParams', 'NodeService', 'StateManager', 'AgentService',
|
||||
function NodeDetailsViewController($stateParams, NodeService, StateManager, AgentService) {
|
||||
'$q', '$stateParams', 'NodeService', 'StateManager', 'AgentService', 'ContainerService', 'Authentication',
|
||||
function NodeDetailsViewController($q, $stateParams, NodeService, StateManager, AgentService, ContainerService, Authentication) {
|
||||
var ctrl = this;
|
||||
|
||||
ctrl.$onInit = initView;
|
||||
|
||||
ctrl.state = {
|
||||
isAgent: false
|
||||
isAgent: false,
|
||||
isAdmin: false
|
||||
};
|
||||
|
||||
function initView() {
|
||||
var applicationState = StateManager.getState();
|
||||
ctrl.state.isAgent = applicationState.endpoint.mode.agentProxy;
|
||||
ctrl.state.isAdmin = Authentication.getUserDetails().role === 1;
|
||||
|
||||
var fetchJobs = ctrl.state.isAdmin && ctrl.state.isAgent;
|
||||
|
||||
var nodeId = $stateParams.id;
|
||||
NodeService.node(nodeId).then(function(node) {
|
||||
$q.all({
|
||||
node: NodeService.node(nodeId),
|
||||
jobs: fetchJobs ? ContainerService.containers(true, { label: ['io.portainer.job.endpoint'] }) : []
|
||||
})
|
||||
.then(function (data) {
|
||||
var node = data.node;
|
||||
ctrl.originalNode = node;
|
||||
ctrl.hostDetails = buildHostDetails(node);
|
||||
ctrl.engineDetails = buildEngineDetails(node);
|
||||
ctrl.nodeDetails = buildNodeDetails(node);
|
||||
ctrl.jobs = data.jobs;
|
||||
if (ctrl.state.isAgent) {
|
||||
var agentApiVersion = applicationState.endpoint.agentApiVersion;
|
||||
ctrl.state.agentApiVersion = agentApiVersion;
|
||||
if (agentApiVersion < 2) {
|
||||
return;
|
||||
}
|
||||
|
||||
AgentService.hostInfo(node.Hostname)
|
||||
.then(function onHostInfoLoad(agentHostInfo) {
|
||||
ctrl.devices = agentHostInfo.PCIDevices;
|
||||
ctrl.disks = agentHostInfo.PhysicalDisks;
|
||||
});
|
||||
.then(function onHostInfoLoad(agentHostInfo) {
|
||||
ctrl.devices = agentHostInfo.PCIDevices;
|
||||
ctrl.disks = agentHostInfo.PhysicalDisks;
|
||||
});
|
||||
}
|
||||
});
|
||||
}
|
||||
|
@ -68,12 +79,12 @@ angular.module('portainer.docker').controller('NodeDetailsViewController', [
|
|||
|
||||
function transformPlugins(pluginsList, type) {
|
||||
return pluginsList
|
||||
.filter(function(plugin) {
|
||||
return plugin.Type === type;
|
||||
})
|
||||
.map(function(plugin) {
|
||||
return plugin.Name;
|
||||
});
|
||||
.filter(function(plugin) {
|
||||
return plugin.Type === type;
|
||||
})
|
||||
.map(function(plugin) {
|
||||
return plugin.Name;
|
||||
});
|
||||
}
|
||||
}
|
||||
]);
|
||||
|
|
|
@ -5,12 +5,14 @@
|
|||
engine-details="$ctrl.engineDetails"
|
||||
disks="$ctrl.disks"
|
||||
devices="$ctrl.devices"
|
||||
|
||||
refresh-url="docker.nodes.node"
|
||||
browse-url="docker.nodes.node.browse"
|
||||
is-job-enabled="$ctrl.state.isAdmin && $ctrl.state.isAgent"
|
||||
job-url="docker.nodes.node.job"
|
||||
jobs="$ctrl.jobs"
|
||||
>
|
||||
<swarm-node-details-panel
|
||||
details="$ctrl.nodeDetails"
|
||||
original-node="$ctrl.originalNode"
|
||||
></swarm-node-details-panel>
|
||||
</host-overview>
|
||||
</host-overview>
|
||||
|
|
20
app/docker/views/nodes/node-job/node-job-controller.js
Normal file
20
app/docker/views/nodes/node-job/node-job-controller.js
Normal file
|
@ -0,0 +1,20 @@
|
|||
angular.module('portainer.docker').controller('NodeJobController', [
|
||||
'$stateParams', 'NodeService', 'HttpRequestHelper', 'Notifications',
|
||||
function NodeJobController($stateParams, NodeService, HttpRequestHelper, Notifications) {
|
||||
var ctrl = this;
|
||||
ctrl.$onInit = $onInit;
|
||||
|
||||
function $onInit() {
|
||||
ctrl.nodeId = $stateParams.id;
|
||||
|
||||
NodeService.node(ctrl.nodeId)
|
||||
.then(function onNodeLoaded(node) {
|
||||
HttpRequestHelper.setPortainerAgentTargetHeader(node.Hostname);
|
||||
ctrl.node = node;
|
||||
})
|
||||
.catch(function onError(err) {
|
||||
Notifications.error('Unable to retrieve host information', err);
|
||||
});
|
||||
}
|
||||
}
|
||||
]);
|
18
app/docker/views/nodes/node-job/node-job.html
Normal file
18
app/docker/views/nodes/node-job/node-job.html
Normal file
|
@ -0,0 +1,18 @@
|
|||
<rd-header>
|
||||
<rd-header-title title-text="Host job execution"></rd-header-title>
|
||||
<rd-header-content>
|
||||
<a ui-sref="docker.swarm">Swarm</a> > <a ui-sref="docker.nodes.node({ id: $ctrl.nodeId })">{{ $ctrl.node.Hostname }}</a> > execute job
|
||||
</rd-header-content>
|
||||
</rd-header>
|
||||
|
||||
<div class="row">
|
||||
<div class="col-sm-12">
|
||||
<rd-widget>
|
||||
<rd-widget-body>
|
||||
<execute-job-form
|
||||
node-name="$ctrl.node.Hostname"
|
||||
></execute-job-form>
|
||||
</rd-widget-body>
|
||||
</rd-widget>
|
||||
</div>
|
||||
</div>
|
4
app/docker/views/nodes/node-job/node-job.js
Normal file
4
app/docker/views/nodes/node-job/node-job.js
Normal file
|
@ -0,0 +1,4 @@
|
|||
angular.module('portainer.docker').component('nodeJobView', {
|
||||
templateUrl: 'app/docker/views/nodes/node-job/node-job.html',
|
||||
controller: 'NodeJobController'
|
||||
});
|
Loading…
Add table
Add a link
Reference in a new issue