mirror of https://github.com/portainer/portainer
extension(storidge): add Storidge extension (#1581)
parent
edadce359c
commit
7817d4bd0b
|
@ -5,6 +5,7 @@ angular.module('portainer', [
|
|||
'ngCookies',
|
||||
'ngSanitize',
|
||||
'ngFileUpload',
|
||||
'ngMessages',
|
||||
'angularUtils.directives.dirPagination',
|
||||
'LocalStorageModule',
|
||||
'angular-jwt',
|
||||
|
@ -40,6 +41,7 @@ angular.module('portainer', [
|
|||
'endpointAccess',
|
||||
'endpoints',
|
||||
'events',
|
||||
'extension.storidge',
|
||||
'image',
|
||||
'images',
|
||||
'initAdmin',
|
||||
|
|
|
@ -1,6 +1,6 @@
|
|||
angular.module('createVolume', [])
|
||||
.controller('CreateVolumeController', ['$q', '$scope', '$state', 'VolumeService', 'PluginService', 'ResourceControlService', 'Authentication', 'Notifications', 'FormValidator',
|
||||
function ($q, $scope, $state, VolumeService, PluginService, ResourceControlService, Authentication, Notifications, FormValidator) {
|
||||
.controller('CreateVolumeController', ['$q', '$scope', '$state', 'VolumeService', 'PluginService', 'ResourceControlService', 'Authentication', 'Notifications', 'FormValidator', 'ExtensionManager',
|
||||
function ($q, $scope, $state, VolumeService, PluginService, ResourceControlService, Authentication, Notifications, FormValidator, ExtensionManager) {
|
||||
|
||||
$scope.formValues = {
|
||||
Driver: 'local',
|
||||
|
@ -40,6 +40,12 @@ function ($q, $scope, $state, VolumeService, PluginService, ResourceControlServi
|
|||
var name = $scope.formValues.Name;
|
||||
var driver = $scope.formValues.Driver;
|
||||
var driverOptions = $scope.formValues.DriverOptions;
|
||||
var storidgeProfile = $scope.formValues.StoridgeProfile;
|
||||
|
||||
if (driver === 'cio:latest' && storidgeProfile) {
|
||||
driverOptions.push({ name: 'profile', value: storidgeProfile.Name });
|
||||
}
|
||||
|
||||
var volumeConfiguration = VolumeService.createVolumeConfiguration(name, driver, driverOptions);
|
||||
var accessControlData = $scope.formValues.AccessControlData;
|
||||
var userDetails = Authentication.getUserDetails();
|
||||
|
@ -82,5 +88,11 @@ function ($q, $scope, $state, VolumeService, PluginService, ResourceControlServi
|
|||
}
|
||||
}
|
||||
|
||||
initView();
|
||||
ExtensionManager.init()
|
||||
.then(function success(data) {
|
||||
initView();
|
||||
})
|
||||
.catch(function error(err) {
|
||||
Notifications.error('Failure', err, 'Unable to initialize extensions');
|
||||
});
|
||||
}]);
|
||||
|
|
|
@ -62,6 +62,14 @@
|
|||
<!-- !driver-options-input-list -->
|
||||
</div>
|
||||
<!-- !driver-options -->
|
||||
<!-- storidge -->
|
||||
<div ng-if="formValues.Driver === 'cio:latest'">
|
||||
<div class="col-sm-12 form-section-title">
|
||||
Storidge
|
||||
</div>
|
||||
<storidge-profile-selector storidge-profile="formValues.StoridgeProfile"></storidge-profile-selector>
|
||||
</div>
|
||||
<!-- storidge -->
|
||||
<!-- access-control -->
|
||||
<por-access-control-form form-data="formValues.AccessControlData" ng-if="applicationState.application.authentication"></por-access-control-form>
|
||||
<!-- !access-control -->
|
||||
|
|
|
@ -57,6 +57,18 @@
|
|||
<li class="sidebar-list" ng-if="applicationState.endpoint.mode.provider === 'DOCKER_STANDALONE' || applicationState.endpoint.mode.provider === 'VMWARE_VIC'">
|
||||
<a ui-sref="engine" ui-sref-active="active">Engine <span class="menu-icon fa fa-th fa-fw"></span></a>
|
||||
</li>
|
||||
<li class="sidebar-title" ng-if="applicationState.endpoint.extensions.length > 0 && isAdmin && applicationState.endpoint.mode.provider === 'DOCKER_SWARM_MODE' && applicationState.endpoint.mode.role === 'MANAGER'">
|
||||
<span>Extensions</span>
|
||||
</li>
|
||||
<li class="sidebar-list" ng-if="applicationState.endpoint.extensions.indexOf('storidge') !== -1 && applicationState.endpoint.mode.provider === 'DOCKER_SWARM_MODE' && applicationState.endpoint.mode.role === 'MANAGER'">
|
||||
<a ui-sref="storidge.cluster" ui-sref-active="active">Storidge <span class="menu-icon fa fa-bolt fa-fw"></span></a>
|
||||
<div class="sidebar-sublist" ng-if="toggle && ($state.current.name === 'storidge.cluster' || $state.current.name === 'storidge.profiles' || $state.current.name === 'storidge.monitor' || $state.current.name === 'storidge.profiles.create' || $state.current.name === 'storidge.profiles.edit')">
|
||||
<a ui-sref="storidge.monitor" ui-sref-active="active">Monitor</a>
|
||||
</div>
|
||||
<div class="sidebar-sublist" ng-if="toggle && ($state.current.name === 'storidge.cluster' || $state.current.name === 'storidge.profiles' || $state.current.name === 'storidge.monitor' || $state.current.name === 'storidge.profiles.create' || $state.current.name === 'storidge.profiles.edit')">
|
||||
<a ui-sref="storidge.profiles" ui-sref-active="active">Profiles</a>
|
||||
</div>
|
||||
</li>
|
||||
<li class="sidebar-title" ng-if="!applicationState.application.authentication || isAdmin || isTeamLeader">
|
||||
<span>Portainer settings</span>
|
||||
</li>
|
||||
|
|
|
@ -1,6 +1,6 @@
|
|||
angular.module('sidebar', [])
|
||||
.controller('SidebarController', ['$q', '$scope', '$state', 'Settings', 'EndpointService', 'StateManager', 'EndpointProvider', 'Notifications', 'Authentication', 'UserService',
|
||||
function ($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, ExtensionManager) {
|
||||
|
||||
$scope.uiVersion = StateManager.getState().application.version;
|
||||
$scope.displayExternalContributors = StateManager.getState().application.displayExternalContributors;
|
||||
|
@ -12,8 +12,10 @@ function ($q, $scope, $state, Settings, EndpointService, StateManager, EndpointP
|
|||
var activeEndpointPublicURL = EndpointProvider.endpointPublicURL();
|
||||
EndpointProvider.setEndpointID(endpoint.Id);
|
||||
EndpointProvider.setEndpointPublicURL(endpoint.PublicURL);
|
||||
|
||||
StateManager.updateEndpointState(true)
|
||||
.then(function success() {
|
||||
ExtensionManager.reset();
|
||||
$state.go('dashboard');
|
||||
})
|
||||
.catch(function error(err) {
|
||||
|
|
|
@ -40,5 +40,6 @@ function ($q, $scope, $state, VolumeService, Notifications) {
|
|||
Notifications.error('Failure', err, 'Unable to retrieve volumes');
|
||||
});
|
||||
}
|
||||
|
||||
initView();
|
||||
}]);
|
||||
|
|
|
@ -0,0 +1,103 @@
|
|||
angular.module('extension.storidge', [])
|
||||
.config(['$stateRegistryProvider', function ($stateRegistryProvider) {
|
||||
'use strict';
|
||||
|
||||
var storidge = {
|
||||
name: 'storidge',
|
||||
abstract: true,
|
||||
url: '/storidge',
|
||||
views: {
|
||||
'content@': {
|
||||
template: '<div ui-view="content@"></div>'
|
||||
},
|
||||
'sidebar@': {
|
||||
template: '<div ui-view="sidebar@"></div>'
|
||||
}
|
||||
}
|
||||
};
|
||||
|
||||
var profiles = {
|
||||
name: 'storidge.profiles',
|
||||
url: '/profiles',
|
||||
views: {
|
||||
'content@': {
|
||||
templateUrl: 'app/extensions/storidge/views/profiles/profiles.html',
|
||||
controller: 'StoridgeProfilesController'
|
||||
},
|
||||
'sidebar@': {
|
||||
templateUrl: 'app/components/sidebar/sidebar.html',
|
||||
controller: 'SidebarController'
|
||||
}
|
||||
}
|
||||
};
|
||||
|
||||
var profileCreation = {
|
||||
name: 'storidge.profiles.create',
|
||||
url: '/create',
|
||||
params: {
|
||||
profileName: ''
|
||||
},
|
||||
views: {
|
||||
'content@': {
|
||||
templateUrl: 'app/extensions/storidge/views/profiles/create/createProfile.html',
|
||||
controller: 'CreateProfileController'
|
||||
},
|
||||
'sidebar@': {
|
||||
templateUrl: 'app/components/sidebar/sidebar.html',
|
||||
controller: 'SidebarController'
|
||||
}
|
||||
}
|
||||
};
|
||||
|
||||
var profileEdition = {
|
||||
name: 'storidge.profiles.edit',
|
||||
url: '/edit/:id',
|
||||
views: {
|
||||
'content@': {
|
||||
templateUrl: 'app/extensions/storidge/views/profiles/edit/editProfile.html',
|
||||
controller: 'EditProfileController'
|
||||
},
|
||||
'sidebar@': {
|
||||
templateUrl: 'app/components/sidebar/sidebar.html',
|
||||
controller: 'SidebarController'
|
||||
}
|
||||
}
|
||||
};
|
||||
|
||||
var cluster = {
|
||||
name: 'storidge.cluster',
|
||||
url: '/cluster',
|
||||
views: {
|
||||
'content@': {
|
||||
templateUrl: 'app/extensions/storidge/views/cluster/cluster.html',
|
||||
controller: 'StoridgeClusterController'
|
||||
},
|
||||
'sidebar@': {
|
||||
templateUrl: 'app/components/sidebar/sidebar.html',
|
||||
controller: 'SidebarController'
|
||||
}
|
||||
}
|
||||
};
|
||||
|
||||
var monitor = {
|
||||
name: 'storidge.monitor',
|
||||
url: '/events',
|
||||
views: {
|
||||
'content@': {
|
||||
templateUrl: 'app/extensions/storidge/views/monitor/monitor.html',
|
||||
controller: 'StoridgeMonitorController'
|
||||
},
|
||||
'sidebar@': {
|
||||
templateUrl: 'app/components/sidebar/sidebar.html',
|
||||
controller: 'SidebarController'
|
||||
}
|
||||
}
|
||||
};
|
||||
|
||||
$stateRegistryProvider.register(storidge);
|
||||
$stateRegistryProvider.register(profiles);
|
||||
$stateRegistryProvider.register(profileCreation);
|
||||
$stateRegistryProvider.register(profileEdition);
|
||||
$stateRegistryProvider.register(cluster);
|
||||
$stateRegistryProvider.register(monitor);
|
||||
}]);
|
|
@ -0,0 +1,89 @@
|
|||
<div class="datatable">
|
||||
<rd-widget>
|
||||
<rd-widget-body classes="no-padding">
|
||||
<div class="toolBar">
|
||||
<div class="toolBarTitle">
|
||||
<i class="fa" ng-class="$ctrl.titleIcon" aria-hidden="true" style="margin-right: 2px;"></i> {{ $ctrl.title }}
|
||||
</div>
|
||||
<div class="settings">
|
||||
<span class="setting" ng-class="{ 'setting-active': $ctrl.state.displayTextFilter }" ng-click="$ctrl.updateDisplayTextFilter()" ng-if="$ctrl.showTextFilter">
|
||||
<i class="fa fa-search" aria-hidden="true"></i> Search
|
||||
</span>
|
||||
</div>
|
||||
</div>
|
||||
<div class="searchBar" ng-if="$ctrl.state.displayTextFilter">
|
||||
<i class="fa fa-search searchIcon" aria-hidden="true"></i>
|
||||
<input type="text" class="searchInput" ng-model="$ctrl.state.textFilter" placeholder="Search...">
|
||||
</div>
|
||||
<div class="table-responsive">
|
||||
<table class="table table-hover">
|
||||
<thead>
|
||||
<tr>
|
||||
<th>
|
||||
<a ng-click="$ctrl.changeOrderBy('Time')">
|
||||
Date
|
||||
<i class="fa fa-sort-alpha-asc" aria-hidden="true" ng-if="$ctrl.state.orderBy === 'Time' && !$ctrl.state.reverseOrder"></i>
|
||||
<i class="fa fa-sort-alpha-desc" aria-hidden="true" ng-if="$ctrl.state.orderBy === 'Time' && $ctrl.state.reverseOrder"></i>
|
||||
</a>
|
||||
</th>
|
||||
<th>
|
||||
<a ng-click="$ctrl.changeOrderBy('Category')">
|
||||
Category
|
||||
<i class="fa fa-sort-alpha-asc" aria-hidden="true" ng-if="$ctrl.state.orderBy === 'Category' && !$ctrl.state.reverseOrder"></i>
|
||||
<i class="fa fa-sort-alpha-desc" aria-hidden="true" ng-if="$ctrl.state.orderBy === 'Category' && $ctrl.state.reverseOrder"></i>
|
||||
</a>
|
||||
</th>
|
||||
<th>
|
||||
<a ng-click="$ctrl.changeOrderBy('Module')">
|
||||
Module
|
||||
<i class="fa fa-sort-alpha-asc" aria-hidden="true" ng-if="$ctrl.state.orderBy === 'Module' && !$ctrl.state.reverseOrder"></i>
|
||||
<i class="fa fa-sort-alpha-desc" aria-hidden="true" ng-if="$ctrl.state.orderBy === 'Module' && $ctrl.state.reverseOrder"></i>
|
||||
</a>
|
||||
</th>
|
||||
<th>
|
||||
<a ng-click="$ctrl.changeOrderBy('Content')">
|
||||
Content
|
||||
<i class="fa fa-sort-alpha-asc" aria-hidden="true" ng-if="$ctrl.state.orderBy === 'Content' && !$ctrl.state.reverseOrder"></i>
|
||||
<i class="fa fa-sort-alpha-desc" aria-hidden="true" ng-if="$ctrl.state.orderBy === 'Content' && $ctrl.state.reverseOrder"></i>
|
||||
</a>
|
||||
</th>
|
||||
</tr>
|
||||
</thead>
|
||||
<tbody>
|
||||
<tr dir-paginate="item in ($ctrl.state.filteredDataSet = ($ctrl.dataset | filter:$ctrl.state.textFilter | orderBy:$ctrl.state.orderBy:$ctrl.state.reverseOrder | itemsPerPage: $ctrl.state.paginatedItemLimit))" ng-class="{active: item.Checked}">
|
||||
<td>{{ item.Time }}</td>
|
||||
<td>{{ item.Category }}</td>
|
||||
<td>{{ item.Module }}</td>
|
||||
<td>{{ item.Content }}</td>
|
||||
</tr>
|
||||
<tr ng-if="!$ctrl.dataset">
|
||||
<td colspan="4" class="text-center text-muted">Loading...</td>
|
||||
</tr>
|
||||
<tr ng-if="$ctrl.state.filteredDataSet.length === 0">
|
||||
<td colspan="4" class="text-center text-muted">No events available.</td>
|
||||
</tr>
|
||||
</tbody>
|
||||
</table>
|
||||
</div>
|
||||
<div class="footer" ng-if="$ctrl.dataset">
|
||||
<div class="paginationControls">
|
||||
<form class="form-inline">
|
||||
<span class="limitSelector">
|
||||
<span style="margin-right: 5px;">
|
||||
Items per page
|
||||
</span>
|
||||
<select class="form-control" ng-model="$ctrl.state.paginatedItemLimit" ng-change="$ctrl.changePaginationLimit()">
|
||||
<option value="0">All</option>
|
||||
<option value="10">10</option>
|
||||
<option value="25">25</option>
|
||||
<option value="50">50</option>
|
||||
<option value="100">100</option>
|
||||
</select>
|
||||
</span>
|
||||
<dir-pagination-controls max-size="5"></dir-pagination-controls>
|
||||
</form>
|
||||
</div>
|
||||
</div>
|
||||
</rd-widget-body>
|
||||
</rd-widget>
|
||||
</div>
|
|
@ -0,0 +1,13 @@
|
|||
angular.module('extension.storidge').component('storidgeClusterEventsDatatable', {
|
||||
templateUrl: 'app/extensions/storidge/components/cluster-events-datatable/storidgeClusterEventsDatatable.html',
|
||||
controller: 'GenericDatatableController',
|
||||
bindings: {
|
||||
title: '@',
|
||||
titleIcon: '@',
|
||||
dataset: '<',
|
||||
tableKey: '@',
|
||||
orderBy: '@',
|
||||
reverseOrder: '<',
|
||||
showTextFilter: '<'
|
||||
}
|
||||
});
|
|
@ -0,0 +1,92 @@
|
|||
<div class="datatable">
|
||||
<rd-widget>
|
||||
<rd-widget-body classes="no-padding">
|
||||
<div class="toolBar">
|
||||
<div class="toolBarTitle">
|
||||
<i class="fa" ng-class="$ctrl.titleIcon" aria-hidden="true" style="margin-right: 2px;"></i> {{ $ctrl.title }}
|
||||
</div>
|
||||
<div class="settings">
|
||||
<span class="setting" ng-class="{ 'setting-active': $ctrl.state.displayTextFilter }" ng-click="$ctrl.updateDisplayTextFilter()" ng-if="$ctrl.showTextFilter">
|
||||
<i class="fa fa-search" aria-hidden="true"></i> Search
|
||||
</span>
|
||||
</div>
|
||||
</div>
|
||||
<div class="searchBar" ng-if="$ctrl.state.displayTextFilter">
|
||||
<i class="fa fa-search searchIcon" aria-hidden="true"></i>
|
||||
<input type="text" class="searchInput" ng-model="$ctrl.state.textFilter" placeholder="Search...">
|
||||
</div>
|
||||
<div class="table-responsive">
|
||||
<table class="table table-hover">
|
||||
<thead>
|
||||
<tr>
|
||||
<th>
|
||||
<a ng-click="$ctrl.changeOrderBy('Name')">
|
||||
Name
|
||||
<i class="fa fa-sort-alpha-asc" aria-hidden="true" ng-if="$ctrl.state.orderBy === 'Name' && !$ctrl.state.reverseOrder"></i>
|
||||
<i class="fa fa-sort-alpha-desc" aria-hidden="true" ng-if="$ctrl.state.orderBy === 'Name' && $ctrl.state.reverseOrder"></i>
|
||||
</a>
|
||||
</th>
|
||||
<th>
|
||||
<a ng-click="$ctrl.changeOrderBy('IP')">
|
||||
IP Address
|
||||
<i class="fa fa-sort-alpha-asc" aria-hidden="true" ng-if="$ctrl.state.orderBy === 'IP' && !$ctrl.state.reverseOrder"></i>
|
||||
<i class="fa fa-sort-alpha-desc" aria-hidden="true" ng-if="$ctrl.state.orderBy === 'IP' && $ctrl.state.reverseOrder"></i>
|
||||
</a>
|
||||
</th>
|
||||
<th>
|
||||
<a ng-click="$ctrl.changeOrderBy('Role')">
|
||||
Role
|
||||
<i class="fa fa-sort-alpha-asc" aria-hidden="true" ng-if="$ctrl.state.orderBy === 'Role' && !$ctrl.state.reverseOrder"></i>
|
||||
<i class="fa fa-sort-alpha-desc" aria-hidden="true" ng-if="$ctrl.state.orderBy === 'Role' && $ctrl.state.reverseOrder"></i>
|
||||
</a>
|
||||
</th>
|
||||
<th>
|
||||
<a ng-click="$ctrl.changeOrderBy('Status')">
|
||||
Status
|
||||
<i class="fa fa-sort-alpha-asc" aria-hidden="true" ng-if="$ctrl.state.orderBy === 'Status' && !$ctrl.state.reverseOrder"></i>
|
||||
<i class="fa fa-sort-alpha-desc" aria-hidden="true" ng-if="$ctrl.state.orderBy === 'Status' && $ctrl.state.reverseOrder"></i>
|
||||
</a>
|
||||
</th>
|
||||
</tr>
|
||||
</thead>
|
||||
<tbody>
|
||||
<tr dir-paginate="item in ($ctrl.state.filteredDataSet = ($ctrl.dataset | filter:$ctrl.state.textFilter | orderBy:$ctrl.state.orderBy:$ctrl.state.reverseOrder | itemsPerPage: $ctrl.state.paginatedItemLimit))" ng-class="{active: item.Checked}">
|
||||
<td>{{ item.Name }}</td>
|
||||
<td>{{ item.IP }}</td>
|
||||
<td>{{ item.Role }}</td>
|
||||
<td>
|
||||
<i class="fa fa-heartbeat space-right green-icon"></i>
|
||||
{{ item.Status }}
|
||||
</td>
|
||||
</tr>
|
||||
<tr ng-if="!$ctrl.dataset">
|
||||
<td colspan="4" class="text-center text-muted">Loading...</td>
|
||||
</tr>
|
||||
<tr ng-if="$ctrl.state.filteredDataSet.length === 0">
|
||||
<td colspan="4" class="text-center text-muted">No nodes available.</td>
|
||||
</tr>
|
||||
</tbody>
|
||||
</table>
|
||||
</div>
|
||||
<div class="footer" ng-if="$ctrl.dataset">
|
||||
<div class="paginationControls">
|
||||
<form class="form-inline">
|
||||
<span class="limitSelector">
|
||||
<span style="margin-right: 5px;">
|
||||
Items per page
|
||||
</span>
|
||||
<select class="form-control" ng-model="$ctrl.state.paginatedItemLimit" ng-change="$ctrl.changePaginationLimit()">
|
||||
<option value="0">All</option>
|
||||
<option value="10">10</option>
|
||||
<option value="25">25</option>
|
||||
<option value="50">50</option>
|
||||
<option value="100">100</option>
|
||||
</select>
|
||||
</span>
|
||||
<dir-pagination-controls max-size="5"></dir-pagination-controls>
|
||||
</form>
|
||||
</div>
|
||||
</div>
|
||||
</rd-widget-body>
|
||||
</rd-widget>
|
||||
</div>
|
|
@ -0,0 +1,13 @@
|
|||
angular.module('extension.storidge').component('storidgeNodesDatatable', {
|
||||
templateUrl: 'app/extensions/storidge/components/nodes-datatable/storidgeNodesDatatable.html',
|
||||
controller: 'GenericDatatableController',
|
||||
bindings: {
|
||||
title: '@',
|
||||
titleIcon: '@',
|
||||
dataset: '<',
|
||||
tableKey: '@',
|
||||
orderBy: '@',
|
||||
reverseOrder: '<',
|
||||
showTextFilter: '<'
|
||||
}
|
||||
});
|
|
@ -0,0 +1,8 @@
|
|||
<div class="form-group">
|
||||
<label for="storidge_profile" class="col-sm-2 col-lg-1 control-label text-left">Profile</label>
|
||||
<div class="col-sm-10 col-lg-11">
|
||||
<select id="storidge_profile" ng-model="$ctrl.storidgeProfile" ng-options="profile.Name for profile in $ctrl.profiles" class="form-control">
|
||||
<option selected disabled hidden value="">Select a profile</option>
|
||||
</select>
|
||||
</div>
|
||||
</div>
|
|
@ -0,0 +1,7 @@
|
|||
angular.module('extension.storidge').component('storidgeProfileSelector', {
|
||||
templateUrl: 'app/extensions/storidge/components/profileSelector/storidgeProfileSelector.html',
|
||||
controller: 'StoridgeProfileSelectorController',
|
||||
bindings: {
|
||||
'storidgeProfile': '='
|
||||
}
|
||||
});
|
|
@ -0,0 +1,17 @@
|
|||
angular.module('extension.storidge')
|
||||
.controller('StoridgeProfileSelectorController', ['StoridgeProfileService', 'Notifications',
|
||||
function (StoridgeProfileService, Notifications) {
|
||||
var ctrl = this;
|
||||
|
||||
function initComponent() {
|
||||
StoridgeProfileService.profiles()
|
||||
.then(function success(data) {
|
||||
ctrl.profiles = data;
|
||||
})
|
||||
.catch(function error(err) {
|
||||
Notifications.error('Failure', err, 'Unable to retrieve Storidge profiles');
|
||||
});
|
||||
}
|
||||
|
||||
initComponent();
|
||||
}]);
|
|
@ -0,0 +1,84 @@
|
|||
<div class="datatable">
|
||||
<rd-widget>
|
||||
<rd-widget-body classes="no-padding">
|
||||
<div class="toolBar">
|
||||
<div class="toolBarTitle">
|
||||
<i class="fa" ng-class="$ctrl.titleIcon" aria-hidden="true" style="margin-right: 2px;"></i> {{ $ctrl.title }}
|
||||
</div>
|
||||
<div class="settings">
|
||||
<span class="setting" ng-class="{ 'setting-active': $ctrl.state.displayTextFilter }" ng-click="$ctrl.updateDisplayTextFilter()" ng-if="$ctrl.showTextFilter">
|
||||
<i class="fa fa-search" aria-hidden="true"></i> Search
|
||||
</span>
|
||||
</div>
|
||||
</div>
|
||||
<div class="actionBar">
|
||||
<button type="button" class="btn btn-sm btn-danger"
|
||||
ng-disabled="$ctrl.state.selectedItemCount === 0" ng-click="$ctrl.removeAction($ctrl.state.selectedItems)">
|
||||
<i class="fa fa-trash space-right" aria-hidden="true"></i>Remove
|
||||
</button>
|
||||
</div>
|
||||
<div class="searchBar" ng-if="$ctrl.state.displayTextFilter">
|
||||
<i class="fa fa-search searchIcon" aria-hidden="true"></i>
|
||||
<input type="text" class="searchInput" ng-model="$ctrl.state.textFilter" placeholder="Search...">
|
||||
</div>
|
||||
<div class="table-responsive">
|
||||
<table class="table table-hover">
|
||||
<thead>
|
||||
<tr>
|
||||
<th>
|
||||
<span class="md-checkbox">
|
||||
<input id="select_all" type="checkbox" ng-model="$ctrl.state.selectAll" ng-change="$ctrl.selectAll()" />
|
||||
<label for="select_all"></label>
|
||||
</span>
|
||||
<a ng-click="$ctrl.changeOrderBy('Name')">
|
||||
Name
|
||||
<i class="fa fa-sort-alpha-asc" aria-hidden="true" ng-if="$ctrl.state.orderBy === 'Name' && !$ctrl.state.reverseOrder"></i>
|
||||
<i class="fa fa-sort-alpha-desc" aria-hidden="true" ng-if="$ctrl.state.orderBy === 'Name' && $ctrl.state.reverseOrder"></i>
|
||||
</a>
|
||||
</th>
|
||||
</tr>
|
||||
</thead>
|
||||
<tbody>
|
||||
<tr dir-paginate="item in ($ctrl.state.filteredDataSet = ($ctrl.dataset | filter:$ctrl.state.textFilter | orderBy:$ctrl.state.orderBy:$ctrl.state.reverseOrder | itemsPerPage: $ctrl.state.paginatedItemLimit))" ng-class="{active: item.Checked}">
|
||||
<td>
|
||||
<span class="md-checkbox">
|
||||
<input id="select_{{ $index }}" type="checkbox" ng-model="item.Checked" ng-change="$ctrl.selectItem(item)"/>
|
||||
<label for="select_{{ $index }}"></label>
|
||||
</span>
|
||||
<a ui-sref="storidge.profiles.edit({id: item.Name})">{{ item.Name }}</a>
|
||||
</td>
|
||||
</tr>
|
||||
<tr ng-if="!$ctrl.dataset">
|
||||
<td class="text-center text-muted">Loading...</td>
|
||||
</tr>
|
||||
<tr ng-if="$ctrl.state.filteredDataSet.length === 0">
|
||||
<td class="text-center text-muted">No profile available.</td>
|
||||
</tr>
|
||||
</tbody>
|
||||
</table>
|
||||
</div>
|
||||
<div class="footer" ng-if="$ctrl.dataset">
|
||||
<div class="infoBar" ng-if="$ctrl.state.selectedItemCount !== 0">
|
||||
{{ $ctrl.state.selectedItemCount }} item(s) selected
|
||||
</div>
|
||||
<div class="paginationControls">
|
||||
<form class="form-inline">
|
||||
<span class="limitSelector">
|
||||
<span style="margin-right: 5px;">
|
||||
Items per page
|
||||
</span>
|
||||
<select class="form-control" ng-model="$ctrl.state.paginatedItemLimit" ng-change="$ctrl.changePaginationLimit()">
|
||||
<option value="0">All</option>
|
||||
<option value="10">10</option>
|
||||
<option value="25">25</option>
|
||||
<option value="50">50</option>
|
||||
<option value="100">100</option>
|
||||
</select>
|
||||
</span>
|
||||
<dir-pagination-controls max-size="5"></dir-pagination-controls>
|
||||
</form>
|
||||
</div>
|
||||
</div>
|
||||
</rd-widget-body>
|
||||
</rd-widget>
|
||||
</div>
|
|
@ -0,0 +1,14 @@
|
|||
angular.module('extension.storidge').component('storidgeProfilesDatatable', {
|
||||
templateUrl: 'app/extensions/storidge/components/profiles-datatable/storidgeProfilesDatatable.html',
|
||||
controller: 'GenericDatatableController',
|
||||
bindings: {
|
||||
title: '@',
|
||||
titleIcon: '@',
|
||||
dataset: '<',
|
||||
tableKey: '@',
|
||||
orderBy: '@',
|
||||
reverseOrder: '<',
|
||||
showTextFilter: '<',
|
||||
removeAction: '<'
|
||||
}
|
||||
});
|
|
@ -0,0 +1,6 @@
|
|||
function StoridgeEventModel(data) {
|
||||
this.Time = data.time;
|
||||
this.Category = data.category;
|
||||
this.Module = data.module;
|
||||
this.Content = data.content;
|
||||
}
|
|
@ -0,0 +1,17 @@
|
|||
function StoridgeInfoModel(data) {
|
||||
this.Domain = data.domain;
|
||||
this.Nodes = data.nodes;
|
||||
this.Status = data.status;
|
||||
this.ProvisionedBandwidth = data.provisionedBandwidth;
|
||||
this.UsedBandwidth = data.usedBandwidth;
|
||||
this.FreeBandwidth = data.freeBandwidth;
|
||||
this.TotalBandwidth = data.totalBandwidth;
|
||||
this.ProvisionedIOPS = data.provisionedIOPS;
|
||||
this.UsedIOPS = data.usedIOPS;
|
||||
this.FreeIOPS = data.freeIOPS;
|
||||
this.TotalIOPS = data.totalIOPS;
|
||||
this.ProvisionedCapacity = data.provisionedCapacity;
|
||||
this.UsedCapacity = data.usedCapacity;
|
||||
this.FreeCapacity = data.freeCapacity;
|
||||
this.TotalCapacity = data.totalCapacity;
|
||||
}
|
|
@ -0,0 +1,6 @@
|
|||
function StoridgeNodeModel(name, data) {
|
||||
this.Name = name;
|
||||
this.IP = data.ip;
|
||||
this.Role = data.role;
|
||||
this.Status = data.status;
|
||||
}
|
|
@ -0,0 +1,57 @@
|
|||
function StoridgeProfileDefaultModel() {
|
||||
this.Directory = '/cio/';
|
||||
this.Capacity = 20;
|
||||
this.Redundancy = 2;
|
||||
this.Provisioning = 'thin';
|
||||
this.Type = 'ssd';
|
||||
this.MinIOPS = 100;
|
||||
this.MaxIOPS = 2000;
|
||||
this.MinBandwidth = 1;
|
||||
this.MaxBandwidth = 100;
|
||||
}
|
||||
|
||||
function StoridgeProfileListModel(data) {
|
||||
this.Name = data;
|
||||
this.Checked = false;
|
||||
}
|
||||
|
||||
function StoridgeProfileModel(name, data) {
|
||||
this.Name = name;
|
||||
this.Directory = data.directory;
|
||||
this.Capacity = data.capacity;
|
||||
this.Provisioning = data.provision;
|
||||
this.Type = data.type;
|
||||
this.Redundancy = data.level;
|
||||
|
||||
if (data.iops) {
|
||||
this.MinIOPS = data.iops.min;
|
||||
this.MaxIOPS = data.iops.max;
|
||||
}
|
||||
|
||||
if (data.bandwidth) {
|
||||
this.MinBandwidth = data.bandwidth.min;
|
||||
this.MaxBandwidth = data.bandwidth.max;
|
||||
}
|
||||
}
|
||||
|
||||
function StoridgeCreateProfileRequest(model) {
|
||||
this.name = model.Name;
|
||||
this.capacity = model.Capacity;
|
||||
this.directory = model.Directory;
|
||||
this.provision = model.Provisioning;
|
||||
this.type = model.Type;
|
||||
this.level = model.Redundancy;
|
||||
if (model.MinIOPS && model.MaxIOPS) {
|
||||
this.iops = {
|
||||
min: model.MinIOPS,
|
||||
max: model.MaxIOPS
|
||||
};
|
||||
}
|
||||
|
||||
if (model.MinBandwidth && model.MaxBandwidth) {
|
||||
this.bandwidth = {
|
||||
min: model.MinBandwidth,
|
||||
max: model.MaxBandwidth
|
||||
};
|
||||
}
|
||||
}
|
|
@ -0,0 +1,44 @@
|
|||
angular.module('extension.storidge')
|
||||
.factory('StoridgeCluster', ['$http', 'StoridgeManager', function StoridgeClusterFactory($http, StoridgeManager) {
|
||||
'use strict';
|
||||
|
||||
var service = {};
|
||||
|
||||
service.queryEvents = function() {
|
||||
return $http({
|
||||
method: 'GET',
|
||||
url: StoridgeManager.StoridgeAPIURL() + '/events',
|
||||
skipAuthorization: true,
|
||||
timeout: 4500,
|
||||
ignoreLoadingBar: true
|
||||
});
|
||||
};
|
||||
|
||||
service.queryVersion = function() {
|
||||
return $http({
|
||||
method: 'GET',
|
||||
url: StoridgeManager.StoridgeAPIURL() + '/version',
|
||||
skipAuthorization: true
|
||||
});
|
||||
};
|
||||
|
||||
service.queryInfo = function() {
|
||||
return $http({
|
||||
method: 'GET',
|
||||
url: StoridgeManager.StoridgeAPIURL() + '/info',
|
||||
skipAuthorization: true,
|
||||
timeout: 4500,
|
||||
ignoreLoadingBar: true
|
||||
});
|
||||
};
|
||||
|
||||
service.reboot = function() {
|
||||
return $http({
|
||||
method: 'POST',
|
||||
url: StoridgeManager.StoridgeAPIURL() + '/cluster/reboot',
|
||||
skipAuthorization: true
|
||||
});
|
||||
};
|
||||
|
||||
return service;
|
||||
}]);
|
|
@ -0,0 +1,24 @@
|
|||
angular.module('extension.storidge')
|
||||
.factory('StoridgeNodes', ['$http', 'StoridgeManager', function StoridgeNodesFactory($http, StoridgeManager) {
|
||||
'use strict';
|
||||
|
||||
var service = {};
|
||||
|
||||
service.query = function() {
|
||||
return $http({
|
||||
method: 'GET',
|
||||
url: StoridgeManager.StoridgeAPIURL() + '/nodes',
|
||||
skipAuthorization: true
|
||||
});
|
||||
};
|
||||
|
||||
service.inspect = function(id) {
|
||||
return $http({
|
||||
method: 'GET',
|
||||
url: StoridgeManager.StoridgeAPIURL() + '/nodes/' + id,
|
||||
skipAuthorization: true
|
||||
});
|
||||
};
|
||||
|
||||
return service;
|
||||
}]);
|
|
@ -0,0 +1,52 @@
|
|||
angular.module('extension.storidge')
|
||||
.factory('StoridgeProfiles', ['$http', 'StoridgeManager', function StoridgeProfilesFactory($http, StoridgeManager) {
|
||||
'use strict';
|
||||
|
||||
var service = {};
|
||||
|
||||
service.create = function(payload) {
|
||||
return $http({
|
||||
method: 'POST',
|
||||
url: StoridgeManager.StoridgeAPIURL() + '/profiles',
|
||||
data: payload,
|
||||
headers: { 'Content-type': 'application/json' },
|
||||
skipAuthorization: true
|
||||
});
|
||||
};
|
||||
|
||||
service.update = function(id, payload) {
|
||||
return $http({
|
||||
method: 'PUT',
|
||||
url: StoridgeManager.StoridgeAPIURL() + '/profiles/' + id,
|
||||
data: payload,
|
||||
headers: { 'Content-type': 'application/json' },
|
||||
skipAuthorization: true
|
||||
});
|
||||
};
|
||||
|
||||
service.query = function() {
|
||||
return $http({
|
||||
method: 'GET',
|
||||
url: StoridgeManager.StoridgeAPIURL() + '/profiles',
|
||||
skipAuthorization: true
|
||||
});
|
||||
};
|
||||
|
||||
service.inspect = function(id) {
|
||||
return $http({
|
||||
method: 'GET',
|
||||
url: StoridgeManager.StoridgeAPIURL() + '/profiles/' + id,
|
||||
skipAuthorization: true
|
||||
});
|
||||
};
|
||||
|
||||
service.delete = function(id) {
|
||||
return $http({
|
||||
method: 'DELETE',
|
||||
url: StoridgeManager.StoridgeAPIURL() + '/profiles/' + id,
|
||||
skipAuthorization: true
|
||||
});
|
||||
};
|
||||
|
||||
return service;
|
||||
}]);
|
|
@ -0,0 +1,189 @@
|
|||
angular.module('extension.storidge')
|
||||
.factory('StoridgeChartService', [function StoridgeChartService() {
|
||||
'use strict';
|
||||
|
||||
// Max. number of items to display on a chart
|
||||
var CHART_LIMIT = 600;
|
||||
|
||||
var service = {};
|
||||
|
||||
service.CreateCapacityChart = function(context) {
|
||||
return new Chart(context, {
|
||||
type: 'doughnut',
|
||||
data: {
|
||||
datasets: [
|
||||
{
|
||||
data: [],
|
||||
backgroundColor: [
|
||||
'rgba(171, 213, 255, 0.7)',
|
||||
'rgba(229, 57, 53, 0.7)'
|
||||
]
|
||||
}
|
||||
],
|
||||
labels: []
|
||||
},
|
||||
options: {
|
||||
tooltips: {
|
||||
callbacks: {
|
||||
label: function(tooltipItem, data) {
|
||||
var dataset = data.datasets[tooltipItem.datasetIndex];
|
||||
var label = data.labels[tooltipItem.index];
|
||||
var value = dataset.data[tooltipItem.index];
|
||||
return label + ': ' + filesize(value, {base: 10, round: 1});
|
||||
}
|
||||
}
|
||||
},
|
||||
animation: {
|
||||
duration: 0
|
||||
},
|
||||
responsiveAnimationDuration: 0,
|
||||
responsive: true,
|
||||
hover: {
|
||||
animationDuration: 0
|
||||
}
|
||||
}
|
||||
});
|
||||
};
|
||||
|
||||
service.CreateIOPSChart = function(context) {
|
||||
return new Chart(context, {
|
||||
type: 'line',
|
||||
data: {
|
||||
labels: [],
|
||||
datasets: [
|
||||
{
|
||||
label: 'IOPS',
|
||||
data: [],
|
||||
fill: true,
|
||||
backgroundColor: 'rgba(151,187,205,0.4)',
|
||||
borderColor: 'rgba(151,187,205,0.6)',
|
||||
pointBackgroundColor: 'rgba(151,187,205,1)',
|
||||
pointBorderColor: 'rgba(151,187,205,1)',
|
||||
pointRadius: 2,
|
||||
borderWidth: 2
|
||||
}
|
||||
]
|
||||
},
|
||||
options: {
|
||||
animation: {
|
||||
duration: 0
|
||||
},
|
||||
responsiveAnimationDuration: 0,
|
||||
responsive: true,
|
||||
tooltips: {
|
||||
mode: 'index',
|
||||
intersect: false,
|
||||
position: 'nearest'
|
||||
},
|
||||
hover: {
|
||||
animationDuration: 0
|
||||
},
|
||||
scales: {
|
||||
yAxes: [
|
||||
{
|
||||
ticks: {
|
||||
beginAtZero: true
|
||||
}
|
||||
}
|
||||
]
|
||||
}
|
||||
}
|
||||
});
|
||||
};
|
||||
|
||||
service.CreateBandwidthChart = function(context) {
|
||||
return new Chart(context, {
|
||||
type: 'line',
|
||||
data: {
|
||||
labels: [],
|
||||
datasets: [
|
||||
{
|
||||
label: 'Bandwidth',
|
||||
data: [],
|
||||
fill: true,
|
||||
backgroundColor: 'rgba(151,187,205,0.4)',
|
||||
borderColor: 'rgba(151,187,205,0.6)',
|
||||
pointBackgroundColor: 'rgba(151,187,205,1)',
|
||||
pointBorderColor: 'rgba(151,187,205,1)',
|
||||
pointRadius: 2,
|
||||
borderWidth: 2
|
||||
}
|
||||
]
|
||||
},
|
||||
options: {
|
||||
animation: {
|
||||
duration: 0
|
||||
},
|
||||
responsiveAnimationDuration: 0,
|
||||
responsive: true,
|
||||
tooltips: {
|
||||
mode: 'index',
|
||||
intersect: false,
|
||||
position: 'nearest',
|
||||
callbacks: {
|
||||
label: function(tooltipItem, data) {
|
||||
var datasetLabel = data.datasets[tooltipItem.datasetIndex].label;
|
||||
return bytePerSecBasedTooltipLabel(datasetLabel, tooltipItem.yLabel);
|
||||
}
|
||||
}
|
||||
},
|
||||
hover: {
|
||||
animationDuration: 0
|
||||
},
|
||||
scales: {
|
||||
yAxes: [
|
||||
{
|
||||
ticks: {
|
||||
beginAtZero: true,
|
||||
callback: bytePerSecBasedAxisLabel
|
||||
}
|
||||
}
|
||||
]
|
||||
}
|
||||
}
|
||||
});
|
||||
};
|
||||
|
||||
service.UpdateChart = function(label, value, chart) {
|
||||
chart.data.labels.push(label);
|
||||
chart.data.datasets[0].data.push(value);
|
||||
|
||||
if (chart.data.datasets[0].data.length > CHART_LIMIT) {
|
||||
chart.data.labels.pop();
|
||||
chart.data.datasets[0].data.pop();
|
||||
}
|
||||
|
||||
chart.update(0);
|
||||
};
|
||||
|
||||
service.UpdatePieChart = function(label, value, chart) {
|
||||
var idx = chart.data.labels.indexOf(label);
|
||||
if (idx > -1) {
|
||||
chart.data.datasets[0].data[idx] = value;
|
||||
} else {
|
||||
chart.data.labels.push(label);
|
||||
chart.data.datasets[0].data.push(value);
|
||||
}
|
||||
|
||||
chart.update(0);
|
||||
};
|
||||
|
||||
function bytePerSecBasedTooltipLabel(label, value) {
|
||||
var processedValue = 0;
|
||||
if (value > 5) {
|
||||
processedValue = filesize(value, {base: 10, round: 1});
|
||||
} else {
|
||||
processedValue = value.toFixed(1) + 'B';
|
||||
}
|
||||
return label + ': ' + processedValue + '/s';
|
||||
}
|
||||
|
||||
function bytePerSecBasedAxisLabel(value, index, values) {
|
||||
if (value > 5) {
|
||||
return filesize(value, {base: 10, round: 1});
|
||||
}
|
||||
return value.toFixed(1) + 'B/s';
|
||||
}
|
||||
|
||||
return service;
|
||||
}]);
|
|
@ -0,0 +1,58 @@
|
|||
angular.module('extension.storidge')
|
||||
.factory('StoridgeClusterService', ['$q', 'StoridgeCluster', function StoridgeClusterServiceFactory($q, StoridgeCluster) {
|
||||
'use strict';
|
||||
var service = {};
|
||||
|
||||
service.reboot = function() {
|
||||
return StoridgeCluster.reboot();
|
||||
};
|
||||
|
||||
service.info = function() {
|
||||
var deferred = $q.defer();
|
||||
|
||||
StoridgeCluster.queryInfo()
|
||||
.then(function success(response) {
|
||||
var info = new StoridgeInfoModel(response.data);
|
||||
deferred.resolve(info);
|
||||
})
|
||||
.catch(function error(err) {
|
||||
deferred.reject({ msg: 'Unable to retrieve Storidge information', err: err });
|
||||
});
|
||||
|
||||
return deferred.promise;
|
||||
};
|
||||
|
||||
service.version = function() {
|
||||
var deferred = $q.defer();
|
||||
|
||||
StoridgeCluster.queryVersion()
|
||||
.then(function success(response) {
|
||||
var version = response.data.version;
|
||||
deferred.resolve(version);
|
||||
})
|
||||
.catch(function error(err) {
|
||||
deferred.reject({ msg: 'Unable to retrieve Storidge version', err: err });
|
||||
});
|
||||
|
||||
return deferred.promise;
|
||||
};
|
||||
|
||||
service.events = function() {
|
||||
var deferred = $q.defer();
|
||||
|
||||
StoridgeCluster.queryEvents()
|
||||
.then(function success(response) {
|
||||
var events = response.data.map(function(item) {
|
||||
return new StoridgeEventModel(item);
|
||||
});
|
||||
deferred.resolve(events);
|
||||
})
|
||||
.catch(function error(err) {
|
||||
deferred.reject({ msg: 'Unable to retrieve Storidge events', err: err });
|
||||
});
|
||||
|
||||
return deferred.promise;
|
||||
};
|
||||
|
||||
return service;
|
||||
}]);
|
|
@ -0,0 +1,48 @@
|
|||
angular.module('extension.storidge')
|
||||
.factory('StoridgeManager', ['$q', 'LocalStorage', 'SystemService', function StoridgeManagerFactory($q, LocalStorage, SystemService) {
|
||||
'use strict';
|
||||
var service = {
|
||||
API: ''
|
||||
};
|
||||
|
||||
service.init = function() {
|
||||
var deferred = $q.defer();
|
||||
|
||||
var storedAPIURL = LocalStorage.getStoridgeAPIURL();
|
||||
if (storedAPIURL) {
|
||||
service.API = storedAPIURL;
|
||||
deferred.resolve();
|
||||
} else {
|
||||
SystemService.info()
|
||||
.then(function success(data) {
|
||||
var endpointAddress = LocalStorage.getEndpointPublicURL();
|
||||
var storidgeAPIURL = '';
|
||||
if (endpointAddress) {
|
||||
storidgeAPIURL = 'http://' + endpointAddress + ':8282';
|
||||
} else {
|
||||
var managerIP = data.Swarm.NodeAddr;
|
||||
storidgeAPIURL = 'http://' + managerIP + ':8282';
|
||||
}
|
||||
|
||||
service.API = storidgeAPIURL;
|
||||
LocalStorage.storeStoridgeAPIURL(storidgeAPIURL);
|
||||
deferred.resolve();
|
||||
})
|
||||
.catch(function error(err) {
|
||||
deferred.reject({ msg: 'Unable to retrieve Storidge API URL', err: err });
|
||||
});
|
||||
}
|
||||
|
||||
return deferred.promise;
|
||||
};
|
||||
|
||||
service.reset = function() {
|
||||
LocalStorage.clearStoridgeAPIURL();
|
||||
};
|
||||
|
||||
service.StoridgeAPIURL = function() {
|
||||
return service.API;
|
||||
};
|
||||
|
||||
return service;
|
||||
}]);
|
|
@ -0,0 +1,30 @@
|
|||
angular.module('extension.storidge')
|
||||
.factory('StoridgeNodeService', ['$q', 'StoridgeNodes', function StoridgeNodeServiceFactory($q, StoridgeNodes) {
|
||||
'use strict';
|
||||
var service = {};
|
||||
|
||||
service.nodes = function() {
|
||||
var deferred = $q.defer();
|
||||
|
||||
StoridgeNodes.query()
|
||||
.then(function success(response) {
|
||||
var nodeData = response.data.nodes;
|
||||
var nodes = [];
|
||||
|
||||
for (var key in nodeData) {
|
||||
if (nodeData.hasOwnProperty(key)) {
|
||||
nodes.push(new StoridgeNodeModel(key, nodeData[key]));
|
||||
}
|
||||
}
|
||||
|
||||
deferred.resolve(nodes);
|
||||
})
|
||||
.catch(function error(err) {
|
||||
deferred.reject({ msg: 'Unable to retrieve Storidge profiles', err: err });
|
||||
});
|
||||
|
||||
return deferred.promise;
|
||||
};
|
||||
|
||||
return service;
|
||||
}]);
|
|
@ -0,0 +1,53 @@
|
|||
angular.module('extension.storidge')
|
||||
.factory('StoridgeProfileService', ['$q', 'StoridgeProfiles', function StoridgeProfileServiceFactory($q, StoridgeProfiles) {
|
||||
'use strict';
|
||||
var service = {};
|
||||
|
||||
service.create = function(model) {
|
||||
var payload = new StoridgeCreateProfileRequest(model);
|
||||
return StoridgeProfiles.create(payload);
|
||||
};
|
||||
|
||||
service.update = function(model) {
|
||||
var payload = new StoridgeCreateProfileRequest(model);
|
||||
return StoridgeProfiles.update(model.Name, payload);
|
||||
};
|
||||
|
||||
service.delete = function(profileName) {
|
||||
return StoridgeProfiles.delete(profileName);
|
||||
};
|
||||
|
||||
service.profile = function(profileName) {
|
||||
var deferred = $q.defer();
|
||||
|
||||
StoridgeProfiles.inspect(profileName)
|
||||
.then(function success(response) {
|
||||
var profile = new StoridgeProfileModel(profileName, response.data);
|
||||
deferred.resolve(profile);
|
||||
})
|
||||
.catch(function error(err) {
|
||||
deferred.reject({ msg: 'Unable to retrieve Storidge profile details', err: err });
|
||||
});
|
||||
|
||||
return deferred.promise;
|
||||
};
|
||||
|
||||
service.profiles = function() {
|
||||
var deferred = $q.defer();
|
||||
|
||||
StoridgeProfiles.query()
|
||||
.then(function success(response) {
|
||||
var profiles = response.data.profiles.map(function (item) {
|
||||
return new StoridgeProfileListModel(item);
|
||||
});
|
||||
deferred.resolve(profiles);
|
||||
})
|
||||
.catch(function error(err) {
|
||||
deferred.reject({ msg: 'Unable to retrieve Storidge profiles', err: err });
|
||||
});
|
||||
|
||||
return deferred.promise;
|
||||
};
|
||||
|
||||
return service;
|
||||
}]);
|
|
@ -0,0 +1,145 @@
|
|||
<rd-header>
|
||||
<rd-header-title title="Storidge cluster">
|
||||
<a data-toggle="tooltip" title="Refresh" ui-sref="storidge.cluster" ui-sref-opts="{reload: true}">
|
||||
<i class="fa fa-refresh" aria-hidden="true"></i>
|
||||
</a>
|
||||
</rd-header-title>
|
||||
<rd-header-content>
|
||||
<a ui-sref="storidge.cluster">Storidge</a>
|
||||
</rd-header-content>
|
||||
</rd-header>
|
||||
|
||||
<div class="row">
|
||||
<div class="col-sm-12">
|
||||
<rd-widget>
|
||||
<rd-widget-header icon="fa-bolt" title="Cluster details"></rd-widget-header>
|
||||
<rd-widget-body>
|
||||
<table class="table">
|
||||
<tbody>
|
||||
<tr>
|
||||
<td>Domain</td>
|
||||
<td>{{ clusterInfo.Domain }}</td>
|
||||
</tr>
|
||||
<tr>
|
||||
<td>Status</td>
|
||||
<td><i class="fa fa-heartbeat space-right green-icon"></i> {{ clusterInfo.Status }}</td>
|
||||
</tr>
|
||||
<tr>
|
||||
<td>Version</td>
|
||||
<td>{{ clusterVersion }}</td>
|
||||
</tr>
|
||||
</tbody>
|
||||
</table>
|
||||
<form class="form-horizontal">
|
||||
<div class="col-sm-12 form-section-title">
|
||||
Actions
|
||||
</div>
|
||||
<div class="form-group">
|
||||
<div class="col-sm-12">
|
||||
<button type="button" class="btn btn-danger btn-sm" ng-click="shutdownCluster()" ng-disabled="state.shutdownInProgress" button-spinner="state.shutdownInProgress">
|
||||
<span ng-hide="state.updateInProgress"><i class="fa fa-power-off space-right" aria-hidden="true"></i> Shutdown the cluster</span>
|
||||
<span ng-show="state.updateInProgress">Shutting down cluster...</span>
|
||||
</button>
|
||||
<button type="button" class="btn btn-danger btn-sm" ng-click="rebootCluster()" ng-disabled="state.rebootInProgress" button-spinner="state.shutdownInProgress">
|
||||
<span ng-hide="state.deleteInProgress"><i class="fa fa-refresh space-right" aria-hidden="true"></i> Reboot the cluster</span>
|
||||
<span ng-show="state.deleteInProgress">Rebooting cluster...</span>
|
||||
</button>
|
||||
</div>
|
||||
</div>
|
||||
</form>
|
||||
</rd-widget-body>
|
||||
</rd-widget>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div class="row">
|
||||
<div class="col-sm-12">
|
||||
<storidge-nodes-datatable
|
||||
title="Storage nodes" title-icon="fa-object-group"
|
||||
dataset="clusterNodes" table-key="storidge_nodes"
|
||||
order-by="Name" show-text-filter="true"
|
||||
></storidge-nodes-datatable>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<!-- <div class="row" ng-if="clusterInfo">
|
||||
<div class="col-sm-12">
|
||||
<rd-widget>
|
||||
<rd-widget-header icon="fa-object-group" title="Storage nodes">
|
||||
<div class="pull-right">
|
||||
Items per page:
|
||||
<select ng-model="state.pagination_count" ng-change="changePaginationCount()">
|
||||
<option value="0">All</option>
|
||||
<option value="10">10</option>
|
||||
<option value="25">25</option>
|
||||
<option value="50">50</option>
|
||||
<option value="100">100</option>
|
||||
</select>
|
||||
</div>
|
||||
</rd-widget-header>
|
||||
<rd-widget-taskbar classes="col-lg-12">
|
||||
<div class="pull-right">
|
||||
<input type="text" id="filter" ng-model="state.filter" placeholder="Filter..." class="form-control input-sm" />
|
||||
</div>
|
||||
</rd-widget-taskbar>
|
||||
<rd-widget-body classes="no-padding">
|
||||
<div class="table-responsive">
|
||||
<table class="table table-hover">
|
||||
<thead>
|
||||
<tr>
|
||||
<th>
|
||||
<a ng-click="order('Name')">
|
||||
Name
|
||||
<span ng-show="sortType == 'Name' && !sortReverse" class="glyphicon glyphicon-chevron-down"></span>
|
||||
<span ng-show="sortType == 'Name' && sortReverse" class="glyphicon glyphicon-chevron-up"></span>
|
||||
</a>
|
||||
</th>
|
||||
<th>
|
||||
<a ng-click="order('IP')">
|
||||
IP address
|
||||
<span ng-show="sortType == 'IP' && !sortReverse" class="glyphicon glyphicon-chevron-down"></span>
|
||||
<span ng-show="sortType == 'IP' && sortReverse" class="glyphicon glyphicon-chevron-up"></span>
|
||||
</a>
|
||||
</th>
|
||||
<th>
|
||||
<a ng-click="order('Role')">
|
||||
Role
|
||||
<span ng-show="sortType == 'Role' && !sortReverse" class="glyphicon glyphicon-chevron-down"></span>
|
||||
<span ng-show="sortType == 'Role' && sortReverse" class="glyphicon glyphicon-chevron-up"></span>
|
||||
</a>
|
||||
</th>
|
||||
<th>
|
||||
<a ng-click="order('Status')">
|
||||
Status
|
||||
<span ng-show="sortType == 'Status' && !sortReverse" class="glyphicon glyphicon-chevron-down"></span>
|
||||
<span ng-show="sortType == 'Status' && sortReverse" class="glyphicon glyphicon-chevron-up"></span>
|
||||
</a>
|
||||
</th>
|
||||
</tr>
|
||||
</thead>
|
||||
<tbody>
|
||||
<tr dir-paginate="node in (clusterNodes | filter:state.filter | orderBy:sortType:sortReverse | itemsPerPage: state.pagination_count)">
|
||||
<td>{{ node.Name }}</td>
|
||||
<td>{{ node.IP }}</td>
|
||||
<td>{{ node.Role }}</td>
|
||||
<td>
|
||||
<i class="fa fa-heartbeat space-right green-icon"></i>
|
||||
{{ node.Status }}
|
||||
</td>
|
||||
</tr>
|
||||
<tr ng-if="!clusterNodes">
|
||||
<td colspan="4" class="text-center text-muted">Loading...</td>
|
||||
</tr>
|
||||
<tr ng-if="clusterNodes.length === 0">
|
||||
<td colspan="4" class="text-center text-muted">No nodes available.</td>
|
||||
</tr>
|
||||
</tbody>
|
||||
</table>
|
||||
<div ng-if="clusterNodes" class="pull-left pagination-controls">
|
||||
<dir-pagination-controls></dir-pagination-controls>
|
||||
</div>
|
||||
</div>
|
||||
</rd-widget-body>
|
||||
</rd-widget>
|
||||
</div>
|
||||
</div> -->
|
|
@ -0,0 +1,87 @@
|
|||
angular.module('extension.storidge')
|
||||
.controller('StoridgeClusterController', ['$q', '$scope', '$state', 'Notifications', 'StoridgeClusterService', 'StoridgeNodeService', 'StoridgeManager', 'ModalService',
|
||||
function ($q, $scope, $state, Notifications, StoridgeClusterService, StoridgeNodeService, StoridgeManager, ModalService) {
|
||||
|
||||
$scope.state = {
|
||||
shutdownInProgress: false,
|
||||
rebootInProgress: false
|
||||
};
|
||||
|
||||
$scope.rebootCluster = function() {
|
||||
ModalService.confirm({
|
||||
title: 'Are you sure?',
|
||||
message: 'All the nodes in the cluster will reboot during the process. Do you want to reboot the Storidge cluster?',
|
||||
buttons: {
|
||||
confirm: {
|
||||
label: 'Reboot',
|
||||
className: 'btn-danger'
|
||||
}
|
||||
},
|
||||
callback: function onConfirm(confirmed) {
|
||||
if(!confirmed) { return; }
|
||||
rebootCluster();
|
||||
}
|
||||
});
|
||||
};
|
||||
|
||||
$scope.shutdownCluster = function() {
|
||||
ModalService.confirm({
|
||||
title: 'Are you sure?',
|
||||
message: 'All the nodes in the cluster will shutdown. Do you want to shutdown the Storidge cluster?',
|
||||
buttons: {
|
||||
confirm: {
|
||||
label: 'Shutdown',
|
||||
className: 'btn-danger'
|
||||
}
|
||||
},
|
||||
callback: function onConfirm(confirmed) {
|
||||
if(!confirmed) { return; }
|
||||
shutdownCluster();
|
||||
}
|
||||
});
|
||||
};
|
||||
|
||||
function shutdownCluster() {
|
||||
Notifications.error('Not implemented', {}, 'Not implemented yet');
|
||||
$state.reload();
|
||||
}
|
||||
|
||||
function rebootCluster() {
|
||||
$scope.state.rebootInProgress = true;
|
||||
StoridgeClusterService.reboot()
|
||||
.then(function success(data) {
|
||||
Notifications.success('Cluster successfully rebooted');
|
||||
$state.reload();
|
||||
})
|
||||
.catch(function error(err) {
|
||||
Notifications.error('Failure', err, 'Unable to reboot cluster');
|
||||
})
|
||||
.finally(function final() {
|
||||
$scope.state.rebootInProgress = false;
|
||||
});
|
||||
}
|
||||
|
||||
function initView() {
|
||||
$q.all({
|
||||
info: StoridgeClusterService.info(),
|
||||
version: StoridgeClusterService.version(),
|
||||
nodes: StoridgeNodeService.nodes()
|
||||
})
|
||||
.then(function success(data) {
|
||||
$scope.clusterInfo = data.info;
|
||||
$scope.clusterVersion = data.version;
|
||||
$scope.clusterNodes = data.nodes;
|
||||
})
|
||||
.catch(function error(err) {
|
||||
Notifications.error('Failure', err, 'Unable to retrieve cluster information');
|
||||
});
|
||||
}
|
||||
|
||||
StoridgeManager.init()
|
||||
.then(function success() {
|
||||
initView();
|
||||
})
|
||||
.catch(function error(err) {
|
||||
Notifications.error('Failure', err, 'Unable to communicate with Storidge API');
|
||||
});
|
||||
}]);
|
|
@ -0,0 +1,201 @@
|
|||
<rd-header>
|
||||
<rd-header-title title="Storidge monitor">
|
||||
<a data-toggle="tooltip" title="Refresh" ui-sref="storidge.monitor" ui-sref-opts="{reload: true}">
|
||||
<i class="fa fa-refresh" aria-hidden="true"></i>
|
||||
</a>
|
||||
</rd-header-title>
|
||||
<rd-header-content>
|
||||
<a ui-sref="storidge.cluster">Storidge</a> > <a ui-sref="storidge.monitor">Cluster monitoring</a>
|
||||
</rd-header-content>
|
||||
</rd-header>
|
||||
|
||||
<div class="row">
|
||||
<div class="col-md-4 col-sm-12">
|
||||
<rd-widget>
|
||||
<rd-widget-header icon="fa-tachometer" title="Cluster capacity"></rd-widget-header>
|
||||
<rd-widget-body>
|
||||
<div class="chart-container" style="position: relative;">
|
||||
<canvas id="capacityChart" width="770" height="400"></canvas>
|
||||
</div>
|
||||
<div style="margin-top: 10px;" ng-if="info">
|
||||
<table class="table">
|
||||
<tbody>
|
||||
<tr>
|
||||
<td>Capacity available</td>
|
||||
<td>{{ ((info.FreeCapacity * 100) / info.TotalCapacity).toFixed(1) }}%</td>
|
||||
</tr>
|
||||
<tr>
|
||||
<td>Provisioned capacity</td>
|
||||
<td>
|
||||
{{ info.ProvisionedCapacity | humansize }}
|
||||
<span ng-if="+(info.ProvisionedCapacity) >= +(info.TotalCapacity)">
|
||||
<i class="fa fa-exclamation-triangle red-icon" aria-hidden="true" style="margin-left: 2px;"></i>
|
||||
</span>
|
||||
</td>
|
||||
</tr>
|
||||
<tr>
|
||||
<td>Total capacity</td>
|
||||
<td>{{ info.TotalCapacity | humansize }}</td>
|
||||
</tr>
|
||||
</tbody>
|
||||
</table>
|
||||
</div>
|
||||
</rd-widget-body>
|
||||
</rd-widget>
|
||||
</div>
|
||||
<div class="col-md-4 col-sm-12">
|
||||
<rd-widget>
|
||||
<rd-widget-header icon="fa-area-chart" title="IOPS usage"></rd-widget-header>
|
||||
<rd-widget-body>
|
||||
<div class="chart-container" style="position: relative;">
|
||||
<canvas id="iopsChart" width="770" height="400"></canvas>
|
||||
</div>
|
||||
<div style="margin-top: 10px;" ng-if="info">
|
||||
<table class="table">
|
||||
<tbody>
|
||||
<tr>
|
||||
<td>IOPS available</td>
|
||||
<td>{{ ((info.FreeIOPS * 100) / info.TotalIOPS).toFixed(1) }}%</td>
|
||||
</tr>
|
||||
<tr>
|
||||
<td>Provisioned IOPS</td>
|
||||
<td>
|
||||
{{ info.ProvisionedIOPS | number }}
|
||||
<span ng-if="+(info.ProvisionedIOPS) >= +(info.TotalIOPS)">
|
||||
<i class="fa fa-exclamation-triangle red-icon" aria-hidden="true" style="margin-left: 2px;"></i>
|
||||
</span>
|
||||
</td>
|
||||
</tr>
|
||||
<tr>
|
||||
<td>Total IOPS</td>
|
||||
<td>{{ info.TotalIOPS | number }}</td>
|
||||
</tr>
|
||||
</tbody>
|
||||
</table>
|
||||
</div>
|
||||
</rd-widget-body>
|
||||
</rd-widget>
|
||||
</div>
|
||||
<div class="col-md-4 col-sm-12">
|
||||
<rd-widget>
|
||||
<rd-widget-header icon="fa-area-chart" title="Bandwith usage"></rd-widget-header>
|
||||
<rd-widget-body>
|
||||
<div class="chart-container" style="position: relative;">
|
||||
<canvas id="bandwithChart" width="770" height="400"></canvas>
|
||||
</div>
|
||||
<div style="margin-top: 10px;" ng-if="info">
|
||||
<table class="table">
|
||||
<tbody>
|
||||
<tr>
|
||||
<td>Bandwidth available</td>
|
||||
<td>{{ ((info.FreeBandwidth * 100) / info.TotalBandwidth).toFixed(1) }}%</td>
|
||||
</tr>
|
||||
<tr>
|
||||
<td>Provisioned bandwidth</td>
|
||||
<td>
|
||||
{{ info.ProvisionedBandwidth | humansize }}
|
||||
<span ng-if="+(info.ProvisionedBandwidth) >= +(info.TotalBandwidth)">
|
||||
<i class="fa fa-exclamation-triangle red-icon" aria-hidden="true" style="margin-left: 2px;"></i>
|
||||
</span>
|
||||
</td>
|
||||
</tr>
|
||||
<tr>
|
||||
<td>Total bandwidth</td>
|
||||
<td>{{ info.TotalBandwidth | humansize }} /s</td>
|
||||
</tr>
|
||||
</tbody>
|
||||
</table>
|
||||
</div>
|
||||
</rd-widget-body>
|
||||
</rd-widget>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div class="row">
|
||||
<div class="col-sm-12">
|
||||
<storidge-cluster-events-datatable
|
||||
title="Cluster events" title-icon="fa-history"
|
||||
dataset="events" table-key="storidge_cluster_events"
|
||||
order-by="Time" show-text-filter="true" reverse-order="true"
|
||||
></storidge-cluster-events-datatable>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<!-- <div class="row">
|
||||
<div class="col-md-12">
|
||||
<rd-widget>
|
||||
<rd-widget-header icon="fa-history" title="Cluster events">
|
||||
<div class="pull-right">
|
||||
Items per page:
|
||||
<select ng-model="state.pagination_count" ng-change="changePaginationCount()">
|
||||
<option value="0">All</option>
|
||||
<option value="10">10</option>
|
||||
<option value="25">25</option>
|
||||
<option value="50">50</option>
|
||||
<option value="100">100</option>
|
||||
</select>
|
||||
</div>
|
||||
</rd-widget-header>
|
||||
<rd-widget-taskbar classes="col-lg-12">
|
||||
<div class="pull-right">
|
||||
<input type="text" id="filter" ng-model="state.filter" placeholder="Filter..." class="form-control input-sm" />
|
||||
</div>
|
||||
</rd-widget-taskbar>
|
||||
<rd-widget-body classes="no-padding">
|
||||
<div class="table-responsive">
|
||||
<table class="table table-hover">
|
||||
<thead>
|
||||
<tr>
|
||||
<th>
|
||||
<a ng-click="order('Time')">
|
||||
Date
|
||||
<span ng-show="sortType == 'Time' && !sortReverse" class="glyphicon glyphicon-chevron-down"></span>
|
||||
<span ng-show="sortType == 'Time' && sortReverse" class="glyphicon glyphicon-chevron-up"></span>
|
||||
</a>
|
||||
</th>
|
||||
<th>
|
||||
<a ng-click="order('Category')">
|
||||
Category
|
||||
<span ng-show="sortType == 'Category' && !sortReverse" class="glyphicon glyphicon-chevron-down"></span>
|
||||
<span ng-show="sortType == 'Category' && sortReverse" class="glyphicon glyphicon-chevron-up"></span>
|
||||
</a>
|
||||
</th>
|
||||
<th>
|
||||
<a ng-click="order('Module')">
|
||||
Module
|
||||
<span ng-show="sortType == 'Module' && !sortReverse" class="glyphicon glyphicon-chevron-down"></span>
|
||||
<span ng-show="sortType == 'Module' && sortReverse" class="glyphicon glyphicon-chevron-up"></span>
|
||||
</a>
|
||||
</th>
|
||||
<th>
|
||||
<a ng-click="order('Content')">
|
||||
Content
|
||||
<span ng-show="sortType == 'Content' && !sortReverse" class="glyphicon glyphicon-chevron-down"></span>
|
||||
<span ng-show="sortType == 'Content' && sortReverse" class="glyphicon glyphicon-chevron-up"></span>
|
||||
</a>
|
||||
</th>
|
||||
</tr>
|
||||
</thead>
|
||||
<tbody>
|
||||
<tr dir-paginate="event in (events | filter:state.filter | orderBy:sortType:sortReverse | itemsPerPage: state.pagination_count)">
|
||||
<td>{{ event.Time }}</td>
|
||||
<td>{{ event.Category }}</td>
|
||||
<td>{{ event.Module }}</td>
|
||||
<td>{{ event.Content }}</td>
|
||||
</tr>
|
||||
<tr ng-if="!events">
|
||||
<td colspan="4" class="text-center text-muted">Loading...</td>
|
||||
</tr>
|
||||
<tr ng-if="events.length === 0">
|
||||
<td colspan="4" class="text-center text-muted">No events available.</td>
|
||||
</tr>
|
||||
</tbody>
|
||||
</table>
|
||||
<div ng-if="events" class="pull-left pagination-controls">
|
||||
<dir-pagination-controls></dir-pagination-controls>
|
||||
</div>
|
||||
</div>
|
||||
</rd-widget-body>
|
||||
</rd-widget>
|
||||
</div>
|
||||
</div> -->
|
|
@ -0,0 +1,108 @@
|
|||
angular.module('extension.storidge')
|
||||
.controller('StoridgeMonitorController', ['$q', '$scope', '$interval', '$document', 'Notifications', 'StoridgeClusterService', 'StoridgeChartService', 'StoridgeManager', 'ModalService',
|
||||
function ($q, $scope, $interval, $document, Notifications, StoridgeClusterService, StoridgeChartService, StoridgeManager, ModalService) {
|
||||
|
||||
$scope.$on('$destroy', function() {
|
||||
stopRepeater();
|
||||
});
|
||||
|
||||
function stopRepeater() {
|
||||
var repeater = $scope.repeater;
|
||||
if (angular.isDefined(repeater)) {
|
||||
$interval.cancel(repeater);
|
||||
repeater = null;
|
||||
}
|
||||
}
|
||||
|
||||
function updateIOPSChart(info, chart) {
|
||||
var usedIOPS = info.UsedIOPS;
|
||||
var label = moment(new Date()).format('HH:mm:ss');
|
||||
|
||||
StoridgeChartService.UpdateChart(label, usedIOPS, chart);
|
||||
}
|
||||
|
||||
function updateBandwithChart(info, chart) {
|
||||
var usedBandwidth = info.UsedBandwidth;
|
||||
var label = moment(new Date()).format('HH:mm:ss');
|
||||
|
||||
StoridgeChartService.UpdateChart(label, usedBandwidth, chart);
|
||||
}
|
||||
|
||||
function updateCapacityChart(info, chart) {
|
||||
var usedCapacity = info.UsedCapacity;
|
||||
var freeCapacity = info.FreeCapacity;
|
||||
|
||||
StoridgeChartService.UpdatePieChart('Free', freeCapacity, chart);
|
||||
StoridgeChartService.UpdatePieChart('Used', usedCapacity, chart);
|
||||
}
|
||||
|
||||
function setUpdateRepeater(iopsChart, bandwidthChart, capacityChart) {
|
||||
var refreshRate = 5000;
|
||||
$scope.repeater = $interval(function() {
|
||||
$q.all({
|
||||
events: StoridgeClusterService.events(),
|
||||
info: StoridgeClusterService.info()
|
||||
})
|
||||
.then(function success(data) {
|
||||
$scope.events = data.events;
|
||||
var info = data.info;
|
||||
$scope.info = info;
|
||||
updateIOPSChart(info, iopsChart);
|
||||
updateBandwithChart(info, bandwidthChart);
|
||||
updateCapacityChart(info, capacityChart);
|
||||
})
|
||||
.catch(function error(err) {
|
||||
stopRepeater();
|
||||
Notifications.error('Failure', err, 'Unable to retrieve cluster information');
|
||||
});
|
||||
}, refreshRate);
|
||||
}
|
||||
|
||||
function startViewUpdate(iopsChart, bandwidthChart, capacityChart) {
|
||||
$q.all({
|
||||
events: StoridgeClusterService.events(),
|
||||
info: StoridgeClusterService.info()
|
||||
})
|
||||
.then(function success(data) {
|
||||
$scope.events = data.events;
|
||||
var info = data.info;
|
||||
$scope.info = info;
|
||||
updateIOPSChart(info, iopsChart);
|
||||
updateBandwithChart(info, bandwidthChart);
|
||||
updateCapacityChart(info, capacityChart);
|
||||
setUpdateRepeater(iopsChart, bandwidthChart, capacityChart);
|
||||
})
|
||||
.catch(function error(err) {
|
||||
stopRepeater();
|
||||
Notifications.error('Failure', err, 'Unable to retrieve cluster information');
|
||||
});
|
||||
}
|
||||
|
||||
function initCharts() {
|
||||
var iopsChartCtx = $('#iopsChart');
|
||||
var iopsChart = StoridgeChartService.CreateIOPSChart(iopsChartCtx);
|
||||
|
||||
var bandwidthChartCtx = $('#bandwithChart');
|
||||
var bandwidthChart = StoridgeChartService.CreateBandwidthChart(bandwidthChartCtx);
|
||||
|
||||
var capacityChartCtx = $('#capacityChart');
|
||||
var capacityChart = StoridgeChartService.CreateCapacityChart(capacityChartCtx);
|
||||
|
||||
startViewUpdate(iopsChart, bandwidthChart, capacityChart);
|
||||
}
|
||||
|
||||
function initView() {
|
||||
|
||||
$document.ready(function() {
|
||||
initCharts();
|
||||
});
|
||||
}
|
||||
|
||||
StoridgeManager.init()
|
||||
.then(function success() {
|
||||
initView();
|
||||
})
|
||||
.catch(function error(err) {
|
||||
Notifications.error('Failure', err, 'Unable to communicate with Storidge API');
|
||||
});
|
||||
}]);
|
|
@ -0,0 +1,200 @@
|
|||
<rd-header>
|
||||
<rd-header-title title="Create profile"></rd-header-title>
|
||||
<rd-header-content>
|
||||
<a ui-sref="storidge.cluster">Storidge</a> > <a ui-sref="storidge.profiles">Profiles</a> > Add profile
|
||||
</rd-header-content>
|
||||
</rd-header>
|
||||
|
||||
<div class="row">
|
||||
<div class="col-md-12">
|
||||
<rd-widget>
|
||||
<rd-widget-body>
|
||||
<form class="form-horizontal" name="storidgeCreateProfileForm">
|
||||
<!-- name-input -->
|
||||
<div class="form-group" ng-class="{ 'has-error': storidgeCreateProfileForm.profile_name.$invalid }">
|
||||
<label for="profile_name" class="col-sm-2 col-lg-1 control-label text-left">Name</label>
|
||||
<div class="col-sm-10 col-lg-11">
|
||||
<input type="text" class="form-control" ng-model="model.Name" name="profile_name" placeholder="e.g. myProfile" ng-change="updatedName()" required>
|
||||
</div>
|
||||
</div>
|
||||
<div class="form-group" ng-show="storidgeCreateProfileForm.profile_name.$invalid">
|
||||
<div class="col-sm-12 small text-danger">
|
||||
<div ng-messages="storidgeCreateProfileForm.profile_name.$error">
|
||||
<p ng-message="required"><i class="fa fa-exclamation-triangle" aria-hidden="true"></i> This field is required.</p>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
<!-- !name-input -->
|
||||
<div class="col-sm-12 form-section-title">
|
||||
Profile configuration
|
||||
</div>
|
||||
<!-- directory -->
|
||||
<div class="form-group" ng-class="{ 'has-error': storidgeCreateProfileForm.profile_directory.$invalid }">
|
||||
<label for="profile_directory" class="col-sm-2 col-lg-1 control-label text-left">Directory</label>
|
||||
<div class="col-sm-10 col-lg-11">
|
||||
<input type="text" class="form-control" ng-model="model.Directory" name="profile_directory" placeholder="e.g. /cio/myProfile" ng-change="updatedDirectory()" required>
|
||||
</div>
|
||||
</div>
|
||||
<div class="form-group" ng-show="storidgeCreateProfileForm.profile_directory.$invalid">
|
||||
<div class="col-sm-12 small text-danger">
|
||||
<div ng-messages="storidgeCreateProfileForm.profile_directory.$error">
|
||||
<p ng-message="required"><i class="fa fa-exclamation-triangle" aria-hidden="true"></i> This field is required.</p>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
<!-- !directory -->
|
||||
<!-- capacity -->
|
||||
<div class="form-group" ng-class="{ 'has-error': storidgeCreateProfileForm.profile_capacity.$invalid }">
|
||||
<label for="profile_capacity" class="col-sm-2 col-lg-1 control-label text-left">Capacity</label>
|
||||
<div class="col-sm-10 col-lg-11">
|
||||
<input type="number" class="form-control" ng-model="model.Capacity" name="profile_capacity" ng-min="1" ng-max="64000" placeholder="2" required>
|
||||
</div>
|
||||
</div>
|
||||
<div class="form-group" ng-show="storidgeCreateProfileForm.profile_capacity.$invalid">
|
||||
<div class="col-sm-12 small text-danger">
|
||||
<div ng-messages="storidgeCreateProfileForm.profile_capacity.$error">
|
||||
<p ng-message="required"><i class="fa fa-exclamation-triangle" aria-hidden="true"></i> This field is required.</p>
|
||||
<p ng-message="min"><i class="fa fa-exclamation-triangle" aria-hidden="true"></i> Minimum value for capacity: 1.</p>
|
||||
<p ng-message="max"><i class="fa fa-exclamation-triangle" aria-hidden="true"></i> Maximum value for capacity: 64000.</p>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
<!-- !capacity -->
|
||||
<!-- redundancy -->
|
||||
<div class="form-group">
|
||||
<label for="profile_redundancy" class="col-sm-2 col-lg-1 control-label text-left">Redundancy</label>
|
||||
<div class="col-sm-10 col-lg-11">
|
||||
<select name="profile_redundancy" ng-model="model.Redundancy" ng-options="+(opt.value) as opt.label for opt in RedundancyOptions" class="form-control">
|
||||
</select>
|
||||
</div>
|
||||
</div>
|
||||
<!-- !redudancy -->
|
||||
<!-- provisioning -->
|
||||
<div class="form-group">
|
||||
<label for="profile_provisioning" class="col-sm-2 col-lg-1 control-label text-left">Provisioning</label>
|
||||
<div class="col-sm-10 col-lg-11">
|
||||
<select name="profile_provisioning" ng-model="model.Provisioning" class="form-control">
|
||||
<option value="thin">Thin</option>
|
||||
<option value="thick">Thick</option>
|
||||
</select>
|
||||
</div>
|
||||
</div>
|
||||
<!-- !provisioning -->
|
||||
<!-- type -->
|
||||
<div class="form-group">
|
||||
<label for="profile_type" class="col-sm-2 col-lg-1 control-label text-left">Type</label>
|
||||
<div class="col-sm-10 col-lg-11">
|
||||
<select name="profile_type" ng-model="model.Type" class="form-control">
|
||||
<option value="ssd">SSD</option>
|
||||
<option value="hdd">HDD</option>
|
||||
</select>
|
||||
</div>
|
||||
</div>
|
||||
<!-- !type -->
|
||||
<!-- iops -->
|
||||
<div ng-if="!state.LimitBandwidth || state.NoLimit">
|
||||
<div class="col-sm-12 form-section-title">
|
||||
IOPS
|
||||
</div>
|
||||
<div class="form-group">
|
||||
<div class="col-sm-12">
|
||||
<label for="permissions" class="control-label text-left">
|
||||
Limit IOPS
|
||||
</label>
|
||||
<label class="switch" style="margin-left: 20px;">
|
||||
<input type="checkbox" ng-model="state.LimitIOPS" ng-change="state.NoLimit = (!state.LimitBandwidth && !state.LimitIOPS)"><i></i>
|
||||
</label>
|
||||
</div>
|
||||
</div>
|
||||
<div class="form-group" ng-if="state.LimitIOPS">
|
||||
<label for="min_iops" class="col-sm-1 control-label text-left">Min</label>
|
||||
<div class="col-sm-5" ng-class="{ 'has-error': storidgeCreateProfileForm.min_iops.$invalid }">
|
||||
<input type="number" class="form-control" ng-model="model.MinIOPS" name="min_iops" ng-min="30" ng-max="999999" placeholder="100" required>
|
||||
</div>
|
||||
<label for="max_iops" class="col-sm-1 control-label text-left">Max</label>
|
||||
<div class="col-sm-5" ng-class="{ 'has-error': storidgeCreateProfileForm.max_iops.$invalid }">
|
||||
<input type="number" class="form-control" ng-model="model.MaxIOPS" name="max_iops" ng-min="30" ng-max="999999" placeholder="2000" required>
|
||||
</div>
|
||||
</div>
|
||||
<div class="form-group" ng-show="storidgeCreateProfileForm.min_iops.$invalid">
|
||||
<div class="col-sm-12 small text-danger">
|
||||
<div ng-messages="storidgeCreateProfileForm.min_iops.$error">
|
||||
<p ng-message="required"><i class="fa fa-exclamation-triangle" aria-hidden="true"></i> A value is required for Min IOPS.</p>
|
||||
<p ng-message="min"><i class="fa fa-exclamation-triangle" aria-hidden="true"></i> Minimum value for Min IOPS: 30.</p>
|
||||
<p ng-message="max"><i class="fa fa-exclamation-triangle" aria-hidden="true"></i> Maximum value for Min IOPS: 999999.</p>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
<div class="form-group" ng-show="storidgeCreateProfileForm.max_iops.$invalid">
|
||||
<div class="col-sm-12 small text-danger">
|
||||
<div ng-messages="storidgeCreateProfileForm.max_iops.$error">
|
||||
<p ng-message="required"><i class="fa fa-exclamation-triangle" aria-hidden="true"></i> A value is required for Max IOPS.</p>
|
||||
<p ng-message="min"><i class="fa fa-exclamation-triangle" aria-hidden="true"></i> Minimum value for Max IOPS: 30.</p>
|
||||
<p ng-message="max"><i class="fa fa-exclamation-triangle" aria-hidden="true"></i> Maximum value for Max IOPS: 999999.</p>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
<!-- !iops -->
|
||||
<!-- bandwidth -->
|
||||
<div ng-if="!state.LimitIOPS || state.NoLimit">
|
||||
<div class="col-sm-12 form-section-title">
|
||||
Bandwidth
|
||||
</div>
|
||||
<div class="form-group">
|
||||
<div class="col-sm-12">
|
||||
<label for="permissions" class="control-label text-left">
|
||||
Limit bandwidth
|
||||
</label>
|
||||
<label class="switch" style="margin-left: 20px;">
|
||||
<input type="checkbox" ng-model="state.LimitBandwidth" ng-change="state.NoLimit = (!state.LimitBandwidth && !state.LimitIOPS)"><i></i>
|
||||
</label>
|
||||
</div>
|
||||
</div>
|
||||
<div class="form-group" ng-if="state.LimitBandwidth">
|
||||
<label for="min_bandwidth" class="col-sm-1 control-label text-left">Min</label>
|
||||
<div class="col-sm-5" ng-class="{ 'has-error': storidgeCreateProfileForm.min_bandwidth.$invalid }">
|
||||
<input type="number" class="form-control" ng-model="model.MinBandwidth" name="min_bandwidth" ng-min="1" ng-max="5000" placeholder="1" required>
|
||||
</div>
|
||||
<label for="max_bandwidth" class="col-sm-1 control-label text-left">Max</label>
|
||||
<div class="col-sm-5" ng-class="{ 'has-error': storidgeCreateProfileForm.max_bandwidth.$invalid }">
|
||||
<input type="number" class="form-control" ng-model="model.MaxBandwidth" name="max_bandwidth" ng-min="1" ng-max="5000" placeholder="100" required>
|
||||
</div>
|
||||
</div>
|
||||
<div class="form-group" ng-show="storidgeCreateProfileForm.min_bandwidth.$invalid">
|
||||
<div class="col-sm-12 small text-danger">
|
||||
<div ng-messages="storidgeCreateProfileForm.min_bandwidth.$error">
|
||||
<p ng-message="required"><i class="fa fa-exclamation-triangle" aria-hidden="true"></i> A value is required for Min bandwidth.</p>
|
||||
<p ng-message="min"><i class="fa fa-exclamation-triangle" aria-hidden="true"></i> Minimum value for Min bandwidth: 1.</p>
|
||||
<p ng-message="max"><i class="fa fa-exclamation-triangle" aria-hidden="true"></i> Maximum value for Min bandwidth: 5000.</p>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
<div class="form-group" ng-show="storidgeCreateProfileForm.max_bandwidth.$invalid">
|
||||
<div class="col-sm-12 small text-danger">
|
||||
<div ng-messages="storidgeCreateProfileForm.max_bandwidth.$error">
|
||||
<p ng-message="required"><i class="fa fa-exclamation-triangle" aria-hidden="true"></i> A value is required for Max bandwidth.</p>
|
||||
<p ng-message="min"><i class="fa fa-exclamation-triangle" aria-hidden="true"></i> Minimum value for Max bandwidth: 1.</p>
|
||||
<p ng-message="max"><i class="fa fa-exclamation-triangle" aria-hidden="true"></i> Maximum value for Max bandwidth: 5000.</p>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
<!-- !bandwidth -->
|
||||
<div class="col-sm-12 form-section-title">
|
||||
Actions
|
||||
</div>
|
||||
<div class="form-group">
|
||||
<div class="col-sm-12">
|
||||
<button type="button" class="btn btn-primary btn-sm" ng-click="create()" ng-disabled="state.actionInProgress || !storidgeCreateProfileForm.$valid" button-spinner="state.actionInProgress">
|
||||
<span ng-hide="state.actionInProgress">Create the profile</span>
|
||||
<span ng-show="state.actionInProgress">Creating profile...</span>
|
||||
</button>
|
||||
</div>
|
||||
</div>
|
||||
<!-- !actions -->
|
||||
</form>
|
||||
</rd-widget-body>
|
||||
</rd-widget>
|
||||
</div>
|
||||
</div>
|
|
@ -0,0 +1,72 @@
|
|||
angular.module('extension.storidge')
|
||||
.controller('CreateProfileController', ['$scope', '$state', '$transition$', 'Notifications', 'StoridgeProfileService', 'StoridgeManager',
|
||||
function ($scope, $state, $transition$, Notifications, StoridgeProfileService, StoridgeManager) {
|
||||
|
||||
$scope.state = {
|
||||
NoLimit: true,
|
||||
LimitIOPS: false,
|
||||
LimitBandwidth: false,
|
||||
ManualInputDirectory: false,
|
||||
actionInProgress: false
|
||||
};
|
||||
|
||||
$scope.RedundancyOptions = [
|
||||
{ value: 2, label: '2-copy' },
|
||||
{ value: 3, label: '3-copy' }
|
||||
];
|
||||
|
||||
$scope.create = function () {
|
||||
var profile = $scope.model;
|
||||
|
||||
if (!$scope.state.LimitIOPS) {
|
||||
delete profile.MinIOPS;
|
||||
delete profile.MaxIOPS;
|
||||
}
|
||||
|
||||
if (!$scope.state.LimitBandwidth) {
|
||||
delete profile.MinBandwidth;
|
||||
delete profile.MaxBandwidth;
|
||||
}
|
||||
|
||||
$scope.state.actionInProgress = true;
|
||||
StoridgeProfileService.create(profile)
|
||||
.then(function success(data) {
|
||||
Notifications.success('Profile successfully created');
|
||||
$state.go('storidge.profiles');
|
||||
})
|
||||
.catch(function error(err) {
|
||||
Notifications.error('Failure', err, 'Unable to create profile');
|
||||
})
|
||||
.finally(function final() {
|
||||
$scope.state.actionInProgress = false;
|
||||
});
|
||||
};
|
||||
|
||||
$scope.updatedName = function() {
|
||||
if (!$scope.state.ManualInputDirectory) {
|
||||
var profile = $scope.model;
|
||||
profile.Directory = '/cio/' + profile.Name;
|
||||
}
|
||||
};
|
||||
|
||||
$scope.updatedDirectory = function() {
|
||||
if (!$scope.state.ManualInputDirectory) {
|
||||
$scope.state.ManualInputDirectory = true;
|
||||
}
|
||||
};
|
||||
|
||||
function initView() {
|
||||
var profile = new StoridgeProfileDefaultModel();
|
||||
profile.Name = $transition$.params().profileName;
|
||||
profile.Directory = '/cio/' + profile.Name;
|
||||
$scope.model = profile;
|
||||
}
|
||||
|
||||
StoridgeManager.init()
|
||||
.then(function success() {
|
||||
initView();
|
||||
})
|
||||
.catch(function error(err) {
|
||||
Notifications.error('Failure', err, 'Unable to communicate with Storidge API');
|
||||
});
|
||||
}]);
|
|
@ -0,0 +1,198 @@
|
|||
<rd-header>
|
||||
<rd-header-title title="Profile details"></rd-header-title>
|
||||
<rd-header-content>
|
||||
<a ui-sref="storidge.cluster">Storidge</a> > <a ui-sref="storidge.profiles">Profiles</a> > {{ profile.Name }}
|
||||
</rd-header-content>
|
||||
</rd-header>
|
||||
|
||||
<div class="row" ng-if="profile">
|
||||
<div class="col-md-12">
|
||||
<rd-widget>
|
||||
<rd-widget-body>
|
||||
<form class="form-horizontal" name="storidgeUpdateProfileForm">
|
||||
<!-- name-input -->
|
||||
<div class="form-group">
|
||||
<label for="profile_name" class="col-sm-2 col-lg-1 control-label text-left">Name</label>
|
||||
<div class="col-sm-10 col-lg-11">
|
||||
<input type="text" class="form-control" ng-model="profile.Name" name="profile_name" disabled>
|
||||
</div>
|
||||
</div>
|
||||
<!-- !name-input -->
|
||||
<div class="col-sm-12 form-section-title">
|
||||
Profile configuration
|
||||
</div>
|
||||
<!-- directory -->
|
||||
<div class="form-group" ng-class="{ 'has-error': storidgeUpdateProfileForm.profile_directory.$invalid }">
|
||||
<label for="profile_directory" class="col-sm-2 col-lg-1 control-label text-left">Directory</label>
|
||||
<div class="col-sm-10 col-lg-11">
|
||||
<input type="text" class="form-control" ng-model="profile.Directory" name="profile_directory" placeholder="e.g. /cio/myProfile" required>
|
||||
</div>
|
||||
</div>
|
||||
<div class="form-group" ng-show="storidgeUpdateProfileForm.profile_directory.$invalid">
|
||||
<div class="col-sm-12 small text-danger">
|
||||
<div ng-messages="storidgeUpdateProfileForm.profile_directory.$error">
|
||||
<p ng-message="required"><i class="fa fa-exclamation-triangle" aria-hidden="true"></i> This field is required.</p>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
<!-- !directory -->
|
||||
<!-- capacity -->
|
||||
<div class="form-group" ng-class="{ 'has-error': storidgeUpdateProfileForm.profile_capacity.$invalid }">
|
||||
<label for="profile_capacity" class="col-sm-2 col-lg-1 control-label text-left">Capacity</label>
|
||||
<div class="col-sm-10 col-lg-11">
|
||||
<input type="number" class="form-control" ng-model="profile.Capacity" name="profile_capacity" ng-min="1" ng-max="64000" placeholder="2" required>
|
||||
</div>
|
||||
</div>
|
||||
<div class="form-group" ng-show="storidgeUpdateProfileForm.profile_capacity.$invalid">
|
||||
<div class="col-sm-12 small text-danger">
|
||||
<div ng-messages="storidgeUpdateProfileForm.profile_capacity.$error">
|
||||
<p ng-message="required"><i class="fa fa-exclamation-triangle" aria-hidden="true"></i> This field is required.</p>
|
||||
<p ng-message="min"><i class="fa fa-exclamation-triangle" aria-hidden="true"></i> Minimum value for capacity: 1.</p>
|
||||
<p ng-message="max"><i class="fa fa-exclamation-triangle" aria-hidden="true"></i> Maximum value for capacity: 64000.</p>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
<!-- !capacity -->
|
||||
<!-- redundancy -->
|
||||
<div class="form-group">
|
||||
<label for="profile_redundancy" class="col-sm-2 col-lg-1 control-label text-left">Redundancy</label>
|
||||
<div class="col-sm-10 col-lg-11">
|
||||
<select name="profile_redundancy" ng-model="profile.Redundancy" ng-options="+(opt.value) as opt.label for opt in RedundancyOptions" class="form-control">
|
||||
</select>
|
||||
</div>
|
||||
</div>
|
||||
<!-- !redudancy -->
|
||||
<!-- provisioning -->
|
||||
<div class="form-group">
|
||||
<label for="profile_provisioning" class="col-sm-2 col-lg-1 control-label text-left">Provisioning</label>
|
||||
<div class="col-sm-10 col-lg-11">
|
||||
<select name="profile_provisioning" ng-model="profile.Provisioning" class="form-control">
|
||||
<option value="thin">Thin</option>
|
||||
<option value="thick">Thick</option>
|
||||
</select>
|
||||
</div>
|
||||
</div>
|
||||
<!-- !provisioning -->
|
||||
<!-- type -->
|
||||
<div class="form-group">
|
||||
<label for="profile_type" class="col-sm-2 col-lg-1 control-label text-left">Type</label>
|
||||
<div class="col-sm-10 col-lg-11">
|
||||
<select name="profile_type" ng-model="profile.Type" class="form-control">
|
||||
<option value="ssd">SSD</option>
|
||||
<option value="hdd">HDD</option>
|
||||
</select>
|
||||
</div>
|
||||
</div>
|
||||
<!-- !type -->
|
||||
<!-- iops -->
|
||||
<div ng-if="!state.LimitBandwidth || state.NoLimit">
|
||||
<div class="col-sm-12 form-section-title">
|
||||
IOPS
|
||||
</div>
|
||||
<div class="form-group">
|
||||
<div class="col-sm-12">
|
||||
<label for="permissions" class="control-label text-left">
|
||||
Limit IOPS
|
||||
</label>
|
||||
<label class="switch" style="margin-left: 20px;">
|
||||
<input type="checkbox" ng-model="state.LimitIOPS" ng-change="state.NoLimit = (!state.LimitBandwidth && !state.LimitIOPS)"><i></i>
|
||||
</label>
|
||||
</div>
|
||||
</div>
|
||||
<div class="form-group" ng-if="state.LimitIOPS">
|
||||
<label for="min_iops" class="col-sm-1 control-label text-left">Min</label>
|
||||
<div class="col-sm-5" ng-class="{ 'has-error': storidgeUpdateProfileForm.min_iops.$invalid }">
|
||||
<input type="number" class="form-control" ng-model="profile.MinIOPS" name="min_iops" ng-min="30" ng-max="999999" placeholder="100" required>
|
||||
</div>
|
||||
<label for="max_iops" class="col-sm-1 control-label text-left">Max</label>
|
||||
<div class="col-sm-5" ng-class="{ 'has-error': storidgeUpdateProfileForm.max_iops.$invalid }">
|
||||
<input type="number" class="form-control" ng-model="profile.MaxIOPS" name="max_iops" ng-min="30" ng-max="999999" placeholder="2000" required>
|
||||
</div>
|
||||
</div>
|
||||
<div class="form-group" ng-show="storidgeUpdateProfileForm.min_iops.$invalid">
|
||||
<div class="col-sm-12 small text-danger">
|
||||
<div ng-messages="storidgeUpdateProfileForm.min_iops.$error">
|
||||
<p ng-message="required"><i class="fa fa-exclamation-triangle" aria-hidden="true"></i> A value is required for Min IOPS.</p>
|
||||
<p ng-message="min"><i class="fa fa-exclamation-triangle" aria-hidden="true"></i> Minimum value for Min IOPS: 30.</p>
|
||||
<p ng-message="max"><i class="fa fa-exclamation-triangle" aria-hidden="true"></i> Maximum value for Min IOPS: 999999.</p>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
<div class="form-group" ng-show="storidgeUpdateProfileForm.max_iops.$invalid">
|
||||
<div class="col-sm-12 small text-danger">
|
||||
<div ng-messages="storidgeUpdateProfileForm.max_iops.$error">
|
||||
<p ng-message="required"><i class="fa fa-exclamation-triangle" aria-hidden="true"></i> A value is required for Max IOPS.</p>
|
||||
<p ng-message="min"><i class="fa fa-exclamation-triangle" aria-hidden="true"></i> Minimum value for Max IOPS: 30.</p>
|
||||
<p ng-message="max"><i class="fa fa-exclamation-triangle" aria-hidden="true"></i> Maximum value for Max IOPS: 999999.</p>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
<!-- !iops -->
|
||||
<!-- bandwidth -->
|
||||
<div ng-if="!state.LimitIOPS || state.NoLimit">
|
||||
<div class="col-sm-12 form-section-title">
|
||||
Bandwidth
|
||||
</div>
|
||||
<div class="form-group">
|
||||
<div class="col-sm-12">
|
||||
<label for="permissions" class="control-label text-left">
|
||||
Limit bandwidth
|
||||
</label>
|
||||
<label class="switch" style="margin-left: 20px;">
|
||||
<input type="checkbox" ng-model="state.LimitBandwidth" ng-change="state.NoLimit = (!state.LimitBandwidth && !state.LimitIOPS)"><i></i>
|
||||
</label>
|
||||
</div>
|
||||
</div>
|
||||
<div class="form-group" ng-if="state.LimitBandwidth">
|
||||
<label for="min_bandwidth" class="col-sm-1 control-label text-left">Min</label>
|
||||
<div class="col-sm-5" ng-class="{ 'has-error': storidgeUpdateProfileForm.min_bandwidth.$invalid }">
|
||||
<input type="number" class="form-control" ng-model="profile.MinBandwidth" name="min_bandwidth" ng-min="1" ng-max="5000" placeholder="1" required>
|
||||
</div>
|
||||
<label for="max_bandwidth" class="col-sm-1 control-label text-left">Max</label>
|
||||
<div class="col-sm-5" ng-class="{ 'has-error': storidgeUpdateProfileForm.max_bandwidth.$invalid }">
|
||||
<input type="number" class="form-control" ng-model="profile.MaxBandwidth" name="max_bandwidth" ng-min="1" ng-max="5000" placeholder="100" required>
|
||||
</div>
|
||||
</div>
|
||||
<div class="form-group" ng-show="storidgeUpdateProfileForm.min_bandwidth.$invalid">
|
||||
<div class="col-sm-12 small text-danger">
|
||||
<div ng-messages="storidgeUpdateProfileForm.min_bandwidth.$error">
|
||||
<p ng-message="required"><i class="fa fa-exclamation-triangle" aria-hidden="true"></i> A value is required for Min bandwidth.</p>
|
||||
<p ng-message="min"><i class="fa fa-exclamation-triangle" aria-hidden="true"></i> Minimum value for Min bandwidth: 1.</p>
|
||||
<p ng-message="max"><i class="fa fa-exclamation-triangle" aria-hidden="true"></i> Maximum value for Min bandwidth: 5000.</p>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
<div class="form-group" ng-show="storidgeUpdateProfileForm.max_bandwidth.$invalid">
|
||||
<div class="col-sm-12 small text-danger">
|
||||
<div ng-messages="storidgeUpdateProfileForm.max_bandwidth.$error">
|
||||
<p ng-message="required"><i class="fa fa-exclamation-triangle" aria-hidden="true"></i> A value is required for Max bandwidth.</p>
|
||||
<p ng-message="min"><i class="fa fa-exclamation-triangle" aria-hidden="true"></i> Minimum value for Max bandwidth: 1.</p>
|
||||
<p ng-message="max"><i class="fa fa-exclamation-triangle" aria-hidden="true"></i> Maximum value for Max bandwidth: 5000.</p>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
<!-- !bandwidth -->
|
||||
<!-- actions -->
|
||||
<div class="col-sm-12 form-section-title">
|
||||
Actions
|
||||
</div>
|
||||
<div class="form-group">
|
||||
<div class="col-sm-12">
|
||||
<button type="button" class="btn btn-primary btn-sm" ng-click="update()" ng-disabled="state.updateInProgress || !storidgeUpdateProfileForm.$valid" button-spinner="state.updateInProgress">
|
||||
<span ng-hide="state.updateInProgress">Update the profile</span>
|
||||
<span ng-show="state.updateInProgress">Updating profile...</span>
|
||||
</button>
|
||||
<button type="button" class="btn btn-danger btn-sm" ng-click="delete()" ng-disabled="state.deleteInProgress" button-spinner="state.deleteInProgress">
|
||||
<span ng-hide="state.deleteInProgress">Delete the profile</span>
|
||||
<span ng-show="state.deleteInProgress">Deleting profile...</span>
|
||||
</button>
|
||||
</div>
|
||||
</div>
|
||||
<!-- !actions -->
|
||||
</form>
|
||||
</rd-widget-body>
|
||||
</rd-widget>
|
||||
</div>
|
||||
</div>
|
|
@ -0,0 +1,98 @@
|
|||
angular.module('extension.storidge')
|
||||
.controller('EditProfileController', ['$scope', '$state', '$transition$', 'Notifications', 'StoridgeProfileService', 'StoridgeManager', 'ModalService',
|
||||
function ($scope, $state, $transition$, Notifications, StoridgeProfileService, StoridgeManager, ModalService) {
|
||||
|
||||
$scope.state = {
|
||||
NoLimit: false,
|
||||
LimitIOPS: false,
|
||||
LimitBandwidth: false,
|
||||
updateInProgress: false,
|
||||
deleteInProgress: false
|
||||
};
|
||||
|
||||
$scope.RedundancyOptions = [
|
||||
{ value: 2, label: '2-copy' },
|
||||
{ value: 3, label: '3-copy' }
|
||||
];
|
||||
|
||||
$scope.update = function() {
|
||||
|
||||
var profile = $scope.profile;
|
||||
|
||||
if (!$scope.state.LimitIOPS) {
|
||||
delete profile.MinIOPS;
|
||||
delete profile.MaxIOPS;
|
||||
}
|
||||
|
||||
if (!$scope.state.LimitBandwidth) {
|
||||
delete profile.MinBandwidth;
|
||||
delete profile.MaxBandwidth;
|
||||
}
|
||||
|
||||
$scope.state.updateInProgress = true;
|
||||
StoridgeProfileService.update(profile)
|
||||
.then(function success(data) {
|
||||
Notifications.success('Profile successfully updated');
|
||||
$state.go('storidge.profiles');
|
||||
})
|
||||
.catch(function error(err) {
|
||||
Notifications.error('Failure', err, 'Unable to update profile');
|
||||
})
|
||||
.finally(function final() {
|
||||
$scope.state.updateInProgress = false;
|
||||
});
|
||||
};
|
||||
|
||||
$scope.delete = function() {
|
||||
ModalService.confirmDeletion(
|
||||
'Do you want to remove this profile?',
|
||||
function onConfirm(confirmed) {
|
||||
if(!confirmed) { return; }
|
||||
deleteProfile();
|
||||
}
|
||||
);
|
||||
};
|
||||
|
||||
function deleteProfile() {
|
||||
var profile = $scope.profile;
|
||||
|
||||
$scope.state.deleteInProgress = true;
|
||||
StoridgeProfileService.delete(profile.Name)
|
||||
.then(function success(data) {
|
||||
Notifications.success('Profile successfully deleted');
|
||||
$state.go('storidge.profiles');
|
||||
})
|
||||
.catch(function error(err) {
|
||||
Notifications.error('Failure', err, 'Unable to delete profile');
|
||||
})
|
||||
.finally(function final() {
|
||||
$scope.state.deleteInProgress = false;
|
||||
});
|
||||
}
|
||||
|
||||
function initView() {
|
||||
StoridgeProfileService.profile($transition$.params().id)
|
||||
.then(function success(data) {
|
||||
var profile = data;
|
||||
if ((profile.MinIOPS && profile.MinIOPS !== 0) || (profile.MaxIOPS && profile.MaxIOPS !== 0)) {
|
||||
$scope.state.LimitIOPS = true;
|
||||
} else if ((profile.MinBandwidth && profile.MinBandwidth !== 0) || (profile.MaxBandwidth && profile.MaxBandwidth !== 0)) {
|
||||
$scope.state.LimitBandwidth = true;
|
||||
} else {
|
||||
$scope.state.NoLimit = true;
|
||||
}
|
||||
$scope.profile = profile;
|
||||
})
|
||||
.catch(function error(err) {
|
||||
Notifications.error('Failure', err, 'Unable to retrieve profile details');
|
||||
});
|
||||
}
|
||||
|
||||
StoridgeManager.init()
|
||||
.then(function success() {
|
||||
initView();
|
||||
})
|
||||
.catch(function error(err) {
|
||||
Notifications.error('Failure', err, 'Unable to communicate with Storidge API');
|
||||
});
|
||||
}]);
|
|
@ -0,0 +1,123 @@
|
|||
<rd-header>
|
||||
<rd-header-title title="Storidge profiles">
|
||||
<a data-toggle="tooltip" title="Refresh" ui-sref="storidge.profiles" ui-sref-opts="{reload: true}">
|
||||
<i class="fa fa-refresh" aria-hidden="true"></i>
|
||||
</a>
|
||||
</rd-header-title>
|
||||
<rd-header-content>
|
||||
<a ui-sref="storidge.cluster">Storidge</a> > <a ui-sref="storidge.profiles">Profiles</a>
|
||||
</rd-header-content>
|
||||
</rd-header>
|
||||
|
||||
<div class="row">
|
||||
<div class="col-md-12">
|
||||
<rd-widget>
|
||||
<rd-widget-header icon="fa-plus" title="Add a profile">
|
||||
</rd-widget-header>
|
||||
<rd-widget-body>
|
||||
<form class="form-horizontal">
|
||||
<!-- name-input -->
|
||||
<div class="form-group">
|
||||
<label for="profile_name" class="col-sm-1 control-label text-left">Name</label>
|
||||
<div class="col-sm-11">
|
||||
<input type="text" class="form-control" ng-model="formValues.Name" id="profile_name" placeholder="e.g. myProfile">
|
||||
</div>
|
||||
</div>
|
||||
<!-- !name-input -->
|
||||
<!-- tag-note -->
|
||||
<div class="form-group">
|
||||
<div class="col-sm-12">
|
||||
<span class="small text-muted">Note: The profile will be created using the default properties.</span>
|
||||
</div>
|
||||
</div>
|
||||
<!-- !tag-note -->
|
||||
<div class="form-group">
|
||||
<div class="col-sm-12">
|
||||
<button type="button" class="btn btn-primary btn-sm" ng-click="create()" ng-disabled="state.actionInProgress || !formValues.Name" button-spinner="state.actionInProgress">
|
||||
<span ng-hide="state.actionInProgress">Create the profile</span>
|
||||
<span ng-show="state.actionInProgress">Creating profile...</span>
|
||||
</button>
|
||||
<button type="button" class="btn btn-primary btn-sm" ng-disabled="!formValues.Name" ui-sref="storidge.profiles.create({ profileName: formValues.Name })">Modify defaults...</button>
|
||||
</div>
|
||||
</div>
|
||||
</form>
|
||||
</rd-widget-body>
|
||||
</rd-widget>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div class="row">
|
||||
<div class="col-sm-12">
|
||||
<storidge-profiles-datatable
|
||||
title="Profiles" title-icon="fa-sticky-note-o"
|
||||
dataset="profiles" table-key="storidge_profiles"
|
||||
order-by="Name" show-text-filter="true"
|
||||
remove-action="removeAction"
|
||||
></storidge-profiles-datatable>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
|
||||
<!-- <div class="row">
|
||||
<div class="col-md-12">
|
||||
<rd-widget>
|
||||
<rd-widget-header icon="fa-sticky-note-o" title="Profiles">
|
||||
<div class="pull-right">
|
||||
Items per page:
|
||||
<select ng-model="state.pagination_count" ng-change="changePaginationCount()">
|
||||
<option value="0">All</option>
|
||||
<option value="10">10</option>
|
||||
<option value="25">25</option>
|
||||
<option value="50">50</option>
|
||||
<option value="100">100</option>
|
||||
</select>
|
||||
</div>
|
||||
</rd-widget-header>
|
||||
<rd-widget-taskbar classes="col-md-12">
|
||||
<div class="pull-left">
|
||||
<button type="button" class="btn btn-danger" ng-click="removeProfiles()" ng-disabled="!state.selectedItemCount"><i class="fa fa-trash space-right" aria-hidden="true"></i>Remove</button>
|
||||
</div>
|
||||
<div class="pull-right">
|
||||
<input type="text" id="filter" ng-model="state.filter" placeholder="Filter..." class="form-control input-sm" />
|
||||
</div>
|
||||
</rd-widget-taskbar>
|
||||
<rd-widget-body classes="no-padding">
|
||||
<div class="table-responsive">
|
||||
<table class="table table-hover">
|
||||
<thead>
|
||||
<tr>
|
||||
<th>
|
||||
<input type="checkbox" ng-model="allSelected" ng-change="selectItems(allSelected)" />
|
||||
</th>
|
||||
<th>
|
||||
<a ng-click="order('Name')">
|
||||
Name
|
||||
<span ng-show="sortType == 'Name' && !sortReverse" class="glyphicon glyphicon-chevron-down"></span>
|
||||
<span ng-show="sortType == 'Name' && sortReverse" class="glyphicon glyphicon-chevron-up"></span>
|
||||
</a>
|
||||
</th>
|
||||
</tr>
|
||||
</thead>
|
||||
<tbody>
|
||||
<tr dir-paginate="profile in (state.filteredProfiles = (profiles | filter:state.filter | orderBy:sortType:sortReverse | itemsPerPage: state.pagination_count))">
|
||||
<td><input type="checkbox" ng-model="profile.Checked" ng-change="selectItem(profile)" /></td>
|
||||
<td>
|
||||
<a ui-sref="storidge.profiles.edit({id: profile.Name})">{{ profile.Name }}</a>
|
||||
</td>
|
||||
</tr>
|
||||
<tr ng-if="!profiles">
|
||||
<td colspan="3" class="text-center text-muted">Loading...</td>
|
||||
</tr>
|
||||
<tr ng-if="state.filteredProfiles.length == 0">
|
||||
<td colspan="3" class="text-center text-muted">No profiles available.</td>
|
||||
</tr>
|
||||
</tbody>
|
||||
</table>
|
||||
<div ng-if="profiles" class="pull-left pagination-controls">
|
||||
<dir-pagination-controls></dir-pagination-controls>
|
||||
</div>
|
||||
</div>
|
||||
</rd-widget-body>
|
||||
</rd-widget>
|
||||
</div>
|
||||
</div> -->
|
|
@ -0,0 +1,70 @@
|
|||
angular.module('extension.storidge')
|
||||
.controller('StoridgeProfilesController', ['$q', '$scope', '$state', 'Notifications', 'StoridgeProfileService', 'StoridgeManager',
|
||||
function ($q, $scope, $state, Notifications, StoridgeProfileService, StoridgeManager) {
|
||||
|
||||
$scope.state = {
|
||||
actionInProgress: false
|
||||
};
|
||||
|
||||
$scope.formValues = {
|
||||
Name: ''
|
||||
};
|
||||
|
||||
$scope.removeAction = function(selectedItems) {
|
||||
var actionCount = selectedItems.length;
|
||||
angular.forEach(selectedItems, function (profile) {
|
||||
StoridgeProfileService.delete(profile.Name)
|
||||
.then(function success() {
|
||||
Notifications.success('Profile successfully removed', profile.Name);
|
||||
var index = $scope.profiles.indexOf(profile);
|
||||
$scope.profiles.splice(index, 1);
|
||||
})
|
||||
.catch(function error(err) {
|
||||
Notifications.error('Failure', err, 'Unable to remove profile');
|
||||
})
|
||||
.finally(function final() {
|
||||
--actionCount;
|
||||
if (actionCount === 0) {
|
||||
$state.reload();
|
||||
}
|
||||
});
|
||||
});
|
||||
};
|
||||
|
||||
$scope.create = function() {
|
||||
var model = new StoridgeProfileDefaultModel();
|
||||
model.Name = $scope.formValues.Name;
|
||||
model.Directory = model.Directory + model.Name;
|
||||
|
||||
$scope.state.actionInProgress = true;
|
||||
StoridgeProfileService.create(model)
|
||||
.then(function success(data) {
|
||||
Notifications.success('Profile successfully created');
|
||||
$state.reload();
|
||||
})
|
||||
.catch(function error(err) {
|
||||
Notifications.error('Failure', err, 'Unable to create profile');
|
||||
})
|
||||
.finally(function final() {
|
||||
$scope.state.actionInProgress = false;
|
||||
});
|
||||
};
|
||||
|
||||
function initView() {
|
||||
StoridgeProfileService.profiles()
|
||||
.then(function success(data) {
|
||||
$scope.profiles = data;
|
||||
})
|
||||
.catch(function error(err) {
|
||||
Notifications.error('Failure', err, 'Unable to retrieve profiles');
|
||||
});
|
||||
}
|
||||
|
||||
StoridgeManager.init()
|
||||
.then(function success() {
|
||||
initView();
|
||||
})
|
||||
.catch(function error(err) {
|
||||
Notifications.error('Failure', err, 'Unable to communicate with Storidge API');
|
||||
});
|
||||
}]);
|
|
@ -87,8 +87,8 @@ angular.module('portainer.services')
|
|||
label: 'TX on eth0',
|
||||
data: [],
|
||||
fill: false,
|
||||
backgroundColor: 'rgba(255,180,174,0.5)',
|
||||
borderColor: 'rgba(255,180,174,0.7)',
|
||||
backgroundColor: 'rgba(255,180,174,0.4)',
|
||||
borderColor: 'rgba(255,180,174,0.6)',
|
||||
pointBackgroundColor: 'rgba(255,180,174,1)',
|
||||
pointBorderColor: 'rgba(255,180,174,1)',
|
||||
pointRadius: 2,
|
||||
|
|
|
@ -0,0 +1,36 @@
|
|||
angular.module('portainer.services')
|
||||
.factory('ExtensionManager', ['$q', 'PluginService', 'StoridgeManager', function ExtensionManagerFactory($q, PluginService, StoridgeManager) {
|
||||
'use strict';
|
||||
var service = {};
|
||||
|
||||
service.init = function() {
|
||||
return $q.all(
|
||||
StoridgeManager.init()
|
||||
);
|
||||
};
|
||||
|
||||
service.reset = function() {
|
||||
StoridgeManager.reset();
|
||||
};
|
||||
|
||||
service.extensions = function() {
|
||||
var deferred = $q.defer();
|
||||
var extensions = [];
|
||||
|
||||
PluginService.volumePlugins()
|
||||
.then(function success(data) {
|
||||
var volumePlugins = data;
|
||||
if (_.includes(volumePlugins, 'cio:latest')) {
|
||||
extensions.push('storidge');
|
||||
}
|
||||
deferred.resolve(extensions);
|
||||
})
|
||||
.catch(function error(err) {
|
||||
deferred.reject({ msg: 'Unable to retrieve extensions', err: err });
|
||||
});
|
||||
|
||||
return deferred.promise;
|
||||
};
|
||||
|
||||
return service;
|
||||
}]);
|
|
@ -41,6 +41,15 @@ angular.module('portainer.services')
|
|||
getPaginationLimit: function(key) {
|
||||
return localStorageService.cookie.get('pagination_' + key);
|
||||
},
|
||||
storeStoridgeAPIURL: function(url) {
|
||||
localStorageService.set('STORIDGE_API_URL', url);
|
||||
},
|
||||
getStoridgeAPIURL: function() {
|
||||
return localStorageService.get('STORIDGE_API_URL');
|
||||
},
|
||||
clearStoridgeAPIURL: function() {
|
||||
return localStorageService.remove('STORIDGE_API_URL');
|
||||
},
|
||||
getDataTableOrder: function(key) {
|
||||
return localStorageService.get('datatable_order_' + key);
|
||||
},
|
||||
|
|
|
@ -1,5 +1,5 @@
|
|||
angular.module('portainer.services')
|
||||
.factory('StateManager', ['$q', 'SystemService', 'InfoHelper', 'LocalStorage', 'SettingsService', 'StatusService', 'APPLICATION_CACHE_VALIDITY', function StateManagerFactory($q, SystemService, InfoHelper, LocalStorage, SettingsService, StatusService, APPLICATION_CACHE_VALIDITY) {
|
||||
.factory('StateManager', ['$q', 'SystemService', 'InfoHelper', 'LocalStorage', 'SettingsService', 'StatusService', 'ExtensionManager', 'APPLICATION_CACHE_VALIDITY', function StateManagerFactory($q, SystemService, InfoHelper, LocalStorage, SettingsService, StatusService, ExtensionManager, APPLICATION_CACHE_VALIDITY) {
|
||||
'use strict';
|
||||
|
||||
var manager = {};
|
||||
|
@ -34,7 +34,6 @@ angular.module('portainer.services')
|
|||
LocalStorage.storeApplicationState(state.application);
|
||||
};
|
||||
|
||||
|
||||
function assignStateFromStatusAndSettings(status, settings) {
|
||||
state.application.authentication = status.Authentication;
|
||||
state.application.analytics = status.Analytics;
|
||||
|
@ -115,13 +114,15 @@ angular.module('portainer.services')
|
|||
}
|
||||
$q.all({
|
||||
info: SystemService.info(),
|
||||
version: SystemService.version()
|
||||
version: SystemService.version(),
|
||||
extensions: ExtensionManager.extensions()
|
||||
})
|
||||
.then(function success(data) {
|
||||
var endpointMode = InfoHelper.determineEndpointMode(data.info);
|
||||
var endpointAPIVersion = parseFloat(data.version.ApiVersion);
|
||||
state.endpoint.mode = endpointMode;
|
||||
state.endpoint.apiVersion = endpointAPIVersion;
|
||||
state.endpoint.extensions = data.extensions;
|
||||
LocalStorage.storeEndpointState(state.endpoint);
|
||||
deferred.resolve();
|
||||
})
|
||||
|
|
|
@ -79,6 +79,10 @@ a[ng-click]{
|
|||
margin-right: 5px;
|
||||
}
|
||||
|
||||
.space-left {
|
||||
margin-left: 5px;
|
||||
}
|
||||
|
||||
.tooltip.portainer-tooltip .tooltip-inner {
|
||||
font-family: Montserrat;
|
||||
background-color: #ffffff;
|
||||
|
@ -377,7 +381,27 @@ ul.sidebar .sidebar-list .sidebar-sublist a {
|
|||
text-indent: 35px;
|
||||
font-size: 12px;
|
||||
color: #b2bfdc;
|
||||
line-height: 40px;
|
||||
line-height: 36px;
|
||||
}
|
||||
|
||||
ul.sidebar .sidebar-title {
|
||||
line-height: 36px;
|
||||
}
|
||||
ul.sidebar .sidebar-title .form-control {
|
||||
height: 36px;
|
||||
padding: 6px 12px;
|
||||
}
|
||||
|
||||
ul.sidebar .sidebar-list {
|
||||
height: 36px;
|
||||
}
|
||||
|
||||
ul.sidebar .sidebar-list a, ul.sidebar .sidebar-list .sidebar-sublist a {
|
||||
line-height: 36px;
|
||||
}
|
||||
|
||||
ul.sidebar .sidebar-list .menu-icon {
|
||||
line-height: 36px;
|
||||
}
|
||||
|
||||
ul.sidebar .sidebar-list .sidebar-sublist a.active {
|
||||
|
@ -386,27 +410,27 @@ ul.sidebar .sidebar-list .sidebar-sublist a.active {
|
|||
background: #2d3e63;
|
||||
}
|
||||
|
||||
@media (max-height: 683px) {
|
||||
@media (max-height: 785px) {
|
||||
ul.sidebar .sidebar-title {
|
||||
line-height: 28px;
|
||||
line-height: 26px;
|
||||
}
|
||||
ul.sidebar .sidebar-title .form-control {
|
||||
height: 28px;
|
||||
padding: 4px 8px;
|
||||
height: 26px;
|
||||
padding: 3px 6px;
|
||||
}
|
||||
ul.sidebar .sidebar-list {
|
||||
height: 28px;
|
||||
height: 26px;
|
||||
}
|
||||
ul.sidebar .sidebar-list a, ul.sidebar .sidebar-list .sidebar-sublist a {
|
||||
font-size: 12px;
|
||||
line-height: 28px;
|
||||
line-height: 26px;
|
||||
}
|
||||
ul.sidebar .sidebar-list .menu-icon {
|
||||
line-height: 28px;
|
||||
line-height: 26px;
|
||||
}
|
||||
}
|
||||
|
||||
@media(min-height: 684px) and (max-height: 850px) {
|
||||
@media(min-height: 786px) and (max-height: 924px) {
|
||||
ul.sidebar .sidebar-title {
|
||||
line-height: 30px;
|
||||
}
|
||||
|
|
|
@ -116,7 +116,7 @@ gruntfile_cfg.src = {
|
|||
js: ['app/**/__module.js', 'app/**/*.js', '!app/**/*.spec.js'],
|
||||
jsTpl: ['<%= distdir %>/templates/**/*.js'],
|
||||
html: ['index.html'],
|
||||
tpl: ['app/components/**/*.html', 'app/directives/**/*.html'],
|
||||
tpl: ['app/components/**/*.html', 'app/directives/**/*.html', 'app/extensions/**/*.html'],
|
||||
css: ['assets/css/app.css', 'app/**/*.css']
|
||||
};
|
||||
|
||||
|
|
47
package.json
47
package.json
|
@ -23,37 +23,38 @@
|
|||
"node": ">= 0.8.4"
|
||||
},
|
||||
"dependencies": {
|
||||
"@uirouter/angularjs": "~1.0.6",
|
||||
"angular": "~1.5.0",
|
||||
"angular-cookies": "~1.5.0",
|
||||
"angular-ui-bootstrap": "~2.5.0",
|
||||
"angular-sanitize": "~1.5.0",
|
||||
"angular-google-analytics": "github:revolunet/angular-google-analytics#~1.1.9",
|
||||
"angular-json-tree": "1.0.1",
|
||||
"angular-jwt": "~0.1.8",
|
||||
"angular-loading-bar": "~0.9.0",
|
||||
"angular-local-storage": "~0.5.2",
|
||||
"angular-messages": "~1.5.0",
|
||||
"angular-mocks": "~1.5.0",
|
||||
"angular-resource": "~1.5.0",
|
||||
"ui-select": "~0.19.6",
|
||||
"angular-sanitize": "~1.5.0",
|
||||
"angular-ui-bootstrap": "~2.5.0",
|
||||
"angular-utils-pagination": "~0.11.1",
|
||||
"angular-local-storage": "~0.5.2",
|
||||
"angular-jwt": "~0.1.8",
|
||||
"angular-json-tree": "1.0.1",
|
||||
"angular-google-analytics": "github:revolunet/angular-google-analytics#~1.1.9",
|
||||
"bootstrap": "~3.3.6",
|
||||
"filesize": "~3.3.0",
|
||||
"jquery": "1.11.1",
|
||||
"lodash": "4.12.0",
|
||||
"rdash-ui": "1.0.*",
|
||||
"moment": "~2.14.1",
|
||||
"font-awesome": "~4.7.0",
|
||||
"ng-file-upload": "~12.2.13",
|
||||
"splitargs": "github:deviantony/splitargs#~0.2.0",
|
||||
"bootbox": "^4.4.0",
|
||||
"isteven-angular-multiselect": "~4.0.0",
|
||||
"toastr": "github:CodeSeven/toastr#~2.1.3",
|
||||
"xterm": "~2.8.1",
|
||||
"chart.js": "~2.6.0",
|
||||
"angularjs-slider": "^6.4.0",
|
||||
"@uirouter/angularjs": "~1.0.6",
|
||||
"bootbox": "^4.4.0",
|
||||
"bootstrap": "~3.3.6",
|
||||
"chart.js": "~2.6.0",
|
||||
"codemirror": "~5.30.0",
|
||||
"filesize": "~3.3.0",
|
||||
"font-awesome": "~4.7.0",
|
||||
"isteven-angular-multiselect": "~4.0.0",
|
||||
"jquery": "1.11.1",
|
||||
"js-yaml": "~3.10.0",
|
||||
"angular-loading-bar": "~0.9.0"
|
||||
"lodash": "4.12.0",
|
||||
"moment": "~2.14.1",
|
||||
"ng-file-upload": "~12.2.13",
|
||||
"rdash-ui": "1.0.*",
|
||||
"splitargs": "github:deviantony/splitargs#~0.2.0",
|
||||
"toastr": "github:CodeSeven/toastr#~2.1.3",
|
||||
"ui-select": "~0.19.6",
|
||||
"xterm": "~2.8.1"
|
||||
},
|
||||
"devDependencies": {
|
||||
"autoprefixer": "^7.1.1",
|
||||
|
|
|
@ -69,6 +69,7 @@ angular:
|
|||
- node_modules/angular-google-analytics/dist/angular-google-analytics.js
|
||||
- node_modules/angular-jwt/dist/angular-jwt.js
|
||||
- node_modules/angular-local-storage/dist/angular-local-storage.js
|
||||
- node_modules/angular-messages/angular-messages.js
|
||||
- node_modules/angular-resource/angular-resource.js
|
||||
- node_modules/angular-sanitize/angular-sanitize.js
|
||||
- node_modules/ui-select/dist/select.js
|
||||
|
@ -86,6 +87,7 @@ angular:
|
|||
- node_modules/angular-google-analytics/dist/angular-google-analytics.min.js
|
||||
- node_modules/angular-jwt/dist/angular-jwt.min.js
|
||||
- node_modules/angular-local-storage/dist/angular-local-storage.min.js
|
||||
- node_modules/angular-messages/angular-messages.min.js
|
||||
- node_modules/angular-resource/angular-resource.min.js
|
||||
- node_modules/angular-sanitize/angular-sanitize.min.js
|
||||
- node_modules/ui-select/dist/select.min.js
|
||||
|
|
|
@ -113,6 +113,10 @@ angular-local-storage@~0.5.2:
|
|||
version "0.5.2"
|
||||
resolved "https://registry.yarnpkg.com/angular-local-storage/-/angular-local-storage-0.5.2.tgz#7079beb0aa5ca91386d223125efefd13ca0ecd0c"
|
||||
|
||||
angular-messages@~1.5.0:
|
||||
version "1.5.11"
|
||||
resolved "https://registry.yarnpkg.com/angular-messages/-/angular-messages-1.5.11.tgz#ea99f0163594fcb0a2db701b3038339250decc90"
|
||||
|
||||
angular-mocks@~1.5.0:
|
||||
version "1.5.11"
|
||||
resolved "https://registry.yarnpkg.com/angular-mocks/-/angular-mocks-1.5.11.tgz#a0e1dd0ea55fd77ee7a757d75536c5e964c86f81"
|
||||
|
@ -2363,7 +2367,7 @@ istanbul@~0.1.40:
|
|||
wordwrap "0.0.x"
|
||||
|
||||
isteven-angular-multiselect@~4.0.0:
|
||||
version v4.0.0
|
||||
version "4.0.0"
|
||||
resolved "https://registry.yarnpkg.com/isteven-angular-multiselect/-/isteven-angular-multiselect-4.0.0.tgz#70276da5ff3bc4d9a0887dc585ee26a1a26a8ed6"
|
||||
|
||||
jquery@1.11.1:
|
||||
|
|
Loading…
Reference in New Issue