Merge branch 'release/1.16.4'

pull/1712/head^2^2 1.16.4
Anthony Lapenna 2018-03-11 20:30:12 +10:00
commit 9c277733d5
19 changed files with 128 additions and 65 deletions

View File

@ -32,7 +32,9 @@ func NewExtensionHandler(bouncer *security.RequestBouncer) *ExtensionHandler {
Logger: log.New(os.Stderr, "", log.LstdFlags), Logger: log.New(os.Stderr, "", log.LstdFlags),
} }
h.Handle("/{endpointId}/extensions", h.Handle("/{endpointId}/extensions",
bouncer.AdministratorAccess(http.HandlerFunc(h.handlePostExtensions))).Methods(http.MethodPost) bouncer.AuthenticatedAccess(http.HandlerFunc(h.handlePostExtensions))).Methods(http.MethodPost)
h.Handle("/{endpointId}/extensions/{extensionType}",
bouncer.AuthenticatedAccess(http.HandlerFunc(h.handleDeleteExtensions))).Methods(http.MethodDelete)
return h return h
} }
@ -75,20 +77,24 @@ func (handler *ExtensionHandler) handlePostExtensions(w http.ResponseWriter, r *
extensionType := portainer.EndpointExtensionType(req.Type) extensionType := portainer.EndpointExtensionType(req.Type)
for _, extension := range endpoint.Extensions { var extension *portainer.EndpointExtension
if extension.Type == extensionType {
httperror.WriteErrorResponse(w, portainer.ErrEndpointExtensionAlreadyAssociated, http.StatusConflict, handler.Logger) for _, ext := range endpoint.Extensions {
return if ext.Type == extensionType {
extension = &ext
} }
} }
extension := portainer.EndpointExtension{ if extension != nil {
Type: extensionType, extension.URL = req.URL
URL: req.URL, } else {
extension = &portainer.EndpointExtension{
Type: extensionType,
URL: req.URL,
}
endpoint.Extensions = append(endpoint.Extensions, *extension)
} }
endpoint.Extensions = append(endpoint.Extensions, extension)
err = handler.EndpointService.UpdateEndpoint(endpoint.ID, endpoint) err = handler.EndpointService.UpdateEndpoint(endpoint.ID, endpoint)
if err != nil { if err != nil {
httperror.WriteErrorResponse(w, err, http.StatusInternalServerError, handler.Logger) httperror.WriteErrorResponse(w, err, http.StatusInternalServerError, handler.Logger)
@ -97,3 +103,41 @@ func (handler *ExtensionHandler) handlePostExtensions(w http.ResponseWriter, r *
encodeJSON(w, extension, handler.Logger) encodeJSON(w, extension, handler.Logger)
} }
func (handler *ExtensionHandler) handleDeleteExtensions(w http.ResponseWriter, r *http.Request) {
vars := mux.Vars(r)
id, err := strconv.Atoi(vars["endpointId"])
if err != nil {
httperror.WriteErrorResponse(w, err, http.StatusBadRequest, handler.Logger)
return
}
endpointID := portainer.EndpointID(id)
endpoint, err := handler.EndpointService.Endpoint(endpointID)
if err == portainer.ErrEndpointNotFound {
httperror.WriteErrorResponse(w, err, http.StatusNotFound, handler.Logger)
return
} else if err != nil {
httperror.WriteErrorResponse(w, err, http.StatusInternalServerError, handler.Logger)
return
}
extType, err := strconv.Atoi(vars["extensionType"])
if err != nil {
httperror.WriteErrorResponse(w, err, http.StatusBadRequest, handler.Logger)
return
}
extensionType := portainer.EndpointExtensionType(extType)
for idx, ext := range endpoint.Extensions {
if ext.Type == extensionType {
endpoint.Extensions = append(endpoint.Extensions[:idx], endpoint.Extensions[idx+1:]...)
}
}
err = handler.EndpointService.UpdateEndpoint(endpoint.ID, endpoint)
if err != nil {
httperror.WriteErrorResponse(w, err, http.StatusInternalServerError, handler.Logger)
return
}
}

View File

@ -400,7 +400,7 @@ type (
const ( const (
// APIVersion is the version number of the Portainer API. // APIVersion is the version number of the Portainer API.
APIVersion = "1.16.3" APIVersion = "1.16.4"
// DBVersion is the version number of the Portainer database. // DBVersion is the version number of the Portainer database.
DBVersion = 8 DBVersion = 8
// DefaultTemplatesURL represents the default URL for the templates definitions. // DefaultTemplatesURL represents the default URL for the templates definitions.

View File

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

View File

@ -26,6 +26,8 @@ angular.module('portainer')
toastr.options.timeOut = 3000; toastr.options.timeOut = 3000;
Terminal.applyAddon(fit);
$uibTooltipProvider.setTriggers({ $uibTooltipProvider.setTriggers({
'mouseenter': 'mouseleave', 'mouseenter': 'mouseleave',
'click': 'click', 'click': 'click',

View File

@ -7,21 +7,11 @@
<div class="form-group"> <div class="form-group">
<div class="col-sm-12"> <div class="col-sm-12">
<label for="tls" class="control-label text-left"> <label for="tls" class="control-label text-left">
Log collection Auto-refresh logs
<portainer-tooltip position="bottom" message="Disabling this option allows you to pause the log collection process."></portainer-tooltip> <portainer-tooltip position="bottom" message="Disabling this option allows you to pause the log collection process and the auto-scrolling."></portainer-tooltip>
</label> </label>
<label class="switch" style="margin-left: 20px;"> <label class="switch" style="margin-left: 20px;">
<input type="checkbox" ng-model="$ctrl.state.logCollection" ng-change="$ctrl.logCollectionChange($ctrl.state.logCollection)"><i></i> <input type="checkbox" ng-model="$ctrl.state.logCollection" ng-change="$ctrl.state.autoScroll = $ctrl.state.logCollection; $ctrl.logCollectionChange($ctrl.state.logCollection)"><i></i>
</label>
</div>
</div>
<div class="form-group">
<div class="col-sm-12">
<label for="tls" class="control-label text-left">
Auto-scrolling
</label>
<label class="switch" style="margin-left: 20px;">
<input type="checkbox" ng-model="$ctrl.state.autoScroll"><i></i>
</label> </label>
</div> </div>
</div> </div>
@ -40,6 +30,7 @@
<div class="col-sm-11"> <div class="col-sm-11">
<button class="btn btn-primary btn-sm" ng-click="$ctrl.copy()" ng-disabled="($ctrl.state.filteredLogs.length === 1 && !$ctrl.state.filteredLogs[0]) || !$ctrl.state.filteredLogs.length"><i class="fa fa-copy space-right" aria-hidden="true"></i>Copy</button> <button class="btn btn-primary btn-sm" ng-click="$ctrl.copy()" ng-disabled="($ctrl.state.filteredLogs.length === 1 && !$ctrl.state.filteredLogs[0]) || !$ctrl.state.filteredLogs.length"><i class="fa fa-copy space-right" aria-hidden="true"></i>Copy</button>
<button class="btn btn-primary btn-sm" ng-click="$ctrl.copySelection()" ng-disabled="($ctrl.state.filteredLogs.length === 1 && !$ctrl.state.filteredLogs[0]) || !$ctrl.state.filteredLogs.length || !$ctrl.state.selectedLines.length"><i class="fa fa-copy space-right" aria-hidden="true"></i>Copy selected lines</button> <button class="btn btn-primary btn-sm" ng-click="$ctrl.copySelection()" ng-disabled="($ctrl.state.filteredLogs.length === 1 && !$ctrl.state.filteredLogs[0]) || !$ctrl.state.filteredLogs.length || !$ctrl.state.selectedLines.length"><i class="fa fa-copy space-right" aria-hidden="true"></i>Copy selected lines</button>
<button class="btn btn-primary btn-sm" ng-click="$ctrl.clearSelection()" ng-disabled="$ctrl.state.selectedLines.length === 0"><i class="fa fa-times space-right" aria-hidden="true"></i>Unselect</button>
<span> <span>
<i id="refreshRateChange" class="fa fa-check green-icon" aria-hidden="true" style="margin-left: 7px; display: none;"></i> <i id="refreshRateChange" class="fa fa-check green-icon" aria-hidden="true" style="margin-left: 7px; display: none;"></i>
</span> </span>
@ -53,8 +44,8 @@
<div class="row" style="height:54%;"> <div class="row" style="height:54%;">
<div class="col-sm-12" style="height:100%;"> <div class="col-sm-12" style="height:100%;">
<pre class="log_viewer" scroll-glue="$ctrl.state.autoScroll"> <pre class="log_viewer" scroll-glue="$ctrl.state.autoScroll" force-glue>
<div ng-repeat="line in $ctrl.state.filteredLogs = ($ctrl.data | filter:$ctrl.state.search) track by $index" class="line" ng-if="line"><p class="inner_line" ng-click="active=!active; $ctrl.selectLine(line)" ng-class="{'line_selected': active}">{{ line }}</p></div> <div ng-repeat="line in $ctrl.state.filteredLogs = ($ctrl.data | filter:$ctrl.state.search) track by $index" class="line" ng-if="line"><p class="inner_line" ng-click="$ctrl.selectLine(line)" ng-class="{ 'line_selected': $ctrl.state.selectedLines.indexOf(line) > -1 }">{{ line }}</p></div>
<div ng-if="!$ctrl.state.filteredLogs.length" class="line"><p class="inner_line">No log line matching the '{{ $ctrl.state.search }}' filter</p></div> <div ng-if="!$ctrl.state.filteredLogs.length" class="line"><p class="inner_line">No log line matching the '{{ $ctrl.state.search }}' filter</p></div>
<div ng-if="$ctrl.state.filteredLogs.length === 1 && !$ctrl.state.filteredLogs[0]" class="line"><p class="inner_line">No logs available</p></div> <div ng-if="$ctrl.state.filteredLogs.length === 1 && !$ctrl.state.filteredLogs[0]" class="line"><p class="inner_line">No logs available</p></div>
</pre> </pre>

View File

@ -15,13 +15,17 @@ function (clipboard) {
this.copy = function() { this.copy = function() {
clipboard.copyText(this.state.filteredLogs); clipboard.copyText(this.state.filteredLogs);
$('#refreshRateChange').show(); $('#refreshRateChange').show();
$('#refreshRateChange').fadeOut(1500); $('#refreshRateChange').fadeOut(2000);
}; };
this.copySelection = function() { this.copySelection = function() {
clipboard.copyText(this.state.selectedLines); clipboard.copyText(this.state.selectedLines);
$('#refreshRateChange').show(); $('#refreshRateChange').show();
$('#refreshRateChange').fadeOut(1500); $('#refreshRateChange').fadeOut(2000);
};
this.clearSelection = function() {
this.state.selectedLines = [];
}; };
this.selectLine = function(line) { this.selectLine = function(line) {

View File

@ -87,7 +87,8 @@ function ($scope, $transition$, Container, Image, EndpointProvider, Notification
term.on('data', function (data) { term.on('data', function (data) {
socket.send(data); socket.send(data);
}); });
term.open(document.getElementById('terminal-container'), true); term.open(document.getElementById('terminal-container'));
term.focus();
term.resize(width, height); term.resize(width, height);
term.setOption('cursorBlink', true); term.setOption('cursorBlink', true);
term.fit(); term.fit();

View File

@ -44,7 +44,6 @@ function ($scope, $transition$, $interval, ServiceService, Notifications) {
ServiceService.logs($transition$.params().id, 1, 1, 0, $scope.state.lineCount) ServiceService.logs($transition$.params().id, 1, 1, 0, $scope.state.lineCount)
.then(function success(data) { .then(function success(data) {
$scope.logs = data; $scope.logs = data;
console.log(JSON.stringify(data, null, 4));
setUpdateRepeater(); setUpdateRepeater();
}) })
.catch(function error(err) { .catch(function error(err) {

View File

@ -1,7 +1,7 @@
<rd-header> <rd-header>
<rd-header-title title="Service logs"></rd-header-title> <rd-header-title title="Service logs"></rd-header-title>
<rd-header-content> <rd-header-content>
<a ui-sref="docker.services">Services</a> > <a ui-sref="docker.services.service({id: service.ID})">{{ service.Name }}</a> &gt; Logs <a ui-sref="docker.services">Services</a> > <a ui-sref="docker.services.service({id: service.Id})">{{ service.Name }}</a> &gt; Logs
</rd-header-content> </rd-header-content>
</rd-header> </rd-header>

View File

@ -1,10 +1,11 @@
angular.module('portainer.app') angular.module('portainer.app')
.factory('Extensions', ['$resource', 'EndpointProvider', 'API_ENDPOINT_ENDPOINTS', function Extensions($resource, EndpointProvider, API_ENDPOINT_ENDPOINTS) { .factory('Extensions', ['$resource', 'EndpointProvider', 'API_ENDPOINT_ENDPOINTS', function Extensions($resource, EndpointProvider, API_ENDPOINT_ENDPOINTS) {
'use strict'; 'use strict';
return $resource(API_ENDPOINT_ENDPOINTS + '/:endpointId/extensions', { return $resource(API_ENDPOINT_ENDPOINTS + '/:endpointId/extensions/:type', {
endpointId: EndpointProvider.endpointID endpointId: EndpointProvider.endpointID
}, },
{ {
register: { method: 'POST', params: { endpointId: '@endpointId' } } register: { method: 'POST' },
deregister: { method: 'DELETE', params: { type: '@type' } }
}); });
}]); }]);

View File

@ -1,5 +1,6 @@
angular.module('portainer.app') angular.module('portainer.app')
.factory('EndpointService', ['$q', 'Endpoints', 'FileUploadService', function EndpointServiceFactory($q, Endpoints, FileUploadService) { .factory('EndpointService', ['$q', 'Endpoints', 'FileUploadService',
function EndpointServiceFactory($q, Endpoints, FileUploadService) {
'use strict'; 'use strict';
var service = {}; var service = {};

View File

@ -3,9 +3,8 @@ angular.module('portainer.app')
'use strict'; 'use strict';
var service = {}; var service = {};
service.registerStoridgeExtension = function(endpointId, url) { service.registerStoridgeExtension = function(url) {
var payload = { var payload = {
endpointId: endpointId,
Type: 1, Type: 1,
URL: url URL: url
}; };
@ -13,5 +12,9 @@ angular.module('portainer.app')
return Extensions.register(payload).$promise; return Extensions.register(payload).$promise;
}; };
service.deregisterStoridgeExtension = function() {
return Extensions.deregister({ type: 1 }).$promise;
};
return service; return service;
}]); }]);

View File

@ -4,7 +4,7 @@ function ExtensionManagerFactory($q, PluginService, SystemService, ExtensionServ
'use strict'; 'use strict';
var service = {}; var service = {};
service.initEndpointExtensions = function(endpointId) { service.initEndpointExtensions = function() {
var deferred = $q.defer(); var deferred = $q.defer();
SystemService.version() SystemService.version()
@ -12,13 +12,11 @@ function ExtensionManagerFactory($q, PluginService, SystemService, ExtensionServ
var endpointAPIVersion = parseFloat(data.ApiVersion); var endpointAPIVersion = parseFloat(data.ApiVersion);
return $q.all([ return $q.all([
endpointAPIVersion >= 1.25 ? initStoridgeExtension(endpointId): null endpointAPIVersion >= 1.25 ? initStoridgeExtension(): null
]); ]);
}) })
.then(function success(data) { .then(function success(data) {
var extensions = data.filter(function filterNull(x) { var extensions = data;
return x;
});
deferred.resolve(extensions); deferred.resolve(extensions);
}) })
.catch(function error(err) { .catch(function error(err) {
@ -28,14 +26,16 @@ function ExtensionManagerFactory($q, PluginService, SystemService, ExtensionServ
return deferred.promise; return deferred.promise;
}; };
function initStoridgeExtension(endpointId) { function initStoridgeExtension() {
var deferred = $q.defer(); var deferred = $q.defer();
PluginService.volumePlugins() PluginService.volumePlugins()
.then(function success(data) { .then(function success(data) {
var volumePlugins = data; var volumePlugins = data;
if (_.includes(volumePlugins, 'cio:latest')) { if (_.includes(volumePlugins, 'cio:latest')) {
return registerStoridgeUsingSwarmManagerIP(endpointId); return registerStoridgeUsingSwarmManagerIP();
} else {
return deregisterStoridgeExtension();
} }
}) })
.then(function success(data) { .then(function success(data) {
@ -48,14 +48,14 @@ function ExtensionManagerFactory($q, PluginService, SystemService, ExtensionServ
return deferred.promise; return deferred.promise;
} }
function registerStoridgeUsingSwarmManagerIP(endpointId) { function registerStoridgeUsingSwarmManagerIP() {
var deferred = $q.defer(); var deferred = $q.defer();
SystemService.info() SystemService.info()
.then(function success(data) { .then(function success(data) {
var managerIP = data.Swarm.NodeAddr; var managerIP = data.Swarm.NodeAddr;
var storidgeAPIURL = 'tcp://' + managerIP + ':8282'; var storidgeAPIURL = 'tcp://' + managerIP + ':8282';
return ExtensionService.registerStoridgeExtension(endpointId, storidgeAPIURL); return ExtensionService.registerStoridgeExtension(storidgeAPIURL);
}) })
.then(function success(data) { .then(function success(data) {
deferred.resolve(data); deferred.resolve(data);
@ -67,5 +67,9 @@ function ExtensionManagerFactory($q, PluginService, SystemService, ExtensionServ
return deferred.promise; return deferred.promise;
} }
function deregisterStoridgeExtension() {
return ExtensionService.deregisterStoridgeExtension();
}
return service; return service;
}]); }]);

View File

@ -1,6 +1,6 @@
angular.module('portainer.app') angular.module('portainer.app')
.controller('AuthenticationController', ['$scope', '$state', '$transition$', '$window', '$timeout', '$sanitize', 'Authentication', 'Users', 'UserService', 'EndpointService', 'StateManager', 'EndpointProvider', 'Notifications', 'SettingsService', .controller('AuthenticationController', ['$scope', '$state', '$transition$', '$window', '$timeout', '$sanitize', 'Authentication', 'Users', 'UserService', 'EndpointService', 'StateManager', 'EndpointProvider', 'Notifications', 'SettingsService', 'ExtensionManager',
function ($scope, $state, $transition$, $window, $timeout, $sanitize, Authentication, Users, UserService, EndpointService, StateManager, EndpointProvider, Notifications, SettingsService) { function ($scope, $state, $transition$, $window, $timeout, $sanitize, Authentication, Users, UserService, EndpointService, StateManager, EndpointProvider, Notifications, SettingsService, ExtensionManager) {
$scope.logo = StateManager.getState().application.logo; $scope.logo = StateManager.getState().application.logo;
@ -18,7 +18,12 @@ function ($scope, $state, $transition$, $window, $timeout, $sanitize, Authentica
if (!endpointID) { if (!endpointID) {
EndpointProvider.setEndpointID(endpoint.Id); EndpointProvider.setEndpointID(endpoint.Id);
} }
StateManager.updateEndpointState(true, endpoint.Extensions)
ExtensionManager.initEndpointExtensions(endpoint.Id)
.then(function success(data) {
var extensions = data;
return StateManager.updateEndpointState(true, extensions);
})
.then(function success(data) { .then(function success(data) {
$state.go('docker.dashboard'); $state.go('docker.dashboard');
}) })

View File

@ -1,6 +1,6 @@
angular.module('portainer.app') angular.module('portainer.app')
.controller('EndpointsController', ['$scope', '$state', '$filter', 'EndpointService', 'Notifications', 'ExtensionManager', 'EndpointProvider', .controller('EndpointsController', ['$scope', '$state', '$filter', 'EndpointService', 'Notifications', 'SystemService', 'EndpointProvider',
function ($scope, $state, $filter, EndpointService, Notifications, ExtensionManager, EndpointProvider) { function ($scope, $state, $filter, EndpointService, Notifications, SystemService, EndpointProvider) {
$scope.state = { $scope.state = {
uploadInProgress: false, uploadInProgress: false,
actionInProgress: false actionInProgress: false
@ -37,8 +37,8 @@ function ($scope, $state, $filter, EndpointService, Notifications, ExtensionMana
endpointId = data.Id; endpointId = data.Id;
var currentEndpointId = EndpointProvider.endpointID(); var currentEndpointId = EndpointProvider.endpointID();
EndpointProvider.setEndpointID(endpointId); EndpointProvider.setEndpointID(endpointId);
ExtensionManager.initEndpointExtensions(endpointId) SystemService.info()
.then(function success(data) { .then(function success() {
Notifications.success('Endpoint created', name); Notifications.success('Endpoint created', name);
$state.reload(); $state.reload();
}) })

View File

@ -1,6 +1,6 @@
angular.module('portainer.app') angular.module('portainer.app')
.controller('SidebarController', ['$q', '$scope', '$state', 'Settings', 'EndpointService', 'StateManager', 'EndpointProvider', 'Notifications', 'Authentication', 'UserService', .controller('SidebarController', ['$q', '$scope', '$state', 'Settings', 'EndpointService', 'StateManager', 'EndpointProvider', 'Notifications', 'Authentication', 'UserService', 'ExtensionManager',
function ($q, $scope, $state, Settings, EndpointService, StateManager, EndpointProvider, Notifications, Authentication, UserService) { function ($q, $scope, $state, Settings, EndpointService, StateManager, EndpointProvider, Notifications, Authentication, UserService, ExtensionManager) {
$scope.switchEndpoint = function(endpoint) { $scope.switchEndpoint = function(endpoint) {
var activeEndpointID = EndpointProvider.endpointID(); var activeEndpointID = EndpointProvider.endpointID();
@ -8,7 +8,11 @@ function ($q, $scope, $state, Settings, EndpointService, StateManager, EndpointP
EndpointProvider.setEndpointID(endpoint.Id); EndpointProvider.setEndpointID(endpoint.Id);
EndpointProvider.setEndpointPublicURL(endpoint.PublicURL); EndpointProvider.setEndpointPublicURL(endpoint.PublicURL);
StateManager.updateEndpointState(true, endpoint.Extensions) ExtensionManager.initEndpointExtensions(endpoint.Id)
.then(function success(data) {
var extensions = data;
return StateManager.updateEndpointState(true, extensions);
})
.then(function success() { .then(function success() {
$state.go('docker.dashboard'); $state.go('docker.dashboard');
}) })

View File

@ -1,5 +1,5 @@
Name: portainer Name: portainer
Version: 1.16.3 Version: 1.16.4
Release: 0 Release: 0
License: Zlib License: Zlib
Summary: A lightweight docker management UI Summary: A lightweight docker management UI

View File

@ -2,7 +2,7 @@
"author": "Portainer.io", "author": "Portainer.io",
"name": "portainer", "name": "portainer",
"homepage": "http://portainer.io", "homepage": "http://portainer.io",
"version": "1.16.3", "version": "1.16.4",
"repository": { "repository": {
"type": "git", "type": "git",
"url": "git@github.com:portainer/portainer.git" "url": "git@github.com:portainer/portainer.git"
@ -50,13 +50,13 @@
"jquery": "^3.3.1", "jquery": "^3.3.1",
"js-yaml": "~3.10.0", "js-yaml": "~3.10.0",
"lodash": "4.12.0", "lodash": "4.12.0",
"moment": "~2.14.1", "moment": "^2.21.0",
"ng-file-upload": "~12.2.13", "ng-file-upload": "~12.2.13",
"rdash-ui": "1.0.*", "rdash-ui": "1.0.*",
"splitargs": "github:deviantony/splitargs#~0.2.0", "splitargs": "github:deviantony/splitargs#~0.2.0",
"toastr": "github:CodeSeven/toastr#~2.1.3", "toastr": "github:CodeSeven/toastr#~2.1.3",
"ui-select": "~0.19.6", "ui-select": "~0.19.6",
"xterm": "~2.8.1" "xterm": "^3.1.0"
}, },
"devDependencies": { "devDependencies": {
"autoprefixer": "^7.1.1", "autoprefixer": "^7.1.1",

View File

@ -2882,10 +2882,14 @@ mkdirp@0.x.x, mkdirp@^0.5.0, mkdirp@^0.5.1, mkdirp@~0.5.0, mkdirp@~0.5.1:
dependencies: dependencies:
minimist "0.0.8" minimist "0.0.8"
moment@^2.10.6, moment@~2.14.1: moment@^2.10.6:
version "2.14.1" version "2.14.1"
resolved "https://registry.yarnpkg.com/moment/-/moment-2.14.1.tgz#b35b27c47e57ed2ddc70053d6b07becdb291741c" resolved "https://registry.yarnpkg.com/moment/-/moment-2.14.1.tgz#b35b27c47e57ed2ddc70053d6b07becdb291741c"
moment@^2.21.0:
version "2.21.0"
resolved "https://registry.yarnpkg.com/moment/-/moment-2.21.0.tgz#2a114b51d2a6ec9e6d83cf803f838a878d8a023a"
morgan@~1.6.1: morgan@~1.6.1:
version "1.6.1" version "1.6.1"
resolved "https://registry.yarnpkg.com/morgan/-/morgan-1.6.1.tgz#5fd818398c6819cba28a7cd6664f292fe1c0bbf2" resolved "https://registry.yarnpkg.com/morgan/-/morgan-1.6.1.tgz#5fd818398c6819cba28a7cd6664f292fe1c0bbf2"
@ -4483,9 +4487,9 @@ xtend@~3.0.0:
version "3.0.0" version "3.0.0"
resolved "https://registry.yarnpkg.com/xtend/-/xtend-3.0.0.tgz#5cce7407baf642cba7becda568111c493f59665a" resolved "https://registry.yarnpkg.com/xtend/-/xtend-3.0.0.tgz#5cce7407baf642cba7becda568111c493f59665a"
xterm@~2.8.1: xterm@^3.1.0:
version "2.8.1" version "3.1.0"
resolved "https://registry.yarnpkg.com/xterm/-/xterm-2.8.1.tgz#3f6b939bcb8d015a1f247d66257102cb16a0b2e1" resolved "https://registry.yarnpkg.com/xterm/-/xterm-3.1.0.tgz#7f7e1c8cf4b80bd881a4e8891213b851423e90c9"
yargs@~3.10.0: yargs@~3.10.0:
version "3.10.0" version "3.10.0"