mirror of https://github.com/portainer/portainer
parent
e6dee37af0
commit
fa9ba303aa
17
app/app.js
17
app/app.js
|
@ -36,6 +36,7 @@ angular.module('portainer', [
|
||||||
'swarm',
|
'swarm',
|
||||||
'network',
|
'network',
|
||||||
'networks',
|
'networks',
|
||||||
|
'node',
|
||||||
'createNetwork',
|
'createNetwork',
|
||||||
'task',
|
'task',
|
||||||
'templates',
|
'templates',
|
||||||
|
@ -398,6 +399,22 @@ angular.module('portainer', [
|
||||||
requiresLogin: true
|
requiresLogin: true
|
||||||
}
|
}
|
||||||
})
|
})
|
||||||
|
.state('node', {
|
||||||
|
url: '^/nodes/:id/',
|
||||||
|
views: {
|
||||||
|
"content": {
|
||||||
|
templateUrl: 'app/components/node/node.html',
|
||||||
|
controller: 'NodeController'
|
||||||
|
},
|
||||||
|
"sidebar": {
|
||||||
|
templateUrl: 'app/components/sidebar/sidebar.html',
|
||||||
|
controller: 'SidebarController'
|
||||||
|
}
|
||||||
|
},
|
||||||
|
data: {
|
||||||
|
requiresLogin: true
|
||||||
|
}
|
||||||
|
})
|
||||||
.state('services', {
|
.state('services', {
|
||||||
url: '/services/',
|
url: '/services/',
|
||||||
views: {
|
views: {
|
||||||
|
|
|
@ -0,0 +1,262 @@
|
||||||
|
<rd-header>
|
||||||
|
<rd-header-title title="Node details">
|
||||||
|
<a data-toggle="tooltip" title="Refresh" ui-sref="node({id: node.Id})" ui-sref-opts="{reload: true}">
|
||||||
|
<i class="fa fa-refresh" aria-hidden="true"></i>
|
||||||
|
</a>
|
||||||
|
</rd-header-title>
|
||||||
|
<rd-header-content>
|
||||||
|
<a ui-sref="swarm">Swarm nodes</a> > <a ui-sref="node({id: node.Id})">{{ node.Hostname }}</a>
|
||||||
|
</rd-header-content>
|
||||||
|
</rd-header>
|
||||||
|
|
||||||
|
<div class="row" ng-if="!node">
|
||||||
|
<div class="col-lg-12 col-md-12 col-sm-12 col-xs-12">
|
||||||
|
<div ng-if="loading">
|
||||||
|
<i class="fa fa-cog fa-spin"></i> Loading..
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<rd-widget ng-if="!loading">
|
||||||
|
<rd-widget-header icon="fa-object-group" title="Node does not exist"></rd-widget-header>
|
||||||
|
<rd-widget-body>
|
||||||
|
<p>It looks like the node you wish to inspect does not exist.</p>
|
||||||
|
</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>
|
||||||
|
<rd-widget-header icon="fa-object-group" title="Node specification"></rd-widget-header>
|
||||||
|
<rd-widget-body classes="no-padding">
|
||||||
|
<table class="table">
|
||||||
|
<tbody>
|
||||||
|
<tr>
|
||||||
|
<td>Name</td>
|
||||||
|
<td>
|
||||||
|
<input type="text" class="input-sm" ng-model="node.Name" placeholder="e.g. my-manager" ng-change="updateNodeAttribute(node, 'Name')">
|
||||||
|
</td>
|
||||||
|
</tr>
|
||||||
|
<tr>
|
||||||
|
<td>Host name</td>
|
||||||
|
<td>{{ node.Hostname }}</td>
|
||||||
|
</tr>
|
||||||
|
<tr>
|
||||||
|
<td>Role</td>
|
||||||
|
<td>{{ node.Role }}</td>
|
||||||
|
</tr>
|
||||||
|
<tr>
|
||||||
|
<td>Availability</td>
|
||||||
|
<td>
|
||||||
|
<div class="input-group input-group-sm">
|
||||||
|
<select name="nodeAvailability" class="selectpicker form-control" ng-model="node.Availability" ng-change="updateNodeAttribute(node, 'Availability')">
|
||||||
|
<option value="active">Active</option>
|
||||||
|
<option value="pause">Pause</option>
|
||||||
|
<option value="drain">Drain</option>
|
||||||
|
</select>
|
||||||
|
</div>
|
||||||
|
</td>
|
||||||
|
</tr>
|
||||||
|
<tr>
|
||||||
|
<td>Status</td>
|
||||||
|
<td><span class="label label-{{ node.Status|nodestatusbadge }}">{{ node.Status }}</span></td>
|
||||||
|
</tr>
|
||||||
|
</tbody>
|
||||||
|
</table>
|
||||||
|
</rd-widget-body>
|
||||||
|
<rd-widget-footer>
|
||||||
|
<p class="small text-muted">
|
||||||
|
View the Docker Swarm mode Node documentation <a href="https://docs.docker.com/engine/swarm/manage-nodes/" target="self">here</a>.
|
||||||
|
</p>
|
||||||
|
<div class="btn-toolbar" role="toolbar">
|
||||||
|
<div class="btn-group" role="group">
|
||||||
|
<button type="button" class="btn btn-primary" ng-disabled="!hasChanges(node, ['Name', 'Availability'])" ng-click="updateNode(node)">Apply changes</button>
|
||||||
|
<button type="button" class="btn btn-default dropdown-toggle" data-toggle="dropdown" aria-haspopup="true" aria-expanded="false">
|
||||||
|
<span class="caret"></span>
|
||||||
|
</button>
|
||||||
|
<ul class="dropdown-menu">
|
||||||
|
<li><a ng-click="cancelChanges(node)">Reset changes</a></li>
|
||||||
|
</ul>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</rd-widget-footer>
|
||||||
|
</rd-widget>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<div class="row" ng-if="node && node.Role === 'manager'">
|
||||||
|
<div class="col-lg-12 col-md-12 col-sm-12 col-xs-12">
|
||||||
|
<rd-widget>
|
||||||
|
<rd-widget-header icon="fa-object-group" title="Manager status"></rd-widget-header>
|
||||||
|
<rd-widget-body classes="no-padding">
|
||||||
|
<table class="table">
|
||||||
|
<tbody>
|
||||||
|
<tr>
|
||||||
|
<td>Leader</td>
|
||||||
|
<td>
|
||||||
|
<span ng-if="node.Leader"><i class="fa fa-check green-icon" aria-hidden="true"></i> Yes</span>
|
||||||
|
<span ng-if="!node.Leader"><i class="fa fa-times red-icon" aria-hidden="true"></i> No</span>
|
||||||
|
</td>
|
||||||
|
</tr>
|
||||||
|
<tr>
|
||||||
|
<td>Reachability</td>
|
||||||
|
<td>{{ node.Reachability }}</td>
|
||||||
|
</tr>
|
||||||
|
<tr>
|
||||||
|
<td>Manager address</td>
|
||||||
|
<td>{{ node.ManagerAddr }}</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>
|
||||||
|
<rd-widget-header icon="fa-object-group" title="Node description"></rd-widget-header>
|
||||||
|
<rd-widget-body classes="no-padding">
|
||||||
|
<table class="table">
|
||||||
|
<tbody>
|
||||||
|
<tr>
|
||||||
|
<td>CPU</td>
|
||||||
|
<td>{{ node.CPUs / 1000000000 }}</td>
|
||||||
|
</tr>
|
||||||
|
<tr>
|
||||||
|
<td>Memory</td>
|
||||||
|
<td>{{ node.Memory|humansize: 2 }}</td>
|
||||||
|
</tr>
|
||||||
|
<tr>
|
||||||
|
<td>Platform</td>
|
||||||
|
<td>{{ node.PlatformOS }} {{ node.PlatformArchitecture }} </td>
|
||||||
|
</tr>
|
||||||
|
<tr>
|
||||||
|
<td>Docker Engine version</td>
|
||||||
|
<td>{{ node.EngineVersion }}</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>
|
||||||
|
<rd-widget-header icon="fa-tasks" title="Node labels">
|
||||||
|
<div class="nopadding">
|
||||||
|
<a class="btn btn-default btn-sm pull-right" ng-click="addLabel(node)">
|
||||||
|
<i class="fa fa-plus-circle" aria-hidden="true"></i> label
|
||||||
|
</a>
|
||||||
|
</div>
|
||||||
|
</rd-widget-header>
|
||||||
|
<rd-widget-body ng-if="!node.Labels || node.Labels.length === 0">
|
||||||
|
<p>There are no labels for this node.</p>
|
||||||
|
</rd-widget-body>
|
||||||
|
<rd-widget-body classes="no-padding" ng-if="node.Labels && node.Labels.length > 0">
|
||||||
|
<table class="table">
|
||||||
|
<thead>
|
||||||
|
<tr>
|
||||||
|
<th>Label</th>
|
||||||
|
<th>Value</th>
|
||||||
|
</tr>
|
||||||
|
</thead>
|
||||||
|
<tbody>
|
||||||
|
<tr ng-repeat="label in node.Labels">
|
||||||
|
<td>
|
||||||
|
<div class="input-group input-group-sm">
|
||||||
|
<span class="input-group-addon fit-text-size">name</span>
|
||||||
|
<input type="text" class="form-control" ng-model="label.key" placeholder="e.g. com.example.foo" ng-change="updateLabel(node, label)">
|
||||||
|
</div>
|
||||||
|
</td>
|
||||||
|
<td>
|
||||||
|
<div class="input-group input-group-sm">
|
||||||
|
<span class="input-group-addon fit-text-size">value</span>
|
||||||
|
<input type="text" class="form-control" ng-model="label.value" placeholder="e.g. bar" ng-change="updateLabel(node, label)">
|
||||||
|
<span class="input-group-btn">
|
||||||
|
<button class="btn btn-default" type="button" ng-click="removeLabel(node, $index)">
|
||||||
|
<i class="fa fa-minus" aria-hidden="true"></i>
|
||||||
|
</button>
|
||||||
|
</span>
|
||||||
|
</div>
|
||||||
|
</td>
|
||||||
|
</tr>
|
||||||
|
</tbody>
|
||||||
|
</table>
|
||||||
|
</rd-widget-body>
|
||||||
|
<rd-widget-footer>
|
||||||
|
<div class="btn-toolbar" role="toolbar">
|
||||||
|
<div class="btn-group" role="group">
|
||||||
|
<button type="button" class="btn btn-primary btn-sm" ng-disabled="!hasChanges(node, ['Labels'])" ng-click="updateNode(node)">Apply changes</button>
|
||||||
|
<button type="button" class="btn btn-default btn-sm dropdown-toggle" data-toggle="dropdown" aria-haspopup="true" aria-expanded="false">
|
||||||
|
<span class="caret"></span>
|
||||||
|
</button>
|
||||||
|
<ul class="dropdown-menu">
|
||||||
|
<li><a ng-click="cancelChanges(node)">Reset changes</a></li>
|
||||||
|
</ul>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</rd-widget-footer>
|
||||||
|
</rd-widget>
|
||||||
|
</div>
|
||||||
|
</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"></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('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 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: pagination_count))">
|
||||||
|
<td><a ui-sref="task({ id: task.Id })">{{ task.Id }}</a></td>
|
||||||
|
<td><span class="label label-{{ task.Status|taskstatusbadge }}">{{ task.Status }}</span></td>
|
||||||
|
<td>{{ task.Slot }}</td>
|
||||||
|
<td>{{ task.Image }}</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>
|
||||||
|
</div>
|
|
@ -0,0 +1,107 @@
|
||||||
|
angular.module('node', [])
|
||||||
|
.controller('NodeController', ['$scope', '$state', '$stateParams', 'LabelHelper', 'Node', 'NodeHelper', 'Task', 'Settings', 'Messages',
|
||||||
|
function ($scope, $state, $stateParams, LabelHelper, Node, NodeHelper, Task, Settings, Messages) {
|
||||||
|
|
||||||
|
$scope.loading = true;
|
||||||
|
$scope.tasks = [];
|
||||||
|
$scope.displayNode = false;
|
||||||
|
$scope.sortType = 'Status';
|
||||||
|
$scope.sortReverse = false;
|
||||||
|
$scope.pagination_count = Settings.pagination_count;
|
||||||
|
|
||||||
|
var originalNode = {};
|
||||||
|
var editedKeys = [];
|
||||||
|
|
||||||
|
$scope.order = function(sortType) {
|
||||||
|
$scope.sortReverse = ($scope.sortType === sortType) ? !$scope.sortReverse : false;
|
||||||
|
$scope.sortType = sortType;
|
||||||
|
};
|
||||||
|
|
||||||
|
$scope.updateNodeAttribute = function updateNodeAttribute(node, key) {
|
||||||
|
editedKeys.push(key);
|
||||||
|
};
|
||||||
|
$scope.addLabel = function addLabel(node) {
|
||||||
|
node.Labels.push({ key: '', value: '', originalValue: '', originalKey: '' });
|
||||||
|
$scope.updateNodeAttribute(node, 'Labels');
|
||||||
|
};
|
||||||
|
$scope.removeLabel = function removeLabel(node, index) {
|
||||||
|
var removedElement = node.Labels.splice(index, 1);
|
||||||
|
if (removedElement !== null) {
|
||||||
|
$scope.updateNodeAttribute(node, 'Labels');
|
||||||
|
}
|
||||||
|
};
|
||||||
|
$scope.updateLabel = function updateLabel(node, label) {
|
||||||
|
if (label.value !== label.originalValue || label.key !== label.originalKey) {
|
||||||
|
$scope.updateNodeAttribute(node, 'Labels');
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
$scope.hasChanges = function(node, elements) {
|
||||||
|
if (!elements) {
|
||||||
|
elements = Object.keys(originalNode);
|
||||||
|
}
|
||||||
|
var hasChanges = false;
|
||||||
|
elements.forEach(function(key) {
|
||||||
|
hasChanges = hasChanges || ((editedKeys.indexOf(key) >= 0) && node[key] !== originalNode[key]);
|
||||||
|
});
|
||||||
|
return hasChanges;
|
||||||
|
};
|
||||||
|
|
||||||
|
$scope.cancelChanges = function(node) {
|
||||||
|
editedKeys.forEach(function(key) {
|
||||||
|
node[key] = originalNode[key];
|
||||||
|
});
|
||||||
|
editedKeys = [];
|
||||||
|
};
|
||||||
|
|
||||||
|
$scope.updateNode = function updateNode(node) {
|
||||||
|
var config = NodeHelper.nodeToConfig(node.Model);
|
||||||
|
config.Name = node.Name;
|
||||||
|
config.Availability = node.Availability;
|
||||||
|
config.Role = node.Role;
|
||||||
|
config.Labels = LabelHelper.fromKeyValueToLabelHash(node.Labels);
|
||||||
|
|
||||||
|
Node.update({ id: node.Id, version: node.Version }, config, function (data) {
|
||||||
|
$('#loadServicesSpinner').hide();
|
||||||
|
Messages.send("Node successfully updated", "Node updated");
|
||||||
|
$state.go('node', {id: node.Id}, {reload: true});
|
||||||
|
}, function (e) {
|
||||||
|
$('#loadServicesSpinner').hide();
|
||||||
|
Messages.error("Failure", e, "Failed to update node");
|
||||||
|
});
|
||||||
|
};
|
||||||
|
|
||||||
|
function loadNodeAndTasks() {
|
||||||
|
$scope.loading = true;
|
||||||
|
if ($scope.applicationState.endpoint.mode.provider === 'DOCKER_SWARM_MODE') {
|
||||||
|
Node.get({ id: $stateParams.id}, function(d) {
|
||||||
|
if (d.message) {
|
||||||
|
Messages.error("Failure", e, "Unable to inspect the node");
|
||||||
|
} else {
|
||||||
|
var node = new NodeViewModel(d);
|
||||||
|
originalNode = angular.copy(node);
|
||||||
|
$scope.node = node;
|
||||||
|
getTasks(d);
|
||||||
|
}
|
||||||
|
$scope.loading = false;
|
||||||
|
});
|
||||||
|
} else {
|
||||||
|
$scope.loading = false;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
function getTasks(node) {
|
||||||
|
if (node) {
|
||||||
|
Task.query({filters: {node: [node.ID]}}, function (tasks) {
|
||||||
|
$scope.tasks = tasks.map(function (task) {
|
||||||
|
return new TaskViewModel(task, [node]);
|
||||||
|
});
|
||||||
|
}, function (e) {
|
||||||
|
Messages.error("Failure", e, "Unable to retrieve tasks associated to the node");
|
||||||
|
});
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
loadNodeAndTasks();
|
||||||
|
|
||||||
|
}]);
|
|
@ -208,7 +208,7 @@
|
||||||
</thead>
|
</thead>
|
||||||
<tbody>
|
<tbody>
|
||||||
<tr dir-paginate="node in (state.filteredNodes = (nodes | filter:state.filter | orderBy:sortType:sortReverse | itemsPerPage: state.pagination_count))">
|
<tr dir-paginate="node in (state.filteredNodes = (nodes | filter:state.filter | orderBy:sortType:sortReverse | itemsPerPage: state.pagination_count))">
|
||||||
<td>{{ node.Description.Hostname }}</td>
|
<td><a ui-sref="node({id: node.ID})">{{ node.Description.Hostname }}</a></td>
|
||||||
<td>{{ node.Spec.Role }}</td>
|
<td>{{ node.Spec.Role }}</td>
|
||||||
<td>{{ node.Description.Resources.NanoCPUs / 1000000000 }}</td>
|
<td>{{ node.Description.Resources.NanoCPUs / 1000000000 }}</td>
|
||||||
<td>{{ node.Description.Resources.MemoryBytes|humansize }}</td>
|
<td>{{ node.Description.Resources.MemoryBytes|humansize }}</td>
|
||||||
|
|
|
@ -29,6 +29,28 @@ angular.module('portainer.helpers', [])
|
||||||
return mode;
|
return mode;
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
|
}])
|
||||||
|
.factory('LabelHelper', [function LabelHelperFactory() {
|
||||||
|
'use strict';
|
||||||
|
return {
|
||||||
|
fromLabelHashToKeyValue: function(labels) {
|
||||||
|
if (labels) {
|
||||||
|
return Object.keys(labels).map(function(key) {
|
||||||
|
return {key: key, value: labels[key], originalKey: key, originalValue: labels[key], added: true};
|
||||||
|
});
|
||||||
|
}
|
||||||
|
return [];
|
||||||
|
},
|
||||||
|
fromKeyValueToLabelHash: function(labelKV) {
|
||||||
|
var labels = {};
|
||||||
|
if (labelKV) {
|
||||||
|
labelKV.forEach(function(label) {
|
||||||
|
labels[label.key] = label.value;
|
||||||
|
});
|
||||||
|
}
|
||||||
|
return labels;
|
||||||
|
}
|
||||||
|
};
|
||||||
}])
|
}])
|
||||||
.factory('ImageHelper', [function ImageHelperFactory() {
|
.factory('ImageHelper', [function ImageHelperFactory() {
|
||||||
'use strict';
|
'use strict';
|
||||||
|
@ -94,6 +116,19 @@ angular.module('portainer.helpers', [])
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
}])
|
}])
|
||||||
|
.factory('NodeHelper', [function NodeHelperFactory() {
|
||||||
|
'use strict';
|
||||||
|
return {
|
||||||
|
nodeToConfig: function(node) {
|
||||||
|
return {
|
||||||
|
Name: node.Spec.Name,
|
||||||
|
Role: node.Spec.Role,
|
||||||
|
Labels: node.Spec.Labels,
|
||||||
|
Availability: node.Spec.Availability
|
||||||
|
};
|
||||||
|
}
|
||||||
|
};
|
||||||
|
}])
|
||||||
.factory('TemplateHelper', [function TemplateHelperFactory() {
|
.factory('TemplateHelper', [function TemplateHelperFactory() {
|
||||||
'use strict';
|
'use strict';
|
||||||
return {
|
return {
|
||||||
|
|
|
@ -153,10 +153,11 @@ angular.module('portainer.services', ['ngResource', 'ngSanitize'])
|
||||||
.factory('Node', ['$resource', 'Settings', function NodeFactory($resource, Settings) {
|
.factory('Node', ['$resource', 'Settings', function NodeFactory($resource, Settings) {
|
||||||
'use strict';
|
'use strict';
|
||||||
// https://docs.docker.com/engine/reference/api/docker_remote_api_<%= remoteApiVersion %>/#/3-7-nodes
|
// https://docs.docker.com/engine/reference/api/docker_remote_api_<%= remoteApiVersion %>/#/3-7-nodes
|
||||||
return $resource(Settings.url + '/nodes', {}, {
|
return $resource(Settings.url + '/nodes/:id/:action', {}, {
|
||||||
query: {
|
query: {method: 'GET', isArray: true},
|
||||||
method: 'GET', isArray: true
|
get: {method: 'GET', params: {id: '@id'}},
|
||||||
}
|
update: { method: 'POST', params: {id: '@id', action: 'update', version: '@version'} },
|
||||||
|
remove: { method: 'DELETE', params: {id: '@id'} }
|
||||||
});
|
});
|
||||||
}])
|
}])
|
||||||
.factory('Swarm', ['$resource', 'Settings', function SwarmFactory($resource, Settings) {
|
.factory('Swarm', ['$resource', 'Settings', function SwarmFactory($resource, Settings) {
|
||||||
|
|
|
@ -14,6 +14,7 @@ function TaskViewModel(data, node_data) {
|
||||||
this.Updated = data.UpdatedAt;
|
this.Updated = data.UpdatedAt;
|
||||||
this.Slot = data.Slot;
|
this.Slot = data.Slot;
|
||||||
this.Status = data.Status.State;
|
this.Status = data.Status.State;
|
||||||
|
this.Image = data.Spec.ContainerSpec ? data.Spec.ContainerSpec.Image : '';
|
||||||
if (node_data) {
|
if (node_data) {
|
||||||
for (var i = 0; i < node_data.length; ++i) {
|
for (var i = 0; i < node_data.length; ++i) {
|
||||||
if (data.NodeID === node_data[i].ID) {
|
if (data.NodeID === node_data[i].ID) {
|
||||||
|
@ -60,6 +61,42 @@ function ServiceViewModel(data) {
|
||||||
this.EditName = false;
|
this.EditName = false;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
function NodeViewModel(data) {
|
||||||
|
this.Model = data;
|
||||||
|
this.Id = data.ID;
|
||||||
|
this.Version = data.Version.Index;
|
||||||
|
this.Name = data.Spec.Name;
|
||||||
|
this.Role = data.Spec.Role;
|
||||||
|
this.CreatedAt = data.CreatedAt;
|
||||||
|
this.UpdatedAt = data.UpdatedAt;
|
||||||
|
this.Availability = data.Spec.Availability;
|
||||||
|
|
||||||
|
var labels = data.Spec.Labels;
|
||||||
|
if (labels) {
|
||||||
|
this.Labels = Object.keys(labels).map(function(key) {
|
||||||
|
return { key: key, value: labels[key], originalKey: key, originalValue: labels[key], added: true };
|
||||||
|
});
|
||||||
|
} else {
|
||||||
|
this.Labels = [];
|
||||||
|
}
|
||||||
|
|
||||||
|
this.Hostname = data.Description.Hostname;
|
||||||
|
this.PlatformArchitecture = data.Description.Platform.Architecture;
|
||||||
|
this.PlatformOS = data.Description.Platform.OS;
|
||||||
|
this.CPUs = data.Description.Resources.NanoCPUs;
|
||||||
|
this.Memory = data.Description.Resources.MemoryBytes;
|
||||||
|
this.EngineVersion = data.Description.Engine.EngineVersion;
|
||||||
|
this.EngineLabels = data.Description.Engine.Labels;
|
||||||
|
this.Plugins = data.Description.Engine.Plugins;
|
||||||
|
this.Status = data.Status.State;
|
||||||
|
|
||||||
|
if (data.ManagerStatus) {
|
||||||
|
this.Leader = data.ManagerStatus.Leader;
|
||||||
|
this.Reachability = data.ManagerStatus.Reachability;
|
||||||
|
this.ManagerAddr = data.ManagerStatus.Addr;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
function ContainerViewModel(data) {
|
function ContainerViewModel(data) {
|
||||||
this.Id = data.Id;
|
this.Id = data.Id;
|
||||||
this.Status = data.Status;
|
this.Status = data.Status;
|
||||||
|
|
Loading…
Reference in New Issue