Compare commits
5 Commits
fix/EE-283
...
feat/GH/44
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
8d67ba424b | ||
|
|
c76ea0f635 | ||
|
|
2873980186 | ||
|
|
a59a7a2bec | ||
|
|
5c63e350d4 |
@@ -247,6 +247,16 @@ angular.module('portainer.kubernetes', ['portainer.app']).config([
|
||||
},
|
||||
};
|
||||
|
||||
const volumeCreation = {
|
||||
name: 'kubernetes.volumes.new',
|
||||
url: '/new',
|
||||
views: {
|
||||
'content@': {
|
||||
component: 'kubernetesCreateVolumeView',
|
||||
},
|
||||
},
|
||||
};
|
||||
|
||||
$stateRegistryProvider.register(kubernetes);
|
||||
$stateRegistryProvider.register(applications);
|
||||
$stateRegistryProvider.register(applicationCreation);
|
||||
@@ -270,5 +280,6 @@ angular.module('portainer.kubernetes', ['portainer.app']).config([
|
||||
$stateRegistryProvider.register(resourcePoolAccess);
|
||||
$stateRegistryProvider.register(volumes);
|
||||
$stateRegistryProvider.register(volume);
|
||||
$stateRegistryProvider.register(volumeCreation);
|
||||
},
|
||||
]);
|
||||
|
||||
@@ -54,6 +54,9 @@
|
||||
<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-alt space-right" aria-hidden="true"></i>Remove
|
||||
</button>
|
||||
<button type="button" class="btn btn-sm btn-primary" ui-sref="kubernetes.volumes.new" ng-if="$ctrl.storageClassAvailable()">
|
||||
<i class="fa fa-plus space-right" aria-hidden="true"></i>Add volume
|
||||
</button>
|
||||
</div>
|
||||
<div class="searchBar">
|
||||
<i class="fa fa-search searchIcon" aria-hidden="true"></i>
|
||||
|
||||
@@ -4,18 +4,23 @@ import KubernetesVolumeHelper from 'Kubernetes/helpers/volumeHelper';
|
||||
// TODO: review - refactor to use `extends GenericDatatableController`
|
||||
class KubernetesVolumesDatatableController {
|
||||
/* @ngInject */
|
||||
constructor($async, $controller, Authentication, KubernetesNamespaceHelper, DatatableService) {
|
||||
constructor($async, $controller, Authentication, KubernetesNamespaceHelper, DatatableService, EndpointProvider) {
|
||||
this.$async = $async;
|
||||
this.$controller = $controller;
|
||||
this.Authentication = Authentication;
|
||||
this.KubernetesNamespaceHelper = KubernetesNamespaceHelper;
|
||||
this.DatatableService = DatatableService;
|
||||
this.EndpointProvider = EndpointProvider;
|
||||
|
||||
this.onInit = this.onInit.bind(this);
|
||||
this.allowSelection = this.allowSelection.bind(this);
|
||||
this.isDisplayed = this.isDisplayed.bind(this);
|
||||
}
|
||||
|
||||
storageClassAvailable() {
|
||||
return this.storageClasses && this.storageClasses.length > 0;
|
||||
}
|
||||
|
||||
onSettingsShowSystemChange() {
|
||||
this.DatatableService.setDataTableSettings(this.tableKey, this.settings);
|
||||
}
|
||||
@@ -49,6 +54,8 @@ class KubernetesVolumesDatatableController {
|
||||
this.prepareTableFromDataset();
|
||||
this.isAdmin = this.Authentication.isAdmin();
|
||||
this.settings.showSystem = false;
|
||||
const endpoint = this.EndpointProvider.currentEndpoint();
|
||||
this.storageClasses = endpoint.Kubernetes.Configuration.StorageClasses;
|
||||
|
||||
this.state.orderBy = this.orderBy;
|
||||
var storedOrder = this.DatatableService.getDataTableOrder(this.tableKey);
|
||||
|
||||
21
app/kubernetes/models/volume/formValues.js
Normal file
21
app/kubernetes/models/volume/formValues.js
Normal file
@@ -0,0 +1,21 @@
|
||||
/**
|
||||
* KubernetesApplicationFormValues Model
|
||||
*/
|
||||
const _KubernetesVolumeFormValues = Object.freeze({
|
||||
Name: '',
|
||||
ResourcePool: {},
|
||||
StorageClass: {},
|
||||
Size: '',
|
||||
SizeUnit: 'GB',
|
||||
isNFSVolume: false,
|
||||
NFSAddress: '',
|
||||
NFSVersion: '',
|
||||
NFSMountPoint: '',
|
||||
NFSOptions: '',
|
||||
});
|
||||
|
||||
export class KubernetesVolumeFormValues {
|
||||
constructor() {
|
||||
Object.assign(this, JSON.parse(JSON.stringify(_KubernetesVolumeFormValues)));
|
||||
}
|
||||
}
|
||||
@@ -23,6 +23,27 @@ export class KubernetesPersistentVolumeClaim {
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* KubernetesPV Model
|
||||
*/
|
||||
const _KubernetesPV = Object.freeze({
|
||||
Name: '',
|
||||
ResourcePool: {}, // KubernetesResourcePool
|
||||
StorageClass: {}, // KubernetesStorageClass
|
||||
Size: '',
|
||||
isNFSVolume: false,
|
||||
NFSAddress: '',
|
||||
NFSVersion: '',
|
||||
NFSMountPoint: '',
|
||||
NFSOptions: '',
|
||||
});
|
||||
|
||||
export class KubernetesPV {
|
||||
constructor() {
|
||||
Object.assign(this, JSON.parse(JSON.stringify(_KubernetesPV)));
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* KubernetesVolume Model (Composite)
|
||||
*/
|
||||
|
||||
58
app/kubernetes/pv/converter.js
Normal file
58
app/kubernetes/pv/converter.js
Normal file
@@ -0,0 +1,58 @@
|
||||
import _ from 'lodash-es';
|
||||
import { KubernetesPV } from 'Kubernetes/models/volume/models';
|
||||
import { KubernetesPVCreatePayload } from 'Kubernetes/pv/payloads';
|
||||
class KubernetesPVConverter {
|
||||
static apiToPV(data) {
|
||||
const res = new KubernetesPV();
|
||||
res.Name = data.metadata.name;
|
||||
res.ResourcePool = {};
|
||||
res.StorageClass = {};
|
||||
res.Size = '';
|
||||
res.isNFSVolume = false;
|
||||
res.NFSAddress = '';
|
||||
res.NFSVersion = '';
|
||||
res.NFSMountPoint = '';
|
||||
res.NFSOptions = '';
|
||||
return res;
|
||||
}
|
||||
|
||||
static formValuesToPV(formValues) {
|
||||
const pv = new KubernetesPV();
|
||||
pv.Name = formValues.Name;
|
||||
pv.ResourcePool = formValues.ResourcePool;
|
||||
pv.StorageClass = formValues.StorageClass;
|
||||
pv.Size = formValues.Size + formValues.SizeUnit;
|
||||
if (formValues.isNFSVolume) {
|
||||
pv.isNFSVolume = formValues.isNFSVolume;
|
||||
pv.NFSAddress = formValues.NFSAddress;
|
||||
pv.NFSVersion = formValues.NFSVersion;
|
||||
pv.NFSMountPoint = formValues.NFSMountPoint;
|
||||
pv.NFSOptions = _.split(formValues.NFSOptions, ',');
|
||||
} else {
|
||||
pv.isNFSVolume = formValues.isNFSVolume;
|
||||
}
|
||||
return pv;
|
||||
}
|
||||
|
||||
static createPayload(pv) {
|
||||
const res = new KubernetesPVCreatePayload();
|
||||
res.metadata.name = pv.Name;
|
||||
res.spec.storageClassName = pv.StorageClass.Name;
|
||||
res.spec.capacity = {
|
||||
storage: pv.Size.replace('B', 'i'),
|
||||
};
|
||||
if (pv.isNFSVolume) {
|
||||
res.spec.nfs = {
|
||||
path: pv.NFSMountPoint,
|
||||
server: pv.NFSAddress,
|
||||
};
|
||||
res.spec.mountOptions = pv.NFSOptions;
|
||||
res.spec.mountOptions.push('nfsvers=' + pv.NFSVersion);
|
||||
} else {
|
||||
// res.spec.
|
||||
}
|
||||
return res;
|
||||
}
|
||||
}
|
||||
|
||||
export default KubernetesPVConverter;
|
||||
19
app/kubernetes/pv/payloads.js
Normal file
19
app/kubernetes/pv/payloads.js
Normal file
@@ -0,0 +1,19 @@
|
||||
import { KubernetesCommonMetadataPayload } from 'Kubernetes/models/common/payloads';
|
||||
|
||||
/**
|
||||
* KubernetesPVCreatePayload Model
|
||||
*/
|
||||
const _KubernetesPVCreatePayload = Object.freeze({
|
||||
metadata: new KubernetesCommonMetadataPayload(),
|
||||
spec: {
|
||||
accessModes: ['ReadWriteOnce'],
|
||||
mountOptions: [],
|
||||
storageClassName: '',
|
||||
},
|
||||
});
|
||||
|
||||
export class KubernetesPVCreatePayload {
|
||||
constructor() {
|
||||
Object.assign(this, JSON.parse(JSON.stringify(_KubernetesPVCreatePayload)));
|
||||
}
|
||||
}
|
||||
22
app/kubernetes/pv/rest.js
Normal file
22
app/kubernetes/pv/rest.js
Normal file
@@ -0,0 +1,22 @@
|
||||
angular.module('portainer.kubernetes').factory('KubernetesPV', [
|
||||
'$resource',
|
||||
'API_ENDPOINT_ENDPOINTS',
|
||||
'EndpointProvider',
|
||||
function KubernetesPVFactory($resource, API_ENDPOINT_ENDPOINTS, EndpointProvider) {
|
||||
'use strict';
|
||||
return function (namespace) {
|
||||
const url = API_ENDPOINT_ENDPOINTS + '/:endpointId/kubernetes/api/v1' + (namespace ? '/namespaces/:namespace' : '') + '/persistentvolumes/:id/:action';
|
||||
return $resource(
|
||||
url,
|
||||
{
|
||||
endpointId: EndpointProvider.endpointID,
|
||||
namespace: namespace,
|
||||
},
|
||||
{
|
||||
get: { method: 'GET' },
|
||||
create: { method: 'POST' },
|
||||
}
|
||||
);
|
||||
};
|
||||
},
|
||||
]);
|
||||
70
app/kubernetes/pv/service.js
Normal file
70
app/kubernetes/pv/service.js
Normal file
@@ -0,0 +1,70 @@
|
||||
import angular from 'angular';
|
||||
import _ from 'lodash-es';
|
||||
import PortainerError from 'Portainer/error';
|
||||
import { KubernetesCommonParams } from 'Kubernetes/models/common/params';
|
||||
import KubernetesPVConverter from 'Kubernetes/pv/converter';
|
||||
|
||||
class KubernetesPVService {
|
||||
/* @ngInject */
|
||||
constructor($async, KubernetesPV) {
|
||||
this.$async = $async;
|
||||
this.KubernetesPV = KubernetesPV;
|
||||
|
||||
this.getAsync = this.getAsync.bind(this);
|
||||
this.getAllAsync = this.getAllAsync.bind(this);
|
||||
this.createAsync = this.createAsync.bind(this);
|
||||
}
|
||||
|
||||
/**
|
||||
* GET
|
||||
*/
|
||||
async getAsync(namespace, name) {
|
||||
try {
|
||||
const params = new KubernetesCommonParams();
|
||||
params.id = name;
|
||||
const data = await this.KubernetesPV(namespace).get(params).$promise;
|
||||
return KubernetesPVConverter.apiToPV(data);
|
||||
} catch (err) {
|
||||
throw new PortainerError('Unable to retrieve persistent volume', err);
|
||||
}
|
||||
}
|
||||
|
||||
async getAllAsync(namespace) {
|
||||
try {
|
||||
// convert from apiPayload to KubernetesPV
|
||||
const data = await this.KubernetesPV(namespace).get().$promise;
|
||||
return _.map(data.items, (item) => KubernetesPVConverter.apiToPV(item));
|
||||
} catch (err) {
|
||||
throw new PortainerError('Unable to retrieve persistent volume', err);
|
||||
}
|
||||
}
|
||||
|
||||
get(namespace, name) {
|
||||
if (name) {
|
||||
return this.$async(this.getAsync, namespace, name);
|
||||
}
|
||||
return this.$async(this.getAllAsync, namespace);
|
||||
}
|
||||
|
||||
/**
|
||||
* CREATE
|
||||
*/
|
||||
|
||||
async createAsync(formValues) {
|
||||
try {
|
||||
const pv = KubernetesPVConverter.formValuesToPV(formValues);
|
||||
const payload = KubernetesPVConverter.createPayload(pv);
|
||||
const data = await this.KubernetesPV().create(payload).$promise;
|
||||
return data;
|
||||
} catch (err) {
|
||||
throw new PortainerError('Unable to create persistent volume', err);
|
||||
}
|
||||
}
|
||||
|
||||
create(pv) {
|
||||
return this.$async(this.createAsync, pv);
|
||||
}
|
||||
}
|
||||
|
||||
export default KubernetesPVService;
|
||||
angular.module('portainer.kubernetes').service('KubernetesPVService', KubernetesPVService);
|
||||
@@ -13,6 +13,7 @@ class KubernetesVolumeService {
|
||||
|
||||
this.getAsync = this.getAsync.bind(this);
|
||||
this.getAllAsync = this.getAllAsync.bind(this);
|
||||
this.createAsync = this.createAsync.bind(this);
|
||||
this.deleteAsync = this.deleteAsync.bind(this);
|
||||
}
|
||||
|
||||
@@ -50,6 +51,22 @@ class KubernetesVolumeService {
|
||||
return this.$async(this.getAllAsync, namespace);
|
||||
}
|
||||
|
||||
/**
|
||||
* CREATE
|
||||
*/
|
||||
|
||||
async createAsync(volume) {
|
||||
try {
|
||||
await this.KubernetesPersistentVolumeClaimService.create(volume);
|
||||
} catch (err) {
|
||||
throw err;
|
||||
}
|
||||
}
|
||||
|
||||
create(volume) {
|
||||
return this.$async(this.createAsync, volume);
|
||||
}
|
||||
|
||||
/**
|
||||
* DELETE
|
||||
*/
|
||||
|
||||
220
app/kubernetes/views/volumes/create/createVolume.html
Normal file
220
app/kubernetes/views/volumes/create/createVolume.html
Normal file
@@ -0,0 +1,220 @@
|
||||
<kubernetes-view-header ng-if="!ctrl.state.isEdit" title="Create volume" state="kubernetes.volumes.new" view-ready="ctrl.state.viewReady">
|
||||
<a ui-sref="kubernetes.volume">Volumes</a> > Create a volume
|
||||
</kubernetes-view-header>
|
||||
|
||||
<kubernetes-view-loading view-ready="ctrl.state.viewReady"></kubernetes-view-loading>
|
||||
|
||||
<div ng-if="ctrl.state.viewReady">
|
||||
<div class="row">
|
||||
<div class="col-xs-12">
|
||||
<rd-widget>
|
||||
<rd-widget-body>
|
||||
<form class="form-horizontal" name="kubernetesVolumeCreationForm" autocomplete="off">
|
||||
<!-- #region NAME FIELD -->
|
||||
<div class="form-group">
|
||||
<label for="volume_name" class="col-sm-1 control-label text-left">Name</label>
|
||||
<div class="col-sm-11">
|
||||
<input
|
||||
type="text"
|
||||
class="form-control"
|
||||
name="volume_name"
|
||||
ng-model="ctrl.formValues.Name"
|
||||
ng-change="ctrl.onChangeName()"
|
||||
placeholder="my-volume"
|
||||
ng-pattern="/^[a-z]([-a-z0-9]*[a-z0-9])?$/"
|
||||
auto-focus
|
||||
required
|
||||
/>
|
||||
</div>
|
||||
</div>
|
||||
<div class="form-group" ng-show="kubernetesVolumeCreationForm.volume_name.$invalid || ctrl.state.alreadyExists">
|
||||
<div class="col-sm-12 small text-warning">
|
||||
<div ng-messages="kubernetesVolumeCreationForm.volume_name.$error">
|
||||
<p ng-message="required"><i class="fa fa-exclamation-triangle" aria-hidden="true"></i> This field is required.</p>
|
||||
<p ng-message="pattern"
|
||||
><i class="fa fa-exclamation-triangle" aria-hidden="true"></i> This field must consist of lower case alphanumeric characters or '-', start with an alphabetic
|
||||
character, and end with an alphanumeric character (e.g. 'my-name', or 'abc-123').</p
|
||||
>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
<!-- #endregion -->
|
||||
|
||||
<!-- #region RESOURCE POOL -->
|
||||
<div class="col-sm-12 form-section-title">
|
||||
Resource pool
|
||||
</div>
|
||||
<div class="form-group">
|
||||
<label for="resource-pool-selector" class="col-sm-1 control-label text-left">Resource pool</label>
|
||||
<div class="col-sm-11">
|
||||
<select
|
||||
class="form-control"
|
||||
id="resource-pool-selector"
|
||||
ng-model="ctrl.formValues.ResourcePool"
|
||||
ng-options="resourcePool.Namespace.Name for resourcePool in ctrl.resourcePools"
|
||||
ng-change="ctrl.onResourcePoolSelectionChange()"
|
||||
ng-disabled="ctrl.state.isEdit"
|
||||
required
|
||||
></select>
|
||||
</div>
|
||||
</div>
|
||||
<!-- #endregion -->
|
||||
|
||||
<!-- #region VOLUME CONFIGURATION -->
|
||||
<div class="col-sm-12 form-section-title">
|
||||
Volume configuration
|
||||
</div>
|
||||
<div class="form-group" ng-if="!ctrl.storageClasses">
|
||||
<div class="col-sm-12 small text-muted">
|
||||
<p ng-if="!ctrl.isAdmin"
|
||||
><i class="fa fa-info-circle orange-icon" aria-hidden="true" style="margin-right: 2px;"></i>No storage option is available to persist data, contact your
|
||||
administrator to enable a storage option.</p
|
||||
>
|
||||
<p ng-if="ctrl.isAdmin"
|
||||
><i class="fa fa-info-circle orange-icon" aria-hidden="true" style="margin-right: 2px;"></i>No storage option is available to persist data, please head over the
|
||||
cluster <a ui-sref="portainer.endpoints.endpoint.kubernetesConfig({id: ctrl.endpoint.Id})">setup view</a> to configure a storage option.</p
|
||||
>
|
||||
</div>
|
||||
</div>
|
||||
<div ng-if="ctrl.storageClasses">
|
||||
<div class="form-group">
|
||||
<div class="col-sm-12">
|
||||
<label for="storage-selector" class="col-sm-1 control-label text-left" style="padding-left: 0px;">Storage</label>
|
||||
<div class="col-sm-5" style="padding-left: 0px;">
|
||||
<select
|
||||
class="form-control"
|
||||
ng-if="ctrl.hasMultipleStorageClassesAvailable()"
|
||||
id="storage-selector"
|
||||
ng-model="ctrl.formValues.StorageClass"
|
||||
ng-options="storageClass as storageClass.Name for storageClass in ctrl.storageClasses"
|
||||
required
|
||||
></select>
|
||||
<input ng-if="!ctrl.hasMultipleStorageClassesAvailable()" type="text" class="form-control" ng-model="ctrl.formValues.StorageClass.Name" disabled />
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
<div class="form-group">
|
||||
<div class="col-sm-12 form-inline">
|
||||
<label for="size" class="col-sm-1 control-label text-left" style="padding-left: 0px;">Size</label>
|
||||
<div class="col-sm-4" style="padding-left: 0px;">
|
||||
<input type="number" class="form-control" name="size" ng-model="ctrl.formValues.Size" placeholder="10" min="0" style="width: 100%;" required />
|
||||
</div>
|
||||
<div class="col-sm-1">
|
||||
<select
|
||||
class="form-control"
|
||||
ng-model="ctrl.formValues.SizeUnit"
|
||||
ng-options="unit for unit in ctrl.state.availableSizeUnits"
|
||||
ng-disabled="ctrl.isEditAndExistingPersistedFolder($index) || ctrl.formValues.Containers.length > 1"
|
||||
style="width: 100%;"
|
||||
></select>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
<!-- #endregion -->
|
||||
|
||||
<!-- #region NFS -->
|
||||
<div class="form-group" ng-if="!ctrl.storages">
|
||||
<label for="nfs" class="col-sm-1 control-label text-left">Use NFS volume</label>
|
||||
<label class="switch" style="margin-left: 20px;"> <input type="checkbox" ng-model="ctrl.formValues.isNFSVolume" /><i></i> </label>
|
||||
</div>
|
||||
<!-- #endregion -->
|
||||
|
||||
<!-- #region NFS-SETTINGS -->
|
||||
<div ng-if="ctrl.formValues.isNFSVolume">
|
||||
<div class="col-sm-12 form-section-title">
|
||||
NFS Settings
|
||||
</div>
|
||||
<div class="form-group">
|
||||
<label for="address" class="col-sm-1 control-label text-left">Address</label>
|
||||
<div class="col-sm-11">
|
||||
<input type="text" class="form-control" name="address" ng-model="ctrl.formValues.NFSAddress" placeholder="e.g. my.nfs-server.com OR xxx.xxx.xxx.xxx" required />
|
||||
</div>
|
||||
</div>
|
||||
<div class="form-group" ng-show="kubernetesVolumeCreationForm.address.$invalid">
|
||||
<div class="col-sm-12 small text-warning">
|
||||
<div ng-messages="kubernetesVolumeCreationForm.address.$error">
|
||||
<p ng-message="required"><i class="fa fa-exclamation-triangle" aria-hidden="true"></i> This field is required.</p>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div class="form-group">
|
||||
<label for="nfs_version" class="col-sm-1 control-label text-left">NFS Version</label>
|
||||
<div class="col-sm-11">
|
||||
<input type="text" class="form-control" name="nfs_version" ng-model="ctrl.formValues.NFSVersion" ng-change="ctrl.onChangeNFSVersion()" required />
|
||||
</div>
|
||||
</div>
|
||||
<div class="form-group" ng-show="kubernetesVolumeCreationForm.nfs_version.$invalid">
|
||||
<div class="col-sm-12 small text-warning">
|
||||
<div ng-messages="kubernetesVolumeCreationForm.nfs_version.$error">
|
||||
<p ng-message="required"><i class="fa fa-exclamation-triangle" aria-hidden="true"></i> This field is required.</p>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div class="form-group">
|
||||
<label for="mount_point" class="col-sm-1 control-label text-left">Mount point</label>
|
||||
<div class="col-sm-11">
|
||||
<input
|
||||
type="text"
|
||||
class="form-control"
|
||||
name="mount_point"
|
||||
ng-model="ctrl.formValues.NFSMountPoint"
|
||||
placeholder="e.g. /export/share, :/export/share, /share or :/share"
|
||||
required
|
||||
/>
|
||||
</div>
|
||||
</div>
|
||||
<div class="form-group" ng-show="kubernetesVolumeCreationForm.mount_point.$invalid">
|
||||
<div class="col-sm-12 small text-warning">
|
||||
<div ng-messages="kubernetesVolumeCreationForm.mount_point.$error">
|
||||
<p ng-message="required"><i class="fa fa-exclamation-triangle" aria-hidden="true"></i> This field is required.</p>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div class="form-group">
|
||||
<label for="nfs_options" class="col-sm-1 control-label text-left">
|
||||
Options
|
||||
<portainer-tooltip position="bottom" message="Comma separated list of options"></portainer-tooltip>
|
||||
</label>
|
||||
<div class="col-sm-11">
|
||||
<input type="text" class="form-control" name="nfs_options" ng-model="ctrl.formValues.NFSOptions" placeholder="e.g. rw, noatime, tcp..." required />
|
||||
</div>
|
||||
</div>
|
||||
<div class="form-group" ng-show="kubernetesVolumeCreationForm.nfs_options.$invalid">
|
||||
<div class="col-sm-12 small text-warning">
|
||||
<div ng-messages="kubernetesVolumeCreationForm.nfs_options.$error">
|
||||
<p ng-message="required"><i class="fa fa-exclamation-triangle" aria-hidden="true"></i> This field is required.</p>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
<!-- #endregion -->
|
||||
|
||||
<!-- #region 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-disabled="!kubernetesVolumeCreationForm.$valid || ctrl.isCreateButtonDisabled()"
|
||||
button-spinner="ctrl.state.actionInProgress"
|
||||
ng-click="ctrl.createVolume()"
|
||||
>
|
||||
<span ng-show="!ctrl.state.isEdit && !ctrl.state.actionInProgress">Create volume</span>
|
||||
<span ng-show="!ctrl.state.isEdit && ctrl.state.actionInProgress">Creation in progress...</span>
|
||||
</button>
|
||||
</div>
|
||||
</div>
|
||||
<!-- #endregion -->
|
||||
</form>
|
||||
</rd-widget-body>
|
||||
</rd-widget>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
8
app/kubernetes/views/volumes/create/createVolume.js
Normal file
8
app/kubernetes/views/volumes/create/createVolume.js
Normal file
@@ -0,0 +1,8 @@
|
||||
angular.module('portainer.kubernetes').component('kubernetesCreateVolumeView', {
|
||||
templateUrl: './createVolume.html',
|
||||
controller: 'KubernetesCreateVolumeController',
|
||||
controllerAs: 'ctrl',
|
||||
bindings: {
|
||||
$transition$: '<',
|
||||
},
|
||||
});
|
||||
114
app/kubernetes/views/volumes/create/createVolumeController.js
Normal file
114
app/kubernetes/views/volumes/create/createVolumeController.js
Normal file
@@ -0,0 +1,114 @@
|
||||
import angular from 'angular';
|
||||
import * as _ from 'lodash-es';
|
||||
import { KubernetesVolumeFormValues } from 'Kubernetes/models/volume/formValues';
|
||||
|
||||
class KubernetesCreateVolumeController {
|
||||
/* @ngInject */
|
||||
constructor(
|
||||
$async,
|
||||
$state,
|
||||
Notifications,
|
||||
EndpointProvider,
|
||||
KubernetesNamespaceHelper,
|
||||
KubernetesResourcePoolService,
|
||||
Authentication,
|
||||
KubernetesVolumeService,
|
||||
KubernetesPVService
|
||||
) {
|
||||
this.$async = $async;
|
||||
this.$state = $state;
|
||||
this.Notifications = Notifications;
|
||||
this.EndpointProvider = EndpointProvider;
|
||||
this.KubernetesNamespaceHelper = KubernetesNamespaceHelper;
|
||||
this.KubernetesResourcePoolService = KubernetesResourcePoolService;
|
||||
this.Authentication = Authentication;
|
||||
this.KubernetesVolumeService = KubernetesVolumeService;
|
||||
this.KubernetesPVService = KubernetesPVService;
|
||||
|
||||
this.onInit = this.onInit.bind(this);
|
||||
this.createVolumeAsync = this.createVolumeAsync.bind(this);
|
||||
}
|
||||
|
||||
onChangeName() {
|
||||
const existingVolume = _.find(this.volumes, { Name: this.formValues.Name });
|
||||
this.state.alreadyExists = (this.state.isEdit && existingVolume && this.application.Id !== existingVolume.Id) || (!this.state.isEdit && existingVolume);
|
||||
}
|
||||
|
||||
onResourcePoolSelectionChange() {
|
||||
this.resetFormValues();
|
||||
}
|
||||
|
||||
resetFormValues() {
|
||||
this.formValues = new KubernetesVolumeFormValues();
|
||||
|
||||
if (this.storageClasses && this.storageClasses.length > 0) {
|
||||
this.formValues.StorageClass = this.storageClasses[0];
|
||||
}
|
||||
|
||||
this.formValues.ResourcePool = this.resourcePools[0];
|
||||
this.formValues.SizeUnit = this.state.availableSizeUnits[0];
|
||||
}
|
||||
|
||||
hasMultipleStorageClassesAvailable() {
|
||||
return this.storageClasses && this.storageClasses.length > 1;
|
||||
}
|
||||
|
||||
isCreateButtonDisabled() {
|
||||
return !this.storageClasses || this.storageClasses.length === 0;
|
||||
}
|
||||
|
||||
async createVolumeAsync() {
|
||||
this.state.actionInProgress = true;
|
||||
try {
|
||||
await this.KubernetesPVService.create(this.formValues);
|
||||
this.Notifications.success('Volume successfully created', this.formValues.Name);
|
||||
this.$state.go('kubernetes.volumes');
|
||||
} catch (err) {
|
||||
this.Notifications.error('Failure', err, 'Unable to create volume');
|
||||
} finally {
|
||||
this.state.actionInProgress = false;
|
||||
}
|
||||
}
|
||||
|
||||
createVolume() {
|
||||
return this.$async(this.createVolumeAsync);
|
||||
}
|
||||
|
||||
async onInit() {
|
||||
try {
|
||||
this.state = {
|
||||
viewReady: false,
|
||||
availableSizeUnits: ['MB', 'GB', 'TB'],
|
||||
alreadyExists: false,
|
||||
actionInProgress: false,
|
||||
};
|
||||
const endpoint = this.EndpointProvider.currentEndpoint();
|
||||
this.endpoint = endpoint;
|
||||
this.isAdmin = this.Authentication.isAdmin();
|
||||
this.storageClasses = endpoint.Kubernetes.Configuration.StorageClasses;
|
||||
const resourcePools = await this.KubernetesResourcePoolService.get();
|
||||
this.resourcePools = _.filter(resourcePools, (resourcePool) => !this.KubernetesNamespaceHelper.isSystemNamespace(resourcePool.Namespace.Name));
|
||||
this.volumes = await this.KubernetesVolumeService.get();
|
||||
|
||||
this.formValues = new KubernetesVolumeFormValues();
|
||||
|
||||
if (this.storageClasses && this.storageClasses.length > 0) {
|
||||
this.formValues.StorageClass = this.storageClasses[0];
|
||||
}
|
||||
|
||||
this.formValues.ResourcePool = this.resourcePools[0];
|
||||
this.formValues.SizeUnit = this.state.availableSizeUnits[0];
|
||||
} catch (err) {
|
||||
this.Notifications.error('Failure', err, 'Unable to load view data');
|
||||
} finally {
|
||||
this.state.viewReady = true;
|
||||
}
|
||||
}
|
||||
|
||||
$onInit() {
|
||||
return this.$async(this.onInit);
|
||||
}
|
||||
}
|
||||
|
||||
export default KubernetesCreateVolumeController;
|
||||
angular.module('portainer.kubernetes').controller('KubernetesCreateVolumeController', KubernetesCreateVolumeController);
|
||||
Reference in New Issue
Block a user