mirror of
https://github.com/portainer/portainer.git
synced 2025-08-08 23:35:31 +02:00
feat(edge): introduce support for Edge agent (#3031)
* feat(edge): fix webconsole and agent deployment command * feat(edge): display agent features when connected to IoT endpoint * feat(edge): add -e CAP_HOST_MANAGEMENT=1 to agent command * feat(edge): add -v /:/host and --name portainer_agent_iot to agent command * style(endpoint-creation): refactor IoT agent to Edge agent * refactor(api): rename AgentIoTEnvironment to AgentEdgeEnvironment * refactor(api): rename AgentIoTEnvironment to AgentEdgeEnvironment * feat(endpoint-creation): update Edge agent deployment instructions * feat(edge): wip edge * feat(edge): refactor key creation * feat(edge): update deployment instructions * feat(home): update Edge agent endpoint item * feat(edge): support dynamic ports * feat(edge): support sleep/wake and snapshots * feat(edge): support offline mode * feat(edge): host job support for Edge endpoints * feat(edge): introduce STANDBY state * feat(edge): update Edge agent deployment command * feat(edge): introduce EDGE_ID support * feat(edge): update default inactivity interval to 5min * feat(edge): reload Edge schedules after restart * fix(edge): fix execution of endpoint job against an Edge endpoint * fix(edge): fix minor issues with scheduling UI/UX * feat(edge): introduce EdgeSchedule version management * feat(edge): switch back to REQUIRED state from ACTIVE on error * refactor(edge): remove comment * feat(edge): updated tunnel status management * feat(edge): fix flickering UI when accessing Edge endpoint from home view * feat(edge): remove STANDBY status * fix(edge): fix an issue with console and Swarm endpoint * fix(edge): fix an issue with stack deployment * fix(edge): reset timer when applying active status * feat(edge): add background ping for Edge endpoints * fix(edge): fix infinite loading loop after Edge endpoint connection failure * fix(home): fix an issue with merge * feat(api): remove SnapshotRaw from EndpointList response * feat(api): add pagination for EndpointList operation * feat(api): rename last_id query parameter to start * feat(api): implement filter for EndpointList operation * fix(edge): prevent a pointer issue after removing an active Edge endpoint * feat(home): front - endpoint backend pagination (#2990) * feat(home): endpoint pagination with backend * feat(api): remove default limit value * fix(endpoints): fix a minor issue with column span * fix(endpointgroup-create): fix an issue with endpoint group creation * feat(app): minor loading optimizations * refactor(api): small refactor of EndpointList operation * fix(home): fix minor loading text display issue * refactor(api): document bolt services functions * feat(home): minor optimization * fix(api): replace seek with index scanning for EndpointPaginated * fix(api): fix invalid starting index issue * fix(api): first implementation of working filter * fix(home): endpoints list keeps backend pagination when it needs to * fix(api): endpoint pagination doesn't drop the first item on pages >=2 anymore * fix(home): UI flickering on page/filter load/change * feat(auth): login spinner * feat(api): support searching in associated endpoint group data * refactor(api): remove unused API endpoint * refactor(api): remove comment * refactor(api): refactor proxy manager * feat(api): declare EndpointList params as optional * feat(api): support groupID filter on endpoints route * feat(api): add new API operations endpointGroupAddEndpoint and endpointGroupDeleteEndpoint * feat(edge): new icon for Edge agent endpoint * fix(edge): fix missing exec quick action * fix(edge): add loading indicator when connecting to Edge endpoint * feat(edge): disable service webhooks for Edge endpoints * feat(endpoints): backend pagination for endpoints view (#3004) * feat(edge): dynamic loading for stack migration feature * feat(edge): wordwrap edge key * feat(endpoint-groups): backend pagination support for create and edit * feat(endpoint-groups): debounce on filter for create/edit views * feat(endpoint-groups): filter assigned on create view * (endpoint-groups): unassigned endpoints edit view * refactor(endpoint-groups): code clean * feat(endpoint-groups): remove message for Unassigned group * refactor(websocket): minor refactor associated to Edge agent * feat(endpoint-group): enable backend pagination (#3017) * feat(api): support groupID filter on endpoints route * feat(api): add new API operations endpointGroupAddEndpoint and endpointGroupDeleteEndpoint * feat(endpoint-groups): backend pagination support for create and edit * feat(endpoint-groups): debounce on filter for create/edit views * feat(endpoint-groups): filter assigned on create view * (endpoint-groups): unassigned endpoints edit view * refactor(endpoint-groups): code clean * feat(endpoint-groups): remove message for Unassigned group * refactor(api): endpoint group endpoint association refactor * refactor(api): rename files and remove comments * refactor(api): remove usage of utils * refactor(api): optional parameters * Merge branch 'feat-endpoint-backend-pagination' into edge # Conflicts: # api/bolt/endpoint/endpoint.go # api/http/handler/endpointgroups/endpointgroup_update.go # api/http/handler/endpointgroups/handler.go # api/http/handler/endpoints/endpoint_list.go # app/portainer/services/api/endpointService.js * fix(api): fix default tunnel server credentials * feat(api): update endpointListOperation behavior and parameters * fix(api): fix interface declaration * feat(edge): support configurable Edge agent checkin interval * feat(edge): support dynamic tunnel credentials * feat(edge): update Edge agent deployment commands * style(edge): update Edge agent settings text * refactor(edge): remove unused credentials management methods * feat(edge): associate a remote addr to tunnel credentials * style(edge): update Edge endpoint icon * feat(edge): support encrypted tunnel credentials * fix(edge): fix invalid pointer cast * feat(bolt): decode endpoints with jsoniter * feat(edge): persist reverse tunnel keyseed * refactor(edge): minor refactor * feat(edge): update chisel library usage * refactor(endpoint): use controller function * feat(api): database migration to DBVersion 19 * refactor(api): refactor AddSchedule function * refactor(schedules): remove comment * refactor(api): remove comment * refactor(api): remove comment * feat(api): tunnel manager now only manage Edge endpoints * refactor(api): clean-up and clarification of the Edge service * refactor(api): clean-up and clarification of the Edge service * fix(api): fix an issue with Edge agent snapshots * refactor(api): add missing comments * refactor(api): update constant description * style(home): remove loading text on error * feat(endpoint): remove 15s timeout for ping request * style(home): display information about associated Edge endpoints * feat(home): redirect to endpoint details on click on unassociated Edge endpoint * feat(settings): remove 60s Edge poll frequency option
This commit is contained in:
parent
2252ab9da7
commit
12a512f01f
86 changed files with 1568 additions and 225 deletions
|
@ -51,15 +51,19 @@
|
|||
<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>
|
||||
{{ item.Endpoint.Name }}
|
||||
<a ng-if="item.Edge" ng-click="$ctrl.getEdgeTaskLogs(item.EndpointId, item.Id)"><i class="fa fa-download" aria-hidden="true" style="margin-left: 5px; margin-right: 2px;"></i> Download logs</a>
|
||||
</td>
|
||||
<td>
|
||||
<a ng-click="$ctrl.goToContainerLogs(item.EndpointId, item.Id)">{{ item.Id | truncate: 32 }}</a>
|
||||
<a ng-if="!item.Edge" ng-click="$ctrl.goToContainerLogs(item.EndpointId, item.Id)">{{ item.Id | truncate: 32 }}</a>
|
||||
<span ng-if="item.Edge">-</span>
|
||||
</td>
|
||||
<td>
|
||||
<span class="label label-{{ item.Status|containerstatusbadge }}">{{ item.Status }}</span>
|
||||
<span ng-if="!item.Edge" class="label label-{{ item.Status|containerstatusbadge }}">{{ item.Status }}</span>
|
||||
<span ng-if="item.Edge">-</span>
|
||||
</td>
|
||||
<td>
|
||||
{{ item.Created | getisodatefromtimestamp}}
|
||||
<span ng-if="!item.Edge">{{ item.Created | getisodatefromtimestamp}}</span>
|
||||
<span ng-if="item.Edge">-</span>
|
||||
</td>
|
||||
</tr>
|
||||
<tr ng-if="!$ctrl.dataset">
|
||||
|
|
|
@ -8,6 +8,7 @@ angular.module('portainer.docker').component('scheduleTasksDatatable', {
|
|||
tableKey: '@',
|
||||
orderBy: '@',
|
||||
reverseOrder: '<',
|
||||
goToContainerLogs: '<'
|
||||
goToContainerLogs: '<',
|
||||
getEdgeTaskLogs: '<'
|
||||
}
|
||||
});
|
||||
|
|
|
@ -1,7 +1,8 @@
|
|||
<div class="blocklist-item" ng-click="$ctrl.onSelect($ctrl.model)">
|
||||
<div class="blocklist-item-box">
|
||||
<span ng-class="['blocklist-item-logo', 'endpoint-item', { azure: $ctrl.model.Type === 3 }]">
|
||||
<i ng-class="$ctrl.model.Type | endpointtypeicon" class="fa-4x blue-icon" aria-hidden="true"></i>
|
||||
<i ng-if="$ctrl.model.Type !== 4" ng-class="$ctrl.model.Type | endpointtypeicon" class="fa-4x blue-icon" aria-hidden="true"></i>
|
||||
<img ng-if="$ctrl.model.Type === 4" src="../../../../../assets/images/edge_endpoint.png" />
|
||||
</span>
|
||||
|
||||
<span class="col-sm-12">
|
||||
|
@ -12,13 +13,16 @@
|
|||
{{ $ctrl.model.Name }}
|
||||
</span>
|
||||
<span class="space-left blocklist-item-subtitle">
|
||||
<span class="label label-{{ $ctrl.model.Status|endpointstatusbadge }}">
|
||||
<span ng-if="$ctrl.model.Type === 4" class="small text-muted">
|
||||
<span ng-if="$ctrl.model.EdgeID"><i class="fas fa-link"></i> associated</span>
|
||||
<span ng-if="!$ctrl.model.EdgeID"><i class="fas fa-unlink"></i> <s>associated</s></span>
|
||||
</span>
|
||||
<span class="label label-{{ $ctrl.model.Status|endpointstatusbadge }}" ng-if="$ctrl.model.Type !== 4">
|
||||
{{ $ctrl.model.Status === 1 ? 'up' : 'down' }}
|
||||
</span>
|
||||
<span class="space-left small text-muted" ng-if="$ctrl.model.Snapshots[0]">
|
||||
{{ $ctrl.model.Snapshots[0].Time | getisodatefromtimestamp }}
|
||||
</span>
|
||||
|
||||
</span>
|
||||
</span>
|
||||
<span>
|
||||
|
@ -64,6 +68,12 @@
|
|||
</span>
|
||||
</div>
|
||||
|
||||
<div class="blocklist-item-line endpoint-item" ng-if="!$ctrl.model.Snapshots[0]">
|
||||
<span class="blocklist-item-desc">
|
||||
No snapshot available
|
||||
</span>
|
||||
</div>
|
||||
|
||||
<div class="blocklist-item-line endpoint-item">
|
||||
<span class="small text-muted">
|
||||
<span ng-if="$ctrl.model.Type === 1">
|
||||
|
@ -82,7 +92,7 @@
|
|||
</span>
|
||||
</span>
|
||||
</span>
|
||||
<span class="small text-muted">
|
||||
<span class="small text-muted" ng-if="$ctrl.model.Type !== 4">
|
||||
{{ $ctrl.model.URL | stripprotocol }}
|
||||
</span>
|
||||
</div>
|
||||
|
|
|
@ -89,7 +89,6 @@ angular.module('portainer.app').controller('EndpointListController', ['Datatable
|
|||
}
|
||||
|
||||
this.$onInit = function() {
|
||||
this.state.loading = true;
|
||||
var textFilter = DatatableService.getDataTableTextFilters(this.tableKey);
|
||||
this.state.paginatedItemLimit = PaginationService.getPaginationLimit(this.tableKey);
|
||||
if (textFilter !== null) {
|
||||
|
|
|
@ -25,7 +25,7 @@ angular.module('portainer.app').component('scheduleForm', {
|
|||
ctrl.formValues = {
|
||||
datetime: ctrl.model.CronExpression ? cronToDatetime(ctrl.model.CronExpression) : moment(),
|
||||
scheduleValue: ctrl.scheduleValues[0],
|
||||
cronMethod: 'basic'
|
||||
cronMethod: ctrl.model.Recurring ? 'advanced' : 'basic'
|
||||
};
|
||||
|
||||
function cronToDatetime(cron) {
|
||||
|
@ -38,7 +38,7 @@ angular.module('portainer.app').component('scheduleForm', {
|
|||
|
||||
function datetimeToCron(datetime) {
|
||||
var date = moment(datetime);
|
||||
return '0 '.concat(date.minutes(), ' ', date.hours(), ' ', date.date(), ' ', (date.month() + 1));
|
||||
return '0 '.concat(date.minutes(), ' ', date.hours(), ' ', date.date(), ' ', (date.month() + 1), ' *');
|
||||
}
|
||||
|
||||
this.action = function() {
|
||||
|
|
|
@ -1,4 +1,15 @@
|
|||
<form class="form-horizontal" name="scheduleForm">
|
||||
<div class="col-sm-12 form-section-title">
|
||||
Information
|
||||
</div>
|
||||
<div class="form-group">
|
||||
<span class="col-sm-12 text-muted small">
|
||||
<p>
|
||||
<i class="fa fa-exclamation-triangle orange-icon" aria-hidden="true" style="margin-right: 2px;"></i> Due to how schedules behave differently on Edge endpoints and other endpoints it is recommended to create specific schedules that will only target one
|
||||
type of endpoint.
|
||||
</p>
|
||||
</span>
|
||||
</div>
|
||||
<div class="col-sm-12 form-section-title">
|
||||
Schedule configuration
|
||||
</div>
|
||||
|
@ -114,7 +125,12 @@
|
|||
</div>
|
||||
<div class="form-group">
|
||||
<span class="col-sm-12 text-muted small">
|
||||
You can refer to the <a href="https://godoc.org/github.com/robfig/cron#hdr-CRON_Expression_Format" target="_blank">following documentation</a> to get more information about the supported cron expression format.
|
||||
<p>
|
||||
You can refer to the <a href="https://godoc.org/github.com/robfig/cron#hdr-CRON_Expression_Format" target="_blank">following documentation</a> to get more information about the supported cron expression format.
|
||||
</p>
|
||||
<p>
|
||||
<i class="fa fa-exclamation-triangle orange-icon" aria-hidden="true" style="margin-right: 2px;"></i> Edge endpoint schedules are managed by <code>cron</code> on the underlying host. You need to use a valid cron expression that is different from the documentation above.
|
||||
</p>
|
||||
</span>
|
||||
</div>
|
||||
</div>
|
||||
|
@ -123,6 +139,13 @@
|
|||
<div class="col-sm-12 form-section-title">
|
||||
Job configuration
|
||||
</div>
|
||||
<div class="form-group">
|
||||
<span class="col-sm-12 text-muted small">
|
||||
<p>
|
||||
<i class="fa fa-exclamation-triangle orange-icon" aria-hidden="true" style="margin-right: 2px;"></i> This configuration will be ignored for Edge endpoint schedules.
|
||||
</p>
|
||||
</span>
|
||||
</div>
|
||||
<!-- image-input -->
|
||||
<div class="form-group">
|
||||
<label for="schedule_image" class="col-sm-2 control-label text-left">Image</label>
|
||||
|
@ -195,8 +218,13 @@
|
|||
</div>
|
||||
<div class="form-group">
|
||||
<span class="col-sm-12 text-muted small">
|
||||
This schedule will be executed via a privileged container on the target hosts. You can access the host filesystem under the
|
||||
<code>/host</code> folder.
|
||||
<p>
|
||||
This schedule will be executed via a privileged container on the target hosts. You can access the host filesystem under the
|
||||
<code>/host</code> folder.
|
||||
</p>
|
||||
<p>
|
||||
<i class="fa fa-exclamation-triangle orange-icon" aria-hidden="true" style="margin-right: 2px;"></i> Edge endpoint schedules are managed by <code>cron</code> on the underlying host. You have full access to the filesystem without having to use the <code>/host</code> folder.
|
||||
</p>
|
||||
</span>
|
||||
</div>
|
||||
<div class="form-group">
|
||||
|
|
|
@ -124,6 +124,8 @@ angular.module('portainer.app')
|
|||
return 'Agent';
|
||||
} else if (type === 3) {
|
||||
return 'Azure ACI';
|
||||
} else if (type === 4) {
|
||||
return 'Edge Agent';
|
||||
}
|
||||
return '';
|
||||
};
|
||||
|
@ -133,6 +135,8 @@ angular.module('portainer.app')
|
|||
return function (type) {
|
||||
if (type === 3) {
|
||||
return 'fab fa-microsoft';
|
||||
} else if (type === 4) {
|
||||
return 'fa fa-cloud';
|
||||
}
|
||||
return 'fab fa-docker';
|
||||
};
|
||||
|
|
|
@ -1,4 +1,5 @@
|
|||
import { createStatus } from '../../docker/models/container';
|
||||
import _ from 'lodash-es';
|
||||
import {createStatus} from '../../docker/models/container';
|
||||
|
||||
export function ScheduleDefaultModel() {
|
||||
this.Name = '';
|
||||
|
@ -9,7 +10,7 @@ export function ScheduleDefaultModel() {
|
|||
}
|
||||
|
||||
function ScriptExecutionDefaultJobModel() {
|
||||
this.Image = '';
|
||||
this.Image = 'ubuntu:latest';
|
||||
this.Endpoints = [];
|
||||
this.FileContent = '';
|
||||
this.File = null;
|
||||
|
@ -23,14 +24,20 @@ export function ScheduleModel(data) {
|
|||
this.JobType = data.JobType;
|
||||
this.CronExpression = data.CronExpression;
|
||||
this.Created = data.Created;
|
||||
this.EdgeSchedule = data.EdgeSchedule;
|
||||
if (this.JobType === 1) {
|
||||
this.Job = new ScriptExecutionJobModel(data.ScriptExecutionJob);
|
||||
this.Job = new ScriptExecutionJobModel(data.ScriptExecutionJob, data.EdgeSchedule);
|
||||
}
|
||||
}
|
||||
|
||||
function ScriptExecutionJobModel(data) {
|
||||
function ScriptExecutionJobModel(data, edgeSchedule) {
|
||||
this.Image = data.Image;
|
||||
this.Endpoints = data.Endpoints;
|
||||
|
||||
if (edgeSchedule !== null) {
|
||||
this.Endpoints = _.concat(data.Endpoints, edgeSchedule.Endpoints);
|
||||
}
|
||||
|
||||
this.FileContent = '';
|
||||
this.Method = 'editor';
|
||||
this.RetryCount = data.RetryCount;
|
||||
|
@ -42,6 +49,7 @@ export function ScriptExecutionTaskModel(data) {
|
|||
this.EndpointId = data.EndpointId;
|
||||
this.Status = createStatus(data.Status);
|
||||
this.Created = data.Created;
|
||||
this.Edge = data.Edge;
|
||||
}
|
||||
|
||||
export function ScheduleCreateRequest(model) {
|
||||
|
|
|
@ -10,6 +10,7 @@ export function SettingsViewModel(data) {
|
|||
this.TemplatesURL = data.TemplatesURL;
|
||||
this.ExternalTemplates = data.ExternalTemplates;
|
||||
this.EnableHostManagementFeatures = data.EnableHostManagementFeatures;
|
||||
this.EdgeAgentCheckinInterval = data.EdgeAgentCheckinInterval;
|
||||
}
|
||||
|
||||
export function PublicSettingsViewModel(settings) {
|
||||
|
|
|
@ -13,8 +13,9 @@ angular.module('portainer.app')
|
|||
update: { method: 'PUT', params: { id: '@id' } },
|
||||
updateAccess: { method: 'PUT', params: { id: '@id', action: 'access' } },
|
||||
remove: { method: 'DELETE', params: { id: '@id'} },
|
||||
snapshots: { method: 'POST', params: { action: 'snapshot' }},
|
||||
snapshot: { method: 'POST', params: { id: '@id', action: 'snapshot' }},
|
||||
executeJob: { method: 'POST', ignoreLoadingBar: true, params: { id: '@id', action: 'job' } }
|
||||
snapshots: { method: 'POST', params: { action: 'snapshot' } },
|
||||
snapshot: { method: 'POST', params: { id: '@id', action: 'snapshot' } },
|
||||
executeJob: { method: 'POST', ignoreLoadingBar: true, params: { id: '@id', action: 'job' } },
|
||||
status: { method: 'GET', params: { id: '@id', action: 'status' } }
|
||||
});
|
||||
}]);
|
||||
|
|
|
@ -66,7 +66,12 @@ function EndpointServiceFactory($q, Endpoints, FileUploadService) {
|
|||
service.createRemoteEndpoint = function(name, type, URL, PublicURL, groupID, tags, TLS, TLSSkipVerify, TLSSkipClientVerify, TLSCAFile, TLSCertFile, TLSKeyFile) {
|
||||
var deferred = $q.defer();
|
||||
|
||||
FileUploadService.createEndpoint(name, type, 'tcp://' + URL, PublicURL, groupID, tags, TLS, TLSSkipVerify, TLSSkipClientVerify, TLSCAFile, TLSCertFile, TLSKeyFile)
|
||||
var endpointURL = URL;
|
||||
if (type !== 4) {
|
||||
endpointURL = 'tcp://' + URL;
|
||||
}
|
||||
|
||||
FileUploadService.createEndpoint(name, type, endpointURL, PublicURL, groupID, tags, TLS, TLSSkipVerify, TLSSkipClientVerify, TLSCAFile, TLSCertFile, TLSKeyFile)
|
||||
.then(function success(response) {
|
||||
deferred.resolve(response.data);
|
||||
})
|
||||
|
|
|
@ -1,8 +1,4 @@
|
|||
import {
|
||||
EndpointGroupModel,
|
||||
EndpointGroupCreateRequest,
|
||||
EndpointGroupUpdateRequest
|
||||
} from '../../models/group';
|
||||
import {EndpointGroupCreateRequest, EndpointGroupModel, EndpointGroupUpdateRequest} from '../../models/group';
|
||||
|
||||
angular.module('portainer.app')
|
||||
.factory('GroupService', ['$q', 'EndpointGroups',
|
||||
|
|
|
@ -171,6 +171,7 @@ function StateManagerFactory($q, SystemService, InfoHelper, LocalStorage, Settin
|
|||
var endpointAPIVersion = parseFloat(data.version.ApiVersion);
|
||||
state.endpoint.mode = endpointMode;
|
||||
state.endpoint.name = endpoint.Name;
|
||||
state.endpoint.type = endpoint.Type;
|
||||
state.endpoint.apiVersion = endpointAPIVersion;
|
||||
state.endpoint.extensions = assignExtensions(extensions);
|
||||
|
||||
|
|
|
@ -1,4 +1,4 @@
|
|||
import { EndpointSecurityFormData } from '../../../components/endpointSecurity/porEndpointSecurityModel';
|
||||
import {EndpointSecurityFormData} from '../../../components/endpointSecurity/porEndpointSecurityModel';
|
||||
|
||||
angular.module('portainer.app')
|
||||
.controller('CreateEndpointController', ['$q', '$scope', '$state', '$filter', 'clipboard', 'EndpointService', 'GroupService', 'TagService', 'Notifications',
|
||||
|
@ -27,6 +27,14 @@ function ($q, $scope, $state, $filter, clipboard, EndpointService, GroupService,
|
|||
$('#copyNotification').fadeOut(2000);
|
||||
};
|
||||
|
||||
$scope.setDefaultPortainerInstanceURL = function() {
|
||||
$scope.formValues.URL = window.location.origin;
|
||||
};
|
||||
|
||||
$scope.resetEndpointURL = function() {
|
||||
$scope.formValues.URL = '';
|
||||
};
|
||||
|
||||
$scope.addDockerEndpoint = function() {
|
||||
var name = $scope.formValues.Name;
|
||||
var URL = $filter('stripprotocol')($scope.formValues.URL);
|
||||
|
@ -56,6 +64,15 @@ function ($q, $scope, $state, $filter, clipboard, EndpointService, GroupService,
|
|||
addEndpoint(name, 2, URL, publicURL, groupId, tags, true, true, true, null, null, null);
|
||||
};
|
||||
|
||||
$scope.addEdgeAgentEndpoint = function() {
|
||||
var name = $scope.formValues.Name;
|
||||
var groupId = $scope.formValues.GroupId;
|
||||
var tags = $scope.formValues.Tags;
|
||||
var URL = $scope.formValues.URL;
|
||||
|
||||
addEndpoint(name, 4, URL, "", groupId, tags, false, false, false, null, null, null);
|
||||
};
|
||||
|
||||
$scope.addAzureEndpoint = function() {
|
||||
var name = $scope.formValues.Name;
|
||||
var applicationId = $scope.formValues.AzureApplicationId;
|
||||
|
@ -85,9 +102,13 @@ function ($q, $scope, $state, $filter, clipboard, EndpointService, GroupService,
|
|||
function addEndpoint(name, type, URL, PublicURL, groupId, tags, TLS, TLSSkipVerify, TLSSkipClientVerify, TLSCAFile, TLSCertFile, TLSKeyFile) {
|
||||
$scope.state.actionInProgress = true;
|
||||
EndpointService.createRemoteEndpoint(name, type, URL, PublicURL, groupId, tags, TLS, TLSSkipVerify, TLSSkipClientVerify, TLSCAFile, TLSCertFile, TLSKeyFile)
|
||||
.then(function success() {
|
||||
.then(function success(data) {
|
||||
Notifications.success('Endpoint created', name);
|
||||
$state.go('portainer.endpoints', {}, {reload: true});
|
||||
if (type === 4) {
|
||||
$state.go('portainer.endpoints.endpoint', { id: data.Id });
|
||||
} else {
|
||||
$state.go('portainer.endpoints', {}, {reload: true});
|
||||
}
|
||||
})
|
||||
.catch(function error(err) {
|
||||
Notifications.error('Failure', err, 'Unable to create endpoint');
|
||||
|
|
|
@ -16,7 +16,7 @@
|
|||
<div class="form-group"></div>
|
||||
<div class="form-group" style="margin-bottom: 0">
|
||||
<div class="boxselector_wrapper">
|
||||
<div>
|
||||
<div ng-click="resetEndpointURL()">
|
||||
<input type="radio" id="docker_endpoint" ng-model="state.EnvironmentType" value="docker">
|
||||
<label for="docker_endpoint">
|
||||
<div class="boxselector_header">
|
||||
|
@ -26,7 +26,7 @@
|
|||
<p>Docker environment</p>
|
||||
</label>
|
||||
</div>
|
||||
<div>
|
||||
<div ng-click="resetEndpointURL()">
|
||||
<input type="radio" id="agent_endpoint" ng-model="state.EnvironmentType" value="agent">
|
||||
<label for="agent_endpoint">
|
||||
<div class="boxselector_header">
|
||||
|
@ -36,6 +36,16 @@
|
|||
<p>Portainer agent</p>
|
||||
</label>
|
||||
</div>
|
||||
<div ng-click="setDefaultPortainerInstanceURL()">
|
||||
<input type="radio" id="edge_agent_endpoint" ng-model="state.EnvironmentType" value="edge_agent">
|
||||
<label for="edge_agent_endpoint">
|
||||
<div class="boxselector_header">
|
||||
<i class="fa fa-cloud" aria-hidden="true" style="margin-right: 2px;"></i>
|
||||
Edge Agent
|
||||
</div>
|
||||
<p>Portainer Edge agent</p>
|
||||
</label>
|
||||
</div>
|
||||
<div>
|
||||
<input type="radio" id="azure_endpoint" ng-model="state.EnvironmentType" value="azure">
|
||||
<label for="azure_endpoint">
|
||||
|
@ -77,6 +87,16 @@
|
|||
</span>
|
||||
</div>
|
||||
</div>
|
||||
<div ng-if="state.EnvironmentType === 'edge_agent'">
|
||||
<div class="col-sm-12 form-section-title" >
|
||||
Information
|
||||
</div>
|
||||
<div class="form-group">
|
||||
<span class="col-sm-12 text-muted small">
|
||||
Allows you to create an endpoint that can be registered with an Edge agent. The Edge agent will initiate the communications with the Portainer instance. All the required information on how to connect an Edge agent to this endpoint will be available after endpoint creation.
|
||||
</span>
|
||||
</div>
|
||||
</div>
|
||||
<div ng-if="state.EnvironmentType === 'azure'">
|
||||
<div class="col-sm-12 form-section-title" >
|
||||
Information
|
||||
|
@ -138,6 +158,26 @@
|
|||
</div>
|
||||
</div>
|
||||
<!-- !endpoint-url-input -->
|
||||
<!-- portainer-instance-input -->
|
||||
<div ng-if="state.EnvironmentType === 'edge_agent'">
|
||||
<div class="form-group">
|
||||
<label for="endpoint_url" class="col-sm-3 col-lg-2 control-label text-left">
|
||||
Portainer server URL
|
||||
<portainer-tooltip position="bottom" message="URL of the Portainer instance that the agent will use to initiate the communications."></portainer-tooltip>
|
||||
</label>
|
||||
<div class="col-sm-9 col-lg-10">
|
||||
<input type="text" class="form-control" name="endpoint_url" ng-model="formValues.URL" placeholder="e.g. 10.0.0.10:9000 or portainer.mydomain.com" required>
|
||||
</div>
|
||||
</div>
|
||||
<div class="form-group" ng-show="endpointCreationForm.endpoint_url.$invalid">
|
||||
<div class="col-sm-12 small text-warning">
|
||||
<div ng-messages="endpointCreationForm.endpoint_url.$error">
|
||||
<p ng-message="required"><i class="fa fa-exclamation-triangle" aria-hidden="true"></i> This field is required.</p>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
<!-- !portainer-instance-input -->
|
||||
<!-- endpoint-public-url-input -->
|
||||
<div ng-if="state.EnvironmentType === 'docker' || state.EnvironmentType === 'agent'">
|
||||
<div class="form-group">
|
||||
|
@ -238,6 +278,10 @@
|
|||
<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>
|
||||
</button>
|
||||
<button ng-if="state.EnvironmentType === 'edge_agent'" type="submit" class="btn btn-primary btn-sm" ng-disabled="state.actionInProgress || !endpointCreationForm.$valid" ng-click="addEdgeAgentEndpoint()" 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>
|
||||
</button>
|
||||
<button ng-if="state.EnvironmentType === 'azure'" type="submit" class="btn btn-primary btn-sm" ng-disabled="state.actionInProgress || !endpointCreationForm.$valid" ng-click="addAzureEndpoint()" 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>
|
||||
|
|
|
@ -1,10 +1,108 @@
|
|||
<rd-header>
|
||||
<rd-header-title title-text="Endpoint details"></rd-header-title>
|
||||
<rd-header-title title-text="Endpoint details">
|
||||
<a data-toggle="tooltip" title="Refresh" ui-sref="portainer.endpoints.endpoint({id: endpoint.Id})" ui-sref-opts="{reload: true}">
|
||||
<i class="fa fa-sync" aria-hidden="true"></i>
|
||||
</a>
|
||||
</rd-header-title>
|
||||
<rd-header-content>
|
||||
<a ui-sref="portainer.endpoints">Endpoints</a> > <a ui-sref="portainer.endpoints.endpoint({id: endpoint.Id})">{{ endpoint.Name }}</a>
|
||||
</rd-header-content>
|
||||
</rd-header>
|
||||
|
||||
<div class="row">
|
||||
<information-panel ng-if="endpoint.Type === 4 && endpoint.EdgeID" title-text="Edge information">
|
||||
<span class="small text-muted">
|
||||
<p>
|
||||
<i class="fa fa-info-circle blue-icon" aria-hidden="true" style="margin-right: 2px;"></i>
|
||||
This Edge endpoint is associated to an Edge environment.
|
||||
</p>
|
||||
<p>
|
||||
Edge key: <code>{{ endpoint.EdgeKey }}</code>
|
||||
</p>
|
||||
<p>
|
||||
Edge identifier: <code>{{ endpoint.EdgeID }}</code>
|
||||
</p>
|
||||
</span>
|
||||
</information-panel>
|
||||
<information-panel ng-if="endpoint.Type === 4 && !endpoint.EdgeID" title-text="Deploy an agent">
|
||||
<span class="small text-muted">
|
||||
<p>
|
||||
<i class="fa fa-info-circle blue-icon" aria-hidden="true" style="margin-right: 2px;"></i>
|
||||
Deploy the Edge agent on your remote Docker environment using the following command
|
||||
</p>
|
||||
<div style="margin-top: 10px;">
|
||||
<uib-tabset active="state.deploymentTab">
|
||||
<uib-tab index="0" heading="Standalone">
|
||||
<code style=display:block;white-space:pre-wrap>
|
||||
docker run -d -v /var/run/docker.sock:/var/run/docker.sock \
|
||||
-v /var/lib/docker/volumes:/var/lib/docker/volumes \
|
||||
-v /:/host \
|
||||
--restart always \
|
||||
-e EDGE=1 \
|
||||
-e EDGE_ID={{ randomEdgeID }} \
|
||||
-e CAP_HOST_MANAGEMENT=1 \
|
||||
-p 8000:80 \
|
||||
-v portainer_agent_data:/data \
|
||||
--name portainer_edge_agent \
|
||||
portainer/agent
|
||||
</code>
|
||||
</uib-tab>
|
||||
<uib-tab index="1" heading="Swarm">
|
||||
<code style=display:block;white-space:pre-wrap>
|
||||
docker network create \
|
||||
--driver overlay \
|
||||
portainer_agent_network;
|
||||
|
||||
docker service create \
|
||||
--name portainer_edge_agent \
|
||||
--network portainer_agent_network \
|
||||
-e AGENT_CLUSTER_ADDR=tasks.portainer_edge_agent \
|
||||
-e EDGE=1 \
|
||||
-e EDGE_ID={{ randomEdgeID }} \
|
||||
-e CAP_HOST_MANAGEMENT=1 \
|
||||
--mode global \
|
||||
--publish mode=host,published=8000,target=80 \
|
||||
--constraint 'node.platform.os == linux' \
|
||||
--mount type=bind,src=//var/run/docker.sock,dst=/var/run/docker.sock \
|
||||
--mount type=bind,src=//var/lib/docker/volumes,dst=/var/lib/docker/volumes \
|
||||
--mount type=bind,src=//,dst=/host \
|
||||
--mount type=volume,src=portainer_agent_data,dst=/data \
|
||||
portainer/agent
|
||||
</code>
|
||||
</uib-tab>
|
||||
</uib-tabset>
|
||||
<div style="margin-top: 10px;">
|
||||
<span class="btn btn-primary btn-sm" ng-click="copyEdgeAgentDeploymentCommand()"><i class="fa fa-copy space-right" aria-hidden="true"></i>Copy command</span>
|
||||
<span id="copyNotificationDeploymentCommand" style="margin-left: 7px; display: none; color: #23ae89;">
|
||||
<i class="fa fa-check" aria-hidden="true" ></i> copied
|
||||
</span>
|
||||
</div>
|
||||
</div>
|
||||
<div class="col-sm-12 form-section-title" style="margin-top: 25px;">
|
||||
Join token
|
||||
</div>
|
||||
<p>
|
||||
<i class="fa fa-info-circle blue-icon" aria-hidden="true" style="margin-right: 2px;"></i>
|
||||
Use the following join token to associate the Edge agent with this endpoint
|
||||
</p>
|
||||
<p>
|
||||
The agent will communicate with Portainer via <u>{{ edgeKeyDetails.instanceURL }}</u> and <u>tcp://{{ edgeKeyDetails.tunnelServerAddr }}</u>
|
||||
</p>
|
||||
<div style="margin-top: 10px; overflow-wrap: break-word;">
|
||||
<code>
|
||||
{{ endpoint.EdgeKey }}
|
||||
</code>
|
||||
<div style="margin-top: 10px;">
|
||||
<span class="btn btn-primary btn-sm" ng-click="copyEdgeAgentKey()"><i class="fa fa-copy space-right" aria-hidden="true"></i>Copy token</span>
|
||||
<span id="copyNotificationEdgeKey" style="margin-left: 7px; display: none; color: #23ae89;">
|
||||
<i class="fa fa-check" aria-hidden="true" ></i> copied
|
||||
</span>
|
||||
</div>
|
||||
</div>
|
||||
</span>
|
||||
</information-panel>
|
||||
</div>
|
||||
|
||||
<div class="row">
|
||||
<div class="col-lg-12 col-md-12 col-xs-12">
|
||||
<rd-widget>
|
||||
|
@ -22,7 +120,7 @@
|
|||
</div>
|
||||
<!-- !name-input -->
|
||||
<!-- endpoint-url-input -->
|
||||
<div class="form-group">
|
||||
<div class="form-group" ng-if="endpoint.Type !== 4">
|
||||
<label for="endpoint_url" class="col-sm-3 col-lg-2 control-label text-left">
|
||||
Endpoint URL
|
||||
<portainer-tooltip position="bottom" message="URL or IP address of a Docker host. The Docker API must be exposed over a TCP port. Please refer to the Docker documentation to configure it."></portainer-tooltip>
|
||||
|
@ -70,7 +168,7 @@
|
|||
</div>
|
||||
<!-- !tags -->
|
||||
<!-- endpoint-security -->
|
||||
<div ng-if="endpointType === 'remote' && endpoint.Type !== 3">
|
||||
<div ng-if="endpointType === 'remote' && endpoint.Type !== 3 && endpoint.Type !== 4">
|
||||
<div class="col-sm-12 form-section-title">
|
||||
Security
|
||||
</div>
|
||||
|
|
|
@ -1,8 +1,10 @@
|
|||
import { EndpointSecurityFormData } from '../../../components/endpointSecurity/porEndpointSecurityModel';
|
||||
import _ from 'lodash-es';
|
||||
import uuidv4 from 'uuid/v4';
|
||||
import {EndpointSecurityFormData} from '../../../components/endpointSecurity/porEndpointSecurityModel';
|
||||
|
||||
angular.module('portainer.app')
|
||||
.controller('EndpointController', ['$q', '$scope', '$state', '$transition$', '$filter', 'EndpointService', 'GroupService', 'TagService', 'EndpointProvider', 'Notifications',
|
||||
function ($q, $scope, $state, $transition$, $filter, EndpointService, GroupService, TagService, EndpointProvider, Notifications) {
|
||||
.controller('EndpointController', ['$q', '$scope', '$state', '$transition$', '$filter', 'clipboard', 'EndpointService', 'GroupService', 'TagService', 'EndpointProvider', 'Notifications',
|
||||
function ($q, $scope, $state, $transition$, $filter, clipboard, EndpointService, GroupService, TagService, EndpointProvider, Notifications) {
|
||||
|
||||
if (!$scope.applicationState.application.endpointManagement) {
|
||||
$state.go('portainer.endpoints');
|
||||
|
@ -10,13 +12,28 @@ function ($q, $scope, $state, $transition$, $filter, EndpointService, GroupServi
|
|||
|
||||
$scope.state = {
|
||||
uploadInProgress: false,
|
||||
actionInProgress: false
|
||||
actionInProgress: false,
|
||||
deploymentTab: 0
|
||||
};
|
||||
|
||||
$scope.formValues = {
|
||||
SecurityFormData: new EndpointSecurityFormData()
|
||||
};
|
||||
|
||||
$scope.copyEdgeAgentDeploymentCommand = function() {
|
||||
if ($scope.state.deploymentTab === 0) {
|
||||
clipboard.copyText('docker run -d -v /var/run/docker.sock:/var/run/docker.sock -v /var/lib/docker/volumes:/var/lib/docker/volumes -v /:/host --restart always -e EDGE=1 -e EDGE_ID=' + $scope.randomEdgeID +' -e CAP_HOST_MANAGEMENT=1 -p 8000:80 -v portainer_agent_data:/data --name portainer_edge_agent portainer/agent');
|
||||
} else {
|
||||
clipboard.copyText('docker network create --driver overlay portainer_agent_network; docker service create --name portainer_edge_agent --network portainer_agent_network -e AGENT_CLUSTER_ADDR=tasks.portainer_edge_agent -e EDGE=1 -e EDGE_ID=' + $scope.randomEdgeID +' -e CAP_HOST_MANAGEMENT=1 --mode global --publish mode=host,published=8000,target=80 --constraint \'node.platform.os == linux\' --mount type=bind,src=//var/run/docker.sock,dst=/var/run/docker.sock --mount type=bind,src=//var/lib/docker/volumes,dst=/var/lib/docker/volume --mount type=bind,src=//,dst=/host --mount type=volume,src=portainer_agent_data,dst=/data portainer/agent');
|
||||
}
|
||||
$('#copyNotificationDeploymentCommand').show().fadeOut(2500);
|
||||
};
|
||||
|
||||
$scope.copyEdgeAgentKey = function() {
|
||||
clipboard.copyText($scope.endpoint.EdgeKey);
|
||||
$('#copyNotificationEdgeKey').show().fadeOut(2500);
|
||||
};
|
||||
|
||||
$scope.updateEndpoint = function() {
|
||||
var endpoint = $scope.endpoint;
|
||||
var securityData = $scope.formValues.SecurityFormData;
|
||||
|
@ -61,6 +78,20 @@ function ($q, $scope, $state, $transition$, $filter, EndpointService, GroupServi
|
|||
});
|
||||
};
|
||||
|
||||
function decodeEdgeKey(key) {
|
||||
let keyInformation = {};
|
||||
|
||||
if (key === "") {
|
||||
return keyInformation;
|
||||
}
|
||||
|
||||
let decodedKey = _.split(atob(key), "|");
|
||||
keyInformation.instanceURL = decodedKey[0];
|
||||
keyInformation.tunnelServerAddr = decodedKey[1];
|
||||
|
||||
return keyInformation;
|
||||
}
|
||||
|
||||
function initView() {
|
||||
$q.all({
|
||||
endpoint: EndpointService.endpoint($transition$.params().id),
|
||||
|
@ -75,6 +106,10 @@ function ($q, $scope, $state, $transition$, $filter, EndpointService, GroupServi
|
|||
$scope.endpointType = 'remote';
|
||||
}
|
||||
endpoint.URL = $filter('stripprotocol')(endpoint.URL);
|
||||
if (endpoint.Type === 4) {
|
||||
$scope.edgeKeyDetails = decodeEdgeKey(endpoint.EdgeKey);
|
||||
$scope.randomEdgeID = uuidv4();
|
||||
}
|
||||
$scope.endpoint = endpoint;
|
||||
$scope.groups = data.groups;
|
||||
$scope.availableTags = data.tags;
|
||||
|
|
|
@ -24,7 +24,12 @@
|
|||
</span>
|
||||
</information-panel>
|
||||
|
||||
<div class="row">
|
||||
<div class="row" style="width:100%; height:100%; text-align: center; display: flex; flex-direction: column; align-items: center; justify-content: center;" ng-if="state.connectingToEdgeEndpoint">
|
||||
Connecting to the Edge endpoint...
|
||||
<i class="fa fa-cog fa-spin" style="margin-left: 5px"></i>
|
||||
</div>
|
||||
|
||||
<div class="row" ng-if="!state.connectingToEdgeEndpoint">
|
||||
<div class="col-sm-12">
|
||||
<endpoint-list
|
||||
title-text="Endpoints" title-icon="fa-plug"
|
||||
|
|
|
@ -1,6 +1,10 @@
|
|||
angular.module('portainer.app')
|
||||
.controller('HomeController', ['$q', '$scope', '$state', 'Authentication', 'EndpointService', 'EndpointHelper', 'GroupService', 'Notifications', 'EndpointProvider', 'StateManager', 'LegacyExtensionManager', 'ModalService', 'MotdService', 'SystemService',
|
||||
function($q, $scope, $state, Authentication, EndpointService, EndpointHelper, GroupService, Notifications, EndpointProvider, StateManager, LegacyExtensionManager, ModalService, MotdService, SystemService) {
|
||||
.controller('HomeController', ['$q', '$scope', '$state', '$interval', 'Authentication', 'EndpointService', 'EndpointHelper', 'GroupService', 'Notifications', 'EndpointProvider', 'StateManager', 'LegacyExtensionManager', 'ModalService', 'MotdService', 'SystemService',
|
||||
function($q, $scope, $state, $interval, Authentication, EndpointService, EndpointHelper, GroupService, Notifications, EndpointProvider, StateManager, LegacyExtensionManager, ModalService, MotdService, SystemService) {
|
||||
|
||||
$scope.state = {
|
||||
connectingToEdgeEndpoint: false,
|
||||
};
|
||||
|
||||
$scope.goToEdit = function(id) {
|
||||
$state.go('portainer.endpoints.endpoint', { id: id });
|
||||
|
@ -9,10 +13,12 @@ angular.module('portainer.app')
|
|||
$scope.goToDashboard = function(endpoint) {
|
||||
if (endpoint.Type === 3) {
|
||||
return switchToAzureEndpoint(endpoint);
|
||||
} else if (endpoint.Type === 4) {
|
||||
return switchToEdgeEndpoint(endpoint);
|
||||
}
|
||||
|
||||
checkEndpointStatus(endpoint)
|
||||
.then(function sucess() {
|
||||
.then(function success() {
|
||||
return switchToDockerEndpoint(endpoint);
|
||||
}).catch(function error(err) {
|
||||
Notifications.error('Failure', err, 'Unable to verify endpoint status');
|
||||
|
@ -41,7 +47,7 @@ angular.module('portainer.app')
|
|||
|
||||
var status = 1;
|
||||
SystemService.ping(endpoint.Id)
|
||||
.then(function sucess() {
|
||||
.then(function success() {
|
||||
status = 1;
|
||||
}).catch(function error() {
|
||||
status = 2;
|
||||
|
@ -52,7 +58,7 @@ angular.module('portainer.app')
|
|||
}
|
||||
|
||||
EndpointService.updateEndpoint(endpoint.Id, { Status: status })
|
||||
.then(function sucess() {
|
||||
.then(function success() {
|
||||
deferred.resolve(endpoint);
|
||||
}).catch(function error(err) {
|
||||
deferred.reject({ msg: 'Unable to update endpoint status', err: err });
|
||||
|
@ -75,11 +81,33 @@ angular.module('portainer.app')
|
|||
});
|
||||
}
|
||||
|
||||
function switchToEdgeEndpoint(endpoint) {
|
||||
if (!endpoint.EdgeID) {
|
||||
$state.go('portainer.endpoints.endpoint', { id: endpoint.Id });
|
||||
return;
|
||||
}
|
||||
|
||||
$scope.state.connectingToEdgeEndpoint = true;
|
||||
SystemService.ping(endpoint.Id)
|
||||
.then(function success() {
|
||||
endpoint.Status = 1;
|
||||
})
|
||||
.catch(function error() {
|
||||
endpoint.Status = 2;
|
||||
})
|
||||
.finally(function final() {
|
||||
switchToDockerEndpoint(endpoint);
|
||||
});
|
||||
}
|
||||
|
||||
|
||||
function switchToDockerEndpoint(endpoint) {
|
||||
if (endpoint.Status === 2 && endpoint.Snapshots[0] && endpoint.Snapshots[0].Swarm === true) {
|
||||
$scope.state.connectingToEdgeEndpoint = false;
|
||||
Notifications.error('Failure', '', 'Endpoint is unreachable. Connect to another swarm manager.');
|
||||
return;
|
||||
} else if (endpoint.Status === 2 && !endpoint.Snapshots[0]) {
|
||||
$scope.state.connectingToEdgeEndpoint = false;
|
||||
Notifications.error('Failure', '', 'Endpoint is unreachable and there is no snapshot available for offline browsing.');
|
||||
return;
|
||||
}
|
||||
|
@ -97,6 +125,9 @@ angular.module('portainer.app')
|
|||
})
|
||||
.catch(function error(err) {
|
||||
Notifications.error('Failure', err, 'Unable to connect to the Docker endpoint');
|
||||
})
|
||||
.finally(function final() {
|
||||
$scope.state.connectingToEdgeEndpoint = false;
|
||||
});
|
||||
}
|
||||
|
||||
|
|
|
@ -1,4 +1,4 @@
|
|||
import { ScheduleDefaultModel } from '../../../models/schedule';
|
||||
import {ScheduleDefaultModel} from '../../../models/schedule';
|
||||
|
||||
angular.module('portainer.app')
|
||||
.controller('CreateScheduleController', ['$q', '$scope', '$state', 'Notifications', 'EndpointService', 'GroupService', 'ScheduleService',
|
||||
|
|
|
@ -56,6 +56,7 @@
|
|||
table-key="schedule-tasks"
|
||||
order-by="Status" reverse-order="true"
|
||||
go-to-container-logs="goToContainerLogs"
|
||||
get-edge-task-logs="getEdgeTaskLogs"
|
||||
></schedule-tasks-datatable>
|
||||
</uib-tab>
|
||||
|
||||
|
|
|
@ -1,6 +1,6 @@
|
|||
angular.module('portainer.app')
|
||||
.controller('ScheduleController', ['$q', '$scope', '$transition$', '$state', 'Notifications', 'EndpointService', 'GroupService', 'ScheduleService', 'EndpointProvider',
|
||||
function ($q, $scope, $transition$, $state, Notifications, EndpointService, GroupService, ScheduleService, EndpointProvider) {
|
||||
.controller('ScheduleController', ['$q', '$scope', '$transition$', '$state', 'Notifications', 'EndpointService', 'GroupService', 'ScheduleService', 'EndpointProvider', 'HostBrowserService', 'FileSaver',
|
||||
function ($q, $scope, $transition$, $state, Notifications, EndpointService, GroupService, ScheduleService, EndpointProvider, HostBrowserService, FileSaver) {
|
||||
|
||||
$scope.state = {
|
||||
actionInProgress: false
|
||||
|
@ -8,6 +8,7 @@ function ($q, $scope, $transition$, $state, Notifications, EndpointService, Grou
|
|||
|
||||
$scope.update = update;
|
||||
$scope.goToContainerLogs = goToContainerLogs;
|
||||
$scope.getEdgeTaskLogs = getEdgeTaskLogs;
|
||||
|
||||
function update() {
|
||||
var model = $scope.schedule;
|
||||
|
@ -31,6 +32,26 @@ function ($q, $scope, $transition$, $state, Notifications, EndpointService, Grou
|
|||
$state.go('docker.containers.container.logs', { id: containerId });
|
||||
}
|
||||
|
||||
function getEdgeTaskLogs(endpointId, scheduleId) {
|
||||
var currentId = EndpointProvider.endpointID();
|
||||
EndpointProvider.setEndpointID(endpointId);
|
||||
|
||||
var filePath = '/host/opt/portainer/scripts/' + scheduleId + '.log';
|
||||
HostBrowserService.get(filePath)
|
||||
.then(function onFileReceived(data) {
|
||||
var downloadData = new Blob([data.file], {
|
||||
type: 'text/plain;charset=utf-8'
|
||||
});
|
||||
FileSaver.saveAs(downloadData, scheduleId + '.log');
|
||||
})
|
||||
.catch(function notifyOnError(err) {
|
||||
Notifications.error('Failure', err, 'Unable to download file');
|
||||
})
|
||||
.finally(function final() {
|
||||
EndpointProvider.setEndpointID(currentId);
|
||||
});
|
||||
}
|
||||
|
||||
function associateEndpointsToTasks(tasks, endpoints) {
|
||||
for (var i = 0; i < tasks.length; i++) {
|
||||
var task = tasks[i];
|
||||
|
|
|
@ -112,7 +112,23 @@
|
|||
</label>
|
||||
</div>
|
||||
</div>
|
||||
<!-- security -->
|
||||
<!-- !security -->
|
||||
<!-- edge -->
|
||||
<div class="col-sm-12 form-section-title">
|
||||
Edge
|
||||
</div>
|
||||
<div class="form-group">
|
||||
<div class="col-sm-12">
|
||||
<label for="edge_checkin" class="col-sm-3 control-label text-left">
|
||||
Edge agent poll frequency
|
||||
<portainer-tooltip position="bottom" message="Specify the interval used by each Edge agent to checkin with the Portainer instance"></portainer-tooltip>
|
||||
</label>
|
||||
<div class="col-sm-9">
|
||||
<select id="edge_checkin" class="form-control" ng-model="settings.EdgeAgentCheckinInterval" ng-options="+(opt.value) as opt.key for opt in state.availableEdgeAgentCheckinOptions"></select>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
<!-- !edge -->
|
||||
<!-- actions -->
|
||||
<div class="form-group">
|
||||
<div class="col-sm-12">
|
||||
|
|
|
@ -3,7 +3,21 @@ angular.module('portainer.app')
|
|||
function ($scope, $state, Notifications, SettingsService, StateManager) {
|
||||
|
||||
$scope.state = {
|
||||
actionInProgress: false
|
||||
actionInProgress: false,
|
||||
availableEdgeAgentCheckinOptions: [
|
||||
{
|
||||
key: '5 seconds',
|
||||
value: 5
|
||||
},
|
||||
{
|
||||
key: '10 seconds',
|
||||
value: 10
|
||||
},
|
||||
{
|
||||
key: '30 seconds',
|
||||
value: 30
|
||||
},
|
||||
]
|
||||
};
|
||||
|
||||
$scope.formValues = {
|
||||
|
|
|
@ -158,14 +158,20 @@ function ($q, $scope, $state, $transition$, StackService, NodeService, ServiceSe
|
|||
function loadStack(id) {
|
||||
var agentProxy = $scope.applicationState.endpoint.mode.agentProxy;
|
||||
|
||||
EndpointService.endpoints()
|
||||
.then(function success(data) {
|
||||
$scope.endpoints = data.value;
|
||||
})
|
||||
.catch(function error(err) {
|
||||
Notifications.error('Failure', err, 'Unable to retrieve endpoints');
|
||||
});
|
||||
|
||||
$q.all({
|
||||
stack: StackService.stack(id),
|
||||
endpoints: EndpointService.endpoints(),
|
||||
groups: GroupService.groups()
|
||||
})
|
||||
.then(function success(data) {
|
||||
var stack = data.stack;
|
||||
$scope.endpoints = data.endpoints.value;
|
||||
$scope.groups = data.groups;
|
||||
$scope.stack = stack;
|
||||
|
||||
|
|
Loading…
Add table
Add a link
Reference in a new issue